harbour-sailfin/core/src/model/player.cpp

237 lines
8.0 KiB
C++

/*
* Sailfin: a Jellyfin client written using Qt
* Copyright (C) 2021-2022 Chris Josten and the Sailfin Contributors.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <JellyfinQt/model/player.h>
#ifdef USE_QTMULTIMEDIA_PLAYER
#include <QtMultimedia>
#include <QtMultimedia/QMediaStreamsControl>
#include <QTimer>
#endif // USE_QTMULTIMEDIA_PLAYER
namespace Jellyfin {
namespace Model {
Q_LOGGING_CATEGORY(player, "jellyfin.model.player");
Player::~Player() {}
#ifdef USE_QTMULTIMEDIA_PLAYER
class QtMultimediaPlayerPrivate {
Q_DECLARE_PUBLIC(QtMultimediaPlayer);
public:
explicit QtMultimediaPlayerPrivate(QtMultimediaPlayer *q);
QMediaPlayer *m_mediaPlayer;
QMediaStreamsControl *m_mediaStreamsControl;
QTimer m_forceSeekTimer;
bool m_seekToResumePosition = false;
qint64 m_resumePosition;
int m_audioIndex = -1;
int m_subtitleIndex = -1;
static const qint64 MS_TICK_FACTOR = 10000;
protected:
QtMultimediaPlayer *q_ptr;
};
QtMultimediaPlayerPrivate::QtMultimediaPlayerPrivate(QtMultimediaPlayer *q)
: m_mediaPlayer(new QMediaPlayer(q, QMediaPlayer::VideoSurface)),
q_ptr(q) {
m_mediaStreamsControl = m_mediaPlayer->service()->requestControl<QMediaStreamsControl *>();
// Yes, this is a very ugly way of forcing the video player to seek to the resume position
q->connect(&m_forceSeekTimer, &QTimer::timeout, q, [this]() {
if (m_seekToResumePosition && m_mediaPlayer->isSeekable()) {
qCDebug(player) << "Trying to seek to the resume position" << (m_resumePosition / MS_TICK_FACTOR);
if (m_mediaPlayer->position() > m_resumePosition / MS_TICK_FACTOR - 500) {
m_seekToResumePosition = false;
m_forceSeekTimer.stop();
} else {
m_mediaPlayer->setPosition(m_resumePosition / MS_TICK_FACTOR);
}
}
});
q->connect(m_mediaPlayer, &QMediaPlayer::seekableChanged, q, [this](bool newSeekable) {
if (newSeekable && m_seekToResumePosition) {
m_forceSeekTimer.start();
}
});
m_forceSeekTimer.setInterval(500);
m_forceSeekTimer.setSingleShot(false);
// Connect other properties
q->connect(m_mediaPlayer, &QMediaPlayer::stateChanged, q, [q](QMediaPlayer::State /*newState*/){
emit q->stateChanged(q->state());
});
q->connect(m_mediaPlayer, &QMediaPlayer::mediaStatusChanged, q, [q, this](QMediaPlayer::MediaStatus newMediaStatus) {
emit q->mediaStatusChanged(q->mediaStatus());
});
q->connect(m_mediaPlayer, &QMediaPlayer::positionChanged, q, &QtMultimediaPlayer::positionChanged);
q->connect(m_mediaPlayer, &QMediaPlayer::durationChanged, q, &QtMultimediaPlayer::durationChanged);
q->connect(m_mediaPlayer, &QMediaPlayer::seekableChanged, q, &QtMultimediaPlayer::seekableChanged);
q->connect(m_mediaPlayer, &QMediaPlayer::audioAvailableChanged, q, &QtMultimediaPlayer::hasAudioChanged);
q->connect(m_mediaPlayer, &QMediaPlayer::videoAvailableChanged, q, &QtMultimediaPlayer::hasVideoChanged);
q->connect(m_mediaPlayer, SIGNAL(error(QMediaPlayer::Error)), q, SLOT(errorStringChanged));
if (m_mediaStreamsControl != nullptr) {
q->connect(m_mediaStreamsControl, &QMediaStreamsControl::streamsChanged, q, [this](){
qCDebug(player) << m_mediaStreamsControl->streamCount() << " streams in the medi source";
if (m_audioIndex >= 0) {
m_mediaStreamsControl->setActive(m_audioIndex, true);
}
if (m_subtitleIndex >= 0) {
m_mediaStreamsControl->setActive(m_subtitleIndex, true);
}
});
}
}
QtMultimediaPlayer::QtMultimediaPlayer(QObject *parent)
: d_ptr(new QtMultimediaPlayerPrivate(this)){
}
QtMultimediaPlayer::~QtMultimediaPlayer() {}
PlayerState QtMultimediaPlayer::state() const {
const Q_D(QtMultimediaPlayer);
switch(d->m_mediaPlayer->state()) {
case QMediaPlayer::StoppedState:
return PlayerState::Stopped;
case QMediaPlayer::PlayingState:
return PlayerState::Playing;
case QMediaPlayer::PausedState:
return PlayerState::Paused;
default:
Q_ASSERT_X(false, "QtMultimediaPlayer::state()", "Invalid switch case");
return PlayerState::Stopped;
}
}
MediaStatus QtMultimediaPlayer::mediaStatus() const {
const Q_D(QtMultimediaPlayer);
switch(d->m_mediaPlayer->mediaStatus()) {
case QMediaPlayer::UnknownMediaStatus:
return MediaStatus::Error;
case QMediaPlayer::NoMedia:
return MediaStatus::NoMedia;
case QMediaPlayer::LoadingMedia:
return MediaStatus::Loading;
case QMediaPlayer::LoadedMedia:
return MediaStatus::Loaded;
case QMediaPlayer::StalledMedia:
return MediaStatus::Stalled;
case QMediaPlayer::BufferingMedia:
return MediaStatus::Buffering;
case QMediaPlayer::BufferedMedia:
return MediaStatus::Buffered;
case QMediaPlayer::EndOfMedia:
return MediaStatus::EndOfMedia;
case QMediaPlayer::InvalidMedia:
default:
return MediaStatus::Error;
}
}
qint64 QtMultimediaPlayer::position() const {
const Q_D(QtMultimediaPlayer);
return d->m_mediaPlayer->position();
}
qint64 QtMultimediaPlayer::duration() const {
const Q_D(QtMultimediaPlayer);
return d->m_mediaPlayer->duration();
}
bool QtMultimediaPlayer::seekable() const {
const Q_D(QtMultimediaPlayer);
return d->m_mediaPlayer->isSeekable();
}
bool QtMultimediaPlayer::hasAudio() const {
const Q_D(QtMultimediaPlayer);
return d->m_mediaPlayer->isAudioAvailable();
}
bool QtMultimediaPlayer::hasVideo() const {
const Q_D(QtMultimediaPlayer);
return d->m_mediaPlayer->isVideoAvailable();
}
QString QtMultimediaPlayer::errorString() const {
const Q_D(QtMultimediaPlayer);
return d->m_mediaPlayer->errorString();
}
void QtMultimediaPlayer::pause() {
Q_D(QtMultimediaPlayer);
d->m_mediaPlayer->pause();
}
void QtMultimediaPlayer::play(qint64 startPosition) {
Q_D(QtMultimediaPlayer);
qCDebug(player) << "Play from position " << startPosition;
d->m_mediaPlayer->play();
d->m_resumePosition = startPosition;
if (startPosition > 0) {
d->m_seekToResumePosition = true;
}
}
void QtMultimediaPlayer::stop() {
Q_D(QtMultimediaPlayer);
d->m_mediaPlayer->stop();
}
void QtMultimediaPlayer::seek(qint64 pos) {
Q_D(QtMultimediaPlayer);
d->m_mediaPlayer->setPosition(pos);
}
void QtMultimediaPlayer::setMedia(const QUrl &url, int audioIndex, int subtitleIndex) {
Q_D(QtMultimediaPlayer);
qCDebug(player) << "Media set to " << url;
if (url.isEmpty()) {
d->m_mediaPlayer->setMedia(QMediaContent());
} else {
d->m_mediaPlayer->setMedia(QMediaContent(url));
}
d->m_audioIndex = audioIndex;
d->m_subtitleIndex = subtitleIndex;
if (d->m_mediaStreamsControl != nullptr) {
qCDebug(player) << "Total stream count: " << d->m_mediaStreamsControl->streamCount();
if (audioIndex >= 0) {
d->m_mediaStreamsControl->setActive(audioIndex, true);
}
if (subtitleIndex >= 0) {
d->m_mediaStreamsControl->setActive(subtitleIndex, true);
}
}
}
QObject *QtMultimediaPlayer::videoOutputSource() const {
const Q_D(QtMultimediaPlayer);
return d->m_mediaPlayer;
}
#endif // USE_QTMULTIMEDIA_PLAYER
} // NS Model
} // NS Jellfyin