5 #if defined(Q_OS_ANDROID) || defined(Q_OS_MAC)
6 const QtAV::VideoRendererId defaultRenderer = QtAV::VideoRendererId_OpenGLWidget;
8 const QtAV::VideoRendererId defaultRenderer = QtAV::VideoRendererId_GLWidget2;
11 #ifndef MEDIA_AUDIOONLY
12 QtAV::VideoRendererId rendererIdFor(const QString &name) {
15 QtAV::VideoRendererId id;
16 } renderers[] = {{"opengl", QtAV::VideoRendererId_OpenGLWidget},
17 {"gl", QtAV::VideoRendererId_GLWidget2},
18 {"d2d", QtAV::VideoRendererId_Direct2D},
19 {"gdi", QtAV::VideoRendererId_GDI},
20 {"xv", QtAV::VideoRendererId_XV},
21 {"x11", QtAV::VideoRendererId_X11},
22 {"qt", QtAV::VideoRendererId_Widget}};
24 for (int i = 0; renderers[i].name; ++i) {
25 if (name == QLatin1String(renderers[i].name)) return renderers[i].id;
28 return defaultRenderer;
32 Media::State stateFor(QtAV::AVPlayer::State state) {
33 static const QMap<QtAV::AVPlayer::State, Media::State> map{
34 {QtAV::AVPlayer::StoppedState, Media::StoppedState},
35 {QtAV::AVPlayer::PlayingState, Media::PlayingState},
36 {QtAV::AVPlayer::PausedState, Media::PausedState}};
42 MediaQtAV::MediaQtAV(QObject *parent)
43 : Media(parent), player1(nullptr), player2(nullptr), currentPlayer(nullptr) {
44 #ifndef MEDIA_AUDIOONLY
45 rendererId = defaultRenderer;
49 #ifndef MEDIA_AUDIOONLY
50 void MediaQtAV::setRenderer(const QString &name) {
51 rendererId = rendererIdFor(name);
55 void MediaQtAV::setAudioOnly(bool value) {
59 void MediaQtAV::init() {
60 #ifndef QT_NO_DEBUG_OUTPUT
61 QtAV::setLogLevel(QtAV::LogAll);
64 #ifndef MEDIA_AUDIOONLY
65 QtAV::Widgets::registerRenderers();
67 player1 = createPlayer(audioOnly);
68 setCurrentPlayer(player1);
71 #ifndef MEDIA_AUDIOONLY
72 QWidget *MediaQtAV::videoWidget() {
73 return currentPlayer->renderer()->widget();
77 Media::State MediaQtAV::state() const {
78 if (currentPlayer->mediaStatus() == QtAV::LoadingMedia) return LoadingState;
79 if (currentPlayer->bufferProgress() > 0. && currentPlayer->bufferProgress() < 1.)
80 return BufferingState;
81 return stateFor(currentPlayer->state());
84 void MediaQtAV::play(const QString &file) {
85 if (currentPlayer->isPlaying()) {
86 smoothSourceChange(file, QString());
90 #ifndef MEDIA_AUDIOONLY
91 if (!currentPlayer->externalAudio().isEmpty()) currentPlayer->setExternalAudio(QString());
93 currentPlayer->play(file);
94 aboutToFinishEmitted = false;
95 lastErrorString.clear();
99 #ifndef MEDIA_AUDIOONLY
100 void MediaQtAV::playSeparateAudioAndVideo(const QString &video, const QString &audio) {
101 if (currentPlayer->isPlaying()) {
102 smoothSourceChange(video, audio);
105 currentPlayer->stop();
106 currentPlayer->setExternalAudio(audio);
107 currentPlayer->play(video);
108 aboutToFinishEmitted = false;
109 lastErrorString.clear();
113 void MediaQtAV::snapshot() {
114 auto videoCapture = currentPlayer->videoCapture();
115 connect(videoCapture, &QtAV::VideoCapture::imageCaptured, this, &Media::snapshotReady);
116 connect(videoCapture, &QtAV::VideoCapture::failed, this,
117 [this] { emit snapshotReady(QImage()); });
118 videoCapture->capture();
122 void MediaQtAV::play() {
123 if (currentPlayer->isPaused())
124 currentPlayer->togglePause();
126 currentPlayer->play();
129 QString MediaQtAV::file() const {
130 return currentPlayer->file();
133 void MediaQtAV::setBufferMilliseconds(qint64 value) {
134 currentPlayer->setBufferValue(value);
137 void MediaQtAV::setUserAgent(const QString &value) {
138 qDebug() << "Setting user agent to" << value;
139 auto options = currentPlayer->optionsForFormat();
140 options.insert(QStringLiteral("user_agent"), value);
141 currentPlayer->setOptionsForFormat(options);
144 void MediaQtAV::enqueue(const QString &file) {
146 if (queue.size() == 1) {
147 qDebug() << "Preloading" << file;
148 auto nextPlayer = player1;
149 if (currentPlayer == player1) {
150 if (player2 == nullptr) player2 = createPlayer(audioOnly);
151 nextPlayer = player2;
153 nextPlayer->setFile(file);
158 void MediaQtAV::clearQueue() {
162 bool MediaQtAV::hasQueue() const {
163 return !queue.isEmpty();
166 qint64 MediaQtAV::position() const {
167 return currentPlayer->position();
170 qint64 MediaQtAV::duration() const {
171 return currentPlayer->duration();
174 qint64 MediaQtAV::remainingTime() const {
175 return currentPlayer->duration() - currentPlayer->position();
178 qreal MediaQtAV::volume() const {
179 return currentPlayer->audio()->volume();
182 void MediaQtAV::setVolume(qreal value) {
183 auto audio = currentPlayer->audio();
184 if (!audio->isOpen()) audio->open();
185 audio->setVolume(value);
188 bool MediaQtAV::volumeMuted() const {
189 return currentPlayer->audio()->isMute();
192 void MediaQtAV::setVolumeMuted(bool value) {
193 currentPlayer->audio()->setMute(value);
196 QString MediaQtAV::errorString() const {
197 return lastErrorString;
200 void MediaQtAV::checkAboutToFinish(qint64 position) {
201 if (!aboutToFinishEmitted && currentPlayer->isPlaying() &&
202 duration() - position < currentPlayer->bufferValue()) {
203 aboutToFinishEmitted = true;
204 emit aboutToFinish();
208 void MediaQtAV::onMediaStatusChange(QtAV::MediaStatus status) {
209 qDebug() << QVariant(status).toString();
212 case QtAV::LoadingMedia:
213 emit stateChanged(LoadingState);
215 case QtAV::BufferedMedia:
216 if (currentPlayer->state() == QtAV::AVPlayer::PlayingState) emit stateChanged(PlayingState);
218 case QtAV::BufferingMedia:
219 emit stateChanged(BufferingState);
221 case QtAV::EndOfMedia:
225 auto nextPlayer = currentPlayer == player1 ? player2 : player1;
226 if (nextPlayer->isLoaded()) {
227 qDebug() << "Preloaded";
228 setCurrentPlayer(nextPlayer);
230 aboutToFinishEmitted = false;
231 lastErrorString.clear();
232 emit sourceChanged();
235 qDebug() << "Not preloaded";
236 currentPlayer->play(queue.dequeue());
240 case QtAV::InvalidMedia:
241 emit stateChanged(Media::ErrorState);
244 qDebug() << "Unhandled" << QVariant(status).toString();
248 void MediaQtAV::onAVError(const QtAV::AVError &e) {
249 lastErrorString = e.string();
250 qDebug() << lastErrorString;
251 emit error(lastErrorString);
254 void MediaQtAV::pause() {
255 currentPlayer->pause();
258 void MediaQtAV::stop() {
259 currentPlayer->stop();
262 void MediaQtAV::seek(qint64 ms) {
263 currentPlayer->setPosition(ms);
266 void MediaQtAV::relativeSeek(qint64 ms) {
267 currentPlayer->setPosition(currentPlayer->position() + ms);
270 QtAV::AVPlayer *MediaQtAV::createPlayer(bool audioOnly) {
271 QtAV::AVPlayer *p = new QtAV::AVPlayer(this);
273 #ifndef MEDIA_AUDIOONLY
276 p->setRenderer(currentPlayer->renderer());
277 p->setVideoDecoderPriority(currentPlayer->videoDecoderPriority());
279 QtAV::VideoRenderer *renderer = QtAV::VideoRenderer::create(rendererId);
280 if (!renderer || !renderer->isAvailable() || !renderer->widget()) {
281 qFatal("No QtAV video renderer");
283 p->setRenderer(renderer);
284 p->setVideoDecoderPriority(
285 {"CUDA", "D3D11", "DXVA", "VAAPI", "VideoToolbox", "FFmpeg"});
290 p->setBufferMode(QtAV::BufferTime);
293 p->setBufferValue(currentPlayer->bufferValue());
294 p->setOptionsForFormat(currentPlayer->optionsForFormat());
300 void MediaQtAV::connectPlayer(QtAV::AVPlayer *player) {
301 connect(player, &QtAV::AVPlayer::error, this, &MediaQtAV::onAVError);
303 connect(player, &QtAV::AVPlayer::sourceChanged, this, &Media::sourceChanged);
304 connect(player, &QtAV::AVPlayer::sourceChanged, this, [this] { aboutToFinishEmitted = false; });
306 connect(player, &QtAV::AVPlayer::bufferProgressChanged, this, &Media::bufferStatus);
307 connect(player, &QtAV::AVPlayer::started, this, &Media::started);
308 connect(player, &QtAV::AVPlayer::stopped, this, &Media::stopped);
309 connect(player, &QtAV::AVPlayer::paused, this, &Media::paused);
311 connect(player, &QtAV::AVPlayer::positionChanged, this, &Media::positionChanged);
312 connect(player, &QtAV::AVPlayer::positionChanged, this, &MediaQtAV::checkAboutToFinish);
314 connect(player, &QtAV::AVPlayer::stateChanged, this, [this](QtAV::AVPlayer::State state) {
315 const State s = stateFor(state);
316 if (s != PlayingState) {
317 emit stateChanged(s);
318 } else if (currentPlayer->mediaStatus() == QtAV::BufferedMedia) {
319 // needed when resuming from pause
320 emit stateChanged(s);
323 connect(player, &QtAV::AVPlayer::mediaStatusChanged, this, &MediaQtAV::onMediaStatusChange);
325 connect(player->audio(), &QtAV::AudioOutput::volumeChanged, this, &Media::volumeChanged);
326 connect(player->audio(), &QtAV::AudioOutput::muteChanged, this, &Media::volumeMutedChanged);
329 void MediaQtAV::setCurrentPlayer(QtAV::AVPlayer *player) {
331 currentPlayer->disconnect(this);
332 player->audio()->setVolume(currentPlayer->audio()->volume());
333 player->audio()->setMute(currentPlayer->audio()->isMute());
334 player->setBufferValue(currentPlayer->bufferValue());
336 currentPlayer = player;
337 connectPlayer(currentPlayer);
340 void MediaQtAV::smoothSourceChange(const QString &file, const QString &externalAudio) {
341 qDebug() << "smoothSourceChange";
342 auto nextPlayer = player1;
343 if (currentPlayer == player1) {
344 if (player2 == nullptr) player2 = createPlayer(audioOnly);
345 nextPlayer = player2;
347 QObject *context = new QObject();
348 connect(nextPlayer, &QtAV::AVPlayer::loaded, context, [this, nextPlayer, context] {
349 qDebug() << "smoothSourceChange preloaded";
350 setCurrentPlayer(nextPlayer);
352 aboutToFinishEmitted = false;
353 lastErrorString.clear();
355 emit sourceChanged();
356 context->deleteLater();
358 QObject *context2 = new QObject();
359 connect(nextPlayer, &QtAV::AVPlayer::mediaStatusChanged, context2,
360 [this, context2](QtAV::MediaStatus mediaStatus) {
361 if (mediaStatus == QtAV::BufferedMedia) {
362 qDebug() << "smoothSourceChange playing";
363 auto oldPlayer = currentPlayer == player1 ? player2 : player1;
365 context2->deleteLater();
368 currentPlayer->play();
370 nextPlayer->setExternalAudio(externalAudio);
371 nextPlayer->setFile(file);