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"
8
10#include <qiodevice.h>
11#include <qvideosink.h>
12#include <qtimer.h>
13#include <QtConcurrent/QtConcurrent>
14
15#include <qloggingcategory.h>
16
18
19namespace QFFmpeg {
20
22{
23public:
24
25 bool isCancelled() const override { return m_cancelled.load(std::memory_order_acquire); }
26
27 void cancel() { m_cancelled.store(true, std::memory_order_release); }
28
29private:
30 std::atomic_bool m_cancelled = false;
31};
32
33} // namespace QFFmpeg
34
35using namespace QFFmpeg;
36
39{
40 m_positionUpdateTimer.setInterval(50);
41 m_positionUpdateTimer.setTimerType(Qt::PreciseTimer);
42 connect(&m_positionUpdateTimer, &QTimer::timeout, this, &QFFmpegMediaPlayer::updatePosition);
43}
44
46{
47 if (m_cancelToken)
48 m_cancelToken->cancel();
49
50 m_loadMedia.waitForFinished();
51};
52
54{
55 return m_playbackEngine ? m_playbackEngine->duration() / 1000 : 0;
56}
57
59{
61 return;
62
63 if (m_playbackEngine) {
64 m_playbackEngine->seek(position * 1000);
65 updatePosition();
66 }
67
68 mediaStatusChanged(QMediaPlayer::LoadedMedia);
69}
70
71void QFFmpegMediaPlayer::updatePosition()
72{
73 positionChanged(m_playbackEngine ? m_playbackEngine->currentPosition() / 1000 : 0);
74}
75
76void QFFmpegMediaPlayer::endOfStream()
77{
78 // start update timer and report end position anyway
79 m_positionUpdateTimer.stop();
81
83 mediaStatusChanged(QMediaPlayer::EndOfMedia);
84}
85
86void QFFmpegMediaPlayer::onLoopChanged()
87{
88 // report about finish and start
89 // reporting both signals is a bit contraversial
90 // but it eshures the idea of notifications about
91 // imporatant position points.
92 // Also, it ensures more predictable flow for testing.
95 m_positionUpdateTimer.stop();
96 m_positionUpdateTimer.start();
97}
98
99void QFFmpegMediaPlayer::onBuffered()
100{
102 mediaStatusChanged(QMediaPlayer::BufferedMedia);
103}
104
106{
107 return m_bufferProgress;
108}
109
110void QFFmpegMediaPlayer::mediaStatusChanged(QMediaPlayer::MediaStatus status)
111{
112 if (mediaStatus() == status)
113 return;
114
115 const auto newBufferProgress = status == QMediaPlayer::BufferingMedia ? 0.25f // to be improved
116 : status == QMediaPlayer::BufferedMedia ? 1.f
117 : 0.f;
118
119 if (!qFuzzyCompare(newBufferProgress, m_bufferProgress)) {
120 m_bufferProgress = newBufferProgress;
121 bufferProgressChanged(newBufferProgress);
122 }
123
125}
126
131
133{
134 return m_playbackRate;
135}
136
138{
139 const float effectiveRate = std::max(static_cast<float>(rate), 0.0f);
140
141 if (qFuzzyCompare(m_playbackRate, effectiveRate))
142 return;
143
144 m_playbackRate = effectiveRate;
145
146 if (m_playbackEngine)
147 m_playbackEngine->setPlaybackRate(effectiveRate);
148
149 emit playbackRateChanged(effectiveRate);
150}
151
153{
154 return m_url;
155}
156
158{
159 return m_device;
160}
161
162void QFFmpegMediaPlayer::handleIncorrectMedia(QMediaPlayer::MediaStatus status)
163{
164 seekableChanged(false);
168 mediaStatusChanged(status);
169 m_playbackEngine = nullptr;
170};
171
173{
174 // Wait for previous unfinished load attempts.
175 if (m_cancelToken)
176 m_cancelToken->cancel();
177
178 m_loadMedia.waitForFinished();
179
180 m_url = media;
181 m_device = stream;
182 m_playbackEngine = nullptr;
183
184 if (media.isEmpty() && !stream) {
185 handleIncorrectMedia(QMediaPlayer::NoMedia);
186 return;
187 }
188
189 mediaStatusChanged(QMediaPlayer::LoadingMedia);
190
191 m_requestedStatus = QMediaPlayer::StoppedState;
192
193 m_cancelToken = std::make_shared<CancelToken>();
194
195 // Load media asynchronously to keep GUI thread responsive while loading media
196 m_loadMedia = QtConcurrent::run([this, media, stream, cancelToken = m_cancelToken] {
197 // On worker thread
198 const MediaDataHolder::Maybe mediaHolder =
199 MediaDataHolder::create(media, stream, cancelToken);
200
201 // Transition back to calling thread using invokeMethod because
202 // QFuture continuations back on calling thread may deadlock (QTBUG-117918)
203 QMetaObject::invokeMethod(this, [this, mediaHolder, cancelToken] {
204 setMediaAsync(mediaHolder, cancelToken);
205 });
206 });
207}
208
209void QFFmpegMediaPlayer::setMediaAsync(QFFmpeg::MediaDataHolder::Maybe mediaDataHolder,
210 const std::shared_ptr<QFFmpeg::CancelToken> &cancelToken)
211{
213
214 // If loading was cancelled, we do not emit any signals about failing
215 // to load media (or any other events). The rationale is that cancellation
216 // either happens during destruction, where the signals are no longer
217 // of interest, or it happens as a response to user requesting to load
218 // another media file. In the latter case, we don't want to risk popping
219 // up error dialogs or similar.
220 if (cancelToken->isCancelled()) {
221 return;
222 }
223
224 if (!mediaDataHolder) {
225 const auto [code, description] = mediaDataHolder.error();
226 error(code, description);
227 handleIncorrectMedia(QMediaPlayer::MediaStatus::InvalidMedia);
228 return;
229 }
230
231 m_playbackEngine = std::make_unique<PlaybackEngine>();
232
233 connect(m_playbackEngine.get(), &PlaybackEngine::endOfStream, this,
234 &QFFmpegMediaPlayer::endOfStream);
235 connect(m_playbackEngine.get(), &PlaybackEngine::errorOccured, this,
236 &QFFmpegMediaPlayer::error);
237 connect(m_playbackEngine.get(), &PlaybackEngine::loopChanged, this,
238 &QFFmpegMediaPlayer::onLoopChanged);
239 connect(m_playbackEngine.get(), &PlaybackEngine::buffered, this,
240 &QFFmpegMediaPlayer::onBuffered);
241
242 m_playbackEngine->setMedia(std::move(*mediaDataHolder.value()));
243
244 m_playbackEngine->setAudioSink(m_audioOutput);
245 m_playbackEngine->setVideoSink(m_videoSink);
246 m_playbackEngine->setLoops(loops());
247 m_playbackEngine->setPlaybackRate(m_playbackRate);
248
252 seekableChanged(m_playbackEngine->isSeekable());
253
255 !m_playbackEngine->streamInfo(QPlatformMediaPlayer::AudioStream).isEmpty());
257 !m_playbackEngine->streamInfo(QPlatformMediaPlayer::VideoStream).isEmpty());
258
259 mediaStatusChanged(QMediaPlayer::LoadedMedia);
260
261 if (m_requestedStatus != QMediaPlayer::StoppedState) {
262 if (m_requestedStatus == QMediaPlayer::PlayingState)
263 play();
264 else if (m_requestedStatus == QMediaPlayer::PausedState)
265 pause();
266 }
267}
268
270{
272 m_requestedStatus = QMediaPlayer::PlayingState;
273 return;
274 }
275
276 if (!m_playbackEngine)
277 return;
278
280 m_playbackEngine->seek(0);
282 }
283
284 runPlayback();
285}
286
287void QFFmpegMediaPlayer::runPlayback()
288{
289 m_playbackEngine->play();
290 m_positionUpdateTimer.start();
292
294 mediaStatusChanged(QMediaPlayer::BufferingMedia);
295}
296
298{
300 m_requestedStatus = QMediaPlayer::PausedState;
301 return;
302 }
303
304 if (!m_playbackEngine)
305 return;
306
308 m_playbackEngine->seek(0);
310 }
311 m_playbackEngine->pause();
312 m_positionUpdateTimer.stop();
314
316 mediaStatusChanged(QMediaPlayer::BufferingMedia);
317}
318
320{
322 m_requestedStatus = QMediaPlayer::StoppedState;
323 return;
324 }
325
326 if (!m_playbackEngine)
327 return;
328
329 m_playbackEngine->stop();
330 m_positionUpdateTimer.stop();
331 m_playbackEngine->seek(0);
334 mediaStatusChanged(QMediaPlayer::LoadedMedia);
335}
336
338{
339 if (m_audioOutput == output)
340 return;
341
342 m_audioOutput = output;
343 if (m_playbackEngine)
344 m_playbackEngine->setAudioSink(output);
345}
346
348{
349 return m_playbackEngine ? m_playbackEngine->metaData() : QMediaMetaData{};
350}
351
353{
354 if (m_videoSink == sink)
355 return;
356
357 m_videoSink = sink;
358 if (m_playbackEngine)
359 m_playbackEngine->setVideoSink(sink);
360}
361
363{
364 return m_videoSink;
365}
366
368{
369 return m_playbackEngine ? m_playbackEngine->streamInfo(type).count() : 0;
370}
371
373{
374 if (!m_playbackEngine || streamNumber < 0
375 || streamNumber >= m_playbackEngine->streamInfo(type).count())
376 return {};
377 return m_playbackEngine->streamInfo(type).at(streamNumber).metaData;
378}
379
381{
382 return m_playbackEngine ? m_playbackEngine->activeTrack(type) : -1;
383}
384
386{
387 if (m_playbackEngine)
388 m_playbackEngine->setActiveTrack(type, streamNumber);
389 else
390 qWarning() << "Cannot set active track without open source";
391}
392
394{
395 if (m_playbackEngine)
396 m_playbackEngine->setLoops(loops);
397
399}
400
402
403#include "moc_qffmpegmediaplayer_p.cpp"
QMediaPlayer player
Definition audio.cpp:213
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 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 positionChanged(qint64 position)
virtual QMediaPlayer::MediaStatus mediaStatus() const
void seekableChanged(bool seekable)
void durationChanged(qint64 duration)
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:166
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
#define emit
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...