Add mutexes
This commit is contained in:
@@ -1306,7 +1306,7 @@ void MainWindow::Exit() {
|
|||||||
else {
|
else {
|
||||||
if (app_->player()->engine()->is_fadeout_enabled()) {
|
if (app_->player()->engine()->is_fadeout_enabled()) {
|
||||||
// To shut down the application when fadeout will be finished
|
// To shut down the application when fadeout will be finished
|
||||||
QObject::connect(&*app_->player()->engine(), &EngineBase::FadeoutFinishedSignal, this, &MainWindow::DoExit);
|
QObject::connect(&*app_->player()->engine(), &EngineBase::Finished, this, &MainWindow::DoExit);
|
||||||
if (app_->player()->GetState() == EngineBase::State::Playing) {
|
if (app_->player()->GetState() == EngineBase::State::Playing) {
|
||||||
app_->player()->Stop();
|
app_->player()->Stop();
|
||||||
ignore_close_ = true;
|
ignore_close_ = true;
|
||||||
|
|||||||
65
src/core/mutex_protected.h
Normal file
65
src/core/mutex_protected.h
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry 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 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MUTEX_PROTECTED_H
|
||||||
|
#define MUTEX_PROTECTED_H
|
||||||
|
|
||||||
|
#include <boost/noncopyable.hpp>
|
||||||
|
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QMutexLocker>
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class mutex_protected : public boost::noncopyable {
|
||||||
|
public:
|
||||||
|
mutex_protected(const mutex_protected &value) : value_(value.value()) {}
|
||||||
|
mutex_protected(const T value) : value_(value) {}
|
||||||
|
~mutex_protected() {}
|
||||||
|
|
||||||
|
T value() const {
|
||||||
|
QMutexLocker l(&mutex_);
|
||||||
|
return value_;
|
||||||
|
}
|
||||||
|
|
||||||
|
T operator==(const mutex_protected &value) const {
|
||||||
|
QMutexLocker l(&mutex_);
|
||||||
|
return value == value_;
|
||||||
|
}
|
||||||
|
|
||||||
|
T operator==(const T value) const {
|
||||||
|
QMutexLocker l(&mutex_);
|
||||||
|
return value == value_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator=(const mutex_protected &value) {
|
||||||
|
QMutexLocker l(&mutex_);
|
||||||
|
value_ = value.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator=(const T value) {
|
||||||
|
QMutexLocker l(&mutex_);
|
||||||
|
value_ = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
T value_;
|
||||||
|
mutable QMutex mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MUTEX_PROTECTED_H
|
||||||
@@ -168,8 +168,6 @@ class EngineBase : public QObject {
|
|||||||
|
|
||||||
void TrackEnded();
|
void TrackEnded();
|
||||||
|
|
||||||
void FadeoutFinishedSignal();
|
|
||||||
|
|
||||||
void StatusText(const QString &text);
|
void StatusText(const QString &text);
|
||||||
void Error(const QString &text);
|
void Error(const QString &text);
|
||||||
|
|
||||||
@@ -188,6 +186,8 @@ class EngineBase : public QObject {
|
|||||||
|
|
||||||
void VolumeChanged(const uint volume);
|
void VolumeChanged(const uint volume);
|
||||||
|
|
||||||
|
void Finished();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool exclusive_mode_;
|
bool exclusive_mode_;
|
||||||
bool volume_control_;
|
bool volume_control_;
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QFuture>
|
#include <QFuture>
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
|
#include <QMutexLocker>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
@@ -604,33 +605,43 @@ void GstEngine::EndOfStreamReached(const int pipeline_id, const bool has_next_tr
|
|||||||
|
|
||||||
void GstEngine::HandlePipelineError(const int pipeline_id, const int domain, const int error_code, const QString &message, const QString &debugstr) {
|
void GstEngine::HandlePipelineError(const int pipeline_id, const int domain, const int error_code, const QString &message, const QString &debugstr) {
|
||||||
|
|
||||||
if (!current_pipeline_ || current_pipeline_->id() != pipeline_id) return;
|
|
||||||
|
|
||||||
qLog(Error) << "GStreamer error:" << domain << error_code << message;
|
qLog(Error) << "GStreamer error:" << domain << error_code << message;
|
||||||
|
|
||||||
FinishPipeline(current_pipeline_);
|
|
||||||
current_pipeline_ = GstEnginePipelinePtr();
|
|
||||||
|
|
||||||
BufferingFinished();
|
|
||||||
Q_EMIT StateChanged(State::Error);
|
|
||||||
|
|
||||||
if (
|
|
||||||
(domain == static_cast<int>(GST_RESOURCE_ERROR) && (
|
|
||||||
error_code == static_cast<int>(GST_RESOURCE_ERROR_NOT_FOUND) ||
|
|
||||||
error_code == static_cast<int>(GST_RESOURCE_ERROR_OPEN_READ) ||
|
|
||||||
error_code == static_cast<int>(GST_RESOURCE_ERROR_NOT_AUTHORIZED)
|
|
||||||
))
|
|
||||||
|| (domain == static_cast<int>(GST_STREAM_ERROR))
|
|
||||||
) {
|
|
||||||
Q_EMIT InvalidSongRequested(stream_url_);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Q_EMIT FatalError();
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_EMIT Error(message);
|
Q_EMIT Error(message);
|
||||||
Q_EMIT Error(debugstr);
|
Q_EMIT Error(debugstr);
|
||||||
|
|
||||||
|
if (fadeout_pause_pipeline_ && pipeline_id == fadeout_pause_pipeline_->id()) {
|
||||||
|
StopFadeoutPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_pipeline_ && current_pipeline_->id() == pipeline_id) {
|
||||||
|
|
||||||
|
FinishPipeline(current_pipeline_);
|
||||||
|
current_pipeline_ = GstEnginePipelinePtr();
|
||||||
|
|
||||||
|
BufferingFinished();
|
||||||
|
Q_EMIT StateChanged(State::Error);
|
||||||
|
|
||||||
|
if (
|
||||||
|
(domain == static_cast<int>(GST_RESOURCE_ERROR) && (
|
||||||
|
error_code == static_cast<int>(GST_RESOURCE_ERROR_NOT_FOUND) ||
|
||||||
|
error_code == static_cast<int>(GST_RESOURCE_ERROR_OPEN_READ) ||
|
||||||
|
error_code == static_cast<int>(GST_RESOURCE_ERROR_NOT_AUTHORIZED)
|
||||||
|
))
|
||||||
|
|| (domain == static_cast<int>(GST_STREAM_ERROR))
|
||||||
|
) {
|
||||||
|
Q_EMIT InvalidSongRequested(stream_url_);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Q_EMIT FatalError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (fadeout_pipelines_.contains(pipeline_id)) {
|
||||||
|
GstEnginePipelinePtr pipeline = fadeout_pipelines_.take(pipeline_id);
|
||||||
|
FinishPipeline(pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEngine::NewMetaData(const int pipeline_id, const EngineMetadata &engine_metadata) {
|
void GstEngine::NewMetaData(const int pipeline_id, const EngineMetadata &engine_metadata) {
|
||||||
@@ -663,22 +674,21 @@ void GstEngine::FadeoutFinished(const int pipeline_id) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GstEnginePipelinePtr pipeline = fadeout_pipelines_.value(pipeline_id);
|
GstEnginePipelinePtr pipeline = fadeout_pipelines_.take(pipeline_id);
|
||||||
fadeout_pipelines_.remove(pipeline_id);
|
|
||||||
FinishPipeline(pipeline);
|
|
||||||
|
|
||||||
Q_EMIT FadeoutFinishedSignal();
|
FinishPipeline(pipeline);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEngine::FadeoutPauseFinished() {
|
void GstEngine::FadeoutPauseFinished() {
|
||||||
|
|
||||||
|
if (!fadeout_pause_pipeline_) return;
|
||||||
|
|
||||||
fadeout_pause_pipeline_->SetStateAsync(GST_STATE_PAUSED);
|
fadeout_pause_pipeline_->SetStateAsync(GST_STATE_PAUSED);
|
||||||
Q_EMIT StateChanged(State::Paused);
|
Q_EMIT StateChanged(State::Paused);
|
||||||
StopTimers();
|
StopTimers();
|
||||||
has_faded_out_to_pause_ = true;
|
has_faded_out_to_pause_ = true;
|
||||||
fadeout_pause_pipeline_ = GstEnginePipelinePtr();
|
fadeout_pause_pipeline_ = GstEnginePipelinePtr();
|
||||||
Q_EMIT FadeoutFinishedSignal();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -703,26 +713,40 @@ void GstEngine::PlayDone(const GstStateChangeReturn ret, const bool pause, const
|
|||||||
|
|
||||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||||
// Failure, but we got a redirection URL - try loading that instead
|
// Failure, but we got a redirection URL - try loading that instead
|
||||||
const QByteArray redirect_url = current_pipeline_->redirect_url();
|
GstEnginePipelinePtr old_pipeline = current_pipeline_;
|
||||||
if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->gst_url()) {
|
current_pipeline_ = GstEnginePipelinePtr();
|
||||||
|
QByteArray redirect_url;
|
||||||
|
{
|
||||||
|
QMutexLocker l(old_pipeline->mutex_redirect_url());
|
||||||
|
redirect_url = old_pipeline->redirect_url();
|
||||||
|
redirect_url.detach();
|
||||||
|
}
|
||||||
|
QByteArray gst_url;
|
||||||
|
{
|
||||||
|
QMutexLocker l(old_pipeline->mutex_url());
|
||||||
|
gst_url = old_pipeline->gst_url();
|
||||||
|
gst_url.detach();
|
||||||
|
}
|
||||||
|
if (!redirect_url.isEmpty() && redirect_url != gst_url) {
|
||||||
qLog(Info) << "Redirecting to" << redirect_url;
|
qLog(Info) << "Redirecting to" << redirect_url;
|
||||||
GstEnginePipelinePtr old_pipeline = current_pipeline_;
|
QUrl media_url;
|
||||||
current_pipeline_ = GstEnginePipelinePtr();
|
QUrl stream_url;
|
||||||
if (old_pipeline) {
|
{
|
||||||
FinishPipeline(old_pipeline);
|
QMutexLocker l(old_pipeline->mutex_url());
|
||||||
|
media_url = old_pipeline->media_url();
|
||||||
|
media_url.detach();
|
||||||
|
stream_url = old_pipeline->stream_url();
|
||||||
|
stream_url.detach();
|
||||||
}
|
}
|
||||||
current_pipeline_ = CreatePipeline(current_pipeline_->media_url(), current_pipeline_->stream_url(), redirect_url, end_nanosec_, current_pipeline_->ebur128_loudness_normalizing_gain_db());
|
current_pipeline_ = CreatePipeline(media_url, stream_url, redirect_url, end_nanosec_, old_pipeline->ebur128_loudness_normalizing_gain_db());
|
||||||
|
FinishPipeline(old_pipeline);
|
||||||
Play(pause, offset_nanosec);
|
Play(pause, offset_nanosec);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Failure - give up
|
// Failure - give up
|
||||||
qLog(Warning) << "Could not set thread to PLAYING.";
|
qLog(Warning) << "Could not set thread to PLAYING.";
|
||||||
GstEnginePipelinePtr old_pipeline = current_pipeline_;
|
FinishPipeline(old_pipeline);
|
||||||
current_pipeline_ = GstEnginePipelinePtr();
|
|
||||||
if (old_pipeline) {
|
|
||||||
FinishPipeline(old_pipeline);
|
|
||||||
}
|
|
||||||
BufferingFinished();
|
BufferingFinished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -806,7 +830,14 @@ void GstEngine::StartFadeout(GstEnginePipelinePtr pipeline) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QObject::disconnect(&*pipeline, nullptr, this, nullptr);
|
QObject::disconnect(&*pipeline, &GstEnginePipeline::FaderFinished, this, &GstEngine::FadeoutPauseFinished);
|
||||||
|
QObject::disconnect(&*pipeline, &GstEnginePipeline::EndOfStreamReached, this, &GstEngine::EndOfStreamReached);
|
||||||
|
QObject::disconnect(&*pipeline, &GstEnginePipeline::MetadataFound, this, &GstEngine::NewMetaData);
|
||||||
|
QObject::disconnect(&*pipeline, &GstEnginePipeline::BufferingStarted, this, &GstEngine::BufferingStarted);
|
||||||
|
QObject::disconnect(&*pipeline, &GstEnginePipeline::BufferingProgress, this, &GstEngine::BufferingProgress);
|
||||||
|
QObject::disconnect(&*pipeline, &GstEnginePipeline::BufferingFinished, this, &GstEngine::BufferingFinished);
|
||||||
|
QObject::disconnect(&*pipeline, &GstEnginePipeline::VolumeChanged, this, &EngineBase::UpdateVolume);
|
||||||
|
QObject::disconnect(&*pipeline, &GstEnginePipeline::AboutToFinish, this, &EngineBase::EmitAboutToFinish);
|
||||||
|
|
||||||
fadeout_pipelines_.insert(pipeline->id(), pipeline);
|
fadeout_pipelines_.insert(pipeline->id(), pipeline);
|
||||||
pipeline->RemoveAllBufferConsumers();
|
pipeline->RemoveAllBufferConsumers();
|
||||||
@@ -954,6 +985,10 @@ void GstEngine::PipelineFinished(const int pipeline_id) {
|
|||||||
|
|
||||||
qLog(Debug) << (current_pipeline_ ? 1 : 0) + old_pipelines_.count() << "pipelines are active";
|
qLog(Debug) << (current_pipeline_ ? 1 : 0) + old_pipelines_.count() << "pipelines are active";
|
||||||
|
|
||||||
|
if (!current_pipeline_ && old_pipelines_.isEmpty()) {
|
||||||
|
Q_EMIT Finished();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEngine::UpdateScope(const int chunk_length) {
|
void GstEngine::UpdateScope(const int chunk_length) {
|
||||||
@@ -1038,15 +1073,23 @@ void GstEngine::StreamDiscovered(GstDiscoverer*, GstDiscovererInfo *info, GError
|
|||||||
GstDiscovererStreamInfo *stream_info = reinterpret_cast<GstDiscovererStreamInfo*>(g_list_first(audio_streams)->data);
|
GstDiscovererStreamInfo *stream_info = reinterpret_cast<GstDiscovererStreamInfo*>(g_list_first(audio_streams)->data);
|
||||||
|
|
||||||
EngineMetadata engine_metadata;
|
EngineMetadata engine_metadata;
|
||||||
if (discovered_url == instance->current_pipeline_->gst_url()) {
|
bool match = false;
|
||||||
engine_metadata.type = EngineMetadata::Type::Current;
|
{
|
||||||
engine_metadata.media_url = instance->current_pipeline_->media_url();
|
QMutexLocker l(instance->current_pipeline_->mutex_url());
|
||||||
engine_metadata.stream_url = instance->current_pipeline_->stream_url();
|
if (discovered_url == instance->current_pipeline_->gst_url()) {
|
||||||
|
match = true;
|
||||||
|
engine_metadata.type = EngineMetadata::Type::Current;
|
||||||
|
engine_metadata.media_url = instance->current_pipeline_->media_url();
|
||||||
|
engine_metadata.stream_url = instance->current_pipeline_->stream_url();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (discovered_url == instance->current_pipeline_->next_gst_url()) {
|
if (!match) {
|
||||||
engine_metadata.type = EngineMetadata::Type::Next;
|
QMutexLocker l(instance->current_pipeline_->mutex_next_url());
|
||||||
engine_metadata.media_url = instance->current_pipeline_->next_media_url();
|
if (discovered_url == instance->current_pipeline_->next_gst_url()) {
|
||||||
engine_metadata.stream_url = instance->current_pipeline_->next_stream_url();
|
engine_metadata.type = EngineMetadata::Type::Next;
|
||||||
|
engine_metadata.media_url = instance->current_pipeline_->next_media_url();
|
||||||
|
engine_metadata.stream_url = instance->current_pipeline_->next_stream_url();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
engine_metadata.samplerate = static_cast<int>(gst_discoverer_audio_info_get_sample_rate(GST_DISCOVERER_AUDIO_INFO(stream_info)));
|
engine_metadata.samplerate = static_cast<int>(gst_discoverer_audio_info_get_sample_rate(GST_DISCOVERER_AUDIO_INFO(stream_info)));
|
||||||
engine_metadata.bitdepth = static_cast<int>(gst_discoverer_audio_info_get_depth(GST_DISCOVERER_AUDIO_INFO(stream_info)));
|
engine_metadata.bitdepth = static_cast<int>(gst_discoverer_audio_info_get_depth(GST_DISCOVERER_AUDIO_INFO(stream_info)));
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
#include <QFuture>
|
#include <QFuture>
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
|
#include <QMutexLocker>
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
@@ -87,7 +88,6 @@ int GstEnginePipeline::sId = 1;
|
|||||||
GstEnginePipeline::GstEnginePipeline(QObject *parent)
|
GstEnginePipeline::GstEnginePipeline(QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
id_(sId++),
|
id_(sId++),
|
||||||
valid_(false),
|
|
||||||
exclusive_mode_(false),
|
exclusive_mode_(false),
|
||||||
volume_enabled_(true),
|
volume_enabled_(true),
|
||||||
fading_enabled_(false),
|
fading_enabled_(false),
|
||||||
@@ -119,6 +119,7 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
|
|||||||
ignore_tags_(false),
|
ignore_tags_(false),
|
||||||
pipeline_connected_(false),
|
pipeline_connected_(false),
|
||||||
pipeline_active_(false),
|
pipeline_active_(false),
|
||||||
|
buffering_(false),
|
||||||
pending_state_(GST_STATE_NULL),
|
pending_state_(GST_STATE_NULL),
|
||||||
pending_seek_nanosec_(-1),
|
pending_seek_nanosec_(-1),
|
||||||
last_known_position_ns_(0),
|
last_known_position_ns_(0),
|
||||||
@@ -127,7 +128,7 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
|
|||||||
volume_set_(false),
|
volume_set_(false),
|
||||||
volume_internal_(-1.0),
|
volume_internal_(-1.0),
|
||||||
volume_percent_(100),
|
volume_percent_(100),
|
||||||
buffering_(false),
|
fader_active_(false),
|
||||||
use_fudge_timer_(false),
|
use_fudge_timer_(false),
|
||||||
pipeline_(nullptr),
|
pipeline_(nullptr),
|
||||||
audiobin_(nullptr),
|
audiobin_(nullptr),
|
||||||
@@ -171,13 +172,13 @@ GstEnginePipeline::~GstEnginePipeline() {
|
|||||||
}
|
}
|
||||||
gst_object_unref(GST_OBJECT(pipeline_));
|
gst_object_unref(GST_OBJECT(pipeline_));
|
||||||
pipeline_ = nullptr;
|
pipeline_ = nullptr;
|
||||||
if (audiobin_ && !pipeline_connected_) {
|
if (audiobin_ && !pipeline_connected_.value()) {
|
||||||
gst_object_unref(GST_OBJECT(audiobin_));
|
gst_object_unref(GST_OBJECT(audiobin_));
|
||||||
}
|
}
|
||||||
audiobin_ = nullptr;
|
audiobin_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
qLog(Debug) << "Pipeline" << id_ << "deleted";
|
qLog(Debug) << "Pipeline" << id() << "deleted";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,10 +239,13 @@ void GstEnginePipeline::set_buffer_high_watermark(const double value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::set_proxy_settings(const QString &address, const bool authentication, const QString &user, const QString &pass) {
|
void GstEnginePipeline::set_proxy_settings(const QString &address, const bool authentication, const QString &user, const QString &pass) {
|
||||||
|
|
||||||
|
QMutexLocker l(&mutex_proxy_);
|
||||||
proxy_address_ = address;
|
proxy_address_ = address;
|
||||||
proxy_authentication_ = authentication;
|
proxy_authentication_ = authentication;
|
||||||
proxy_user_ = user;
|
proxy_user_ = user;
|
||||||
proxy_pass_ = pass;
|
proxy_pass_ = pass;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::set_channels(const bool enabled, const int channels) {
|
void GstEnginePipeline::set_channels(const bool enabled, const int channels) {
|
||||||
@@ -268,6 +272,7 @@ void GstEnginePipeline::set_spotify_login(const QString &spotify_username, const
|
|||||||
spotify_password_ = spotify_password;
|
spotify_password_ = spotify_password;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // HAVE_SPOTIFY
|
#endif // HAVE_SPOTIFY
|
||||||
|
|
||||||
QString GstEnginePipeline::GstStateText(const GstState state) {
|
QString GstEnginePipeline::GstStateText(const GstState state) {
|
||||||
@@ -291,7 +296,7 @@ QString GstEnginePipeline::GstStateText(const GstState state) {
|
|||||||
|
|
||||||
GstElement *GstEnginePipeline::CreateElement(const QString &factory_name, const QString &name, GstElement *bin, QString &error) const {
|
GstElement *GstEnginePipeline::CreateElement(const QString &factory_name, const QString &name, GstElement *bin, QString &error) const {
|
||||||
|
|
||||||
QString unique_name = QLatin1String("pipeline") + QLatin1Char('-') + QString::number(id_) + QLatin1Char('-') + (name.isEmpty() ? factory_name : name);
|
QString unique_name = QLatin1String("pipeline") + QLatin1Char('-') + QString::number(id()) + QLatin1Char('-') + (name.isEmpty() ? factory_name : name);
|
||||||
|
|
||||||
GstElement *element = gst_element_factory_make(factory_name.toUtf8().constData(), unique_name.toUtf8().constData());
|
GstElement *element = gst_element_factory_make(factory_name.toUtf8().constData(), unique_name.toUtf8().constData());
|
||||||
if (!element) {
|
if (!element) {
|
||||||
@@ -310,6 +315,7 @@ void GstEnginePipeline::Disconnect() {
|
|||||||
if (pipeline_) {
|
if (pipeline_) {
|
||||||
|
|
||||||
if (fader_) {
|
if (fader_) {
|
||||||
|
fader_active_ = false;
|
||||||
if (fader_->state() != QTimeLine::NotRunning) {
|
if (fader_->state() != QTimeLine::NotRunning) {
|
||||||
fader_->stop();
|
fader_->stop();
|
||||||
}
|
}
|
||||||
@@ -379,7 +385,7 @@ void GstEnginePipeline::Disconnect() {
|
|||||||
|
|
||||||
bool GstEnginePipeline::Finish() {
|
bool GstEnginePipeline::Finish() {
|
||||||
|
|
||||||
qLog(Debug) << "Finishing pipeline" << id_;
|
qLog(Debug) << "Finishing pipeline" << id();
|
||||||
|
|
||||||
finish_requested_ = true;
|
finish_requested_ = true;
|
||||||
|
|
||||||
@@ -392,15 +398,19 @@ bool GstEnginePipeline::Finish() {
|
|||||||
SetStateAsync(GST_STATE_NULL);
|
SetStateAsync(GST_STATE_NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
return finished_;
|
return finished_.value();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db, QString &error) {
|
bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db, QString &error) {
|
||||||
|
|
||||||
media_url_ = media_url;
|
{
|
||||||
stream_url_ = stream_url;
|
QMutexLocker l(&mutex_url_);
|
||||||
gst_url_ = gst_url;
|
media_url_ = media_url;
|
||||||
|
stream_url_ = stream_url;
|
||||||
|
gst_url_ = gst_url;
|
||||||
|
}
|
||||||
|
|
||||||
end_offset_nanosec_ = end_nanosec;
|
end_offset_nanosec_ = end_nanosec;
|
||||||
ebur128_loudness_normalizing_gain_db_ = ebur128_loudness_normalizing_gain_db;
|
ebur128_loudness_normalizing_gain_db_ = ebur128_loudness_normalizing_gain_db;
|
||||||
|
|
||||||
@@ -448,7 +458,10 @@ bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_ur
|
|||||||
flags &= ~GST_PLAY_FLAG_SOFT_VOLUME;
|
flags &= ~GST_PLAY_FLAG_SOFT_VOLUME;
|
||||||
g_object_set(G_OBJECT(pipeline_), "flags", flags, nullptr);
|
g_object_set(G_OBJECT(pipeline_), "flags", flags, nullptr);
|
||||||
|
|
||||||
g_object_set(G_OBJECT(pipeline_), "uri", gst_url.constData(), nullptr);
|
{
|
||||||
|
QMutexLocker l(&mutex_url_);
|
||||||
|
g_object_set(G_OBJECT(pipeline_), "uri", gst_url.constData(), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
pipeline_connected_ = true;
|
pipeline_connected_ = true;
|
||||||
|
|
||||||
@@ -968,7 +981,7 @@ GstPadProbeReturn GstEnginePipeline::UpstreamEventsProbeCallback(GstPad *pad, Gs
|
|||||||
|
|
||||||
switch (GST_EVENT_TYPE(e)) {
|
switch (GST_EVENT_TYPE(e)) {
|
||||||
case GST_EVENT_SEGMENT:
|
case GST_EVENT_SEGMENT:
|
||||||
if (!instance->segment_start_received_) {
|
if (!instance->segment_start_received_.value()) {
|
||||||
// The segment start time is used to calculate the proper offset of data buffers from the start of the stream
|
// The segment start time is used to calculate the proper offset of data buffers from the start of the stream
|
||||||
const GstSegment *segment = nullptr;
|
const GstSegment *segment = nullptr;
|
||||||
gst_event_parse_segment(e, &segment);
|
gst_event_parse_segment(e, &segment);
|
||||||
@@ -1008,7 +1021,7 @@ void GstEnginePipeline::ElementAddedCallback(GstBin *bin, GstBin *sub_bin, GstEl
|
|||||||
}
|
}
|
||||||
|
|
||||||
instance->SetupVolume(volume);
|
instance->SetupVolume(volume);
|
||||||
instance->SetVolume(instance->volume_percent_);
|
instance->SetVolume(instance->volume_percent_.value());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1036,11 +1049,14 @@ void GstEnginePipeline::SourceSetupCallback(GstElement *playbin, GstElement *sou
|
|||||||
|
|
||||||
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||||
|
|
||||||
if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "device") && !instance->source_device().isEmpty()) {
|
{
|
||||||
// Gstreamer is not able to handle device in URL (referring to Gstreamer documentation, this might be added in the future).
|
QMutexLocker l(&instance->mutex_source_device_);
|
||||||
// Despite that, for now we include device inside URL: we decompose it during Init and set device here, when this callback is called.
|
if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "device") && !instance->source_device().isEmpty()) {
|
||||||
qLog(Debug) << "Setting device";
|
// Gstreamer is not able to handle device in URL (referring to Gstreamer documentation, this might be added in the future).
|
||||||
g_object_set(source, "device", instance->source_device().toLocal8Bit().constData(), nullptr);
|
// Despite that, for now we include device inside URL: we decompose it during Init and set device here, when this callback is called.
|
||||||
|
qLog(Debug) << "Setting device";
|
||||||
|
g_object_set(source, "device", instance->source_device().toLocal8Bit().constData(), nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "user-agent")) {
|
if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "user-agent")) {
|
||||||
@@ -1050,40 +1066,46 @@ void GstEnginePipeline::SourceSetupCallback(GstElement *playbin, GstElement *sou
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "ssl-strict")) {
|
if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "ssl-strict")) {
|
||||||
qLog(Debug) << "Turning" << (instance->strict_ssl_enabled_ ? "on" : "off") << "strict SSL";
|
qLog(Debug) << "Turning" << (instance->strict_ssl_enabled_.value() ? "on" : "off") << "strict SSL";
|
||||||
g_object_set(source, "ssl-strict", instance->strict_ssl_enabled_ ? TRUE : FALSE, nullptr);
|
g_object_set(source, "ssl-strict", instance->strict_ssl_enabled_.value() ? TRUE : FALSE, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!instance->proxy_address_.isEmpty() && g_object_class_find_property(G_OBJECT_GET_CLASS(source), "proxy")) {
|
{
|
||||||
qLog(Debug) << "Setting proxy to" << instance->proxy_address_;
|
QMutexLocker l(&instance->mutex_proxy_);
|
||||||
g_object_set(source, "proxy", instance->proxy_address_.toUtf8().constData(), nullptr);
|
if (!instance->proxy_address_.isEmpty() && g_object_class_find_property(G_OBJECT_GET_CLASS(source), "proxy")) {
|
||||||
if (instance->proxy_authentication_ &&
|
qLog(Debug) << "Setting proxy to" << instance->proxy_address_;
|
||||||
g_object_class_find_property(G_OBJECT_GET_CLASS(source), "proxy-id") &&
|
g_object_set(source, "proxy", instance->proxy_address_.toUtf8().constData(), nullptr);
|
||||||
g_object_class_find_property(G_OBJECT_GET_CLASS(source), "proxy-pw") &&
|
if (instance->proxy_authentication_ &&
|
||||||
!instance->proxy_user_.isEmpty() &&
|
g_object_class_find_property(G_OBJECT_GET_CLASS(source), "proxy-id") &&
|
||||||
!instance->proxy_pass_.isEmpty())
|
g_object_class_find_property(G_OBJECT_GET_CLASS(source), "proxy-pw") &&
|
||||||
{
|
!instance->proxy_user_.isEmpty() &&
|
||||||
g_object_set(source, "proxy-id", instance->proxy_user_.toUtf8().constData(), "proxy-pw", instance->proxy_pass_.toUtf8().constData(), nullptr);
|
!instance->proxy_pass_.isEmpty())
|
||||||
|
{
|
||||||
|
g_object_set(source, "proxy-id", instance->proxy_user_.toUtf8().constData(), "proxy-pw", instance->proxy_pass_.toUtf8().constData(), nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_SPOTIFY
|
#ifdef HAVE_SPOTIFY
|
||||||
if (instance->media_url_.scheme() == QStringLiteral("spotify")) {
|
{
|
||||||
if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "bitrate")) {
|
QMutexLocker l(&instance->mutex_url_);
|
||||||
g_object_set(source, "bitrate", 2, nullptr);
|
if (instance->media_url_.scheme() == QStringLiteral("spotify")) {
|
||||||
}
|
if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "bitrate")) {
|
||||||
if (!instance->spotify_username_.isEmpty() &&
|
g_object_set(source, "bitrate", 2, nullptr);
|
||||||
!instance->spotify_password_.isEmpty() &&
|
}
|
||||||
g_object_class_find_property(G_OBJECT_GET_CLASS(source), "username") &&
|
if (!instance->spotify_username_.isEmpty() &&
|
||||||
g_object_class_find_property(G_OBJECT_GET_CLASS(source), "password")) {
|
!instance->spotify_password_.isEmpty() &&
|
||||||
g_object_set(source, "username", instance->spotify_username_.toUtf8().constData(), nullptr);
|
g_object_class_find_property(G_OBJECT_GET_CLASS(source), "username") &&
|
||||||
g_object_set(source, "password", instance->spotify_password_.toUtf8().constData(), nullptr);
|
g_object_class_find_property(G_OBJECT_GET_CLASS(source), "password")) {
|
||||||
|
g_object_set(source, "username", instance->spotify_username_.toUtf8().constData(), nullptr);
|
||||||
|
g_object_set(source, "password", instance->spotify_password_.toUtf8().constData(), nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// If the pipeline was buffering we stop that now.
|
// If the pipeline was buffering we stop that now.
|
||||||
if (instance->buffering_) {
|
if (instance->buffering_.value()) {
|
||||||
qLog(Debug) << "Buffering finished";
|
qLog(Debug) << "Buffering finished";
|
||||||
instance->buffering_ = false;
|
instance->buffering_ = false;
|
||||||
Q_EMIT instance->BufferingFinished();
|
Q_EMIT instance->BufferingFinished();
|
||||||
@@ -1099,12 +1121,13 @@ void GstEnginePipeline::NotifyVolumeCallback(GstElement *element, GParamSpec *pa
|
|||||||
|
|
||||||
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||||
|
|
||||||
if (!instance->volume_set_) return;
|
if (!instance->volume_set_.value()) return;
|
||||||
|
|
||||||
g_object_get(G_OBJECT(instance->volume_), "volume", &instance->volume_internal_, nullptr);
|
const double volume_internal = instance->volume_internal_.value();
|
||||||
|
g_object_get(G_OBJECT(instance->volume_), "volume", &volume_internal, nullptr);
|
||||||
|
|
||||||
const uint volume_percent = static_cast<uint>(qBound(0L, lround(instance->volume_internal_ / 0.01), 100L));
|
const uint volume_percent = static_cast<uint>(qBound(0L, lround(instance->volume_internal_.value() / 0.01), 100L));
|
||||||
if (volume_percent != instance->volume_percent_) {
|
if (volume_percent != instance->volume_percent_.value()) {
|
||||||
instance->volume_percent_ = volume_percent;
|
instance->volume_percent_ = volume_percent;
|
||||||
Q_EMIT instance->VolumeChanged(volume_percent);
|
Q_EMIT instance->VolumeChanged(volume_percent);
|
||||||
}
|
}
|
||||||
@@ -1137,8 +1160,8 @@ void GstEnginePipeline::PadAddedCallback(GstElement *element, GstPad *pad, gpoin
|
|||||||
instance->pad_probe_cb_id_ = gst_pad_add_probe(pad, static_cast<GstPadProbeType>(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH), PadProbeCallback, instance, nullptr);
|
instance->pad_probe_cb_id_ = gst_pad_add_probe(pad, static_cast<GstPadProbeType>(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH), PadProbeCallback, instance, nullptr);
|
||||||
|
|
||||||
instance->pipeline_connected_ = true;
|
instance->pipeline_connected_ = true;
|
||||||
if (instance->pending_seek_nanosec_ != -1 && instance->pipeline_active_) {
|
if (instance->pending_seek_nanosec_.value() != -1 && instance->pipeline_active_.value()) {
|
||||||
QMetaObject::invokeMethod(instance, "Seek", Qt::QueuedConnection, Q_ARG(qint64, instance->pending_seek_nanosec_));
|
QMetaObject::invokeMethod(instance, "Seek", Qt::QueuedConnection, Q_ARG(qint64, instance->pending_seek_nanosec_.value()));
|
||||||
instance->pending_seek_nanosec_ = -1;
|
instance->pending_seek_nanosec_ = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1206,7 +1229,7 @@ GstPadProbeReturn GstEnginePipeline::BufferProbeCallback(GstPad *pad, GstPadProb
|
|||||||
GstBuffer *buf = gst_pad_probe_info_get_buffer(info);
|
GstBuffer *buf = gst_pad_probe_info_get_buffer(info);
|
||||||
GstBuffer *buf16 = nullptr;
|
GstBuffer *buf16 = nullptr;
|
||||||
|
|
||||||
quint64 start_time = GST_BUFFER_TIMESTAMP(buf) - instance->segment_start_;
|
quint64 start_time = GST_BUFFER_TIMESTAMP(buf) - instance->segment_start_.value();
|
||||||
quint64 duration = GST_BUFFER_DURATION(buf);
|
quint64 duration = GST_BUFFER_DURATION(buf);
|
||||||
qint64 end_time = static_cast<qint64>(start_time + duration);
|
qint64 end_time = static_cast<qint64>(start_time + duration);
|
||||||
|
|
||||||
@@ -1324,19 +1347,23 @@ GstPadProbeReturn GstEnginePipeline::BufferProbeCallback(GstPad *pad, GstPadProb
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the end time of this buffer so we can stop playback if it's after the end time of this song.
|
// Calculate the end time of this buffer so we can stop playback if it's after the end time of this song.
|
||||||
if (instance->end_offset_nanosec_ > 0 && end_time > instance->end_offset_nanosec_) {
|
if (instance->end_offset_nanosec_.value() > 0 && end_time > instance->end_offset_nanosec_.value()) {
|
||||||
if (instance->has_next_valid_url() && instance->next_stream_url_ == instance->stream_url_ && instance->next_beginning_offset_nanosec_ == instance->end_offset_nanosec_) {
|
if (instance->HasNextUrl()) {
|
||||||
// The "next" song is actually the next segment of this file - so cheat and keep on playing, but just tell the Engine we've moved on.
|
QMutexLocker mutex_locker_url(&instance->mutex_url_);
|
||||||
instance->end_offset_nanosec_ = instance->next_end_offset_nanosec_;
|
QMutexLocker mutex_locker_next_url(&instance->mutex_next_url_);
|
||||||
instance->next_media_url_.clear();
|
if (instance->next_stream_url_ == instance->stream_url_ && instance->next_beginning_offset_nanosec_ == instance->end_offset_nanosec_) {
|
||||||
instance->next_stream_url_.clear();
|
// The "next" song is actually the next segment of this file - so cheat and keep on playing, but just tell the Engine we've moved on.
|
||||||
instance->next_gst_url_.clear();
|
instance->end_offset_nanosec_ = instance->next_end_offset_nanosec_;
|
||||||
instance->next_beginning_offset_nanosec_ = 0;
|
instance->next_media_url_.clear();
|
||||||
instance->next_end_offset_nanosec_ = 0;
|
instance->next_stream_url_.clear();
|
||||||
|
instance->next_gst_url_.clear();
|
||||||
|
instance->next_beginning_offset_nanosec_ = 0;
|
||||||
|
instance->next_end_offset_nanosec_ = 0;
|
||||||
|
|
||||||
// GstEngine will try to seek to the start of the new section, but we're already there so ignore it.
|
// GstEngine will try to seek to the start of the new section, but we're already there so ignore it.
|
||||||
instance->ignore_next_seek_ = true;
|
instance->ignore_next_seek_ = true;
|
||||||
Q_EMIT instance->EndOfStreamReached(instance->id(), true);
|
Q_EMIT instance->EndOfStreamReached(instance->id(), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// There's no next song
|
// There's no next song
|
||||||
@@ -1354,11 +1381,14 @@ void GstEnginePipeline::AboutToFinishCallback(GstPlayBin *playbin, gpointer self
|
|||||||
|
|
||||||
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||||
|
|
||||||
qLog(Debug) << "Stream from URL" << instance->gst_url_ << "about to finish.";
|
{
|
||||||
|
QMutexLocker l(&instance->mutex_url_);
|
||||||
|
qLog(Debug) << "Stream from URL" << instance->gst_url_ << "about to finish.";
|
||||||
|
}
|
||||||
|
|
||||||
instance->about_to_finish_ = true;
|
instance->about_to_finish_ = true;
|
||||||
|
|
||||||
if (instance->has_next_valid_url() && !instance->next_uri_set_) {
|
if (instance->HasNextUrl() && !instance->next_uri_set_.value()) {
|
||||||
instance->SetNextUrl();
|
instance->SetNextUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1458,18 +1488,22 @@ void GstEnginePipeline::StreamStatusMessageReceived(GstMessage *msg) {
|
|||||||
|
|
||||||
void GstEnginePipeline::StreamStartMessageReceived() {
|
void GstEnginePipeline::StreamStartMessageReceived() {
|
||||||
|
|
||||||
if (next_uri_set_) {
|
if (next_uri_set_.value()) {
|
||||||
qLog(Debug) << "Stream changed from URL" << gst_url_ << "to" << next_gst_url_;
|
|
||||||
next_uri_set_ = false;
|
next_uri_set_ = false;
|
||||||
next_uri_reset_ = false;
|
next_uri_reset_ = false;
|
||||||
about_to_finish_ = false;
|
about_to_finish_ = false;
|
||||||
media_url_ = next_media_url_;
|
{
|
||||||
stream_url_ = next_stream_url_;
|
QMutexLocker lock_url(&mutex_url_);
|
||||||
gst_url_ = next_gst_url_;
|
QMutexLocker lock_next_url(&mutex_next_url_);
|
||||||
|
qLog(Debug) << "Stream changed from URL" << gst_url_ << "to" << next_gst_url_;
|
||||||
|
media_url_ = next_media_url_;
|
||||||
|
stream_url_ = next_stream_url_;
|
||||||
|
gst_url_ = next_gst_url_;
|
||||||
|
next_stream_url_.clear();
|
||||||
|
next_media_url_.clear();
|
||||||
|
next_gst_url_.clear();
|
||||||
|
}
|
||||||
end_offset_nanosec_ = next_end_offset_nanosec_;
|
end_offset_nanosec_ = next_end_offset_nanosec_;
|
||||||
next_stream_url_.clear();
|
|
||||||
next_media_url_.clear();
|
|
||||||
next_gst_url_.clear();
|
|
||||||
next_beginning_offset_nanosec_ = 0;
|
next_beginning_offset_nanosec_ = 0;
|
||||||
next_end_offset_nanosec_ = 0;
|
next_end_offset_nanosec_ = 0;
|
||||||
|
|
||||||
@@ -1505,6 +1539,7 @@ void GstEnginePipeline::ElementMessageReceived(GstMessage *msg) {
|
|||||||
const char *uri = gst_structure_get_string(structure, "new-location");
|
const char *uri = gst_structure_get_string(structure, "new-location");
|
||||||
|
|
||||||
// Set the redirect URL. In mmssrc redirect messages come during the initial state change to PLAYING, so callers can pick up this URL after the state change has failed.
|
// Set the redirect URL. In mmssrc redirect messages come during the initial state change to PLAYING, so callers can pick up this URL after the state change has failed.
|
||||||
|
QMutexLocker l(&mutex_redirect_url_);
|
||||||
redirect_url_ = uri;
|
redirect_url_ = uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1523,7 +1558,7 @@ void GstEnginePipeline::ErrorMessageReceived(GstMessage *msg) {
|
|||||||
g_error_free(error);
|
g_error_free(error);
|
||||||
g_free(debugs);
|
g_free(debugs);
|
||||||
|
|
||||||
if (pipeline_active_ && next_uri_set_ && (domain == GST_CORE_ERROR || domain == GST_RESOURCE_ERROR || domain == GST_STREAM_ERROR)) {
|
if (pipeline_active_.value() && next_uri_set_.value() && (domain == GST_CORE_ERROR || domain == GST_RESOURCE_ERROR || domain == GST_STREAM_ERROR)) {
|
||||||
// A track is still playing and the next uri is not playable. We ignore the error here so it can play until the end.
|
// A track is still playing and the next uri is not playable. We ignore the error here so it can play until the end.
|
||||||
// But there is no message send to the bus when the current track finishes, we have to add an EOS ourself.
|
// But there is no message send to the bus when the current track finishes, we have to add an EOS ourself.
|
||||||
qLog(Info) << "Ignoring error" << domain << code << message << debugstr << "when loading next track";
|
qLog(Info) << "Ignoring error" << domain << code << message << debugstr << "when loading next track";
|
||||||
@@ -1536,10 +1571,13 @@ void GstEnginePipeline::ErrorMessageReceived(GstMessage *msg) {
|
|||||||
qLog(Error) << __FUNCTION__ << "ID:" << id() << "Domain:" << domain << "Code:" << code << "Error:" << message;
|
qLog(Error) << __FUNCTION__ << "ID:" << id() << "Domain:" << domain << "Code:" << code << "Error:" << message;
|
||||||
qLog(Error) << __FUNCTION__ << "ID:" << id() << "Domain:" << domain << "Code:" << code << "Debug:" << debugstr;
|
qLog(Error) << __FUNCTION__ << "ID:" << id() << "Domain:" << domain << "Code:" << code << "Debug:" << debugstr;
|
||||||
|
|
||||||
if (!redirect_url_.isEmpty() && debugstr.contains(QLatin1String("A redirect message was posted on the bus and should have been handled by the application."))) {
|
{
|
||||||
// mmssrc posts a message on the bus *and* makes an error message when it wants to do a redirect.
|
QMutexLocker l(&mutex_redirect_url_);
|
||||||
// We handle the message, but now we have to ignore the error too.
|
if (!redirect_url_.isEmpty() && debugstr.contains(QLatin1String("A redirect message was posted on the bus and should have been handled by the application."))) {
|
||||||
return;
|
// mmssrc posts a message on the bus *and* makes an error message when it wants to do a redirect.
|
||||||
|
// We handle the message, but now we have to ignore the error too.
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
@@ -1555,15 +1593,18 @@ void GstEnginePipeline::ErrorMessageReceived(GstMessage *msg) {
|
|||||||
|
|
||||||
void GstEnginePipeline::TagMessageReceived(GstMessage *msg) {
|
void GstEnginePipeline::TagMessageReceived(GstMessage *msg) {
|
||||||
|
|
||||||
if (ignore_tags_) return;
|
if (ignore_tags_.value()) return;
|
||||||
|
|
||||||
GstTagList *taglist = nullptr;
|
GstTagList *taglist = nullptr;
|
||||||
gst_message_parse_tag(msg, &taglist);
|
gst_message_parse_tag(msg, &taglist);
|
||||||
|
|
||||||
EngineMetadata engine_metadata;
|
EngineMetadata engine_metadata;
|
||||||
engine_metadata.type = EngineMetadata::Type::Current;
|
engine_metadata.type = EngineMetadata::Type::Current;
|
||||||
engine_metadata.media_url = media_url_;
|
{
|
||||||
engine_metadata.stream_url = stream_url_;
|
QMutexLocker l(&mutex_url_);
|
||||||
|
engine_metadata.media_url = media_url_;
|
||||||
|
engine_metadata.stream_url = stream_url_;
|
||||||
|
}
|
||||||
engine_metadata.title = ParseStrTag(taglist, GST_TAG_TITLE);
|
engine_metadata.title = ParseStrTag(taglist, GST_TAG_TITLE);
|
||||||
engine_metadata.artist = ParseStrTag(taglist, GST_TAG_ARTIST);
|
engine_metadata.artist = ParseStrTag(taglist, GST_TAG_ARTIST);
|
||||||
engine_metadata.comment = ParseStrTag(taglist, GST_TAG_COMMENT);
|
engine_metadata.comment = ParseStrTag(taglist, GST_TAG_COMMENT);
|
||||||
@@ -1643,35 +1684,38 @@ void GstEnginePipeline::StateChangedMessageReceived(GstMessage *msg) {
|
|||||||
|
|
||||||
qLog(Debug) << "Pipeline state changed from" << GstStateText(old_state) << "to" << GstStateText(new_state);
|
qLog(Debug) << "Pipeline state changed from" << GstStateText(old_state) << "to" << GstStateText(new_state);
|
||||||
|
|
||||||
if (!pipeline_active_ && (new_state == GST_STATE_PAUSED || new_state == GST_STATE_PLAYING)) {
|
if (!pipeline_active_.value() && (new_state == GST_STATE_PAUSED || new_state == GST_STATE_PLAYING)) {
|
||||||
qLog(Debug) << "Pipeline is active";
|
qLog(Debug) << "Pipeline is active";
|
||||||
pipeline_active_ = true;
|
pipeline_active_ = true;
|
||||||
if (pipeline_connected_) {
|
if (pipeline_connected_.value()) {
|
||||||
if (!volume_set_) {
|
if (!volume_set_.value()) {
|
||||||
SetVolume(volume_percent_);
|
SetVolume(volume_percent_.value());
|
||||||
}
|
}
|
||||||
if (pending_seek_nanosec_ != -1) {
|
if (pending_seek_nanosec_.value() != -1) {
|
||||||
if (next_uri_reset_ && new_state == GST_STATE_PAUSED) {
|
if (next_uri_reset_.value() && new_state == GST_STATE_PAUSED) {
|
||||||
qLog(Debug) << "Reverting next uri and going to playing state.";
|
qLog(Debug) << "Reverting next uri and going to playing state.";
|
||||||
next_uri_reset_ = false;
|
next_uri_reset_ = false;
|
||||||
pending_state_ = GST_STATE_PLAYING;
|
pending_state_ = GST_STATE_PLAYING;
|
||||||
SeekDelayed(pending_seek_nanosec_);
|
SeekDelayed(pending_seek_nanosec_.value());
|
||||||
pending_seek_nanosec_ = -1;
|
pending_seek_nanosec_ = -1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SeekAsync(pending_seek_nanosec_);
|
SeekAsync(pending_seek_nanosec_.value());
|
||||||
pending_seek_nanosec_ = -1;
|
pending_seek_nanosec_ = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (pipeline_active_ && new_state != GST_STATE_PAUSED && new_state != GST_STATE_PLAYING) {
|
else if (pipeline_active_.value() && new_state != GST_STATE_PAUSED && new_state != GST_STATE_PLAYING) {
|
||||||
qLog(Debug) << "Pipeline is inactive";
|
qLog(Debug) << "Pipeline is inactive";
|
||||||
pipeline_active_ = false;
|
pipeline_active_ = false;
|
||||||
if (next_uri_set_ && new_state == GST_STATE_READY) {
|
if (next_uri_set_.value() && new_state == GST_STATE_READY) {
|
||||||
next_uri_set_ = false;
|
next_uri_set_ = false;
|
||||||
g_object_set(G_OBJECT(pipeline_), "uri", gst_url_.constData(), nullptr);
|
{
|
||||||
|
QMutexLocker l(&mutex_url_);
|
||||||
|
g_object_set(G_OBJECT(pipeline_), "uri", gst_url_.constData(), nullptr);
|
||||||
|
}
|
||||||
if (pending_seek_nanosec_ == -1) {
|
if (pending_seek_nanosec_ == -1) {
|
||||||
qLog(Debug) << "Reverting next uri and going to playing state.";
|
qLog(Debug) << "Reverting next uri and going to playing state.";
|
||||||
SetStateAsync(GST_STATE_PLAYING);
|
SetStateAsync(GST_STATE_PLAYING);
|
||||||
@@ -1684,13 +1728,13 @@ void GstEnginePipeline::StateChangedMessageReceived(GstMessage *msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pipeline_active_ && !buffering_) {
|
if (pipeline_active_.value() && !buffering_.value()) {
|
||||||
if (pending_seek_nanosec_ != -1 && new_state == GST_STATE_PAUSED) {
|
if (pending_seek_nanosec_.value() != -1 && new_state == GST_STATE_PAUSED) {
|
||||||
SeekAsync(pending_seek_nanosec_);
|
SeekAsync(pending_seek_nanosec_.value());
|
||||||
pending_seek_nanosec_ = -1;
|
pending_seek_nanosec_ = -1;
|
||||||
}
|
}
|
||||||
else if (pending_state_ != GST_STATE_NULL) {
|
else if (pending_state_.value() != GST_STATE_NULL) {
|
||||||
SetStateAsync(pending_state_);
|
SetStateAsync(pending_state_.value());
|
||||||
pending_state_ = GST_STATE_NULL;
|
pending_state_ = GST_STATE_NULL;
|
||||||
}
|
}
|
||||||
if (fader_ && fader_->state() != QTimeLine::State::Running && new_state == GST_STATE_PLAYING) {
|
if (fader_ && fader_->state() != QTimeLine::State::Running && new_state == GST_STATE_PLAYING) {
|
||||||
@@ -1699,6 +1743,11 @@ void GstEnginePipeline::StateChangedMessageReceived(GstMessage *msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (new_state == GST_STATE_NULL && !finished_.value() && finish_requested_.value()) {
|
||||||
|
finished_ = true;
|
||||||
|
Q_EMIT Finished();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::BufferingMessageReceived(GstMessage *msg) {
|
void GstEnginePipeline::BufferingMessageReceived(GstMessage *msg) {
|
||||||
@@ -1713,58 +1762,36 @@ void GstEnginePipeline::BufferingMessageReceived(GstMessage *msg) {
|
|||||||
|
|
||||||
const GstState current_state = state();
|
const GstState current_state = state();
|
||||||
|
|
||||||
if (percent < 100 && !buffering_) {
|
if (percent < 100 && !buffering_.value()) {
|
||||||
qLog(Debug) << "Buffering started";
|
qLog(Debug) << "Buffering started";
|
||||||
buffering_ = true;
|
buffering_ = true;
|
||||||
Q_EMIT BufferingStarted();
|
Q_EMIT BufferingStarted();
|
||||||
if (current_state == GST_STATE_PLAYING) {
|
if (current_state == GST_STATE_PLAYING) {
|
||||||
SetStateAsync(GST_STATE_PAUSED);
|
SetStateAsync(GST_STATE_PAUSED);
|
||||||
if (pending_state_ == GST_STATE_NULL) {
|
if (pending_state_.value() == GST_STATE_NULL) {
|
||||||
pending_state_ = current_state;
|
pending_state_ = current_state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (percent == 100 && buffering_) {
|
else if (percent == 100 && buffering_.value()) {
|
||||||
qLog(Debug) << "Buffering finished";
|
qLog(Debug) << "Buffering finished";
|
||||||
buffering_ = false;
|
buffering_ = false;
|
||||||
Q_EMIT BufferingFinished();
|
Q_EMIT BufferingFinished();
|
||||||
if (pending_seek_nanosec_ != -1) {
|
if (pending_seek_nanosec_.value() != -1) {
|
||||||
SeekAsync(pending_seek_nanosec_);
|
SeekAsync(pending_seek_nanosec_.value());
|
||||||
pending_seek_nanosec_ = -1;
|
pending_seek_nanosec_ = -1;
|
||||||
}
|
}
|
||||||
else if (pending_state_ != GST_STATE_NULL) {
|
else if (pending_state_.value() != GST_STATE_NULL) {
|
||||||
SetStateAsync(pending_state_);
|
SetStateAsync(pending_state_.value());
|
||||||
pending_state_ = GST_STATE_NULL;
|
pending_state_ = GST_STATE_NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (buffering_) {
|
else if (buffering_.value()) {
|
||||||
Q_EMIT BufferingProgress(percent);
|
Q_EMIT BufferingProgress(percent);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 GstEnginePipeline::position() const {
|
|
||||||
|
|
||||||
if (pipeline_active_) {
|
|
||||||
gint64 current_position = 0;
|
|
||||||
if (gst_element_query_position(pipeline_, GST_FORMAT_TIME, ¤t_position)) {
|
|
||||||
last_known_position_ns_ = current_position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return last_known_position_ns_;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
qint64 GstEnginePipeline::length() const {
|
|
||||||
|
|
||||||
gint64 value = 0;
|
|
||||||
if (pipeline_) gst_element_query_duration(pipeline_, GST_FORMAT_TIME, &value);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
GstState GstEnginePipeline::state() const {
|
GstState GstEnginePipeline::state() const {
|
||||||
|
|
||||||
GstState s = GST_STATE_NULL, sp = GST_STATE_NULL;
|
GstState s = GST_STATE_NULL, sp = GST_STATE_NULL;
|
||||||
@@ -1776,15 +1803,37 @@ GstState GstEnginePipeline::state() const {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qint64 GstEnginePipeline::length() const {
|
||||||
|
|
||||||
|
gint64 value = 0;
|
||||||
|
if (pipeline_) gst_element_query_duration(pipeline_, GST_FORMAT_TIME, &value);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 GstEnginePipeline::position() const {
|
||||||
|
|
||||||
|
if (pipeline_active_.value()) {
|
||||||
|
gint64 current_position = 0;
|
||||||
|
if (gst_element_query_position(pipeline_, GST_FORMAT_TIME, ¤t_position)) {
|
||||||
|
last_known_position_ns_ = current_position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return last_known_position_ns_;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
QFuture<GstStateChangeReturn> GstEnginePipeline::SetStateAsync(const GstState state) {
|
QFuture<GstStateChangeReturn> GstEnginePipeline::SetStateAsync(const GstState state) {
|
||||||
|
|
||||||
qLog(Debug) << "Setting pipeline" << id_ << "state to" << GstStateText(state);
|
qLog(Debug) << "Setting pipeline" << id() << "state to" << GstStateText(state);
|
||||||
|
|
||||||
QFutureWatcher<GstStateChangeReturn> *watcher = new QFutureWatcher<GstStateChangeReturn>();
|
QFutureWatcher<GstStateChangeReturn> *watcher = new QFutureWatcher<GstStateChangeReturn>();
|
||||||
QObject::connect(watcher, &QFutureWatcher<GstStateChangeReturn>::finished, this, [this, watcher, state]() {
|
QObject::connect(watcher, &QFutureWatcher<GstStateChangeReturn>::finished, this, [this, watcher, state]() {
|
||||||
const GstStateChangeReturn state_change = watcher->result();
|
const GstStateChangeReturn state_change_return = watcher->result();
|
||||||
watcher->deleteLater();
|
watcher->deleteLater();
|
||||||
SetStateAsyncFinished(state, state_change);
|
SetStateAsyncFinished(state, state_change_return);
|
||||||
});
|
});
|
||||||
QFuture<GstStateChangeReturn> future = QtConcurrent::run(&set_state_threadpool_, &gst_element_set_state, pipeline_, state);
|
QFuture<GstStateChangeReturn> future = QtConcurrent::run(&set_state_threadpool_, &gst_element_set_state, pipeline_, state);
|
||||||
watcher->setFuture(future);
|
watcher->setFuture(future);
|
||||||
@@ -1793,15 +1842,15 @@ QFuture<GstStateChangeReturn> GstEnginePipeline::SetStateAsync(const GstState st
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::SetStateAsyncFinished(const GstState state, const GstStateChangeReturn state_change) {
|
void GstEnginePipeline::SetStateAsyncFinished(const GstState state, const GstStateChangeReturn state_change_return) {
|
||||||
|
|
||||||
switch (state_change) {
|
switch (state_change_return) {
|
||||||
case GST_STATE_CHANGE_SUCCESS:
|
case GST_STATE_CHANGE_SUCCESS:
|
||||||
case GST_STATE_CHANGE_ASYNC:
|
case GST_STATE_CHANGE_ASYNC:
|
||||||
case GST_STATE_CHANGE_NO_PREROLL:
|
case GST_STATE_CHANGE_NO_PREROLL:
|
||||||
qLog(Debug) << "Pipeline" << id_ << "state successfully set to" << GstStateText(state);
|
qLog(Debug) << "Pipeline" << id() << "state successfully set to" << GstStateText(state);
|
||||||
Q_EMIT SetStateFinished(state_change);
|
Q_EMIT SetStateFinished(state_change_return);
|
||||||
if (!finished_ && finish_requested_) {
|
if (!finished_.value() && finish_requested_.value()) {
|
||||||
finished_ = true;
|
finished_ = true;
|
||||||
Q_EMIT Finished();
|
Q_EMIT Finished();
|
||||||
}
|
}
|
||||||
@@ -1829,17 +1878,17 @@ QFuture<GstStateChangeReturn> GstEnginePipeline::Play(const bool pause, const qu
|
|||||||
|
|
||||||
bool GstEnginePipeline::Seek(const qint64 nanosec) {
|
bool GstEnginePipeline::Seek(const qint64 nanosec) {
|
||||||
|
|
||||||
if (ignore_next_seek_) {
|
if (ignore_next_seek_.value()) {
|
||||||
ignore_next_seek_ = false;
|
ignore_next_seek_ = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pipeline_connected_ || !pipeline_active_) {
|
if (!pipeline_connected_.value() || !pipeline_active_.value()) {
|
||||||
pending_seek_nanosec_ = nanosec;
|
pending_seek_nanosec_ = nanosec;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next_uri_set_) {
|
if (next_uri_set_.value()) {
|
||||||
pending_seek_nanosec_ = nanosec;
|
pending_seek_nanosec_ = nanosec;
|
||||||
SetStateAsync(GST_STATE_READY);
|
SetStateAsync(GST_STATE_READY);
|
||||||
return true;
|
return true;
|
||||||
@@ -1854,9 +1903,9 @@ bool GstEnginePipeline::Seek(const qint64 nanosec) {
|
|||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
qLog(Debug) << "Seek succeeded";
|
qLog(Debug) << "Seek succeeded";
|
||||||
if (pending_state_ != GST_STATE_NULL) {
|
if (pending_state_.value() != GST_STATE_NULL) {
|
||||||
qLog(Debug) << "Setting state from pending state" << GstStateText(pending_state_);
|
qLog(Debug) << "Setting state from pending state" << GstStateText(pending_state_.value());
|
||||||
SetStateAsync(pending_state_);
|
SetStateAsync(pending_state_.value());
|
||||||
pending_state_ = GST_STATE_NULL;
|
pending_state_ = GST_STATE_NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1879,31 +1928,14 @@ void GstEnginePipeline::SeekDelayed(const qint64 nanosec) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::SetEBUR128LoudnessNormalizingGain_dB(const double ebur128_loudness_normalizing_gain_db) {
|
|
||||||
|
|
||||||
ebur128_loudness_normalizing_gain_db_ = ebur128_loudness_normalizing_gain_db;
|
|
||||||
UpdateEBUR128LoudnessNormalizingGaindB();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void GstEnginePipeline::UpdateEBUR128LoudnessNormalizingGaindB() {
|
|
||||||
|
|
||||||
if (volume_ebur128_) {
|
|
||||||
auto dB_to_mult = [](const double gain_dB) { return std::pow(10., gain_dB / 20.); };
|
|
||||||
|
|
||||||
g_object_set(G_OBJECT(volume_ebur128_), "volume", dB_to_mult(ebur128_loudness_normalizing_gain_db_), nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void GstEnginePipeline::SetVolume(const uint volume_percent) {
|
void GstEnginePipeline::SetVolume(const uint volume_percent) {
|
||||||
|
|
||||||
if (volume_) {
|
if (volume_) {
|
||||||
const double volume_internal = static_cast<double>(volume_percent) * 0.01;
|
const double volume_internal = static_cast<double>(volume_percent) * 0.01;
|
||||||
if (!volume_set_ || volume_internal != volume_internal_) {
|
if (!volume_set_.value() || volume_internal != volume_internal_.value()) {
|
||||||
volume_internal_ = volume_internal;
|
volume_internal_ = volume_internal;
|
||||||
g_object_set(G_OBJECT(volume_), "volume", volume_internal, nullptr);
|
g_object_set(G_OBJECT(volume_), "volume", volume_internal, nullptr);
|
||||||
if (pipeline_active_) {
|
if (pipeline_active_.value()) {
|
||||||
volume_set_ = true;
|
volume_set_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1913,14 +1945,6 @@ void GstEnginePipeline::SetVolume(const uint volume_percent) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::SetFaderVolume(const qreal volume) {
|
|
||||||
|
|
||||||
if (volume_fading_) {
|
|
||||||
g_object_set(G_OBJECT(volume_fading_), "volume", volume, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void GstEnginePipeline::SetStereoBalance(const float value) {
|
void GstEnginePipeline::SetStereoBalance(const float value) {
|
||||||
|
|
||||||
stereo_balance_ = value;
|
stereo_balance_ = value;
|
||||||
@@ -1973,8 +1997,27 @@ void GstEnginePipeline::UpdateEqualizer() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GstEnginePipeline::SetEBUR128LoudnessNormalizingGain_dB(const double ebur128_loudness_normalizing_gain_db) {
|
||||||
|
|
||||||
|
ebur128_loudness_normalizing_gain_db_ = ebur128_loudness_normalizing_gain_db;
|
||||||
|
UpdateEBUR128LoudnessNormalizingGaindB();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void GstEnginePipeline::UpdateEBUR128LoudnessNormalizingGaindB() {
|
||||||
|
|
||||||
|
if (volume_ebur128_) {
|
||||||
|
auto dB_to_mult = [](const double gain_dB) { return std::pow(10., gain_dB / 20.); };
|
||||||
|
|
||||||
|
g_object_set(G_OBJECT(volume_ebur128_), "volume", dB_to_mult(ebur128_loudness_normalizing_gain_db_), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::StartFader(const qint64 duration_nanosec, const QTimeLine::Direction direction, const QEasingCurve::Type shape, const bool use_fudge_timer) {
|
void GstEnginePipeline::StartFader(const qint64 duration_nanosec, const QTimeLine::Direction direction, const QEasingCurve::Type shape, const bool use_fudge_timer) {
|
||||||
|
|
||||||
|
fader_active_ = true;
|
||||||
|
|
||||||
const qint64 duration_msec = duration_nanosec / kNsecPerMsec;
|
const qint64 duration_msec = duration_nanosec / kNsecPerMsec;
|
||||||
|
|
||||||
// If there's already another fader running then start from the same time that one was already at.
|
// If there's already another fader running then start from the same time that one was already at.
|
||||||
@@ -2007,17 +2050,25 @@ void GstEnginePipeline::StartFader(const qint64 duration_nanosec, const QTimeLin
|
|||||||
|
|
||||||
SetFaderVolume(fader_->currentValue());
|
SetFaderVolume(fader_->currentValue());
|
||||||
|
|
||||||
qLog(Debug) << "Pipeline" << id_ << "with state" << GstStateText(state()) << "set to fade from" << start_time;
|
qLog(Debug) << "Pipeline" << id() << "with state" << GstStateText(state()) << "set to fade from" << start_time;
|
||||||
|
|
||||||
if (pipeline_active_) {
|
if (pipeline_active_.value()) {
|
||||||
fader_->resume();
|
fader_->resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GstEnginePipeline::SetFaderVolume(const qreal volume) {
|
||||||
|
|
||||||
|
if (volume_fading_) {
|
||||||
|
g_object_set(G_OBJECT(volume_fading_), "volume", volume, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::ResumeFaderAsync() {
|
void GstEnginePipeline::ResumeFaderAsync() {
|
||||||
|
|
||||||
if (fader_) {
|
if (fader_active_.value()) {
|
||||||
QMetaObject::invokeMethod(&*fader_, "resume", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(&*fader_, "resume", Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2025,8 +2076,9 @@ void GstEnginePipeline::ResumeFaderAsync() {
|
|||||||
|
|
||||||
void GstEnginePipeline::FaderTimelineFinished() {
|
void GstEnginePipeline::FaderTimelineFinished() {
|
||||||
|
|
||||||
qLog(Debug) << "Pipeline" << id_ << "finished fading";
|
qLog(Debug) << "Pipeline" << id() << "finished fading";
|
||||||
|
|
||||||
|
fader_active_ = false;
|
||||||
fader_.reset();
|
fader_.reset();
|
||||||
|
|
||||||
// Wait a little while longer before emitting the finished signal (and probably destroying the pipeline) to account for delays in the audio server/driver.
|
// Wait a little while longer before emitting the finished signal (and probably destroying the pipeline) to account for delays in the audio server/driver.
|
||||||
@@ -2046,7 +2098,7 @@ void GstEnginePipeline::timerEvent(QTimerEvent *e) {
|
|||||||
|
|
||||||
if (e->timerId() == fader_fudge_timer_.timerId()) {
|
if (e->timerId() == fader_fudge_timer_.timerId()) {
|
||||||
fader_fudge_timer_.stop();
|
fader_fudge_timer_.stop();
|
||||||
Q_EMIT FaderFinished(id_);
|
Q_EMIT FaderFinished(id());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2054,6 +2106,54 @@ void GstEnginePipeline::timerEvent(QTimerEvent *e) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GstEnginePipeline::HasNextUrl() const {
|
||||||
|
|
||||||
|
QMutexLocker l(&mutex_next_url_);
|
||||||
|
return next_stream_url_.isValid();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void GstEnginePipeline::PrepareNextUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_nanosec, const qint64 end_nanosec) {
|
||||||
|
|
||||||
|
{
|
||||||
|
QMutexLocker l(&mutex_next_url_);
|
||||||
|
next_media_url_ = media_url;
|
||||||
|
next_stream_url_ = stream_url;
|
||||||
|
next_gst_url_ = gst_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
next_beginning_offset_nanosec_ = beginning_nanosec;
|
||||||
|
next_end_offset_nanosec_ = end_nanosec;
|
||||||
|
|
||||||
|
if (about_to_finish_.value()) {
|
||||||
|
SetNextUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void GstEnginePipeline::SetNextUrl() {
|
||||||
|
|
||||||
|
if (about_to_finish_.value() && HasNextUrl() && !next_uri_set_.value()) {
|
||||||
|
// Set the next uri. When the current song ends it will be played automatically and a STREAM_START message is send to the bus.
|
||||||
|
// When the next uri is not playable an error message is send when the pipeline goes to PLAY (or PAUSE) state or immediately if it is currently in PLAY state.
|
||||||
|
next_uri_set_ = true;
|
||||||
|
{
|
||||||
|
QMutexLocker l(&mutex_next_url_);
|
||||||
|
qLog(Debug) << "Setting next URL to" << next_gst_url_;
|
||||||
|
g_object_set(G_OBJECT(pipeline_), "uri", next_gst_url_.constData(), nullptr);
|
||||||
|
}
|
||||||
|
about_to_finish_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void GstEnginePipeline::SetSourceDevice(const QString &device) {
|
||||||
|
|
||||||
|
QMutexLocker l(&mutex_source_device_);
|
||||||
|
source_device_ = device;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::AddBufferConsumer(GstBufferConsumer *consumer) {
|
void GstEnginePipeline::AddBufferConsumer(GstBufferConsumer *consumer) {
|
||||||
QMutexLocker l(&mutex_buffer_consumers_);
|
QMutexLocker l(&mutex_buffer_consumers_);
|
||||||
buffer_consumers_ << consumer;
|
buffer_consumers_ << consumer;
|
||||||
@@ -2069,29 +2169,3 @@ void GstEnginePipeline::RemoveAllBufferConsumers() {
|
|||||||
buffer_consumers_.clear();
|
buffer_consumers_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::PrepareNextUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_nanosec, const qint64 end_nanosec) {
|
|
||||||
|
|
||||||
next_media_url_ = media_url;
|
|
||||||
next_stream_url_ = stream_url;
|
|
||||||
next_gst_url_ = gst_url;
|
|
||||||
next_beginning_offset_nanosec_ = beginning_nanosec;
|
|
||||||
next_end_offset_nanosec_ = end_nanosec;
|
|
||||||
|
|
||||||
if (about_to_finish_) {
|
|
||||||
SetNextUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void GstEnginePipeline::SetNextUrl() {
|
|
||||||
|
|
||||||
if (about_to_finish_ && has_next_valid_url() && !next_uri_set_) {
|
|
||||||
// Set the next uri. When the current song ends it will be played automatically and a STREAM_START message is send to the bus.
|
|
||||||
// When the next uri is not playable an error message is send when the pipeline goes to PLAY (or PAUSE) state or immediately if it is currently in PLAY state.
|
|
||||||
next_uri_set_ = true;
|
|
||||||
qLog(Debug) << "Setting next URL to" << next_gst_url_;
|
|
||||||
g_object_set(G_OBJECT(pipeline_), "uri", next_gst_url_.constData(), nullptr);
|
|
||||||
about_to_finish_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include "core/shared_ptr.h"
|
#include "core/shared_ptr.h"
|
||||||
|
#include "core/mutex_protected.h"
|
||||||
#include "enginemetadata.h"
|
#include "enginemetadata.h"
|
||||||
|
|
||||||
class QTimer;
|
class QTimer;
|
||||||
@@ -59,7 +60,7 @@ class GstEnginePipeline : public QObject {
|
|||||||
~GstEnginePipeline() override;
|
~GstEnginePipeline() override;
|
||||||
|
|
||||||
// Globally unique across all pipelines.
|
// Globally unique across all pipelines.
|
||||||
int id() const { return id_; }
|
int id() const { return id_.value(); }
|
||||||
|
|
||||||
// Call these setters before Init
|
// Call these setters before Init
|
||||||
void set_output_device(const QString &output, const QVariant &device);
|
void set_output_device(const QString &output, const QVariant &device);
|
||||||
@@ -97,69 +98,63 @@ class GstEnginePipeline : public QObject {
|
|||||||
Q_INVOKABLE bool Seek(const qint64 nanosec);
|
Q_INVOKABLE bool Seek(const qint64 nanosec);
|
||||||
void SeekAsync(const qint64 nanosec);
|
void SeekAsync(const qint64 nanosec);
|
||||||
void SeekDelayed(const qint64 nanosec);
|
void SeekDelayed(const qint64 nanosec);
|
||||||
void SetEBUR128LoudnessNormalizingGain_dB(const double ebur128_loudness_normalizing_gain_db);
|
|
||||||
void SetVolume(const uint volume_percent);
|
void SetVolume(const uint volume_percent);
|
||||||
void SetStereoBalance(const float value);
|
void SetStereoBalance(const float value);
|
||||||
void SetEqualizerParams(const int preamp, const QList<int> &band_gains);
|
void SetEqualizerParams(const int preamp, const QList<int> &band_gains);
|
||||||
|
void SetEBUR128LoudnessNormalizingGain_dB(const double ebur128_loudness_normalizing_gain_db);
|
||||||
void StartFader(const qint64 duration_nanosec, const QTimeLine::Direction direction = QTimeLine::Forward, const QEasingCurve::Type shape = QEasingCurve::Linear, const bool use_fudge_timer = true);
|
|
||||||
|
|
||||||
// If this is set then it will be loaded automatically when playback finishes for gapless playback
|
// If this is set then it will be loaded automatically when playback finishes for gapless playback
|
||||||
|
bool HasNextUrl() const;
|
||||||
void PrepareNextUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_nanosec, const qint64 end_nanosec);
|
void PrepareNextUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_nanosec, const qint64 end_nanosec);
|
||||||
void SetNextUrl();
|
void SetNextUrl();
|
||||||
bool has_next_valid_url() const { return next_stream_url_.isValid(); }
|
|
||||||
|
|
||||||
void SetSourceDevice(const QString &device) { source_device_ = device; }
|
void SetSourceDevice(const QString &device);
|
||||||
|
|
||||||
|
void StartFader(const qint64 duration_nanosec, const QTimeLine::Direction direction = QTimeLine::Forward, const QEasingCurve::Type shape = QEasingCurve::Linear, const bool use_fudge_timer = true);
|
||||||
|
|
||||||
// Get information about the music playback
|
// Get information about the music playback
|
||||||
QUrl media_url() const { return media_url_; }
|
QUrl media_url() const { return media_url_; }
|
||||||
QUrl stream_url() const { return stream_url_; }
|
QUrl stream_url() const { return stream_url_; }
|
||||||
double ebur128_loudness_normalizing_gain_db() const { return ebur128_loudness_normalizing_gain_db_; }
|
|
||||||
QByteArray gst_url() const { return gst_url_; }
|
QByteArray gst_url() const { return gst_url_; }
|
||||||
|
QMutex *mutex_url() const { return &mutex_url_; }
|
||||||
QUrl next_media_url() const { return next_media_url_; }
|
QUrl next_media_url() const { return next_media_url_; }
|
||||||
QUrl next_stream_url() const { return next_stream_url_; }
|
QUrl next_stream_url() const { return next_stream_url_; }
|
||||||
QByteArray next_gst_url() const { return next_gst_url_; }
|
QByteArray next_gst_url() const { return next_gst_url_; }
|
||||||
bool is_valid() const { return valid_; }
|
QMutex *mutex_next_url() const { return &mutex_next_url_; }
|
||||||
|
double ebur128_loudness_normalizing_gain_db() const { return ebur128_loudness_normalizing_gain_db_; }
|
||||||
|
|
||||||
// Please note that this method (unlike GstEngine's.position()) is multiple-section media unaware.
|
|
||||||
qint64 position() const;
|
|
||||||
// Please note that this method (unlike GstEngine's.length()) is multiple-section media unaware.
|
|
||||||
qint64 length() const;
|
|
||||||
// Returns this pipeline's state. May return GST_STATE_NULL if the state check timed out. The timeout value is a reasonable default.
|
// Returns this pipeline's state. May return GST_STATE_NULL if the state check timed out. The timeout value is a reasonable default.
|
||||||
GstState state() const;
|
GstState state() const;
|
||||||
qint64 segment_start() const { return segment_start_; }
|
// Please note that this method (unlike GstEngine's.length()) is multiple-section media unaware.
|
||||||
|
qint64 length() const;
|
||||||
|
// Please note that this method (unlike GstEngine's.position()) is multiple-section media unaware.
|
||||||
|
qint64 position() const;
|
||||||
|
qint64 segment_start() const { return segment_start_.value(); }
|
||||||
|
|
||||||
// Don't allow the user to change the playback state (playing/paused) while the pipeline is buffering.
|
// Don't allow the user to change the playback state (playing/paused) while the pipeline is buffering.
|
||||||
bool is_buffering() const { return buffering_; }
|
bool is_buffering() const { return buffering_.value(); }
|
||||||
|
|
||||||
QByteArray redirect_url() const { return redirect_url_; }
|
|
||||||
|
|
||||||
QString source_device() const { return source_device_; }
|
|
||||||
|
|
||||||
bool exclusive_mode() const { return exclusive_mode_; }
|
bool exclusive_mode() const { return exclusive_mode_; }
|
||||||
|
|
||||||
public Q_SLOTS:
|
QByteArray redirect_url() const { return redirect_url_; }
|
||||||
void SetFaderVolume(const qreal volume);
|
QMutex *mutex_redirect_url() { return &mutex_redirect_url_; }
|
||||||
|
|
||||||
|
QString source_device() const { return source_device_; }
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
|
void SetStateFinished(const GstStateChangeReturn state_change_return);
|
||||||
void Error(const int pipeline_id, const int domain, const int error_code, const QString &message, const QString &debug);
|
void Error(const int pipeline_id, const int domain, const int error_code, const QString &message, const QString &debug);
|
||||||
|
|
||||||
void EndOfStreamReached(const int pipeline_id, const bool has_next_track);
|
void EndOfStreamReached(const int pipeline_id, const bool has_next_track);
|
||||||
void MetadataFound(const int pipeline_id, const EngineMetadata &bundle);
|
void MetadataFound(const int pipeline_id, const EngineMetadata &bundle);
|
||||||
|
void AboutToFinish();
|
||||||
|
void Finished();
|
||||||
void VolumeChanged(const uint volume);
|
void VolumeChanged(const uint volume);
|
||||||
void FaderFinished(const int pipeline_id);
|
void FaderFinished(const int pipeline_id);
|
||||||
|
|
||||||
void BufferingStarted();
|
void BufferingStarted();
|
||||||
void BufferingProgress(const int percent);
|
void BufferingProgress(const int percent);
|
||||||
void BufferingFinished();
|
void BufferingFinished();
|
||||||
|
|
||||||
void AboutToFinish();
|
|
||||||
|
|
||||||
void Finished();
|
|
||||||
|
|
||||||
void SetStateFinished(const GstStateChangeReturn state);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void timerEvent(QTimerEvent*) override;
|
void timerEvent(QTimerEvent*) override;
|
||||||
|
|
||||||
@@ -199,27 +194,28 @@ class GstEnginePipeline : public QObject {
|
|||||||
void UpdateEqualizer();
|
void UpdateEqualizer();
|
||||||
|
|
||||||
void Disconnect();
|
void Disconnect();
|
||||||
|
|
||||||
void ResumeFaderAsync();
|
void ResumeFaderAsync();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void SetStateAsyncFinished(const GstState state, const GstStateChangeReturn state_change);
|
void SetStateAsyncFinished(const GstState state, const GstStateChangeReturn state_change_return);
|
||||||
|
void SetFaderVolume(const qreal volume);
|
||||||
void FaderTimelineFinished();
|
void FaderTimelineFinished();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Using == to compare two pipelines is a bad idea, because new ones often get created in the same address as old ones. This ID will be unique for each pipeline.
|
// Using == to compare two pipelines is a bad idea, because new ones often get created in the same address as old ones. This ID will be unique for each pipeline.
|
||||||
// Threading warning: access to the static ID field isn't protected by a mutex because all pipeline creation is currently done in the main thread.
|
// Threading warning: access to the static ID field isn't protected by a mutex because all pipeline creation is currently done in the main thread.
|
||||||
static int sId;
|
static int sId;
|
||||||
int id_;
|
mutex_protected<int> id_;
|
||||||
|
|
||||||
|
QThreadPool set_state_threadpool_;
|
||||||
|
|
||||||
// General settings for the pipeline
|
// General settings for the pipeline
|
||||||
bool valid_;
|
|
||||||
QString output_;
|
QString output_;
|
||||||
QVariant device_;
|
QVariant device_;
|
||||||
bool exclusive_mode_;
|
bool exclusive_mode_;
|
||||||
bool volume_enabled_;
|
bool volume_enabled_;
|
||||||
bool fading_enabled_;
|
bool fading_enabled_;
|
||||||
bool strict_ssl_enabled_;
|
mutex_protected<bool> strict_ssl_enabled_;
|
||||||
|
|
||||||
// Buffering
|
// Buffering
|
||||||
quint64 buffer_duration_nanosec_;
|
quint64 buffer_duration_nanosec_;
|
||||||
@@ -231,6 +227,7 @@ class GstEnginePipeline : public QObject {
|
|||||||
bool proxy_authentication_;
|
bool proxy_authentication_;
|
||||||
QString proxy_user_;
|
QString proxy_user_;
|
||||||
QString proxy_pass_;
|
QString proxy_pass_;
|
||||||
|
QMutex mutex_proxy_;
|
||||||
|
|
||||||
// Channels
|
// Channels
|
||||||
bool channels_enabled_;
|
bool channels_enabled_;
|
||||||
@@ -270,43 +267,52 @@ class GstEnginePipeline : public QObject {
|
|||||||
QUrl media_url_;
|
QUrl media_url_;
|
||||||
QUrl stream_url_;
|
QUrl stream_url_;
|
||||||
QByteArray gst_url_;
|
QByteArray gst_url_;
|
||||||
|
mutable QMutex mutex_url_;
|
||||||
|
|
||||||
QUrl next_media_url_;
|
QUrl next_media_url_;
|
||||||
QUrl next_stream_url_;
|
QUrl next_stream_url_;
|
||||||
QByteArray next_gst_url_;
|
QByteArray next_gst_url_;
|
||||||
|
mutable QMutex mutex_next_url_;
|
||||||
|
|
||||||
double ebur128_loudness_normalizing_gain_db_;
|
double ebur128_loudness_normalizing_gain_db_;
|
||||||
|
|
||||||
// These get called when there is a new audio buffer available
|
// These get called when there is a new audio buffer available
|
||||||
QList<GstBufferConsumer*> buffer_consumers_;
|
QList<GstBufferConsumer*> buffer_consumers_;
|
||||||
QMutex mutex_buffer_consumers_;
|
QMutex mutex_buffer_consumers_;
|
||||||
qint64 segment_start_;
|
|
||||||
bool segment_start_received_;
|
mutex_protected<qint64> segment_start_;
|
||||||
|
mutex_protected<bool> segment_start_received_;
|
||||||
|
GstSegment last_playbin_segment_{};
|
||||||
|
|
||||||
// If this is > 0 then the pipeline will be forced to stop when playback goes past this position.
|
// If this is > 0 then the pipeline will be forced to stop when playback goes past this position.
|
||||||
qint64 end_offset_nanosec_;
|
mutex_protected<qint64> end_offset_nanosec_;
|
||||||
|
|
||||||
// We store the beginning and end for the preloading song too, so we can just carry on without reloading the file if the sections carry on from each other.
|
// We store the beginning and end for the preloading song too, so we can just carry on without reloading the file if the sections carry on from each other.
|
||||||
qint64 next_beginning_offset_nanosec_;
|
mutex_protected<qint64> next_beginning_offset_nanosec_;
|
||||||
qint64 next_end_offset_nanosec_;
|
mutex_protected<qint64> next_end_offset_nanosec_;
|
||||||
|
|
||||||
// Set temporarily when moving to the next contiguous section in a multipart file.
|
// Set temporarily when moving to the next contiguous section in a multipart file.
|
||||||
bool ignore_next_seek_;
|
mutex_protected<bool> ignore_next_seek_;
|
||||||
|
|
||||||
// Set temporarily when switching out the decode bin, so metadata doesn't get sent while the Player still thinks it's playing the last song
|
// Set temporarily when switching out the decode bin, so metadata doesn't get sent while the Player still thinks it's playing the last song
|
||||||
bool ignore_tags_;
|
mutex_protected<bool> ignore_tags_;
|
||||||
|
|
||||||
// When the gstreamer source requests a redirect we store the URL here and callers can pick it up after the state change to PLAYING fails.
|
// When the gstreamer source requests a redirect we store the URL here and callers can pick it up after the state change to PLAYING fails.
|
||||||
|
mutable QMutex mutex_redirect_url_;
|
||||||
QByteArray redirect_url_;
|
QByteArray redirect_url_;
|
||||||
|
|
||||||
// When we need to specify the device to use as source (for CD device)
|
// When we need to specify the device to use as source (for CD device)
|
||||||
QString source_device_;
|
QString source_device_;
|
||||||
|
QMutex mutex_source_device_;
|
||||||
|
|
||||||
// Seeking while the pipeline is in the READY state doesn't work, so we have to wait until it goes to PAUSED or PLAYING.
|
// Seeking while the pipeline is in the READY state doesn't work, so we have to wait until it goes to PAUSED or PLAYING.
|
||||||
// Also, we have to wait for the playbin to be connected.
|
// Also, we have to wait for the playbin to be connected.
|
||||||
bool pipeline_connected_;
|
mutex_protected<bool> pipeline_connected_;
|
||||||
bool pipeline_active_;
|
mutex_protected<bool> pipeline_active_;
|
||||||
|
mutex_protected<bool> buffering_;
|
||||||
|
|
||||||
GstState pending_state_;
|
mutex_protected<GstState> pending_state_;
|
||||||
qint64 pending_seek_nanosec_;
|
mutex_protected<qint64> pending_seek_nanosec_;
|
||||||
|
|
||||||
// We can only use gst_element_query_position() when the pipeline is in
|
// We can only use gst_element_query_position() when the pipeline is in
|
||||||
// PAUSED nor PLAYING state. Whenever we get a new position (e.g. after a correct call to gst_element_query_position() or after a seek), we store
|
// PAUSED nor PLAYING state. Whenever we get a new position (e.g. after a correct call to gst_element_query_position() or after a seek), we store
|
||||||
@@ -314,15 +320,14 @@ class GstEnginePipeline : public QObject {
|
|||||||
mutable gint64 last_known_position_ns_;
|
mutable gint64 last_known_position_ns_;
|
||||||
|
|
||||||
// Complete the transition to the next song when it starts playing
|
// Complete the transition to the next song when it starts playing
|
||||||
bool next_uri_set_;
|
mutex_protected<bool> next_uri_set_;
|
||||||
bool next_uri_reset_;
|
mutex_protected<bool> next_uri_reset_;
|
||||||
|
|
||||||
bool volume_set_;
|
mutex_protected<bool> volume_set_;
|
||||||
gdouble volume_internal_;
|
mutex_protected<gdouble> volume_internal_;
|
||||||
uint volume_percent_;
|
mutex_protected<uint> volume_percent_;
|
||||||
|
|
||||||
bool buffering_;
|
|
||||||
|
|
||||||
|
mutex_protected<bool> fader_active_;
|
||||||
SharedPtr<QTimeLine> fader_;
|
SharedPtr<QTimeLine> fader_;
|
||||||
QBasicTimer fader_fudge_timer_;
|
QBasicTimer fader_fudge_timer_;
|
||||||
bool use_fudge_timer_;
|
bool use_fudge_timer_;
|
||||||
@@ -351,16 +356,10 @@ class GstEnginePipeline : public QObject {
|
|||||||
glong about_to_finish_cb_id_;
|
glong about_to_finish_cb_id_;
|
||||||
glong notify_volume_cb_id_;
|
glong notify_volume_cb_id_;
|
||||||
|
|
||||||
QThreadPool set_state_threadpool_;
|
|
||||||
|
|
||||||
GstSegment last_playbin_segment_{};
|
|
||||||
|
|
||||||
bool logged_unsupported_analyzer_format_;
|
bool logged_unsupported_analyzer_format_;
|
||||||
|
mutex_protected<bool> about_to_finish_;
|
||||||
bool about_to_finish_;
|
mutex_protected<bool> finish_requested_;
|
||||||
|
mutex_protected<bool> finished_;
|
||||||
bool finish_requested_;
|
|
||||||
bool finished_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using GstEnginePipelinePtr = SharedPtr<GstEnginePipeline>;
|
using GstEnginePipelinePtr = SharedPtr<GstEnginePipeline>;
|
||||||
|
|||||||
Reference in New Issue
Block a user