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