Add stream discoverer to gstreamer pipeline and continuous updating of bitrate
This commit is contained in:
@@ -59,11 +59,11 @@ Engine::Base::Base()
|
||||
|
||||
Engine::Base::~Base() {}
|
||||
|
||||
bool Engine::Base::Load(const QUrl &media_url, const QUrl &original_url, TrackChangeFlags, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
bool Engine::Base::Load(const QUrl &stream_url, const QUrl &original_url, TrackChangeFlags, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
|
||||
Q_UNUSED(force_stop_at_end);
|
||||
|
||||
media_url_ = media_url;
|
||||
stream_url_ = stream_url;
|
||||
original_url_ = original_url;
|
||||
beginning_nanosec_ = beginning_nanosec;
|
||||
end_nanosec_ = end_nanosec;
|
||||
@@ -73,9 +73,9 @@ bool Engine::Base::Load(const QUrl &media_url, const QUrl &original_url, TrackCh
|
||||
|
||||
}
|
||||
|
||||
bool Engine::Base::Play(const QUrl &media_url, const QUrl &original_url, TrackChangeFlags flags, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
bool Engine::Base::Play(const QUrl &stream_url, const QUrl &original_url, TrackChangeFlags flags, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
|
||||
if (!Load(media_url, original_url, flags, force_stop_at_end, beginning_nanosec, end_nanosec))
|
||||
if (!Load(stream_url, original_url, flags, force_stop_at_end, beginning_nanosec, end_nanosec))
|
||||
return false;
|
||||
|
||||
return Play(0);
|
||||
|
||||
@@ -69,8 +69,8 @@ public:
|
||||
|
||||
virtual bool Init() = 0;
|
||||
virtual State state() const = 0;
|
||||
virtual void StartPreloading(const QUrl &media_url, const QUrl &original_url, bool, qint64, qint64) {}
|
||||
virtual bool Load(const QUrl &media_url, const QUrl &original_url, TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
virtual void StartPreloading(const QUrl &stream_url, const QUrl &original_url, bool, qint64, qint64) {}
|
||||
virtual bool Load(const QUrl &stream_url, const QUrl &original_url, TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
virtual bool Play(quint64 offset_nanosec) = 0;
|
||||
virtual void Stop(bool stop_after = false) = 0;
|
||||
virtual void Pause() = 0;
|
||||
@@ -98,7 +98,7 @@ public:
|
||||
|
||||
// Plays a media stream represented with the URL 'u' from the given 'beginning' to the given 'end' (usually from 0 to a song's length).
|
||||
// Both markers should be passed in nanoseconds. 'end' can be negative, indicating that the real length of 'u' stream is unknown.
|
||||
bool Play(const QUrl &media_url, const QUrl &original_url, TrackChangeFlags c, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool Play(const QUrl &stream_url, const QUrl &original_url, TrackChangeFlags c, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
void SetVolume(uint value);
|
||||
static uint MakeVolumeLogarithmic(uint volume);
|
||||
|
||||
@@ -168,7 +168,7 @@ signals:
|
||||
uint volume_;
|
||||
quint64 beginning_nanosec_;
|
||||
qint64 end_nanosec_;
|
||||
QUrl media_url_;
|
||||
QUrl stream_url_;
|
||||
QUrl original_url_;
|
||||
Scope scope_;
|
||||
bool buffering_;
|
||||
@@ -206,8 +206,10 @@ private:
|
||||
};
|
||||
|
||||
struct SimpleMetaBundle {
|
||||
SimpleMetaBundle() : length(-1), year(-1), track(-1), samplerate(-1), bitdepth(-1) {}
|
||||
SimpleMetaBundle() : minor(true), length(-1), year(-1), track(-1), samplerate(-1), bitdepth(-1) {}
|
||||
QUrl url;
|
||||
QUrl stream_url;
|
||||
bool minor;
|
||||
QString title;
|
||||
QString artist;
|
||||
QString album;
|
||||
|
||||
@@ -114,7 +114,7 @@ bool GstEngine::Init() {
|
||||
|
||||
Engine::State GstEngine::state() const {
|
||||
|
||||
if (!current_pipeline_) return media_url_.isEmpty() ? Engine::Empty : Engine::Idle;
|
||||
if (!current_pipeline_) return stream_url_.isEmpty() ? Engine::Empty : Engine::Idle;
|
||||
|
||||
switch (current_pipeline_->state()) {
|
||||
case GST_STATE_NULL:
|
||||
@@ -131,11 +131,11 @@ Engine::State GstEngine::state() const {
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::StartPreloading(const QUrl &media_url, const QUrl &original_url, bool force_stop_at_end, qint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
void GstEngine::StartPreloading(const QUrl &stream_url, const QUrl &original_url, bool force_stop_at_end, qint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
|
||||
EnsureInitialised();
|
||||
|
||||
QByteArray gst_url = FixupUrl(media_url);
|
||||
QByteArray gst_url = FixupUrl(stream_url);
|
||||
|
||||
// No crossfading, so we can just queue the new URL in the existing pipeline and get gapless playback (hopefully)
|
||||
if (current_pipeline_)
|
||||
@@ -143,20 +143,20 @@ void GstEngine::StartPreloading(const QUrl &media_url, const QUrl &original_url,
|
||||
|
||||
}
|
||||
|
||||
bool GstEngine::Load(const QUrl &media_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
bool GstEngine::Load(const QUrl &stream_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
|
||||
EnsureInitialised();
|
||||
|
||||
Engine::Base::Load(media_url, original_url, change, force_stop_at_end, beginning_nanosec, end_nanosec);
|
||||
Engine::Base::Load(stream_url, original_url, change, force_stop_at_end, beginning_nanosec, end_nanosec);
|
||||
|
||||
QByteArray gst_url = FixupUrl(media_url);
|
||||
QByteArray gst_url = FixupUrl(stream_url);
|
||||
|
||||
bool crossfade = current_pipeline_ && ((crossfade_enabled_ && change & Engine::Manual) || (autocrossfade_enabled_ && change & Engine::Auto) || ((crossfade_enabled_ || autocrossfade_enabled_) && change & Engine::Intro));
|
||||
|
||||
if (change & Engine::Auto && change & Engine::SameAlbum && !crossfade_same_album_)
|
||||
crossfade = false;
|
||||
|
||||
if (!crossfade && current_pipeline_ && current_pipeline_->media_url() == gst_url && change & Engine::Auto) {
|
||||
if (!crossfade && current_pipeline_ && current_pipeline_->stream_url() == gst_url && change & Engine::Auto) {
|
||||
// We're not crossfading, and the pipeline is already playing the URI we want, so just do nothing.
|
||||
return true;
|
||||
}
|
||||
@@ -202,7 +202,7 @@ void GstEngine::Stop(bool stop_after) {
|
||||
|
||||
StopTimers();
|
||||
|
||||
media_url_ = QUrl(); // To ensure we return Empty from state()
|
||||
stream_url_ = QUrl(); // To ensure we return Empty from state()
|
||||
original_url_ = QUrl();
|
||||
beginning_nanosec_ = end_nanosec_ = 0;
|
||||
|
||||
@@ -503,7 +503,7 @@ void GstEngine::HandlePipelineError(int pipeline_id, const QString &message, int
|
||||
emit StateChanged(Engine::Error);
|
||||
|
||||
if (domain == GST_RESOURCE_ERROR && (error_code == GST_RESOURCE_ERROR_NOT_FOUND || error_code == GST_RESOURCE_ERROR_NOT_AUTHORIZED)) {
|
||||
emit InvalidSongRequested(media_url_);
|
||||
emit InvalidSongRequested(stream_url_);
|
||||
}
|
||||
else {
|
||||
emit FatalError();
|
||||
@@ -515,10 +515,9 @@ void GstEngine::HandlePipelineError(int pipeline_id, const QString &message, int
|
||||
|
||||
void GstEngine::NewMetaData(int pipeline_id, const Engine::SimpleMetaBundle &bundle) {
|
||||
|
||||
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
|
||||
return;
|
||||
|
||||
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id) return;
|
||||
emit MetaData(bundle);
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::AddBufferToScope(GstBuffer *buf, int pipeline_id) {
|
||||
@@ -581,7 +580,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
|
||||
QByteArray redirect_url = current_pipeline_->redirect_url();
|
||||
if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->media_url()) {
|
||||
if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->stream_url()) {
|
||||
qLog(Info) << "Redirecting to" << redirect_url;
|
||||
current_pipeline_ = CreatePipeline(redirect_url, current_pipeline_->original_url(), end_nanosec_);
|
||||
Play(offset_nanosec);
|
||||
@@ -604,7 +603,7 @@ void GstEngine::PlayDone(QFuture<GstStateChangeReturn> future, const quint64 off
|
||||
|
||||
emit StateChanged(Engine::Playing);
|
||||
// We've successfully started playing a media stream with this url
|
||||
emit ValidSongRequested(media_url_);
|
||||
emit ValidSongRequested(stream_url_);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -69,8 +69,8 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
|
||||
|
||||
bool Init();
|
||||
Engine::State state() const;
|
||||
void StartPreloading(const QUrl &media_url, const QUrl &original_url, bool force_stop_at_end, qint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool Load(const QUrl &media_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
void StartPreloading(const QUrl &stream_url, const QUrl &original_url, bool force_stop_at_end, qint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool Load(const QUrl &stream_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool Play(quint64 offset_nanosec);
|
||||
void Stop(bool stop_after = false);
|
||||
void Pause();
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
@@ -51,6 +52,7 @@
|
||||
|
||||
const int GstEnginePipeline::kGstStateTimeoutNanosecs = 10000000;
|
||||
const int GstEnginePipeline::kFaderFudgeMsec = 2000;
|
||||
const int GstEnginePipeline::kDiscoveryTimeoutS = 10;
|
||||
|
||||
const int GstEnginePipeline::kEqBandCount = 10;
|
||||
const int GstEnginePipeline::kEqBandFrequencies[] = { 60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000 };
|
||||
@@ -102,7 +104,8 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
|
||||
equalizer_preamp_(nullptr),
|
||||
equalizer_(nullptr),
|
||||
rgvolume_(nullptr),
|
||||
rglimiter_(nullptr)
|
||||
rglimiter_(nullptr),
|
||||
discoverer_(nullptr)
|
||||
{
|
||||
|
||||
if (!sElementDeleter) {
|
||||
@@ -122,6 +125,11 @@ GstEnginePipeline::~GstEnginePipeline() {
|
||||
gst_object_unref(GST_OBJECT(pipeline_));
|
||||
}
|
||||
|
||||
if (discoverer_) {
|
||||
gst_discoverer_stop(discoverer_);
|
||||
g_object_unref(discoverer_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::set_output_device(const QString &output, const QVariant &device) {
|
||||
@@ -403,6 +411,11 @@ bool GstEnginePipeline::InitAudioBin() {
|
||||
bus_cb_id_ = gst_bus_add_watch(bus, BusCallback, this);
|
||||
gst_object_unref(bus);
|
||||
|
||||
// Add request to discover the stream
|
||||
if (!gst_discoverer_discover_uri_async(discoverer_, stream_url_.toStdString().c_str())) {
|
||||
qLog(Error) << "Failed to start stream discovery for" << stream_url_;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
@@ -415,22 +428,29 @@ bool GstEnginePipeline::InitFromString(const QString &pipeline) {
|
||||
|
||||
}
|
||||
|
||||
bool GstEnginePipeline::InitFromUrl(const QByteArray &media_url, const QUrl original_url, qint64 end_nanosec) {
|
||||
bool GstEnginePipeline::InitFromUrl(const QByteArray &stream_url, const QUrl original_url, qint64 end_nanosec) {
|
||||
|
||||
media_url_ = media_url;
|
||||
stream_url_ = stream_url;
|
||||
original_url_ = original_url;
|
||||
end_offset_nanosec_ = end_nanosec;
|
||||
|
||||
pipeline_ = engine_->CreateElement("playbin");
|
||||
if (!pipeline_) return false;
|
||||
|
||||
g_object_set(G_OBJECT(pipeline_), "uri", media_url.constData(), nullptr);
|
||||
g_object_set(G_OBJECT(pipeline_), "uri", stream_url.constData(), nullptr);
|
||||
|
||||
CHECKED_GCONNECT(G_OBJECT(pipeline_), "about-to-finish", &AboutToFinishCallback, this);
|
||||
|
||||
CHECKED_GCONNECT(G_OBJECT(pipeline_), "pad-added", &NewPadCallback, this);
|
||||
CHECKED_GCONNECT(G_OBJECT(pipeline_), "notify::source", &SourceSetupCallback, this);
|
||||
|
||||
// Setting up a discoverer
|
||||
discoverer_ = gst_discoverer_new(kDiscoveryTimeoutS * GST_SECOND, NULL);
|
||||
if (!discoverer_) return false;
|
||||
CHECKED_GCONNECT(G_OBJECT(discoverer_), "discovered", &StreamDiscovered, this);
|
||||
CHECKED_GCONNECT(G_OBJECT(discoverer_), "finished", &StreamDiscoveryFinished, this);
|
||||
gst_discoverer_start(discoverer_);
|
||||
|
||||
if (!InitAudioBin()) return false;
|
||||
|
||||
// Set playbin's sink to be our costum audio-sink.
|
||||
@@ -536,10 +556,10 @@ void GstEnginePipeline::StreamStartMessageReceived() {
|
||||
if (next_uri_set_) {
|
||||
next_uri_set_ = false;
|
||||
|
||||
media_url_ = next_media_url_;
|
||||
stream_url_ = next_stream_url_;
|
||||
original_url_ = next_original_url_;
|
||||
end_offset_nanosec_ = next_end_offset_nanosec_;
|
||||
next_media_url_ = QByteArray();
|
||||
next_stream_url_ = QByteArray();
|
||||
next_original_url_ = QUrl();
|
||||
next_beginning_offset_nanosec_ = 0;
|
||||
next_end_offset_nanosec_ = 0;
|
||||
@@ -613,6 +633,8 @@ void GstEnginePipeline::ErrorMessageReceived(GstMessage *msg) {
|
||||
|
||||
void GstEnginePipeline::TagMessageReceived(GstMessage *msg) {
|
||||
|
||||
if (ignore_tags_) return;
|
||||
|
||||
GstTagList *taglist = nullptr;
|
||||
gst_message_parse_tag(msg, &taglist);
|
||||
|
||||
@@ -633,10 +655,7 @@ void GstEnginePipeline::TagMessageReceived(GstMessage *msg) {
|
||||
|
||||
gst_tag_list_free(taglist);
|
||||
|
||||
if (ignore_tags_) return;
|
||||
|
||||
if (!bundle.title.isEmpty() || !bundle.artist.isEmpty() || !bundle.comment.isEmpty() || !bundle.album.isEmpty())
|
||||
emit MetadataFound(id(), bundle);
|
||||
emit MetadataFound(id(), bundle);
|
||||
|
||||
}
|
||||
|
||||
@@ -688,7 +707,7 @@ void GstEnginePipeline::StateChangedMessageReceived(GstMessage *msg) {
|
||||
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", media_url_.constData(), nullptr);
|
||||
g_object_set(G_OBJECT(pipeline_), "uri", stream_url_.constData(), nullptr);
|
||||
SetState(GST_STATE_PLAYING);
|
||||
}
|
||||
}
|
||||
@@ -816,10 +835,10 @@ GstPadProbeReturn GstEnginePipeline::HandoffCallback(GstPad*, GstPadProbeInfo *i
|
||||
quint64 end_time = start_time + duration;
|
||||
|
||||
if (end_time > instance->end_offset_nanosec_) {
|
||||
if (instance->has_next_valid_url() && instance->next_media_url_ == instance->media_url_ && instance->next_beginning_offset_nanosec_ == instance->end_offset_nanosec_) {
|
||||
if (instance->has_next_valid_url() && instance->next_stream_url_ == instance->stream_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_media_url_ = QByteArray();
|
||||
instance->next_stream_url_ = QByteArray();
|
||||
instance->next_original_url_ = QUrl();
|
||||
instance->next_beginning_offset_nanosec_ = 0;
|
||||
instance->next_end_offset_nanosec_ = 0;
|
||||
@@ -867,13 +886,13 @@ GstPadProbeReturn GstEnginePipeline::EventHandoffCallback(GstPad*, GstPadProbeIn
|
||||
|
||||
void GstEnginePipeline::AboutToFinishCallback(GstPlayBin *bin, gpointer self) {
|
||||
|
||||
GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||
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_media_url_.constData(), nullptr);
|
||||
g_object_set(G_OBJECT(instance->pipeline_), "uri", instance->next_stream_url_.constData(), nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1123,11 +1142,79 @@ void GstEnginePipeline::RemoveAllBufferConsumers() {
|
||||
buffer_consumers_.clear();
|
||||
}
|
||||
|
||||
void GstEnginePipeline::SetNextUrl(const QByteArray &media_url, const QUrl &original_url, qint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
void GstEnginePipeline::SetNextUrl(const QByteArray &stream_url, const QUrl &original_url, qint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
|
||||
next_media_url_ = media_url;
|
||||
next_stream_url_ = stream_url;
|
||||
next_original_url_ = original_url;
|
||||
next_beginning_offset_nanosec_ = beginning_nanosec;
|
||||
next_end_offset_nanosec_ = end_nanosec;
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::StreamDiscovered(GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, gpointer self) {
|
||||
|
||||
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||
if (!instance) return;
|
||||
|
||||
QString discovered_url(gst_discoverer_info_get_uri(info));
|
||||
|
||||
GstDiscovererResult result = gst_discoverer_info_get_result(info);
|
||||
if (result != GST_DISCOVERER_OK) {
|
||||
QString error_message = GSTdiscovererErrorMessage(result);
|
||||
qLog(Error) << QString("Stream discovery for %1 failed: %2").arg(discovered_url).arg(error_message);
|
||||
return;
|
||||
}
|
||||
|
||||
GList *audio_streams = gst_discoverer_info_get_audio_streams(info);
|
||||
if (audio_streams) {
|
||||
|
||||
GstDiscovererStreamInfo *stream_info = (GstDiscovererStreamInfo*) g_list_first(audio_streams)->data;
|
||||
|
||||
Engine::SimpleMetaBundle bundle;
|
||||
bundle.minor = true;
|
||||
bundle.url = instance->original_url();
|
||||
bundle.stream_url = QUrl(discovered_url);
|
||||
bundle.samplerate = gst_discoverer_audio_info_get_sample_rate(GST_DISCOVERER_AUDIO_INFO(stream_info));
|
||||
bundle.bitdepth = gst_discoverer_audio_info_get_depth(GST_DISCOVERER_AUDIO_INFO(stream_info));
|
||||
bundle.bitrate = gst_discoverer_audio_info_get_bitrate(GST_DISCOVERER_AUDIO_INFO(stream_info));
|
||||
|
||||
GstCaps *stream_caps = gst_discoverer_stream_info_get_caps(stream_info);
|
||||
gchar *decoder_description = gst_pb_utils_get_codec_description(stream_caps);
|
||||
QString filetype_description = (decoder_description ? QString(decoder_description) : QString("Unknown"));
|
||||
|
||||
gst_caps_unref(stream_caps);
|
||||
g_free(decoder_description);
|
||||
gst_discoverer_stream_info_list_free(audio_streams);
|
||||
|
||||
qLog(Info) << QString("Got stream info for %1: %2").arg(discovered_url).arg(filetype_description);
|
||||
|
||||
emit instance->MetadataFound(instance->id(), bundle);
|
||||
|
||||
}
|
||||
else {
|
||||
qLog(Error) << QString("Could not detect an audio stream in %1").arg(discovered_url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::StreamDiscoveryFinished(GstDiscoverer *discoverer, gpointer self) {
|
||||
//GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||
}
|
||||
|
||||
QString GstEnginePipeline::GSTdiscovererErrorMessage(GstDiscovererResult result) {
|
||||
|
||||
switch (result) {
|
||||
case (GST_DISCOVERER_URI_INVALID):
|
||||
return tr("Invalid URL");
|
||||
case (GST_DISCOVERER_TIMEOUT):
|
||||
return tr("Connection timed out");
|
||||
case (GST_DISCOVERER_BUSY):
|
||||
return tr("The discoverer is busy");
|
||||
case (GST_DISCOVERER_MISSING_PLUGINS):
|
||||
return tr("Missing plugins");
|
||||
case (GST_DISCOVERER_ERROR):
|
||||
default:
|
||||
return tr("Could not get details");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <glib-object.h>
|
||||
#include <glib/gtypes.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
@@ -74,7 +75,7 @@ class GstEnginePipeline : public QObject {
|
||||
void set_buffer_min_fill(int percent);
|
||||
|
||||
// Creates the pipeline, returns false on error
|
||||
bool InitFromUrl(const QByteArray &media_url, const QUrl original_url, qint64 end_nanosec);
|
||||
bool InitFromUrl(const QByteArray &stream_url, const QUrl original_url, qint64 end_nanosec);
|
||||
bool InitFromString(const QString &pipeline);
|
||||
|
||||
// GstBufferConsumers get fed audio data. Thread-safe.
|
||||
@@ -92,13 +93,13 @@ class GstEnginePipeline : public QObject {
|
||||
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 QByteArray &media_url, const QUrl &original_url, qint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool has_next_valid_url() const { return !next_media_url_.isNull() && !next_media_url_.isEmpty(); }
|
||||
void SetNextUrl(const QByteArray &stream_url, const QUrl &original_url, qint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool has_next_valid_url() const { return !next_stream_url_.isNull() && !next_stream_url_.isEmpty(); }
|
||||
|
||||
void SetSourceDevice(QString device) { source_device_ = device; }
|
||||
|
||||
// Get information about the music playback
|
||||
QByteArray media_url() const { return media_url_; }
|
||||
QByteArray stream_url() const { return stream_url_; }
|
||||
QUrl original_url() const { return original_url_; }
|
||||
bool is_valid() const { return valid_; }
|
||||
// Please note that this method (unlike GstEngine's.position()) is multiple-section media unaware.
|
||||
@@ -165,12 +166,17 @@ signals:
|
||||
void UpdateEqualizer();
|
||||
void UpdateStereoBalance();
|
||||
|
||||
static void StreamDiscovered(GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, gpointer instance);
|
||||
static void StreamDiscoveryFinished(GstDiscoverer *discoverer, gpointer instance);
|
||||
static QString GSTdiscovererErrorMessage(GstDiscovererResult result);
|
||||
|
||||
private slots:
|
||||
void FaderTimelineFinished();
|
||||
|
||||
private:
|
||||
static const int kGstStateTimeoutNanosecs;
|
||||
static const int kFaderFudgeMsec;
|
||||
static const int kDiscoveryTimeoutS;
|
||||
static const int kEqBandCount;
|
||||
static const int kEqBandFrequencies[];
|
||||
|
||||
@@ -217,9 +223,9 @@ signals:
|
||||
bool segment_start_received_;
|
||||
|
||||
// The URL that is currently playing, and the URL that is to be preloaded when the current track is close to finishing.
|
||||
QByteArray media_url_;
|
||||
QByteArray stream_url_;
|
||||
QUrl original_url_;
|
||||
QByteArray next_media_url_;
|
||||
QByteArray next_stream_url_;
|
||||
QUrl next_original_url_;
|
||||
|
||||
// If this is > 0 then the pipeline will be forced to stop when playback goes past this position.
|
||||
@@ -278,6 +284,7 @@ signals:
|
||||
GstElement *equalizer_;
|
||||
GstElement *rgvolume_;
|
||||
GstElement *rglimiter_;
|
||||
GstDiscoverer *discoverer_;
|
||||
|
||||
uint bus_cb_id_;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
@@ -51,6 +52,7 @@ void GstStartup::InitialiseGStreamer() {
|
||||
SetEnvironment();
|
||||
|
||||
gst_init(nullptr, nullptr);
|
||||
gst_pb_utils_init();
|
||||
|
||||
#ifdef HAVE_MOODBAR
|
||||
gstfastspectrum_register_static();
|
||||
|
||||
@@ -65,8 +65,8 @@ bool PhononEngine::CanDecode(const QUrl &url) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PhononEngine::Load(const QUrl &media_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
media_object_->setCurrentSource(Phonon::MediaSource(media_url));
|
||||
bool PhononEngine::Load(const QUrl &stream_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
media_object_->setCurrentSource(Phonon::MediaSource(stream_url));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class PhononEngine : public Engine::Base {
|
||||
|
||||
bool CanDecode(const QUrl &url);
|
||||
|
||||
bool Load(const QUrl &media_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool Load(const QUrl &stream_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool Play(quint64 offset_nanosec);
|
||||
void Stop(bool stop_after = false);
|
||||
void Pause();
|
||||
|
||||
@@ -98,12 +98,12 @@ bool VLCEngine::Init() {
|
||||
|
||||
}
|
||||
|
||||
bool VLCEngine::Load(const QUrl &media_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
bool VLCEngine::Load(const QUrl &stream_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
|
||||
if (!Initialised()) return false;
|
||||
|
||||
// Create the media object
|
||||
VlcScopedRef<libvlc_media_t> media(libvlc_media_new_location(instance_, media_url.toEncoded().constData()));
|
||||
VlcScopedRef<libvlc_media_t> media(libvlc_media_new_location(instance_, stream_url.toEncoded().constData()));
|
||||
|
||||
libvlc_media_player_set_media(player_, media);
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class VLCEngine : public Engine::Base {
|
||||
|
||||
bool Init();
|
||||
Engine::State state() const { return state_; }
|
||||
bool Load(const QUrl &media_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool Load(const QUrl &stream_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool Play(quint64 offset_nanosec);
|
||||
void Stop(bool stop_after = false);
|
||||
void Pause();
|
||||
|
||||
@@ -301,22 +301,22 @@ Engine::State XineEngine::state() const {
|
||||
return Engine::Empty;
|
||||
case XINE_STATUS_STOP:
|
||||
default:
|
||||
return media_url_.isEmpty() ? Engine::Empty : Engine::Idle;
|
||||
return stream_url_.isEmpty() ? Engine::Empty : Engine::Idle;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool XineEngine::Load(const QUrl &media_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
bool XineEngine::Load(const QUrl &stream_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
|
||||
if (!EnsureStream()) return false;
|
||||
|
||||
have_metadata_ = false;
|
||||
|
||||
Engine::Base::Load(media_url, original_url, change, force_stop_at_end, beginning_nanosec, end_nanosec);
|
||||
Engine::Base::Load(stream_url, original_url, change, force_stop_at_end, beginning_nanosec, end_nanosec);
|
||||
|
||||
xine_close(stream_);
|
||||
|
||||
int result = xine_open(stream_, media_url.toString().toUtf8());
|
||||
int result = xine_open(stream_, stream_url.toString().toUtf8());
|
||||
if (result) {
|
||||
|
||||
#if !defined(XINE_SAFE_MODE) && defined(XINE_ANALYZER)
|
||||
@@ -502,7 +502,7 @@ uint XineEngine::length() const {
|
||||
|
||||
// Xine often delivers nonsense values for VBR files and such, so we only use the length for remote files
|
||||
|
||||
if (media_url_.isLocalFile()) return 0;
|
||||
if (stream_url_.isLocalFile()) return 0;
|
||||
else {
|
||||
int pos = 0, time = 0, length = 0;
|
||||
|
||||
@@ -695,7 +695,7 @@ void XineEngine::XineEventListener(void *p, const xine_event_t *event) {
|
||||
message += QString::fromUtf8((char*)data + data->parameters);
|
||||
}
|
||||
emit engine->StateChanged(Engine::Error);
|
||||
emit engine->InvalidSongRequested(engine->media_url_);
|
||||
emit engine->InvalidSongRequested(engine->stream_url_);
|
||||
break;
|
||||
case XINE_MSG_UNKNOWN_HOST:
|
||||
message = "The host is unknown.";
|
||||
@@ -704,7 +704,7 @@ void XineEngine::XineEventListener(void *p, const xine_event_t *event) {
|
||||
message += QString::fromUtf8((char*)data + data->parameters);
|
||||
}
|
||||
emit engine->StateChanged(Engine::Error);
|
||||
emit engine->InvalidSongRequested(engine->media_url_);
|
||||
emit engine->InvalidSongRequested(engine->stream_url_);
|
||||
break;
|
||||
case XINE_MSG_UNKNOWN_DEVICE:
|
||||
message = "The device name you specified seems invalid.";
|
||||
@@ -713,7 +713,7 @@ void XineEngine::XineEventListener(void *p, const xine_event_t *event) {
|
||||
message += QString::fromUtf8((char*)data + data->parameters);
|
||||
}
|
||||
emit engine->StateChanged(Engine::Error);
|
||||
emit engine->InvalidSongRequested(engine->media_url_);
|
||||
emit engine->InvalidSongRequested(engine->stream_url_);
|
||||
break;
|
||||
case XINE_MSG_NETWORK_UNREACHABLE:
|
||||
message = "The network appears unreachable.";
|
||||
@@ -722,7 +722,7 @@ void XineEngine::XineEventListener(void *p, const xine_event_t *event) {
|
||||
message += QString::fromUtf8((char*)data + data->parameters);
|
||||
}
|
||||
emit engine->StateChanged(Engine::Error);
|
||||
emit engine->InvalidSongRequested(engine->media_url_);
|
||||
emit engine->InvalidSongRequested(engine->stream_url_);
|
||||
break;
|
||||
case XINE_MSG_AUDIO_OUT_UNAVAILABLE:
|
||||
message = "Audio output unavailable; the device is busy.";
|
||||
@@ -740,7 +740,7 @@ void XineEngine::XineEventListener(void *p, const xine_event_t *event) {
|
||||
message += QString::fromUtf8((char*)data + data->parameters);
|
||||
}
|
||||
emit engine->StateChanged(Engine::Error);
|
||||
emit engine->InvalidSongRequested(engine->media_url_);
|
||||
emit engine->InvalidSongRequested(engine->stream_url_);
|
||||
break;
|
||||
case XINE_MSG_FILE_NOT_FOUND:
|
||||
message = "File not found.";
|
||||
@@ -749,7 +749,7 @@ void XineEngine::XineEventListener(void *p, const xine_event_t *event) {
|
||||
message += QString::fromUtf8((char*)data + data->parameters);
|
||||
}
|
||||
emit engine->StateChanged(Engine::Error);
|
||||
emit engine->InvalidSongRequested(engine->media_url_);
|
||||
emit engine->InvalidSongRequested(engine->stream_url_);
|
||||
break;
|
||||
case XINE_MSG_PERMISSION_ERROR:
|
||||
message = "Access denied.";
|
||||
@@ -758,7 +758,7 @@ void XineEngine::XineEventListener(void *p, const xine_event_t *event) {
|
||||
message += QString::fromUtf8((char*)data + data->parameters);
|
||||
}
|
||||
emit engine->StateChanged(Engine::Error);
|
||||
emit engine->InvalidSongRequested(engine->media_url_);
|
||||
emit engine->InvalidSongRequested(engine->stream_url_);
|
||||
break;
|
||||
case XINE_MSG_READ_ERROR:
|
||||
message = "Read error.";
|
||||
@@ -767,7 +767,7 @@ void XineEngine::XineEventListener(void *p, const xine_event_t *event) {
|
||||
message += QString::fromUtf8((char*)data + data->parameters);
|
||||
}
|
||||
emit engine->StateChanged(Engine::Error);
|
||||
emit engine->InvalidSongRequested(engine->media_url_);
|
||||
emit engine->InvalidSongRequested(engine->stream_url_);
|
||||
break;
|
||||
case XINE_MSG_LIBRARY_LOAD_ERROR:
|
||||
message = "A problem occurred while loading a library or decoder.";
|
||||
@@ -885,7 +885,7 @@ void XineEngine::DetermineAndShowErrorMessage() {
|
||||
// xine can read the plugin but it didn't find any codec
|
||||
// THUS xine=daft for telling us it could handle the format in canDecode!
|
||||
message = "There is no available decoder.";
|
||||
QString const ext = QFileInfo(media_url_.path()).completeSuffix();
|
||||
QString const ext = QFileInfo(stream_url_.path()).completeSuffix();
|
||||
break;
|
||||
}
|
||||
result = xine_get_stream_info(stream_, XINE_STREAM_INFO_HAS_AUDIO);
|
||||
|
||||
@@ -54,7 +54,7 @@ class XineEngine : public Engine::Base {
|
||||
|
||||
bool Init();
|
||||
Engine::State state() const;
|
||||
bool Load(const QUrl &media_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool Load(const QUrl &stream_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool Play(quint64 offset_nanosec);
|
||||
void Stop(bool stop_after = false);
|
||||
void Pause();
|
||||
@@ -103,7 +103,7 @@ class XineEngine : public Engine::Base {
|
||||
#endif
|
||||
float preamp_;
|
||||
|
||||
QUrl media_url_;
|
||||
QUrl stream_url_;
|
||||
QUrl original_url_;
|
||||
bool have_metadata_;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user