| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <private/qgstreamerplayercontrol_p.h> |
| #include <private/qgstreamerplayersession_p.h> |
| |
| #include <private/qmediaplaylistnavigator_p.h> |
| #include <private/qmediaresourcepolicy_p.h> |
| #include <private/qmediaresourceset_p.h> |
| |
| #include <QtCore/qdir.h> |
| #include <QtCore/qsocketnotifier.h> |
| #include <QtCore/qurl.h> |
| #include <QtCore/qdebug.h> |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| |
| //#define DEBUG_PLAYBIN |
| |
| QT_BEGIN_NAMESPACE |
| |
| QGstreamerPlayerControl::QGstreamerPlayerControl(QGstreamerPlayerSession *session, QObject *parent) |
| : QMediaPlayerControl(parent) |
| , m_session(session) |
| { |
| m_resources = QMediaResourcePolicy::createResourceSet<QMediaPlayerResourceSetInterface>(); |
| Q_ASSERT(m_resources); |
| |
| connect(m_session, &QGstreamerPlayerSession::positionChanged, this, &QGstreamerPlayerControl::positionChanged); |
| connect(m_session, &QGstreamerPlayerSession::durationChanged, this, &QGstreamerPlayerControl::durationChanged); |
| connect(m_session, &QGstreamerPlayerSession::mutedStateChanged, this, &QGstreamerPlayerControl::mutedChanged); |
| connect(m_session, &QGstreamerPlayerSession::volumeChanged, this, &QGstreamerPlayerControl::volumeChanged); |
| connect(m_session, &QGstreamerPlayerSession::stateChanged, this, &QGstreamerPlayerControl::updateSessionState); |
| connect(m_session, &QGstreamerPlayerSession::bufferingProgressChanged, this, &QGstreamerPlayerControl::setBufferProgress); |
| connect(m_session, &QGstreamerPlayerSession::playbackFinished, this, &QGstreamerPlayerControl::processEOS); |
| connect(m_session, &QGstreamerPlayerSession::audioAvailableChanged, this, &QGstreamerPlayerControl::audioAvailableChanged); |
| connect(m_session, &QGstreamerPlayerSession::videoAvailableChanged, this, &QGstreamerPlayerControl::videoAvailableChanged); |
| connect(m_session, &QGstreamerPlayerSession::seekableChanged, this, &QGstreamerPlayerControl::seekableChanged); |
| connect(m_session, &QGstreamerPlayerSession::error, this, &QGstreamerPlayerControl::error); |
| connect(m_session, &QGstreamerPlayerSession::invalidMedia, this, &QGstreamerPlayerControl::handleInvalidMedia); |
| connect(m_session, &QGstreamerPlayerSession::playbackRateChanged, this, &QGstreamerPlayerControl::playbackRateChanged); |
| |
| connect(m_resources, &QMediaPlayerResourceSetInterface::resourcesGranted, this, &QGstreamerPlayerControl::handleResourcesGranted); |
| //denied signal should be queued to have correct state update process, |
| //since in playOrPause, when acquire is call on resource set, it may trigger a resourcesDenied signal immediately, |
| //so handleResourcesDenied should be processed later, otherwise it will be overwritten by state update later in playOrPause. |
| connect(m_resources, &QMediaPlayerResourceSetInterface::resourcesDenied, |
| this, &QGstreamerPlayerControl::handleResourcesDenied, Qt::QueuedConnection); |
| connect(m_resources, &QMediaPlayerResourceSetInterface::resourcesLost, this, &QGstreamerPlayerControl::handleResourcesLost); |
| } |
| |
| QGstreamerPlayerControl::~QGstreamerPlayerControl() |
| { |
| QMediaResourcePolicy::destroyResourceSet(m_resources); |
| } |
| |
| QMediaPlayerResourceSetInterface* QGstreamerPlayerControl::resources() const |
| { |
| return m_resources; |
| } |
| |
| qint64 QGstreamerPlayerControl::position() const |
| { |
| if (m_mediaStatus == QMediaPlayer::EndOfMedia) |
| return m_session->duration(); |
| |
| return m_pendingSeekPosition != -1 ? m_pendingSeekPosition : m_session->position(); |
| } |
| |
| qint64 QGstreamerPlayerControl::duration() const |
| { |
| return m_session->duration(); |
| } |
| |
| QMediaPlayer::State QGstreamerPlayerControl::state() const |
| { |
| return m_currentState; |
| } |
| |
| QMediaPlayer::MediaStatus QGstreamerPlayerControl::mediaStatus() const |
| { |
| return m_mediaStatus; |
| } |
| |
| int QGstreamerPlayerControl::bufferStatus() const |
| { |
| if (m_bufferProgress == -1) { |
| return m_session->state() == QMediaPlayer::StoppedState ? 0 : 100; |
| } else |
| return m_bufferProgress; |
| } |
| |
| int QGstreamerPlayerControl::volume() const |
| { |
| return m_session->volume(); |
| } |
| |
| bool QGstreamerPlayerControl::isMuted() const |
| { |
| return m_session->isMuted(); |
| } |
| |
| bool QGstreamerPlayerControl::isSeekable() const |
| { |
| return m_session->isSeekable(); |
| } |
| |
| QMediaTimeRange QGstreamerPlayerControl::availablePlaybackRanges() const |
| { |
| return m_session->availablePlaybackRanges(); |
| } |
| |
| qreal QGstreamerPlayerControl::playbackRate() const |
| { |
| return m_session->playbackRate(); |
| } |
| |
| void QGstreamerPlayerControl::setPlaybackRate(qreal rate) |
| { |
| m_session->setPlaybackRate(rate); |
| } |
| |
| void QGstreamerPlayerControl::setPosition(qint64 pos) |
| { |
| #ifdef DEBUG_PLAYBIN |
| qDebug() << Q_FUNC_INFO << pos/1000.0; |
| #endif |
| |
| pushState(); |
| |
| if (m_mediaStatus == QMediaPlayer::EndOfMedia) { |
| m_mediaStatus = QMediaPlayer::LoadedMedia; |
| } |
| |
| if (m_currentState == QMediaPlayer::StoppedState) { |
| m_pendingSeekPosition = pos; |
| emit positionChanged(m_pendingSeekPosition); |
| } else if (m_session->isSeekable()) { |
| m_session->showPrerollFrames(true); |
| m_session->seek(pos); |
| m_pendingSeekPosition = -1; |
| } else if (m_session->state() == QMediaPlayer::StoppedState) { |
| m_pendingSeekPosition = pos; |
| emit positionChanged(m_pendingSeekPosition); |
| } else if (m_pendingSeekPosition != -1) { |
| m_pendingSeekPosition = -1; |
| emit positionChanged(m_pendingSeekPosition); |
| } |
| |
| popAndNotifyState(); |
| } |
| |
| void QGstreamerPlayerControl::play() |
| { |
| #ifdef DEBUG_PLAYBIN |
| qDebug() << Q_FUNC_INFO; |
| #endif |
| //m_userRequestedState is needed to know that we need to resume playback when resource-policy |
| //regranted the resources after lost, since m_currentState will become paused when resources are |
| //lost. |
| m_userRequestedState = QMediaPlayer::PlayingState; |
| playOrPause(QMediaPlayer::PlayingState); |
| } |
| |
| void QGstreamerPlayerControl::pause() |
| { |
| #ifdef DEBUG_PLAYBIN |
| qDebug() << Q_FUNC_INFO; |
| #endif |
| m_userRequestedState = QMediaPlayer::PausedState; |
| // If the playback has not been started yet but pause is requested. |
| // Seek to the beginning to show first frame. |
| if (m_pendingSeekPosition == -1 && m_session->position() == 0) |
| m_pendingSeekPosition = 0; |
| |
| playOrPause(QMediaPlayer::PausedState); |
| } |
| |
| void QGstreamerPlayerControl::playOrPause(QMediaPlayer::State newState) |
| { |
| if (m_mediaStatus == QMediaPlayer::NoMedia) |
| return; |
| |
| pushState(); |
| |
| if (m_setMediaPending) { |
| m_mediaStatus = QMediaPlayer::LoadingMedia; |
| setMedia(m_currentResource, m_stream); |
| } |
| |
| if (m_mediaStatus == QMediaPlayer::EndOfMedia && m_pendingSeekPosition == -1) { |
| m_pendingSeekPosition = 0; |
| } |
| |
| if (!m_resources->isGranted()) |
| m_resources->acquire(); |
| |
| if (m_resources->isGranted()) { |
| // show prerolled frame if switching from stopped state |
| if (m_pendingSeekPosition == -1) { |
| m_session->showPrerollFrames(true); |
| } else if (m_session->state() == QMediaPlayer::StoppedState) { |
| // Don't evaluate the next two conditions. |
| } else if (m_session->isSeekable()) { |
| m_session->pause(); |
| m_session->showPrerollFrames(true); |
| m_session->seek(m_pendingSeekPosition); |
| m_pendingSeekPosition = -1; |
| } else { |
| m_pendingSeekPosition = -1; |
| } |
| |
| bool ok = false; |
| |
| //To prevent displaying the first video frame when playback is resumed |
| //the pipeline is paused instead of playing, seeked to requested position, |
| //and after seeking is finished (position updated) playback is restarted |
| //with show-preroll-frame enabled. |
| if (newState == QMediaPlayer::PlayingState && m_pendingSeekPosition == -1) |
| ok = m_session->play(); |
| else |
| ok = m_session->pause(); |
| |
| if (!ok) |
| newState = QMediaPlayer::StoppedState; |
| } |
| |
| if (m_mediaStatus == QMediaPlayer::InvalidMedia) |
| m_mediaStatus = QMediaPlayer::LoadingMedia; |
| |
| m_currentState = newState; |
| |
| if (m_mediaStatus == QMediaPlayer::EndOfMedia || m_mediaStatus == QMediaPlayer::LoadedMedia) { |
| if (m_bufferProgress == -1 || m_bufferProgress == 100) |
| m_mediaStatus = QMediaPlayer::BufferedMedia; |
| else |
| m_mediaStatus = QMediaPlayer::BufferingMedia; |
| } |
| |
| popAndNotifyState(); |
| |
| emit positionChanged(position()); |
| } |
| |
| void QGstreamerPlayerControl::stop() |
| { |
| #ifdef DEBUG_PLAYBIN |
| qDebug() << Q_FUNC_INFO; |
| #endif |
| m_userRequestedState = QMediaPlayer::StoppedState; |
| |
| pushState(); |
| |
| if (m_currentState != QMediaPlayer::StoppedState) { |
| m_currentState = QMediaPlayer::StoppedState; |
| m_session->showPrerollFrames(false); // stop showing prerolled frames in stop state |
| // Since gst is not going to send GST_STATE_PAUSED |
| // when pipeline is already paused, |
| // needs to update media status directly. |
| if (m_session->state() == QMediaPlayer::PausedState) |
| updateMediaStatus(); |
| else if (m_resources->isGranted()) |
| m_session->pause(); |
| |
| if (m_mediaStatus != QMediaPlayer::EndOfMedia) { |
| m_pendingSeekPosition = 0; |
| emit positionChanged(position()); |
| } |
| } |
| |
| popAndNotifyState(); |
| } |
| |
| void QGstreamerPlayerControl::setVolume(int volume) |
| { |
| m_session->setVolume(volume); |
| } |
| |
| void QGstreamerPlayerControl::setMuted(bool muted) |
| { |
| m_session->setMuted(muted); |
| } |
| |
| QMediaContent QGstreamerPlayerControl::media() const |
| { |
| return m_currentResource; |
| } |
| |
| const QIODevice *QGstreamerPlayerControl::mediaStream() const |
| { |
| return m_stream; |
| } |
| |
| void QGstreamerPlayerControl::setMedia(const QMediaContent &content, QIODevice *stream) |
| { |
| #ifdef DEBUG_PLAYBIN |
| qDebug() << Q_FUNC_INFO; |
| #endif |
| |
| pushState(); |
| |
| m_currentState = QMediaPlayer::StoppedState; |
| QMediaContent oldMedia = m_currentResource; |
| m_pendingSeekPosition = -1; |
| m_session->showPrerollFrames(false); // do not show prerolled frames until pause() or play() explicitly called |
| m_setMediaPending = false; |
| |
| if (!content.isNull() || stream) { |
| if (!m_resources->isGranted()) |
| m_resources->acquire(); |
| } else { |
| m_resources->release(); |
| } |
| |
| m_session->stop(); |
| |
| bool userStreamValid = false; |
| |
| if (m_bufferProgress != -1) { |
| m_bufferProgress = -1; |
| emit bufferStatusChanged(0); |
| } |
| |
| m_currentResource = content; |
| m_stream = stream; |
| |
| QNetworkRequest request = content.request(); |
| |
| if (m_stream) |
| userStreamValid = stream->isOpen() && m_stream->isReadable(); |
| |
| #if !QT_CONFIG(gstreamer_app) |
| m_session->loadFromUri(request); |
| #else |
| if (m_stream) { |
| if (userStreamValid){ |
| m_session->loadFromStream(request, m_stream); |
| } else { |
| m_mediaStatus = QMediaPlayer::InvalidMedia; |
| emit error(QMediaPlayer::FormatError, tr("Attempting to play invalid user stream")); |
| if (m_currentState != QMediaPlayer::PlayingState) |
| m_resources->release(); |
| popAndNotifyState(); |
| return; |
| } |
| } else |
| m_session->loadFromUri(request); |
| #endif |
| |
| #if QT_CONFIG(gstreamer_app) |
| if (!request.url().isEmpty() || userStreamValid) { |
| #else |
| if (!request.url().isEmpty()) { |
| #endif |
| m_mediaStatus = QMediaPlayer::LoadingMedia; |
| m_session->pause(); |
| } else { |
| m_mediaStatus = QMediaPlayer::NoMedia; |
| setBufferProgress(0); |
| } |
| |
| if (m_currentResource != oldMedia) |
| emit mediaChanged(m_currentResource); |
| |
| emit positionChanged(position()); |
| |
| if (content.isNull() && !stream) |
| m_resources->release(); |
| |
| popAndNotifyState(); |
| } |
| |
| void QGstreamerPlayerControl::setVideoOutput(QObject *output) |
| { |
| m_session->setVideoRenderer(output); |
| } |
| |
| bool QGstreamerPlayerControl::isAudioAvailable() const |
| { |
| return m_session->isAudioAvailable(); |
| } |
| |
| bool QGstreamerPlayerControl::isVideoAvailable() const |
| { |
| return m_session->isVideoAvailable(); |
| } |
| |
| void QGstreamerPlayerControl::updateSessionState(QMediaPlayer::State state) |
| { |
| pushState(); |
| |
| if (state == QMediaPlayer::StoppedState) { |
| m_session->showPrerollFrames(false); |
| m_currentState = QMediaPlayer::StoppedState; |
| } |
| |
| if (state == QMediaPlayer::PausedState && m_currentState != QMediaPlayer::StoppedState) { |
| if (m_pendingSeekPosition != -1 && m_session->isSeekable()) { |
| m_session->showPrerollFrames(true); |
| m_session->seek(m_pendingSeekPosition); |
| } |
| m_pendingSeekPosition = -1; |
| |
| if (m_currentState == QMediaPlayer::PlayingState) { |
| if (m_bufferProgress == -1 || m_bufferProgress == 100) |
| m_session->play(); |
| } |
| } |
| |
| updateMediaStatus(); |
| |
| popAndNotifyState(); |
| } |
| |
| void QGstreamerPlayerControl::updateMediaStatus() |
| { |
| //EndOfMedia status should be kept, until reset by pause, play or setMedia |
| if (m_mediaStatus == QMediaPlayer::EndOfMedia) |
| return; |
| |
| pushState(); |
| QMediaPlayer::MediaStatus oldStatus = m_mediaStatus; |
| |
| switch (m_session->state()) { |
| case QMediaPlayer::StoppedState: |
| if (m_currentResource.isNull()) |
| m_mediaStatus = QMediaPlayer::NoMedia; |
| else if (oldStatus != QMediaPlayer::InvalidMedia) |
| m_mediaStatus = QMediaPlayer::LoadingMedia; |
| break; |
| |
| case QMediaPlayer::PlayingState: |
| case QMediaPlayer::PausedState: |
| if (m_currentState == QMediaPlayer::StoppedState) { |
| m_mediaStatus = QMediaPlayer::LoadedMedia; |
| } else { |
| if (m_bufferProgress == -1 || m_bufferProgress == 100) |
| m_mediaStatus = QMediaPlayer::BufferedMedia; |
| else |
| m_mediaStatus = QMediaPlayer::StalledMedia; |
| } |
| break; |
| } |
| |
| if (m_currentState == QMediaPlayer::PlayingState && !m_resources->isGranted()) |
| m_mediaStatus = QMediaPlayer::StalledMedia; |
| |
| popAndNotifyState(); |
| } |
| |
| void QGstreamerPlayerControl::processEOS() |
| { |
| pushState(); |
| m_mediaStatus = QMediaPlayer::EndOfMedia; |
| emit positionChanged(position()); |
| m_session->endOfMediaReset(); |
| |
| if (m_currentState != QMediaPlayer::StoppedState) { |
| m_currentState = QMediaPlayer::StoppedState; |
| m_session->showPrerollFrames(false); // stop showing prerolled frames in stop state |
| } |
| |
| popAndNotifyState(); |
| } |
| |
| void QGstreamerPlayerControl::setBufferProgress(int progress) |
| { |
| if (m_bufferProgress == progress || m_mediaStatus == QMediaPlayer::NoMedia) |
| return; |
| |
| #ifdef DEBUG_PLAYBIN |
| qDebug() << Q_FUNC_INFO << progress; |
| #endif |
| m_bufferProgress = progress; |
| |
| if (m_resources->isGranted()) { |
| if (m_currentState == QMediaPlayer::PlayingState && |
| m_bufferProgress == 100 && |
| m_session->state() != QMediaPlayer::PlayingState) |
| m_session->play(); |
| |
| if (!m_session->isLiveSource() && m_bufferProgress < 100 && |
| (m_session->state() == QMediaPlayer::PlayingState || |
| m_session->pendingState() == QMediaPlayer::PlayingState)) |
| m_session->pause(); |
| } |
| |
| updateMediaStatus(); |
| |
| emit bufferStatusChanged(m_bufferProgress); |
| } |
| |
| void QGstreamerPlayerControl::handleInvalidMedia() |
| { |
| pushState(); |
| m_mediaStatus = QMediaPlayer::InvalidMedia; |
| m_currentState = QMediaPlayer::StoppedState; |
| m_setMediaPending = true; |
| popAndNotifyState(); |
| } |
| |
| void QGstreamerPlayerControl::handleResourcesGranted() |
| { |
| pushState(); |
| |
| //This may be triggered when there is an auto resume |
| //from resource-policy, we need to take action according to m_userRequestedState |
| //rather than m_currentState |
| m_currentState = m_userRequestedState; |
| if (m_currentState != QMediaPlayer::StoppedState) |
| playOrPause(m_currentState); |
| else |
| updateMediaStatus(); |
| |
| popAndNotifyState(); |
| } |
| |
| void QGstreamerPlayerControl::handleResourcesLost() |
| { |
| //on resource lost the pipeline should be paused |
| //player status is changed to paused |
| pushState(); |
| QMediaPlayer::State oldState = m_currentState; |
| |
| m_session->pause(); |
| |
| if (oldState != QMediaPlayer::StoppedState ) |
| m_currentState = QMediaPlayer::PausedState; |
| |
| popAndNotifyState(); |
| } |
| |
| void QGstreamerPlayerControl::handleResourcesDenied() |
| { |
| //on resource denied the pipeline should stay paused |
| //player status is changed to paused |
| pushState(); |
| |
| if (m_currentState != QMediaPlayer::StoppedState ) |
| m_currentState = QMediaPlayer::PausedState; |
| |
| popAndNotifyState(); |
| } |
| |
| void QGstreamerPlayerControl::pushState() |
| { |
| m_stateStack.push(m_currentState); |
| m_mediaStatusStack.push(m_mediaStatus); |
| } |
| |
| void QGstreamerPlayerControl::popAndNotifyState() |
| { |
| Q_ASSERT(!m_stateStack.isEmpty()); |
| |
| QMediaPlayer::State oldState = m_stateStack.pop(); |
| QMediaPlayer::MediaStatus oldMediaStatus = m_mediaStatusStack.pop(); |
| |
| if (m_stateStack.isEmpty()) { |
| if (m_mediaStatus != oldMediaStatus) { |
| #ifdef DEBUG_PLAYBIN |
| qDebug() << "Media status changed:" << m_mediaStatus; |
| #endif |
| emit mediaStatusChanged(m_mediaStatus); |
| } |
| |
| if (m_currentState != oldState) { |
| #ifdef DEBUG_PLAYBIN |
| qDebug() << "State changed:" << m_currentState; |
| #endif |
| emit stateChanged(m_currentState); |
| } |
| } |
| } |
| |
| QT_END_NAMESPACE |