]> git.sur5r.net Git - minitube/blob - lib/media/src/qtav/mediaqtav.cpp
New upstream version 3.8
[minitube] / lib / media / src / qtav / mediaqtav.cpp
1 #include "mediaqtav.h"
2
3 namespace {
4
5 #if defined(Q_OS_ANDROID) || defined(Q_OS_MAC)
6 const QtAV::VideoRendererId defaultRenderer = QtAV::VideoRendererId_OpenGLWidget;
7 #else
8 const QtAV::VideoRendererId defaultRenderer = QtAV::VideoRendererId_GLWidget2;
9 #endif
10
11 #ifndef MEDIA_AUDIOONLY
12 QtAV::VideoRendererId rendererIdFor(const QString &name) {
13     static const struct {
14         const char *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}};
23
24     for (int i = 0; renderers[i].name; ++i) {
25         if (name == QLatin1String(renderers[i].name)) return renderers[i].id;
26     }
27
28     return defaultRenderer;
29 }
30 #endif
31
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}};
37     return map[state];
38 }
39
40 } // namespace
41
42 MediaQtAV::MediaQtAV(QObject *parent)
43     : Media(parent), player1(nullptr), player2(nullptr), currentPlayer(nullptr) {
44 #ifndef MEDIA_AUDIOONLY
45     rendererId = defaultRenderer;
46 #endif
47 }
48
49 #ifndef MEDIA_AUDIOONLY
50 void MediaQtAV::setRenderer(const QString &name) {
51     rendererId = rendererIdFor(name);
52 }
53 #endif
54
55 void MediaQtAV::setAudioOnly(bool value) {
56     audioOnly = value;
57 }
58
59 void MediaQtAV::init() {
60 #ifndef QT_NO_DEBUG_OUTPUT
61     QtAV::setLogLevel(QtAV::LogAll);
62 #endif
63
64 #ifndef MEDIA_AUDIOONLY
65     QtAV::Widgets::registerRenderers();
66 #endif
67     player1 = createPlayer(audioOnly);
68     setCurrentPlayer(player1);
69 }
70
71 #ifndef MEDIA_AUDIOONLY
72 QWidget *MediaQtAV::videoWidget() {
73     return currentPlayer->renderer()->widget();
74 }
75 #endif
76
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());
82 }
83
84 void MediaQtAV::play(const QString &file) {
85     if (currentPlayer->isPlaying()) {
86         smoothSourceChange(file, QString());
87         return;
88     }
89
90 #ifndef MEDIA_AUDIOONLY
91     if (!currentPlayer->externalAudio().isEmpty()) currentPlayer->setExternalAudio(QString());
92 #endif
93     currentPlayer->play(file);
94     aboutToFinishEmitted = false;
95     lastErrorString.clear();
96     clearQueue();
97 }
98
99 #ifndef MEDIA_AUDIOONLY
100 void MediaQtAV::playSeparateAudioAndVideo(const QString &video, const QString &audio) {
101     if (currentPlayer->isPlaying()) {
102         smoothSourceChange(video, audio);
103         return;
104     }
105     currentPlayer->stop();
106     currentPlayer->setExternalAudio(audio);
107     currentPlayer->play(video);
108     aboutToFinishEmitted = false;
109     lastErrorString.clear();
110     clearQueue();
111 }
112
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();
119 }
120 #endif
121
122 void MediaQtAV::play() {
123     if (currentPlayer->isPaused())
124         currentPlayer->togglePause();
125     else
126         currentPlayer->play();
127 }
128
129 QString MediaQtAV::file() const {
130     return currentPlayer->file();
131 }
132
133 void MediaQtAV::setBufferMilliseconds(qint64 value) {
134     currentPlayer->setBufferValue(value);
135 }
136
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);
142 }
143
144 void MediaQtAV::enqueue(const QString &file) {
145     queue << 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;
152         }
153         nextPlayer->setFile(file);
154         nextPlayer->load();
155     }
156 }
157
158 void MediaQtAV::clearQueue() {
159     queue.clear();
160 }
161
162 bool MediaQtAV::hasQueue() const {
163     return !queue.isEmpty();
164 }
165
166 qint64 MediaQtAV::position() const {
167     return currentPlayer->position();
168 }
169
170 qint64 MediaQtAV::duration() const {
171     return currentPlayer->duration();
172 }
173
174 qint64 MediaQtAV::remainingTime() const {
175     return currentPlayer->duration() - currentPlayer->position();
176 }
177
178 qreal MediaQtAV::volume() const {
179     return currentPlayer->audio()->volume();
180 }
181
182 void MediaQtAV::setVolume(qreal value) {
183     auto audio = currentPlayer->audio();
184     if (!audio->isOpen()) audio->open();
185     audio->setVolume(value);
186 }
187
188 bool MediaQtAV::volumeMuted() const {
189     return currentPlayer->audio()->isMute();
190 }
191
192 void MediaQtAV::setVolumeMuted(bool value) {
193     currentPlayer->audio()->setMute(value);
194 }
195
196 QString MediaQtAV::errorString() const {
197     return lastErrorString;
198 }
199
200 void MediaQtAV::checkAboutToFinish(qint64 position) {
201     if (!aboutToFinishEmitted && currentPlayer->isPlaying() &&
202         duration() - position < currentPlayer->bufferValue()) {
203         aboutToFinishEmitted = true;
204         emit aboutToFinish();
205     }
206 }
207
208 void MediaQtAV::onMediaStatusChange(QtAV::MediaStatus status) {
209     qDebug() << QVariant(status).toString();
210
211     switch (status) {
212     case QtAV::LoadingMedia:
213         emit stateChanged(LoadingState);
214         break;
215     case QtAV::BufferedMedia:
216         if (currentPlayer->state() == QtAV::AVPlayer::PlayingState) emit stateChanged(PlayingState);
217         break;
218     case QtAV::BufferingMedia:
219         emit stateChanged(BufferingState);
220         break;
221     case QtAV::EndOfMedia:
222         if (queue.isEmpty())
223             emit finished();
224         else {
225             auto nextPlayer = currentPlayer == player1 ? player2 : player1;
226             if (nextPlayer->isLoaded()) {
227                 qDebug() << "Preloaded";
228                 setCurrentPlayer(nextPlayer);
229                 nextPlayer->play();
230                 aboutToFinishEmitted = false;
231                 lastErrorString.clear();
232                 emit sourceChanged();
233                 queue.dequeue();
234             } else {
235                 qDebug() << "Not preloaded";
236                 currentPlayer->play(queue.dequeue());
237             }
238         }
239         break;
240     case QtAV::InvalidMedia:
241         emit stateChanged(Media::ErrorState);
242         break;
243     default:
244         qDebug() << "Unhandled" << QVariant(status).toString();
245     }
246 }
247
248 void MediaQtAV::onAVError(const QtAV::AVError &e) {
249     lastErrorString = e.string();
250     qDebug() << lastErrorString;
251     emit error(lastErrorString);
252 }
253
254 void MediaQtAV::pause() {
255     currentPlayer->pause();
256 }
257
258 void MediaQtAV::stop() {
259     currentPlayer->stop();
260 }
261
262 void MediaQtAV::seek(qint64 ms) {
263     currentPlayer->setPosition(ms);
264 }
265
266 void MediaQtAV::relativeSeek(qint64 ms) {
267     currentPlayer->setPosition(currentPlayer->position() + ms);
268 }
269
270 QtAV::AVPlayer *MediaQtAV::createPlayer(bool audioOnly) {
271     QtAV::AVPlayer *p = new QtAV::AVPlayer(this);
272
273 #ifndef MEDIA_AUDIOONLY
274     if (!audioOnly) {
275         if (currentPlayer) {
276             p->setRenderer(currentPlayer->renderer());
277             p->setVideoDecoderPriority(currentPlayer->videoDecoderPriority());
278         } else {
279             QtAV::VideoRenderer *renderer = QtAV::VideoRenderer::create(rendererId);
280             if (!renderer || !renderer->isAvailable() || !renderer->widget()) {
281                 qFatal("No QtAV video renderer");
282             }
283             p->setRenderer(renderer);
284             p->setVideoDecoderPriority(
285                     {"CUDA", "D3D11", "DXVA", "VAAPI", "VideoToolbox", "FFmpeg"});
286         }
287     }
288 #endif
289
290     p->setBufferMode(QtAV::BufferTime);
291
292     if (currentPlayer) {
293         p->setBufferValue(currentPlayer->bufferValue());
294         p->setOptionsForFormat(currentPlayer->optionsForFormat());
295     }
296
297     return p;
298 }
299
300 void MediaQtAV::connectPlayer(QtAV::AVPlayer *player) {
301     connect(player, &QtAV::AVPlayer::error, this, &MediaQtAV::onAVError);
302
303     connect(player, &QtAV::AVPlayer::sourceChanged, this, &Media::sourceChanged);
304     connect(player, &QtAV::AVPlayer::sourceChanged, this, [this] { aboutToFinishEmitted = false; });
305
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);
310
311     connect(player, &QtAV::AVPlayer::positionChanged, this, &Media::positionChanged);
312     connect(player, &QtAV::AVPlayer::positionChanged, this, &MediaQtAV::checkAboutToFinish);
313
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);
321         }
322     });
323     connect(player, &QtAV::AVPlayer::mediaStatusChanged, this, &MediaQtAV::onMediaStatusChange);
324
325     connect(player->audio(), &QtAV::AudioOutput::volumeChanged, this, &Media::volumeChanged);
326     connect(player->audio(), &QtAV::AudioOutput::muteChanged, this, &Media::volumeMutedChanged);
327 }
328
329 void MediaQtAV::setCurrentPlayer(QtAV::AVPlayer *player) {
330     if (currentPlayer) {
331         currentPlayer->disconnect(this);
332         player->audio()->setVolume(currentPlayer->audio()->volume());
333         player->audio()->setMute(currentPlayer->audio()->isMute());
334         player->setBufferValue(currentPlayer->bufferValue());
335     }
336     currentPlayer = player;
337     connectPlayer(currentPlayer);
338 }
339
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;
346     }
347     QObject *context = new QObject();
348     connect(nextPlayer, &QtAV::AVPlayer::loaded, context, [this, nextPlayer, context] {
349         qDebug() << "smoothSourceChange preloaded";
350         setCurrentPlayer(nextPlayer);
351
352         aboutToFinishEmitted = false;
353         lastErrorString.clear();
354         clearQueue();
355         emit sourceChanged();
356         context->deleteLater();
357
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;
364                         oldPlayer->stop();
365                         context2->deleteLater();
366                     }
367                 });
368         currentPlayer->play();
369     });
370     nextPlayer->setExternalAudio(externalAudio);
371     nextPlayer->setFile(file);
372     nextPlayer->load();
373 }