banshee r3201 - in branches/banshee/stable: . src/Plugins/Banshee.Plugins.Audioscrobbler
- From: rubenv svn gnome org
- To: svn-commits-list gnome org
- Subject: banshee r3201 - in branches/banshee/stable: . src/Plugins/Banshee.Plugins.Audioscrobbler
- Date: Sun, 10 Feb 2008 14:59:45 +0000 (GMT)
Author: rubenv
Date: Sun Feb 10 14:59:45 2008
New Revision: 3201
URL: http://svn.gnome.org/viewvc/banshee?rev=3201&view=rev
Log:
2008-02-10 Ruben Vermeersch <ruben savanne be>
* src/Plugins/Banshee.Plugins.Audioscrobbler/AudioscrobblerPlugin.cs:
* src/Plugins/Banshee.Plugins.Audioscrobbler/Engine.cs:
* src/Plugins/Banshee.Plugins.Audioscrobbler/Queue.cs: Updated to the
new audioscrobbler protocol (1.2) and connection failure guidelines.
(BGO: #404965, #501405). Add connection time-out. (BGO: #469490).
Patch by Pepijn van de Geer (pvandegeer gmail com).
Modified:
branches/banshee/stable/ChangeLog
branches/banshee/stable/src/Plugins/Banshee.Plugins.Audioscrobbler/AudioscrobblerPlugin.cs
branches/banshee/stable/src/Plugins/Banshee.Plugins.Audioscrobbler/Engine.cs
branches/banshee/stable/src/Plugins/Banshee.Plugins.Audioscrobbler/Queue.cs
Modified: branches/banshee/stable/src/Plugins/Banshee.Plugins.Audioscrobbler/AudioscrobblerPlugin.cs
==============================================================================
--- branches/banshee/stable/src/Plugins/Banshee.Plugins.Audioscrobbler/AudioscrobblerPlugin.cs (original)
+++ branches/banshee/stable/src/Plugins/Banshee.Plugins.Audioscrobbler/AudioscrobblerPlugin.cs Sun Feb 10 14:59:45 2008
@@ -75,7 +75,8 @@
get {
return new string [] {
"Chris Toshok",
- "Aaron Bockover"
+ "Aaron Bockover",
+ "Pepijn van de Geer"
};
}
}
Modified: branches/banshee/stable/src/Plugins/Banshee.Plugins.Audioscrobbler/Engine.cs
==============================================================================
--- branches/banshee/stable/src/Plugins/Banshee.Plugins.Audioscrobbler/Engine.cs (original)
+++ branches/banshee/stable/src/Plugins/Banshee.Plugins.Audioscrobbler/Engine.cs Sun Feb 10 14:59:45 2008
@@ -56,15 +56,18 @@
const int TICK_INTERVAL = 2000; /* 2 seconds */
const int FAILURE_LOG_MINUTES = 5; /* 5 minute delay on logging failure to upload information */
const int RETRY_SECONDS = 60; /* 60 second delay for transmission retries */
+ const int MAX_RETRY_SECONDS = 7200; /* 2 hours, as defined in the last.fm protocol */
+ const int TIME_OUT = 5000; /* 5 seconds timeout for webrequests */
const string CLIENT_ID = "bsh";
const string CLIENT_VERSION = "0.1";
const string SCROBBLER_URL = "http://post.audioscrobbler.com/";
- const string SCROBBLER_VERSION = "1.1";
+ const string SCROBBLER_VERSION = "1.2";
string username;
string md5_pass;
+ string session_id = null;
string post_url;
- string security_token;
+ string now_playing_url;
uint timeout_id;
DateTime next_interval;
@@ -72,10 +75,17 @@
Queue queue;
+ int hard_failures = 0;
+ int hard_failure_retry_sec = 60;
+
+ bool now_playing_submitted;
bool song_started; /* if we were watching the current song from the beginning */
bool queued; /* if current_track has been queued */
- bool sought; /* if the user has sought in the current playing song */
+ DateTime song_start_time;
+ TrackInfo last_track;
+
+ WebRequest now_playing_post;
WebRequest current_web_req;
IAsyncResult current_async_result;
State state;
@@ -91,6 +101,7 @@
{
song_started = false;
PlayerEngineCore.EventChanged += OnPlayerEngineEventChanged;
+ PlayerEngineCore.StateChanged += OnPlayerEngineStateChanged;
queue.TrackAdded += delegate(object o, EventArgs args) {
StartTransitionHandler ();
};
@@ -106,8 +117,12 @@
public void Stop ()
{
+ // Queue the current track for later submission
+ Queue(PlayerEngineCore.CurrentTrack);
+
PlayerEngineCore.EventChanged -= OnPlayerEngineEventChanged;
-
+ PlayerEngineCore.StateChanged -= OnPlayerEngineStateChanged;
+
StopTransitionHandler ();
if (current_web_req != null) {
@@ -133,8 +148,8 @@
this.username = username;
this.md5_pass = MD5Encode (pass);
- if (security_token != null) {
- security_token = null;
+ if (session_id != null) {
+ session_id = null;
state = State.NEED_HANDSHAKE;
}
}
@@ -151,38 +166,59 @@
return CryptoConvert.ToHex (hash).ToLower ();
}
+ void OnPlayerEngineStateChanged(object o, PlayerEngineStateArgs args)
+ {
+ if (PlayerEngineCore.CurrentState == PlayerEngineState.Paused &&
+ PlayerEngineCore.LastState == PlayerEngineState.Playing) {
+ st.Stop();
+ }
+ else if (PlayerEngineCore.CurrentState == PlayerEngineState.Playing &&
+ PlayerEngineCore.LastState == PlayerEngineState.Paused) {
+ st.Start();
+ }
+ }
+
+ // We need to time how long the song has played
+ class SongTimer
+ {
+ private DateTime start_time;
+ public int PlayTime = 0;
+ public void Start() { start_time = DateTime.Now; }
+ public void Stop() { PlayTime += (int) (DateTime.Now - start_time).TotalSeconds;}
+ public void Reset() { PlayTime = 0; }
+ }
+
+ SongTimer st = new SongTimer();
+
+ void Queue (TrackInfo track) {
+ if (song_started && !queued && track.Duration.TotalSeconds > 30 &&
+ track.Artist != "" && track.Title != "" &&
+ (st.PlayTime > track.Duration.TotalSeconds / 2 || st.PlayTime > 240)) {
+ queue.Add (track, song_start_time);
+ queued = true;
+ }
+ }
+
void OnPlayerEngineEventChanged(object o, PlayerEngineEventArgs args)
{
switch (args.Event) {
- /* Queue if we're watching this song from the beginning,
- * it isn't queued yet and the user didn't seek until now,
- * we're actually playing, song position and length are greater than 0
- * and we already played half of the song or 240 seconds */
- case PlayerEngineEvent.Iterate:
- if (song_started && !queued && !sought && PlayerEngineCore.CurrentState == PlayerEngineState.Playing &&
- PlayerEngineCore.Length > 0 && PlayerEngineCore.Position > 0 &&
- (PlayerEngineCore.Position > PlayerEngineCore.Length / 2 || PlayerEngineCore.Position > 240)) {
- TrackInfo track = PlayerEngineCore.CurrentTrack;
- if (track == null) {
- queued = sought = false;
- } else {
- queue.Add (track, DateTime.Now - TimeSpan.FromSeconds (PlayerEngineCore.Position));
- queued = true;
- }
- }
- break;
- /* Start of Stream: new song started */
case PlayerEngineEvent.StartOfStream:
- queued = sought = false;
+ // Queue the previous track in case of a skip
+ st.Stop();
+ Queue(last_track);
+
+ st.Reset(); st.Start();
+ song_start_time = DateTime.Now;
+ last_track = PlayerEngineCore.CurrentTrack;
+ now_playing_submitted = queued = false;
song_started = true;
+
+ StartTransitionHandler();
break;
- /* End of Stream: song finished */
case PlayerEngineEvent.EndOfStream:
- song_started = queued = sought = false;
- break;
- /* Did the user seek? */
- case PlayerEngineEvent.Seek:
- sought = true;
+ st.Stop();
+ Queue(PlayerEngineCore.CurrentTrack);
+ queued = true;
break;
}
}
@@ -193,17 +229,22 @@
* involving the network. */
if (!Globals.Network.Connected)
return true;
-
+
+ if ((state == State.IDLE || state == State.NEED_TRANSMIT) && hard_failures > 2) {
+ state = State.NEED_HANDSHAKE;
+ hard_failures = 0;
+ }
+
/* and address changes in our engine state */
switch (state) {
case State.IDLE:
- if (queue.Count > 0) {
- if (username != null && md5_pass != null && security_token == null)
- state = State.NEED_HANDSHAKE;
- else
- state = State.NEED_TRANSMIT;
+ if (username != null && md5_pass != null && session_id == null) {
+ state = State.NEED_HANDSHAKE;
} else {
- StopTransitionHandler ();
+ if (queue.Count > 0)
+ state = State.NEED_TRANSMIT;
+ else if (now_playing_submitted)
+ StopTransitionHandler ();
}
break;
case State.NEED_HANDSHAKE:
@@ -222,6 +263,12 @@
/* nothing here */
break;
}
+
+ // Only submit if queue is empty, otherwise the submission
+ // gets overruled by the queue submission by Last.fm
+ if (queue.Count == 0 && !now_playing_submitted && state == State.IDLE
+ && PlayerEngineCore.CurrentState == PlayerEngineState.Playing)
+ NowPlaying(PlayerEngineCore.CurrentTrack);
return true;
}
@@ -251,7 +298,7 @@
StringBuilder sb = new StringBuilder ();
- sb.AppendFormat ("u={0}&s={1}", HttpUtility.UrlEncode (username), security_token);
+ sb.AppendFormat ("s={0}", session_id);
sb.Append (queue.GetTransmitInfo (out num_tracks_transmitted));
@@ -259,29 +306,33 @@
current_web_req.Method = "POST";
current_web_req.ContentType = "application/x-www-form-urlencoded";
current_web_req.ContentLength = sb.Length;
-
+
TransmitState ts = new TransmitState ();
ts.Count = num_tracks_transmitted;
ts.StringBuilder = sb;
state = State.WAITING_FOR_REQ_STREAM;
current_async_result = current_web_req.BeginGetRequestStream (TransmitGetRequestStream, ts);
- if (current_async_result == null) {
+ if (!(current_async_result.AsyncWaitHandle.WaitOne (TIME_OUT, false))) {
+ LogCore.Instance.PushWarning("Audioscrobbler upload failed",
+ "The request timed out and was aborted", false);
next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
+ hard_failures++;
state = State.IDLE;
- }
+ current_web_req.Abort();
+
+ }
}
void TransmitGetRequestStream (IAsyncResult ar)
{
Stream stream;
-
try {
stream = current_web_req.EndGetRequestStream (ar);
}
catch (Exception e) {
- Console.WriteLine ("Failed to get the request stream: {0}", e);
-
+ LogCore.Instance.PushError ("Audioscrobbler upload failed",
+ String.Format("Failed to get the request stream: {0}", e));
state = State.IDLE;
next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
return;
@@ -298,6 +349,7 @@
current_async_result = current_web_req.BeginGetResponse (TransmitGetResponse, ts);
if (current_async_result == null) {
next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
+ hard_failures++;
state = State.IDLE;
}
}
@@ -310,17 +362,15 @@
resp = current_web_req.EndGetResponse (ar);
}
catch (Exception e) {
- Console.WriteLine ("Failed to get the response: {0}", e);
-
+ LogCore.Instance.PushError ("Audioscrobbler upload failed",
+ String.Format("Failed to get the response: {0}", e));
state = State.IDLE;
next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
return;
}
TransmitState ts = (TransmitState) ar.AsyncState;
-
Stream s = resp.GetResponseStream ();
-
StreamReader sr = new StreamReader (s, Encoding.UTF8);
string line;
@@ -333,18 +383,18 @@
last_upload_failed_logged = now;
}
/* retransmit the queue on the next interval */
+ hard_failures++;
state = State.NEED_TRANSMIT;
}
- else if (line.StartsWith ("BADUSER")
- || line.StartsWith ("BADAUTH")) {
+ else if (line.StartsWith ("BADSESSION")) {
if (now - last_upload_failed_logged > TimeSpan.FromMinutes(FAILURE_LOG_MINUTES)) {
- LogCore.Instance.PushWarning ("Audioscrobbler upload failed", "invalid authentication", false);
+ LogCore.Instance.PushWarning ("Audioscrobbler upload failed", "Session ID sent was invalid", false);
last_upload_failed_logged = now;
}
/* attempt to re-handshake (and retransmit) on the next interval */
- security_token = null;
+ session_id = null;
next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
- state = State.IDLE;
+ state = State.NEED_HANDSHAKE;
return;
}
else if (line.StartsWith ("OK")) {
@@ -356,58 +406,64 @@
/* we succeeded, pop the elements off our queue */
queue.RemoveRange (0, ts.Count);
queue.Save ();
+ hard_failures = 0;
state = State.IDLE;
}
else {
if (now - last_upload_failed_logged > TimeSpan.FromMinutes(FAILURE_LOG_MINUTES)) {
- LogCore.Instance.PushDebug ("Audioscrobbler upload failed", String.Format ("Unrecognized response: {0}", line), false);
+ LogCore.Instance.PushWarning("Audioscrobbler upload failed", String.Format ("Unrecognized response: {0}", line), false);
last_upload_failed_logged = now;
}
+ hard_failures++;
state = State.IDLE;
}
-
- /* now get the next interval */
- line = sr.ReadLine ();
- if (line.StartsWith ("INTERVAL")) {
- int interval_seconds = Int32.Parse (line.Substring ("INTERVAL".Length));
- next_interval = DateTime.Now + new TimeSpan (0, 0, interval_seconds);
- }
- else {
- Console.WriteLine ("expected INTERVAL..");
- }
}
//
// Async code for handshaking
//
- void Handshake ()
+ private string UnixTime ()
{
- string uri = String.Format ("{0}?hs=true&p={1}&c={2}&v={3}&u={4}",
+ return ((int) (DateTime.UtcNow - new DateTime (1970, 1, 1)).TotalSeconds).ToString ();
+ }
+
+ void Handshake ()
+ {
+ string timestamp = UnixTime();
+ string security_token = MD5Encode (md5_pass + timestamp);
+
+ string uri = String.Format ("{0}?hs=true&p={1}&c={2}&v={3}&u={4}&t={5}&a={6}",
SCROBBLER_URL,
SCROBBLER_VERSION,
CLIENT_ID, CLIENT_VERSION,
- HttpUtility.UrlEncode (username));
-
+ HttpUtility.UrlEncode (username),
+ timestamp,
+ security_token);
current_web_req = WebRequest.Create (uri);
state = State.WAITING_FOR_HANDSHAKE_RESP;
current_async_result = current_web_req.BeginGetResponse (HandshakeGetResponse, null);
if (current_async_result == null) {
- next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
- state = State.IDLE;
+ next_interval = DateTime.Now + new TimeSpan (0, 0, hard_failure_retry_sec);
+ hard_failures++;
+ if (hard_failure_retry_sec < MAX_RETRY_SECONDS)
+ hard_failure_retry_sec *= 2;
+ state = State.NEED_HANDSHAKE;
}
}
void HandshakeGetResponse (IAsyncResult ar)
{
bool success = false;
+ bool hard_failure = false;
WebResponse resp;
try {
resp = current_web_req.EndGetResponse (ar);
}
catch (Exception e) {
- Console.WriteLine ("failed to handshake: {0}", e);
+ LogCore.Instance.PushError ("Audioscrobbler init failed",
+ String.Format("Failed to handshake: {0}", e));
/* back off for a time before trying again */
state = State.IDLE;
@@ -422,45 +478,113 @@
string line;
line = sr.ReadLine ();
- if (line.StartsWith ("FAILED")) {
- LogCore.Instance.PushWarning ("Audioscrobbler sign-on failed", line.Substring ("FAILED".Length).Trim(), false);
+ if (line.StartsWith ("BANNED")) {
+ LogCore.Instance.PushWarning ("Audioscrobbler sign-on failed", "Player is banned", false);
}
- else if (line.StartsWith ("BADUSER")) {
+ else if (line.StartsWith ("BADAUTH")) {
LogCore.Instance.PushWarning ("Audioscrobbler sign-on failed", "unrecognized user/password", false);
}
- else if (line.StartsWith ("UPDATE")) {
- LogCore.Instance.PushInformation ("Audioscrobbler plugin needs updating",
- String.Format ("Fetch a newer version at {0}\nor update to a newer version of Banshee",
- line.Substring ("UPDATE".Length).Trim()), false);
- success = true;
+ else if (line.StartsWith ("BADTIME")) {
+ LogCore.Instance.PushWarning ("Audioscrobbler sign-on failed",
+ "timestamp provided was not close enough to the current time", false);
+ }
+ else if (line.StartsWith ("FAILED")) {
+ LogCore.Instance.PushWarning ("Audioscrobbler sign-on failed",
+ String.Format ("Temporary server failure: {0}",
+ line.Substring ("FAILED".Length).Trim()), false);
+ hard_failure = true;
}
- else if (line.StartsWith ("UPTODATE")) {
+ else if (line.StartsWith ("OK")) {
success = true;
}
+ else {
+ LogCore.Instance.PushError ("Audioscrobbler sign-on failed",
+ String.Format ("Unknown error: {0}",
+ line.Trim()), false);
+ hard_failure = true;
+ }
- /* read the challenge string and post url, if
- * this was a successful handshake */
if (success == true) {
- string challenge = sr.ReadLine ().Trim ();
+ LogCore.Instance.PushInformation ("Audioscrobbler sign-on succeeded", "Session ID received", false);
+ session_id = sr.ReadLine ().Trim ();
+ now_playing_url = sr.ReadLine ().Trim ();
post_url = sr.ReadLine ().Trim ();
-
- security_token = MD5Encode (md5_pass + challenge);
- //Console.WriteLine ("security token = {0}", security_token);
- }
-
- /* read the trailing interval */
- line = sr.ReadLine ();
- if (line.StartsWith ("INTERVAL")) {
- int interval_seconds = Int32.Parse (line.Substring ("INTERVAL".Length));
- next_interval = DateTime.Now + new TimeSpan (0, 0, interval_seconds);
+ hard_failures = 0;
+ hard_failure_retry_sec = 60;
+
}
- else {
- Console.WriteLine ("expected INTERVAL..");
+ if (hard_failure == true) {
+ next_interval = DateTime.Now + new TimeSpan (0, 0, hard_failure_retry_sec);
+ hard_failures++;
+ if (hard_failure_retry_sec < MAX_RETRY_SECONDS)
+ hard_failure_retry_sec *= 2;
+
}
/* XXX we shouldn't just try to handshake again for BADUSER */
state = success ? State.IDLE : State.NEED_HANDSHAKE;
}
+
+ //
+ // Async code for now playing
+
+ void NowPlaying (TrackInfo track)
+ {
+ if (session_id != null && track.Artist != "" && track.Title != "") {
+
+ string str_track_number = "";
+ if (track.TrackNumber != 0)
+ str_track_number = track.TrackNumber.ToString();
+
+ string uri = String.Format ("{0}?s={1}&a={2}&t={3}&b={4}&l={5}&n={6}&m={7}",
+ now_playing_url,
+ session_id,
+ HttpUtility.UrlEncode(track.Artist),
+ HttpUtility.UrlEncode(track.Title),
+ HttpUtility.UrlEncode(track.Album),
+ track.Duration.TotalSeconds.ToString(),
+ str_track_number,
+ "" /* musicbrainz id */);
+
+ now_playing_post = WebRequest.Create (uri);
+ now_playing_post.Method = "POST";
+ now_playing_post.ContentType = "application/x-www-form-urlencoded";
+ now_playing_post.ContentLength = uri.Length;
+ now_playing_post.BeginGetResponse (NowPlayingGetResponse, null);
+ now_playing_submitted = true;
+ }
+ }
+
+ void NowPlayingGetResponse (IAsyncResult ar)
+ {
+ try {
+
+ WebResponse my_resp = now_playing_post.EndGetResponse (ar);
+
+ Stream s = my_resp.GetResponseStream ();
+ StreamReader sr = new StreamReader (s, Encoding.UTF8);
+
+ string line = sr.ReadLine ();
+ if (line.StartsWith ("BADSESSION")) {
+ LogCore.Instance.PushWarning ("Audioscrobbler NowPlaying failed", "Session ID sent was invalid", false);
+ /* attempt to re-handshake on the next interval */
+ session_id = null;
+ next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
+ state = State.NEED_HANDSHAKE;
+ return;
+ }
+ else if (line.StartsWith ("OK")) {
+ // NowPlaying submitted
+ }
+ else {
+ LogCore.Instance.PushWarning ("Audioscrobbler NowPlaying failed", "Unexpected or no response", false);
+ }
+ }
+ catch (Exception e) {
+ LogCore.Instance.PushError ("Audioscrobbler NowPlaying failed",
+ String.Format("Failed to post NowPlaying: {0}", e));
+ }
+ }
}
}
Modified: branches/banshee/stable/src/Plugins/Banshee.Plugins.Audioscrobbler/Queue.cs
==============================================================================
--- branches/banshee/stable/src/Plugins/Banshee.Plugins.Audioscrobbler/Queue.cs (original)
+++ branches/banshee/stable/src/Plugins/Banshee.Plugins.Audioscrobbler/Queue.cs Sun Feb 10 14:59:45 2008
@@ -50,21 +50,23 @@
this.artist = track.Artist;
this.album = track.Album;
this.title = track.Title;
- this.duration = (int)track.Duration.TotalSeconds;
- this.start_time = start_time.ToUniversalTime ();
+ this.track_number = (int) track.TrackNumber;
+ this.duration = (int) track.Duration.TotalSeconds;
+ this.start_time = DateTimeUtil.ToTimeT(start_time.ToUniversalTime ());
}
public QueuedTrack (string artist, string album,
- string title, int duration, DateTime start_time)
+ string title, int track_number, int duration, long start_time)
{
this.artist = artist;
this.album = album;
this.title = title;
+ this.track_number = track_number;
this.duration = duration;
this.start_time = start_time;
}
- public DateTime StartTime {
+ public long StartTime {
get { return start_time; }
}
public string Artist {
@@ -73,6 +75,9 @@
public string Album {
get { return album; }
}
+ public int TrackNumber {
+ get { return track_number; }
+ }
public string Title {
get { return title; }
}
@@ -83,8 +88,9 @@
string artist;
string album;
string title;
+ int track_number;
int duration;
- DateTime start_time;
+ long start_time;
}
ArrayList queue;
@@ -120,8 +126,9 @@
writer.WriteElementString ("Artist", track.Artist);
writer.WriteElementString ("Album", track.Album);
writer.WriteElementString ("Title", track.Title);
+ writer.WriteElementString ("TrackNumber", track.TrackNumber.ToString());
writer.WriteElementString ("Duration", track.Duration.ToString());
- writer.WriteElementString ("StartTime", DateTimeUtil.ToTimeT(track.StartTime).ToString());
+ writer.WriteElementString ("StartTime", track.StartTime.ToString());
writer.WriteEndElement (); // Track
}
writer.WriteEndElement (); // AudioscrobblerQueue
@@ -144,8 +151,9 @@
string artist = "";
string album = "";
string title = "";
+ int track_number = 0;
int duration = 0;
- DateTime start_time = new DateTime (0);
+ long start_time = 0;
foreach (XmlNode child in node.ChildNodes) {
if (child.Name == "Artist" && child.ChildNodes.Count != 0) {
@@ -154,15 +162,16 @@
album = child.ChildNodes [0].Value;
} else if (child.Name == "Title" && child.ChildNodes.Count != 0) {
title = child.ChildNodes [0].Value;
+ } else if (child.Name == "TrackNumber" && child.ChildNodes.Count != 0) {
+ track_number = Convert.ToInt32 (child.ChildNodes [0].Value);
} else if (child.Name == "Duration" && child.ChildNodes.Count != 0) {
duration = Convert.ToInt32 (child.ChildNodes [0].Value);
} else if (child.Name == "StartTime" && child.ChildNodes.Count != 0) {
- long time = Convert.ToInt64 (child.ChildNodes [0].Value);
- start_time = DateTimeUtil.FromTimeT (time);
+ start_time = Convert.ToInt64 (child.ChildNodes [0].Value);
}
}
- queue.Add (new QueuedTrack (artist, album, title, duration, start_time));
+ queue.Add (new QueuedTrack (artist, album, title, track_number, duration, start_time));
}
} catch {
}
@@ -170,24 +179,31 @@
public string GetTransmitInfo (out int num_tracks)
{
+ string str_track_number = "";
StringBuilder sb = new StringBuilder ();
-
+
int i;
for (i = 0; i < queue.Count; i ++) {
- /* we queue a maximum of 10 tracks per request */
- if (i == 9) break;
+ /* Last.FM 1.2 can handle up to 50 songs in one request */
+ if (i == 49) break;
QueuedTrack track = (QueuedTrack)queue[i];
+ if (track.TrackNumber != 0)
+ str_track_number = track.TrackNumber.ToString();
+
sb.AppendFormat (
- "&a[{6}]={0}&t[{6}]={1}&b[{6}]={2}&m[{6}]={3}&l[{6}]={4}&i[{6}]={5}",
- HttpUtility.UrlEncode (track.Artist),
- HttpUtility.UrlEncode (track.Title),
- HttpUtility.UrlEncode (track.Album),
- "" /* musicbrainz id */,
- track.Duration.ToString (),
- HttpUtility.UrlEncode (track.StartTime.ToString ("yyyy-MM-dd HH:mm:ss")),
- i);
+ "&a[{9}]={0}&t[{9}]={1}&i[{9}]={2}&o[{9}]={3}&r[{9}]={4}&l[{9}]={5}&b[{9}]={6}&n[{9}]={7}&m[{9}]={8}",
+ HttpUtility.UrlEncode (track.Artist),
+ HttpUtility.UrlEncode (track.Title),
+ track.StartTime.ToString (),
+ "P" /* source: chosen by user */,
+ "" /* rating: L/B/S */,
+ track.Duration.ToString (),
+ HttpUtility.UrlEncode (track.Album),
+ str_track_number,
+ "" /* musicbrainz id */,
+ i);
}
num_tracks = i;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]