Add stream discoverer to gstreamer pipeline and continuous updating of bitrate
This commit is contained in:
parent
8962644ba8
commit
e45a0bf24b
|
@ -103,6 +103,7 @@ pkg_check_modules(GSTREAMER_BASE gstreamer-base-1.0)
|
|||
pkg_check_modules(GSTREAMER_AUDIO gstreamer-audio-1.0)
|
||||
pkg_check_modules(GSTREAMER_APP gstreamer-app-1.0)
|
||||
pkg_check_modules(GSTREAMER_TAG gstreamer-tag-1.0)
|
||||
pkg_check_modules(GSTREAMER_PBUTILS gstreamer-pbutils-1.0)
|
||||
pkg_check_modules(LIBXINE libxine)
|
||||
pkg_check_modules(LIBVLC libvlc)
|
||||
pkg_check_modules(PHONON phonon4qt5)
|
||||
|
@ -274,6 +275,7 @@ optional_component(GSTREAMER ON "Engine: GStreamer backend"
|
|||
DEPENDS "gstreamer-app-1.0" GSTREAMER_APP_FOUND
|
||||
DEPENDS "gstreamer-audio-1.0" GSTREAMER_AUDIO_FOUND
|
||||
DEPENDS "gstreamer-tag-1.0" GSTREAMER_TAG_FOUND
|
||||
DEPENDS "gstreamer-pbutils-1.0" GSTREAMER_PBUTILS_FOUND
|
||||
)
|
||||
|
||||
optional_component(XINE ON "Engine: Xine backend"
|
||||
|
|
|
@ -58,6 +58,7 @@ if(HAVE_GSTREAMER)
|
|||
include_directories(${GSTREAMER_AUDIO_INCLUDE_DIRS})
|
||||
include_directories(${GSTREAMER_BASE_INCLUDE_DIRS})
|
||||
include_directories(${GSTREAMER_TAG_INCLUDE_DIRS})
|
||||
include_directories(${GSTREAMER_PBUTILS_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_PHONON)
|
||||
|
@ -1039,7 +1040,7 @@ if(HAVE_ALSA)
|
|||
endif(HAVE_ALSA)
|
||||
|
||||
if(HAVE_GSTREAMER)
|
||||
target_link_libraries(strawberry_lib ${GSTREAMER_LIBRARIES} ${GSTREAMER_BASE_LIBRARIES} ${GSTREAMER_AUDIO_LIBRARIES} ${GSTREAMER_APP_LIBRARIES} ${GSTREAMER_TAG_LIBRARIES})
|
||||
target_link_libraries(strawberry_lib ${GSTREAMER_LIBRARIES} ${GSTREAMER_BASE_LIBRARIES} ${GSTREAMER_AUDIO_LIBRARIES} ${GSTREAMER_APP_LIBRARIES} ${GSTREAMER_TAG_LIBRARIES} ${GSTREAMER_PBUTILS_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_XINE)
|
||||
|
|
|
@ -704,6 +704,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
|||
|
||||
// Context
|
||||
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), context_view_, SLOT(SongChanged(Song)));
|
||||
connect(app_->playlist_manager(), SIGNAL(SongMetadataChanged(Song)), context_view_, SLOT(SongChanged(Song)));
|
||||
connect(app_->player(), SIGNAL(PlaylistFinished()), context_view_, SLOT(Stopped()));
|
||||
connect(app_->player(), SIGNAL(Playing()), context_view_, SLOT(Playing()));
|
||||
connect(app_->player(), SIGNAL(Stopped()), context_view_, SLOT(Stopped()));
|
||||
|
@ -854,7 +855,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
|||
|
||||
RefreshStyleSheet();
|
||||
|
||||
qLog(Debug) << "Started";
|
||||
qLog(Debug) << "Started" << QThread::currentThread();
|
||||
initialised_ = true;
|
||||
|
||||
app_->scrobbler()->ConnectError();
|
||||
|
|
|
@ -291,7 +291,7 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
|||
|
||||
case UrlHandler::LoadResult::TrackAvailable: {
|
||||
|
||||
qLog(Debug) << "URL handler for" << result.original_url_ << "returned" << result.media_url_;
|
||||
qLog(Debug) << "URL handler for" << result.original_url_ << "returned" << result.stream_url_;
|
||||
|
||||
Song song;
|
||||
if (is_current) song = item->Metadata();
|
||||
|
@ -299,14 +299,14 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
|||
|
||||
bool update(false);
|
||||
|
||||
// Set the media url in the temporary metadata.
|
||||
// Set the stream url in the temporary metadata.
|
||||
if (
|
||||
(result.media_url_.isValid())
|
||||
(result.stream_url_.isValid())
|
||||
&&
|
||||
(result.media_url_ != song.url())
|
||||
(result.stream_url_ != song.url())
|
||||
)
|
||||
{
|
||||
song.set_stream_url(result.media_url_);
|
||||
song.set_stream_url(result.stream_url_);
|
||||
update = true;
|
||||
}
|
||||
|
||||
|
@ -351,13 +351,13 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
|||
}
|
||||
|
||||
if (is_current) {
|
||||
qLog(Debug) << "Playing song" << item->Metadata().title() << result.media_url_;
|
||||
engine_->Play(result.media_url_, result.original_url_, stream_change_type_, item->Metadata().has_cue(), item->Metadata().beginning_nanosec(), item->Metadata().end_nanosec());
|
||||
qLog(Debug) << "Playing song" << item->Metadata().title() << result.stream_url_;
|
||||
engine_->Play(result.stream_url_, result.original_url_, stream_change_type_, item->Metadata().has_cue(), item->Metadata().beginning_nanosec(), item->Metadata().end_nanosec());
|
||||
current_item_ = item;
|
||||
}
|
||||
else if (is_next) {
|
||||
qLog(Debug) << "Preloading next song" << next_item->Metadata().title() << result.media_url_;
|
||||
engine_->StartPreloading(result.media_url_, next_item->Url(), next_item->Metadata().has_cue(), next_item->Metadata().beginning_nanosec(), next_item->Metadata().end_nanosec());
|
||||
qLog(Debug) << "Preloading next song" << next_item->Metadata().title() << result.stream_url_;
|
||||
engine_->StartPreloading(result.stream_url_, next_item->Url(), next_item->Metadata().has_cue(), next_item->Metadata().beginning_nanosec(), next_item->Metadata().end_nanosec());
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -595,7 +595,7 @@ void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle)
|
|||
}
|
||||
|
||||
current_item_ = app_->playlist_manager()->active()->current_item();
|
||||
const QUrl url = (current_item_->MediaUrl().isValid() ? current_item_->MediaUrl() : current_item_->Url());
|
||||
const QUrl url = (current_item_->StreamUrl().isValid() ? current_item_->StreamUrl() : current_item_->Url());
|
||||
|
||||
if (url_handlers_.contains(url.scheme())) {
|
||||
// It's already loading
|
||||
|
@ -663,32 +663,13 @@ void Player::EngineMetadataReceived(const Engine::SimpleMetaBundle &bundle) {
|
|||
PlaylistItemPtr item = app_->playlist_manager()->active()->current_item();
|
||||
if (!item) return;
|
||||
|
||||
if (bundle.url != item->Metadata().url()) return;
|
||||
if (bundle.url != item->Url()) return;
|
||||
|
||||
Engine::SimpleMetaBundle bundle_copy = bundle;
|
||||
|
||||
// Maybe the metadata is from icycast and has "Artist - Title" shoved together in the title field.
|
||||
const int dash_pos = bundle_copy.title.indexOf('-');
|
||||
if (dash_pos != -1 && bundle_copy.artist.isEmpty()) {
|
||||
// Split on " - " if it exists, otherwise split on "-".
|
||||
const int space_dash_pos = bundle_copy.title.indexOf(" - ");
|
||||
if (space_dash_pos != -1) {
|
||||
bundle_copy.artist = bundle_copy.title.left(space_dash_pos).trimmed();
|
||||
bundle_copy.title = bundle_copy.title.mid(space_dash_pos + 3).trimmed();
|
||||
}
|
||||
else {
|
||||
bundle_copy.artist = bundle_copy.title.left(dash_pos).trimmed();
|
||||
bundle_copy.title = bundle_copy.title.mid(dash_pos + 1).trimmed();
|
||||
}
|
||||
}
|
||||
|
||||
Song song = item->Metadata();
|
||||
song.MergeFromSimpleMetaBundle(bundle_copy);
|
||||
bool minor = song.MergeFromSimpleMetaBundle(bundle);
|
||||
|
||||
// Ignore useless metadata
|
||||
if (song.title().isEmpty() && song.artist().isEmpty()) return;
|
||||
|
||||
app_->playlist_manager()->active()->SetStreamMetadata(item->Url(), song);
|
||||
app_->playlist_manager()->active()->SetStreamMetadata(item->Url(), song, minor);
|
||||
|
||||
}
|
||||
|
||||
|
@ -768,7 +749,7 @@ void Player::TrackAboutToEnd() {
|
|||
// Crossfade is off, so start preloading the next track so we don't get a gap between songs.
|
||||
if (!has_next_row || !next_item) return;
|
||||
|
||||
QUrl url = (next_item->MediaUrl().isValid() ? next_item->MediaUrl() : next_item->Url());
|
||||
QUrl url = (next_item->StreamUrl().isValid() ? next_item->StreamUrl() : next_item->Url());
|
||||
|
||||
// Get the actual track URL rather than the stream URL.
|
||||
if (url_handlers_.contains(url.scheme())) {
|
||||
|
@ -784,7 +765,7 @@ void Player::TrackAboutToEnd() {
|
|||
loading_async_ << url;
|
||||
return;
|
||||
case UrlHandler::LoadResult::TrackAvailable:
|
||||
url = result.media_url_;
|
||||
url = result.stream_url_;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1149,28 +1149,57 @@ void Song::ToMTP(LIBMTP_track_t *track) const {
|
|||
}
|
||||
#endif
|
||||
|
||||
void Song::MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle) {
|
||||
|
||||
if (d->init_from_file_ || d->url_.scheme() == "file") {
|
||||
// This Song was already loaded using taglib. Our tags are probably better than the engine's.
|
||||
// Note: init_from_file_ is used for non-file:// URLs when the metadata is known to be good, like from Jamendo.
|
||||
return;
|
||||
}
|
||||
bool Song::MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle) {
|
||||
|
||||
d->valid_ = true;
|
||||
if (!bundle.title.isEmpty()) set_title(bundle.title);
|
||||
if (!bundle.artist.isEmpty()) set_artist(bundle.artist);
|
||||
if (!bundle.album.isEmpty()) set_album(bundle.album);
|
||||
if (!bundle.comment.isEmpty()) d->comment_ = bundle.comment;
|
||||
if (!bundle.genre.isEmpty()) d->genre_ = bundle.genre;
|
||||
|
||||
bool minor = true;
|
||||
|
||||
if (d->init_from_file_ || is_collection_song() || d->url_.isLocalFile()) {
|
||||
// This Song was already loaded using taglib. Our tags are probably better than the engine's.
|
||||
if (title() != bundle.title && title().isEmpty() && !bundle.title.isEmpty()) {
|
||||
set_title(bundle.title);
|
||||
minor = false;
|
||||
}
|
||||
if (artist() != bundle.artist && artist().isEmpty() && !bundle.artist.isEmpty()) {
|
||||
set_artist(bundle.artist);
|
||||
minor = false;
|
||||
}
|
||||
if (album() != bundle.album && album().isEmpty() && !bundle.album.isEmpty()) {
|
||||
set_album(bundle.album);
|
||||
minor = false;
|
||||
}
|
||||
if (comment().isEmpty() && !bundle.comment.isEmpty()) set_comment(bundle.comment);
|
||||
if (genre().isEmpty() && !bundle.genre.isEmpty()) set_genre(bundle.genre);
|
||||
if (lyrics().isEmpty() && !bundle.lyrics.isEmpty()) set_lyrics(bundle.lyrics);
|
||||
}
|
||||
else {
|
||||
if (title() != bundle.title && !bundle.title.isEmpty()) {
|
||||
set_title(bundle.title);
|
||||
minor = false;
|
||||
}
|
||||
if (artist() != bundle.artist && !bundle.artist.isEmpty()) {
|
||||
set_artist(bundle.artist);
|
||||
minor = false;
|
||||
}
|
||||
if (album() != bundle.album && !bundle.album.isEmpty()) {
|
||||
set_album(bundle.album);
|
||||
minor = false;
|
||||
}
|
||||
if (!bundle.comment.isEmpty()) set_comment(bundle.comment);
|
||||
if (!bundle.genre.isEmpty()) set_genre(bundle.genre);
|
||||
if (!bundle.lyrics.isEmpty()) set_lyrics(bundle.lyrics);
|
||||
}
|
||||
|
||||
if (bundle.length > 0) set_length_nanosec(bundle.length);
|
||||
if (bundle.year > 0) d->year_ = bundle.year;
|
||||
if (bundle.track > 0) d->track_ = bundle.track;
|
||||
if (bundle.filetype != FileType_Unknown) d->filetype_ = bundle.filetype;
|
||||
if (bundle.samplerate > 0) d->samplerate_ = bundle.samplerate;
|
||||
if (bundle.bitdepth > 0) d->samplerate_ = bundle.bitdepth;
|
||||
if (bundle.bitdepth > 0) d->bitdepth_ = bundle.bitdepth;
|
||||
if (bundle.bitrate > 0) d->bitrate_ = bundle.bitrate;
|
||||
if (!bundle.lyrics.isEmpty()) d->lyrics_ = bundle.lyrics;
|
||||
|
||||
return minor;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -156,7 +156,7 @@ class Song {
|
|||
void InitFromFilePartial(const QString &filename); // Just store the filename: incomplete but fast
|
||||
void InitArtManual(); // Check if there is already a art in the cache and store the filename in art_manual
|
||||
|
||||
void MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle);
|
||||
bool MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle);
|
||||
|
||||
#ifdef HAVE_LIBGPOD
|
||||
void InitFromItdb(const _Itdb_Track *track, const QString &prefix);
|
||||
|
|
|
@ -28,10 +28,10 @@
|
|||
#include "song.h"
|
||||
#include "urlhandler.h"
|
||||
|
||||
UrlHandler::LoadResult::LoadResult(const QUrl &original_url, const Type type, const QUrl &media_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 length_nanosec, const QString error) :
|
||||
UrlHandler::LoadResult::LoadResult(const QUrl &original_url, const Type type, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 length_nanosec, const QString error) :
|
||||
original_url_(original_url),
|
||||
type_(type),
|
||||
media_url_(media_url),
|
||||
stream_url_(stream_url),
|
||||
filetype_(filetype),
|
||||
samplerate_(samplerate),
|
||||
bit_depth_(bit_depth),
|
||||
|
|
|
@ -50,14 +50,14 @@ class UrlHandler : public QObject {
|
|||
// AsyncLoadComplete will be emitted later with the same original_url.
|
||||
WillLoadAsynchronously,
|
||||
|
||||
// There was a track available. Its url is in media_url.
|
||||
// There was a track available. Its url is in stream_url.
|
||||
TrackAvailable,
|
||||
|
||||
// There was a error
|
||||
Error,
|
||||
};
|
||||
|
||||
LoadResult(const QUrl &original_url = QUrl(), const Type type = NoMoreTracks, const QUrl &media_url = QUrl(), const Song::FileType filetype = Song::FileType_Stream, const int samplerate = -1, const int bitdepth = -1, const qint64 length_nanosec_ = -1, const QString error = QString());
|
||||
LoadResult(const QUrl &original_url = QUrl(), const Type type = NoMoreTracks, const QUrl &stream_url = QUrl(), const Song::FileType filetype = Song::FileType_Stream, const int samplerate = -1, const int bitdepth = -1, const qint64 length_nanosec_ = -1, const QString error = QString());
|
||||
|
||||
// The url that the playlist item has in Url().
|
||||
// Might be something unplayable like lastfm://...
|
||||
|
@ -66,7 +66,7 @@ class UrlHandler : public QObject {
|
|||
Type type_;
|
||||
|
||||
// The actual url to something that gstreamer can play.
|
||||
QUrl media_url_;
|
||||
QUrl stream_url_;
|
||||
|
||||
// The type of the stream
|
||||
Song::FileType filetype_;
|
||||
|
|
|
@ -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_;
|
||||
|
||||
|
|
|
@ -1478,29 +1478,25 @@ void Playlist::StopAfter(int row) {
|
|||
|
||||
}
|
||||
|
||||
void Playlist::SetStreamMetadata(const QUrl &url, const Song &song) {
|
||||
void Playlist::SetStreamMetadata(const QUrl &url, const Song &song, const bool minor) {
|
||||
|
||||
if (!current_item()) return;
|
||||
if (current_item()->Url() != url) return;
|
||||
if (!current_item() || current_item()->Url() != url) return;
|
||||
|
||||
// Don't update the metadata if it's only a minor change from before
|
||||
if (
|
||||
current_item()->Metadata().filetype() == song.filetype() &&
|
||||
current_item()->Metadata().artist() == song.artist() &&
|
||||
current_item()->Metadata().title() == song.title() &&
|
||||
current_item()->Metadata().album() == song.album()
|
||||
) return;
|
||||
|
||||
// TODO: Update context & playlist if changed, but don't show popup.
|
||||
//(song.bitrate() <= 0 || current_item()->Metadata().bitrate() == song.bitrate())
|
||||
//(song.samplerate() <= 0 || current_item()->Metadata().samplerate() == song.samplerate())
|
||||
//(song.bitdepth() <= 0 || current_item()->Metadata().bitdepth() == song.bitdepth())
|
||||
|
||||
qLog(Debug) << "Setting metadata for" << url << "to" << song.artist() << song.title();
|
||||
//qLog(Debug) << "Setting temporary metadata for" << url;
|
||||
|
||||
current_item()->SetTemporaryMetadata(song);
|
||||
|
||||
InformOfCurrentSongChange();
|
||||
if (minor) {
|
||||
emit dataChanged(index(current_item_index_.row(), 0), index(current_item_index_.row(), ColumnCount - 1));
|
||||
// if the song is invalid, we won't play it - there's no point in informing anybody about the change
|
||||
const Song metadata(current_item_metadata());
|
||||
if (metadata.is_valid()) {
|
||||
emit SongMetadataChanged(metadata);
|
||||
}
|
||||
}
|
||||
else {
|
||||
InformOfCurrentSongChange();
|
||||
}
|
||||
|
||||
UpdateScrobblePoint();
|
||||
|
||||
|
@ -1936,12 +1932,11 @@ bool Playlist::ApplyValidityOnCurrentSong(const QUrl &url, bool valid) {
|
|||
Song current_song = current->Metadata();
|
||||
|
||||
// If validity has changed, reload the item
|
||||
// FIXME: Why?
|
||||
// Removed this because it caused "Empty filename passed to function" errors when not using local filenames.
|
||||
// It also causes Context and Playing widget to reload the image and getting stuck in playing mode when the URL is broken.
|
||||
//if(!current_song.is_cdda() && current_song.url() == url && current_song.is_valid() != QFile::exists(current_song.url().toLocalFile())) {
|
||||
//ReloadItems(QList<int>() << current_row());
|
||||
//}
|
||||
if (current_song.source() == Song::Source_LocalFile || current_song.source() == Song::Source_Collection) {
|
||||
if (current_song.url() == url && current_song.url().isLocalFile() && current_song.is_valid() != QFile::exists(current_song.url().toLocalFile())) {
|
||||
ReloadItems(QList<int>() << current_row());
|
||||
}
|
||||
}
|
||||
|
||||
// Gray out the song if it's now broken; otherwise undo the gray color
|
||||
if (valid) {
|
||||
|
|
|
@ -281,7 +281,7 @@ class Playlist : public QAbstractListModel {
|
|||
void IgnoreSorting(bool value) { ignore_sorting_ = value; }
|
||||
|
||||
void ClearStreamMetadata();
|
||||
void SetStreamMetadata(const QUrl &url, const Song &song);
|
||||
void SetStreamMetadata(const QUrl &url, const Song &song, const bool minor);
|
||||
void ItemChanged(PlaylistItemPtr item);
|
||||
void UpdateItems(const SongList &songs);
|
||||
|
||||
|
@ -298,10 +298,11 @@ class Playlist : public QAbstractListModel {
|
|||
// Removes items with given indices from the playlist. This operation is not undoable.
|
||||
void RemoveItemsWithoutUndo(const QList<int> &indices);
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void RestoreFinished();
|
||||
void PlaylistLoaded();
|
||||
void CurrentSongChanged(const Song &metadata);
|
||||
void SongMetadataChanged(const Song &metadata);
|
||||
void EditingFinished(const QModelIndex &index);
|
||||
void PlayRequested(const QModelIndex &index);
|
||||
|
||||
|
@ -314,7 +315,7 @@ signals:
|
|||
// Signals that the queue has changed, meaning that the remaining queued items should update their position.
|
||||
void QueueChanged();
|
||||
|
||||
private:
|
||||
private:
|
||||
void SetCurrentIsPaused(bool paused);
|
||||
int NextVirtualIndex(int i, bool ignore_repeat_track) const;
|
||||
int PreviousVirtualIndex(int i, bool ignore_repeat_track) const;
|
||||
|
@ -346,7 +347,7 @@ private:
|
|||
void ItemsLoaded(QFuture<PlaylistItemList> future);
|
||||
void SongInsertVetoListenerDestroyed();
|
||||
|
||||
private:
|
||||
private:
|
||||
bool is_loading_;
|
||||
PlaylistFilter *proxy_;
|
||||
Queue *queue_;
|
||||
|
@ -396,8 +397,4 @@ private:
|
|||
|
||||
};
|
||||
|
||||
// QDataStream& operator <<(QDataStream&, const Playlist*);
|
||||
// QDataStream& operator >>(QDataStream&, Playlist*&);
|
||||
|
||||
#endif // PLAYLIST_H
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ class PlaylistItem : public std::enable_shared_from_this<PlaylistItem> {
|
|||
void SetTemporaryMetadata(const Song &metadata);
|
||||
void ClearTemporaryMetadata();
|
||||
bool HasTemporaryMetadata() const { return temp_metadata_.is_valid(); }
|
||||
QUrl MediaUrl() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() && temp_metadata_.url().isValid() ? temp_metadata_.url() : QUrl(); }
|
||||
QUrl StreamUrl() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() && temp_metadata_.url().isValid() ? temp_metadata_.url() : QUrl(); }
|
||||
|
||||
// Background colors.
|
||||
void SetBackgroundColor(short priority, const QColor &color);
|
||||
|
|
|
@ -140,6 +140,7 @@ Playlist *PlaylistManager::AddPlaylist(int id, const QString &name, const QStrin
|
|||
ret->set_ui_path(ui_path);
|
||||
|
||||
connect(ret, SIGNAL(CurrentSongChanged(Song)), SIGNAL(CurrentSongChanged(Song)));
|
||||
connect(ret, SIGNAL(SongMetadataChanged(Song)), SIGNAL(SongMetadataChanged(Song)));
|
||||
connect(ret, SIGNAL(PlaylistChanged()), SLOT(OneOfPlaylistsChanged()));
|
||||
connect(ret, SIGNAL(PlaylistChanged()), SLOT(UpdateSummaryText()));
|
||||
connect(ret, SIGNAL(EditingFinished(QModelIndex)), SIGNAL(EditingFinished(QModelIndex)));
|
||||
|
|
|
@ -119,6 +119,7 @@ public slots:
|
|||
|
||||
// Forwarded from individual playlists
|
||||
void CurrentSongChanged(const Song& song);
|
||||
void SongMetadataChanged(const Song& song);
|
||||
|
||||
// Signals that one of manager's playlists has changed (new items, new ordering etc.) - the argument shows which.
|
||||
void PlaylistChanged(Playlist *playlist);
|
||||
|
|
|
@ -658,14 +658,14 @@ void QobuzService::GetStreamURL(const QUrl &url) {
|
|||
|
||||
}
|
||||
|
||||
void QobuzService::HandleStreamURLFinished(const QUrl &original_url, const QUrl &media_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error) {
|
||||
void QobuzService::HandleStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error) {
|
||||
|
||||
QobuzStreamURLRequest *stream_url_req = qobject_cast<QobuzStreamURLRequest*>(sender());
|
||||
if (!stream_url_req || !stream_url_requests_.contains(stream_url_req)) return;
|
||||
stream_url_req->deleteLater();
|
||||
stream_url_requests_.removeAll(stream_url_req);
|
||||
|
||||
emit StreamURLFinished(original_url, media_url, filetype, samplerate, bit_depth, duration, error);
|
||||
emit StreamURLFinished(original_url, stream_url, filetype, samplerate, bit_depth, duration, error);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -144,7 +144,7 @@ class QobuzService : public InternetService {
|
|||
void ArtistsUpdateProgressReceived(const int id, const int progress);
|
||||
void AlbumsUpdateProgressReceived(const int id, const int progress);
|
||||
void SongsUpdateProgressReceived(const int id, const int progress);
|
||||
void HandleStreamURLFinished(const QUrl &original_url, const QUrl &media_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error);
|
||||
void HandleStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error);
|
||||
|
||||
private:
|
||||
typedef QPair<QString, QString> Param;
|
||||
|
|
|
@ -51,14 +51,14 @@ UrlHandler::LoadResult QobuzUrlHandler::StartLoading(const QUrl &url) {
|
|||
|
||||
}
|
||||
|
||||
void QobuzUrlHandler::GetStreamURLFinished(const QUrl &original_url, const QUrl &media_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error) {
|
||||
void QobuzUrlHandler::GetStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error) {
|
||||
|
||||
if (task_id_ == -1) return;
|
||||
CancelTask();
|
||||
if (error.isEmpty())
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::TrackAvailable, media_url, filetype, samplerate, bit_depth, duration));
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::TrackAvailable, stream_url, filetype, samplerate, bit_depth, duration));
|
||||
else
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::Error, media_url, filetype, -1, -1, -1, error));
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::Error, stream_url, filetype, -1, -1, -1, error));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ class QobuzUrlHandler : public UrlHandler {
|
|||
void CancelTask();
|
||||
|
||||
private slots:
|
||||
void GetStreamURLFinished(const QUrl &original_url, const QUrl &media_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error = QString());
|
||||
void GetStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error = QString());
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
|
|
|
@ -52,17 +52,17 @@ UrlHandler::LoadResult SubsonicUrlHandler::StartLoading(const QUrl &url) {
|
|||
url_query.addQueryItem(encoded_param.first, encoded_param.second);
|
||||
}
|
||||
|
||||
QUrl media_url(server_url());
|
||||
QUrl stream_url(server_url());
|
||||
|
||||
if (!media_url.path().isEmpty() && media_url.path().right(1) == "/") {
|
||||
media_url.setPath(media_url.path() + QString("rest/stream"));
|
||||
if (!stream_url.path().isEmpty() && stream_url.path().right(1) == "/") {
|
||||
stream_url.setPath(stream_url.path() + QString("rest/stream"));
|
||||
}
|
||||
else
|
||||
media_url.setPath(media_url.path() + QString("/rest/stream"));
|
||||
stream_url.setPath(stream_url.path() + QString("/rest/stream"));
|
||||
|
||||
media_url.setQuery(url_query);
|
||||
stream_url.setQuery(url_query);
|
||||
|
||||
return LoadResult(url, LoadResult::TrackAvailable, media_url);
|
||||
return LoadResult(url, LoadResult::TrackAvailable, stream_url);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -51,14 +51,14 @@ UrlHandler::LoadResult TidalUrlHandler::StartLoading(const QUrl &url) {
|
|||
|
||||
}
|
||||
|
||||
void TidalUrlHandler::GetStreamURLFinished(const QUrl &original_url, const QUrl &media_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error) {
|
||||
void TidalUrlHandler::GetStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error) {
|
||||
|
||||
if (task_id_ == -1) return;
|
||||
CancelTask();
|
||||
if (error.isEmpty())
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::TrackAvailable, media_url, filetype, samplerate, bit_depth, duration));
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::TrackAvailable, stream_url, filetype, samplerate, bit_depth, duration));
|
||||
else
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::Error, media_url, filetype, -1, -1, -1, error));
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::Error, stream_url, filetype, -1, -1, -1, error));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ class TidalUrlHandler : public UrlHandler {
|
|||
void CancelTask();
|
||||
|
||||
private slots:
|
||||
void GetStreamURLFinished(const QUrl &original_url, const QUrl &media_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error = QString());
|
||||
void GetStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error = QString());
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
|
|
Loading…
Reference in New Issue