Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

add end of track event

  • Loading branch information...
commit fdfd85824cd8feeed00c94545f0bc4182fe8f49c 1 parent 12f61e7
@Floby authored
View
6 lib/Player.js
@@ -16,6 +16,9 @@ function Player (session) {
this._session._sp_session.music_delivery = function(buffer) {
self.emit('data', buffer);
}
+ this._session._sp_session.end_of_track = function() {
+ self.emit('track-end');
+ }
this.readable = true;
}
util.inherits(Player, EventEmitter);
@@ -34,5 +37,8 @@ Player.prototype.play = function play() {
Player.prototype.stop = function stop() {
b.session_player_play(this._session._sp_session, false);
};
+Player.prototype.flush = function flush() {
+ b.player_flush_audio(this._session._sp_session);
+};
module.exports = Player;
View
11 lib/Search.js
@@ -23,6 +23,15 @@ function Search (session, query) {
this._query = query || (typeof session == 'string') ? session : null;
+ this.trackOffset = 0,
+ this.trackCount = 10,
+ this.albumOffset = 0,
+ this.albumCount = 0,
+ this.artistOffset = 0,
+ this.artistCount = 0,
+ this.playlistOffset = 0,
+ this.playlistCount = 0
+
this._sp_search = null;
}
util.inherits(Search, EventEmitter);
@@ -36,7 +45,7 @@ Search.prototype.execute = function execute(cb) {
this._session._sp_session,
this._query,
this.trackOffset || 0,
- this.trackCount || 1,
+ this.trackCount || 10,
this.albumOffset || 0,
this.albumCount || 0,
this.artistOffset || 0,
View
12 lib/Track.js
@@ -5,6 +5,9 @@ var EventEmitter = require('events').EventEmitter;
function Track (sp_track) {
this._sp_track = sp_track;
+ if(this.isReady()) {
+ this._populateAttributes();
+ }
}
util.inherits(Track, EventEmitter);
@@ -12,5 +15,14 @@ Track.prototype.isReady = function isReady() {
return b.track_is_loaded(this._sp_track);
};
+Track.prototype._populateAttributes = function _populateAttributes() {
+ var dms = this.duration = b.track_duration(this._sp_track);
+ // get seconds instead
+ dms = Math.floor(dms / 1000);
+ var min = Math.floor(dms / 60);
+ var sec = (dms % 60); if(sec < 10) sec = '0'+sec;
+ this.humanDuration = min+':'+sec;
+};
+
module.exports = Track;
View
8 src/audio.cc
@@ -28,23 +28,31 @@
#include "audio.h"
#include <stdlib.h>
+/**
+ * get an audio data chunk from the fifo
+ */
audio_fifo_data_t* audio_get(audio_fifo_t *af)
{
+ // if the queue is empty, do nothing
if(af->qlen == 0) {
return 0;
}
audio_fifo_data_t *afd;
+ // make sure we're the only one using the queue right now
pthread_mutex_lock(&af->mutex);
+ // if the queue is empty, do nothing
afd = TAILQ_FIRST(&af->q);
if(!afd) {
pthread_mutex_unlock(&af->mutex);
return NULL;
}
+ // get the data at the head of the queue
TAILQ_REMOVE(&af->q, afd, link);
af->qlen -= afd->nsamples;
+ // we're done using the queue
pthread_mutex_unlock(&af->mutex);
return afd;
}
View
5 src/audio.h
@@ -32,12 +32,17 @@
#include "queue.h"
+// we don't need much more about spotify session than what a pointer looks like
+typedef struct sp_session sp_session;
+
+
/* --- Types --- */
typedef struct audio_fifo_data {
TAILQ_ENTRY(audio_fifo_data) link;
int channels;
int rate;
int nsamples;
+ sp_session* session;
int16_t samples[0];
} audio_fifo_data_t;
View
32 src/player.cc
@@ -28,45 +28,41 @@ using namespace node;
// one second buffer
-// we make a circular buffer
-static int16_t buffer[BUFFER_SIZE];
-static unsigned int read_index = 0;
-static unsigned int write_index = 0;
-static bool buffer_full = false;
static audio_fifo_t g_audiofifo;
-static sp_session* current_session = NULL;
-
/**
* spotify callback for the music_delivery event.
* See https://developer.spotify.com/technologies/libspotify/docs/12.1.45/structsp__session__callbacks.html
+ * In this, we are called from an internal spotify thread. We buffer the data in the audio_fifo structure
*/
extern int call_music_delivery_callback(sp_session* session, const sp_audioformat *format, const void *frames, int num_frames) {
audio_fifo_t *af = &g_audiofifo;
audio_fifo_data_t *afd;
size_t s;
- current_session = session;
if (num_frames == 0)
return 0; // Audio discontinuity, do nothing
+ // make sure we're the only one using the queue
pthread_mutex_lock(&af->mutex);
/* Buffer one second of audio */
+ // if more, tell spotify we didn't use any samples
if (af->qlen > format->sample_rate) {
pthread_mutex_unlock(&af->mutex);
return 0;
}
+ // we calculate the memory we need to allocate for these samples
s = num_frames * sizeof(int16_t) * format->channels;
afd = (audio_fifo_data_t*) malloc(sizeof(*afd) + s);
memcpy(afd->samples, frames, s);
afd->nsamples = num_frames;
-
+ afd->session = session;
afd->rate = format->sample_rate;
afd->channels = format->channels;
@@ -91,21 +87,21 @@ static void read_delivered_music(uv_idle_t* handle, int status) {
return;
}
- sp_session* spsession = current_session;
- ObjectHandle<sp_session>* session = (ObjectHandle<sp_session>*) sp_session_userdata(spsession);
-
- Handle<Value> cbv = session->object->Get(String::New("music_delivery"));
- if(!cbv->IsFunction()) {
- return;
- }
- Handle<Function> cb = Local<Function>(Function::Cast(*cbv));
-
while(af->qlen > 0) {
afd = audio_get(af);
if(!afd) {
break;
}
+ sp_session* spsession = afd->session;
+ ObjectHandle<sp_session>* session = (ObjectHandle<sp_session>*) sp_session_userdata(spsession);
+
+ Handle<Value> cbv = session->object->Get(String::New("music_delivery"));
+ if(!cbv->IsFunction()) {
+ return;
+ }
+ Handle<Function> cb = Local<Function>(Function::Cast(*cbv));
+
Buffer* buffer = Buffer::New((char*) afd->samples, afd->nsamples * sizeof(int16_t)* afd->channels, free_music_data, afd);
buffer->handle_->Set(String::New("channels"), Number::New(afd->channels));
buffer->handle_->Set(String::New("rate"), Number::New(afd->rate));
View
18 src/session.cc
@@ -26,6 +26,11 @@ Handle<Value> nsp::JsNoOp(const Arguments& args) {
return args.This();
}
+/*
+ * The following callback function do nothing more than getting the session object and calling their
+ * Javascript counterparts
+ */
+
/**
* spotify callback for the logged_in event.
* See https://developer.spotify.com/technologies/libspotify/docs/12.1.45/structsp__session__callbacks.html
@@ -154,6 +159,19 @@ static void call_log_message_callback(sp_session* session, const char* data) {
* See https://developer.spotify.com/technologies/libspotify/docs/12.1.45/structsp__session__callbacks.html
*/
static void call_end_of_track_callback(sp_session* session) {
+ ObjectHandle<sp_session>* s = (ObjectHandle<sp_session>*) sp_session_userdata(session);
+ Handle<Object> o = s->object;
+ Handle<Value> cbv = o->Get(String::New("end_of_track"));
+ if(!cbv->IsFunction()) {
+ return;
+ }
+
+ Handle<Function> cb = Local<Function>(Function::Cast(*cbv));
+ const unsigned int argc = 0;
+ Local<Value> argv[argc] = {};
+ cb->Call(Context::GetCurrent()->Global(), argc, argv);
+
+ return;
}
/**
View
19 src/track.cc
@@ -40,7 +40,26 @@ static Handle<Value> Track_Is_Loaded(const Arguments& args) {
return scope.Close(Boolean::New(loaded));
}
+/**
+ * JS track_duration implementation. checks if a given track is loaded
+ */
+static Handle<Value> Track_Duration(const Arguments& args) {
+ HandleScope scope;
+
+ // test arguments sanity
+ assert(args.Length() == 1);
+ assert(args[0]->IsObject());
+
+ // gets sp_track pointer from given object
+ ObjectHandle<sp_track>* track = ObjectHandle<sp_track>::Unwrap(args[0]);
+
+ // actually call sp_track_is_loaded
+ int duration = sp_track_duration(track->pointer);
+
+ return scope.Close(Number::New(duration));
+}
void nsp::init_track(Handle<Object> target) {
NODE_SET_METHOD(target, "track_is_loaded", Track_Is_Loaded);
+ NODE_SET_METHOD(target, "track_duration", Track_Duration);
}
View
33 test/test-050-player01-end-of-track.js
@@ -0,0 +1,33 @@
+var sp = require('../lib/libspotify');
+var cred = require('../spotify_key/passwd');
+var testutil = require('./util');
+
+var session = null;
+
+exports.setUp = function(cb) {
+ testutil.getDefaultTestSession(function(s) {
+ session = s;
+ cb();
+ })
+};
+exports.testEndOfShortTrack = function(test) {
+ var search = new sp.Search('album:"Indie Rock to the Blues" track:"short song I"');
+ search.execute(function() {
+ test.ok(search.tracks.length > 0);
+ var track = search.tracks[0];
+ var player = session.getPlayer();
+ player.load(track);
+ player.play();
+ console.log('playing track, enjoy it, waiting for it to end in %s', track.humanDuration);
+ var called_end = false;
+ player.once('track-end', function() {
+ called_end = true;
+ player.stop();
+ });
+ setTimeout(function() {
+ test.ok(called_end);
+ player.stop();
+ test.done();
+ }, track.duration + 2000); // 2 seconds margin
+ });
+}
Please sign in to comment.
Something went wrong with that request. Please try again.