1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include <private/qgstreamerplayercontrol_p.h>
41#include <private/qgstreamerplayersession_p.h>
42
43#include <private/qmediaplaylistnavigator_p.h>
44#include <private/qmediaresourcepolicy_p.h>
45#include <private/qmediaresourceset_p.h>
46
47#include <QtCore/qdir.h>
48#include <QtCore/qsocketnotifier.h>
49#include <QtCore/qurl.h>
50#include <QtCore/qdebug.h>
51
52#include <sys/types.h>
53#include <sys/stat.h>
54#include <fcntl.h>
55
56//#define DEBUG_PLAYBIN
57
58QT_BEGIN_NAMESPACE
59
60QGstreamerPlayerControl::QGstreamerPlayerControl(QGstreamerPlayerSession *session, QObject *parent)
61 : QMediaPlayerControl(parent)
62 , m_session(session)
63{
64 m_resources = QMediaResourcePolicy::createResourceSet<QMediaPlayerResourceSetInterface>();
65 Q_ASSERT(m_resources);
66
67 connect(sender: m_session, signal: &QGstreamerPlayerSession::positionChanged, receiver: this, slot: &QGstreamerPlayerControl::positionChanged);
68 connect(sender: m_session, signal: &QGstreamerPlayerSession::durationChanged, receiver: this, slot: &QGstreamerPlayerControl::durationChanged);
69 connect(sender: m_session, signal: &QGstreamerPlayerSession::mutedStateChanged, receiver: this, slot: &QGstreamerPlayerControl::mutedChanged);
70 connect(sender: m_session, signal: &QGstreamerPlayerSession::volumeChanged, receiver: this, slot: &QGstreamerPlayerControl::volumeChanged);
71 connect(sender: m_session, signal: &QGstreamerPlayerSession::stateChanged, receiver: this, slot: &QGstreamerPlayerControl::updateSessionState);
72 connect(sender: m_session, signal: &QGstreamerPlayerSession::bufferingProgressChanged, receiver: this, slot: &QGstreamerPlayerControl::setBufferProgress);
73 connect(sender: m_session, signal: &QGstreamerPlayerSession::playbackFinished, receiver: this, slot: &QGstreamerPlayerControl::processEOS);
74 connect(sender: m_session, signal: &QGstreamerPlayerSession::audioAvailableChanged, receiver: this, slot: &QGstreamerPlayerControl::audioAvailableChanged);
75 connect(sender: m_session, signal: &QGstreamerPlayerSession::videoAvailableChanged, receiver: this, slot: &QGstreamerPlayerControl::videoAvailableChanged);
76 connect(sender: m_session, signal: &QGstreamerPlayerSession::seekableChanged, receiver: this, slot: &QGstreamerPlayerControl::seekableChanged);
77 connect(sender: m_session, signal: &QGstreamerPlayerSession::error, receiver: this, slot: &QGstreamerPlayerControl::error);
78 connect(sender: m_session, signal: &QGstreamerPlayerSession::invalidMedia, receiver: this, slot: &QGstreamerPlayerControl::handleInvalidMedia);
79 connect(sender: m_session, signal: &QGstreamerPlayerSession::playbackRateChanged, receiver: this, slot: &QGstreamerPlayerControl::playbackRateChanged);
80
81 connect(sender: m_resources, signal: &QMediaPlayerResourceSetInterface::resourcesGranted, receiver: this, slot: &QGstreamerPlayerControl::handleResourcesGranted);
82 //denied signal should be queued to have correct state update process,
83 //since in playOrPause, when acquire is call on resource set, it may trigger a resourcesDenied signal immediately,
84 //so handleResourcesDenied should be processed later, otherwise it will be overwritten by state update later in playOrPause.
85 connect(sender: m_resources, signal: &QMediaPlayerResourceSetInterface::resourcesDenied,
86 receiver: this, slot: &QGstreamerPlayerControl::handleResourcesDenied, type: Qt::QueuedConnection);
87 connect(sender: m_resources, signal: &QMediaPlayerResourceSetInterface::resourcesLost, receiver: this, slot: &QGstreamerPlayerControl::handleResourcesLost);
88}
89
90QGstreamerPlayerControl::~QGstreamerPlayerControl()
91{
92 QMediaResourcePolicy::destroyResourceSet(resourceSet: m_resources);
93}
94
95QMediaPlayerResourceSetInterface* QGstreamerPlayerControl::resources() const
96{
97 return m_resources;
98}
99
100qint64 QGstreamerPlayerControl::position() const
101{
102 if (m_mediaStatus == QMediaPlayer::EndOfMedia)
103 return m_session->duration();
104
105 return m_pendingSeekPosition != -1 ? m_pendingSeekPosition : m_session->position();
106}
107
108qint64 QGstreamerPlayerControl::duration() const
109{
110 return m_session->duration();
111}
112
113QMediaPlayer::State QGstreamerPlayerControl::state() const
114{
115 return m_currentState;
116}
117
118QMediaPlayer::MediaStatus QGstreamerPlayerControl::mediaStatus() const
119{
120 return m_mediaStatus;
121}
122
123int QGstreamerPlayerControl::bufferStatus() const
124{
125 if (m_bufferProgress == -1) {
126 return m_session->state() == QMediaPlayer::StoppedState ? 0 : 100;
127 } else
128 return m_bufferProgress;
129}
130
131int QGstreamerPlayerControl::volume() const
132{
133 return m_session->volume();
134}
135
136bool QGstreamerPlayerControl::isMuted() const
137{
138 return m_session->isMuted();
139}
140
141bool QGstreamerPlayerControl::isSeekable() const
142{
143 return m_session->isSeekable();
144}
145
146QMediaTimeRange QGstreamerPlayerControl::availablePlaybackRanges() const
147{
148 return m_session->availablePlaybackRanges();
149}
150
151qreal QGstreamerPlayerControl::playbackRate() const
152{
153 return m_session->playbackRate();
154}
155
156void QGstreamerPlayerControl::setPlaybackRate(qreal rate)
157{
158 m_session->setPlaybackRate(rate);
159}
160
161void QGstreamerPlayerControl::setPosition(qint64 pos)
162{
163#ifdef DEBUG_PLAYBIN
164 qDebug() << Q_FUNC_INFO << pos/1000.0;
165#endif
166
167 pushState();
168
169 if (m_mediaStatus == QMediaPlayer::EndOfMedia) {
170 m_mediaStatus = QMediaPlayer::LoadedMedia;
171 }
172
173 if (m_currentState == QMediaPlayer::StoppedState) {
174 m_pendingSeekPosition = pos;
175 emit positionChanged(position: m_pendingSeekPosition);
176 } else if (m_session->isSeekable()) {
177 m_session->showPrerollFrames(enabled: true);
178 m_session->seek(pos);
179 m_pendingSeekPosition = -1;
180 } else if (m_session->state() == QMediaPlayer::StoppedState) {
181 m_pendingSeekPosition = pos;
182 emit positionChanged(position: m_pendingSeekPosition);
183 } else if (m_pendingSeekPosition != -1) {
184 m_pendingSeekPosition = -1;
185 emit positionChanged(position: m_pendingSeekPosition);
186 }
187
188 popAndNotifyState();
189}
190
191void QGstreamerPlayerControl::play()
192{
193#ifdef DEBUG_PLAYBIN
194 qDebug() << Q_FUNC_INFO;
195#endif
196 //m_userRequestedState is needed to know that we need to resume playback when resource-policy
197 //regranted the resources after lost, since m_currentState will become paused when resources are
198 //lost.
199 m_userRequestedState = QMediaPlayer::PlayingState;
200 playOrPause(state: QMediaPlayer::PlayingState);
201}
202
203void QGstreamerPlayerControl::pause()
204{
205#ifdef DEBUG_PLAYBIN
206 qDebug() << Q_FUNC_INFO;
207#endif
208 m_userRequestedState = QMediaPlayer::PausedState;
209 // If the playback has not been started yet but pause is requested.
210 // Seek to the beginning to show first frame.
211 if (m_pendingSeekPosition == -1 && m_session->position() == 0)
212 m_pendingSeekPosition = 0;
213
214 playOrPause(state: QMediaPlayer::PausedState);
215}
216
217void QGstreamerPlayerControl::playOrPause(QMediaPlayer::State newState)
218{
219 if (m_mediaStatus == QMediaPlayer::NoMedia)
220 return;
221
222 pushState();
223
224 if (m_setMediaPending) {
225 m_mediaStatus = QMediaPlayer::LoadingMedia;
226 setMedia(m_currentResource, m_stream);
227 }
228
229 if (m_mediaStatus == QMediaPlayer::EndOfMedia && m_pendingSeekPosition == -1) {
230 m_pendingSeekPosition = 0;
231 }
232
233 if (!m_resources->isGranted())
234 m_resources->acquire();
235
236 if (m_resources->isGranted()) {
237 // show prerolled frame if switching from stopped state
238 if (m_pendingSeekPosition == -1) {
239 m_session->showPrerollFrames(enabled: true);
240 } else if (m_session->state() == QMediaPlayer::StoppedState) {
241 // Don't evaluate the next two conditions.
242 } else if (m_session->isSeekable()) {
243 m_session->pause();
244 m_session->showPrerollFrames(enabled: true);
245 m_session->seek(pos: m_pendingSeekPosition);
246 m_pendingSeekPosition = -1;
247 } else {
248 m_pendingSeekPosition = -1;
249 }
250
251 bool ok = false;
252
253 //To prevent displaying the first video frame when playback is resumed
254 //the pipeline is paused instead of playing, seeked to requested position,
255 //and after seeking is finished (position updated) playback is restarted
256 //with show-preroll-frame enabled.
257 if (newState == QMediaPlayer::PlayingState && m_pendingSeekPosition == -1)
258 ok = m_session->play();
259 else
260 ok = m_session->pause();
261
262 if (!ok)
263 newState = QMediaPlayer::StoppedState;
264 }
265
266 if (m_mediaStatus == QMediaPlayer::InvalidMedia)
267 m_mediaStatus = QMediaPlayer::LoadingMedia;
268
269 m_currentState = newState;
270
271 if (m_mediaStatus == QMediaPlayer::EndOfMedia || m_mediaStatus == QMediaPlayer::LoadedMedia) {
272 if (m_bufferProgress == -1 || m_bufferProgress == 100)
273 m_mediaStatus = QMediaPlayer::BufferedMedia;
274 else
275 m_mediaStatus = QMediaPlayer::BufferingMedia;
276 }
277
278 popAndNotifyState();
279
280 emit positionChanged(position: position());
281}
282
283void QGstreamerPlayerControl::stop()
284{
285#ifdef DEBUG_PLAYBIN
286 qDebug() << Q_FUNC_INFO;
287#endif
288 m_userRequestedState = QMediaPlayer::StoppedState;
289
290 pushState();
291
292 if (m_currentState != QMediaPlayer::StoppedState) {
293 m_currentState = QMediaPlayer::StoppedState;
294 m_session->showPrerollFrames(enabled: false); // stop showing prerolled frames in stop state
295 // Since gst is not going to send GST_STATE_PAUSED
296 // when pipeline is already paused,
297 // needs to update media status directly.
298 if (m_session->state() == QMediaPlayer::PausedState)
299 updateMediaStatus();
300 else if (m_resources->isGranted())
301 m_session->pause();
302
303 if (m_mediaStatus != QMediaPlayer::EndOfMedia) {
304 m_pendingSeekPosition = 0;
305 emit positionChanged(position: position());
306 }
307 }
308
309 popAndNotifyState();
310}
311
312void QGstreamerPlayerControl::setVolume(int volume)
313{
314 m_session->setVolume(volume);
315}
316
317void QGstreamerPlayerControl::setMuted(bool muted)
318{
319 m_session->setMuted(muted);
320}
321
322QMediaContent QGstreamerPlayerControl::media() const
323{
324 return m_currentResource;
325}
326
327const QIODevice *QGstreamerPlayerControl::mediaStream() const
328{
329 return m_stream;
330}
331
332void QGstreamerPlayerControl::setMedia(const QMediaContent &content, QIODevice *stream)
333{
334#ifdef DEBUG_PLAYBIN
335 qDebug() << Q_FUNC_INFO;
336#endif
337
338 pushState();
339
340 m_currentState = QMediaPlayer::StoppedState;
341 QMediaContent oldMedia = m_currentResource;
342 m_pendingSeekPosition = -1;
343 m_session->showPrerollFrames(enabled: false); // do not show prerolled frames until pause() or play() explicitly called
344 m_setMediaPending = false;
345
346 if (!content.isNull() || stream) {
347 if (!m_resources->isGranted())
348 m_resources->acquire();
349 } else {
350 m_resources->release();
351 }
352
353 m_session->stop();
354
355 bool userStreamValid = false;
356
357 if (m_bufferProgress != -1) {
358 m_bufferProgress = -1;
359 emit bufferStatusChanged(percentFilled: 0);
360 }
361
362 m_currentResource = content;
363 m_stream = stream;
364
365 QNetworkRequest request = content.request();
366
367 if (m_stream)
368 userStreamValid = stream->isOpen() && m_stream->isReadable();
369
370#if !QT_CONFIG(gstreamer_app)
371 m_session->loadFromUri(request);
372#else
373 if (m_stream) {
374 if (userStreamValid){
375 m_session->loadFromStream(url: request, stream: m_stream);
376 } else {
377 m_mediaStatus = QMediaPlayer::InvalidMedia;
378 emit error(error: QMediaPlayer::FormatError, errorString: tr(s: "Attempting to play invalid user stream"));
379 if (m_currentState != QMediaPlayer::PlayingState)
380 m_resources->release();
381 popAndNotifyState();
382 return;
383 }
384 } else
385 m_session->loadFromUri(url: request);
386#endif
387
388#if QT_CONFIG(gstreamer_app)
389 if (!request.url().isEmpty() || userStreamValid) {
390#else
391 if (!request.url().isEmpty()) {
392#endif
393 m_mediaStatus = QMediaPlayer::LoadingMedia;
394 m_session->pause();
395 } else {
396 m_mediaStatus = QMediaPlayer::NoMedia;
397 setBufferProgress(0);
398 }
399
400 if (m_currentResource != oldMedia)
401 emit mediaChanged(content: m_currentResource);
402
403 emit positionChanged(position: position());
404
405 if (content.isNull() && !stream)
406 m_resources->release();
407
408 popAndNotifyState();
409}
410
411void QGstreamerPlayerControl::setVideoOutput(QObject *output)
412{
413 m_session->setVideoRenderer(output);
414}
415
416bool QGstreamerPlayerControl::isAudioAvailable() const
417{
418 return m_session->isAudioAvailable();
419}
420
421bool QGstreamerPlayerControl::isVideoAvailable() const
422{
423 return m_session->isVideoAvailable();
424}
425
426void QGstreamerPlayerControl::updateSessionState(QMediaPlayer::State state)
427{
428 pushState();
429
430 if (state == QMediaPlayer::StoppedState) {
431 m_session->showPrerollFrames(enabled: false);
432 m_currentState = QMediaPlayer::StoppedState;
433 }
434
435 if (state == QMediaPlayer::PausedState && m_currentState != QMediaPlayer::StoppedState) {
436 if (m_pendingSeekPosition != -1 && m_session->isSeekable()) {
437 m_session->showPrerollFrames(enabled: true);
438 m_session->seek(pos: m_pendingSeekPosition);
439 }
440 m_pendingSeekPosition = -1;
441
442 if (m_currentState == QMediaPlayer::PlayingState) {
443 if (m_bufferProgress == -1 || m_bufferProgress == 100)
444 m_session->play();
445 }
446 }
447
448 updateMediaStatus();
449
450 popAndNotifyState();
451}
452
453void QGstreamerPlayerControl::updateMediaStatus()
454{
455 //EndOfMedia status should be kept, until reset by pause, play or setMedia
456 if (m_mediaStatus == QMediaPlayer::EndOfMedia)
457 return;
458
459 pushState();
460 QMediaPlayer::MediaStatus oldStatus = m_mediaStatus;
461
462 switch (m_session->state()) {
463 case QMediaPlayer::StoppedState:
464 if (m_currentResource.isNull())
465 m_mediaStatus = QMediaPlayer::NoMedia;
466 else if (oldStatus != QMediaPlayer::InvalidMedia)
467 m_mediaStatus = QMediaPlayer::LoadingMedia;
468 break;
469
470 case QMediaPlayer::PlayingState:
471 case QMediaPlayer::PausedState:
472 if (m_currentState == QMediaPlayer::StoppedState) {
473 m_mediaStatus = QMediaPlayer::LoadedMedia;
474 } else {
475 if (m_bufferProgress == -1 || m_bufferProgress == 100)
476 m_mediaStatus = QMediaPlayer::BufferedMedia;
477 else
478 m_mediaStatus = QMediaPlayer::StalledMedia;
479 }
480 break;
481 }
482
483 if (m_currentState == QMediaPlayer::PlayingState && !m_resources->isGranted())
484 m_mediaStatus = QMediaPlayer::StalledMedia;
485
486 popAndNotifyState();
487}
488
489void QGstreamerPlayerControl::processEOS()
490{
491 pushState();
492 m_mediaStatus = QMediaPlayer::EndOfMedia;
493 emit positionChanged(position: position());
494 m_session->endOfMediaReset();
495
496 if (m_currentState != QMediaPlayer::StoppedState) {
497 m_currentState = QMediaPlayer::StoppedState;
498 m_session->showPrerollFrames(enabled: false); // stop showing prerolled frames in stop state
499 }
500
501 popAndNotifyState();
502}
503
504void QGstreamerPlayerControl::setBufferProgress(int progress)
505{
506 if (m_bufferProgress == progress || m_mediaStatus == QMediaPlayer::NoMedia)
507 return;
508
509#ifdef DEBUG_PLAYBIN
510 qDebug() << Q_FUNC_INFO << progress;
511#endif
512 m_bufferProgress = progress;
513
514 if (m_resources->isGranted()) {
515 if (m_currentState == QMediaPlayer::PlayingState &&
516 m_bufferProgress == 100 &&
517 m_session->state() != QMediaPlayer::PlayingState)
518 m_session->play();
519
520 if (!m_session->isLiveSource() && m_bufferProgress < 100 &&
521 (m_session->state() == QMediaPlayer::PlayingState ||
522 m_session->pendingState() == QMediaPlayer::PlayingState))
523 m_session->pause();
524 }
525
526 updateMediaStatus();
527
528 emit bufferStatusChanged(percentFilled: m_bufferProgress);
529}
530
531void QGstreamerPlayerControl::handleInvalidMedia()
532{
533 pushState();
534 m_mediaStatus = QMediaPlayer::InvalidMedia;
535 m_currentState = QMediaPlayer::StoppedState;
536 m_setMediaPending = true;
537 popAndNotifyState();
538}
539
540void QGstreamerPlayerControl::handleResourcesGranted()
541{
542 pushState();
543
544 //This may be triggered when there is an auto resume
545 //from resource-policy, we need to take action according to m_userRequestedState
546 //rather than m_currentState
547 m_currentState = m_userRequestedState;
548 if (m_currentState != QMediaPlayer::StoppedState)
549 playOrPause(newState: m_currentState);
550 else
551 updateMediaStatus();
552
553 popAndNotifyState();
554}
555
556void QGstreamerPlayerControl::handleResourcesLost()
557{
558 //on resource lost the pipeline should be paused
559 //player status is changed to paused
560 pushState();
561 QMediaPlayer::State oldState = m_currentState;
562
563 m_session->pause();
564
565 if (oldState != QMediaPlayer::StoppedState )
566 m_currentState = QMediaPlayer::PausedState;
567
568 popAndNotifyState();
569}
570
571void QGstreamerPlayerControl::handleResourcesDenied()
572{
573 //on resource denied the pipeline should stay paused
574 //player status is changed to paused
575 pushState();
576
577 if (m_currentState != QMediaPlayer::StoppedState )
578 m_currentState = QMediaPlayer::PausedState;
579
580 popAndNotifyState();
581}
582
583void QGstreamerPlayerControl::pushState()
584{
585 m_stateStack.push(t: m_currentState);
586 m_mediaStatusStack.push(t: m_mediaStatus);
587}
588
589void QGstreamerPlayerControl::popAndNotifyState()
590{
591 Q_ASSERT(!m_stateStack.isEmpty());
592
593 QMediaPlayer::State oldState = m_stateStack.pop();
594 QMediaPlayer::MediaStatus oldMediaStatus = m_mediaStatusStack.pop();
595
596 if (m_stateStack.isEmpty()) {
597 if (m_mediaStatus != oldMediaStatus) {
598#ifdef DEBUG_PLAYBIN
599 qDebug() << "Media status changed:" << m_mediaStatus;
600#endif
601 emit mediaStatusChanged(status: m_mediaStatus);
602 }
603
604 if (m_currentState != oldState) {
605#ifdef DEBUG_PLAYBIN
606 qDebug() << "State changed:" << m_currentState;
607#endif
608 emit stateChanged(newState: m_currentState);
609 }
610 }
611}
612
613QT_END_NAMESPACE
614

source code of qtmultimedia/src/gsttools/qgstreamerplayercontrol.cpp