From e45a0bf24b64790f442915abb9b06f9bf4e14f7b Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Sat, 7 Sep 2019 23:34:13 +0200 Subject: [PATCH] Add stream discoverer to gstreamer pipeline and continuous updating of bitrate --- CMakeLists.txt | 2 + src/CMakeLists.txt | 3 +- src/core/mainwindow.cpp | 3 +- src/core/player.cpp | 49 ++++------- src/core/song.cpp | 57 +++++++++---- src/core/song.h | 2 +- src/core/urlhandler.cpp | 4 +- src/core/urlhandler.h | 6 +- src/engine/enginebase.cpp | 8 +- src/engine/enginebase.h | 12 +-- src/engine/gstengine.cpp | 27 +++---- src/engine/gstengine.h | 4 +- src/engine/gstenginepipeline.cpp | 121 ++++++++++++++++++++++++---- src/engine/gstenginepipeline.h | 19 +++-- src/engine/gststartup.cpp | 2 + src/engine/phononengine.cpp | 4 +- src/engine/phononengine.h | 2 +- src/engine/vlcengine.cpp | 4 +- src/engine/vlcengine.h | 2 +- src/engine/xineengine.cpp | 28 +++---- src/engine/xineengine.h | 4 +- src/playlist/playlist.cpp | 43 +++++----- src/playlist/playlist.h | 13 ++- src/playlist/playlistitem.h | 2 +- src/playlist/playlistmanager.cpp | 1 + src/playlist/playlistmanager.h | 1 + src/qobuz/qobuzservice.cpp | 4 +- src/qobuz/qobuzservice.h | 2 +- src/qobuz/qobuzurlhandler.cpp | 6 +- src/qobuz/qobuzurlhandler.h | 2 +- src/subsonic/subsonicurlhandler.cpp | 12 +-- src/tidal/tidalurlhandler.cpp | 6 +- src/tidal/tidalurlhandler.h | 2 +- 33 files changed, 281 insertions(+), 176 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f98223a6d..6ddfda8d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9d8d9b492..505fc68bb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp index 238867c42..e2c7576d9 100644 --- a/src/core/mainwindow.cpp +++ b/src/core/mainwindow.cpp @@ -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(); diff --git a/src/core/player.cpp b/src/core/player.cpp index 2f9e0dbcd..7c40901a3 100644 --- a/src/core/player.cpp +++ b/src/core/player.cpp @@ -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; } } diff --git a/src/core/song.cpp b/src/core/song.cpp index 15c4a6ff8..96b31e643 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -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; } diff --git a/src/core/song.h b/src/core/song.h index 952c7204a..1c2cbacf8 100644 --- a/src/core/song.h +++ b/src/core/song.h @@ -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); diff --git a/src/core/urlhandler.cpp b/src/core/urlhandler.cpp index 82fade99c..f08cc00ff 100644 --- a/src/core/urlhandler.cpp +++ b/src/core/urlhandler.cpp @@ -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), diff --git a/src/core/urlhandler.h b/src/core/urlhandler.h index ed63799e6..f5a877209 100644 --- a/src/core/urlhandler.h +++ b/src/core/urlhandler.h @@ -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_; diff --git a/src/engine/enginebase.cpp b/src/engine/enginebase.cpp index 2bd71eb2f..f935a8f6a 100644 --- a/src/engine/enginebase.cpp +++ b/src/engine/enginebase.cpp @@ -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); diff --git a/src/engine/enginebase.h b/src/engine/enginebase.h index 34462254b..05451f6ad 100644 --- a/src/engine/enginebase.h +++ b/src/engine/enginebase.h @@ -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; diff --git a/src/engine/gstengine.cpp b/src/engine/gstengine.cpp index 57ed02dc0..be8a31984 100644 --- a/src/engine/gstengine.cpp +++ b/src/engine/gstengine.cpp @@ -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 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 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_); } diff --git a/src/engine/gstengine.h b/src/engine/gstengine.h index f1ab5cfb6..27a02f741 100644 --- a/src/engine/gstengine.h +++ b/src/engine/gstengine.h @@ -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(); diff --git a/src/engine/gstenginepipeline.cpp b/src/engine/gstenginepipeline.cpp index 2e8e2011a..1b4340699 100644 --- a/src/engine/gstenginepipeline.cpp +++ b/src/engine/gstenginepipeline.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -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(self); + GstEnginePipeline *instance = reinterpret_cast(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(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(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"); + } + +} diff --git a/src/engine/gstenginepipeline.h b/src/engine/gstenginepipeline.h index 13a2291c7..3f024bb77 100644 --- a/src/engine/gstenginepipeline.h +++ b/src/engine/gstenginepipeline.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -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_; diff --git a/src/engine/gststartup.cpp b/src/engine/gststartup.cpp index 4a4d23aad..8f94c4afa 100644 --- a/src/engine/gststartup.cpp +++ b/src/engine/gststartup.cpp @@ -20,6 +20,7 @@ #include "config.h" #include +#include #include #include @@ -51,6 +52,7 @@ void GstStartup::InitialiseGStreamer() { SetEnvironment(); gst_init(nullptr, nullptr); + gst_pb_utils_init(); #ifdef HAVE_MOODBAR gstfastspectrum_register_static(); diff --git a/src/engine/phononengine.cpp b/src/engine/phononengine.cpp index 1c45e36c2..90b1c7231 100644 --- a/src/engine/phononengine.cpp +++ b/src/engine/phononengine.cpp @@ -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; } diff --git a/src/engine/phononengine.h b/src/engine/phononengine.h index bb201ed73..c5ab4ca9e 100644 --- a/src/engine/phononengine.h +++ b/src/engine/phononengine.h @@ -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(); diff --git a/src/engine/vlcengine.cpp b/src/engine/vlcengine.cpp index b260e3bd2..045f9d5b4 100644 --- a/src/engine/vlcengine.cpp +++ b/src/engine/vlcengine.cpp @@ -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 media(libvlc_media_new_location(instance_, media_url.toEncoded().constData())); + VlcScopedRef media(libvlc_media_new_location(instance_, stream_url.toEncoded().constData())); libvlc_media_player_set_media(player_, media); diff --git a/src/engine/vlcengine.h b/src/engine/vlcengine.h index e9229e426..eb431f493 100644 --- a/src/engine/vlcengine.h +++ b/src/engine/vlcengine.h @@ -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(); diff --git a/src/engine/xineengine.cpp b/src/engine/xineengine.cpp index 0af7738c3..974d1cf4d 100644 --- a/src/engine/xineengine.cpp +++ b/src/engine/xineengine.cpp @@ -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); diff --git a/src/engine/xineengine.h b/src/engine/xineengine.h index 14dea0c6d..262b53d9d 100644 --- a/src/engine/xineengine.h +++ b/src/engine/xineengine.h @@ -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_; diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp index 302313951..e59f6c8cf 100644 --- a/src/playlist/playlist.cpp +++ b/src/playlist/playlist.cpp @@ -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() << 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() << current_row()); + } + } // Gray out the song if it's now broken; otherwise undo the gray color if (valid) { diff --git a/src/playlist/playlist.h b/src/playlist/playlist.h index fe749b1a4..e794d4918 100644 --- a/src/playlist/playlist.h +++ b/src/playlist/playlist.h @@ -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 &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 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 - diff --git a/src/playlist/playlistitem.h b/src/playlist/playlistitem.h index db1d30d12..41345da46 100644 --- a/src/playlist/playlistitem.h +++ b/src/playlist/playlistitem.h @@ -81,7 +81,7 @@ class PlaylistItem : public std::enable_shared_from_this { 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); diff --git a/src/playlist/playlistmanager.cpp b/src/playlist/playlistmanager.cpp index b5009e48a..a9dbec189 100644 --- a/src/playlist/playlistmanager.cpp +++ b/src/playlist/playlistmanager.cpp @@ -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))); diff --git a/src/playlist/playlistmanager.h b/src/playlist/playlistmanager.h index 4b75cc972..6e9d944e4 100644 --- a/src/playlist/playlistmanager.h +++ b/src/playlist/playlistmanager.h @@ -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); diff --git a/src/qobuz/qobuzservice.cpp b/src/qobuz/qobuzservice.cpp index 8ed9d925f..152688b04 100644 --- a/src/qobuz/qobuzservice.cpp +++ b/src/qobuz/qobuzservice.cpp @@ -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(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); } diff --git a/src/qobuz/qobuzservice.h b/src/qobuz/qobuzservice.h index 5aae20bbc..78ea9bc5d 100644 --- a/src/qobuz/qobuzservice.h +++ b/src/qobuz/qobuzservice.h @@ -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 Param; diff --git a/src/qobuz/qobuzurlhandler.cpp b/src/qobuz/qobuzurlhandler.cpp index eb85bf513..a70596987 100644 --- a/src/qobuz/qobuzurlhandler.cpp +++ b/src/qobuz/qobuzurlhandler.cpp @@ -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)); } diff --git a/src/qobuz/qobuzurlhandler.h b/src/qobuz/qobuzurlhandler.h index 5167ecb54..db1e84c3e 100644 --- a/src/qobuz/qobuzurlhandler.h +++ b/src/qobuz/qobuzurlhandler.h @@ -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_; diff --git a/src/subsonic/subsonicurlhandler.cpp b/src/subsonic/subsonicurlhandler.cpp index f5b7e7913..212cebbf7 100644 --- a/src/subsonic/subsonicurlhandler.cpp +++ b/src/subsonic/subsonicurlhandler.cpp @@ -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); } diff --git a/src/tidal/tidalurlhandler.cpp b/src/tidal/tidalurlhandler.cpp index fe55d9fcb..43f5c72b3 100644 --- a/src/tidal/tidalurlhandler.cpp +++ b/src/tidal/tidalurlhandler.cpp @@ -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)); } diff --git a/src/tidal/tidalurlhandler.h b/src/tidal/tidalurlhandler.h index c23e358f6..a1e8b7d6f 100644 --- a/src/tidal/tidalurlhandler.h +++ b/src/tidal/tidalurlhandler.h @@ -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_;