QMediaPlayer needs to fill its internal buffer before it can play the stream otherwise it can end up in a stalled state. This commit also simplifies the logic for when the player needs to be restarted and remove the need of an internal timer to poll the player state. Fixes choppy http playback.
301 lines
7.6 KiB
C++
301 lines
7.6 KiB
C++
/*
|
|
* Cantata
|
|
*
|
|
* Copyright (c) 2011-2018 Craig Drummond <craig.p.drummond@gmail.com>
|
|
*
|
|
* ----
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "httpstream.h"
|
|
#include "mpdconnection.h"
|
|
#include "mpdstatus.h"
|
|
#include "gui/settings.h"
|
|
#include "support/globalstatic.h"
|
|
#include "support/configuration.h"
|
|
#ifndef LIBVLC_FOUND
|
|
#include <QtMultimedia/QMediaPlayer>
|
|
#endif
|
|
#include <QTimer>
|
|
|
|
static const int constPlayerCheckPeriod=250;
|
|
static const int constMaxPlayStateChecks=2000/constPlayerCheckPeriod;
|
|
|
|
#include <QDebug>
|
|
static bool debugEnabled=false;
|
|
#define DBUG if (debugEnabled) qWarning() << metaObject()->className() << __FUNCTION__
|
|
void HttpStream::enableDebug()
|
|
{
|
|
debugEnabled=true;
|
|
}
|
|
|
|
GLOBAL_STATIC(HttpStream, instance)
|
|
|
|
HttpStream::HttpStream(QObject *p)
|
|
: QObject(p)
|
|
, enabled(false)
|
|
, state(MPDState_Inactive)
|
|
, muted(false)
|
|
, playStateChecks(0)
|
|
, currentVolume(50)
|
|
, unmuteVol(50)
|
|
, playStateCheckTimer(nullptr)
|
|
, player(nullptr)
|
|
{
|
|
}
|
|
|
|
void HttpStream::save() const
|
|
{
|
|
Configuration config(metaObject()->className());
|
|
config.set("volume", currentVolume);
|
|
}
|
|
|
|
void HttpStream::setEnabled(bool e)
|
|
{
|
|
if (e==enabled) {
|
|
return;
|
|
}
|
|
|
|
enabled=e;
|
|
if (enabled) {
|
|
connect(MPDConnection::self(), SIGNAL(streamUrl(QString)), this, SLOT(streamUrl(QString)));
|
|
connect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(updateStatus()));
|
|
streamUrl(MPDConnection::self()->getDetails().streamUrl);
|
|
} else {
|
|
disconnect(MPDConnection::self(), SIGNAL(streamUrl(QString)), this, SLOT(streamUrl(QString)));
|
|
disconnect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(updateStatus()));
|
|
if (player) {
|
|
save();
|
|
#ifdef LIBVLC_FOUND
|
|
libvlc_media_player_stop(player);
|
|
#else
|
|
player->stop();
|
|
#endif
|
|
}
|
|
}
|
|
emit isEnabled(enabled);
|
|
}
|
|
|
|
void HttpStream::setVolume(int vol)
|
|
{
|
|
DBUG << vol;
|
|
if (player) {
|
|
currentVolume=vol;
|
|
#ifdef LIBVLC_FOUND
|
|
libvlc_audio_set_volume(player, vol);
|
|
#else
|
|
player->setVolume(vol);
|
|
#endif
|
|
emit update();
|
|
}
|
|
}
|
|
|
|
int HttpStream::volume()
|
|
{
|
|
if (!enabled) {
|
|
return -1;
|
|
}
|
|
|
|
int vol=currentVolume;
|
|
if (player && !isMuted()) {
|
|
#ifdef LIBVLC_FOUND
|
|
vol=libvlc_audio_get_volume(player);
|
|
#else
|
|
vol=player->volume();
|
|
#endif
|
|
if (vol<0) {
|
|
vol=currentVolume;
|
|
} else {
|
|
currentVolume=vol;
|
|
}
|
|
}
|
|
DBUG << vol;
|
|
return vol;
|
|
}
|
|
|
|
void HttpStream::toggleMute()
|
|
{
|
|
DBUG << isMuted();
|
|
if (player) {
|
|
muted=!muted;
|
|
#ifdef LIBVLC_FOUND
|
|
libvlc_audio_set_mute(player, muted);
|
|
#else
|
|
player->setMuted(!muted);
|
|
#endif
|
|
emit update();
|
|
}
|
|
}
|
|
|
|
static const char *constUrlProperty="url";
|
|
|
|
void HttpStream::streamUrl(const QString &url)
|
|
{
|
|
MPDStatus * const status = MPDStatus::self();
|
|
DBUG << url;
|
|
#ifdef LIBVLC_FOUND
|
|
if (player) {
|
|
libvlc_media_player_stop(player);
|
|
libvlc_media_player_release(player);
|
|
libvlc_release(instance);
|
|
player=0;
|
|
}
|
|
#else
|
|
if (player && player->property(constUrlProperty).toString()!=url) {
|
|
player->stop();
|
|
player->deleteLater();
|
|
player=nullptr;
|
|
}
|
|
#endif
|
|
QUrl qUrl(url);
|
|
if (!url.isEmpty() && qUrl.isValid() && qUrl.scheme().startsWith("http") && !player) {
|
|
#ifdef LIBVLC_FOUND
|
|
instance = libvlc_new(0, NULL);
|
|
QByteArray byteArrayUrl = url.toUtf8();
|
|
media = libvlc_media_new_location(instance, byteArrayUrl.constData());
|
|
player = libvlc_media_player_new_from_media(media);
|
|
libvlc_media_release(media);
|
|
#else
|
|
player=new QMediaPlayer(this);
|
|
player->setMedia(qUrl);
|
|
connect(player, &QMediaPlayer::bufferStatusChanged, this, &HttpStream::bufferingProgress);
|
|
#endif
|
|
muted=false;
|
|
setVolume(Configuration(metaObject()->className()).get("volume", currentVolume));
|
|
}
|
|
if (player) {
|
|
state=0xFFFF; // Force an update...
|
|
updateStatus();
|
|
} else {
|
|
state=MPDState_Inactive;
|
|
}
|
|
emit update();
|
|
}
|
|
|
|
void HttpStream::bufferingProgress(int progress)
|
|
{
|
|
MPDStatus * const status = MPDStatus::self();
|
|
if (status->state() == MPDState_Playing) {
|
|
if (progress == 100) {
|
|
player->play();
|
|
} else {
|
|
player->pause();
|
|
}
|
|
}
|
|
}
|
|
|
|
void HttpStream::updateStatus()
|
|
{
|
|
if (!player) {
|
|
return;
|
|
}
|
|
|
|
MPDStatus * const status = MPDStatus::self();
|
|
DBUG << status->state() << state;
|
|
|
|
// evaluates to true when it is needed to start or restart media player
|
|
bool playerNeedsToStart = status->state() == MPDState_Playing;
|
|
#ifndef LIBVLC_FOUND
|
|
playerNeedsToStart = playerNeedsToStart && player->state() == QMediaPlayer::StoppedState;
|
|
#endif
|
|
|
|
if (status->state()==state && !playerNeedsToStart) {
|
|
return;
|
|
}
|
|
|
|
state=status->state();
|
|
switch (status->state()) {
|
|
case MPDState_Playing:
|
|
// Only start playback if not aready playing
|
|
#ifdef LIBVLC_FOUND
|
|
if (libvlc_Playing!=libvlc_media_player_get_state(player)) {
|
|
libvlc_media_player_play(player);
|
|
startTimer();
|
|
}
|
|
#else
|
|
if (playerNeedsToStart) {
|
|
QUrl url = player->media().canonicalUrl();
|
|
if (!url.isEmpty())
|
|
{
|
|
DBUG << "Setting media" << url;
|
|
player->setMedia(url);
|
|
}
|
|
}
|
|
#endif
|
|
break;
|
|
case MPDState_Paused:
|
|
#ifndef LIBVLC_FOUND
|
|
player->stop();
|
|
#endif
|
|
case MPDState_Inactive:
|
|
case MPDState_Stopped:
|
|
#ifdef LIBVLC_FOUND
|
|
libvlc_media_player_stop(player);
|
|
stopTimer();
|
|
#else
|
|
player->stop();
|
|
#endif
|
|
break;
|
|
default:
|
|
#ifdef LIBVLC_FOUND
|
|
stopTimer();
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
void HttpStream::checkPlayer()
|
|
{
|
|
#ifdef LIBVLC_FOUND
|
|
if (0==--playStateChecks) {
|
|
stopTimer();
|
|
DBUG << "Max checks reached";
|
|
}
|
|
if (libvlc_Playing==libvlc_media_player_get_state(player)) {
|
|
DBUG << "Playing";
|
|
stopTimer();
|
|
} else {
|
|
DBUG << "Try again";
|
|
libvlc_media_player_play(player);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void HttpStream::startTimer()
|
|
{
|
|
if (!playStateCheckTimer) {
|
|
playStateCheckTimer=new QTimer(this);
|
|
playStateCheckTimer->setSingleShot(false);
|
|
playStateCheckTimer->setInterval(constPlayerCheckPeriod);
|
|
connect(playStateCheckTimer, SIGNAL(timeout()), SLOT(checkPlayer()));
|
|
}
|
|
playStateChecks = constMaxPlayStateChecks;
|
|
DBUG << playStateChecks;
|
|
playStateCheckTimer->start();
|
|
}
|
|
|
|
void HttpStream::stopTimer()
|
|
{
|
|
if (playStateCheckTimer) {
|
|
DBUG;
|
|
playStateCheckTimer->stop();
|
|
}
|
|
playStateChecks=0;
|
|
}
|
|
|
|
#include "moc_httpstream.cpp"
|