237 lines
8.0 KiB
C++
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
|