Remove the special load behaviour from Playlist Items and instead add URL Handlers that do the same job but for all playlist item types.

This commit is contained in:
David Sansome 2011-04-28 15:10:28 +00:00
parent 044a97720c
commit 255682b057
44 changed files with 796 additions and 401 deletions

View File

@ -313,5 +313,6 @@
<file>schema/schema-29.sql</file> <file>schema/schema-29.sql</file>
<file>schema/schema-30.sql</file> <file>schema/schema-30.sql</file>
<file>schema/schema-31.sql</file> <file>schema/schema-31.sql</file>
<file>schema/schema-32.sql</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -0,0 +1,3 @@
UPDATE magnatune_songs SET filename = "magnatune://" || substr(filename, 8);
UPDATE schema_version SET version=32;

View File

@ -10,6 +10,69 @@ import json
import operator import operator
import os.path 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): class DigitallyImportedServiceBase(clementine.RadioService):
# Set these in subclasses # Set these in subclasses
HOMEPAGE_URL = None HOMEPAGE_URL = None
@ -27,6 +90,9 @@ class DigitallyImportedServiceBase(clementine.RadioService):
def __init__(self, model): def __init__(self, model):
clementine.RadioService.__init__(self, self.SERVICE_NAME, 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.network = clementine.NetworkAccessManager(self)
self.path = os.path.dirname(__file__) self.path = os.path.dirname(__file__)
@ -35,7 +101,6 @@ class DigitallyImportedServiceBase(clementine.RadioService):
self.password = "" self.password = ""
self.context_index = None self.context_index = None
self.last_original_url = None
self.menu = None self.menu = None
self.root = None self.root = None
self.task_id = None self.task_id = None
@ -134,64 +199,10 @@ class DigitallyImportedServiceBase(clementine.RadioService):
self.root.appendRow(item) self.root.appendRow(item)
def playlistitem_options(self): def playlistitem_options(self):
return clementine.PlaylistItem.Options( return clementine.PlaylistItem.Options(clementine.PlaylistItem.PauseDisabled)
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
def LoadStation(self, key): def LoadStation(self, key):
raise NotImplementedError() raise NotImplementedError()
def LoadPlaylistFinished(self): def LoadPlaylistFinished(self):
# Get the QNetworkReply that called this slot self.url_handler.LoadPlaylistFinished(self.sender())
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)

View File

@ -88,6 +88,7 @@ set(SOURCES
core/songloader.cpp core/songloader.cpp
core/stylesheetloader.cpp core/stylesheetloader.cpp
core/taskmanager.cpp core/taskmanager.cpp
core/urlhandler.cpp
core/utilities.cpp core/utilities.cpp
covers/albumcoverfetcher.cpp covers/albumcoverfetcher.cpp
@ -170,6 +171,7 @@ set(SOURCES
radio/magnatunedownloaddialog.cpp radio/magnatunedownloaddialog.cpp
radio/magnatuneplaylistitem.cpp radio/magnatuneplaylistitem.cpp
radio/magnatuneservice.cpp radio/magnatuneservice.cpp
radio/magnatuneurlhandler.cpp
radio/radiomodel.cpp radio/radiomodel.cpp
radio/radioplaylistitem.cpp radio/radioplaylistitem.cpp
radio/radioservice.cpp radio/radioservice.cpp
@ -177,6 +179,7 @@ set(SOURCES
radio/radioviewcontainer.cpp radio/radioviewcontainer.cpp
radio/savedradio.cpp radio/savedradio.cpp
radio/somafmservice.cpp radio/somafmservice.cpp
radio/somafmurlhandler.cpp
scripting/installscriptdialog.cpp scripting/installscriptdialog.cpp
scripting/languageengine.cpp scripting/languageengine.cpp
@ -305,6 +308,7 @@ set(HEADERS
core/player.h core/player.h
core/songloader.h core/songloader.h
core/taskmanager.h core/taskmanager.h
core/urlhandler.h
covers/albumcoverfetcher.h covers/albumcoverfetcher.h
covers/albumcoverfetchersearch.h covers/albumcoverfetchersearch.h
@ -385,6 +389,7 @@ set(HEADERS
radio/radioviewcontainer.h radio/radioviewcontainer.h
radio/savedradio.h radio/savedradio.h
radio/somafmservice.h radio/somafmservice.h
radio/somafmurlhandler.h
scripting/installscriptdialog.h scripting/installscriptdialog.h
scripting/languageengine.h scripting/languageengine.h
@ -592,6 +597,7 @@ if(HAVE_LIBLASTFM)
radio/lastfmconfig.cpp radio/lastfmconfig.cpp
radio/lastfmservice.cpp radio/lastfmservice.cpp
radio/lastfmstationdialog.cpp radio/lastfmstationdialog.cpp
radio/lastfmurlhandler.cpp
songinfo/echonestsimilarartists.cpp songinfo/echonestsimilarartists.cpp
songinfo/echonesttags.cpp songinfo/echonesttags.cpp
songinfo/lastfmtrackinfoprovider.cpp songinfo/lastfmtrackinfoprovider.cpp
@ -618,6 +624,7 @@ if(HAVE_SPOTIFY)
radio/spotifyconfig.cpp radio/spotifyconfig.cpp
radio/spotifyserver.cpp radio/spotifyserver.cpp
radio/spotifyservice.cpp radio/spotifyservice.cpp
radio/spotifyurlhandler.cpp
) )
list(APPEND HEADERS list(APPEND HEADERS
radio/spotifyconfig.h radio/spotifyconfig.h
@ -814,8 +821,8 @@ if(HAVE_SCRIPTING_PYTHON)
${CMAKE_CURRENT_BINARY_DIR}/sipclementineLibraryBackendAlbum.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementineLibraryBackendAlbum.cpp
${CMAKE_CURRENT_BINARY_DIR}/sipclementinePlaylistItemOptions.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementinePlaylistItemOptions.cpp
${CMAKE_CURRENT_BINARY_DIR}/sipclementinePlaylistItemPtr.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementinePlaylistItemPtr.cpp
${CMAKE_CURRENT_BINARY_DIR}/sipclementinePlaylistItemSpecialLoadResult.cpp
${CMAKE_CURRENT_BINARY_DIR}/sipclementineTaskManagerTask.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementineTaskManagerTask.cpp
${CMAKE_CURRENT_BINARY_DIR}/sipclementineUrlHandlerLoadResult.cpp
${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100CoverSearchResult.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100CoverSearchResult.cpp
${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100Directory.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100Directory.cpp
${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100LibraryBackendAlbum.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100LibraryBackendAlbum.cpp

View File

@ -32,7 +32,7 @@
#include <QVariant> #include <QVariant>
const char* Database::kDatabaseFilename = "clementine.db"; const char* Database::kDatabaseFilename = "clementine.db";
const int Database::kSchemaVersion = 31; const int Database::kSchemaVersion = 32;
const char* Database::kMagicAllSongsTables = "%allsongstables"; const char* Database::kMagicAllSongsTables = "%allsongstables";
int Database::sNextConnectionId = 1; int Database::sNextConnectionId = 1;

View File

@ -251,8 +251,7 @@ int Mpris1Player::GetCaps(Engine::State state) const {
} }
} }
if (playlists->active()->next_row() != -1 || if (playlists->active()->next_row() != -1) {
playlists->active()->current_item_options() & PlaylistItem::ContainsMultipleTracks) {
caps |= CAN_GO_NEXT; caps |= CAN_GO_NEXT;
} }
if (playlists->active()->previous_row() != -1) { if (playlists->active()->previous_row() != -1) {

View File

@ -18,6 +18,7 @@
#include "config.h" #include "config.h"
#include "player.h" #include "player.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/urlhandler.h"
#include "engines/enginebase.h" #include "engines/enginebase.h"
#include "engines/gstengine.h" #include "engines/gstengine.h"
#include "library/librarybackend.h" #include "library/librarybackend.h"
@ -37,11 +38,10 @@
using boost::shared_ptr; using boost::shared_ptr;
Player::Player(PlaylistManagerInterface* playlists, LastFMService* lastfm, Player::Player(PlaylistManagerInterface* playlists, QObject* parent)
QObject* parent)
: PlayerInterface(parent), : PlayerInterface(parent),
playlists_(playlists), playlists_(playlists),
lastfm_(lastfm), lastfm_(NULL),
engine_(new GstEngine), engine_(new GstEngine),
stream_change_type_(Engine::First), stream_change_type_(Engine::First),
last_state_(Engine::Empty), last_state_(Engine::Empty),
@ -71,20 +71,27 @@ void Player::Init() {
SLOT(EngineMetadataReceived(Engine::SimpleMetaBundle))); SLOT(EngineMetadataReceived(Engine::SimpleMetaBundle)));
engine_->SetVolume(settings_.value("volume", 50).toInt()); engine_->SetVolume(settings_.value("volume", 50).toInt());
#ifdef HAVE_LIBLASTFM
lastfm_ = RadioModel::Service<LastFMService>();
#endif
} }
void Player::ReloadSettings() { void Player::ReloadSettings() {
engine_->ReloadSettings(); engine_->ReloadSettings();
} }
void Player::HandleSpecialLoad(const PlaylistItem::SpecialLoadResult &result) { void Player::HandleLoadResult(const UrlHandler::LoadResult& result) {
switch (result.type_) { 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(); loading_async_ = QUrl();
NextItem(Engine::Auto); NextItem(Engine::Auto);
break; break;
case PlaylistItem::SpecialLoadResult::TrackAvailable: { case UrlHandler::LoadResult::TrackAvailable: {
// Might've been an async load, so check we're still on the same item // Might've been an async load, so check we're still on the same item
int current_index = playlists_->active()->current_row(); int current_index = playlists_->active()->current_row();
if (current_index == -1) if (current_index == -1)
@ -94,6 +101,9 @@ void Player::HandleSpecialLoad(const PlaylistItem::SpecialLoadResult &result) {
if (!item || item->Url() != result.original_url_) if (!item || item->Url() != result.original_url_)
return; return;
qLog(Debug) << "URL handler for" << result.original_url_
<< "returned" << result.media_url_;
engine_->Play(result.media_url_, stream_change_type_, engine_->Play(result.media_url_, stream_change_type_,
item->Metadata().has_cue(), item->Metadata().has_cue(),
item->Metadata().beginning_nanosec(), item->Metadata().beginning_nanosec(),
@ -104,7 +114,10 @@ void Player::HandleSpecialLoad(const PlaylistItem::SpecialLoadResult &result) {
break; 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 // We'll get called again later with either NoMoreTracks or TrackAvailable
loading_async_ = result.original_url_; loading_async_ = result.original_url_;
break; break;
@ -122,15 +135,18 @@ void Player::NextInternal(Engine::TrackChangeFlags change) {
return; return;
} }
if (playlists_->active()->current_item() && if (playlists_->active()->current_item()) {
playlists_->active()->current_item()->options() & PlaylistItem::ContainsMultipleTracks) { const QUrl url = playlists_->active()->current_item()->Url();
// The next track is already being loaded
if (playlists_->active()->current_item()->Url() == loading_async_)
return;
stream_change_type_ = change; if (url_handlers_.contains(url.scheme())) {
HandleSpecialLoad(playlists_->active()->current_item()->LoadNext()); // The next track is already being loaded
return; if (url == loading_async_)
return;
stream_change_type_ = change;
HandleLoadResult(url_handlers_[url.scheme()]->LoadNext(url));
return;
}
} }
NextItem(change); NextItem(change);
@ -261,16 +277,16 @@ void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle)
playlists_->active()->set_current_row(index); playlists_->active()->set_current_row(index);
current_item_ = playlists_->active()->current_item(); 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 // It's already loading
if (current_item_->Url() == loading_async_) if (url == loading_async_)
return; return;
stream_change_type_ = change; stream_change_type_ = change;
HandleSpecialLoad(current_item_->StartLoading()); HandleLoadResult(url_handlers_[url.scheme()]->StartLoading(url));
} } else {
else {
loading_async_ = QUrl(); loading_async_ = QUrl();
engine_->Play(current_item_->Url(), change, engine_->Play(current_item_->Url(), change,
current_item_->Metadata().has_cue(), current_item_->Metadata().has_cue(),
@ -397,8 +413,6 @@ void Player::ShowOSD() {
} }
void Player::TrackAboutToEnd() { void Player::TrackAboutToEnd() {
const bool current_contains_multiple_tracks =
current_item_->options() & PlaylistItem::ContainsMultipleTracks;
const bool has_next_row = playlists_->active()->next_row() != -1; const bool has_next_row = playlists_->active()->next_row() != -1;
PlaylistItemPtr next_item; PlaylistItemPtr next_item;
@ -419,7 +433,6 @@ void Player::TrackAboutToEnd() {
// user doesn't want to crossfade between tracks on the same album, then // user doesn't want to crossfade between tracks on the same album, then
// don't do this automatic crossfading. // don't do this automatic crossfading.
if (engine_->crossfade_same_album() || if (engine_->crossfade_same_album() ||
current_contains_multiple_tracks ||
!has_next_row || !has_next_row ||
!next_item || !next_item ||
!current_item_->Metadata().IsOnSameAlbum(next_item->Metadata())) { !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 // Crossfade is off, so start preloading the next track so we don't get a
// gap between songs. // gap between songs.
if (current_contains_multiple_tracks || !has_next_row) if (!has_next_row || !next_item)
return;
if (!next_item)
return; return;
QUrl url = next_item->Url(); QUrl url = next_item->Url();
// Get the actual track URL rather than the stream URL. // Get the actual track URL rather than the stream URL.
if (next_item->options() & PlaylistItem::ContainsMultipleTracks) { if (url_handlers_.contains(url.scheme())) {
PlaylistItem::SpecialLoadResult result = next_item->LoadNext(); UrlHandler::LoadResult result = url_handlers_[url.scheme()]->LoadNext(url);
switch (result.type_) { switch (result.type_) {
case PlaylistItem::SpecialLoadResult::NoMoreTracks: case UrlHandler::LoadResult::NoMoreTracks:
return; return;
case PlaylistItem::SpecialLoadResult::WillLoadAsynchronously: case UrlHandler::LoadResult::WillLoadAsynchronously:
loading_async_ = next_item->Url(); loading_async_ = url;
return; return;
case PlaylistItem::SpecialLoadResult::TrackAvailable: case UrlHandler::LoadResult::TrackAvailable:
url = result.media_url_; url = result.media_url_;
break; 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 // current item we can change the current item by skipping to the next song
NextItem(Engine::Auto); 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<UrlHandler*>(object);
const QString scheme = url_handlers_.key(handler);
if (!scheme.isEmpty()) {
url_handlers_.remove(scheme);
}
}

View File

@ -25,14 +25,15 @@
#include "config.h" #include "config.h"
#include "core/song.h" #include "core/song.h"
#include "core/urlhandler.h"
#include "covers/albumcoverloader.h" #include "covers/albumcoverloader.h"
#include "engines/engine_fwd.h" #include "engines/engine_fwd.h"
#include "playlist/playlistitem.h" #include "playlist/playlistitem.h"
class LastFMService; class LastFMService;
class MainWindow;
class PlaylistManagerInterface; class PlaylistManagerInterface;
class Settings; class Settings;
class MainWindow;
class PlayerInterface : public QObject { class PlayerInterface : public QObject {
@ -49,6 +50,9 @@ public:
virtual PlaylistItemPtr GetItemAt(int pos) const = 0; virtual PlaylistItemPtr GetItemAt(int pos) const = 0;
virtual PlaylistManagerInterface* playlists() const = 0; virtual PlaylistManagerInterface* playlists() const = 0;
virtual void AddUrlHandler(UrlHandler* handler) = 0;
virtual void RemoveUrlHandler(UrlHandler* handler) = 0;
public slots: public slots:
virtual void ReloadSettings() = 0; virtual void ReloadSettings() = 0;
@ -72,7 +76,6 @@ public slots:
// Moves the position of the currently playing song five seconds backwards. // Moves the position of the currently playing song five seconds backwards.
virtual void SeekBackward() = 0; virtual void SeekBackward() = 0;
virtual void HandleSpecialLoad(const PlaylistItem::SpecialLoadResult& result) = 0;
virtual void CurrentMetadataChanged(const Song& metadata) = 0; virtual void CurrentMetadataChanged(const Song& metadata) = 0;
virtual void Mute() = 0; virtual void Mute() = 0;
@ -103,8 +106,7 @@ class Player : public PlayerInterface {
Q_OBJECT Q_OBJECT
public: public:
Player(PlaylistManagerInterface* playlists, LastFMService* lastfm, Player(PlaylistManagerInterface* playlists, QObject* parent = 0);
QObject* parent = 0);
~Player(); ~Player();
void Init(); void Init();
@ -117,6 +119,9 @@ public:
PlaylistItemPtr GetItemAt(int pos) const; PlaylistItemPtr GetItemAt(int pos) const;
PlaylistManagerInterface* playlists() const { return playlists_; } PlaylistManagerInterface* playlists() const { return playlists_; }
void AddUrlHandler(UrlHandler* handler);
void RemoveUrlHandler(UrlHandler* handler);
public slots: public slots:
void ReloadSettings(); void ReloadSettings();
@ -131,7 +136,6 @@ public slots:
void SeekForward(); void SeekForward();
void SeekBackward(); void SeekBackward();
void HandleSpecialLoad(const PlaylistItem::SpecialLoadResult& result);
void CurrentMetadataChanged(const Song& metadata); void CurrentMetadataChanged(const Song& metadata);
void Mute(); void Mute();
@ -154,6 +158,9 @@ public slots:
void ValidSongRequested(const QUrl&); void ValidSongRequested(const QUrl&);
void InvalidSongRequested(const QUrl&); void InvalidSongRequested(const QUrl&);
void UrlHandlerDestroyed(QObject* object);
void HandleLoadResult(const UrlHandler::LoadResult& result);
private: private:
PlaylistManagerInterface* playlists_; PlaylistManagerInterface* playlists_;
LastFMService* lastfm_; LastFMService* lastfm_;
@ -165,6 +172,8 @@ public slots:
Engine::TrackChangeFlags stream_change_type_; Engine::TrackChangeFlags stream_change_type_;
Engine::State last_state_; Engine::State last_state_;
QMap<QString, UrlHandler*> url_handlers_;
QUrl loading_async_; QUrl loading_async_;
int volume_before_mute_; int volume_before_mute_;

29
src/core/urlhandler.cpp Normal file
View File

@ -0,0 +1,29 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 <http://www.gnu.org/licenses/>.
*/
#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)
{
}

76
src/core/urlhandler.h Normal file
View File

@ -0,0 +1,76 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 <http://www.gnu.org/licenses/>.
*/
#ifndef URLHANDLER_H
#define URLHANDLER_H
#include <QObject>
#include <QUrl>
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

View File

@ -374,21 +374,13 @@ int main(int argc, char *argv[]) {
database->Start(true); database->Start(true);
TaskManager task_manager; TaskManager task_manager;
PlaylistManager playlists(&task_manager, NULL); 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 // Initialize the repository of cover providers to avoid race conditions
// later // later
CoverProviders::instance(); CoverProviders::instance();
// Get the last.fm service if it's available
LastFMService* lastfm_service = NULL;
#ifdef HAVE_LIBLASTFM
lastfm_service = RadioModel::Service<LastFMService>();
#endif
// Create the player
Player player(&playlists, lastfm_service);
// Create the tray icon and OSD // Create the tray icon and OSD
scoped_ptr<SystemTrayIcon> tray_icon(SystemTrayIcon::CreateSystemTrayIcon()); scoped_ptr<SystemTrayIcon> tray_icon(SystemTrayIcon::CreateSystemTrayIcon());
OSD osd(tray_icon.get()); OSD osd(tray_icon.get());

View File

@ -30,11 +30,6 @@
#include <QtConcurrentRun> #include <QtConcurrentRun>
#include <QtDebug> #include <QtDebug>
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) { PlaylistItem* PlaylistItem::NewFromType(const QString& type) {
if (type == "Library") if (type == "Library")

View File

@ -41,55 +41,14 @@ class PlaylistItem : public boost::enable_shared_from_this<PlaylistItem> {
enum Option { enum Option {
Default = 0x00, 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. // Disables the "pause" action.
PauseDisabled = 0x04, PauseDisabled = 0x01,
// Enables the last.fm "ban" action. // Enables the last.fm "ban" action.
LastFMControls = 0x08, LastFMControls = 0x02,
}; };
Q_DECLARE_FLAGS(Options, Option); 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 QString type() const { return type_; }
virtual Options options() const { return Default; } virtual Options options() const { return Default; }
@ -102,14 +61,6 @@ class PlaylistItem : public boost::enable_shared_from_this<PlaylistItem> {
virtual Song Metadata() const = 0; virtual Song Metadata() const = 0;
virtual QUrl Url() 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 SetTemporaryMetadata(const Song& metadata);
void ClearTemporaryMetadata(); void ClearTemporaryMetadata();
bool HasTemporaryMetadata() const { return temp_metadata_.is_valid(); } bool HasTemporaryMetadata() const { return temp_metadata_.is_valid(); }

View File

@ -17,9 +17,11 @@
#include "lastfmservice.h" #include "lastfmservice.h"
#include "lastfmstationdialog.h" #include "lastfmstationdialog.h"
#include "lastfmurlhandler.h"
#include "radiomodel.h" #include "radiomodel.h"
#include "radioplaylistitem.h" #include "radioplaylistitem.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/player.h"
#include "core/song.h" #include "core/song.h"
#include "core/taskmanager.h" #include "core/taskmanager.h"
#include "ui/iconloader.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) LastFMService::LastFMService(RadioModel* parent)
: RadioService(kServiceName, parent), : RadioService(kServiceName, parent),
url_handler_(new LastFMUrlHandler(this, this)),
scrobbler_(NULL), scrobbler_(NULL),
already_scrobbled_(false), already_scrobbled_(false),
station_dialog_(new LastFMStationDialog), station_dialog_(new LastFMStationDialog),
@ -99,6 +102,8 @@ LastFMService::LastFMService(RadioModel* parent)
add_artist_action_->setEnabled(false); add_artist_action_->setEnabled(false);
add_tag_action_->setEnabled(false); add_tag_action_->setEnabled(false);
add_custom_action_->setEnabled(false); add_custom_action_->setEnabled(false);
model()->player()->AddUrlHandler(url_handler_);
} }
LastFMService::~LastFMService() { LastFMService::~LastFMService() {
@ -356,26 +361,9 @@ QUrl LastFMService::FixupUrl(const QUrl& url) {
return ret; return ret;
} }
PlaylistItem::SpecialLoadResult LastFMService::StartLoading(const QUrl& url) { QUrl LastFMService::DeququeNextMediaUrl() {
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&) {
if (playlist_.empty()) { if (playlist_.empty()) {
return PlaylistItem::SpecialLoadResult(); return QUrl();
} }
lastfm::MutableTrack track = playlist_.dequeue(); lastfm::MutableTrack track = playlist_.dequeue();
@ -390,8 +378,7 @@ PlaylistItem::SpecialLoadResult LastFMService::LoadNext(const QUrl&) {
next_metadata_ = track; next_metadata_ = track;
StreamMetadataReady(); StreamMetadataReady();
return PlaylistItem::SpecialLoadResult( return last_track_.url();
PlaylistItem::SpecialLoadResult::TrackAvailable, last_url_, last_track_.url());
} }
void LastFMService::StreamMetadataReady() { void LastFMService::StreamMetadataReady() {
@ -413,8 +400,7 @@ void LastFMService::TunerError(lastfm::ws::Error error) {
tune_task_id_ = 0; tune_task_id_ = 0;
if (error == lastfm::ws::NotEnoughContent) { if (error == lastfm::ws::NotEnoughContent) {
emit AsyncLoadFinished(PlaylistItem::SpecialLoadResult( url_handler_->TunerError();
PlaylistItem::SpecialLoadResult::NoMoreTracks, last_url_));
return; return;
} }
@ -452,7 +438,7 @@ QString LastFMService::ErrorString(lastfm::ws::Error error) const {
void LastFMService::TunerTrackAvailable() { void LastFMService::TunerTrackAvailable() {
if (initial_tune_) { if (initial_tune_) {
emit AsyncLoadFinished(LoadNext(last_url_)); url_handler_->TunerTrackAvailable();
initial_tune_ = false; initial_tune_ = false;
} }
} }
@ -556,7 +542,7 @@ void LastFMService::Ban() {
last_track_ = mtrack; last_track_ = mtrack;
Scrobble(); Scrobble();
emit AsyncLoadFinished(LoadNext(last_url_)); model()->player()->Next();
} }
void LastFMService::ShowContextMenu(const QModelIndex& index, const QPoint &global_pos) { void LastFMService::ShowContextMenu(const QModelIndex& index, const QPoint &global_pos) {
@ -792,8 +778,7 @@ void LastFMService::FetchMoreTracksFinished() {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender()); QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
if (!reply) { if (!reply) {
qLog(Warning) << "Invalid reply on radio.getPlaylist"; qLog(Warning) << "Invalid reply on radio.getPlaylist";
emit AsyncLoadFinished(PlaylistItem::SpecialLoadResult( url_handler_->TunerError();
PlaylistItem::SpecialLoadResult::NoMoreTracks, reply->url()));
return; return;
} }
reply->deleteLater(); reply->deleteLater();
@ -838,7 +823,14 @@ void LastFMService::FetchMoreTracksFinished() {
TunerTrackAvailable(); 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(); playlist_.clear();
// Remove all the old album art URLs // Remove all the old album art URLs
@ -855,8 +847,7 @@ void LastFMService::TuneFinished() {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender()); QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
if (!reply) { if (!reply) {
qLog(Warning) << "Invalid reply on radio.tune"; qLog(Warning) << "Invalid reply on radio.tune";
emit AsyncLoadFinished(PlaylistItem::SpecialLoadResult( url_handler_->TunerError();
PlaylistItem::SpecialLoadResult::NoMoreTracks, reply->url()));
return; return;
} }
@ -865,10 +856,8 @@ void LastFMService::TuneFinished() {
} }
PlaylistItem::Options LastFMService::playlistitem_options() const { PlaylistItem::Options LastFMService::playlistitem_options() const {
return PlaylistItem::SpecialPlayBehaviour | return PlaylistItem::LastFMControls |
PlaylistItem::LastFMControls | PlaylistItem::PauseDisabled;
PlaylistItem::PauseDisabled |
PlaylistItem::ContainsMultipleTracks;
} }
PlaylistItemPtr LastFMService::PlaylistItemForUrl(const QUrl& url) { PlaylistItemPtr LastFMService::PlaylistItemForUrl(const QUrl& url) {

View File

@ -42,12 +42,14 @@ uint qHash(const lastfm::Track& track);
#include <boost/scoped_ptr.hpp> #include <boost/scoped_ptr.hpp>
class QAction; class LastFMUrlHandler;
class QAction;
class QNetworkAccessManager; class QNetworkAccessManager;
class LastFMService : public RadioService { class LastFMService : public RadioService {
Q_OBJECT Q_OBJECT
friend class LastFMUrlHandler;
public: public:
LastFMService(RadioModel* parent); LastFMService(RadioModel* parent);
@ -83,9 +85,6 @@ class LastFMService : public RadioService {
void ShowContextMenu(const QModelIndex& index, const QPoint &global_pos); 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; PlaylistItem::Options playlistitem_options() const;
void ReloadSettings(); void ReloadSettings();
@ -105,6 +104,7 @@ class LastFMService : public RadioService {
void UpdateSubscriberStatus(); void UpdateSubscriberStatus();
void FetchMoreTracks(); void FetchMoreTracks();
QUrl DeququeNextMediaUrl();
PlaylistItemPtr PlaylistItemForUrl(const QUrl& url); PlaylistItemPtr PlaylistItemForUrl(const QUrl& url);
@ -169,11 +169,13 @@ class LastFMService : public RadioService {
const QIcon& icon, QStandardItem* parent); const QIcon& icon, QStandardItem* parent);
static QUrl FixupUrl(const QUrl& url); static QUrl FixupUrl(const QUrl& url);
void Tune(const lastfm::RadioStation& station); void Tune(const QUrl& station);
void AddSelectedToPlaylist(bool clear_first); void AddSelectedToPlaylist(bool clear_first);
private: private:
LastFMUrlHandler* url_handler_;
lastfm::Audioscrobbler* scrobbler_; lastfm::Audioscrobbler* scrobbler_;
lastfm::Track last_track_; lastfm::Track last_track_;
lastfm::Track next_metadata_; lastfm::Track next_metadata_;

View File

@ -0,0 +1,48 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 <http://www.gnu.org/licenses/>.
*/
#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);
}

View File

@ -0,0 +1,43 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -16,7 +16,6 @@
*/ */
#include "magnatuneplaylistitem.h" #include "magnatuneplaylistitem.h"
#include "magnatuneservice.h"
#include "radiomodel.h" #include "radiomodel.h"
MagnatunePlaylistItem::MagnatunePlaylistItem(const QString& type) MagnatunePlaylistItem::MagnatunePlaylistItem(const QString& type)
@ -37,18 +36,6 @@ bool MagnatunePlaylistItem::InitFromQuery(const SqlRow& query) {
return song_.is_valid(); return song_.is_valid();
} }
PlaylistItem::Options MagnatunePlaylistItem::options() const {
return SpecialPlayBehaviour;
}
QUrl MagnatunePlaylistItem::Url() const { QUrl MagnatunePlaylistItem::Url() const {
return song_.url(); return song_.url();
} }
PlaylistItem::SpecialLoadResult MagnatunePlaylistItem::StartLoading() {
MagnatuneService* service = RadioModel::Service<MagnatuneService>();
QUrl url(Url());
return SpecialLoadResult(PlaylistItem::SpecialLoadResult::TrackAvailable,
url, service->ModifyUrl(url));
}

View File

@ -27,10 +27,7 @@ class MagnatunePlaylistItem : public LibraryPlaylistItem {
bool InitFromQuery(const SqlRow& query); bool InitFromQuery(const SqlRow& query);
Options options() const;
QUrl Url() const; QUrl Url() const;
SpecialLoadResult StartLoading();
}; };
#endif // MAGNATUNEPLAYLISTITEM_H #endif // MAGNATUNEPLAYLISTITEM_H

View File

@ -18,10 +18,12 @@
#include "magnatunedownloaddialog.h" #include "magnatunedownloaddialog.h"
#include "magnatuneplaylistitem.h" #include "magnatuneplaylistitem.h"
#include "magnatuneservice.h" #include "magnatuneservice.h"
#include "magnatuneurlhandler.h"
#include "radiomodel.h" #include "radiomodel.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/mergedproxymodel.h" #include "core/mergedproxymodel.h"
#include "core/network.h" #include "core/network.h"
#include "core/player.h"
#include "core/song.h" #include "core/song.h"
#include "core/taskmanager.h" #include "core/taskmanager.h"
#include "library/librarymodel.h" #include "library/librarymodel.h"
@ -61,6 +63,7 @@ const char* MagnatuneService::kDownloadUrl = "http://download.magnatune.com/buy/
MagnatuneService::MagnatuneService(RadioModel* parent) MagnatuneService::MagnatuneService(RadioModel* parent)
: RadioService(kServiceName, parent), : RadioService(kServiceName, parent),
url_handler_(new MagnatuneUrlHandler(this, this)),
context_menu_(NULL), context_menu_(NULL),
root_(NULL), root_(NULL),
library_backend_(NULL), library_backend_(NULL),
@ -89,6 +92,8 @@ MagnatuneService::MagnatuneService(RadioModel* parent)
library_sort_model_->setSortRole(LibraryModel::Role_SortText); library_sort_model_->setSortRole(LibraryModel::Role_SortText);
library_sort_model_->setDynamicSortFilter(true); library_sort_model_->setDynamicSortFilter(true);
library_sort_model_->sort(0); library_sort_model_->sort(0);
model()->player()->AddUrlHandler(url_handler_);
} }
MagnatuneService::~MagnatuneService() { MagnatuneService::~MagnatuneService() {
@ -205,9 +210,13 @@ Song MagnatuneService::ReadTrack(QXmlStreamReader& reader) {
if (name == "year") song.set_year(value.toInt()); if (name == "year") song.set_year(value.toInt());
if (name == "magnatunegenres") song.set_genre(value.section(',', 0, 0)); if (name == "magnatunegenres") song.set_genre(value.section(',', 0, 0));
if (name == "seconds") song.set_length_nanosec(value.toInt() * kNsecPerSec); 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 == "cover_small") song.set_art_automatic(value);
if (name == "albumsku") song.set_comment(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 MagnatuneService::ModifyUrl(const QUrl& url) const {
QUrl ret(url); QUrl ret(url);
ret.setScheme("http");
switch(membership_) { switch(membership_) {
case Membership_None: case Membership_None:

View File

@ -28,6 +28,7 @@ class QMenu;
class LibraryBackend; class LibraryBackend;
class LibraryModel; class LibraryModel;
class MagnatuneUrlHandler;
class MagnatuneService : public RadioService { class MagnatuneService : public RadioService {
Q_OBJECT Q_OBJECT
@ -106,6 +107,8 @@ class MagnatuneService : public RadioService {
Song ReadTrack(QXmlStreamReader& reader); Song ReadTrack(QXmlStreamReader& reader);
private: private:
MagnatuneUrlHandler* url_handler_;
QMenu* context_menu_; QMenu* context_menu_;
QModelIndex context_item_; QModelIndex context_item_;
QStandardItem* root_; QStandardItem* root_;

View File

@ -0,0 +1,28 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 <http://www.gnu.org/licenses/>.
*/
#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));
}

View File

@ -0,0 +1,37 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -39,11 +39,13 @@
QMap<QString, RadioService*>* RadioModel::sServices = NULL; QMap<QString, RadioService*>* RadioModel::sServices = NULL;
RadioModel::RadioModel(BackgroundThread<Database>* db_thread, RadioModel::RadioModel(BackgroundThread<Database>* db_thread,
TaskManager* task_manager, QObject* parent) TaskManager* task_manager, PlayerInterface* player,
QObject* parent)
: QStandardItemModel(parent), : QStandardItemModel(parent),
db_thread_(db_thread), db_thread_(db_thread),
merged_model_(new MergedProxyModel(this)), merged_model_(new MergedProxyModel(this)),
task_manager_(task_manager) task_manager_(task_manager),
player_(player)
{ {
if (!sServices) { if (!sServices) {
sServices = new QMap<QString, RadioService*>; sServices = new QMap<QString, RadioService*>;
@ -76,10 +78,9 @@ void RadioModel::AddService(RadioService *service) {
root->setData(QVariant::fromValue(service), Role_Service); root->setData(QVariant::fromValue(service), Role_Service);
invisibleRootItem()->appendRow(root); invisibleRootItem()->appendRow(root);
qDebug() << "Adding:" << service->name(); qLog(Debug) << "Adding radio service:" << service->name();
sServices->insert(service->name(), service); 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(StreamError(QString)), SIGNAL(StreamError(QString)));
connect(service, SIGNAL(StreamMetadataFound(QUrl,Song)), SIGNAL(StreamMetadataFound(QUrl,Song))); connect(service, SIGNAL(StreamMetadataFound(QUrl,Song)), SIGNAL(StreamMetadataFound(QUrl,Song)));
connect(service, SIGNAL(OpenSettingsAtPage(SettingsDialog::Page)), SIGNAL(OpenSettingsAtPage(SettingsDialog::Page))); connect(service, SIGNAL(OpenSettingsAtPage(SettingsDialog::Page)), SIGNAL(OpenSettingsAtPage(SettingsDialog::Page)));

View File

@ -26,6 +26,7 @@
class Database; class Database;
class MergedProxyModel; class MergedProxyModel;
class PlayerInterface;
class RadioService; class RadioService;
class SettingsDialog; class SettingsDialog;
class TaskManager; class TaskManager;
@ -39,7 +40,7 @@ class RadioModel : public QStandardItemModel {
public: public:
RadioModel(BackgroundThread<Database>* db_thread, TaskManager* task_manager, RadioModel(BackgroundThread<Database>* db_thread, TaskManager* task_manager,
QObject* parent = 0); PlayerInterface* player, QObject* parent = 0);
enum Role { enum Role {
// Services can use this role to distinguish between different types of // Services can use this role to distinguish between different types of
@ -132,9 +133,9 @@ public:
BackgroundThread<Database>* db_thread() const { return db_thread_; } BackgroundThread<Database>* db_thread() const { return db_thread_; }
MergedProxyModel* merged_model() const { return merged_model_; } MergedProxyModel* merged_model() const { return merged_model_; }
TaskManager* task_manager() const { return task_manager_; } TaskManager* task_manager() const { return task_manager_; }
PlayerInterface* player() const { return player_; }
signals: signals:
void AsyncLoadFinished(const PlaylistItem::SpecialLoadResult& result);
void StreamError(const QString& message); void StreamError(const QString& message);
void StreamMetadataFound(const QUrl& original_url, const Song& song); void StreamMetadataFound(const QUrl& original_url, const Song& song);
void OpenSettingsAtPage(SettingsDialog::Page); void OpenSettingsAtPage(SettingsDialog::Page);
@ -149,6 +150,7 @@ private:
BackgroundThread<Database>* db_thread_; BackgroundThread<Database>* db_thread_;
MergedProxyModel* merged_model_; MergedProxyModel* merged_model_;
TaskManager* task_manager_; TaskManager* task_manager_;
PlayerInterface* player_;
}; };
#endif // RADIOMODEL_H #endif // RADIOMODEL_H

View File

@ -93,20 +93,6 @@ Song RadioPlaylistItem::Metadata() const {
return metadata_; 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 { QUrl RadioPlaylistItem::Url() const {
return metadata_.url(); return metadata_.url();
} }

View File

@ -38,9 +38,6 @@ class RadioPlaylistItem : public PlaylistItem {
Song Metadata() const; Song Metadata() const;
QUrl Url() const; QUrl Url() const;
SpecialLoadResult StartLoading();
SpecialLoadResult LoadNext();
protected: protected:
QVariant DatabaseValue(DatabaseColumn) const; QVariant DatabaseValue(DatabaseColumn) const;
Song DatabaseSongMetadata() const { return metadata_; } Song DatabaseSongMetadata() const { return metadata_; }

View File

@ -76,15 +76,6 @@ QAction* RadioService::GetOpenInNewPlaylistAction() {
return open_in_new_playlist_; 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) { void RadioService::AddItemToPlaylist(const QModelIndex& index, AddMode add_mode) {
AddItemsToPlaylist(QModelIndexList() << index, add_mode); AddItemsToPlaylist(QModelIndexList() << index, add_mode);
} }

View File

@ -48,9 +48,6 @@ public:
virtual void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos) { virtual void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos) {
Q_UNUSED(index); Q_UNUSED(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 PlaylistItem::Options playlistitem_options() const { return PlaylistItem::Default; }
virtual QWidget* HeaderWidget() const { return NULL; } virtual QWidget* HeaderWidget() const { return NULL; }
@ -60,7 +57,6 @@ public:
virtual QString Icon() { return QString(); } virtual QString Icon() { return QString(); }
signals: signals:
void AsyncLoadFinished(const PlaylistItem::SpecialLoadResult& result);
void StreamError(const QString& message); void StreamError(const QString& message);
void StreamMetadataFound(const QUrl& original_url, const Song& song); void StreamMetadataFound(const QUrl& original_url, const Song& song);
void OpenSettingsAtPage(SettingsDialog::Page page); void OpenSettingsAtPage(SettingsDialog::Page page);

View File

@ -16,20 +16,20 @@
*/ */
#include "somafmservice.h" #include "somafmservice.h"
#include "somafmurlhandler.h"
#include "radiomodel.h" #include "radiomodel.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/network.h" #include "core/network.h"
#include "core/player.h"
#include "core/taskmanager.h" #include "core/taskmanager.h"
#include "ui/iconloader.h" #include "ui/iconloader.h"
#include <QCoreApplication>
#include <QDesktopServices>
#include <QMenu>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QNetworkReply> #include <QNetworkReply>
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include <QSettings>
#include <QTemporaryFile>
#include <QMenu>
#include <QDesktopServices>
#include <QCoreApplication>
#include <QtDebug> #include <QtDebug>
const char* SomaFMService::kServiceName = "SomaFM"; const char* SomaFMService::kServiceName = "SomaFM";
@ -38,12 +38,13 @@ const char* SomaFMService::kHomepage = "http://somafm.com";
SomaFMService::SomaFMService(RadioModel* parent) SomaFMService::SomaFMService(RadioModel* parent)
: RadioService(kServiceName, parent), : RadioService(kServiceName, parent),
url_handler_(new SomaFMUrlHandler(this, this)),
root_(NULL), root_(NULL),
context_menu_(NULL), context_menu_(NULL),
get_channels_task_id_(0), get_channels_task_id_(0),
get_stream_task_id_(0),
network_(new NetworkAccessManager(this)) network_(new NetworkAccessManager(this))
{ {
model()->player()->AddUrlHandler(url_handler_);
} }
SomaFMService::~SomaFMService() { SomaFMService::~SomaFMService() {
@ -79,51 +80,6 @@ void SomaFMService::ShowContextMenu(const QModelIndex& index, const QPoint& glob
context_menu_->popup(global_pos); 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<QNetworkReply*>(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() { void SomaFMService::RefreshChannels() {
QNetworkReply* reply = network_->get(QNetworkRequest(QUrl(kChannelListUrl))); QNetworkReply* reply = network_->get(QNetworkRequest(QUrl(kChannelListUrl)));
connect(reply, SIGNAL(finished()), SLOT(RefreshChannelsFinished())); connect(reply, SIGNAL(finished()), SLOT(RefreshChannelsFinished()));
@ -181,7 +137,10 @@ void SomaFMService::ReadChannel(QXmlStreamReader& reader) {
} else if (reader.name() == "dj") { } else if (reader.name() == "dj") {
song.set_artist(reader.readElementText()); song.set_artist(reader.readElementText());
} else if (reader.name() == "fastpls" && reader.attributes().value("format") == "mp3") { } 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 { } else {
ConsumeElement(reader); ConsumeElement(reader);
} }
@ -216,6 +175,5 @@ QModelIndex SomaFMService::GetCurrentIndex() {
} }
PlaylistItem::Options SomaFMService::playlistitem_options() const { PlaylistItem::Options SomaFMService::playlistitem_options() const {
return PlaylistItem::SpecialPlayBehaviour | return PlaylistItem::PauseDisabled;
PlaylistItem::PauseDisabled;
} }

View File

@ -22,6 +22,8 @@
#include "radioservice.h" #include "radioservice.h"
class SomaFMUrlHandler;
class QNetworkAccessManager; class QNetworkAccessManager;
class QMenu; class QMenu;
@ -46,7 +48,8 @@ class SomaFMService : public RadioService {
void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos); void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos);
PlaylistItem::Options playlistitem_options() const; PlaylistItem::Options playlistitem_options() const;
PlaylistItem::SpecialLoadResult StartLoading(const QUrl& url);
QNetworkAccessManager* network() const { return network_; }
protected: protected:
QModelIndex GetCurrentIndex(); QModelIndex GetCurrentIndex();
@ -54,7 +57,6 @@ class SomaFMService : public RadioService {
private slots: private slots:
void RefreshChannels(); void RefreshChannels();
void RefreshChannelsFinished(); void RefreshChannelsFinished();
void LoadPlaylistFinished();
void Homepage(); void Homepage();
@ -63,12 +65,13 @@ class SomaFMService : public RadioService {
void ConsumeElement(QXmlStreamReader& reader); void ConsumeElement(QXmlStreamReader& reader);
private: private:
SomaFMUrlHandler* url_handler_;
QStandardItem* root_; QStandardItem* root_;
QMenu* context_menu_; QMenu* context_menu_;
QStandardItem* context_item_; QStandardItem* context_item_;
int get_channels_task_id_; int get_channels_task_id_;
int get_stream_task_id_;
QNetworkAccessManager* network_; QNetworkAccessManager* network_;
}; };

View File

@ -0,0 +1,76 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 <http://www.gnu.org/licenses/>.
*/
#include "radiomodel.h"
#include "somafmservice.h"
#include "somafmurlhandler.h"
#include "core/logging.h"
#include "core/taskmanager.h"
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QSettings>
#include <QTemporaryFile>
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<QNetworkReply*>(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()));
}

View File

@ -0,0 +1,44 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -1,9 +1,11 @@
#include "radiomodel.h" #include "radiomodel.h"
#include "spotifyserver.h" #include "spotifyserver.h"
#include "spotifyservice.h" #include "spotifyservice.h"
#include "spotifyurlhandler.h"
#include "core/database.h" #include "core/database.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/mergedproxymodel.h" #include "core/mergedproxymodel.h"
#include "core/player.h"
#include "core/taskmanager.h" #include "core/taskmanager.h"
#include "library/library.h" #include "library/library.h"
#include "library/librarybackend.h" #include "library/librarybackend.h"
@ -17,8 +19,6 @@
#include <QProcess> #include <QProcess>
#include <QSettings> #include <QSettings>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QTcpServer>
#include <QTemporaryFile>
const char* SpotifyService::kServiceName = "Spotify"; const char* SpotifyService::kServiceName = "Spotify";
const char* SpotifyService::kSettingsGroup = "Spotify"; const char* SpotifyService::kSettingsGroup = "Spotify";
@ -28,6 +28,7 @@ const char* SpotifyService::kSearchFtsTable = "spotify_search_songs_fts";
SpotifyService::SpotifyService(RadioModel* parent) SpotifyService::SpotifyService(RadioModel* parent)
: RadioService(kServiceName, parent), : RadioService(kServiceName, parent),
server_(NULL), server_(NULL),
url_handler_(new SpotifyUrlHandler(this, this)),
blob_process_(NULL), blob_process_(NULL),
root_(NULL), root_(NULL),
search_results_(NULL), search_results_(NULL),
@ -54,6 +55,8 @@ SpotifyService::SpotifyService(RadioModel* parent)
library_sort_model_->setSortRole(LibraryModel::Role_SortText); library_sort_model_->setSortRole(LibraryModel::Role_SortText);
library_sort_model_->setDynamicSortFilter(true); library_sort_model_->setDynamicSortFilter(true);
library_sort_model_->sort(0); library_sort_model_->sort(0);
model()->player()->AddUrlHandler(url_handler_);
} }
SpotifyService::~SpotifyService() { SpotifyService::~SpotifyService() {
@ -311,35 +314,7 @@ void SpotifyService::SongFromProtobuf(const protobuf::Track& track, Song* song)
} }
PlaylistItem::Options SpotifyService::playlistitem_options() const { PlaylistItem::Options SpotifyService::playlistitem_options() const {
return PlaylistItem::SpecialPlayBehaviour | return PlaylistItem::PauseDisabled;
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)));
} }
void SpotifyService::EnsureMenuCreated() { void SpotifyService::EnsureMenuCreated() {
@ -398,3 +373,8 @@ void SpotifyService::SearchResults(const protobuf::SearchResponse& response) {
library_backend_->DeleteAll(); library_backend_->DeleteAll();
library_backend_->AddOrUpdateSongs(songs); library_backend_->AddOrUpdateSongs(songs);
} }
SpotifyServer* SpotifyService::server() const {
const_cast<SpotifyService*>(this)->EnsureServerCreated();
return server_;
}

View File

@ -13,6 +13,7 @@
class LibraryBackend; class LibraryBackend;
class LibraryModel; class LibraryModel;
class SpotifyServer; class SpotifyServer;
class SpotifyUrlHandler;
class QMenu; class QMenu;
class QSortFilterProxyModel; class QSortFilterProxyModel;
@ -37,20 +38,21 @@ public:
Role_UserPlaylistIndex = RadioModel::RoleCount, Role_UserPlaylistIndex = RadioModel::RoleCount,
}; };
static const char* kServiceName;
static const char* kSettingsGroup;
static const char* kSearchSongsTable;
static const char* kSearchFtsTable;
virtual QStandardItem* CreateRootItem(); virtual QStandardItem* CreateRootItem();
virtual void LazyPopulate(QStandardItem* parent); virtual void LazyPopulate(QStandardItem* parent);
void Login(const QString& username, const QString& password); void Login(const QString& username, const QString& password);
PlaylistItem::SpecialLoadResult StartLoading(const QUrl& url);
PlaylistItem::Options playlistitem_options() const; PlaylistItem::Options playlistitem_options() const;
QWidget* HeaderWidget() const; QWidget* HeaderWidget() const;
static const char* kServiceName; SpotifyServer* server() const;
static const char* kSettingsGroup;
static const char* kSearchSongsTable;
static const char* kSearchFtsTable;
signals: signals:
void LoginFinished(bool success); void LoginFinished(bool success);
@ -81,6 +83,7 @@ private slots:
private: private:
SpotifyServer* server_; SpotifyServer* server_;
SpotifyUrlHandler* url_handler_;
QString blob_path_; QString blob_path_;
QProcess* blob_process_; QProcess* blob_process_;

View File

@ -0,0 +1,52 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 <http://www.gnu.org/licenses/>.
*/
#include "spotifyserver.h"
#include "spotifyservice.h"
#include "spotifyurlhandler.h"
#include "core/logging.h"
#include <QTcpServer>
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)));
}

View File

@ -0,0 +1,37 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -34,6 +34,7 @@
%Include songloader.sip %Include songloader.sip
%Include taskmanager.sip %Include taskmanager.sip
%Include uiinterface.sip %Include uiinterface.sip
%Include urlhandler.sip
// Remember: when adding a class that inherits from QObject, add it to the list // 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 // in scriptinterface.sip as well, or else it won't cast properly when calling

View File

@ -12,6 +12,9 @@ public:
PlaylistItemPtr GetItemAt(int pos) const; PlaylistItemPtr GetItemAt(int pos) const;
PlaylistManagerInterface* playlists() const; PlaylistManagerInterface* playlists() const;
void AddUrlHandler(UrlHandler* handler);
void RemoveUrlHandler(UrlHandler* handler);
public slots: public slots:
// Manual track change to the specified track // Manual track change to the specified track
void PlayAt(int i, Engine::TrackChangeType change, bool reshuffle); void PlayAt(int i, Engine::TrackChangeType change, bool reshuffle);

View File

@ -17,10 +17,7 @@ class PlaylistItem {
Represents a single row in a playlist. Represents a single row in a playlist.
Playlists in Clementine are lists of PlaylistItems. At a minimum each Playlists in Clementine are lists of PlaylistItems. At a minimum each
PlaylistItem contains some metadata and a URL, but items may also have special PlaylistItem contains some metadata and a URL.
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 is an abstract class and instances of it cannot be created 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 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 - C{Default} - no special behaviour, the L{Url()} is used directly when
playing the song. 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{PauseDisabled} - disables the "pause" action.
- C{LastFMControls} - enables the last.fm "ban" 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: public:
enum Option { enum Option {
Default, Default,
SpecialPlayBehaviour,
ContainsMultipleTracks,
PauseDisabled, PauseDisabled,
LastFMControls, LastFMControls,
}; };
typedef QFlags<PlaylistItem::Option> Options; typedef QFlags<PlaylistItem::Option> 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; QString type() const;
%Docstring %Docstring
type() -> str type() -> str

View File

@ -15,9 +15,6 @@ public:
virtual void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos); 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 PlaylistItem::Options playlistitem_options() const;
virtual QWidget* HeaderWidget() const /Transfer/; virtual QWidget* HeaderWidget() const /Transfer/;
@ -27,7 +24,6 @@ public:
virtual QString Icon(); virtual QString Icon();
signals: signals:
void AsyncLoadFinished(const PlaylistItem::SpecialLoadResult& result);
void StreamError(const QString& message); void StreamError(const QString& message);
void StreamMetadataFound(const QUrl& original_url, const Song& song); void StreamMetadataFound(const QUrl& original_url, const Song& song);
void OpenSettingsAtPage(SettingsDialog::Page page); void OpenSettingsAtPage(SettingsDialog::Page page);

View File

@ -52,6 +52,7 @@ appropriately.
CLASS(SongLoader), CLASS(SongLoader),
CLASS(TaskManager), CLASS(TaskManager),
CLASS(UIInterface), CLASS(UIInterface),
CLASS(UrlHandler),
{0, 0} {0, 0}
}; };
#undef CLASS #undef CLASS

View File

@ -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);
};

View File

@ -523,7 +523,6 @@ MainWindow::MainWindow(
// Radio connections // Radio connections
connect(radio_model_, SIGNAL(StreamError(QString)), SLOT(ShowErrorDialog(QString))); 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(StreamMetadataFound(QUrl,Song)), playlists_, SLOT(SetActiveStreamMetadata(QUrl,Song)));
connect(radio_model_, SIGNAL(OpenSettingsAtPage(SettingsDialog::Page)), SLOT(OpenSettingsDialogAtPage(SettingsDialog::Page))); connect(radio_model_, SIGNAL(OpenSettingsAtPage(SettingsDialog::Page)), SLOT(OpenSettingsDialogAtPage(SettingsDialog::Page)));
connect(radio_model_, SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); connect(radio_model_, SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));