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*)));