Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qffmpegmediaplayer.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include "private/qplatformaudiooutput_p.h"
6#include "qvideosink.h"
7#include "qaudiooutput.h"
10#include <qiodevice.h>
11#include <qvideosink.h>
12#include <QtMultimedia/qplaybackoptions.h>
13#include <qtimer.h>
14#include <QtConcurrent/QtConcurrent>
15
16#include <qloggingcategory.h>
17
19
20namespace QFFmpeg {
21
23{
24public:
25
26 bool isCancelled() const override { return m_cancelled.load(std::memory_order_acquire); }
27
28 void cancel() { m_cancelled.store(true, std::memory_order_release); }
29
30private:
31 std::atomic_bool m_cancelled = false;
32};
33
34} // namespace QFFmpeg
35
36using namespace QFFmpeg;
37
38QFFmpegMediaPlayer::QFFmpegMediaPlayer(QMediaPlayer *player)
39 : QPlatformMediaPlayer(player)
40{
41 m_positionUpdateTimer.setInterval(50);
42 m_positionUpdateTimer.setTimerType(Qt::PreciseTimer);
43 connect(&m_positionUpdateTimer, &QTimer::timeout, this, &QFFmpegMediaPlayer::updatePosition);
44}
45
47{
48 if (m_cancelToken)
49 m_cancelToken->cancel();
50
51 m_loadMedia.waitForFinished();
52};
53
55{
56 return m_playbackEngine ? toUserDuration(m_playbackEngine->duration()).get() : 0;
57}
58
59void QFFmpegMediaPlayer::setPosition(qint64 position)
60{
61 if (mediaStatus() == QMediaPlayer::LoadingMedia)
62 return;
63
64 if (m_playbackEngine) {
65 m_demuxerBuffered = false;
66 m_playbackEngine->seek(toTrackPosition(UserTrackPosition(position)));
67 updatePosition();
68 }
69
70 mediaStatusChanged(QMediaPlayer::LoadedMedia);
71}
72
73void QFFmpegMediaPlayer::updatePosition()
74{
75 positionChanged(m_playbackEngine ? toUserPosition(m_playbackEngine->currentPosition()).get()
76 : 0);
77}
78
79void QFFmpegMediaPlayer::endOfStream()
80{
81 // stop update timer and report end position anyway
82 m_positionUpdateTimer.stop();
83 QPointer currentPlaybackEngine(m_playbackEngine.get());
84 positionChanged(duration());
85
86 // skip changing state and mediaStatus if playbackEngine has been recreated,
87 // e.g. if new media has been loaded as a response to positionChanged signal
88 if (currentPlaybackEngine)
89 stateChanged(QMediaPlayer::StoppedState);
90 if (currentPlaybackEngine)
91 mediaStatusChanged(QMediaPlayer::EndOfMedia);
92}
93
94void QFFmpegMediaPlayer::onLoopChanged()
95{
96 // report about finish and start
97 // reporting both signals is a bit contraversial
98 // but it eshures the idea of notifications about
99 // imporatant position points.
100 // Also, it ensures more predictable flow for testing.
101 positionChanged(duration());
102 positionChanged(0);
103 m_positionUpdateTimer.stop();
104 m_positionUpdateTimer.start();
105}
106
107void QFFmpegMediaPlayer::onBuffered()
108{
109 m_demuxerBuffered = true;
110 if (mediaStatus() == QMediaPlayer::BufferingMedia)
111 mediaStatusChanged(QMediaPlayer::BufferedMedia);
112}
113
115{
116 return m_bufferProgress;
117}
118
119void QFFmpegMediaPlayer::mediaStatusChanged(QMediaPlayer::MediaStatus status)
120{
121 if (mediaStatus() == status)
122 return;
123
124 const auto newBufferProgress = status == QMediaPlayer::BufferingMedia ? 0.25f // to be improved
125 : status == QMediaPlayer::BufferedMedia ? 1.f
126 : 0.f;
127
128 if (!QtPrivate::fuzzyCompare(newBufferProgress, m_bufferProgress)) {
129 m_bufferProgress = newBufferProgress;
130 bufferProgressChanged(newBufferProgress);
131 }
132
133 QPlatformMediaPlayer::mediaStatusChanged(status);
134}
135
140
142{
143 return m_playbackRate;
144}
145
147{
148 const float effectiveRate = std::max(static_cast<float>(rate), 0.0f);
149
150 if (QtPrivate::fuzzyCompare(m_playbackRate, effectiveRate))
151 return;
152
153 m_playbackRate = effectiveRate;
154
155 if (m_playbackEngine)
156 m_playbackEngine->setPlaybackRate(effectiveRate);
157
158 playbackRateChanged(effectiveRate);
159}
160
162{
163 return m_url;
164}
165
167{
168 return m_device;
169}
170
171void QFFmpegMediaPlayer::handleIncorrectMedia(QMediaPlayer::MediaStatus status)
172{
173 seekableChanged(false);
174 audioAvailableChanged(false);
175 videoAvailableChanged(false);
176 metaDataChanged();
177 mediaStatusChanged(status);
178 m_playbackEngine = nullptr;
179};
180
181void QFFmpegMediaPlayer::setMedia(const QUrl &media, QIODevice *stream)
182{
183 // Wait for previous unfinished load attempts.
184 if (m_cancelToken)
185 m_cancelToken->cancel();
186
187 m_loadMedia.waitForFinished();
188
189 m_url = media;
190 m_device = stream;
191 m_playbackEngine = nullptr;
192 m_demuxerBuffered = false;
193
194 if (media.isEmpty() && !stream) {
195 handleIncorrectMedia(QMediaPlayer::NoMedia);
196 return;
197 }
198
199 mediaStatusChanged(QMediaPlayer::LoadingMedia);
200
201 m_requestedStatus = QMediaPlayer::StoppedState;
202
203 m_cancelToken = std::make_shared<CancelToken>();
204
205 // Load media asynchronously to keep GUI thread responsive while loading media
206 m_loadMedia = QtConcurrent::run([this, media, stream, cancelToken = m_cancelToken] {
207 // On worker thread
208 const MediaDataHolder::Maybe mediaHolder =
209 MediaDataHolder::create(media, stream, playbackOptions(), cancelToken);
210
211 // Transition back to calling thread using invokeMethod because
212 // QFuture continuations back on calling thread may deadlock (QTBUG-117918)
213 QMetaObject::invokeMethod(this, [this, mediaHolder, cancelToken] {
214 setMediaAsync(mediaHolder, cancelToken);
215 });
216 });
217}
218
219void QFFmpegMediaPlayer::setMediaAsync(QFFmpeg::MediaDataHolder::Maybe mediaDataHolder,
220 const std::shared_ptr<QFFmpeg::CancelToken> &cancelToken)
221{
222 // If loading was cancelled, we do not emit any signals about failing
223 // to load media (or any other events). The rationale is that cancellation
224 // either happens during destruction, where the signals are no longer
225 // of interest, or it happens as a response to user requesting to load
226 // another media file. In the latter case, we don't want to risk popping
227 // up error dialogs or similar.
228 if (cancelToken->isCancelled()) {
229 return;
230 }
231
232 Q_ASSERT(mediaStatus() == QMediaPlayer::LoadingMedia);
233
234 if (!mediaDataHolder) {
235 const auto [code, description] = mediaDataHolder.error();
236 error(code, description);
237 handleIncorrectMedia(QMediaPlayer::MediaStatus::InvalidMedia);
238 return;
239 }
240
241 m_playbackEngine = std::make_unique<PlaybackEngine>(playbackOptions());
242
243 connect(m_playbackEngine.get(), &PlaybackEngine::endOfStream, this,
244 &QFFmpegMediaPlayer::endOfStream);
245 connect(m_playbackEngine.get(), &PlaybackEngine::errorOccured, this,
246 &QFFmpegMediaPlayer::error);
247 connect(m_playbackEngine.get(), &PlaybackEngine::loopChanged, this,
248 &QFFmpegMediaPlayer::onLoopChanged);
249 connect(m_playbackEngine.get(), &PlaybackEngine::buffered, this,
250 &QFFmpegMediaPlayer::onBuffered);
251
252 m_playbackEngine->setMedia(std::move(*mediaDataHolder.value()));
253
254 m_playbackEngine->setAudioBufferOutput(m_audioBufferOutput);
255 m_playbackEngine->setAudioSink(m_audioOutput);
256 m_playbackEngine->setVideoSink(m_videoSink);
257
258 m_playbackEngine->setLoops(loops());
259 m_playbackEngine->setPlaybackRate(m_playbackRate);
260 m_playbackEngine->setPitchCompensation(m_pitchCompensation);
261
262 durationChanged(duration());
263 tracksChanged();
264 metaDataChanged();
265 seekableChanged(m_playbackEngine->isSeekable());
266
267 audioAvailableChanged(
268 !m_playbackEngine->streamInfo(QPlatformMediaPlayer::AudioStream).isEmpty());
269 videoAvailableChanged(
270 !m_playbackEngine->streamInfo(QPlatformMediaPlayer::VideoStream).isEmpty());
271
272 mediaStatusChanged(QMediaPlayer::LoadedMedia);
273
274 if (m_requestedStatus != QMediaPlayer::StoppedState) {
275 if (m_requestedStatus == QMediaPlayer::PlayingState)
276 play();
277 else if (m_requestedStatus == QMediaPlayer::PausedState)
278 pause();
279 }
280}
281
283{
284 if (mediaStatus() == QMediaPlayer::LoadingMedia) {
285 m_requestedStatus = QMediaPlayer::PlayingState;
286 return;
287 }
288
289 if (!m_playbackEngine)
290 return;
291
292 if (mediaStatus() == QMediaPlayer::EndOfMedia && state() == QMediaPlayer::StoppedState) {
293 m_playbackEngine->seek(TrackPosition(0));
294 positionChanged(0);
295 }
296
297 runPlayback();
298}
299
300void QFFmpegMediaPlayer::runPlayback()
301{
302 m_playbackEngine->play();
303 m_positionUpdateTimer.start();
304 stateChanged(QMediaPlayer::PlayingState);
305
306 if (mediaStatus() == QMediaPlayer::LoadedMedia || mediaStatus() == QMediaPlayer::EndOfMedia) {
307 mediaStatusChanged(m_demuxerBuffered ? QMediaPlayer::BufferedMedia
308 : QMediaPlayer::BufferingMedia);
309 }
310}
311
313{
314 if (mediaStatus() == QMediaPlayer::LoadingMedia) {
315 m_requestedStatus = QMediaPlayer::PausedState;
316 return;
317 }
318
319 if (!m_playbackEngine)
320 return;
321
322 if (mediaStatus() == QMediaPlayer::EndOfMedia && state() == QMediaPlayer::StoppedState) {
323 m_playbackEngine->seek(TrackPosition(0));
324 positionChanged(0);
325 }
326 m_playbackEngine->pause();
327 m_positionUpdateTimer.stop();
328 stateChanged(QMediaPlayer::PausedState);
329
330 if (mediaStatus() == QMediaPlayer::LoadedMedia || mediaStatus() == QMediaPlayer::EndOfMedia) {
331 mediaStatusChanged(m_demuxerBuffered ? QMediaPlayer::BufferedMedia
332 : QMediaPlayer::BufferingMedia);
333 }
334}
335
337{
338 if (mediaStatus() == QMediaPlayer::LoadingMedia) {
339 m_requestedStatus = QMediaPlayer::StoppedState;
340 return;
341 }
342
343 if (!m_playbackEngine)
344 return;
345
346 m_demuxerBuffered = false;
347 m_playbackEngine->stop();
348 m_positionUpdateTimer.stop();
349 m_playbackEngine->seek(TrackPosition(0));
350 positionChanged(0);
351 stateChanged(QMediaPlayer::StoppedState);
352 mediaStatusChanged(QMediaPlayer::LoadedMedia);
353}
354
355void QFFmpegMediaPlayer::setAudioOutput(QPlatformAudioOutput *output)
356{
357 m_audioOutput = output;
358 if (m_playbackEngine)
359 m_playbackEngine->setAudioSink(output);
360}
361
362void QFFmpegMediaPlayer::setAudioBufferOutput(QAudioBufferOutput *output) {
363 m_audioBufferOutput = output;
364 if (m_playbackEngine)
365 m_playbackEngine->setAudioBufferOutput(output);
366}
367
369{
370 return m_playbackEngine ? m_playbackEngine->metaData() : QMediaMetaData{};
371}
372
373void QFFmpegMediaPlayer::setVideoSink(QVideoSink *sink)
374{
375 m_videoSink = sink;
376 if (m_playbackEngine)
377 m_playbackEngine->setVideoSink(sink);
378}
379
380QVideoSink *QFFmpegMediaPlayer::videoSink() const
381{
382 return m_videoSink;
383}
384
385int QFFmpegMediaPlayer::trackCount(TrackType type)
386{
387 return m_playbackEngine ? m_playbackEngine->streamInfo(type).count() : 0;
388}
389
390QMediaMetaData QFFmpegMediaPlayer::trackMetaData(TrackType type, int streamNumber)
391{
392 if (!m_playbackEngine || streamNumber < 0
393 || streamNumber >= m_playbackEngine->streamInfo(type).count())
394 return {};
395 return m_playbackEngine->streamInfo(type).at(streamNumber).metaData;
396}
397
398int QFFmpegMediaPlayer::activeTrack(TrackType type)
399{
400 return m_playbackEngine ? m_playbackEngine->activeTrack(type) : -1;
401}
402
403void QFFmpegMediaPlayer::setActiveTrack(TrackType type, int streamNumber)
404{
405 if (m_playbackEngine)
406 m_playbackEngine->setActiveTrack(type, streamNumber);
407 else
408 qWarning() << "Cannot set active track without open source";
409}
410
412{
413 if (m_playbackEngine)
414 m_playbackEngine->setLoops(loops);
415
416 QPlatformMediaPlayer::setLoops(loops);
417}
418
420{
421 if (enabled == m_pitchCompensation)
422 return;
423
424 m_pitchCompensation = enabled;
425 if (m_playbackEngine)
426 m_playbackEngine->setPitchCompensation(enabled);
427 QPlatformMediaPlayer::pitchCompensationChanged(enabled);
428}
429
431{
432 return m_pitchCompensation;
433}
434
437{
438 return PitchCompensationAvailability::Available;
439}
440
441QT_END_NAMESPACE
442
443#include "moc_qffmpegmediaplayer_p.cpp"
void setActiveTrack(TrackType, int streamNumber) override
QMediaMetaData trackMetaData(TrackType type, int streamNumber) override
void setLoops(int loops) override
QVideoSink * videoSink() const
bool pitchCompensation() const override
void setAudioOutput(QPlatformAudioOutput *) override
qint64 duration() const override
float bufferProgress() const override
const QIODevice * mediaStream() const override
void setPitchCompensation(bool enabled) override
void setPosition(qint64 position) override
void setPlaybackRate(qreal rate) override
int activeTrack(TrackType) override
QUrl media() const override
QMediaTimeRange availablePlaybackRanges() const override
qreal playbackRate() const override
PitchCompensationAvailability pitchCompensationAvailability() const override
QMediaMetaData metaData() const override
void setMedia(const QUrl &media, QIODevice *stream) override
int trackCount(TrackType) override
bool isCancelled() const override
QT_MANGLE_NAMESPACE(QMacScreenCaptureStreamDelegate) QMacScreenCaptureStreamDelegate
Combined button and popup list for selecting options.