Get GrooveShark songs' length (give the ability to seek through stream). markStream and markSongComplete, as resquested by GrooveShark

This commit is contained in:
Arnaud Bienner 2011-10-02 12:05:56 +02:00
parent f142279a0a
commit 4143823870
8 changed files with 147 additions and 34 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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