Files
utun2/tools/chatgui/src/videoplayer_engine.h
T
evgeny 6542618927 chatgui: воспроизведение видео — FFmpeg-плеер + frameless окно
- videoplayer_engine: декод в worker-потоке (FFmpeg), A/V sync по аудио-курсору,
  seek с epoch-защитой от устаревших кадров
- videoplayer_window: окно без рамки, drag-перенос, dblclick/Alt+Enter fullscreen,
  Esc закрытие, timeline + play/pause/close, авто-скрытие контролов
- видео-баббл в списке: превью-кадр (jpg), play-оверлей, длительность, прогресс
- превью извлекается после скачивания (video_extract_thumbnail) в фоне
- mainwindow: синглтон-плеер, openVideoRequested → openVideo
- CMake: HAVE_FFMPEG для vibechat
2026-09-08 00:55:06 +03:00

93 lines
2.8 KiB
C++

#pragma once
#include <QObject>
#include <QImage>
#include <QString>
#include <QTimer>
#include <QElapsedTimer>
#include <QAtomicInt>
#include <QAtomicInteger>
#include <deque>
#include <vector>
#include <memory>
#include <utility>
#include <cstdint>
class QThread;
/* Плеер видео на FFmpeg (GUI-процесс). Декод в отдельном потоке,
* отрисовка кадров по мастер-часам (аудио-курсор или системный таймер). */
class VideoPlayerEngine : public QObject {
Q_OBJECT
public:
enum State { Stopped = 0, Playing = 1, Paused = 2, Finished = 3 };
explicit VideoPlayerEngine(QObject* parent = nullptr);
~VideoPlayerEngine() override;
void open(const QString& path);
void play();
void pause();
void togglePlayPause();
void seekMs(qint64 ms);
void stop();
void close();
State state() const { return m_state; }
qint64 durationMs() const { return m_durationMs; }
qint64 positionMs() const { return m_positionMs; }
bool hasAudio() const { return m_hasAudio; }
int videoWidth() const { return m_videoWidth; }
int videoHeight() const { return m_videoHeight; }
QString filePath() const { return m_path; }
QImage currentFrame() const { return m_renderFrame; }
signals:
void opened(int width, int height, qint64 durationMs, bool hasAudio);
void positionChanged(qint64 ms);
void stateChanged(int state);
void finished();
void error(const QString& msg);
void framePresented();
private slots:
void onWorkerOpened(int width, int height, qint64 durationMs, bool hasAudio);
void onAudioDecoded(std::shared_ptr<std::vector<int16_t>> pcm, int sampleRate, int channels, qint64 durationMs);
void onFrameDecoded(const QImage& image, qint64 ptsMs, int epoch);
void onWorkerError(const QString& msg);
void onWorkerFinished();
void presentTick();
private:
void setState(State s);
qint64 clockMs() const;
void clearPending();
QString m_path;
QThread* m_thread = nullptr;
class Worker* m_worker = nullptr;
State m_state = Stopped;
qint64 m_durationMs = 0;
qint64 m_positionMs = 0;
bool m_hasAudio = false;
int m_videoWidth = 0;
int m_videoHeight = 0;
/* управление worker'ом (атомики, доступны из обоих потоков) */
QAtomicInt m_abort{0};
QAtomicInt m_paused{0};
QAtomicInt m_seekReq{0};
QAtomicInteger<qint64> m_seekTargetMs{0};
QAtomicInt m_inFlight{0};
QAtomicInt m_epoch{0};
/* презентация (только GUI-поток) */
std::deque<std::pair<QImage, qint64>> m_pending;
QImage m_renderFrame;
QTimer m_presentTimer;
int m_audioSampleRate = 0;
QElapsedTimer m_elapsed;
qint64 m_elapsedBaseMs = 0;
};