]> git.sur5r.net Git - minitube/blob - lib/media/src/qtav/mediaqtav.cpp
New upstream version 3.1
[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 QtAV::AVPlayer *MediaQtAV::createPlayer(bool audioOnly) {
267     QtAV::AVPlayer *p = new QtAV::AVPlayer(this);
268
269 #ifndef MEDIA_AUDIOONLY
270     if (!audioOnly) {
271         if (currentPlayer) {
272             p->setRenderer(currentPlayer->renderer());
273             p->setVideoDecoderPriority(currentPlayer->videoDecoderPriority());
274         } else {
275             QtAV::VideoRenderer *renderer = QtAV::VideoRenderer::create(rendererId);
276             if (!renderer || !renderer->isAvailable() || !renderer->widget()) {
277                 qFatal("No QtAV video renderer");
278             }
279             p->setRenderer(renderer);
280             p->setVideoDecoderPriority(
281                     {"CUDA", "D3D11", "DXVA", "VAAPI", "VideoToolbox", "FFmpeg"});
282         }
283     }
284 #endif
285
286     p->setBufferMode(QtAV::BufferTime);
287
288     if (currentPlayer) {
289         p->setBufferValue(currentPlayer->bufferValue());
290         p->setOptionsForFormat(currentPlayer->optionsForFormat());
291     }
292
293     return p;
294 }
295
296 void MediaQtAV::connectPlayer(QtAV::AVPlayer *player) {
297     connect(player, &QtAV::AVPlayer::error, this, &MediaQtAV::onAVError);
298
299     connect(player, &QtAV::AVPlayer::sourceChanged, this, &Media::sourceChanged);
300     connect(player, &QtAV::AVPlayer::sourceChanged, this, [this] { aboutToFinishEmitted = false; });
301
302     connect(player, &QtAV::AVPlayer::bufferProgressChanged, this, &Media::bufferStatus);
303     connect(player, &QtAV::AVPlayer::started, this, &Media::started);
304     connect(player, &QtAV::AVPlayer::stopped, this, &Media::stopped);
305     connect(player, &QtAV::AVPlayer::paused, this, &Media::paused);
306
307     connect(player, &QtAV::AVPlayer::positionChanged, this, &Media::positionChanged);
308     connect(player, &QtAV::AVPlayer::positionChanged, this, &MediaQtAV::checkAboutToFinish);
309
310     connect(player, &QtAV::AVPlayer::stateChanged, this, [this](QtAV::AVPlayer::State state) {
311         const State s = stateFor(state);
312         if (s != PlayingState) {
313             emit stateChanged(s);
314         } else if (currentPlayer->mediaStatus() == QtAV::BufferedMedia) {
315             // needed when resuming from pause
316             emit stateChanged(s);
317         }
318     });
319     connect(player, &QtAV::AVPlayer::mediaStatusChanged, this, &MediaQtAV::onMediaStatusChange);
320
321     connect(player->audio(), &QtAV::AudioOutput::volumeChanged, this, &Media::volumeChanged);
322     connect(player->audio(), &QtAV::AudioOutput::muteChanged, this, &Media::volumeMutedChanged);
323 }
324
325 void MediaQtAV::setCurrentPlayer(QtAV::AVPlayer *player) {
326     if (currentPlayer) {
327         currentPlayer->disconnect(this);
328         player->audio()->setVolume(currentPlayer->audio()->volume());
329         player->audio()->setMute(currentPlayer->audio()->isMute());
330         player->setBufferValue(currentPlayer->bufferValue());
331     }
332     currentPlayer = player;
333     connectPlayer(currentPlayer);
334 }
335
336 void MediaQtAV::smoothSourceChange(const QString &file, const QString &externalAudio) {
337     qDebug() << "smoothSourceChange";
338     auto nextPlayer = player1;
339     if (currentPlayer == player1) {
340         if (player2 == nullptr) player2 = createPlayer(audioOnly);
341         nextPlayer = player2;
342     }
343     QObject *context = new QObject();
344     connect(nextPlayer, &QtAV::AVPlayer::loaded, context, [this, nextPlayer, context] {
345         qDebug() << "smoothSourceChange preloaded";
346         setCurrentPlayer(nextPlayer);
347
348         aboutToFinishEmitted = false;
349         lastErrorString.clear();
350         clearQueue();
351         emit sourceChanged();
352         context->deleteLater();
353
354         QObject *context2 = new QObject();
355         connect(nextPlayer, &QtAV::AVPlayer::mediaStatusChanged, context2,
356                 [this, context2](QtAV::MediaStatus mediaStatus) {
357                     if (mediaStatus == QtAV::BufferedMedia) {
358                         qDebug() << "smoothSourceChange playing";
359                         auto oldPlayer = currentPlayer == player1 ? player2 : player1;
360                         oldPlayer->stop();
361                         context2->deleteLater();
362                     }
363                 });
364         currentPlayer->play();
365     });
366     nextPlayer->setExternalAudio(externalAudio);
367     nextPlayer->setFile(file);
368     nextPlayer->load();
369 }