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:
parent
044a97720c
commit
255682b057
@ -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>
|
||||
|
3
data/schema/schema-32.sql
Normal file
3
data/schema/schema-32.sql
Normal file
@ -0,0 +1,3 @@
|
||||
UPDATE magnatune_songs SET filename = "magnatune://" || substr(filename, 8);
|
||||
|
||||
UPDATE schema_version SET version=32;
|
@ -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())
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
29
src/core/urlhandler.cpp
Normal 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
76
src/core/urlhandler.h
Normal 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
|
12
src/main.cpp
12
src/main.cpp
@ -374,21 +374,13 @@ int main(int argc, char *argv[]) {
|
||||
database->Start(true);
|
||||
TaskManager task_manager;
|
||||
PlaylistManager playlists(&task_manager, NULL);
|
||||
RadioModel radio_model(database.get(), &task_manager, NULL);
|
||||
Player player(&playlists);
|
||||
RadioModel radio_model(database.get(), &task_manager, &player, NULL);
|
||||
|
||||
// Initialize the repository of cover providers to avoid race conditions
|
||||
// later
|
||||
CoverProviders::instance();
|
||||
|
||||
// Get the last.fm service if it's available
|
||||
LastFMService* lastfm_service = NULL;
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
lastfm_service = RadioModel::Service<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());
|
||||
|
@ -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")
|
||||
|
@ -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(); }
|
||||
|
@ -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) {
|
||||
|
@ -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_;
|
||||
|
48
src/radio/lastfmurlhandler.cpp
Normal file
48
src/radio/lastfmurlhandler.cpp
Normal 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);
|
||||
}
|
43
src/radio/lastfmurlhandler.h
Normal file
43
src/radio/lastfmurlhandler.h
Normal 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
|
@ -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));
|
||||
}
|
||||
|
@ -27,10 +27,7 @@ class MagnatunePlaylistItem : public LibraryPlaylistItem {
|
||||
|
||||
bool InitFromQuery(const SqlRow& query);
|
||||
|
||||
Options options() const;
|
||||
|
||||
QUrl Url() const;
|
||||
SpecialLoadResult StartLoading();
|
||||
};
|
||||
|
||||
#endif // MAGNATUNEPLAYLISTITEM_H
|
||||
|
@ -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:
|
||||
|
@ -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_;
|
||||
|
28
src/radio/magnatuneurlhandler.cpp
Normal file
28
src/radio/magnatuneurlhandler.cpp
Normal 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));
|
||||
}
|
37
src/radio/magnatuneurlhandler.h
Normal file
37
src/radio/magnatuneurlhandler.h
Normal 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
|
@ -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)));
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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_; }
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
76
src/radio/somafmurlhandler.cpp
Normal file
76
src/radio/somafmurlhandler.cpp
Normal 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()));
|
||||
}
|
44
src/radio/somafmurlhandler.h
Normal file
44
src/radio/somafmurlhandler.h
Normal 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
|
@ -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_;
|
||||
}
|
||||
|
@ -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_;
|
||||
|
52
src/radio/spotifyurlhandler.cpp
Normal file
52
src/radio/spotifyurlhandler.cpp
Normal 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)));
|
||||
}
|
37
src/radio/spotifyurlhandler.h
Normal file
37
src/radio/spotifyurlhandler.h
Normal 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
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -52,6 +52,7 @@ appropriately.
|
||||
CLASS(SongLoader),
|
||||
CLASS(TaskManager),
|
||||
CLASS(UIInterface),
|
||||
CLASS(UrlHandler),
|
||||
{0, 0}
|
||||
};
|
||||
#undef CLASS
|
||||
|
48
src/scripting/python/urlhandler.sip
Normal file
48
src/scripting/python/urlhandler.sip
Normal 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);
|
||||
};
|
@ -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*)));
|
||||
|
Loading…
x
Reference in New Issue
Block a user