diff --git a/src/core/player.cpp b/src/core/player.cpp index 8222a9498..a090cc376 100644 --- a/src/core/player.cpp +++ b/src/core/player.cpp @@ -146,9 +146,10 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult& result) { item->SetTemporaryMetadata(song); app_->playlist_manager()->active()->InformOfCurrentSongChange(); } - engine_->Play( - result.media_url_, stream_change_type_, item->Metadata().has_cue(), - item->Metadata().beginning_nanosec(), item->Metadata().end_nanosec()); + MediaPlaybackRequest req(result.media_url_); + engine_->Play(req, stream_change_type_, item->Metadata().has_cue(), + item->Metadata().beginning_nanosec(), + item->Metadata().end_nanosec()); current_item_ = item; loading_async_ = QUrl(); @@ -420,8 +421,8 @@ void Player::PlayAt(int index, Engine::TrackChangeFlags change, HandleLoadResult(url_handlers_[url.scheme()]->StartLoading(url)); } else { loading_async_ = QUrl(); - engine_->Play(current_item_->Url(), change, - current_item_->Metadata().has_cue(), + MediaPlaybackRequest req(current_item_->Url()); + engine_->Play(req, change, current_item_->Metadata().has_cue(), current_item_->Metadata().beginning_nanosec(), current_item_->Metadata().end_nanosec()); @@ -604,7 +605,8 @@ void Player::TrackAboutToEnd() { break; } } - engine_->StartPreloading(url, next_item->Metadata().has_cue(), + MediaPlaybackRequest req(url); + engine_->StartPreloading(req, next_item->Metadata().has_cue(), next_item->Metadata().beginning_nanosec(), next_item->Metadata().end_nanosec()); } diff --git a/src/engines/enginebase.cpp b/src/engines/enginebase.cpp index a6b6da15c..514e99b3c 100644 --- a/src/engines/enginebase.cpp +++ b/src/engines/enginebase.cpp @@ -43,12 +43,12 @@ Engine::Base::Base() Engine::Base::~Base() {} -bool Engine::Base::Load(const QUrl& url, TrackChangeFlags, +bool Engine::Base::Load(const MediaPlaybackRequest& req, TrackChangeFlags, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) { Q_UNUSED(force_stop_at_end); - url_ = url; + playback_req_ = req; beginning_nanosec_ = beginning_nanosec; end_nanosec_ = end_nanosec; @@ -92,10 +92,10 @@ void Engine::Base::EmitAboutToEnd() { int Engine::Base::AddBackgroundStream(const QUrl& url) { return -1; } -bool Engine::Base::Play(const QUrl& u, TrackChangeFlags c, +bool Engine::Base::Play(const MediaPlaybackRequest& req, TrackChangeFlags c, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) { - if (!Load(u, c, force_stop_at_end, beginning_nanosec, end_nanosec)) + if (!Load(req, c, force_stop_at_end, beginning_nanosec, end_nanosec)) return false; return Play(0); diff --git a/src/engines/enginebase.h b/src/engines/enginebase.h index d8ee0cf5e..e5b291d69 100644 --- a/src/engines/enginebase.h +++ b/src/engines/enginebase.h @@ -32,6 +32,7 @@ #include #include "engine_fwd.h" +#include "playbackrequest.h" namespace Engine { @@ -45,7 +46,8 @@ class Base : public QObject { virtual bool Init() = 0; - virtual void StartPreloading(const QUrl&, bool, qint64, qint64) {} + virtual void StartPreloading(const MediaPlaybackRequest&, bool, qint64, + qint64) {} virtual bool Play(quint64 offset_nanosec) = 0; virtual void Stop(bool stop_after = false) = 0; virtual void Pause() = 0; @@ -62,7 +64,7 @@ class Base : public QObject { // Subclasses should respect given markers (beginning and end) which are // in milliseconds. - virtual bool Load(const QUrl& url, TrackChangeFlags change, + virtual bool Load(const MediaPlaybackRequest& req, TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec); // Sets new values for the beginning and end markers of the currently playing @@ -78,8 +80,9 @@ class Base : public QObject { // 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& u, TrackChangeFlags c, bool force_stop_at_end, - quint64 beginning_nanosec, qint64 end_nanosec); + bool Play(const MediaPlaybackRequest& req, TrackChangeFlags c, + bool force_stop_at_end, quint64 beginning_nanosec, + qint64 end_nanosec); void SetVolume(uint value); @@ -139,7 +142,7 @@ signals: uint volume_; quint64 beginning_nanosec_; qint64 end_nanosec_; - QUrl url_; + MediaPlaybackRequest playback_req_; Scope scope_; bool fadeout_enabled_; diff --git a/src/engines/gstengine.cpp b/src/engines/gstengine.cpp index 052e4bf29..a4e2a3e1d 100644 --- a/src/engines/gstengine.cpp +++ b/src/engines/gstengine.cpp @@ -251,7 +251,8 @@ qint64 GstEngine::length_nanosec() const { } Engine::State GstEngine::state() const { - if (!current_pipeline_) return url_.isEmpty() ? Engine::Empty : Engine::Idle; + if (!current_pipeline_) + return playback_req_.url_.isEmpty() ? Engine::Empty : Engine::Idle; switch (current_pipeline_->state()) { case GST_STATE_NULL: @@ -368,23 +369,24 @@ void GstEngine::UpdateScope(int chunk_length) { } } -void GstEngine::StartPreloading(const QUrl& url, bool force_stop_at_end, +void GstEngine::StartPreloading(const MediaPlaybackRequest& req, + bool force_stop_at_end, qint64 beginning_nanosec, qint64 end_nanosec) { EnsureInitialised(); // No crossfading, so we can just queue the new URL in the existing // pipeline and get gapless playback (hopefully) if (current_pipeline_) - current_pipeline_->SetNextUrl(url, beginning_nanosec, + current_pipeline_->SetNextReq(req, beginning_nanosec, force_stop_at_end ? end_nanosec : 0); } -bool GstEngine::Load(const QUrl& url, Engine::TrackChangeFlags change, - bool force_stop_at_end, quint64 beginning_nanosec, - qint64 end_nanosec) { +bool GstEngine::Load(const MediaPlaybackRequest& req, + Engine::TrackChangeFlags change, bool force_stop_at_end, + quint64 beginning_nanosec, qint64 end_nanosec) { EnsureInitialised(); - Engine::Base::Load(url, change, force_stop_at_end, beginning_nanosec, + Engine::Base::Load(req, change, force_stop_at_end, beginning_nanosec, end_nanosec); bool crossfade = @@ -397,7 +399,7 @@ bool GstEngine::Load(const QUrl& url, Engine::TrackChangeFlags change, !crossfade_same_album_) crossfade = false; - if (!crossfade && current_pipeline_ && current_pipeline_->url() == url && + if (!crossfade && current_pipeline_ && current_pipeline_->url() == req.url_ && change & Engine::Auto) { // We're not crossfading, and the pipeline is already playing the URI we // want, so just do nothing. @@ -405,7 +407,7 @@ bool GstEngine::Load(const QUrl& url, Engine::TrackChangeFlags change, } shared_ptr pipeline = - CreatePipeline(url, force_stop_at_end ? end_nanosec : 0); + CreatePipeline(req, force_stop_at_end ? end_nanosec : 0); if (!pipeline) return false; if (crossfade) StartFadeout(); @@ -505,13 +507,14 @@ void GstEngine::PlayDone(QFuture future, emit StateChanged(Engine::Playing); // we've successfully started playing a media stream with this url - emit ValidSongRequested(url_); + emit ValidSongRequested(playback_req_.url_); } void GstEngine::Stop(bool stop_after) { StopTimers(); - url_ = QUrl(); // To ensure we return Empty from state() + playback_req_ = + MediaPlaybackRequest(); // To ensure we return Empty from state() beginning_nanosec_ = end_nanosec_ = 0; // Check if we started a fade out. If it isn't finished yet and the user @@ -695,14 +698,13 @@ void GstEngine::HandlePipelineError(int pipeline_id, const QString& message, // try to reload the URL in case of a drop of the connection if (domain == GST_RESOURCE_ERROR && error_code == GST_RESOURCE_ERROR_SEEK) { - if (Load(url_, 0, false, 0, 0)) { - + if (Load(playback_req_, 0, false, 0, 0)) { current_pipeline_->SetState(GST_STATE_PLAYING); return; } - qLog(Warning) << "Attempt to reload " << url_ << " failed"; + qLog(Warning) << "Attempt to reload " << playback_req_.url_ << " failed"; } current_pipeline_.reset(); @@ -710,7 +712,7 @@ void GstEngine::HandlePipelineError(int pipeline_id, const QString& message, BufferingFinished(); emit StateChanged(Engine::Error); // unable to play media stream with this url - emit InvalidSongRequested(url_); + emit InvalidSongRequested(playback_req_.url_); // TODO: the types of errors listed below won't be shown to user - they will // get logged and the current song will be skipped; instead of maintaining @@ -822,21 +824,21 @@ shared_ptr GstEngine::CreatePipeline() { return ret; } -shared_ptr GstEngine::CreatePipeline(const QUrl& url, - qint64 end_nanosec) { +shared_ptr GstEngine::CreatePipeline( + const MediaPlaybackRequest& req, qint64 end_nanosec) { shared_ptr ret = CreatePipeline(); - if (url.scheme() == "hypnotoad") { + if (req.url_.scheme() == "hypnotoad") { ret->InitFromString(kHypnotoadPipeline); return ret; } - if (url.scheme() == "enterprise") { + if (req.url_.scheme() == "enterprise") { ret->InitFromString(kEnterprisePipeline); return ret; } - if (!ret->InitFromUrl(url, end_nanosec)) ret.reset(); + if (!ret->InitFromReq(req, end_nanosec)) ret.reset(); return ret; } @@ -885,7 +887,7 @@ int GstEngine::AddBackgroundStream(const QUrl& url) { return -1; } pipeline->SetVolume(30); - pipeline->SetNextUrl(url, 0, 0); + pipeline->SetNextReq(url, 0, 0); return AddBackgroundStream(pipeline); } @@ -895,7 +897,7 @@ void GstEngine::StopBackgroundStream(int id) { void GstEngine::BackgroundStreamFinished() { GstEnginePipeline* pipeline = qobject_cast(sender()); - pipeline->SetNextUrl(pipeline->url(), 0, 0); + pipeline->SetNextReq(pipeline->url(), 0, 0); } void GstEngine::SetBackgroundStreamVolume(int id, int volume) { diff --git a/src/engines/gstengine.h b/src/engines/gstengine.h index 184ec4109..a1b182e3b 100644 --- a/src/engines/gstengine.h +++ b/src/engines/gstengine.h @@ -95,9 +95,9 @@ class GstEngine : public Engine::Base, public BufferConsumer { void ConsumeBuffer(GstBuffer* buffer, int pipeline_id); public slots: - void StartPreloading(const QUrl& url, bool force_stop_at_end, + void StartPreloading(const MediaPlaybackRequest& req, bool force_stop_at_end, qint64 beginning_nanosec, qint64 end_nanosec); - bool Load(const QUrl&, Engine::TrackChangeFlags change, + bool Load(const MediaPlaybackRequest&, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec); bool Play(quint64 offset_nanosec); @@ -162,8 +162,8 @@ class GstEngine : public Engine::Base, public BufferConsumer { void StopTimers(); std::shared_ptr CreatePipeline(); - std::shared_ptr CreatePipeline(const QUrl& url, - qint64 end_nanosec); + std::shared_ptr CreatePipeline( + const MediaPlaybackRequest& req, qint64 end_nanosec); void UpdateScope(int chunk_length); diff --git a/src/engines/gstenginepipeline.cpp b/src/engines/gstenginepipeline.cpp index 335bc06a4..8acb29533 100644 --- a/src/engines/gstenginepipeline.cpp +++ b/src/engines/gstenginepipeline.cpp @@ -510,25 +510,25 @@ bool GstEnginePipeline::InitFromString(const QString& pipeline) { return gst_element_link(new_bin, audiobin_); } -bool GstEnginePipeline::InitFromUrl(const QUrl& url, qint64 end_nanosec) { +bool GstEnginePipeline::InitFromReq(const MediaPlaybackRequest& req, + qint64 end_nanosec) { pipeline_ = gst_pipeline_new("pipeline"); - if (url.scheme() == "cdda" && !url.path().isEmpty()) { + current_ = req; + if (current_.url_.scheme() == "cdda" && !current_.url_.path().isEmpty()) { // Currently, Gstreamer can't handle input CD devices inside cdda URL. So // we handle them ourself: we extract the track number and re-create an // URL with only cdda:// + the track number (which can be handled by // Gstreamer). We keep the device in mind, and we will set it later using // SourceSetupCallback - QStringList path = url.path().split('/'); - url_ = QUrl(QString("cdda://%1").arg(path.takeLast())); + QStringList path = current_.url_.path().split('/'); + current_.url_ = QUrl(QString("cdda://%1").arg(path.takeLast())); source_device_ = path.join("/"); - } else { - url_ = url; } end_offset_nanosec_ = end_nanosec; // Decode bin - if (!ReplaceDecodeBin(url_)) return false; + if (!ReplaceDecodeBin(current_.url_)) return false; return Init(); } @@ -917,13 +917,13 @@ GstPadProbeReturn GstEnginePipeline::HandoffCallback(GstPad*, if (end_time > instance->end_offset_nanosec_) { if (instance->has_next_valid_url()) { - if (instance->next_url_ == instance->url_ && + if (instance->next_.url_ == instance->current_.url_ && instance->next_beginning_offset_nanosec_ == instance->end_offset_nanosec_) { // The "next" song is actually the next segment of this file - so // cheat and keep on playing, but just tell the Engine we've moved on. instance->end_offset_nanosec_ = instance->next_end_offset_nanosec_; - instance->next_url_ = QUrl(); + instance->next_ = MediaPlaybackRequest(); instance->next_beginning_offset_nanosec_ = 0; instance->next_end_offset_nanosec_ = 0; @@ -995,8 +995,8 @@ void GstEnginePipeline::SourceDrainedCallback(GstURIDecodeBin* bin, // not start or with some offset. So just do nothing here: when the song // finished, EndOfStreamReached/TrackEnded will be emitted anyway so // NextItem will be called. - !(instance->url_.scheme() != "spotify" && - instance->next_url_.scheme() == "spotify")) { + !(instance->current_.url_.scheme() != "spotify" && + instance->next_.url_.scheme() == "spotify")) { instance->TransitionToNext(); } } @@ -1042,16 +1042,16 @@ void GstEnginePipeline::TransitionToNext() { ignore_tags_ = true; - if (!ReplaceDecodeBin(next_url_)) { - qLog(Error) << "ReplaceDecodeBin failed with " << next_url_; + if (!ReplaceDecodeBin(next_.url_)) { + qLog(Error) << "ReplaceDecodeBin failed with " << next_.url_; return; } gst_element_set_state(uridecodebin_, GST_STATE_PLAYING); MaybeLinkDecodeToAudio(); - url_ = next_url_; + current_ = next_; end_offset_nanosec_ = next_end_offset_nanosec_; - next_url_ = QUrl(); + next_ = MediaPlaybackRequest(); next_beginning_offset_nanosec_ = 0; next_end_offset_nanosec_ = 0; @@ -1093,7 +1093,7 @@ GstState GstEnginePipeline::state() const { QFuture GstEnginePipeline::SetState(GstState state) { #ifdef HAVE_SPOTIFY - if (url_.scheme() == "spotify" && !buffering_) { + if (current_.url_.scheme() == "spotify" && !buffering_) { const GstState current_state = this->state(); if (state == GST_STATE_PAUSED && current_state == GST_STATE_PLAYING) { @@ -1275,9 +1275,10 @@ void GstEnginePipeline::RemoveAllBufferConsumers() { buffer_consumers_.clear(); } -void GstEnginePipeline::SetNextUrl(const QUrl& url, qint64 beginning_nanosec, +void GstEnginePipeline::SetNextReq(const MediaPlaybackRequest& req, + qint64 beginning_nanosec, qint64 end_nanosec) { - next_url_ = url; + next_ = req; next_beginning_offset_nanosec_ = beginning_nanosec; next_end_offset_nanosec_ = end_nanosec; } diff --git a/src/engines/gstenginepipeline.h b/src/engines/gstenginepipeline.h index 9cba84460..7cbf15791 100644 --- a/src/engines/gstenginepipeline.h +++ b/src/engines/gstenginepipeline.h @@ -31,6 +31,7 @@ #include #include "engine_fwd.h" +#include "playbackrequest.h" class GstElementDeleter; class GstEngine; @@ -58,7 +59,7 @@ class GstEnginePipeline : public QObject { void set_sample_rate(int rate); // Creates the pipeline, returns false on error - bool InitFromUrl(const QUrl& url, qint64 end_nanosec); + bool InitFromReq(const MediaPlaybackRequest& req, qint64 end_nanosec); bool InitFromString(const QString& pipeline); // BufferConsumers get fed audio data. Thread-safe. @@ -80,12 +81,12 @@ class GstEnginePipeline : public QObject { // If this is set then it will be loaded automatically when playback finishes // for gapless playback - void SetNextUrl(const QUrl& url, qint64 beginning_nanosec, + void SetNextReq(const MediaPlaybackRequest& req, qint64 beginning_nanosec, qint64 end_nanosec); - bool has_next_valid_url() const { return next_url_.isValid(); } + bool has_next_valid_url() const { return next_.url_.isValid(); } // Get information about the music playback - QUrl url() const { return url_; } + QUrl url() const { return current_.url_; } bool is_valid() const { return valid_; } // Please note that this method (unlike GstEngine's.position()) is // multiple-section media unaware. @@ -226,8 +227,8 @@ signals: // The URL that is currently playing, and the URL that is to be preloaded // when the current track is close to finishing. - QUrl url_; - QUrl next_url_; + MediaPlaybackRequest current_; + MediaPlaybackRequest next_; // If this is > 0 then the pipeline will be forced to stop when playback goes // past this position. diff --git a/src/engines/playbackrequest.h b/src/engines/playbackrequest.h new file mode 100644 index 000000000..649529687 --- /dev/null +++ b/src/engines/playbackrequest.h @@ -0,0 +1,31 @@ +/* This file is part of Clementine. + Copyright 2020, Jim Broadus + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + +#ifndef ENGINES_PLAYBACKREQUEST_H_ +#define ENGINES_PLAYBACKREQUEST_H_ + +#include + +class MediaPlaybackRequest { + public: + MediaPlaybackRequest(const QUrl& url) : url_(url) {} + MediaPlaybackRequest() {} + + QUrl url_; +}; + +#endif // ENGINES_PLAYBACKREQUEST_H_