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-30.sql</file>
<file>schema/schema-31.sql</file>
<file>schema/schema-32.sql</file>
</qresource>
</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 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())

View File

@ -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

View File

@ -32,7 +32,7 @@
#include <QVariant>
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;

View File

@ -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) {

View File

@ -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<LastFMService>();
#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<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 "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<QString, UrlHandler*> url_handlers_;
QUrl loading_async_;
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);
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<LastFMService>();
#endif
// Create the player
Player player(&playlists, lastfm_service);
// Create the tray icon and OSD
scoped_ptr<SystemTrayIcon> tray_icon(SystemTrayIcon::CreateSystemTrayIcon());
OSD osd(tray_icon.get());

View File

@ -30,11 +30,6 @@
#include <QtConcurrentRun>
#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) {
if (type == "Library")

View File

@ -41,55 +41,14 @@ class PlaylistItem : public boost::enable_shared_from_this<PlaylistItem> {
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<PlaylistItem> {
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(); }

View File

@ -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<QNetworkReply*>(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<QNetworkReply*>(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) {

View File

@ -42,12 +42,14 @@ uint qHash(const lastfm::Track& track);
#include <boost/scoped_ptr.hpp>
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_;

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 "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<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);
Options options() const;
QUrl Url() const;
SpecialLoadResult StartLoading();
};
#endif // MAGNATUNEPLAYLISTITEM_H

View File

@ -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:

View File

@ -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_;

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;
RadioModel::RadioModel(BackgroundThread<Database>* 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<QString, RadioService*>;
@ -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)));

View File

@ -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<Database>* 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<Database>* 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<Database>* db_thread_;
MergedProxyModel* merged_model_;
TaskManager* task_manager_;
PlayerInterface* player_;
};
#endif // RADIOMODEL_H

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <QCoreApplication>
#include <QDesktopServices>
#include <QMenu>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QXmlStreamReader>
#include <QSettings>
#include <QTemporaryFile>
#include <QMenu>
#include <QDesktopServices>
#include <QCoreApplication>
#include <QtDebug>
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<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() {
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;
}

View File

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

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 "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 <QProcess>
#include <QSettings>
#include <QSortFilterProxyModel>
#include <QTcpServer>
#include <QTemporaryFile>
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<SpotifyService*>(this)->EnsureServerCreated();
return server_;
}

View File

@ -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_;

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 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

View File

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

View File

@ -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<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;
%Docstring
type() -> str

View File

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

View File

@ -52,6 +52,7 @@ appropriately.
CLASS(SongLoader),
CLASS(TaskManager),
CLASS(UIInterface),
CLASS(UrlHandler),
{0, 0}
};
#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
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*)));