mirror of
https://github.com/clementine-player/Clementine
synced 2024-12-17 12:02:48 +01:00
Get GrooveShark songs' length (give the ability to seek through stream). markStream and markSongComplete, as resquested by GrooveShark
This commit is contained in:
parent
f142279a0a
commit
4143823870
@ -104,6 +104,14 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult& result) {
|
||||
qLog(Debug) << "URL handler for" << result.original_url_
|
||||
<< "returned" << result.media_url_;
|
||||
|
||||
// If there was no length info in song's metadata, use the one provided by
|
||||
// URL handler, if there is one
|
||||
if (item->Metadata().length_nanosec() <= 0 && result.length_nanosec_ != -1) {
|
||||
Song song = item->Metadata();
|
||||
song.set_length_nanosec(result.length_nanosec_);
|
||||
item->SetTemporaryMetadata(song);
|
||||
playlists_->active()->UpdateItems(SongList() << song);
|
||||
}
|
||||
engine_->Play(result.media_url_, stream_change_type_,
|
||||
item->Metadata().has_cue(),
|
||||
item->Metadata().beginning_nanosec(),
|
||||
@ -276,6 +284,10 @@ int Player::GetVolume() const {
|
||||
void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle) {
|
||||
if (change == Engine::Manual && engine_->position_nanosec() != engine_->length_nanosec()) {
|
||||
emit TrackSkipped(current_item_);
|
||||
const QUrl& url = current_item_->Url();
|
||||
if (url_handlers_.contains(url.scheme())) {
|
||||
url_handlers_[url.scheme()]->TrackSkipped();
|
||||
}
|
||||
}
|
||||
|
||||
if (current_item_ && current_item_->Metadata().IsOnSameAlbum(
|
||||
@ -427,8 +439,10 @@ void Player::TrackAboutToEnd() {
|
||||
// again immediately after.
|
||||
if (playlists_->active()->current_item()) {
|
||||
const QUrl url = playlists_->active()->current_item()->Url();
|
||||
if (url_handlers_.contains(url.scheme()))
|
||||
if (url_handlers_.contains(url.scheme())) {
|
||||
url_handlers_[url.scheme()]->TrackAboutToEnd();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const bool has_next_row = playlists_->active()->next_row() != -1;
|
||||
|
@ -18,8 +18,8 @@
|
||||
#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)
|
||||
const QUrl& original_url, Type type, const QUrl& media_url, qint64 length_nanosec)
|
||||
: original_url_(original_url), type_(type), media_url_(media_url), length_nanosec_(length_nanosec)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,8 @@ public:
|
||||
|
||||
LoadResult(const QUrl& original_url = QUrl(),
|
||||
Type type = NoMoreTracks,
|
||||
const QUrl& media_url = QUrl());
|
||||
const QUrl& media_url = QUrl(),
|
||||
qint64 length_nanosec_ = -1);
|
||||
|
||||
// The url that the playlist item has in Url().
|
||||
// Might be something unplayable like lastfm://...
|
||||
@ -59,6 +60,9 @@ public:
|
||||
|
||||
// The actual url to something that gstreamer can play.
|
||||
QUrl media_url_;
|
||||
|
||||
// Track length, if we are able to get it only now
|
||||
qint64 length_nanosec_;
|
||||
};
|
||||
|
||||
// Called by the Player when a song starts loading - gives the handler
|
||||
@ -69,6 +73,10 @@ public:
|
||||
// get another track to play.
|
||||
virtual LoadResult LoadNext(const QUrl& url) { return LoadResult(url); }
|
||||
|
||||
// Functions to be warned when something happen to a track handled by UrlHandler.
|
||||
virtual void TrackAboutToEnd() { };
|
||||
virtual void TrackSkipped() { };
|
||||
|
||||
signals:
|
||||
void AsyncLoadComplete(const UrlHandler::LoadResult& result);
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
Copyright 2011, 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
|
||||
@ -156,9 +156,8 @@ void GrooveSharkService::InitCountry() {
|
||||
if (!country_.isEmpty())
|
||||
return;
|
||||
// Get country info
|
||||
QNetworkReply *reply_country;
|
||||
QNetworkReply *reply_country = CreateRequest("getCountry", QList<Param>(), true);
|
||||
|
||||
reply_country = CreateRequest("getCountry", QList<Param>(), true);
|
||||
// Wait for the reply
|
||||
{
|
||||
QEventLoop event_loop;
|
||||
@ -176,14 +175,14 @@ void GrooveSharkService::InitCountry() {
|
||||
country_ = ExtractResult(reply_country);
|
||||
}
|
||||
|
||||
QUrl GrooveSharkService::GetStreamingUrlFromSongId(const QString& song_id) {
|
||||
QUrl GrooveSharkService::GetStreamingUrlFromSongId(const QString& song_id,
|
||||
QString* server_id, QString* stream_key, qint64* length_nanosec) {
|
||||
QList<Param> parameters;
|
||||
QNetworkReply *reply;
|
||||
|
||||
InitCountry();
|
||||
parameters << Param("songID", song_id)
|
||||
<< Param("country", country_);
|
||||
reply = CreateRequest("getSubscriberStreamKey", parameters, true);
|
||||
QNetworkReply* reply = CreateRequest("getSubscriberStreamKey", parameters, true);
|
||||
// Wait for the reply
|
||||
{
|
||||
QEventLoop event_loop;
|
||||
@ -199,6 +198,11 @@ QUrl GrooveSharkService::GetStreamingUrlFromSongId(const QString& song_id) {
|
||||
timeout_timer.stop();
|
||||
}
|
||||
QVariantMap result = ExtractResult(reply);
|
||||
server_id->clear();
|
||||
server_id->append(result["StreamServerID"].toString());
|
||||
stream_key->clear();
|
||||
stream_key->append(result["StreamKey"].toString());
|
||||
*length_nanosec = result["uSecs"].toLongLong() * 1000;
|
||||
|
||||
return QUrl(result["url"].toString());
|
||||
}
|
||||
@ -211,9 +215,7 @@ void GrooveSharkService::Login(const QString& username, const QString& password)
|
||||
password_ = QCryptographicHash::hash(password.toLocal8Bit(), QCryptographicHash::Md5).toHex();
|
||||
|
||||
QList<Param> parameters;
|
||||
QNetworkReply *reply;
|
||||
|
||||
reply = CreateRequest("startSession", parameters, false, true);
|
||||
QNetworkReply *reply = CreateRequest("startSession", parameters, false, true);
|
||||
|
||||
connect(reply, SIGNAL(finished()), SLOT(SessionCreated()));
|
||||
}
|
||||
@ -237,13 +239,10 @@ void GrooveSharkService::SessionCreated() {
|
||||
|
||||
void GrooveSharkService::AuthenticateSession() {
|
||||
QList<Param> parameters;
|
||||
QNetworkReply *reply;
|
||||
|
||||
parameters << Param("login", username_)
|
||||
<< Param("password", password_);
|
||||
|
||||
reply = CreateRequest("authenticate", parameters, true, true);
|
||||
|
||||
QNetworkReply *reply = CreateRequest("authenticate", parameters, true, true);
|
||||
connect(reply, SIGNAL(finished()), SLOT(Authenticated()));
|
||||
}
|
||||
|
||||
@ -333,9 +332,7 @@ void GrooveSharkService::EnsureConnected() {
|
||||
}
|
||||
|
||||
void GrooveSharkService::RetrieveUserPlaylists() {
|
||||
QNetworkReply *reply;
|
||||
|
||||
reply = CreateRequest("getUserPlaylists", QList<Param>(), true);
|
||||
QNetworkReply* reply = CreateRequest("getUserPlaylists", QList<Param>(), true);
|
||||
|
||||
connect(reply, SIGNAL(finished()), SLOT(UserPlaylistsRetrieved()));
|
||||
}
|
||||
@ -396,6 +393,53 @@ void GrooveSharkService::PlaylistSongsRetrieved() {
|
||||
root_->appendRow(item);
|
||||
}
|
||||
|
||||
|
||||
void GrooveSharkService::MarkStreamKeyOver30Secs(const QString& stream_key,
|
||||
const QString& server_id) {
|
||||
QList<Param> parameters;
|
||||
parameters << Param("streamKey", stream_key)
|
||||
<< Param("streamServerID", server_id);
|
||||
|
||||
QNetworkReply* reply = CreateRequest("markStreamKeyOver30Secs", parameters, true, false);
|
||||
connect(reply, SIGNAL(finished()), SLOT(StreamMarked()));
|
||||
}
|
||||
|
||||
void GrooveSharkService::StreamMarked() {
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
if (!reply)
|
||||
return;
|
||||
|
||||
reply->deleteLater();
|
||||
QVariantMap result = ExtractResult(reply);
|
||||
if (!result["success"].toBool()) {
|
||||
qLog(Warning) << "GrooveShark markStreamKeyOver30Secs failed";
|
||||
}
|
||||
}
|
||||
|
||||
void GrooveSharkService::MarkSongComplete(const QString& song_id,
|
||||
const QString& stream_key,
|
||||
const QString& server_id) {
|
||||
QList<Param> parameters;
|
||||
parameters << Param("songID", song_id)
|
||||
<< Param("streamKey", stream_key)
|
||||
<< Param("streamServerID", server_id);
|
||||
|
||||
QNetworkReply* reply = CreateRequest("markSongComplete", parameters, true, false);
|
||||
connect(reply, SIGNAL(finished()), SLOT(SongMarkedAsComplete()));
|
||||
}
|
||||
|
||||
void GrooveSharkService::SongMarkedAsComplete() {
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
if (!reply)
|
||||
return;
|
||||
|
||||
reply->deleteLater();
|
||||
QVariantMap result = ExtractResult(reply);
|
||||
if (!result["success"].toBool()) {
|
||||
qLog(Warning) << "GrooveShark markSongComplete failed";
|
||||
}
|
||||
}
|
||||
|
||||
void GrooveSharkService::OpenSearchTab() {
|
||||
model()->player()->playlists()->New(tr("Search GrooveShark"), SongList(),
|
||||
GrooveSharkSearchPlaylistType::kName);
|
||||
@ -411,7 +455,6 @@ QNetworkReply* GrooveSharkService::CreateRequest(const QString& method_name, QLi
|
||||
bool need_authentication,
|
||||
bool use_https) {
|
||||
QVariantMap request_params;
|
||||
|
||||
request_params.insert("method", method_name);
|
||||
|
||||
QVariantMap header;
|
||||
@ -470,7 +513,6 @@ SongList GrooveSharkService::ExtractSongs(const QVariantMap& result) {
|
||||
QString album_name = result_song["AlbumName"].toString();
|
||||
QString cover = result_song["CoverArtFilename"].toString();
|
||||
song.Init(song_name, artist_name, album_name, 0);
|
||||
song.set_id(song_id);
|
||||
song.set_art_automatic(QString(kUrlCover) + cover);
|
||||
// Special kind of URL: because we need to request a stream key for each
|
||||
// play, we generate a fake URL for now, and we will create a real streaming
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
Copyright 2011, 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
|
||||
@ -57,11 +57,15 @@ class GrooveSharkService : public InternetService {
|
||||
|
||||
void Search(const QString& text, Playlist* playlist, bool now = false);
|
||||
// User should be logged in to be able to generate streaming urls
|
||||
QUrl GetStreamingUrlFromSongId(const QString& song_id);
|
||||
QUrl GetStreamingUrlFromSongId(const QString& song_id,
|
||||
QString* server_id, QString* stream_key,
|
||||
qint64* length_nanosec);
|
||||
void Login(const QString& username, const QString& password);
|
||||
void Logout();
|
||||
bool IsLoggedIn() { return !session_id_.isEmpty(); }
|
||||
void RetrieveUserPlaylists();
|
||||
void MarkStreamKeyOver30Secs(const QString& stream_key, const QString& server_id);
|
||||
void MarkSongComplete(const QString& song_id, const QString& stream_key, const QString& server_id);
|
||||
|
||||
// Persisted in the settings and updated on each Login().
|
||||
LoginState login_state() const { return login_state_; }
|
||||
@ -105,6 +109,8 @@ class GrooveSharkService : public InternetService {
|
||||
void Authenticated();
|
||||
void UserPlaylistsRetrieved();
|
||||
void PlaylistSongsRetrieved();
|
||||
void StreamMarked();
|
||||
void SongMarkedAsComplete();
|
||||
|
||||
private:
|
||||
void EnsureMenuCreated();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
Copyright 2011, 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
|
||||
@ -15,21 +15,55 @@
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "groovesharkservice.h"
|
||||
#include "groovesharkurlhandler.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#include "groovesharkservice.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
|
||||
GrooveSharkUrlHandler::GrooveSharkUrlHandler(GrooveSharkService* service, QObject* parent)
|
||||
: UrlHandler(parent),
|
||||
service_(service) {
|
||||
service_(service),
|
||||
timer_mark_stream_key_(new QTimer(this)) {
|
||||
// We have to warn GrooveShark when user has listened for more than 30
|
||||
// seconds of a song, and when it ends. I guess this is used by GrooveShark
|
||||
// for statistics and user history.
|
||||
// To do this, we have TrackAboutToEnd method, and timer_mark_stream_key_ timer.
|
||||
// It is not perfect, as we may call GrooveShark MarkStreamKeyOver30Secs even
|
||||
// if user hasn't actually listen to 30 seconds (e.g. stream set to pause
|
||||
// state) but this is not a big deal and it should be accurate enough anyway.
|
||||
timer_mark_stream_key_->setInterval(30000);
|
||||
timer_mark_stream_key_->setSingleShot(true);
|
||||
connect(timer_mark_stream_key_, SIGNAL(timeout()), SLOT(MarkStreamKeyOver30Secs()));
|
||||
}
|
||||
|
||||
UrlHandler::LoadResult GrooveSharkUrlHandler::StartLoading(const QUrl& url) {
|
||||
QString song_id = url.toString().remove("grooveshark://");
|
||||
QUrl streaming_url = service_->GetStreamingUrlFromSongId(song_id);
|
||||
qint64 length_nanosec;
|
||||
last_song_id_ = url.toString().remove("grooveshark://");
|
||||
|
||||
QUrl streaming_url = service_->GetStreamingUrlFromSongId(last_song_id_,
|
||||
&last_server_id_, &last_stream_key_, &length_nanosec);
|
||||
qLog(Debug) << "GrooveShark Streaming URL: " << streaming_url;
|
||||
return LoadResult(url, LoadResult::TrackAvailable, streaming_url);
|
||||
|
||||
timer_mark_stream_key_->start();
|
||||
|
||||
return LoadResult(url, LoadResult::TrackAvailable, streaming_url, length_nanosec);
|
||||
}
|
||||
|
||||
void GrooveSharkUrlHandler::TrackAboutToEnd() {
|
||||
if (timer_mark_stream_key_->isActive()) {
|
||||
timer_mark_stream_key_->stop();
|
||||
return;
|
||||
}
|
||||
service_->MarkSongComplete(last_song_id_, last_stream_key_, last_server_id_);
|
||||
}
|
||||
|
||||
void GrooveSharkUrlHandler::TrackSkipped() {
|
||||
timer_mark_stream_key_->stop();
|
||||
}
|
||||
|
||||
void GrooveSharkUrlHandler::MarkStreamKeyOver30Secs() {
|
||||
service_->MarkStreamKeyOver30Secs(last_stream_key_, last_server_id_);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
Copyright 2011, 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
|
||||
@ -21,19 +21,27 @@
|
||||
#include "core/urlhandler.h"
|
||||
|
||||
class GrooveSharkService;
|
||||
class QTimer;
|
||||
|
||||
class GrooveSharkUrlHandler : public UrlHandler {
|
||||
Q_OBJECT
|
||||
public:
|
||||
GrooveSharkUrlHandler(GrooveSharkService* service, QObject* parent);
|
||||
|
||||
QString scheme() const { return "grooveshark"; }
|
||||
LoadResult StartLoading(const QUrl& url);
|
||||
void TrackAboutToEnd();
|
||||
void TrackSkipped();
|
||||
|
||||
private slots:
|
||||
void MarkStreamKeyOver30Secs();
|
||||
|
||||
private:
|
||||
GrooveSharkService* service_;
|
||||
QTimer* timer_mark_stream_key_;
|
||||
QString last_song_id_;
|
||||
QString last_server_id_;
|
||||
QString last_stream_key;
|
||||
QString last_stream_key_;
|
||||
};
|
||||
|
||||
#endif // GROOVESHARKURLHANDLER_H
|
||||
|
@ -969,9 +969,10 @@ void Playlist::UpdateItems(const SongList& songs) {
|
||||
// Update current items list
|
||||
for (int i=0; i<items_.size(); i++) {
|
||||
PlaylistItemPtr item = items_[i];
|
||||
|
||||
if (item->Metadata().url() == song.url() &&
|
||||
item->Metadata().filetype() == Song::Type_Unknown) {
|
||||
(item->Metadata().filetype() == Song::Type_Unknown ||
|
||||
// Stream may change and may need to be updated too
|
||||
item->Metadata().filetype() == Song::Type_Stream)) {
|
||||
PlaylistItemPtr new_item;
|
||||
if (song.id() == -1) {
|
||||
new_item = PlaylistItemPtr(new SongPlaylistItem(song));
|
||||
|
Loading…
Reference in New Issue
Block a user