From 255682b057a71a04aaa49dc5989068d3a525161b Mon Sep 17 00:00:00 2001 From: David Sansome Date: Thu, 28 Apr 2011 15:10:28 +0000 Subject: [PATCH] Remove the special load behaviour from Playlist Items and instead add URL Handlers that do the same job but for all playlist item types. --- data/data.qrc | 1 + data/schema/schema-32.sql | 3 + .../digitallyimported-radio/servicebase.py | 125 ++++++++++-------- src/CMakeLists.txt | 9 +- src/core/database.cpp | 2 +- src/core/mpris1.cpp | 3 +- src/core/player.cpp | 115 +++++++++++----- src/core/player.h | 19 ++- src/core/urlhandler.cpp | 29 ++++ src/core/urlhandler.h | 76 +++++++++++ src/main.cpp | 12 +- src/playlist/playlistitem.cpp | 5 - src/playlist/playlistitem.h | 53 +------- src/radio/lastfmservice.cpp | 57 ++++---- src/radio/lastfmservice.h | 12 +- src/radio/lastfmurlhandler.cpp | 48 +++++++ src/radio/lastfmurlhandler.h | 43 ++++++ src/radio/magnatuneplaylistitem.cpp | 13 -- src/radio/magnatuneplaylistitem.h | 3 - src/radio/magnatuneservice.cpp | 12 +- src/radio/magnatuneservice.h | 3 + src/radio/magnatuneurlhandler.cpp | 28 ++++ src/radio/magnatuneurlhandler.h | 37 ++++++ src/radio/radiomodel.cpp | 9 +- src/radio/radiomodel.h | 6 +- src/radio/radioplaylistitem.cpp | 14 -- src/radio/radioplaylistitem.h | 3 - src/radio/radioservice.cpp | 9 -- src/radio/radioservice.h | 4 - src/radio/somafmservice.cpp | 66 ++------- src/radio/somafmservice.h | 9 +- src/radio/somafmurlhandler.cpp | 76 +++++++++++ src/radio/somafmurlhandler.h | 44 ++++++ src/radio/spotifyservice.cpp | 42 ++---- src/radio/spotifyservice.h | 13 +- src/radio/spotifyurlhandler.cpp | 52 ++++++++ src/radio/spotifyurlhandler.h | 37 ++++++ src/scripting/python/clementine.sip | 1 + src/scripting/python/player.sip | 3 + src/scripting/python/playlistitem.sip | 47 +------ src/scripting/python/radioservice.sip | 4 - src/scripting/python/scriptinterface.sip | 1 + src/scripting/python/urlhandler.sip | 48 +++++++ src/ui/mainwindow.cpp | 1 - 44 files changed, 796 insertions(+), 401 deletions(-) create mode 100644 data/schema/schema-32.sql create mode 100644 src/core/urlhandler.cpp create mode 100644 src/core/urlhandler.h create mode 100644 src/radio/lastfmurlhandler.cpp create mode 100644 src/radio/lastfmurlhandler.h create mode 100644 src/radio/magnatuneurlhandler.cpp create mode 100644 src/radio/magnatuneurlhandler.h create mode 100644 src/radio/somafmurlhandler.cpp create mode 100644 src/radio/somafmurlhandler.h create mode 100644 src/radio/spotifyurlhandler.cpp create mode 100644 src/radio/spotifyurlhandler.h create mode 100644 src/scripting/python/urlhandler.sip diff --git a/data/data.qrc b/data/data.qrc index 686bd8a31..3db07f0ae 100644 --- a/data/data.qrc +++ b/data/data.qrc @@ -313,5 +313,6 @@ schema/schema-29.sql schema/schema-30.sql schema/schema-31.sql + schema/schema-32.sql diff --git a/data/schema/schema-32.sql b/data/schema/schema-32.sql new file mode 100644 index 000000000..4bb37bdbe --- /dev/null +++ b/data/schema/schema-32.sql @@ -0,0 +1,3 @@ +UPDATE magnatune_songs SET filename = "magnatune://" || substr(filename, 8); + +UPDATE schema_version SET version=32; diff --git a/scripts/digitallyimported-radio/servicebase.py b/scripts/digitallyimported-radio/servicebase.py index 9279ef43b..c25fdf6fd 100644 --- a/scripts/digitallyimported-radio/servicebase.py +++ b/scripts/digitallyimported-radio/servicebase.py @@ -10,6 +10,69 @@ import json import operator import os.path +class DigitallyImportedUrlHandler(clementine.UrlHandler): + def __init__(self, service): + clementine.UrlHandler.__init__(self, service) + self.service = service + + self.last_original_url = None + self.task_id = None + + def scheme(self): + return "digitallyimported" + + def StartLoading(self, original_url): + result = clementine.UrlHandler.LoadResult() + + if self.task_id is not None: + return result + if self.service.PLAYLISTS[self.service.audio_type]["premium"] and \ + (len(self.service.username) == 0 or len(self.service.password) == 0): + self.service.StreamError.emit(self.tr("You have selected a Premium-only audio type but do not have any account details entered")) + return result + + key = original_url.host() + self.service.LoadStation(key) + + # Save the original URL so we can emit it in the finished signal later + self.last_original_url = original_url + + # Tell the user what's happening + self.task_id = clementine.task_manager.StartTask(self.tr("Loading stream")) + + result.type_ = clementine.UrlHandler.LoadResult.WillLoadAsynchronously + result.original_url_ = original_url + return result + + def LoadPlaylistFinished(self, reply): + reply.deleteLater() + + if self.task_id is None: + return + + # Stop the spinner in the status bar + clementine.task_manager.SetTaskFinished(self.task_id) + self.task_id = None + + # Try to parse the playlist + parser = clementine.PlaylistParser(clementine.library) + songs = parser.LoadFromDevice(reply) + + # Failed to get the playlist? + if len(songs) == 0: + self.service.StreamError.emit("Error loading playlist '%s'" % reply.url().toString()) + return + + result = clementine.UrlHandler.LoadResult() + result.original_url_ = self.last_original_url + + # Take the first track in the playlist + result.type_ = clementine.UrlHandler.LoadResult.TrackAvailable + result.media_url_ = songs[0].url() + + self.AsyncLoadComplete.emit(result) + + class DigitallyImportedServiceBase(clementine.RadioService): # Set these in subclasses HOMEPAGE_URL = None @@ -27,6 +90,9 @@ class DigitallyImportedServiceBase(clementine.RadioService): def __init__(self, model): clementine.RadioService.__init__(self, self.SERVICE_NAME, model) + self.url_handler = DigitallyImportedUrlHandler(self) + clementine.player.AddUrlHandler(self.url_handler) + self.network = clementine.NetworkAccessManager(self) self.path = os.path.dirname(__file__) @@ -35,7 +101,6 @@ class DigitallyImportedServiceBase(clementine.RadioService): self.password = "" self.context_index = None - self.last_original_url = None self.menu = None self.root = None self.task_id = None @@ -134,64 +199,10 @@ class DigitallyImportedServiceBase(clementine.RadioService): self.root.appendRow(item) def playlistitem_options(self): - return clementine.PlaylistItem.Options( - clementine.PlaylistItem.SpecialPlayBehaviour | - clementine.PlaylistItem.PauseDisabled) - - def StartLoading(self, original_url): - result = clementine.PlaylistItem.SpecialLoadResult() - - if self.task_id is not None: - return result - if original_url.scheme() != "digitallyimported": - return result - if self.PLAYLISTS[self.audio_type]["premium"] and \ - (len(self.username) == 0 or len(self.password) == 0): - self.StreamError.emit(self.tr("You have selected a Premium-only audio type but do not have any account details entered")) - return result - - key = original_url.host() - self.LoadStation(key) - - # Save the original URL so we can emit it in the finished signal later - self.last_original_url = original_url - - # Tell the user what's happening - self.task_id = clementine.task_manager.StartTask(self.tr("Loading stream")) - - result.type_ = clementine.PlaylistItem.SpecialLoadResult.WillLoadAsynchronously - result.original_url_ = original_url - return result + return clementine.PlaylistItem.Options(clementine.PlaylistItem.PauseDisabled) def LoadStation(self, key): raise NotImplementedError() def LoadPlaylistFinished(self): - # Get the QNetworkReply that called this slot - reply = self.sender() - reply.deleteLater() - - if self.task_id is None: - return - - # Stop the spinner in the status bar - clementine.task_manager.SetTaskFinished(self.task_id) - self.task_id = None - - # Try to parse the playlist - parser = clementine.PlaylistParser(clementine.library) - songs = parser.LoadFromDevice(reply) - - # Failed to get the playlist? - if len(songs) == 0: - self.StreamError.emit("Error loading playlist '%s'" % reply.url().toString()) - return - - result = clementine.PlaylistItem.SpecialLoadResult() - result.original_url_ = self.last_original_url - - # Take the first track in the playlist - result.type_ = clementine.PlaylistItem.SpecialLoadResult.TrackAvailable - result.media_url_ = songs[0].url() - - self.AsyncLoadFinished.emit(result) + self.url_handler.LoadPlaylistFinished(self.sender()) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c66c861e9..c9dd80677 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -88,6 +88,7 @@ set(SOURCES core/songloader.cpp core/stylesheetloader.cpp core/taskmanager.cpp + core/urlhandler.cpp core/utilities.cpp covers/albumcoverfetcher.cpp @@ -170,6 +171,7 @@ set(SOURCES radio/magnatunedownloaddialog.cpp radio/magnatuneplaylistitem.cpp radio/magnatuneservice.cpp + radio/magnatuneurlhandler.cpp radio/radiomodel.cpp radio/radioplaylistitem.cpp radio/radioservice.cpp @@ -177,6 +179,7 @@ set(SOURCES radio/radioviewcontainer.cpp radio/savedradio.cpp radio/somafmservice.cpp + radio/somafmurlhandler.cpp scripting/installscriptdialog.cpp scripting/languageengine.cpp @@ -305,6 +308,7 @@ set(HEADERS core/player.h core/songloader.h core/taskmanager.h + core/urlhandler.h covers/albumcoverfetcher.h covers/albumcoverfetchersearch.h @@ -385,6 +389,7 @@ set(HEADERS radio/radioviewcontainer.h radio/savedradio.h radio/somafmservice.h + radio/somafmurlhandler.h scripting/installscriptdialog.h scripting/languageengine.h @@ -592,6 +597,7 @@ if(HAVE_LIBLASTFM) radio/lastfmconfig.cpp radio/lastfmservice.cpp radio/lastfmstationdialog.cpp + radio/lastfmurlhandler.cpp songinfo/echonestsimilarartists.cpp songinfo/echonesttags.cpp songinfo/lastfmtrackinfoprovider.cpp @@ -618,6 +624,7 @@ if(HAVE_SPOTIFY) radio/spotifyconfig.cpp radio/spotifyserver.cpp radio/spotifyservice.cpp + radio/spotifyurlhandler.cpp ) list(APPEND HEADERS radio/spotifyconfig.h @@ -814,8 +821,8 @@ if(HAVE_SCRIPTING_PYTHON) ${CMAKE_CURRENT_BINARY_DIR}/sipclementineLibraryBackendAlbum.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementinePlaylistItemOptions.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementinePlaylistItemPtr.cpp - ${CMAKE_CURRENT_BINARY_DIR}/sipclementinePlaylistItemSpecialLoadResult.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementineTaskManagerTask.cpp + ${CMAKE_CURRENT_BINARY_DIR}/sipclementineUrlHandlerLoadResult.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100CoverSearchResult.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100Directory.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100LibraryBackendAlbum.cpp diff --git a/src/core/database.cpp b/src/core/database.cpp index b89b99185..4f5013b2a 100644 --- a/src/core/database.cpp +++ b/src/core/database.cpp @@ -32,7 +32,7 @@ #include const char* Database::kDatabaseFilename = "clementine.db"; -const int Database::kSchemaVersion = 31; +const int Database::kSchemaVersion = 32; const char* Database::kMagicAllSongsTables = "%allsongstables"; int Database::sNextConnectionId = 1; diff --git a/src/core/mpris1.cpp b/src/core/mpris1.cpp index c11935a63..34af5d89c 100644 --- a/src/core/mpris1.cpp +++ b/src/core/mpris1.cpp @@ -251,8 +251,7 @@ int Mpris1Player::GetCaps(Engine::State state) const { } } - if (playlists->active()->next_row() != -1 || - playlists->active()->current_item_options() & PlaylistItem::ContainsMultipleTracks) { + if (playlists->active()->next_row() != -1) { caps |= CAN_GO_NEXT; } if (playlists->active()->previous_row() != -1) { diff --git a/src/core/player.cpp b/src/core/player.cpp index 5b2835a16..48c7905af 100644 --- a/src/core/player.cpp +++ b/src/core/player.cpp @@ -18,6 +18,7 @@ #include "config.h" #include "player.h" #include "core/logging.h" +#include "core/urlhandler.h" #include "engines/enginebase.h" #include "engines/gstengine.h" #include "library/librarybackend.h" @@ -37,11 +38,10 @@ using boost::shared_ptr; -Player::Player(PlaylistManagerInterface* playlists, LastFMService* lastfm, - QObject* parent) +Player::Player(PlaylistManagerInterface* playlists, QObject* parent) : PlayerInterface(parent), playlists_(playlists), - lastfm_(lastfm), + lastfm_(NULL), engine_(new GstEngine), stream_change_type_(Engine::First), last_state_(Engine::Empty), @@ -71,20 +71,27 @@ void Player::Init() { SLOT(EngineMetadataReceived(Engine::SimpleMetaBundle))); engine_->SetVolume(settings_.value("volume", 50).toInt()); + +#ifdef HAVE_LIBLASTFM + lastfm_ = RadioModel::Service(); +#endif } void Player::ReloadSettings() { engine_->ReloadSettings(); } -void Player::HandleSpecialLoad(const PlaylistItem::SpecialLoadResult &result) { +void Player::HandleLoadResult(const UrlHandler::LoadResult& result) { switch (result.type_) { - case PlaylistItem::SpecialLoadResult::NoMoreTracks: + case UrlHandler::LoadResult::NoMoreTracks: + qLog(Debug) << "URL handler for" << result.original_url_ + << "said no more tracks"; + loading_async_ = QUrl(); NextItem(Engine::Auto); break; - case PlaylistItem::SpecialLoadResult::TrackAvailable: { + case UrlHandler::LoadResult::TrackAvailable: { // Might've been an async load, so check we're still on the same item int current_index = playlists_->active()->current_row(); if (current_index == -1) @@ -94,6 +101,9 @@ void Player::HandleSpecialLoad(const PlaylistItem::SpecialLoadResult &result) { if (!item || item->Url() != result.original_url_) return; + qLog(Debug) << "URL handler for" << result.original_url_ + << "returned" << result.media_url_; + engine_->Play(result.media_url_, stream_change_type_, item->Metadata().has_cue(), item->Metadata().beginning_nanosec(), @@ -104,7 +114,10 @@ void Player::HandleSpecialLoad(const PlaylistItem::SpecialLoadResult &result) { break; } - case PlaylistItem::SpecialLoadResult::WillLoadAsynchronously: + case UrlHandler::LoadResult::WillLoadAsynchronously: + qLog(Debug) << "URL handler for" << result.original_url_ + << "is loading asynchronously"; + // We'll get called again later with either NoMoreTracks or TrackAvailable loading_async_ = result.original_url_; break; @@ -122,15 +135,18 @@ void Player::NextInternal(Engine::TrackChangeFlags change) { return; } - if (playlists_->active()->current_item() && - playlists_->active()->current_item()->options() & PlaylistItem::ContainsMultipleTracks) { - // The next track is already being loaded - if (playlists_->active()->current_item()->Url() == loading_async_) - return; + if (playlists_->active()->current_item()) { + const QUrl url = playlists_->active()->current_item()->Url(); - stream_change_type_ = change; - HandleSpecialLoad(playlists_->active()->current_item()->LoadNext()); - return; + if (url_handlers_.contains(url.scheme())) { + // The next track is already being loaded + if (url == loading_async_) + return; + + stream_change_type_ = change; + HandleLoadResult(url_handlers_[url.scheme()]->LoadNext(url)); + return; + } } NextItem(change); @@ -261,16 +277,16 @@ void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle) playlists_->active()->set_current_row(index); current_item_ = playlists_->active()->current_item(); + const QUrl url = current_item_->Url(); - if (current_item_->options() & PlaylistItem::SpecialPlayBehaviour) { + if (url_handlers_.contains(url.scheme())) { // It's already loading - if (current_item_->Url() == loading_async_) + if (url == loading_async_) return; stream_change_type_ = change; - HandleSpecialLoad(current_item_->StartLoading()); - } - else { + HandleLoadResult(url_handlers_[url.scheme()]->StartLoading(url)); + } else { loading_async_ = QUrl(); engine_->Play(current_item_->Url(), change, current_item_->Metadata().has_cue(), @@ -397,8 +413,6 @@ void Player::ShowOSD() { } void Player::TrackAboutToEnd() { - const bool current_contains_multiple_tracks = - current_item_->options() & PlaylistItem::ContainsMultipleTracks; const bool has_next_row = playlists_->active()->next_row() != -1; PlaylistItemPtr next_item; @@ -419,7 +433,6 @@ void Player::TrackAboutToEnd() { // user doesn't want to crossfade between tracks on the same album, then // don't do this automatic crossfading. if (engine_->crossfade_same_album() || - current_contains_multiple_tracks || !has_next_row || !next_item || !current_item_->Metadata().IsOnSameAlbum(next_item->Metadata())) { @@ -430,26 +443,23 @@ void Player::TrackAboutToEnd() { // Crossfade is off, so start preloading the next track so we don't get a // gap between songs. - if (current_contains_multiple_tracks || !has_next_row) - return; - - if (!next_item) + if (!has_next_row || !next_item) return; QUrl url = next_item->Url(); // Get the actual track URL rather than the stream URL. - if (next_item->options() & PlaylistItem::ContainsMultipleTracks) { - PlaylistItem::SpecialLoadResult result = next_item->LoadNext(); + if (url_handlers_.contains(url.scheme())) { + UrlHandler::LoadResult result = url_handlers_[url.scheme()]->LoadNext(url); switch (result.type_) { - case PlaylistItem::SpecialLoadResult::NoMoreTracks: + case UrlHandler::LoadResult::NoMoreTracks: return; - case PlaylistItem::SpecialLoadResult::WillLoadAsynchronously: - loading_async_ = next_item->Url(); + case UrlHandler::LoadResult::WillLoadAsynchronously: + loading_async_ = url; return; - case PlaylistItem::SpecialLoadResult::TrackAvailable: + case UrlHandler::LoadResult::TrackAvailable: url = result.media_url_; break; } @@ -470,3 +480,42 @@ void Player::InvalidSongRequested(const QUrl& url) { // current item we can change the current item by skipping to the next song NextItem(Engine::Auto); } + +void Player::AddUrlHandler(UrlHandler* handler) { + const QString scheme = handler->scheme(); + + if (url_handlers_.contains(scheme)) { + qLog(Warning) << "Tried to register a URL handler for" << scheme + << "but one was already registered"; + return; + } + + qLog(Info) << "Registered URL handler for" << scheme; + url_handlers_.insert(scheme, handler); + connect(handler, SIGNAL(destroyed(QObject*)), SLOT(UrlHandlerDestroyed(QObject*))); + connect(handler, SIGNAL(AsyncLoadComplete(UrlHandler::LoadResult)), + SLOT(HandleLoadResult(UrlHandler::LoadResult))); +} + +void Player::RemoveUrlHandler(UrlHandler* handler) { + const QString scheme = url_handlers_.key(handler); + if (scheme.isEmpty()) { + qLog(Warning) << "Tried to remove a URL handler for" << handler->scheme() + << "that wasn't registered"; + return; + } + + qLog(Info) << "Removed URL handler for" << scheme; + url_handlers_.remove(scheme); + disconnect(handler, SIGNAL(destroyed(QObject*)), this, SLOT(UrlHandlerDestroyed(QObject*))); + disconnect(handler, SIGNAL(AsyncLoadComplete(UrlHandler::LoadResult)), + this, SLOT(HandleLoadResult(UrlHandler::LoadResult))); +} + +void Player::UrlHandlerDestroyed(QObject* object) { + UrlHandler* handler = static_cast(object); + const QString scheme = url_handlers_.key(handler); + if (!scheme.isEmpty()) { + url_handlers_.remove(scheme); + } +} diff --git a/src/core/player.h b/src/core/player.h index e5b1f0f6f..7d5839f9d 100644 --- a/src/core/player.h +++ b/src/core/player.h @@ -25,14 +25,15 @@ #include "config.h" #include "core/song.h" +#include "core/urlhandler.h" #include "covers/albumcoverloader.h" #include "engines/engine_fwd.h" #include "playlist/playlistitem.h" class LastFMService; +class MainWindow; class PlaylistManagerInterface; class Settings; -class MainWindow; class PlayerInterface : public QObject { @@ -49,6 +50,9 @@ public: virtual PlaylistItemPtr GetItemAt(int pos) const = 0; virtual PlaylistManagerInterface* playlists() const = 0; + virtual void AddUrlHandler(UrlHandler* handler) = 0; + virtual void RemoveUrlHandler(UrlHandler* handler) = 0; + public slots: virtual void ReloadSettings() = 0; @@ -72,7 +76,6 @@ public slots: // Moves the position of the currently playing song five seconds backwards. virtual void SeekBackward() = 0; - virtual void HandleSpecialLoad(const PlaylistItem::SpecialLoadResult& result) = 0; virtual void CurrentMetadataChanged(const Song& metadata) = 0; virtual void Mute() = 0; @@ -103,8 +106,7 @@ class Player : public PlayerInterface { Q_OBJECT public: - Player(PlaylistManagerInterface* playlists, LastFMService* lastfm, - QObject* parent = 0); + Player(PlaylistManagerInterface* playlists, QObject* parent = 0); ~Player(); void Init(); @@ -117,6 +119,9 @@ public: PlaylistItemPtr GetItemAt(int pos) const; PlaylistManagerInterface* playlists() const { return playlists_; } + void AddUrlHandler(UrlHandler* handler); + void RemoveUrlHandler(UrlHandler* handler); + public slots: void ReloadSettings(); @@ -131,7 +136,6 @@ public slots: void SeekForward(); void SeekBackward(); - void HandleSpecialLoad(const PlaylistItem::SpecialLoadResult& result); void CurrentMetadataChanged(const Song& metadata); void Mute(); @@ -154,6 +158,9 @@ public slots: void ValidSongRequested(const QUrl&); void InvalidSongRequested(const QUrl&); + void UrlHandlerDestroyed(QObject* object); + void HandleLoadResult(const UrlHandler::LoadResult& result); + private: PlaylistManagerInterface* playlists_; LastFMService* lastfm_; @@ -165,6 +172,8 @@ public slots: Engine::TrackChangeFlags stream_change_type_; Engine::State last_state_; + QMap url_handlers_; + QUrl loading_async_; int volume_before_mute_; diff --git a/src/core/urlhandler.cpp b/src/core/urlhandler.cpp new file mode 100644 index 000000000..f22578eaa --- /dev/null +++ b/src/core/urlhandler.cpp @@ -0,0 +1,29 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + 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 . +*/ + +#include "urlhandler.h" + +UrlHandler::LoadResult::LoadResult( + const QUrl& original_url, Type type, const QUrl& media_url) + : original_url_(original_url), type_(type), media_url_(media_url) +{ +} + +UrlHandler::UrlHandler(QObject* parent) + : QObject(parent) +{ +} diff --git a/src/core/urlhandler.h b/src/core/urlhandler.h new file mode 100644 index 000000000..2b6de0a8d --- /dev/null +++ b/src/core/urlhandler.h @@ -0,0 +1,76 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + 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 URLHANDLER_H +#define URLHANDLER_H + +#include +#include + +class UrlHandler : public QObject { + Q_OBJECT + +public: + UrlHandler(QObject* parent = 0); + + // The URL scheme that this handler handles. + virtual QString scheme() const = 0; + + // Returned by StartLoading() and LoadNext(), indicates what the player + // should do when it wants to load a URL. + struct LoadResult { + enum Type { + // There wasn't a track available, and the player should move on to the + // next playlist item. + NoMoreTracks, + + // There might be another track available but the handler needs to do some + // work (eg. fetching a remote playlist) to find out. AsyncLoadComplete + // will be emitted later with the same original_url. + WillLoadAsynchronously, + + // There was a track available. Its url is in media_url. + TrackAvailable, + }; + + LoadResult(const QUrl& original_url = QUrl(), + Type type = NoMoreTracks, + const QUrl& media_url = QUrl()); + + // The url that the playlist item has in Url(). + // Might be something unplayable like lastfm://... + QUrl original_url_; + + Type type_; + + // The actual url to something that gstreamer can play. + QUrl media_url_; + }; + + // Called by the Player when a song starts loading - gives the handler + // a chance to do something clever to get a playable track. + virtual LoadResult StartLoading(const QUrl& url) { return LoadResult(url); } + + // Called by the player when a song finishes - gives the handler a chance to + // get another track to play. + virtual LoadResult LoadNext(const QUrl& url) { return LoadResult(url); } + +signals: + void AsyncLoadComplete(const UrlHandler::LoadResult& result); +}; + +#endif // URLHANDLER_H diff --git a/src/main.cpp b/src/main.cpp index 85466b047..087edd225 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -374,21 +374,13 @@ int main(int argc, char *argv[]) { database->Start(true); TaskManager task_manager; PlaylistManager playlists(&task_manager, NULL); - RadioModel radio_model(database.get(), &task_manager, NULL); + Player player(&playlists); + RadioModel radio_model(database.get(), &task_manager, &player, NULL); // Initialize the repository of cover providers to avoid race conditions // later CoverProviders::instance(); - // Get the last.fm service if it's available - LastFMService* lastfm_service = NULL; -#ifdef HAVE_LIBLASTFM - lastfm_service = RadioModel::Service(); -#endif - - // Create the player - Player player(&playlists, lastfm_service); - // Create the tray icon and OSD scoped_ptr tray_icon(SystemTrayIcon::CreateSystemTrayIcon()); OSD osd(tray_icon.get()); diff --git a/src/playlist/playlistitem.cpp b/src/playlist/playlistitem.cpp index 9742281d9..a532489ef 100644 --- a/src/playlist/playlistitem.cpp +++ b/src/playlist/playlistitem.cpp @@ -30,11 +30,6 @@ #include #include -PlaylistItem::SpecialLoadResult::SpecialLoadResult( - Type type, const QUrl& original_url, const QUrl& media_url) - : type_(type), original_url_(original_url), media_url_(media_url) -{ -} PlaylistItem* PlaylistItem::NewFromType(const QString& type) { if (type == "Library") diff --git a/src/playlist/playlistitem.h b/src/playlist/playlistitem.h index 5d806334f..c859b138b 100644 --- a/src/playlist/playlistitem.h +++ b/src/playlist/playlistitem.h @@ -41,55 +41,14 @@ class PlaylistItem : public boost::enable_shared_from_this { enum Option { Default = 0x00, - // The URL returned by Url() isn't the actual URL of the music - the - // item needs to do something special before it can get an actual URL. - // Causes StartLoading() to get called when the user wants to play. - SpecialPlayBehaviour = 0x01, - - // This item might be able to provide another track after one finishes, for - // example in a radio stream. Causes LoadNext() to get called when the - // next URL is required. - ContainsMultipleTracks = 0x02, - // Disables the "pause" action. - PauseDisabled = 0x04, + PauseDisabled = 0x01, // Enables the last.fm "ban" action. - LastFMControls = 0x08, + LastFMControls = 0x02, }; Q_DECLARE_FLAGS(Options, Option); - // Returned by StartLoading() and LoadNext(), indicates what the player - // should do when it wants to load a playlist item that is marked - // SpecialPlayBehaviour or ContainsMultipleTracks. - struct SpecialLoadResult { - enum Type { - // There wasn't a track available, and the player should move on to the - // next playlist item. - NoMoreTracks, - - // There might be another track available, something will call the - // player's HandleSpecialLoad() slot later with the same original_url. - WillLoadAsynchronously, - - // There was a track available. Its url is in media_url. - TrackAvailable, - }; - - SpecialLoadResult(Type type = NoMoreTracks, - const QUrl& original_url = QUrl(), - const QUrl& media_url = QUrl()); - - Type type_; - - // The url that the playlist items has in Url(). - // Might be something unplayable like lastfm://... - QUrl original_url_; - - // The actual url to something that gstreamer can play. - QUrl media_url_; - }; - virtual QString type() const { return type_; } virtual Options options() const { return Default; } @@ -102,14 +61,6 @@ class PlaylistItem : public boost::enable_shared_from_this { virtual Song Metadata() const = 0; virtual QUrl Url() const = 0; - // Called by the Player if SpecialPlayBehaviour is set - gives the playlist - // item a chance to do something clever to get a playable track. - virtual SpecialLoadResult StartLoading() { return SpecialLoadResult(); } - - // Called by the player if ContainsMultipleTracks is set - gives the playlist - // item a chance to get another track to play. - virtual SpecialLoadResult LoadNext() { return SpecialLoadResult(); } - void SetTemporaryMetadata(const Song& metadata); void ClearTemporaryMetadata(); bool HasTemporaryMetadata() const { return temp_metadata_.is_valid(); } diff --git a/src/radio/lastfmservice.cpp b/src/radio/lastfmservice.cpp index f71cfa1aa..2d8eced86 100644 --- a/src/radio/lastfmservice.cpp +++ b/src/radio/lastfmservice.cpp @@ -17,9 +17,11 @@ #include "lastfmservice.h" #include "lastfmstationdialog.h" +#include "lastfmurlhandler.h" #include "radiomodel.h" #include "radioplaylistitem.h" #include "core/logging.h" +#include "core/player.h" #include "core/song.h" #include "core/taskmanager.h" #include "ui/iconloader.h" @@ -64,6 +66,7 @@ const char* LastFMService::kTitleCustom = QT_TR_NOOP("Last.fm Custom Radio: %1") LastFMService::LastFMService(RadioModel* parent) : RadioService(kServiceName, parent), + url_handler_(new LastFMUrlHandler(this, this)), scrobbler_(NULL), already_scrobbled_(false), station_dialog_(new LastFMStationDialog), @@ -99,6 +102,8 @@ LastFMService::LastFMService(RadioModel* parent) add_artist_action_->setEnabled(false); add_tag_action_->setEnabled(false); add_custom_action_->setEnabled(false); + + model()->player()->AddUrlHandler(url_handler_); } LastFMService::~LastFMService() { @@ -356,26 +361,9 @@ QUrl LastFMService::FixupUrl(const QUrl& url) { return ret; } -PlaylistItem::SpecialLoadResult LastFMService::StartLoading(const QUrl& url) { - if (url.scheme() != "lastfm") - return PlaylistItem::SpecialLoadResult(); - if (!IsAuthenticated()) - return PlaylistItem::SpecialLoadResult(); - - if (!tune_task_id_) - tune_task_id_ = model()->task_manager()->StartTask(tr("Loading Last.fm radio")); - - last_url_ = url; - initial_tune_ = true; - Tune(lastfm::RadioStation(FixupUrl(url))); - - return PlaylistItem::SpecialLoadResult( - PlaylistItem::SpecialLoadResult::WillLoadAsynchronously, url); -} - -PlaylistItem::SpecialLoadResult LastFMService::LoadNext(const QUrl&) { +QUrl LastFMService::DeququeNextMediaUrl() { if (playlist_.empty()) { - return PlaylistItem::SpecialLoadResult(); + return QUrl(); } lastfm::MutableTrack track = playlist_.dequeue(); @@ -390,8 +378,7 @@ PlaylistItem::SpecialLoadResult LastFMService::LoadNext(const QUrl&) { next_metadata_ = track; StreamMetadataReady(); - return PlaylistItem::SpecialLoadResult( - PlaylistItem::SpecialLoadResult::TrackAvailable, last_url_, last_track_.url()); + return last_track_.url(); } void LastFMService::StreamMetadataReady() { @@ -413,8 +400,7 @@ void LastFMService::TunerError(lastfm::ws::Error error) { tune_task_id_ = 0; if (error == lastfm::ws::NotEnoughContent) { - emit AsyncLoadFinished(PlaylistItem::SpecialLoadResult( - PlaylistItem::SpecialLoadResult::NoMoreTracks, last_url_)); + url_handler_->TunerError(); return; } @@ -452,7 +438,7 @@ QString LastFMService::ErrorString(lastfm::ws::Error error) const { void LastFMService::TunerTrackAvailable() { if (initial_tune_) { - emit AsyncLoadFinished(LoadNext(last_url_)); + url_handler_->TunerTrackAvailable(); initial_tune_ = false; } } @@ -556,7 +542,7 @@ void LastFMService::Ban() { last_track_ = mtrack; Scrobble(); - emit AsyncLoadFinished(LoadNext(last_url_)); + model()->player()->Next(); } void LastFMService::ShowContextMenu(const QModelIndex& index, const QPoint &global_pos) { @@ -792,8 +778,7 @@ void LastFMService::FetchMoreTracksFinished() { QNetworkReply* reply = qobject_cast(sender()); if (!reply) { qLog(Warning) << "Invalid reply on radio.getPlaylist"; - emit AsyncLoadFinished(PlaylistItem::SpecialLoadResult( - PlaylistItem::SpecialLoadResult::NoMoreTracks, reply->url())); + url_handler_->TunerError(); return; } reply->deleteLater(); @@ -838,7 +823,14 @@ void LastFMService::FetchMoreTracksFinished() { TunerTrackAvailable(); } -void LastFMService::Tune(const lastfm::RadioStation& station) { +void LastFMService::Tune(const QUrl& url) { + if (!tune_task_id_) + tune_task_id_ = model()->task_manager()->StartTask(tr("Loading Last.fm radio")); + + last_url_ = url; + initial_tune_ = true; + const lastfm::RadioStation station(FixupUrl(url)); + playlist_.clear(); // Remove all the old album art URLs @@ -855,8 +847,7 @@ void LastFMService::TuneFinished() { QNetworkReply* reply = qobject_cast(sender()); if (!reply) { qLog(Warning) << "Invalid reply on radio.tune"; - emit AsyncLoadFinished(PlaylistItem::SpecialLoadResult( - PlaylistItem::SpecialLoadResult::NoMoreTracks, reply->url())); + url_handler_->TunerError(); return; } @@ -865,10 +856,8 @@ void LastFMService::TuneFinished() { } PlaylistItem::Options LastFMService::playlistitem_options() const { - return PlaylistItem::SpecialPlayBehaviour | - PlaylistItem::LastFMControls | - PlaylistItem::PauseDisabled | - PlaylistItem::ContainsMultipleTracks; + return PlaylistItem::LastFMControls | + PlaylistItem::PauseDisabled; } PlaylistItemPtr LastFMService::PlaylistItemForUrl(const QUrl& url) { diff --git a/src/radio/lastfmservice.h b/src/radio/lastfmservice.h index f45b595e1..740752a0a 100644 --- a/src/radio/lastfmservice.h +++ b/src/radio/lastfmservice.h @@ -42,12 +42,14 @@ uint qHash(const lastfm::Track& track); #include -class QAction; +class LastFMUrlHandler; +class QAction; class QNetworkAccessManager; class LastFMService : public RadioService { Q_OBJECT + friend class LastFMUrlHandler; public: LastFMService(RadioModel* parent); @@ -83,9 +85,6 @@ class LastFMService : public RadioService { void ShowContextMenu(const QModelIndex& index, const QPoint &global_pos); - PlaylistItem::SpecialLoadResult StartLoading(const QUrl& url); - PlaylistItem::SpecialLoadResult LoadNext(const QUrl& url); - PlaylistItem::Options playlistitem_options() const; void ReloadSettings(); @@ -105,6 +104,7 @@ class LastFMService : public RadioService { void UpdateSubscriberStatus(); void FetchMoreTracks(); + QUrl DeququeNextMediaUrl(); PlaylistItemPtr PlaylistItemForUrl(const QUrl& url); @@ -169,11 +169,13 @@ class LastFMService : public RadioService { const QIcon& icon, QStandardItem* parent); static QUrl FixupUrl(const QUrl& url); - void Tune(const lastfm::RadioStation& station); + void Tune(const QUrl& station); void AddSelectedToPlaylist(bool clear_first); private: + LastFMUrlHandler* url_handler_; + lastfm::Audioscrobbler* scrobbler_; lastfm::Track last_track_; lastfm::Track next_metadata_; diff --git a/src/radio/lastfmurlhandler.cpp b/src/radio/lastfmurlhandler.cpp new file mode 100644 index 000000000..5617fb9e2 --- /dev/null +++ b/src/radio/lastfmurlhandler.cpp @@ -0,0 +1,48 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + 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 . +*/ + +#include "lastfmservice.h" +#include "lastfmurlhandler.h" + +LastFMUrlHandler::LastFMUrlHandler(LastFMService* service, QObject* parent) + : UrlHandler(parent), + service_(service) { +} + +UrlHandler::LoadResult LastFMUrlHandler::StartLoading(const QUrl& url) { + if (!service_->IsAuthenticated()) + return LoadResult(); + + service_->Tune(url); + return LoadResult(url, LoadResult::WillLoadAsynchronously); +} + +void LastFMUrlHandler::TunerTrackAvailable() { + emit AsyncLoadComplete(LoadNext(service_->last_url_)); +} + +void LastFMUrlHandler::TunerError() { + emit AsyncLoadComplete(LoadResult(service_->last_url_, LoadResult::NoMoreTracks)); +} + +UrlHandler::LoadResult LastFMUrlHandler::LoadNext(const QUrl& url) { + const QUrl media_url = service_->DeququeNextMediaUrl(); + if (media_url.isEmpty()) { + return LoadResult(); + } + return LoadResult(url, LoadResult::TrackAvailable, media_url); +} diff --git a/src/radio/lastfmurlhandler.h b/src/radio/lastfmurlhandler.h new file mode 100644 index 000000000..be91a836d --- /dev/null +++ b/src/radio/lastfmurlhandler.h @@ -0,0 +1,43 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + 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 LASTFMURLHANDLER_H +#define LASTFMURLHANDLER_H + +#include "core/urlhandler.h" + +class LastFMService; + + +class LastFMUrlHandler : public UrlHandler { + friend class LastFMService; + +public: + LastFMUrlHandler(LastFMService* service, QObject* parent); + + QString scheme() const { return "lastfm"; } + LoadResult StartLoading(const QUrl& url); + LoadResult LoadNext(const QUrl& url); + + void TunerTrackAvailable(); + void TunerError(); + +private: + LastFMService* service_; +}; + +#endif // LASTFMURLHANDLER_H diff --git a/src/radio/magnatuneplaylistitem.cpp b/src/radio/magnatuneplaylistitem.cpp index e7a8ccfe8..f38bfeb68 100644 --- a/src/radio/magnatuneplaylistitem.cpp +++ b/src/radio/magnatuneplaylistitem.cpp @@ -16,7 +16,6 @@ */ #include "magnatuneplaylistitem.h" -#include "magnatuneservice.h" #include "radiomodel.h" MagnatunePlaylistItem::MagnatunePlaylistItem(const QString& type) @@ -37,18 +36,6 @@ bool MagnatunePlaylistItem::InitFromQuery(const SqlRow& query) { return song_.is_valid(); } -PlaylistItem::Options MagnatunePlaylistItem::options() const { - return SpecialPlayBehaviour; -} - QUrl MagnatunePlaylistItem::Url() const { return song_.url(); } - -PlaylistItem::SpecialLoadResult MagnatunePlaylistItem::StartLoading() { - MagnatuneService* service = RadioModel::Service(); - QUrl url(Url()); - - return SpecialLoadResult(PlaylistItem::SpecialLoadResult::TrackAvailable, - url, service->ModifyUrl(url)); -} diff --git a/src/radio/magnatuneplaylistitem.h b/src/radio/magnatuneplaylistitem.h index fadab8133..c7066ddc1 100644 --- a/src/radio/magnatuneplaylistitem.h +++ b/src/radio/magnatuneplaylistitem.h @@ -27,10 +27,7 @@ class MagnatunePlaylistItem : public LibraryPlaylistItem { bool InitFromQuery(const SqlRow& query); - Options options() const; - QUrl Url() const; - SpecialLoadResult StartLoading(); }; #endif // MAGNATUNEPLAYLISTITEM_H diff --git a/src/radio/magnatuneservice.cpp b/src/radio/magnatuneservice.cpp index c07fc87c9..bf5675b52 100644 --- a/src/radio/magnatuneservice.cpp +++ b/src/radio/magnatuneservice.cpp @@ -18,10 +18,12 @@ #include "magnatunedownloaddialog.h" #include "magnatuneplaylistitem.h" #include "magnatuneservice.h" +#include "magnatuneurlhandler.h" #include "radiomodel.h" #include "core/logging.h" #include "core/mergedproxymodel.h" #include "core/network.h" +#include "core/player.h" #include "core/song.h" #include "core/taskmanager.h" #include "library/librarymodel.h" @@ -61,6 +63,7 @@ const char* MagnatuneService::kDownloadUrl = "http://download.magnatune.com/buy/ MagnatuneService::MagnatuneService(RadioModel* parent) : RadioService(kServiceName, parent), + url_handler_(new MagnatuneUrlHandler(this, this)), context_menu_(NULL), root_(NULL), library_backend_(NULL), @@ -89,6 +92,8 @@ MagnatuneService::MagnatuneService(RadioModel* parent) library_sort_model_->setSortRole(LibraryModel::Role_SortText); library_sort_model_->setDynamicSortFilter(true); library_sort_model_->sort(0); + + model()->player()->AddUrlHandler(url_handler_); } MagnatuneService::~MagnatuneService() { @@ -205,9 +210,13 @@ Song MagnatuneService::ReadTrack(QXmlStreamReader& reader) { if (name == "year") song.set_year(value.toInt()); if (name == "magnatunegenres") song.set_genre(value.section(',', 0, 0)); if (name == "seconds") song.set_length_nanosec(value.toInt() * kNsecPerSec); - if (name == "url") song.set_url(QUrl(value)); if (name == "cover_small") song.set_art_automatic(value); if (name == "albumsku") song.set_comment(value); + if (name == "url") { + QUrl url(value); + url.setScheme("magnatune"); + song.set_url(url); + } } } @@ -290,6 +299,7 @@ void MagnatuneService::Homepage() { QUrl MagnatuneService::ModifyUrl(const QUrl& url) const { QUrl ret(url); + ret.setScheme("http"); switch(membership_) { case Membership_None: diff --git a/src/radio/magnatuneservice.h b/src/radio/magnatuneservice.h index b23fed48b..51fee4c4e 100644 --- a/src/radio/magnatuneservice.h +++ b/src/radio/magnatuneservice.h @@ -28,6 +28,7 @@ class QMenu; class LibraryBackend; class LibraryModel; +class MagnatuneUrlHandler; class MagnatuneService : public RadioService { Q_OBJECT @@ -106,6 +107,8 @@ class MagnatuneService : public RadioService { Song ReadTrack(QXmlStreamReader& reader); private: + MagnatuneUrlHandler* url_handler_; + QMenu* context_menu_; QModelIndex context_item_; QStandardItem* root_; diff --git a/src/radio/magnatuneurlhandler.cpp b/src/radio/magnatuneurlhandler.cpp new file mode 100644 index 000000000..86785557a --- /dev/null +++ b/src/radio/magnatuneurlhandler.cpp @@ -0,0 +1,28 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + 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 . +*/ + +#include "magnatuneservice.h" +#include "magnatuneurlhandler.h" + +MagnatuneUrlHandler::MagnatuneUrlHandler(MagnatuneService* service, QObject* parent) + : UrlHandler(parent), + service_(service) { +} + +UrlHandler::LoadResult MagnatuneUrlHandler::StartLoading(const QUrl& url) { + return LoadResult(url, LoadResult::TrackAvailable, service_->ModifyUrl(url)); +} diff --git a/src/radio/magnatuneurlhandler.h b/src/radio/magnatuneurlhandler.h new file mode 100644 index 000000000..73d9dcb92 --- /dev/null +++ b/src/radio/magnatuneurlhandler.h @@ -0,0 +1,37 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + 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 MAGNATUNEURLHANDLER_H +#define MAGNATUNEURLHANDLER_H + +#include "core/urlhandler.h" + +class MagnatuneService; + + +class MagnatuneUrlHandler : public UrlHandler { +public: + MagnatuneUrlHandler(MagnatuneService* service, QObject* parent); + + QString scheme() const { return "magnatune"; } + LoadResult StartLoading(const QUrl& url); + +private: + MagnatuneService* service_; +}; + +#endif // MAGNATUNEURLHANDLER_H diff --git a/src/radio/radiomodel.cpp b/src/radio/radiomodel.cpp index 4e13a701c..8ed753564 100644 --- a/src/radio/radiomodel.cpp +++ b/src/radio/radiomodel.cpp @@ -39,11 +39,13 @@ QMap* RadioModel::sServices = NULL; RadioModel::RadioModel(BackgroundThread* db_thread, - TaskManager* task_manager, QObject* parent) + TaskManager* task_manager, PlayerInterface* player, + QObject* parent) : QStandardItemModel(parent), db_thread_(db_thread), merged_model_(new MergedProxyModel(this)), - task_manager_(task_manager) + task_manager_(task_manager), + player_(player) { if (!sServices) { sServices = new QMap; @@ -76,10 +78,9 @@ void RadioModel::AddService(RadioService *service) { root->setData(QVariant::fromValue(service), Role_Service); invisibleRootItem()->appendRow(root); - qDebug() << "Adding:" << service->name(); + qLog(Debug) << "Adding radio service:" << service->name(); sServices->insert(service->name(), service); - connect(service, SIGNAL(AsyncLoadFinished(PlaylistItem::SpecialLoadResult)), SIGNAL(AsyncLoadFinished(PlaylistItem::SpecialLoadResult))); connect(service, SIGNAL(StreamError(QString)), SIGNAL(StreamError(QString))); connect(service, SIGNAL(StreamMetadataFound(QUrl,Song)), SIGNAL(StreamMetadataFound(QUrl,Song))); connect(service, SIGNAL(OpenSettingsAtPage(SettingsDialog::Page)), SIGNAL(OpenSettingsAtPage(SettingsDialog::Page))); diff --git a/src/radio/radiomodel.h b/src/radio/radiomodel.h index f88b1eff5..f61625260 100644 --- a/src/radio/radiomodel.h +++ b/src/radio/radiomodel.h @@ -26,6 +26,7 @@ class Database; class MergedProxyModel; +class PlayerInterface; class RadioService; class SettingsDialog; class TaskManager; @@ -39,7 +40,7 @@ class RadioModel : public QStandardItemModel { public: RadioModel(BackgroundThread* db_thread, TaskManager* task_manager, - QObject* parent = 0); + PlayerInterface* player, QObject* parent = 0); enum Role { // Services can use this role to distinguish between different types of @@ -132,9 +133,9 @@ public: BackgroundThread* db_thread() const { return db_thread_; } MergedProxyModel* merged_model() const { return merged_model_; } TaskManager* task_manager() const { return task_manager_; } + PlayerInterface* player() const { return player_; } signals: - void AsyncLoadFinished(const PlaylistItem::SpecialLoadResult& result); void StreamError(const QString& message); void StreamMetadataFound(const QUrl& original_url, const Song& song); void OpenSettingsAtPage(SettingsDialog::Page); @@ -149,6 +150,7 @@ private: BackgroundThread* db_thread_; MergedProxyModel* merged_model_; TaskManager* task_manager_; + PlayerInterface* player_; }; #endif // RADIOMODEL_H diff --git a/src/radio/radioplaylistitem.cpp b/src/radio/radioplaylistitem.cpp index 5670549c0..861ff30a2 100644 --- a/src/radio/radioplaylistitem.cpp +++ b/src/radio/radioplaylistitem.cpp @@ -93,20 +93,6 @@ Song RadioPlaylistItem::Metadata() const { return metadata_; } -PlaylistItem::SpecialLoadResult RadioPlaylistItem::StartLoading() { - RadioService* s = service(); - if (!s) - return SpecialLoadResult(); - return s->StartLoading(Url()); -} - -PlaylistItem::SpecialLoadResult RadioPlaylistItem::LoadNext() { - RadioService* s = service(); - if (!s) - return SpecialLoadResult(); - return s->LoadNext(Url()); -} - QUrl RadioPlaylistItem::Url() const { return metadata_.url(); } diff --git a/src/radio/radioplaylistitem.h b/src/radio/radioplaylistitem.h index d264f1b1b..c5fdecfba 100644 --- a/src/radio/radioplaylistitem.h +++ b/src/radio/radioplaylistitem.h @@ -38,9 +38,6 @@ class RadioPlaylistItem : public PlaylistItem { Song Metadata() const; QUrl Url() const; - SpecialLoadResult StartLoading(); - SpecialLoadResult LoadNext(); - protected: QVariant DatabaseValue(DatabaseColumn) const; Song DatabaseSongMetadata() const { return metadata_; } diff --git a/src/radio/radioservice.cpp b/src/radio/radioservice.cpp index 83b267ed6..79629c6c7 100644 --- a/src/radio/radioservice.cpp +++ b/src/radio/radioservice.cpp @@ -76,15 +76,6 @@ QAction* RadioService::GetOpenInNewPlaylistAction() { return open_in_new_playlist_; } -PlaylistItem::SpecialLoadResult RadioService::StartLoading(const QUrl &url) { - return PlaylistItem::SpecialLoadResult( - PlaylistItem::SpecialLoadResult::TrackAvailable, url, url); -} - -PlaylistItem::SpecialLoadResult RadioService::LoadNext(const QUrl&) { - return PlaylistItem::SpecialLoadResult(); -} - void RadioService::AddItemToPlaylist(const QModelIndex& index, AddMode add_mode) { AddItemsToPlaylist(QModelIndexList() << index, add_mode); } diff --git a/src/radio/radioservice.h b/src/radio/radioservice.h index 0facb67b4..cb42de7b7 100644 --- a/src/radio/radioservice.h +++ b/src/radio/radioservice.h @@ -48,9 +48,6 @@ public: virtual void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos) { Q_UNUSED(index); Q_UNUSED(global_pos); } - virtual PlaylistItem::SpecialLoadResult StartLoading(const QUrl& url); - virtual PlaylistItem::SpecialLoadResult LoadNext(const QUrl& url); - virtual PlaylistItem::Options playlistitem_options() const { return PlaylistItem::Default; } virtual QWidget* HeaderWidget() const { return NULL; } @@ -60,7 +57,6 @@ public: virtual QString Icon() { return QString(); } signals: - void AsyncLoadFinished(const PlaylistItem::SpecialLoadResult& result); void StreamError(const QString& message); void StreamMetadataFound(const QUrl& original_url, const Song& song); void OpenSettingsAtPage(SettingsDialog::Page page); diff --git a/src/radio/somafmservice.cpp b/src/radio/somafmservice.cpp index c2ad1ada8..652dfee8a 100644 --- a/src/radio/somafmservice.cpp +++ b/src/radio/somafmservice.cpp @@ -16,20 +16,20 @@ */ #include "somafmservice.h" +#include "somafmurlhandler.h" #include "radiomodel.h" #include "core/logging.h" #include "core/network.h" +#include "core/player.h" #include "core/taskmanager.h" #include "ui/iconloader.h" +#include +#include +#include #include #include #include -#include -#include -#include -#include -#include #include const char* SomaFMService::kServiceName = "SomaFM"; @@ -38,12 +38,13 @@ const char* SomaFMService::kHomepage = "http://somafm.com"; SomaFMService::SomaFMService(RadioModel* parent) : RadioService(kServiceName, parent), + url_handler_(new SomaFMUrlHandler(this, this)), root_(NULL), context_menu_(NULL), get_channels_task_id_(0), - get_stream_task_id_(0), network_(new NetworkAccessManager(this)) { + model()->player()->AddUrlHandler(url_handler_); } SomaFMService::~SomaFMService() { @@ -79,51 +80,6 @@ void SomaFMService::ShowContextMenu(const QModelIndex& index, const QPoint& glob context_menu_->popup(global_pos); } -PlaylistItem::SpecialLoadResult SomaFMService::StartLoading(const QUrl& url) { - // Load the playlist - QNetworkRequest request = QNetworkRequest(url); - request.setRawHeader("User-Agent", QString("%1 %2").arg( - QCoreApplication::applicationName(), QCoreApplication::applicationVersion()).toUtf8()); - - QNetworkReply* reply = network_->get(request); - connect(reply, SIGNAL(finished()), SLOT(LoadPlaylistFinished())); - - if (!get_stream_task_id_) - get_stream_task_id_ = model()->task_manager()->StartTask(tr("Loading stream")); - - return PlaylistItem::SpecialLoadResult( - PlaylistItem::SpecialLoadResult::WillLoadAsynchronously, url); -} - -void SomaFMService::LoadPlaylistFinished() { - QNetworkReply* reply = qobject_cast(sender()); - model()->task_manager()->SetTaskFinished(get_stream_task_id_); - get_stream_task_id_ = 0; - - QUrl original_url(reply->url()); - - if (reply->error() != QNetworkReply::NoError) { - // TODO: Error handling - qLog(Error) << reply->errorString(); - emit AsyncLoadFinished(PlaylistItem::SpecialLoadResult( - PlaylistItem::SpecialLoadResult::NoMoreTracks, original_url)); - return; - } - - // TODO: Replace with some more robust .pls parsing :( - QTemporaryFile temp_file; - temp_file.open(); - temp_file.write(reply->readAll()); - temp_file.flush(); - - QSettings s(temp_file.fileName(), QSettings::IniFormat); - s.beginGroup("playlist"); - - emit AsyncLoadFinished(PlaylistItem::SpecialLoadResult( - PlaylistItem::SpecialLoadResult::TrackAvailable, - original_url, s.value("File1").toString())); -} - void SomaFMService::RefreshChannels() { QNetworkReply* reply = network_->get(QNetworkRequest(QUrl(kChannelListUrl))); connect(reply, SIGNAL(finished()), SLOT(RefreshChannelsFinished())); @@ -181,7 +137,10 @@ void SomaFMService::ReadChannel(QXmlStreamReader& reader) { } else if (reader.name() == "dj") { song.set_artist(reader.readElementText()); } else if (reader.name() == "fastpls" && reader.attributes().value("format") == "mp3") { - song.set_url(QUrl(reader.readElementText())); + QUrl url(reader.readElementText()); + url.setScheme("somafm"); + + song.set_url(url); } else { ConsumeElement(reader); } @@ -216,6 +175,5 @@ QModelIndex SomaFMService::GetCurrentIndex() { } PlaylistItem::Options SomaFMService::playlistitem_options() const { - return PlaylistItem::SpecialPlayBehaviour | - PlaylistItem::PauseDisabled; + return PlaylistItem::PauseDisabled; } diff --git a/src/radio/somafmservice.h b/src/radio/somafmservice.h index ddb0fb185..d612b560d 100644 --- a/src/radio/somafmservice.h +++ b/src/radio/somafmservice.h @@ -22,6 +22,8 @@ #include "radioservice.h" +class SomaFMUrlHandler; + class QNetworkAccessManager; class QMenu; @@ -46,7 +48,8 @@ class SomaFMService : public RadioService { void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos); PlaylistItem::Options playlistitem_options() const; - PlaylistItem::SpecialLoadResult StartLoading(const QUrl& url); + + QNetworkAccessManager* network() const { return network_; } protected: QModelIndex GetCurrentIndex(); @@ -54,7 +57,6 @@ class SomaFMService : public RadioService { private slots: void RefreshChannels(); void RefreshChannelsFinished(); - void LoadPlaylistFinished(); void Homepage(); @@ -63,12 +65,13 @@ class SomaFMService : public RadioService { void ConsumeElement(QXmlStreamReader& reader); private: + SomaFMUrlHandler* url_handler_; + QStandardItem* root_; QMenu* context_menu_; QStandardItem* context_item_; int get_channels_task_id_; - int get_stream_task_id_; QNetworkAccessManager* network_; }; diff --git a/src/radio/somafmurlhandler.cpp b/src/radio/somafmurlhandler.cpp new file mode 100644 index 000000000..f93dc025c --- /dev/null +++ b/src/radio/somafmurlhandler.cpp @@ -0,0 +1,76 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + 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 . +*/ + +#include "radiomodel.h" +#include "somafmservice.h" +#include "somafmurlhandler.h" +#include "core/logging.h" +#include "core/taskmanager.h" + +#include +#include +#include +#include + +SomaFMUrlHandler::SomaFMUrlHandler(SomaFMService* service, QObject* parent) + : UrlHandler(parent), + service_(service), + task_id_(0) +{ +} + +UrlHandler::LoadResult SomaFMUrlHandler::StartLoading(const QUrl& url) { + QUrl playlist_url = url; + playlist_url.setScheme("http"); + + // Load the playlist + QNetworkReply* reply = service_->network()->get(QNetworkRequest(playlist_url)); + connect(reply, SIGNAL(finished()), SLOT(LoadPlaylistFinished())); + + if (!task_id_) + task_id_ = service_->model()->task_manager()->StartTask(tr("Loading stream")); + + return LoadResult(url, LoadResult::WillLoadAsynchronously); +} + +void SomaFMUrlHandler::LoadPlaylistFinished() { + QNetworkReply* reply = qobject_cast(sender()); + service_->model()->task_manager()->SetTaskFinished(task_id_); + task_id_ = 0; + + QUrl original_url(reply->url()); + original_url.setScheme("somafm"); + + if (reply->error() != QNetworkReply::NoError) { + // TODO: Error handling + qLog(Error) << reply->errorString(); + emit AsyncLoadComplete(LoadResult(original_url, LoadResult::NoMoreTracks)); + return; + } + + // TODO: Replace with some more robust .pls parsing :( + QTemporaryFile temp_file; + temp_file.open(); + temp_file.write(reply->readAll()); + temp_file.flush(); + + QSettings s(temp_file.fileName(), QSettings::IniFormat); + s.beginGroup("playlist"); + + emit AsyncLoadComplete(LoadResult(original_url, LoadResult::TrackAvailable, + s.value("File1").toString())); +} diff --git a/src/radio/somafmurlhandler.h b/src/radio/somafmurlhandler.h new file mode 100644 index 000000000..fb794dc98 --- /dev/null +++ b/src/radio/somafmurlhandler.h @@ -0,0 +1,44 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + 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 SOMAFMURLHANDLER_H +#define SOMAFMURLHANDLER_H + +#include "core/urlhandler.h" + +class SomaFMService; + + +class SomaFMUrlHandler : public UrlHandler { + Q_OBJECT + +public: + SomaFMUrlHandler(SomaFMService* service, QObject* parent); + + QString scheme() const { return "somafm"; } + LoadResult StartLoading(const QUrl& url); + +private slots: + void LoadPlaylistFinished(); + +private: + SomaFMService* service_; + + int task_id_; +}; + +#endif // SOMAFMURLHANDLER_H diff --git a/src/radio/spotifyservice.cpp b/src/radio/spotifyservice.cpp index 267f9a3bb..4835b6930 100644 --- a/src/radio/spotifyservice.cpp +++ b/src/radio/spotifyservice.cpp @@ -1,9 +1,11 @@ #include "radiomodel.h" #include "spotifyserver.h" #include "spotifyservice.h" +#include "spotifyurlhandler.h" #include "core/database.h" #include "core/logging.h" #include "core/mergedproxymodel.h" +#include "core/player.h" #include "core/taskmanager.h" #include "library/library.h" #include "library/librarybackend.h" @@ -17,8 +19,6 @@ #include #include #include -#include -#include const char* SpotifyService::kServiceName = "Spotify"; const char* SpotifyService::kSettingsGroup = "Spotify"; @@ -28,6 +28,7 @@ const char* SpotifyService::kSearchFtsTable = "spotify_search_songs_fts"; SpotifyService::SpotifyService(RadioModel* parent) : RadioService(kServiceName, parent), server_(NULL), + url_handler_(new SpotifyUrlHandler(this, this)), blob_process_(NULL), root_(NULL), search_results_(NULL), @@ -54,6 +55,8 @@ SpotifyService::SpotifyService(RadioModel* parent) library_sort_model_->setSortRole(LibraryModel::Role_SortText); library_sort_model_->setDynamicSortFilter(true); library_sort_model_->sort(0); + + model()->player()->AddUrlHandler(url_handler_); } SpotifyService::~SpotifyService() { @@ -311,35 +314,7 @@ void SpotifyService::SongFromProtobuf(const protobuf::Track& track, Song* song) } PlaylistItem::Options SpotifyService::playlistitem_options() const { - return PlaylistItem::SpecialPlayBehaviour | - PlaylistItem::PauseDisabled; -} - -PlaylistItem::SpecialLoadResult SpotifyService::StartLoading(const QUrl& url) { - // Pick an unused local port. There's a possible race condition here - - // something else might grab the port before gstreamer does. - quint16 port = 0; - - { - QTcpServer server; - server.listen(QHostAddress::LocalHost); - port = server.serverPort(); - } - - if (port == 0) { - qLog(Warning) << "Couldn't pick an unused port"; - return PlaylistItem::SpecialLoadResult(); - } - - // Tell Spotify to start sending to this port - EnsureServerCreated(); - server_->StartPlayback(url.toString(), port); - - // Tell gstreamer to listen on this port - return PlaylistItem::SpecialLoadResult( - PlaylistItem::SpecialLoadResult::TrackAvailable, - url, - QUrl("tcp://localhost:" + QString::number(port))); + return PlaylistItem::PauseDisabled; } void SpotifyService::EnsureMenuCreated() { @@ -398,3 +373,8 @@ void SpotifyService::SearchResults(const protobuf::SearchResponse& response) { library_backend_->DeleteAll(); library_backend_->AddOrUpdateSongs(songs); } + +SpotifyServer* SpotifyService::server() const { + const_cast(this)->EnsureServerCreated(); + return server_; +} diff --git a/src/radio/spotifyservice.h b/src/radio/spotifyservice.h index 925fd5df0..b4f233488 100644 --- a/src/radio/spotifyservice.h +++ b/src/radio/spotifyservice.h @@ -13,6 +13,7 @@ class LibraryBackend; class LibraryModel; class SpotifyServer; +class SpotifyUrlHandler; class QMenu; class QSortFilterProxyModel; @@ -37,20 +38,21 @@ public: Role_UserPlaylistIndex = RadioModel::RoleCount, }; + static const char* kServiceName; + static const char* kSettingsGroup; + static const char* kSearchSongsTable; + static const char* kSearchFtsTable; + virtual QStandardItem* CreateRootItem(); virtual void LazyPopulate(QStandardItem* parent); void Login(const QString& username, const QString& password); - PlaylistItem::SpecialLoadResult StartLoading(const QUrl& url); PlaylistItem::Options playlistitem_options() const; QWidget* HeaderWidget() const; - static const char* kServiceName; - static const char* kSettingsGroup; - static const char* kSearchSongsTable; - static const char* kSearchFtsTable; + SpotifyServer* server() const; signals: void LoginFinished(bool success); @@ -81,6 +83,7 @@ private slots: private: SpotifyServer* server_; + SpotifyUrlHandler* url_handler_; QString blob_path_; QProcess* blob_process_; diff --git a/src/radio/spotifyurlhandler.cpp b/src/radio/spotifyurlhandler.cpp new file mode 100644 index 000000000..0c4b48427 --- /dev/null +++ b/src/radio/spotifyurlhandler.cpp @@ -0,0 +1,52 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + 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 . +*/ + +#include "spotifyserver.h" +#include "spotifyservice.h" +#include "spotifyurlhandler.h" +#include "core/logging.h" + +#include + +SpotifyUrlHandler::SpotifyUrlHandler(SpotifyService* service, QObject* parent) + : UrlHandler(parent), + service_(service) { +} + +UrlHandler::LoadResult SpotifyUrlHandler::StartLoading(const QUrl& url) { + // Pick an unused local port. There's a possible race condition here - + // something else might grab the port before gstreamer does. + quint16 port = 0; + + { + QTcpServer server; + server.listen(QHostAddress::LocalHost); + port = server.serverPort(); + } + + if (port == 0) { + qLog(Warning) << "Couldn't pick an unused port"; + return LoadResult(); + } + + // Tell Spotify to start sending to this port + service_->server()->StartPlayback(url.toString(), port); + + // Tell gstreamer to listen on this port + return LoadResult(url, LoadResult::TrackAvailable, + QUrl("tcp://localhost:" + QString::number(port))); +} diff --git a/src/radio/spotifyurlhandler.h b/src/radio/spotifyurlhandler.h new file mode 100644 index 000000000..ff8da6b90 --- /dev/null +++ b/src/radio/spotifyurlhandler.h @@ -0,0 +1,37 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + 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 SPOTIFYURLHANDLER_H +#define SPOTIFYURLHANDLER_H + +#include "core/urlhandler.h" + +class SpotifyService; + + +class SpotifyUrlHandler : public UrlHandler { +public: + SpotifyUrlHandler(SpotifyService* service, QObject* parent); + + QString scheme() const { return "spotify"; } + LoadResult StartLoading(const QUrl& url); + +private: + SpotifyService* service_; +}; + +#endif // SPOTIFYURLHANDLER_H diff --git a/src/scripting/python/clementine.sip b/src/scripting/python/clementine.sip index b6bce81d7..e3fa6e33a 100644 --- a/src/scripting/python/clementine.sip +++ b/src/scripting/python/clementine.sip @@ -34,6 +34,7 @@ %Include songloader.sip %Include taskmanager.sip %Include uiinterface.sip +%Include urlhandler.sip // Remember: when adding a class that inherits from QObject, add it to the list // in scriptinterface.sip as well, or else it won't cast properly when calling diff --git a/src/scripting/python/player.sip b/src/scripting/python/player.sip index c1ed6eb20..aa5136601 100644 --- a/src/scripting/python/player.sip +++ b/src/scripting/python/player.sip @@ -12,6 +12,9 @@ public: PlaylistItemPtr GetItemAt(int pos) const; PlaylistManagerInterface* playlists() const; + void AddUrlHandler(UrlHandler* handler); + void RemoveUrlHandler(UrlHandler* handler); + public slots: // Manual track change to the specified track void PlayAt(int i, Engine::TrackChangeType change, bool reshuffle); diff --git a/src/scripting/python/playlistitem.sip b/src/scripting/python/playlistitem.sip index 27835c872..fc590bf05 100644 --- a/src/scripting/python/playlistitem.sip +++ b/src/scripting/python/playlistitem.sip @@ -17,10 +17,7 @@ class PlaylistItem { Represents a single row in a playlist. Playlists in Clementine are lists of PlaylistItems. At a minimum each -PlaylistItem contains some metadata and a URL, but items may also have special -loading behaviour associated with them if playing the item is more complicated -than just loading a URL (for example, Last.fm stations have to request a -special playlist using the Last.fm API). +PlaylistItem contains some metadata and a URL. PlaylistItem is an abstract class and instances of it cannot be created directly by Python code. If you want to add items to a playlist you should use @@ -43,13 +40,6 @@ describe the item's behaviour when it is played. Valid values are: - C{Default} - no special behaviour, the L{Url()} is used directly when playing the song. - - C{SpecialPlayBehaviour} - The URL returned by Url() isn't the actual URL of - the music - the item needs to do something special before it can get an - actual URL. Causes StartLoading() to get called when the user wants to - play. - - C{ContainsMultipleTracks} - this item might be able to provide another - track after one finishes, for example in a radio stream. Causes LoadNext() - to get called when the next URL is required. - C{PauseDisabled} - disables the "pause" action. - C{LastFMControls} - enables the last.fm "ban" action. @@ -58,46 +48,11 @@ describe the item's behaviour when it is played. Valid values are: public: enum Option { Default, - SpecialPlayBehaviour, - ContainsMultipleTracks, PauseDisabled, LastFMControls, }; typedef QFlags Options; - struct SpecialLoadResult { -%Docstring -Returned by StartLoading() and LoadNext(), indicates what the player should do -when it wants to load a playlist item that is marked SpecialPlayBehaviour or -ContainsMultipleTracks. - -Valid values for the type_ field are: - - - C{NoMoreTracks} - there wasn't a track available, and the player should - move on to the next playlist item. - - C{WillLoadAsynchronously} - there might be another track available, - something will call the player's HandleSpecialLoad() slot later with the - same original_url. - - C{TrackAvailable} - There was a track available. Its url is in media_url. - -%End - - enum Type { - NoMoreTracks, - WillLoadAsynchronously, - TrackAvailable, - }; - - SpecialLoadResult(); // Workaround SIP Mercurial 3e647ed0f2a2 - SpecialLoadResult(Type type, - const QUrl& original_url = QUrl(), - const QUrl& media_url = QUrl()); - - Type type_; - QUrl original_url_; - QUrl media_url_; - }; - QString type() const; %Docstring type() -> str diff --git a/src/scripting/python/radioservice.sip b/src/scripting/python/radioservice.sip index 377d88d35..41b2e139c 100644 --- a/src/scripting/python/radioservice.sip +++ b/src/scripting/python/radioservice.sip @@ -15,9 +15,6 @@ public: virtual void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos); - virtual PlaylistItem::SpecialLoadResult StartLoading(const QUrl& url); - virtual PlaylistItem::SpecialLoadResult LoadNext(const QUrl& url); - virtual PlaylistItem::Options playlistitem_options() const; virtual QWidget* HeaderWidget() const /Transfer/; @@ -27,7 +24,6 @@ public: virtual QString Icon(); signals: - void AsyncLoadFinished(const PlaylistItem::SpecialLoadResult& result); void StreamError(const QString& message); void StreamMetadataFound(const QUrl& original_url, const Song& song); void OpenSettingsAtPage(SettingsDialog::Page page); diff --git a/src/scripting/python/scriptinterface.sip b/src/scripting/python/scriptinterface.sip index ede7712ee..a3589f2b9 100644 --- a/src/scripting/python/scriptinterface.sip +++ b/src/scripting/python/scriptinterface.sip @@ -52,6 +52,7 @@ appropriately. CLASS(SongLoader), CLASS(TaskManager), CLASS(UIInterface), + CLASS(UrlHandler), {0, 0} }; #undef CLASS diff --git a/src/scripting/python/urlhandler.sip b/src/scripting/python/urlhandler.sip new file mode 100644 index 000000000..93c2a67c4 --- /dev/null +++ b/src/scripting/python/urlhandler.sip @@ -0,0 +1,48 @@ +class UrlHandler : QObject { + +%TypeHeaderCode +#include "core/urlhandler.h" +%End + +public: + UrlHandler(QObject* parent /TransferThis/ = 0); + + // The URL scheme that this handler handles. + virtual QString scheme() const = 0; + + struct LoadResult { +%Docstring +Returned by StartLoading() and LoadNext(), indicates what the player should do +when it wants to load a URL. + +Valid values for the type_ field are: + + - C{NoMoreTracks} - there wasn't a track available, and the player should + move on to the next playlist item. + - C{WillLoadAsynchronously} - there might be another track available but the + handler needs to do some work (eg. fetching a remote playlist) to find out. + AsyncLoadComplete will be emitted later with the same original_url. + - C{TrackAvailable} - There was a track available. Its url is in media_url. + +%End + enum Type { + NoMoreTracks, + WillLoadAsynchronously, + TrackAvailable, + }; + + LoadResult(const QUrl& original_url = QUrl(), + Type type = NoMoreTracks, + const QUrl& media_url = QUrl()); + + QUrl original_url_; + Type type_; + QUrl media_url_; + }; + + virtual LoadResult StartLoading(const QUrl& url); + virtual LoadResult LoadNext(const QUrl& url); + +signals: + void AsyncLoadComplete(const UrlHandler::LoadResult& result); +}; diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index eb4894a65..4db4d6549 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -523,7 +523,6 @@ MainWindow::MainWindow( // Radio connections connect(radio_model_, SIGNAL(StreamError(QString)), SLOT(ShowErrorDialog(QString))); - connect(radio_model_, SIGNAL(AsyncLoadFinished(PlaylistItem::SpecialLoadResult)), player_, SLOT(HandleSpecialLoad(PlaylistItem::SpecialLoadResult))); connect(radio_model_, SIGNAL(StreamMetadataFound(QUrl,Song)), playlists_, SLOT(SetActiveStreamMetadata(QUrl,Song))); connect(radio_model_, SIGNAL(OpenSettingsAtPage(SettingsDialog::Page)), SLOT(OpenSettingsDialogAtPage(SettingsDialog::Page))); connect(radio_model_, SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));