Improvements to gstreamer backend.
- Use PlayBin instead of URIDecodeBin - Change QUrl to QByteArray in pipeline - Move URL stuff to FixupUrl() in GstEngine
This commit is contained in:
parent
0891bb0128
commit
1817127a90
@ -34,6 +34,7 @@
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
#include <QVariant>
|
||||
#include <QByteArray>
|
||||
|
||||
#include "enginetype.h"
|
||||
#include "engine_fwd.h"
|
||||
|
@ -262,8 +262,7 @@ Engine::State GstEngine::state() const {
|
||||
|
||||
void GstEngine::ConsumeBuffer(GstBuffer *buffer, int pipeline_id) {
|
||||
|
||||
// Schedule this to run in the GUI thread. The buffer gets added to the
|
||||
// queue and unreffed by UpdateScope.
|
||||
// Schedule this to run in the GUI thread. The buffer gets added to the queue and unreffed by UpdateScope.
|
||||
if (!QMetaObject::invokeMethod(this, "AddBufferToScope", Q_ARG(GstBuffer*, buffer), Q_ARG(int, pipeline_id))) {
|
||||
qLog(Warning) << "Failed to invoke AddBufferToScope on GstEngine";
|
||||
}
|
||||
@ -360,26 +359,47 @@ void GstEngine::StartPreloading(const QUrl &url, bool force_stop_at_end, qint64
|
||||
|
||||
EnsureInitialised();
|
||||
|
||||
QUrl gst_url = FixupUrl(url);
|
||||
QByteArray gst_url = FixupUrl(url);
|
||||
|
||||
// No crossfading, so we can just queue the new URL in the existing
|
||||
// pipeline and get gapless playback (hopefully)
|
||||
// No crossfading, so we can just queue the new URL in the existing pipeline and get gapless playback (hopefully)
|
||||
if (current_pipeline_)
|
||||
current_pipeline_->SetNextUrl(gst_url, beginning_nanosec, force_stop_at_end ? end_nanosec : 0);
|
||||
|
||||
}
|
||||
|
||||
QUrl GstEngine::FixupUrl(const QUrl &url) {
|
||||
QByteArray GstEngine::FixupUrl(const QUrl &url) {
|
||||
|
||||
QUrl copy = url;
|
||||
EnsureInitialised();
|
||||
|
||||
QByteArray uri;
|
||||
|
||||
// It's a file:// url with a hostname set. QUrl::fromLocalFile does this when given a \\host\share\file path on Windows. Munge it back into a path that gstreamer will recognise.
|
||||
if (url.scheme() == "file" && !url.host().isEmpty()) {
|
||||
copy.setPath("//" + copy.host() + copy.path());
|
||||
copy.setHost(QString());
|
||||
QString str = "//" + url.host() + url.path();
|
||||
uri = str.toLocal8Bit();
|
||||
}
|
||||
else if (url.scheme() == "cdda") {
|
||||
QString str;
|
||||
if (url.path().isEmpty()) {
|
||||
str = url.toString();
|
||||
str.remove(str.lastIndexOf(QChar('a')), 1);
|
||||
}
|
||||
else {
|
||||
// Currently, Gstreamer can't handle input CD devices inside cdda URL.
|
||||
// So we handle them ourselve: we extract the track number and re-create an URL with only cdda:// + the track number (which can be handled by Gstreamer).
|
||||
// We keep the device in mind, and we will set it later using SourceSetupCallback
|
||||
QStringList path = url.path().split('/');
|
||||
str = QString("cdda://%1a").arg(path.takeLast());
|
||||
QString device = path.join("/");
|
||||
current_pipeline_->SetSourceDevice(device);
|
||||
}
|
||||
uri = str.toLocal8Bit();
|
||||
}
|
||||
else {
|
||||
uri = url.toEncoded();
|
||||
}
|
||||
|
||||
return copy;
|
||||
return uri;
|
||||
|
||||
}
|
||||
|
||||
@ -389,7 +409,7 @@ bool GstEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool forc
|
||||
|
||||
Engine::Base::Load(url, change, force_stop_at_end, beginning_nanosec, end_nanosec);
|
||||
|
||||
QUrl gst_url = FixupUrl(url);
|
||||
QByteArray gst_url = FixupUrl(url);
|
||||
|
||||
bool crossfade = current_pipeline_ && ((crossfade_enabled_ && change & Engine::Manual) || (autocrossfade_enabled_ && change & Engine::Auto) || ((crossfade_enabled_ || autocrossfade_enabled_) && change & Engine::Intro));
|
||||
|
||||
@ -477,7 +497,7 @@ void GstEngine::PlayDone(QFuture<GstStateChangeReturn> future, const quint64 off
|
||||
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
// Failure, but we got a redirection URL - try loading that instead
|
||||
QUrl redirect_url = current_pipeline_->redirect_url();
|
||||
QByteArray redirect_url = current_pipeline_->redirect_url();
|
||||
if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->url()) {
|
||||
qLog(Info) << "Redirecting to" << redirect_url;
|
||||
current_pipeline_ = CreatePipeline(redirect_url, end_nanosec_);
|
||||
@ -511,8 +531,7 @@ void GstEngine::Stop(bool stop_after) {
|
||||
url_ = QUrl(); // To ensure we return Empty from state()
|
||||
beginning_nanosec_ = end_nanosec_ = 0;
|
||||
|
||||
// Check if we started a fade out. If it isn't finished yet and the user
|
||||
// pressed stop, we cancel the fader and just stop the playback.
|
||||
// Check if we started a fade out. If it isn't finished yet and the user pressed stop, we cancel the fader and just stop the playback.
|
||||
if (is_fading_out_to_pause_) {
|
||||
disconnect(current_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0);
|
||||
is_fading_out_to_pause_ = false;
|
||||
@ -552,8 +571,7 @@ void GstEngine::Pause() {
|
||||
|
||||
if (!current_pipeline_ || current_pipeline_->is_buffering()) return;
|
||||
|
||||
// Check if we started a fade out. If it isn't finished yet and the user
|
||||
// pressed play, we inverse the fader and resume the playback.
|
||||
// Check if we started a fade out. If it isn't finished yet and the user pressed play, we inverse the fader and resume the playback.
|
||||
if (is_fading_out_to_pause_) {
|
||||
disconnect(current_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0);
|
||||
current_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Forward, QTimeLine::EaseInOutCurve, false);
|
||||
@ -699,9 +717,8 @@ void GstEngine::HandlePipelineError(int pipeline_id, const QString &message, int
|
||||
// unable to play media stream with this url
|
||||
emit InvalidSongRequested(url_);
|
||||
|
||||
// TODO: the types of errors listed below won't be shown to user - they will
|
||||
// get logged and the current song will be skipped; instead of maintaining
|
||||
// the list we should probably:
|
||||
// TODO: the types of errors listed below won't be shown to user
|
||||
// they will get logged and the current song will be skipped; instead of maintaining the list we should probably:
|
||||
// - don't report any engine's errors to user (always just log and skip)
|
||||
// - come up with a less intrusive error box (not a dialog but a notification
|
||||
// popup of some kind) and then report all errors
|
||||
@ -813,6 +830,16 @@ shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline(const QUrl &url, qint64
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!ret->InitFromUrl(url.toEncoded(), end_nanosec)) ret.reset();
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline(const QByteArray &url, qint64 end_nanosec) {
|
||||
|
||||
shared_ptr<GstEnginePipeline> ret = CreatePipeline();
|
||||
|
||||
if (!ret->InitFromUrl(url, end_nanosec)) ret.reset();
|
||||
|
||||
return ret;
|
||||
|
@ -138,7 +138,6 @@ class GstEngine : public Engine::Base, public BufferConsumer {
|
||||
void BufferingFinished();
|
||||
|
||||
private:
|
||||
|
||||
PluginDetailsList GetPluginList(const QString &classname) const;
|
||||
|
||||
void StartFadeout();
|
||||
@ -149,17 +148,17 @@ class GstEngine : public Engine::Base, public BufferConsumer {
|
||||
|
||||
std::shared_ptr<GstEnginePipeline> CreatePipeline();
|
||||
std::shared_ptr<GstEnginePipeline> CreatePipeline(const QUrl &url, qint64 end_nanosec);
|
||||
std::shared_ptr<GstEnginePipeline> CreatePipeline(const QByteArray &url, qint64 end_nanosec);
|
||||
|
||||
void UpdateScope(int chunk_length);
|
||||
|
||||
static QUrl FixupUrl(const QUrl &url);
|
||||
|
||||
QByteArray FixupUrl(const QUrl &url);
|
||||
|
||||
private:
|
||||
static const qint64 kTimerIntervalNanosec = 1000 *kNsecPerMsec; // 1s
|
||||
static const qint64 kPreloadGapNanosec = 2000 *kNsecPerMsec; // 2s
|
||||
static const qint64 kPreloadGapNanosec = 3000 *kNsecPerMsec; // 3s
|
||||
static const qint64 kSeekDelayNanosec = 100 *kNsecPerMsec; // 100msec
|
||||
|
||||
static const char *kHypnotoadPipeline;
|
||||
static const char *kEnterprisePipeline;
|
||||
|
||||
TaskManager *task_manager_;
|
||||
@ -215,6 +214,4 @@ class GstEngine : public Engine::Base, public BufferConsumer {
|
||||
|
||||
};
|
||||
|
||||
//Q_DECLARE_METATYPE(GstEngine::OutputDetails)
|
||||
|
||||
#endif /* GSTENGINE_H */
|
||||
|
@ -25,8 +25,13 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QPair>
|
||||
#include <QRegExp>
|
||||
#include <QByteArray>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QUuid>
|
||||
#include <QList>
|
||||
#include <QMetaObject>
|
||||
#include <QMutexLocker>
|
||||
|
||||
#include "bufferconsumer.h"
|
||||
#include "gstelementdeleter.h"
|
||||
@ -56,9 +61,6 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
|
||||
sink_(GstEngine::kAutoSink),
|
||||
segment_start_(0),
|
||||
segment_start_received_(false),
|
||||
emit_track_ended_on_stream_start_(false),
|
||||
emit_track_ended_on_time_discontinuity_(false),
|
||||
last_buffer_offset_(0),
|
||||
eq_enabled_(false),
|
||||
eq_preamp_(0),
|
||||
stereo_balance_(0.0f),
|
||||
@ -78,11 +80,10 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
|
||||
pipeline_is_initialised_(false),
|
||||
pipeline_is_connected_(false),
|
||||
pending_seek_nanosec_(-1),
|
||||
last_known_position_ns_(0),
|
||||
next_uri_set_(false),
|
||||
volume_percent_(100),
|
||||
volume_modifier_(1.0),
|
||||
pipeline_(nullptr),
|
||||
uridecodebin_(nullptr),
|
||||
audiobin_(nullptr),
|
||||
queue_(nullptr),
|
||||
audioconvert_(nullptr),
|
||||
@ -130,50 +131,23 @@ void GstEnginePipeline::set_mono_playback(bool enabled) {
|
||||
mono_playback_ = enabled;
|
||||
}
|
||||
|
||||
bool GstEnginePipeline::ReplaceDecodeBin(GstElement *new_bin) {
|
||||
|
||||
if (!new_bin) return false;
|
||||
bool GstEnginePipeline::InitDecodeBin(GstElement *decode_bin) {
|
||||
|
||||
// Destroy the old elements if they are set
|
||||
// Note that the caller to this function MUST schedule the old uridecodebin_
|
||||
// for deletion in the main thread.
|
||||
if (uridecodebin_) {
|
||||
gst_bin_remove(GST_BIN(pipeline_), uridecodebin_);
|
||||
}
|
||||
if (!decode_bin) return false;
|
||||
|
||||
uridecodebin_ = new_bin;
|
||||
segment_start_ = 0;
|
||||
segment_start_received_ = false;
|
||||
pipeline_is_connected_ = false;
|
||||
gst_bin_add(GST_BIN(pipeline_), uridecodebin_);
|
||||
pipeline_ = gst_pipeline_new("pipeline");
|
||||
|
||||
gst_bin_add(GST_BIN(pipeline_), decode_bin);
|
||||
|
||||
if (!InitAudioBin()) return false;
|
||||
gst_bin_add(GST_BIN(pipeline_), audiobin_);
|
||||
|
||||
gst_element_link(decode_bin, audiobin_);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool GstEnginePipeline::ReplaceDecodeBin(const QUrl &url) {
|
||||
|
||||
QByteArray uri;
|
||||
|
||||
if (url.scheme() == "cdda") {
|
||||
QString str = url.toString();
|
||||
str.remove(str.lastIndexOf(QChar('a')), 1);
|
||||
uri = str.toLocal8Bit();;
|
||||
}
|
||||
else {
|
||||
uri = url.toEncoded();
|
||||
}
|
||||
GstElement *new_bin = nullptr;
|
||||
new_bin = engine_->CreateElement("uridecodebin");
|
||||
g_object_set(G_OBJECT(new_bin), "uri", uri.constData(), nullptr);
|
||||
CHECKED_GCONNECT(G_OBJECT(new_bin), "drained", &SourceDrainedCallback, this);
|
||||
CHECKED_GCONNECT(G_OBJECT(new_bin), "pad-added", &NewPadCallback, this);
|
||||
CHECKED_GCONNECT(G_OBJECT(new_bin), "notify::source", &SourceSetupCallback, this);
|
||||
|
||||
return ReplaceDecodeBin(new_bin);
|
||||
|
||||
}
|
||||
|
||||
GstElement *GstEnginePipeline::CreateDecodeBinFromString(const char *pipeline) {
|
||||
|
||||
GError *error = nullptr;
|
||||
@ -196,7 +170,7 @@ GstElement *GstEnginePipeline::CreateDecodeBinFromString(const char *pipeline) {
|
||||
|
||||
}
|
||||
|
||||
bool GstEnginePipeline::Init() {
|
||||
bool GstEnginePipeline::InitAudioBin() {
|
||||
|
||||
// Here we create all the parts of the gstreamer pipeline - from the source to the sink. The parts of the pipeline are split up into bins:
|
||||
// uri decode bin -> audio bin
|
||||
@ -216,7 +190,7 @@ bool GstEnginePipeline::Init() {
|
||||
// Audio bin
|
||||
audiobin_ = gst_bin_new("audiobin");
|
||||
//audiobin_ = gst_bin_new("playbackbin");
|
||||
gst_bin_add(GST_BIN(pipeline_), audiobin_);
|
||||
//gst_bin_add(GST_BIN(pipeline_), audiobin_);
|
||||
|
||||
// Create the sink
|
||||
if (!(audiosink_ = engine_->CreateElement(sink_, audiobin_))) return false;
|
||||
@ -232,7 +206,6 @@ bool GstEnginePipeline::Init() {
|
||||
g_object_set(G_OBJECT(audiosink_), "device", device_.toInt(), nullptr);
|
||||
break;
|
||||
case QVariant::String:
|
||||
//qLog(Debug) << device_.toString().toUtf8().constData();
|
||||
//qLog(Info) << "g_object_set: " << device_.toString().toUtf8().constData();
|
||||
//g_object_set(G_OBJECT(audiosink_), "device", device_.toString().toUtf8().constData(), nullptr);
|
||||
g_object_set(audiosink_, "device", device_.toString().toUtf8().constData(), nullptr);
|
||||
@ -276,10 +249,8 @@ bool GstEnginePipeline::Init() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the replaygain elements if it's enabled. event_probe is the
|
||||
// audioconvert element we attach the probe to, which will change depending
|
||||
// on whether replaygain is enabled. convert_sink is the element after the
|
||||
// first audioconvert, which again will change.
|
||||
// Create the replaygain elements if it's enabled. event_probe is the audioconvert element we attach the probe to, which will change depending on whether replaygain is enabled.
|
||||
// convert_sink is the element after the first audioconvert, which again will change.
|
||||
GstElement *event_probe = audioconvert_;
|
||||
GstElement *convert_sink = tee;
|
||||
|
||||
@ -306,8 +277,7 @@ bool GstEnginePipeline::Init() {
|
||||
gst_object_unref(pad);
|
||||
|
||||
// Add a data probe on the src pad of the audioconvert element for our scope.
|
||||
// We do it here because we want pre-equalized and pre-volume samples
|
||||
// so that our visualization are not be affected by them.
|
||||
// We do it here because we want pre-equalized and pre-volume samples so that our visualization are not be affected by them.
|
||||
pad = gst_element_get_static_pad(event_probe, "src");
|
||||
gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, &EventHandoffCallback, this, NULL);
|
||||
gst_object_unref(pad);
|
||||
@ -317,11 +287,10 @@ bool GstEnginePipeline::Init() {
|
||||
|
||||
// Setting the equalizer bands:
|
||||
//
|
||||
// GStreamer's GstIirEqualizerNBands sets up shelve filters for the first and
|
||||
// last bands as corner cases. That was causing the "inverted slider" bug.
|
||||
// GStreamer's GstIirEqualizerNBands sets up shelve filters for the first and last bands as corner cases.
|
||||
// That was causing the "inverted slider" bug.
|
||||
// As a workaround, we create two dummy bands at both ends of the spectrum.
|
||||
// This causes the actual first and last adjustable bands to be
|
||||
// implemented using band-pass filters.
|
||||
// This causes the actual first and last adjustable bands to be implemented using band-pass filters.
|
||||
|
||||
g_object_set(G_OBJECT(equalizer_), "num-bands", 10 + 2, nullptr);
|
||||
|
||||
@ -351,11 +320,8 @@ bool GstEnginePipeline::Init() {
|
||||
// Set the stereo balance.
|
||||
g_object_set(G_OBJECT(stereo_panorama_), "panorama", stereo_balance_, nullptr);
|
||||
|
||||
// Set the buffer duration. We set this on this queue instead of the
|
||||
// decode bin (in ReplaceDecodeBin()) because setting it on the decode bin
|
||||
// only affects network sources.
|
||||
// Disable the default buffer and byte limits, so we only buffer based on
|
||||
// time.
|
||||
// Set the buffer duration. We set this on this queue instead of the decode bin (in ReplaceDecodeBin()) because setting it on the decode bin only affects network sources.
|
||||
// Disable the default buffer and byte limits, so we only buffer based on time.
|
||||
|
||||
g_object_set(G_OBJECT(queue_), "max-size-buffers", 0, nullptr);
|
||||
g_object_set(G_OBJECT(queue_), "max-size-bytes", 0, nullptr);
|
||||
@ -401,61 +367,35 @@ bool GstEnginePipeline::Init() {
|
||||
gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), BusCallbackSync, this, nullptr);
|
||||
bus_cb_id_ = gst_bus_add_watch(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), BusCallback, this);
|
||||
|
||||
MaybeLinkDecodeToAudio();
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::MaybeLinkDecodeToAudio() {
|
||||
|
||||
if (!uridecodebin_ || !audiobin_) return;
|
||||
|
||||
GstPad *pad = gst_element_get_static_pad(uridecodebin_, "src");
|
||||
if (!pad) return;
|
||||
|
||||
gst_object_unref(pad);
|
||||
gst_element_link(uridecodebin_, audiobin_);
|
||||
|
||||
}
|
||||
|
||||
bool GstEnginePipeline::InitFromString(const QString &pipeline) {
|
||||
|
||||
pipeline_ = gst_pipeline_new("pipeline");
|
||||
|
||||
GstElement *new_bin = CreateDecodeBinFromString(pipeline.toLatin1().constData());
|
||||
if (!new_bin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReplaceDecodeBin(new_bin)) return false;
|
||||
|
||||
if (!Init()) return false;
|
||||
return gst_element_link(new_bin, audiobin_);
|
||||
GstElement *new_bin = CreateDecodeBinFromString(pipeline.toUtf8().constData());
|
||||
return InitDecodeBin(new_bin);
|
||||
|
||||
}
|
||||
|
||||
bool GstEnginePipeline::InitFromUrl(const QUrl &url, qint64 end_nanosec) {
|
||||
bool GstEnginePipeline::InitFromUrl(const QByteArray &url, qint64 end_nanosec) {
|
||||
|
||||
pipeline_ = gst_pipeline_new("pipeline");
|
||||
|
||||
if (url.scheme() == "cdda" && !url.path().isEmpty()) {
|
||||
// Currently, Gstreamer can't handle input CD devices inside cdda URL.
|
||||
// So we handle them ourselve: we extract the track number and re-create an URL with only cdda:// + the track number (which can be handled by Gstreamer).
|
||||
// We keep the device in mind, and we will set it later using SourceSetupCallback
|
||||
QStringList path = url.path().split('/');
|
||||
url_ = QUrl(QString("cdda://%1").arg(path.takeLast()));
|
||||
source_device_ = path.join("/");
|
||||
}
|
||||
else {
|
||||
url_ = url;
|
||||
}
|
||||
end_offset_nanosec_ = end_nanosec;
|
||||
|
||||
// Decode bin
|
||||
if (!ReplaceDecodeBin(url_)) return false;
|
||||
pipeline_ = engine_->CreateElement("playbin");
|
||||
g_object_set(G_OBJECT(pipeline_), "uri", url.constData(), nullptr);
|
||||
CHECKED_GCONNECT(G_OBJECT(pipeline_), "about-to-finish", &AboutToFinishCallback, this);
|
||||
|
||||
return Init();
|
||||
CHECKED_GCONNECT(G_OBJECT(pipeline_), "pad-added", &NewPadCallback, this);
|
||||
CHECKED_GCONNECT(G_OBJECT(pipeline_), "notify::source", &SourceSetupCallback, this);
|
||||
|
||||
if (!InitAudioBin()) return false;
|
||||
|
||||
// Set playbin's sink to be our costum audio-sink.
|
||||
g_object_set(GST_OBJECT(pipeline_), "audio-sink", audiobin_, NULL);
|
||||
pipeline_is_connected_ = true;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@ -497,7 +437,7 @@ gboolean GstEnginePipeline::BusCallback(GstBus*, GstMessage *msg, gpointer self)
|
||||
|
||||
}
|
||||
|
||||
GstBusSyncReply GstEnginePipeline::BusCallbackSync(GstBus*, GstMessage *msg, gpointer self) {
|
||||
GstBusSyncReply GstEnginePipeline::BusCallbackSync(GstBus *, GstMessage *msg, gpointer self) {
|
||||
|
||||
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||
|
||||
@ -533,11 +473,7 @@ GstBusSyncReply GstEnginePipeline::BusCallbackSync(GstBus*, GstMessage *msg, gpo
|
||||
break;
|
||||
|
||||
case GST_MESSAGE_STREAM_START:
|
||||
if (instance->emit_track_ended_on_stream_start_) {
|
||||
qLog(Debug) << "New segment started, EOS will signal on next buffer discontinuity";
|
||||
instance->emit_track_ended_on_stream_start_ = false;
|
||||
instance->emit_track_ended_on_time_discontinuity_ = true;
|
||||
}
|
||||
instance->StreamStartMessageReceived();
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -564,9 +500,25 @@ void GstEnginePipeline::StreamStatusMessageReceived(GstMessage *msg) {
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::TaskEnterCallback(GstTask*, GThread*, gpointer) {
|
||||
void GstEnginePipeline::StreamStartMessageReceived() {
|
||||
|
||||
if (next_uri_set_) {
|
||||
next_uri_set_ = false;
|
||||
|
||||
url_ = next_url_;
|
||||
end_offset_nanosec_ = next_end_offset_nanosec_;
|
||||
next_url_ = QByteArray();
|
||||
next_beginning_offset_nanosec_ = 0;
|
||||
next_end_offset_nanosec_ = 0;
|
||||
|
||||
emit EndOfStreamReached(id(), true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::TaskEnterCallback(GstTask *, GThread *, gpointer) {
|
||||
|
||||
// Bump the priority of the thread only on OS X
|
||||
// Bump the priority of the thread only on OS X
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
sched_param param;
|
||||
@ -585,10 +537,8 @@ void GstEnginePipeline::ElementMessageReceived(GstMessage *msg) {
|
||||
if (gst_structure_has_name(structure, "redirect")) {
|
||||
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.
|
||||
redirect_url_ = QUrl::fromEncoded(uri);
|
||||
// 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.
|
||||
redirect_url_ = uri;
|
||||
}
|
||||
|
||||
}
|
||||
@ -606,6 +556,16 @@ void GstEnginePipeline::ErrorMessageReceived(GstMessage *msg) {
|
||||
g_error_free(error);
|
||||
free(debugs);
|
||||
|
||||
if (pipeline_is_initialised_ && next_uri_set_ && (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.
|
||||
// But there is no message send to the bus when the current track finishes, we have to add an EOS ourself.
|
||||
qLog(Debug) << "Ignoring error when loading next track";
|
||||
GstPad* sinkpad = gst_element_get_static_pad(audiobin_, "sink");
|
||||
gst_pad_send_event(sinkpad, gst_event_new_eos());
|
||||
gst_object_unref(sinkpad);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!redirect_url_.isEmpty() && debugstr.contains("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.
|
||||
// We handle the message, but now we have to ignore the error too.
|
||||
@ -653,7 +613,7 @@ QString GstEnginePipeline::ParseTag(GstTagList *list, const char *tag) const {
|
||||
}
|
||||
|
||||
void GstEnginePipeline::StateChangedMessageReceived(GstMessage *msg) {
|
||||
|
||||
|
||||
if (msg->src != GST_OBJECT(pipeline_)) {
|
||||
// We only care about state changes of the whole pipeline.
|
||||
return;
|
||||
@ -671,22 +631,21 @@ void GstEnginePipeline::StateChangedMessageReceived(GstMessage *msg) {
|
||||
|
||||
if (pipeline_is_initialised_ && new_state != GST_STATE_PAUSED && new_state != GST_STATE_PLAYING) {
|
||||
pipeline_is_initialised_ = false;
|
||||
|
||||
if (next_uri_set_ && new_state == GST_STATE_READY) {
|
||||
// Revert uri and go back to PLAY state again
|
||||
next_uri_set_ = false;
|
||||
g_object_set(G_OBJECT(pipeline_), "uri", url_.constData(), nullptr);
|
||||
SetState(GST_STATE_PLAYING);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::BufferingMessageReceived(GstMessage *msg) {
|
||||
|
||||
// Only handle buffering messages from the queue2 element in audiobin - not
|
||||
// the one that's created automatically by uridecodebin.
|
||||
if (GST_ELEMENT(GST_MESSAGE_SRC(msg)) != queue_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are loading new next track, we don't have to pause the playback.
|
||||
// The buffering is for the next track and not the current one.
|
||||
if (emit_track_ended_on_stream_start_) {
|
||||
qLog(Debug) << "Buffering next track";
|
||||
// Only handle buffering messages from the queue2 element in audiobin - not the one that's created automatically by uridecodebin.
|
||||
if (GST_ELEMENT(GST_MESSAGE_SRC(msg)) != queue_) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -727,9 +686,7 @@ void GstEnginePipeline::NewPadCallback(GstElement*, GstPad *pad, gpointer self)
|
||||
gst_pad_link(pad, audiopad);
|
||||
gst_object_unref(audiopad);
|
||||
|
||||
// Offset the timestamps on all the buffers coming out of the decodebin so
|
||||
// they line up exactly with the end of the last buffer from the old
|
||||
// decodebin.
|
||||
// Offset the timestamps on all the buffers coming out of the decodebin so they line up exactly with the end of the last buffer from the old decodebin.
|
||||
// "Running time" is the time since the last flushing seek.
|
||||
GstClockTime running_time = gst_segment_to_running_time(&instance->last_decodebin_segment_, GST_FORMAT_TIME, instance->last_decodebin_segment_.position);
|
||||
gst_pad_set_offset(pad, running_time);
|
||||
@ -750,9 +707,7 @@ GstPadProbeReturn GstEnginePipeline::DecodebinProbe(GstPad *pad, GstPadProbeInfo
|
||||
const GstPadProbeType info_type = GST_PAD_PROBE_INFO_TYPE(info);
|
||||
|
||||
if (info_type & GST_PAD_PROBE_TYPE_BUFFER) {
|
||||
// The decodebin produced a buffer. Record its end time, so we can offset
|
||||
// the buffers produced by the next decodebin when transitioning to the next
|
||||
// song.
|
||||
// The decodebin produced a buffer. Record its end time, so we can offset the buffers produced by the next decodebin when transitioning to the next song.
|
||||
GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER(info);
|
||||
|
||||
GstClockTime timestamp = GST_BUFFER_TIMESTAMP(buffer);
|
||||
@ -772,13 +727,11 @@ GstPadProbeReturn GstEnginePipeline::DecodebinProbe(GstPad *pad, GstPadProbeInfo
|
||||
GstEventType event_type = GST_EVENT_TYPE(event);
|
||||
|
||||
if (event_type == GST_EVENT_SEGMENT) {
|
||||
// A new segment started, we need to save this to calculate running time
|
||||
// offsets later.
|
||||
// A new segment started, we need to save this to calculate running time offsets later.
|
||||
gst_event_copy_segment(event, &instance->last_decodebin_segment_);
|
||||
}
|
||||
else if (event_type == GST_EVENT_FLUSH_START) {
|
||||
// A flushing seek resets the running time to 0, so remove any offset
|
||||
// we set on this pad before.
|
||||
// A flushing seek resets the running time to 0, so remove any offset we set on this pad before.
|
||||
gst_pad_set_offset(pad, 0);
|
||||
}
|
||||
}
|
||||
@ -803,32 +756,23 @@ GstPadProbeReturn GstEnginePipeline::HandoffCallback(GstPad*, GstPadProbeInfo *i
|
||||
consumer->ConsumeBuffer(buf, instance->id());
|
||||
}
|
||||
|
||||
// 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) {
|
||||
quint64 start_time = GST_BUFFER_TIMESTAMP(buf) - instance->segment_start_;
|
||||
quint64 duration = GST_BUFFER_DURATION(buf);
|
||||
quint64 end_time = start_time + duration;
|
||||
|
||||
if (end_time > instance->end_offset_nanosec_) {
|
||||
if (instance->has_next_valid_url()) {
|
||||
if (instance->next_url_ == instance->url_ && instance->next_beginning_offset_nanosec_ == instance->end_offset_nanosec_) {
|
||||
// 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.
|
||||
if (instance->has_next_valid_url() && instance->next_url_ == instance->url_ && instance->next_beginning_offset_nanosec_ == instance->end_offset_nanosec_) {
|
||||
// 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->end_offset_nanosec_ = instance->next_end_offset_nanosec_;
|
||||
instance->next_url_ = QUrl();
|
||||
instance->next_url_ = QByteArray();
|
||||
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;
|
||||
emit instance->EndOfStreamReached(instance->id(), true);
|
||||
}
|
||||
else {
|
||||
// We have a next song but we can't cheat, so move to it normally.
|
||||
instance->TransitionToNext();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// There's no next song
|
||||
@ -837,16 +781,6 @@ GstPadProbeReturn GstEnginePipeline::HandoffCallback(GstPad*, GstPadProbeInfo *i
|
||||
}
|
||||
}
|
||||
|
||||
if (instance->emit_track_ended_on_time_discontinuity_) {
|
||||
if (GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DISCONT) || GST_BUFFER_OFFSET(buf) < instance->last_buffer_offset_) {
|
||||
qLog(Debug) << "Buffer discontinuity - emitting EOS";
|
||||
instance->emit_track_ended_on_time_discontinuity_ = false;
|
||||
emit instance->EndOfStreamReached(instance->id(), true);
|
||||
}
|
||||
}
|
||||
|
||||
instance->last_buffer_offset_ = GST_BUFFER_OFFSET(buf);
|
||||
|
||||
return GST_PAD_PROBE_OK;
|
||||
|
||||
}
|
||||
@ -861,8 +795,7 @@ GstPadProbeReturn GstEnginePipeline::EventHandoffCallback(GstPad*, GstPadProbeIn
|
||||
switch (GST_EVENT_TYPE(e)) {
|
||||
case GST_EVENT_SEGMENT:
|
||||
if (!instance->segment_start_received_) {
|
||||
// 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;
|
||||
gst_event_parse_segment(e, &segment);
|
||||
instance->segment_start_ = segment->start;
|
||||
@ -878,17 +811,20 @@ GstPadProbeReturn GstEnginePipeline::EventHandoffCallback(GstPad*, GstPadProbeIn
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::SourceDrainedCallback(GstURIDecodeBin *bin, gpointer self) {
|
||||
|
||||
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||
void GstEnginePipeline::AboutToFinishCallback(GstPlayBin *bin, gpointer self) {
|
||||
|
||||
if (instance->has_next_valid_url()) {
|
||||
instance->TransitionToNext();
|
||||
GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||
|
||||
if (instance->has_next_valid_url() && !instance->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.
|
||||
instance->next_uri_set_ = true;
|
||||
g_object_set(G_OBJECT(instance->pipeline_), "uri", instance->next_url_.constData(), nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::SourceSetupCallback(GstURIDecodeBin *bin, GParamSpec *pspec, gpointer self) {
|
||||
void GstEnginePipeline::SourceSetupCallback(GstPlayBin *bin, GParamSpec *pspec, gpointer self) {
|
||||
|
||||
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||
GstElement *element;
|
||||
@ -913,32 +849,12 @@ void GstEnginePipeline::SourceSetupCallback(GstURIDecodeBin *bin, GParamSpec *ps
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::TransitionToNext() {
|
||||
|
||||
GstElement *old_decode_bin = uridecodebin_;
|
||||
|
||||
ignore_tags_ = true;
|
||||
|
||||
ReplaceDecodeBin(next_url_);
|
||||
gst_element_set_state(uridecodebin_, GST_STATE_PLAYING);
|
||||
MaybeLinkDecodeToAudio();
|
||||
|
||||
url_ = next_url_;
|
||||
end_offset_nanosec_ = next_end_offset_nanosec_;
|
||||
next_url_ = QUrl();
|
||||
next_beginning_offset_nanosec_ = 0;
|
||||
next_end_offset_nanosec_ = 0;
|
||||
|
||||
// This function gets called when the source has been drained, even if the song hasn't finished playing yet.
|
||||
// We'll get a new stream when it really does finish, so emit TrackEnded then.
|
||||
emit_track_ended_on_stream_start_ = true;
|
||||
|
||||
// This has to happen *after* the gst_element_set_state on the new bin to fix an occasional race condition deadlock.
|
||||
sElementDeleter->DeleteElementLater(old_decode_bin);
|
||||
|
||||
ignore_tags_ = false;
|
||||
// If the pipeline was buffering we stop that now.
|
||||
if (instance->buffering_) {
|
||||
instance->buffering_ = false;
|
||||
emit instance->BufferingFinished();
|
||||
instance->SetState(GST_STATE_PLAYING);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -986,6 +902,14 @@ bool GstEnginePipeline::Seek(qint64 nanosec) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (next_uri_set_) {
|
||||
qDebug() << "MYTODO: gstenginepipeline.seek: seeking after Transition";
|
||||
|
||||
pending_seek_nanosec_ = nanosec;
|
||||
SetState(GST_STATE_READY);
|
||||
return true;
|
||||
}
|
||||
|
||||
pending_seek_nanosec_ = -1;
|
||||
last_known_position_ns_ = nanosec;
|
||||
return gst_element_seek_simple(pipeline_, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, nanosec);
|
||||
@ -1119,6 +1043,7 @@ void GstEnginePipeline::timerEvent(QTimerEvent *e) {
|
||||
}
|
||||
|
||||
QObject::timerEvent(e);
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::AddBufferConsumer(BufferConsumer *consumer) {
|
||||
@ -1136,7 +1061,7 @@ void GstEnginePipeline::RemoveAllBufferConsumers() {
|
||||
buffer_consumers_.clear();
|
||||
}
|
||||
|
||||
void GstEnginePipeline::SetNextUrl(const QUrl &url, qint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
void GstEnginePipeline::SetNextUrl(const QByteArray &url, qint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
|
||||
next_url_ = url;
|
||||
next_beginning_offset_nanosec_ = beginning_nanosec;
|
||||
|
@ -33,7 +33,8 @@
|
||||
#include <QObject>
|
||||
#include <QThreadPool>
|
||||
#include <QTimeLine>
|
||||
#include <QUrl>
|
||||
#include <QByteArray>
|
||||
#include <QVariant>
|
||||
|
||||
#include "engine_fwd.h"
|
||||
|
||||
@ -42,7 +43,7 @@ class GstEngine;
|
||||
class BufferConsumer;
|
||||
|
||||
struct GstQueue;
|
||||
struct GstURIDecodeBin;
|
||||
struct GstPlayBin;
|
||||
|
||||
class GstEnginePipeline : public QObject {
|
||||
Q_OBJECT
|
||||
@ -62,7 +63,7 @@ class GstEnginePipeline : public QObject {
|
||||
void set_mono_playback(bool enabled);
|
||||
|
||||
// Creates the pipeline, returns false on error
|
||||
bool InitFromUrl(const QUrl &url, qint64 end_nanosec);
|
||||
bool InitFromUrl(const QByteArray &url, qint64 end_nanosec);
|
||||
bool InitFromString(const QString &pipeline);
|
||||
|
||||
// BufferConsumers get fed audio data. Thread-safe.
|
||||
@ -79,30 +80,27 @@ class GstEnginePipeline : public QObject {
|
||||
void SetStereoBalance(float value);
|
||||
void StartFader(qint64 duration_nanosec, QTimeLine::Direction direction = QTimeLine::Forward, QTimeLine::CurveShape shape = QTimeLine::LinearCurve, bool use_fudge_timer = true);
|
||||
|
||||
// If this is set then it will be loaded automatically when playback finishes
|
||||
// for gapless playback
|
||||
void SetNextUrl(const QUrl &url, qint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool has_next_valid_url() const { return next_url_.isValid(); }
|
||||
// If this is set then it will be loaded automatically when playback finishes for gapless playback
|
||||
void SetNextUrl(const QByteArray &url, qint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool has_next_valid_url() const { return !next_url_.isNull() && !next_url_.isEmpty(); }
|
||||
|
||||
void SetSourceDevice(QString device) { source_device_ = device; }
|
||||
|
||||
// Get information about the music playback
|
||||
QUrl url() const { return url_; }
|
||||
QByteArray url() const { return url_; }
|
||||
bool is_valid() const { return valid_; }
|
||||
// Please note that this method (unlike GstEngine's.position()) is
|
||||
// multiple-section media unaware.
|
||||
// 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.
|
||||
// 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;
|
||||
qint64 segment_start() const { return segment_start_; }
|
||||
|
||||
// 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_; }
|
||||
|
||||
QUrl redirect_url() const { return redirect_url_; }
|
||||
QByteArray redirect_url() const { return redirect_url_; }
|
||||
|
||||
QString source_device() const { return source_device_; }
|
||||
|
||||
@ -125,16 +123,15 @@ signals:
|
||||
void timerEvent(QTimerEvent*);
|
||||
|
||||
private:
|
||||
// Static callbacks. The GstEnginePipeline instance is passed in the last
|
||||
// argument.
|
||||
// Static callbacks. The GstEnginePipeline instance is passed in the last argument.
|
||||
static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage*, gpointer);
|
||||
static gboolean BusCallback(GstBus*, GstMessage*, gpointer);
|
||||
static void NewPadCallback(GstElement*, GstPad*, gpointer);
|
||||
static GstPadProbeReturn HandoffCallback(GstPad*, GstPadProbeInfo*, gpointer);
|
||||
static GstPadProbeReturn EventHandoffCallback(GstPad*, GstPadProbeInfo*, gpointer);
|
||||
static void AboutToFinishCallback(GstPlayBin*, gpointer);
|
||||
static GstPadProbeReturn DecodebinProbe(GstPad*, GstPadProbeInfo*, gpointer);
|
||||
static void SourceDrainedCallback(GstURIDecodeBin*, gpointer);
|
||||
static void SourceSetupCallback(GstURIDecodeBin*, GParamSpec *pspec, gpointer);
|
||||
static void SourceSetupCallback(GstPlayBin*, GParamSpec* pspec, gpointer);
|
||||
static void TaskEnterCallback(GstTask*, GThread*, gpointer);
|
||||
|
||||
void TagMessageReceived(GstMessage*);
|
||||
@ -143,23 +140,17 @@ signals:
|
||||
void StateChangedMessageReceived(GstMessage*);
|
||||
void BufferingMessageReceived(GstMessage*);
|
||||
void StreamStatusMessageReceived(GstMessage*);
|
||||
void StreamStartMessageReceived();
|
||||
|
||||
QString ParseTag(GstTagList *list, const char *tag) const;
|
||||
|
||||
bool Init();
|
||||
bool InitDecodeBin(GstElement* new_bin);
|
||||
bool InitAudioBin();
|
||||
GstElement *CreateDecodeBinFromString(const char *pipeline);
|
||||
|
||||
void UpdateVolume();
|
||||
void UpdateEqualizer();
|
||||
void UpdateStereoBalance();
|
||||
bool ReplaceDecodeBin(GstElement *new_bin);
|
||||
bool ReplaceDecodeBin(const QUrl &url);
|
||||
|
||||
void TransitionToNext();
|
||||
|
||||
// If the decodebin is special (ie. not really a uridecodebin) then it'll have
|
||||
// a src pad immediately and we can link it after everything's created.
|
||||
void MaybeLinkDecodeToAudio();
|
||||
|
||||
private slots:
|
||||
void FaderTimelineFinished();
|
||||
@ -174,11 +165,8 @@ signals:
|
||||
|
||||
GstEngine *engine_;
|
||||
|
||||
// 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.
|
||||
// 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.
|
||||
static int sId;
|
||||
int id_;
|
||||
|
||||
@ -192,9 +180,6 @@ signals:
|
||||
QMutex buffer_consumers_mutex_;
|
||||
qint64 segment_start_;
|
||||
bool segment_start_received_;
|
||||
bool emit_track_ended_on_stream_start_;
|
||||
bool emit_track_ended_on_time_discontinuity_;
|
||||
qint64 last_buffer_offset_;
|
||||
|
||||
// Equalizer
|
||||
bool eq_enabled_;
|
||||
@ -220,8 +205,8 @@ signals:
|
||||
bool mono_playback_;
|
||||
|
||||
// The URL that is currently playing, and the URL that is to be preloaded when the current track is close to finishing.
|
||||
QUrl url_;
|
||||
QUrl next_url_;
|
||||
QByteArray url_;
|
||||
QByteArray next_url_;
|
||||
|
||||
// If this is > 0 then the pipeline will be forced to stop when playback goes past this position.
|
||||
qint64 end_offset_nanosec_;
|
||||
@ -237,7 +222,7 @@ signals:
|
||||
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.
|
||||
QUrl redirect_url_;
|
||||
QByteArray redirect_url_;
|
||||
|
||||
// When we need to specify the device to use as source (for CD device)
|
||||
QString source_device_;
|
||||
@ -248,12 +233,13 @@ signals:
|
||||
qint64 pending_seek_nanosec_;
|
||||
|
||||
// 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
|
||||
// it here so that we can use it when using gst_element_query_position() is
|
||||
// not possible.
|
||||
// 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
|
||||
// it here so that we can use it when using gst_element_query_position() is not possible.
|
||||
mutable gint64 last_known_position_ns_;
|
||||
|
||||
// Complete the transition to the next song when it starts playing
|
||||
bool next_uri_set_;
|
||||
|
||||
int volume_percent_;
|
||||
qreal volume_modifier_;
|
||||
|
||||
@ -263,9 +249,7 @@ signals:
|
||||
|
||||
GstElement *pipeline_;
|
||||
|
||||
// Bins
|
||||
// uridecodebin ! audiobin
|
||||
GstElement *uridecodebin_;
|
||||
// The audiobin is either linked with a decodebin or set as sink of the playbin pipeline.
|
||||
GstElement *audiobin_;
|
||||
|
||||
// Elements in the audiobin. See comments in Init()'s definition.
|
||||
|
Loading…
x
Reference in New Issue
Block a user