From 9ed5503ee32a7cc86074b212d90d233cc05f0fe6 Mon Sep 17 00:00:00 2001 From: Jim Broadus Date: Mon, 17 Feb 2020 22:24:07 -0800 Subject: [PATCH] Add MediaPlaybackRequest class. Add a class to wrap the URL in the playback engines. In the future, this will contain authentication information for the specified URL. It can also include the start and end time as well as other data that is currently specified along with the URL. --- src/core/player.cpp | 14 ++++++---- src/engines/enginebase.cpp | 8 +++--- src/engines/enginebase.h | 13 +++++---- src/engines/gstengine.cpp | 46 ++++++++++++++++--------------- src/engines/gstengine.h | 8 +++--- src/engines/gstenginepipeline.cpp | 37 +++++++++++++------------ src/engines/gstenginepipeline.h | 13 +++++---- src/engines/playbackrequest.h | 31 +++++++++++++++++++++ 8 files changed, 105 insertions(+), 65 deletions(-) create mode 100644 src/engines/playbackrequest.h 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_