Detect end of a track's playback with Spotify Web API / Playback SDK - spotify

Working with Spotify Web API and Web Playback SDK, does anybody know a good way to detect when a track's playback has finished, i.e. the song is over?
I know about some event named player_state_changed in Web Playback SDK, but the data provided in the event doesn't seem to be very helpful to me.
In my web app, the next song to be played is determined pretty spontaneously. It would be nice, if I could determine it only, when the current track has reached its end. Hence, there is no playlist. Instead, the app plays single tracks in series and should determine the next song, when the current one has ended.
Meanwhile I try it with this code here:
var bCallingNextSong = false;
player.addListener('player_state_changed', state => {
if (state != null &&
state.position == 0 &&
state.duration == 0 &&
state.paused == true) {
if (!bCallingNextSong) {
playNextSong();
}
}
});
function playNextSong() {
$.ajax({
url:"playback.php", //the page containing php script
type: "post", //request type,
dataType: 'json',
data: {action: "next", device: deviceId},
success: function(result) {
bCallingNextSong = false;
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
bCallingNextSong = false;
}
});
}
and in playback.php:
switch ($action) {
case "next":
//some blackbox magic here to determine next song, do logging etc.
//but for now just play the same song over and over
$api->play($deviceId, ['uris' => "spotify:track:0eGsygTp906u18L0Oimnem"],]);
echo json_encode(array("answer"=>"played next"));
break;
// more code
}
However, this will often (but not always) throw an InvalidStateError (I haven't found out where exactly yet nor why). Stack says dequeueUpdates / tryUpdate / _processUpdate / _appendUpdate. Yet, I'm a beginner and I've gotta learn how to cope with Firefox Debugger first.. :)

My last answer got broken so I will now add new solution which work for now.
this.player.addListener('player_state_changed', (state) => {
console.log(state);
if (
this.state
&& state.track_window.previous_tracks.find(x => x.id === state.track_window.current_track.id)
&& !this.state.paused
&& state.paused
) {
console.log('Track ended');
this.setTrackEnd(true);
}
this.state = state;
});
So what is different from last solution? I check that current_track is in previous track list. But there was problem now with position check because it seems that current track appears to previous track list before track is completely end. But that wasn't problem in my app because there is always small delays with commands.

EDIT: This is not working anymore for some reason. Check my other answer which will work now. https://stackoverflow.com/a/51114848/8081009
Here is my solution. It works most of the time. Actually I didn't get it broken in any scenarios I can imagine. So hold last state and check that is it gone to paused state. Hopefully Spotify will ease our work with track ended event. It would be great if they does. This is still better than polling their /me/player/currently-playing endpoint for every 1 second or so.
this.player.addListener(
'player_state_changed',
state =>
{
console.log(state);
if(this.state && !this.state.paused && state.paused && state.position === 0) {
console.log('Track ended');
this.setTrackEnd(true);
}
this.state = state;
}
);

I had this same problem, this is how I solved it.
When a song finish playing it gets added to the state.track_window.previous_tracks list
state.track_window.previous_tracks gets populated only if there is a next track and it starts playing
But every time you call the api to play a new track, it clears the state.track_window.previous_tracks & state.track_window.next_tracks list.
If there is a next track, I want to stop and choose my own next track and then call the api
var alreadyPlayed = state.track_window.previous_tracks
if(alreadyPlayed.length > 0) {
player.pause()
}
if there is no next track the player will be paused when the song finish
if(state.paused && state.position === 0) {
//song ended
ChooseNextSong()
}
the state callback gets fired many times but I want to call ChooseNextSong()
only once, so I increment startCheck every time it fires
if(state.paused && state.position === 0) {
//song ended
startCheck++
if(startCheck === 1) {
ChooseNextSong()
}
}
check if song started
if(state.position === 0 && alreadyPlayed.length === 0 && !state.paused)
{
//song started
startCheck = 0
}

You should use the player_state_changed event and look at which track is playing to see if the track has changed. You can find the current track in playerStateObject.track_window.current_track.
A possible implementation could look like this:
let currentTrack = '';
player.on('player_state_changed', state => {
if( state.track_window.current_track.id != currentTrack ) {
// The track changed!
currentTrack = state.track_window.current_track.id;
}
});

Related

How to manually stop getDisplayMedia stream to end screen capture?

I'm interested in getting a screenshot from the user and I'm using the getDisplayMedia API to capture the user's screen:
const constraints = { video: true, audio: false };
if (navigator.mediaDevices["getDisplayMedia"]) {
navigator.mediaDevices["getDisplayMedia"](constraints).then(startStream).catch(error);
} else {
navigator.getDisplayMedia(constraints).then(startStream).catch(error);
}
When executed, the browser prompts the user if they want to share their display. After the user accepts the prompt, the provided callback receives a MediaStream. For visualization, I'm binding it directly to a element:
const startStream = (stream: MediaStream) => {
this.video.nativeElement.srcObject = stream;
};
This is simple and very effective so far. Nevertheless, I'm only interested in a single frame and I'd therefore like to manually stop the stream as soon as I've processed it.
What I tried is to remove the video element from the DOM, but Chrome keeps displaying a message that the screen is currently captured. So this only affected the video element but not the stream itself:
I've looked at the Screen Capture API article on MDN but couldn't find any hints on how to stop the stream.
How do I end the stream properly so that the prompt stops as well?
Rather than stopping the stream itself, you can stop its tracks.
Iterate over the tracks using the getTracks method and call stop() on each of them:
stream.getTracks()
.forEach(track => track.stop())
As soon as all tracks are stopped, Chrome's capturing prompt disappears as well.
start screen capture sample code:
async function startCapture() {
logElem.innerHTML = "";
try {
videoElem.srcObject = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
dumpOptionsInfo();
} catch(err) {
console.error("Error: " + err);
}
}
stop screen capture sample code:
function stopCapture(evt) {
let tracks = videoElem.srcObject.getTracks();
tracks.forEach(track => track.stop());
videoElem.srcObject = null;
}
more info: MDN - Stopping display capture

Async base-local with MQTT

I need to synchronize a base and a local client with MQTT. If client publishes then the other one will get the message.
If my MQTT broker is down, I need to stop sending messages, save the messages somewhere, wait for a connection, then continue sending.
If my local or base client is down for a second, I need to save the message which I didn't send, then send it when I turn on my base/local.
I'm working with Node.js and can't figure out how to implement this.
This is my handler when I connect or disconnect with my MQTT server.
client.on('connect',()=>{
store.state = true;
run(store).then((value)=>console.log('stop run'));
});
client.on('offline',()=>{
store.state = false;
console.log('offline');
});
This is my run function. I use store.state to decide if I should stop this interval. But this code does not seem to be a good way to implement my concept.
function run(store) {
return new Promise((resolve,reject)=>{
let interval = setInterval(()=>{
if (!store.state) {
clearInterval(interval);
resolve(true);
}
else if (store.queue.length > 0) {
let data = store.queue.pop();
let res = client.publish('push',JSON.stringify(data),{qos:2});
}
},300)
});
}
What should I do to implement a function which always sends, stop upon 'disconnect', then continues sending when connected?
I don't think set interval which 300ms is good.
If you want something that "always runs", at set intervals and in spite of any errors inside the loop, setInterval() makes sense. You are right that queued messages can be cleared faster than "once every 300 ms".
Since MQTT.js has a built-in queue, you could simplify a lot by using it. However, your messages are published to a target called "push", so I guess you want them delivered in the order of the queue. This answer keeps the queue and focuses on sending the next message as soon as the last one is confirmed.
What if res=client.publish(..) false ?
Good point! If you want to make sure it arrives, better to remove it once the publish has succeeded. For this, you need to retrieve the value without removing it, and use the callback argument to find out what happened (publish() is asynchronous). If that was the only change, it might look like:
let data = store.queue[store.queue.length - 1];
client.publish('push', JSON.stringify(data), {qos:2}, (err) => {
if(!err) {
store.queue.pop();
}
// Ready for next publish; call this function again
});
Extending that to include a callback-based run:
function publishFromQueue(data) {
return new Promise((resolve,reject)=>{
let res = client.publish('push', JSON.stringify(data), {qos:2}, (err) => {
resolve(!err);
});
});
}
async function run(store) {
while (store.queue.length > 0 && store.state) {
let data = store.queue[store.queue.length - 1];
let res = await publishFromQueue(data);
if(res) {
store.queue.pop();
}
}
}
This should deliver all the queued messages in order as soon as possible, without blocking. The only drawback is that it does not run constantly. You have two options:
Recur at set intervals, as you have already done. Slower, though you could set a shorter interval.
Only run() when needed, like:
let isRunning = false; //Global for tracking state of running
function queueMessage(data) {
store.queue.push(data);
if(!isRunning) {
isRunning = true;
run(store);
}
isRunning = false;
}
As long as you can use this instead of pushing to the queue, it should come out similar length, and more immediate and efficient.

Google Actions: in dialogflow stop all mediaObjects

I have a Google Action that plays some pre-recorder stories. I use mediaObject to play them. Something like:
conv.ask(new MediaObject({
name: title,
url: storia,
description: autore,
image: new Image({
url: largeImageUrl,
alt: title,
}),
}));
I since some stories are longer than 4 minutes, I've split them in two, and use a media status control:
app.intent('Media Status', (conv) => {
const mediaStatus = conv.arguments.get('MEDIA_STATUS');
if (mediaStatus && mediaStatus.status === 'FINISHED') {
console.log(`track finished: ${conv.user.storage.track}`);
console.log(`storia2: ${conv.data.storia2}`);
if (conv.data.storia2 !== undefined) {
secondaParte = `<audio src="` + randomPhrase(continuaFavola) + `">
<desc>Vediamo come va a finire...</desc>
</audio>`;
storia = conv.data.storia2;
conv.data.storia2 = undefined;
conv.ask(new SimpleResponse('<speak>' + secondaParte +'</speak>'));
} else {
conv.close(new SimpleResponse('<speak>' + goodnight +'</speak>'));
}
} else {
conv.ask('Media Status ignoto');
}
});
It works.
The problem is that sometimes, while the media object is playing, if I say the "stop" command, the media object stops, but the stop intent is not triggered (it should say "Ok, goodbye" and close the action), so the action remains waiting for a prompt. A second stop actually execute the stop intent. So basically I need to stop it twice.
In the log I can't see any trace of the first stop command (that has been clearly received since the media object has stopped), only the second one.
Even worse, when I play the second half of the story, this time the command is supposed to close:
conv.close(new MediaObject({ ... );
If I say "OK Google, stop", instead of stopping the playback, the action ends but the media object keeps playing (I know the action is quit because I can give standard commands, like "what's the time" or "set an alarm".
What a mess.
So is there a way to close the action, and force stop of any currently playing media object?

VIDEOJS: playing a pre-roll add before each playlist item

I'm using the videojs-playlist plugin along with Google's videojs-ima plugin. Everything works swimmingly except I am only getting a preload ad before the first video. I want one before each video in the playlist.
Basic setup is boilerplate, but for reference:
this.player = videojs('currentvideo', { autoplay : true, fluid : true });
this.player.playlist(this.playlist);
this.player.playlist.autoadvance(5);
const skippable_linear = {google's test ad};
const options = {
id: 'currentvideo',
adTagUrl: skippable_linear,
debug : true
};
this.player.ima(
options
);
this.player.ima.requestAds();
I have tried various ways of manually calling ads from inside an 'ended' event handler, such as calling requestAds again:
const _this = this;
this.player.on( 'ended', function(){
/* some other stuff */
_this.player.ima.requestAds();
});
This does play an ad where I want it, but
this breaks playlist's 'autoadvance' setting (next video doesn't start playing when the ad is finished), and
this puts the player into "ad display" mode (scrubber is unavailable, etc).
Is there a simple way to just say, "play an ad now" programmatically? I've tried, without joy, to use all of the seemingly applicable methods exposed by both the ima plugin and the contrib-ads plugin it relies on. I'll admit here that this is the first time I've ever had to deal with videos that run ads, so I'm kind of a noob.
I am trying to do the same thing. Just like you I failed when calling player.ima.requestAds() on events. I dug deeper and the best I could come up with is what I share bellow.
According to the videojs-ima API you have to use the setContentWithAdTag method instead of whatever you are using to switch the player content. In our case it is the player.playlist.next method.
I combined the code found in the videojs-ima examples with the original playlist.next to write my own next.
Then quite brutally I overrode the original plugin method.
Here's the code:
player.playlist(myPlayilst);
player.playlist.autoadvance(2);
player.playlistUi(); //videojs-playlist-ui
player.ima({
id: 'video5',
adTagUrl: 'thy adserver request'
});
//override playlist.next
player.playlist.next = function(){
var nextIndex = 0,
playlist = this.player_.playlist,
list = this.player_.playlist();
//everything below is copied directly from the original `next` (except for the "//load with ad")
// Repeat
if (playlist.repeat_) {
nextIndex = playlist.currentIndex_ + 1;
if (nextIndex > list.length - 1) {
nextIndex = 0;
}
} else {
// Don't go past the end of the playlist.
nextIndex = Math.min(playlist.currentIndex_ + 1, list.length - 1);
}
// Make the change
if (nextIndex !== playlist.currentIndex_) {
//load with ad
this.player_.playlist.currentItem(nextIndex);
this.player_.ima.setContentWithAdTag(
this.player_.playlist.currentItem(),
null,
true);
this.player_.ima.requestAds();
/////
return list[playlist.currentItem()];
}
}
You will probably need to override other methods that change the current playback, like playlist.previous.
I use videojs-playlist-ui so in my case it was neccessary to change the onclick handler called switchPlaylistItem_. I used some good old brute force to do that like this:
videojs.getComponent('PlaylistMenuItem').prototype.switchPlaylistItem_ = function(e){
this.player_.playlist.currentItem(this.player_.playlist().indexOf(this.item));
this.player_.ima.setContentWithAdTag(
this.player_.playlist.currentItem(),
null,
true);
this.player_.ima.requestAds();
};
PlaylistMenuItem's prototype should be changed before initializing the player.
This solution works, but it feels hacky, so if anyone can come up with something cleaner, please share!
I ended up forking videojs-playlist, and adding the option to override the player.src method. Feel free to use it:
fw-videojs-playlist
Details on how to use it are all in the github readme (including an example with ima.setContentWithAdTag)

phantomJS scraping with breaks not working

I'm trying to scrape some URLS from a webservice, its working perfect but I need to scrape something like 10,000 pages from the same web servicve.
I do this by creating multiple phantomJS processes and they each open and evaluate a different URL (Its the same service, all I change is one parameter in the URL of the website).
Problem is I don't want to open 10,000 pages at once, since I don't want their service to crash, and I don't want my server to crash either.
I'm trying to make some logic of opening/evaluating/insertingToDB ~10 pages, and then sleeping for 1 minute or so.
Let's say this is what I have now:
var numOfRequests = 10,000; //Total requests
for (var dataIndex = 0; dataIndex < numOfRequests; dataIndex++) {
phantom.create({'port' : freeport}, function(ph) {
ph.createPage(function(page) {
page.open("http://..." + data[dataIncFirstPage], function(status) {
I want to insert somewhere in the middle something like:
if(dataIndex % 10 == 0){
sleep(60); //I can use the sleep module
}
Every where I try to place sleepJS the program crashes/freezes/loops forever...
Any idea what I should try?
I've tried placing the above code as the first line after the for loop, but this doesn't work (maybe because of the callback functions that are waiting to fire..)
If I place it inside the phantom.create() callback also doesn't work..
Realize that NodeJS runs asynchronously and in your for-loop, each method call is being executing one after the other. That phantom.create call finishes near immediately, and then the next cycle of the for-loop kicks in.
To answer your question, you want the sleep command at the end of the phantom.create block, still in side the for-loop. Like this:
var numOfRequests = 10000; // Total requests
for( var dataIndex = 0; dataIndex < numOfRequests; dataIndex++ ) {
phantom.create( { 'port' : freeport }, function( ph ) {
// ..whatever in here
} );
if(dataIndex % 10 == 0){
sleep(60); //I can use the sleep module
}
}
Also, consider using a package to help with these control flow issues. Async is a good one, and has a method, eachLimit that will concurrently run a number of processes, up to a limit. Handy! You will need to create an input object array for each iteration you wish to run, like this:
var dataInputs = [ { id: 0, data: "/abc"}, { id : 1, data : "/def"} ];
function processPhantom( dataItem, callback ){
console.log("Starting processing for " + JSON.stringify( dataItem ) );
phantom.create( { 'port' : freeport }, function( ph ) {
// ..whatever in here.
//When done, in inner-most callback, call:
//callback(null); //let the next parallel items into the queue
//or
//callback( new Error("Something went wrong") ); //break the processing
} );
}
async.eachLimit( dataInputs, 10, processPhantom, function( err ){
//Can check for err.
//It is here that everything is finished.
console.log("Finished with async.eachLimit");
});
Sleeping for a minute isn't a bad idea, but in groups of 10, that will take you 1000 minutes, which is over 16 hours! Would be more convenient for you to only call when there is space in your queue - and be sure to log what requests are in process, and have completed.

Resources