X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;ds=sidebyside;f=lib%2Fmedia%2Fsrc%2Fqtav%2Fmediaqtav.cpp;fp=lib%2Fmedia%2Fsrc%2Fqtav%2Fmediaqtav.cpp;h=c5a095bc85fe57c4afcd02cdef44da8a9f42f4b4;hb=ccc918169d49d046c29001c21c492dfbbde063aa;hp=0000000000000000000000000000000000000000;hpb=533489a63a9716c645a11a99ca446978b20eedd0;p=minitube diff --git a/lib/media/src/qtav/mediaqtav.cpp b/lib/media/src/qtav/mediaqtav.cpp new file mode 100644 index 0000000..c5a095b --- /dev/null +++ b/lib/media/src/qtav/mediaqtav.cpp @@ -0,0 +1,369 @@ +#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 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(); +}