Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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"
9
11#include <qiodevice.h>
12#include <qvideosink.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
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 ? m_playbackEngine->duration() / 1000 : 0;
57}
58
60{
62 return;
63
64 if (m_playbackEngine) {
65 m_playbackEngine->seek(position * 1000);
66 updatePosition();
67 }
68
69 mediaStatusChanged(QMediaPlayer::LoadedMedia);
70}
71
72void QFFmpegMediaPlayer::updatePosition()
73{
74 positionChanged(m_playbackEngine ? m_playbackEngine->currentPosition() / 1000 : 0);
75}
76
77void QFFmpegMediaPlayer::endOfStream()
78{
79 // stop update timer and report end position anyway
80 m_positionUpdateTimer.stop();
81 QPointer currentPlaybackEngine(m_playbackEngine.get());
83
84 // skip changing state and mediaStatus if playbackEngine has been recreated,
85 // e.g. if new media has been loaded as a response to positionChanged signal
86 if (currentPlaybackEngine)
88 if (currentPlaybackEngine)
89 mediaStatusChanged(QMediaPlayer::EndOfMedia);
90}
91
92void QFFmpegMediaPlayer::onLoopChanged()
93{
94 // report about finish and start
95 // reporting both signals is a bit contraversial
96 // but it eshures the idea of notifications about
97 // imporatant position points.
98 // Also, it ensures more predictable flow for testing.
101 m_positionUpdateTimer.stop();
102 m_positionUpdateTimer.start();
103}
104
105void QFFmpegMediaPlayer::onBuffered()
106{
108 mediaStatusChanged(QMediaPlayer::BufferedMedia);
109}
110
112{
113 return m_bufferProgress;
114}
115
116void QFFmpegMediaPlayer::mediaStatusChanged(QMediaPlayer::MediaStatus status)
117{
118 if (mediaStatus() == status)
119 return;
120
121 const auto newBufferProgress = status == QMediaPlayer::BufferingMedia ? 0.25f // to be improved
122 : status == QMediaPlayer::BufferedMedia ? 1.f
123 : 0.f;
124
125 if (!qFuzzyCompare(newBufferProgress, m_bufferProgress)) {
126 m_bufferProgress = newBufferProgress;
127 bufferProgressChanged(newBufferProgress);
128 }
129
131}
132
137
139{
140 return m_playbackRate;
141}
142
144{
145 const float effectiveRate = std::max(static_cast<float>(rate), 0.0f);
146
147 if (qFuzzyCompare(m_playbackRate, effectiveRate))
148 return;
149
150 m_playbackRate = effectiveRate;
151
152 if (m_playbackEngine)
153 m_playbackEngine->setPlaybackRate(effectiveRate);
154
155 playbackRateChanged(effectiveRate);
156}
157
159{
160 return m_url;
161}
162
164{
165 return m_device;
166}
167
168void QFFmpegMediaPlayer::handleIncorrectMedia(QMediaPlayer::MediaStatus status)
169{
170 seekableChanged(false);
174 mediaStatusChanged(status);
175 m_playbackEngine = nullptr;
176};
177
179{
180 // Wait for previous unfinished load attempts.
181 if (m_cancelToken)
182 m_cancelToken->cancel();
183
184 m_loadMedia.waitForFinished();
185
186 m_url = media;
187 m_device = stream;
188 m_playbackEngine = nullptr;
189
190 if (media.isEmpty() && !stream) {
191 handleIncorrectMedia(QMediaPlayer::NoMedia);
192 return;
193 }
194
195 mediaStatusChanged(QMediaPlayer::LoadingMedia);
196
197 m_requestedStatus = QMediaPlayer::StoppedState;
198
199 m_cancelToken = std::make_shared<CancelToken>();
200
201 // Load media asynchronously to keep GUI thread responsive while loading media
202 m_loadMedia = QtConcurrent::run([this, media, stream, cancelToken = m_cancelToken] {
203 // On worker thread
204 const MediaDataHolder::Maybe mediaHolder =
205 MediaDataHolder::create(media, stream, cancelToken);
206
207 // Transition back to calling thread using invokeMethod because
208 // QFuture continuations back on calling thread may deadlock (QTBUG-117918)
209 QMetaObject::invokeMethod(this, [this, mediaHolder, cancelToken] {
210 setMediaAsync(mediaHolder, cancelToken);
211 });
212 });
213}
214
215void QFFmpegMediaPlayer::setMediaAsync(QFFmpeg::MediaDataHolder::Maybe mediaDataHolder,
216 const std::shared_ptr<QFFmpeg::CancelToken> &cancelToken)
217{
219
220 // If loading was cancelled, we do not emit any signals about failing
221 // to load media (or any other events). The rationale is that cancellation
222 // either happens during destruction, where the signals are no longer
223 // of interest, or it happens as a response to user requesting to load
224 // another media file. In the latter case, we don't want to risk popping
225 // up error dialogs or similar.
226 if (cancelToken->isCancelled()) {
227 return;
228 }
229
230 if (!mediaDataHolder) {
231 const auto [code, description] = mediaDataHolder.error();
232 error(code, description);
233 handleIncorrectMedia(QMediaPlayer::MediaStatus::InvalidMedia);
234 return;
235 }
236
237 m_playbackEngine = std::make_unique<PlaybackEngine>();
238
239 connect(m_playbackEngine.get(), &PlaybackEngine::endOfStream, this,
240 &QFFmpegMediaPlayer::endOfStream);
241 connect(m_playbackEngine.get(), &PlaybackEngine::errorOccured, this,
242 &QFFmpegMediaPlayer::error);
243 connect(m_playbackEngine.get(), &PlaybackEngine::loopChanged, this,
244 &QFFmpegMediaPlayer::onLoopChanged);
245 connect(m_playbackEngine.get(), &PlaybackEngine::buffered, this,
246 &QFFmpegMediaPlayer::onBuffered);
247
248 m_playbackEngine->setMedia(std::move(*mediaDataHolder.value()));
249
250 m_playbackEngine->setAudioBufferOutput(m_audioBufferOutput);
251 m_playbackEngine->setAudioSink(m_audioOutput);
252 m_playbackEngine->setVideoSink(m_videoSink);
253
254 m_playbackEngine->setLoops(loops());
255 m_playbackEngine->setPlaybackRate(m_playbackRate);
256
260 seekableChanged(m_playbackEngine->isSeekable());
261
263 !m_playbackEngine->streamInfo(QPlatformMediaPlayer::AudioStream).isEmpty());
265 !m_playbackEngine->streamInfo(QPlatformMediaPlayer::VideoStream).isEmpty());
266
267 mediaStatusChanged(QMediaPlayer::LoadedMedia);
268
269 if (m_requestedStatus != QMediaPlayer::StoppedState) {
270 if (m_requestedStatus == QMediaPlayer::PlayingState)
271 play();
272 else if (m_requestedStatus == QMediaPlayer::PausedState)
273 pause();
274 }
275}
276
278{
280 m_requestedStatus = QMediaPlayer::PlayingState;
281 return;
282 }
283
284 if (!m_playbackEngine)
285 return;
286
288 m_playbackEngine->seek(0);
290 }
291
292 runPlayback();
293}
294
295void QFFmpegMediaPlayer::runPlayback()
296{
297 m_playbackEngine->play();
298 m_positionUpdateTimer.start();
300
302 mediaStatusChanged(QMediaPlayer::BufferingMedia);
303}
304
306{
308 m_requestedStatus = QMediaPlayer::PausedState;
309 return;
310 }
311
312 if (!m_playbackEngine)
313 return;
314
316 m_playbackEngine->seek(0);
318 }
319 m_playbackEngine->pause();
320 m_positionUpdateTimer.stop();
322
324 mediaStatusChanged(QMediaPlayer::BufferingMedia);
325}
326
328{
330 m_requestedStatus = QMediaPlayer::StoppedState;
331 return;
332 }
333
334 if (!m_playbackEngine)
335 return;
336
337 m_playbackEngine->stop();
338 m_positionUpdateTimer.stop();
339 m_playbackEngine->seek(0);
342 mediaStatusChanged(QMediaPlayer::LoadedMedia);
343}
344
346{
347 m_audioOutput = output;
348 if (m_playbackEngine)
349 m_playbackEngine->setAudioSink(output);
350}
351
353 m_audioBufferOutput = output;
354 if (m_playbackEngine)
355 m_playbackEngine->setAudioBufferOutput(output);
356}
357
359{
360 return m_playbackEngine ? m_playbackEngine->metaData() : QMediaMetaData{};
361}
362
364{
365 m_videoSink = sink;
366 if (m_playbackEngine)
367 m_playbackEngine->setVideoSink(sink);
368}
369
371{
372 return m_videoSink;
373}
374
376{
377 return m_playbackEngine ? m_playbackEngine->streamInfo(type).count() : 0;
378}
379
381{
382 if (!m_playbackEngine || streamNumber < 0
383 || streamNumber >= m_playbackEngine->streamInfo(type).count())
384 return {};
385 return m_playbackEngine->streamInfo(type).at(streamNumber).metaData;
386}
387
389{
390 return m_playbackEngine ? m_playbackEngine->activeTrack(type) : -1;
391}
392
394{
395 if (m_playbackEngine)
396 m_playbackEngine->setActiveTrack(type, streamNumber);
397 else
398 qWarning() << "Cannot set active track without open source";
399}
400
402{
403 if (m_playbackEngine)
404 m_playbackEngine->setLoops(loops);
405
407}
408
410
411#include "moc_qffmpegmediaplayer_p.cpp"
QMediaPlayer player
Definition audio.cpp:213
\inmodule QtMultimedia
void setActiveTrack(TrackType, int streamNumber) override
void setVideoSink(QVideoSink *sink) override
QMediaMetaData trackMetaData(TrackType type, int streamNumber) override
void setLoops(int loops) override
QVideoSink * videoSink() const
void setAudioOutput(QPlatformAudioOutput *) override
qint64 duration() const override
float bufferProgress() const override
const QIODevice * mediaStream() const 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
QFFmpegMediaPlayer(QMediaPlayer *player)
QMediaMetaData metaData() const override
void setAudioBufferOutput(QAudioBufferOutput *) override
void setMedia(const QUrl &media, QIODevice *stream) override
int trackCount(TrackType) override
bool isCancelled() const override
static Maybe create(const QUrl &url, QIODevice *stream, const std::shared_ptr< ICancelToken > &cancelToken)
void errorOccured(int, const QString &)
void waitForFinished()
Definition qfuture.h:102
\inmodule QtCore \reentrant
Definition qiodevice.h:34
\inmodule QtMultimedia
The QMediaPlayer class allows the playing of a media files.
MediaStatus
\qmlproperty enumeration QtMultimedia::MediaPlayer::playbackState
The QMediaTimeRange class represents a set of zero or more disjoint time intervals.
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
virtual QMediaPlayer::PlaybackState state() const
void durationChanged(std::chrono::milliseconds ms)
void positionChanged(std::chrono::milliseconds ms)
virtual QMediaPlayer::MediaStatus mediaStatus() const
void seekableChanged(bool seekable)
void playbackRateChanged(qreal rate)
void audioAvailableChanged(bool audioAvailable)
void stateChanged(QMediaPlayer::PlaybackState newState)
void videoAvailableChanged(bool videoAvailable)
virtual qint64 position() const
virtual void setLoops(int loops)
void mediaStatusChanged(QMediaPlayer::MediaStatus status)
void bufferProgressChanged(float progress)
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
void setInterval(int msec)
Definition qtimer.cpp:579
void stop()
Stops the timer.
Definition qtimer.cpp:267
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
void setTimerType(Qt::TimerType atype)
Definition qtimer.cpp:651
\inmodule QtCore
Definition qurl.h:94
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
Definition qurl.cpp:1896
The QVideoSink class represents a generic sink for video data.
Definition qvideosink.h:22
Combined button and popup list for selecting options.
QTCONCURRENT_RUN_NODISCARD auto run(QThreadPool *pool, Function &&f, Args &&...args)
@ PreciseTimer
DBusConnection const char DBusError * error
EGLStreamKHR stream
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
#define qWarning
Definition qlogging.h:167
GLenum type
GLuint GLenum * rate
GLsizei GLenum GLboolean sink
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
QT_BEGIN_NAMESPACE typedef uchar * output
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...