--- /dev/null
+#include "mediaqtav.h"
+
+namespace {
+
+#if defined(Q_OS_ANDROID) || defined(Q_OS_MAC)
+const QtAV::VideoRendererId defaultRenderer = QtAV::VideoRendererId_OpenGLWidget;
+#else
+const QtAV::VideoRendererId defaultRenderer = QtAV::VideoRendererId_GLWidget2;
+#endif
+
+#ifndef MEDIA_AUDIOONLY
+QtAV::VideoRendererId rendererIdFor(const QString &name) {
+ static const struct {
+ const char *name;
+ QtAV::VideoRendererId id;
+ } renderers[] = {{"opengl", QtAV::VideoRendererId_OpenGLWidget},
+ {"gl", QtAV::VideoRendererId_GLWidget2},
+ {"d2d", QtAV::VideoRendererId_Direct2D},
+ {"gdi", QtAV::VideoRendererId_GDI},
+ {"xv", QtAV::VideoRendererId_XV},
+ {"x11", QtAV::VideoRendererId_X11},
+ {"qt", QtAV::VideoRendererId_Widget}};
+
+ for (int i = 0; renderers[i].name; ++i) {
+ if (name == QLatin1String(renderers[i].name)) return renderers[i].id;
+ }
+
+ return defaultRenderer;
+}
+#endif
+
+Media::State stateFor(QtAV::AVPlayer::State state) {
+ static const QMap<QtAV::AVPlayer::State, Media::State> map{
+ {QtAV::AVPlayer::StoppedState, Media::StoppedState},
+ {QtAV::AVPlayer::PlayingState, Media::PlayingState},
+ {QtAV::AVPlayer::PausedState, Media::PausedState}};
+ return map[state];
+}
+
+} // namespace
+
+MediaQtAV::MediaQtAV(QObject *parent)
+ : Media(parent), player1(nullptr), player2(nullptr), currentPlayer(nullptr) {
+#ifndef MEDIA_AUDIOONLY
+ rendererId = defaultRenderer;
+#endif
+}
+
+#ifndef MEDIA_AUDIOONLY
+void MediaQtAV::setRenderer(const QString &name) {
+ rendererId = rendererIdFor(name);
+}
+#endif
+
+void MediaQtAV::setAudioOnly(bool value) {
+ audioOnly = value;
+}
+
+void MediaQtAV::init() {
+#ifndef QT_NO_DEBUG_OUTPUT
+ QtAV::setLogLevel(QtAV::LogAll);
+#endif
+
+#ifndef MEDIA_AUDIOONLY
+ QtAV::Widgets::registerRenderers();
+#endif
+ player1 = createPlayer(audioOnly);
+ setCurrentPlayer(player1);
+}
+
+#ifndef MEDIA_AUDIOONLY
+QWidget *MediaQtAV::videoWidget() {
+ return currentPlayer->renderer()->widget();
+}
+#endif
+
+Media::State MediaQtAV::state() const {
+ if (currentPlayer->mediaStatus() == QtAV::LoadingMedia) return LoadingState;
+ if (currentPlayer->bufferProgress() > 0. && currentPlayer->bufferProgress() < 1.)
+ return BufferingState;
+ return stateFor(currentPlayer->state());
+}
+
+void MediaQtAV::play(const QString &file) {
+ if (currentPlayer->isPlaying()) {
+ smoothSourceChange(file, QString());
+ return;
+ }
+
+#ifndef MEDIA_AUDIOONLY
+ if (!currentPlayer->externalAudio().isEmpty()) currentPlayer->setExternalAudio(QString());
+#endif
+ currentPlayer->play(file);
+ aboutToFinishEmitted = false;
+ lastErrorString.clear();
+ clearQueue();
+}
+
+#ifndef MEDIA_AUDIOONLY
+void MediaQtAV::playSeparateAudioAndVideo(const QString &video, const QString &audio) {
+ if (currentPlayer->isPlaying()) {
+ smoothSourceChange(video, audio);
+ return;
+ }
+ currentPlayer->stop();
+ currentPlayer->setExternalAudio(audio);
+ currentPlayer->play(video);
+ aboutToFinishEmitted = false;
+ lastErrorString.clear();
+ clearQueue();
+}
+
+void MediaQtAV::snapshot() {
+ auto videoCapture = currentPlayer->videoCapture();
+ connect(videoCapture, &QtAV::VideoCapture::imageCaptured, this, &Media::snapshotReady);
+ connect(videoCapture, &QtAV::VideoCapture::failed, this,
+ [this] { emit snapshotReady(QImage()); });
+ videoCapture->capture();
+}
+#endif
+
+void MediaQtAV::play() {
+ if (currentPlayer->isPaused())
+ currentPlayer->togglePause();
+ else
+ currentPlayer->play();
+}
+
+QString MediaQtAV::file() const {
+ return currentPlayer->file();
+}
+
+void MediaQtAV::setBufferMilliseconds(qint64 value) {
+ currentPlayer->setBufferValue(value);
+}
+
+void MediaQtAV::setUserAgent(const QString &value) {
+ qDebug() << "Setting user agent to" << value;
+ auto options = currentPlayer->optionsForFormat();
+ options.insert(QStringLiteral("user_agent"), value);
+ currentPlayer->setOptionsForFormat(options);
+}
+
+void MediaQtAV::enqueue(const QString &file) {
+ queue << file;
+ if (queue.size() == 1) {
+ qDebug() << "Preloading" << file;
+ auto nextPlayer = player1;
+ if (currentPlayer == player1) {
+ if (player2 == nullptr) player2 = createPlayer(audioOnly);
+ nextPlayer = player2;
+ }
+ nextPlayer->setFile(file);
+ nextPlayer->load();
+ }
+}
+
+void MediaQtAV::clearQueue() {
+ queue.clear();
+}
+
+bool MediaQtAV::hasQueue() const {
+ return !queue.isEmpty();
+}
+
+qint64 MediaQtAV::position() const {
+ return currentPlayer->position();
+}
+
+qint64 MediaQtAV::duration() const {
+ return currentPlayer->duration();
+}
+
+qint64 MediaQtAV::remainingTime() const {
+ return currentPlayer->duration() - currentPlayer->position();
+}
+
+qreal MediaQtAV::volume() const {
+ return currentPlayer->audio()->volume();
+}
+
+void MediaQtAV::setVolume(qreal value) {
+ auto audio = currentPlayer->audio();
+ if (!audio->isOpen()) audio->open();
+ audio->setVolume(value);
+}
+
+bool MediaQtAV::volumeMuted() const {
+ return currentPlayer->audio()->isMute();
+}
+
+void MediaQtAV::setVolumeMuted(bool value) {
+ currentPlayer->audio()->setMute(value);
+}
+
+QString MediaQtAV::errorString() const {
+ return lastErrorString;
+}
+
+void MediaQtAV::checkAboutToFinish(qint64 position) {
+ if (!aboutToFinishEmitted && currentPlayer->isPlaying() &&
+ duration() - position < currentPlayer->bufferValue()) {
+ aboutToFinishEmitted = true;
+ emit aboutToFinish();
+ }
+}
+
+void MediaQtAV::onMediaStatusChange(QtAV::MediaStatus status) {
+ qDebug() << QVariant(status).toString();
+
+ switch (status) {
+ case QtAV::LoadingMedia:
+ emit stateChanged(LoadingState);
+ break;
+ case QtAV::BufferedMedia:
+ if (currentPlayer->state() == QtAV::AVPlayer::PlayingState) emit stateChanged(PlayingState);
+ break;
+ case QtAV::BufferingMedia:
+ emit stateChanged(BufferingState);
+ break;
+ case QtAV::EndOfMedia:
+ if (queue.isEmpty())
+ emit finished();
+ else {
+ auto nextPlayer = currentPlayer == player1 ? player2 : player1;
+ if (nextPlayer->isLoaded()) {
+ qDebug() << "Preloaded";
+ setCurrentPlayer(nextPlayer);
+ nextPlayer->play();
+ aboutToFinishEmitted = false;
+ lastErrorString.clear();
+ emit sourceChanged();
+ queue.dequeue();
+ } else {
+ qDebug() << "Not preloaded";
+ currentPlayer->play(queue.dequeue());
+ }
+ }
+ break;
+ case QtAV::InvalidMedia:
+ emit stateChanged(Media::ErrorState);
+ break;
+ default:
+ qDebug() << "Unhandled" << QVariant(status).toString();
+ }
+}
+
+void MediaQtAV::onAVError(const QtAV::AVError &e) {
+ lastErrorString = e.string();
+ qDebug() << lastErrorString;
+ emit error(lastErrorString);
+}
+
+void MediaQtAV::pause() {
+ currentPlayer->pause();
+}
+
+void MediaQtAV::stop() {
+ currentPlayer->stop();
+}
+
+void MediaQtAV::seek(qint64 ms) {
+ currentPlayer->setPosition(ms);
+}
+
+QtAV::AVPlayer *MediaQtAV::createPlayer(bool audioOnly) {
+ QtAV::AVPlayer *p = new QtAV::AVPlayer(this);
+
+#ifndef MEDIA_AUDIOONLY
+ if (!audioOnly) {
+ if (currentPlayer) {
+ p->setRenderer(currentPlayer->renderer());
+ p->setVideoDecoderPriority(currentPlayer->videoDecoderPriority());
+ } else {
+ QtAV::VideoRenderer *renderer = QtAV::VideoRenderer::create(rendererId);
+ if (!renderer || !renderer->isAvailable() || !renderer->widget()) {
+ qFatal("No QtAV video renderer");
+ }
+ p->setRenderer(renderer);
+ p->setVideoDecoderPriority(
+ {"CUDA", "D3D11", "DXVA", "VAAPI", "VideoToolbox", "FFmpeg"});
+ }
+ }
+#endif
+
+ p->setBufferMode(QtAV::BufferTime);
+
+ if (currentPlayer) {
+ p->setBufferValue(currentPlayer->bufferValue());
+ p->setOptionsForFormat(currentPlayer->optionsForFormat());
+ }
+
+ return p;
+}
+
+void MediaQtAV::connectPlayer(QtAV::AVPlayer *player) {
+ connect(player, &QtAV::AVPlayer::error, this, &MediaQtAV::onAVError);
+
+ connect(player, &QtAV::AVPlayer::sourceChanged, this, &Media::sourceChanged);
+ connect(player, &QtAV::AVPlayer::sourceChanged, this, [this] { aboutToFinishEmitted = false; });
+
+ connect(player, &QtAV::AVPlayer::bufferProgressChanged, this, &Media::bufferStatus);
+ connect(player, &QtAV::AVPlayer::started, this, &Media::started);
+ connect(player, &QtAV::AVPlayer::stopped, this, &Media::stopped);
+ connect(player, &QtAV::AVPlayer::paused, this, &Media::paused);
+
+ connect(player, &QtAV::AVPlayer::positionChanged, this, &Media::positionChanged);
+ connect(player, &QtAV::AVPlayer::positionChanged, this, &MediaQtAV::checkAboutToFinish);
+
+ connect(player, &QtAV::AVPlayer::stateChanged, this, [this](QtAV::AVPlayer::State state) {
+ const State s = stateFor(state);
+ if (s != PlayingState) {
+ emit stateChanged(s);
+ } else if (currentPlayer->mediaStatus() == QtAV::BufferedMedia) {
+ // needed when resuming from pause
+ emit stateChanged(s);
+ }
+ });
+ connect(player, &QtAV::AVPlayer::mediaStatusChanged, this, &MediaQtAV::onMediaStatusChange);
+
+ connect(player->audio(), &QtAV::AudioOutput::volumeChanged, this, &Media::volumeChanged);
+ connect(player->audio(), &QtAV::AudioOutput::muteChanged, this, &Media::volumeMutedChanged);
+}
+
+void MediaQtAV::setCurrentPlayer(QtAV::AVPlayer *player) {
+ if (currentPlayer) {
+ currentPlayer->disconnect(this);
+ player->audio()->setVolume(currentPlayer->audio()->volume());
+ player->audio()->setMute(currentPlayer->audio()->isMute());
+ player->setBufferValue(currentPlayer->bufferValue());
+ }
+ currentPlayer = player;
+ connectPlayer(currentPlayer);
+}
+
+void MediaQtAV::smoothSourceChange(const QString &file, const QString &externalAudio) {
+ qDebug() << "smoothSourceChange";
+ auto nextPlayer = player1;
+ if (currentPlayer == player1) {
+ if (player2 == nullptr) player2 = createPlayer(audioOnly);
+ nextPlayer = player2;
+ }
+ QObject *context = new QObject();
+ connect(nextPlayer, &QtAV::AVPlayer::loaded, context, [this, nextPlayer, context] {
+ qDebug() << "smoothSourceChange preloaded";
+ setCurrentPlayer(nextPlayer);
+
+ aboutToFinishEmitted = false;
+ lastErrorString.clear();
+ clearQueue();
+ emit sourceChanged();
+ context->deleteLater();
+
+ QObject *context2 = new QObject();
+ connect(nextPlayer, &QtAV::AVPlayer::mediaStatusChanged, context2,
+ [this, context2](QtAV::MediaStatus mediaStatus) {
+ if (mediaStatus == QtAV::BufferedMedia) {
+ qDebug() << "smoothSourceChange playing";
+ auto oldPlayer = currentPlayer == player1 ? player2 : player1;
+ oldPlayer->stop();
+ context2->deleteLater();
+ }
+ });
+ currentPlayer->play();
+ });
+ nextPlayer->setExternalAudio(externalAudio);
+ nextPlayer->setFile(file);
+ nextPlayer->load();
+}