SoundCloud Javascript SDK 2.0 - whileplaying event - javascript

I'm using the SoundCloud Javascript SDK. Recently, the 2.0 SDK was announced, and there no longer seems to be a 'whileplaying' event. In the following code, the 'whileplaying' handler never gets executed.
SC.stream(sound_id, function(sound) {
sound.play({
whileplaying: function() { console.log('whileplaying'); }
})
);
Am I doing something wrong? If this was taken out of the SDK, is there a workaround?

By looking into the non-obfuscated JS files (the API itself and the soundmanager file), you can find the events you are looking for.
For example:
sound._player.on("stateChange", function(state) {
console.log(state);
});
Or:
sound._player.on("positionChange", function(position) {
console.log(position);
});
I have quickly testes both for HTML5 and Flash tracks.

I'm putting together a Soundcloud audio player for my site and this proved to be a headache: Soundcloud haven't released any docs for the js sdk v2 yet, and it doesn't use soundManager anymore so the streaming audio object is completely different.
Until the docs are available to clarify the situation, you could get by with setInterval or the requestAnimationFrame polyfill to call the audio object's methods (https://developers.soundcloud.com/docs/api/javascript-sdk-2), having stored it in a global variable so you can access it from other functions:
Using setInterval
var updater, scTrack;
function updatePosition(){
// Do stuff, update timers and transport position
var loadedPosition = scTrack.getLoadedPosition(),
currentPosition = scTrack.getCurrentPosition();
console.log('Current: '+currentPosition+', Loaded: '+loadedPosition);
}
SC.stream(sound_id, function(sound){
scTrack = sound;
scTrack.play();
updater = setInterval( updatePosition, 100);
});
And then with whatever handler you have for a pause or stop button:
scTrack.pause();
clearInterval(updater);
Using requestAnimationFrame
( Polyfill available here: https://gist.github.com/paulirish/1579671 )
var updater, scTrack;
function updatePosition(){
updater = requestAnimationFrame(updatePosition);
// Do stuff, update timers and transport position
var loadedPosition = scTrack.getLoadedPosition(),
currentPosition = scTrack.getCurrentPosition();
console.log('Current: '+currentPosition+', Loaded: '+loadedPosition);
}
SC.stream(sound_id, function(sound){
scTrack = sound;
scTrack.play();
updatePosition();
});
For the pause / stop handler:
scTrack.pause();
cancelAnimationFrame(updater);
// This hasn't always cleared the animation request for me so I also set the loop variable to undefined:
updater = undefined;
I'm sure there's a better way of doing it, but it works for now - hopefully the forthcoming docs will clear it up.

Related

Cleaner way to hook into the window.onload of a web page?

I'm building a small iOS App in Swift and I use the WKWebView. What I am trying to achieve is to be notified when a web page has rendered completely. It is a known issue with WKWebView that none of its loading notifications work.
However, this approach seems to work and the idea is to hook into the window.onload function in Javascript and notify Swift from there. However, here too I found an issue. If I use JQuery, in some pages the callback doesn't happen apparently because the web pages already define window.onload. If I use the pure Javascript way, some web pages break i.e. they don't load at all apparently because I'm overriding the window.onload.
// using JQuery, this function never get called for web pages that do window.onload =
$(window).load(function() {
window.webkit.messageHandlers.callbackHandler.postMessage(
JSON.stringify({body: "window finished loading"}));
});
// works always but breaks web pages that use window.onload
window.onload = function() {
window.webkit.messageHandlers.callbackHandler.postMessage(
JSON.stringify({body: "window finished loading"}));
};
The question is how can I append this line notification to an existing window.onload and define one window.onload if it doesn't exist? other ideas also welcome e.g. queuing window.load implementations?
For completeness I have included below the two known ways to get such notifications using WKWebView natively.
(1) getting WKNavigationDelegate life-cycle notifications but the callback notification triggers too early when the web page has not yet rendered completely.
class ViewController: UIViewController {
// ...
func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
NSLog("didFinishNavigation callback received ... too early!")
}
}
(2) Using the Key-Value Observer method (KVO) but here again callback notification triggers too early too:
webView.addObserver(viewController, forKeyPath: "estimatedProgress", options: .New, context: nil)
//
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<()>) {
guard let webView = object as? WKWebView else {return}
guard let change = change else {return}
guard let keyPath = keyPath else {return}
switch keyPath {
case "estimatedProgress":
if ((1.0 - webView.estimatedProgress) < 1e-10) {
NSLog("'estimatedProgress' callback received ... too early!")
}
break
default: break
}
}
it is the same issue for the "loading" KVO.
UPDATE: in addition to the accepted answer, the top ranked answer to this other question running-jquery-after-all-other-js-has-executed solves this OP too by polling the DOM for a specific change e.g. when all the Javascript has completed in addition to the loading of the page.
Instead of the load event you can listen to DOMContentLoaded.
Also you can perform the callback at the end of the execution stack by doing the following:
window.setTimeout(callback, 0);
Additionally you can try calling removeEventListener in your callback.
For Example:
if (window.addEventListener) {
var documentIsReady = function() {
window.removeEventListener("load", documentIsReady);
if (typeof window.isMyiOSAppAlreadyNotified === 'undefined') {
window.webkit.messageHandlers.callbackHandler.postMessage(JSON.stringify({body: "window onload"}));
}
window.isMyiOSAppAlreadyNotified = true;
};
window.addEventListener("load", function() { window.setTimeout(documentIsReady, 0); });
}
To avoid polluting the global(window) scope with variables like isMyiOSAppAlreadyNotified you can apply the module pattern.
I found a way that works. However despite my futile attempt to avoid duplicate notifications I still get two notifications for some web pages and haven't found a way to fix it yet ...
if (window.addEventListener) {
window.addEventListener("load",
function() {
// try to make sure it is called only once ...
if (typeof window.isMyiOSAppAlreadyNotified === 'undefined') {
// notify my iOS App that the page has finished loading
window.webkit.messageHandlers.callbackHandler.postMessage(
JSON.stringify({body: "window onload"}));
}
window.isMyiOSAppAlreadyNotified = true;
}
);
}

Javascript syncronous call with update

I have a function that performs a long task. I would like to create a function that is able to notify the caller of the progress. Ultimately I want to update the UI with the current progress.
Something like this:
function myLongMethod(progressCallback)
{
for(var i = 0 ... )
{
progressCallback(i) ;
}
}
This works but updates on UI are not smooth. Is there a better way? I would prefer something with a jquery Deferred object using deferred.notify(). Any ideas?
Your code is fine. You have got another problem. Javscript always runs on the UI thread. Your operation is blocking this thread (the browser) and you will see some blocking of your browser window.
Luckily there is a workaround implemented in modern browser called web workers. It's simple just call in your main script another script which then get executed:
var w = new Worker("another_script.js");
If your worker is ready you can react on the result by adding a event listner to the worker:
w.onmessage = function(event) {
//do something
}
When you use this pattern, your UI did not block. You can even return data from a web worker and include scripts into it. More details you can find here and here is a good starting tutorial.
Hi you can apply the easing effect to your UI for smoothness and i am giving the following code it may help you
var oldProgress = 0;
var uiUpdater = null;
function updateUI(newProgress){
if(uiUpdater !=null){
// update your ui to the old progress first
window.clearInterval(uiUpdater); // clearing the previous timer
}
var diff = newProgress - oldProgress;
oldProgress = newProgress;
var stepSize = diff/5; // applying the new change in 5 steps to the UI
uiUpdater = window.setInterVal(function(){
// updating your UI after every 100 milliseconds
// to give the smoothness
diff -= stepSize; // decreasing the difference gradually
if(diff<=0){
window.clearInterval(uiUpdater); // clearing the interval once the update is done
}
},100);
}
You have to call the "updateUI" method from you callback with the new progress.

node.js setInterval not working in custom module

I am developing a web application in node.js to collect data from devices on a network using snmp. This is my first real encounter with node.js and javascript. In the app each device will be manipulated through a module I named SnmpMonitor.js. This module will maintain basic device data as well as the snmp and database connection.
One of the features of the app is the ability to constantly monitor data from smart metering devices. To do this I created the following code to start and stop the monitoring of the device. It uses setInterval to constantly send a snmp get request to the device. Then the event listener picks it up and will add the collected data to a database. Right now the listener just prints to show it was successful.
var dataOIDs = ["1.3.6.1.2.1.1.1.0","1.3.6.1.2.1.1.2.0"];
var intervalDuration = 500;
var monitorIntervalID;
var dataCollectionEvent = "dataCollectionComplete";
var emitter = events.EventEmitter(); // Uses native Event Module
//...
function startMonitor(){
if(monitorIntervalID !== undefined){
console.log("Device monitor has already started");
} else {
monitorIntervalID = setInterval(getSnmp,intervalDuration,dataOIDs,dataCollectionEvent);
emitter.on(dataCollectionEvent,dataCallback);
}
}
function dataCallback(recievedData){
// receivedData is returned from getSnmp completion event
// TODO put data in database
console.log("Event happened");
}
function stopMonitor(){
if(monitorIntervalID !== undefined){
clearInterval(monitorIntervalID);
emitter.removeListener(dataCollectionEvent,dataCallback);
} else {
console.log("Must start collecting data before it can be stopped");
}
}
//...
I also have a test file, test.js, that requires the module, starts monitoring, waits 10 seconds, then stops it.
var test = require("./SnmpMonitor");
test.startMonitor();
setTimeout(test.stopMonitor,10000);
My problem is that the setInterval function in startMonitor() is not being run. I have tried placing console.log("test"); before, inside, and after it to test it. The inside test output never executes. The monitorIntervalID variable is also returned as undefined. I have tested setInterval(function(){ console.log("test"); },500); in my test.js file and it runs fine with no issues. I feel like this is a noobie mistake but I just can't seem to figure out why it won't execute.
Here is a link to the entire module: SnmpMonitor.js
I not sure exactly what was wrong but I got it to work by overhauling the whole class/module. I thought the way I had it was going to allow me to create new monitors objects but I was wrong. Instead I created two functions inside the monitor file that do the same thing. I changed the start function to the following.
SnmpMonitor.prototype.start = function() {
var snmpSession = new SNMP(this.deviceInfo.ipaddress,this.emitter);
var oids = this.deviceInfo.oids;
var emit = this.emitter;
var duration = this.intervalDuration;
this.intervalID = setInterval(function(){
snmpSession.get(dataCollectionEvent,emit,oids);
},duration);
};
The setInterval function seems to work best when the callback function is set inside an anonymous function, even though technically you can pass it directly. Using the this. notation I created some class/module/function variables (whatever its called in js) that are in scope of the whole class. For some reason the variables accessed through this. do not work so well when directly in a function or expression so I created temp variables for them. In my other version all the variables were global and js doesn't seem to like that.

videojs public method access outside ready function and IE

I built a player on top of videoJS and I'm having trouble accessing public functions inside videoJS .ready(). The thing is that my code appears to be working everywhere except IE (works in chrome, safari, ff, etc.):
var myPlayer = _V_('myvideojsId');
myPlayer.ready(function() {
var player = this;
player.myPublicFunction = function() {
alert("hey!");
}
});
myPlayer.myPublicFunction();
In IE I get
Object does not support this property or method
on the myPlayer.myPublicFunction() line. Are the other browsers letting me get away with bad code or is this IE's fault?
Any help would be great, thank you!
Chris
Referencing their documentation, it shows exactly what Jonathan has said:
https://github.com/zencoder/video-js/blob/master/docs/api.md#wait-until-the-player-is-ready
He's right about IE by the way. As much as we all love to hate it, it has found real issues for me many times.
Just for quicker reference, here's an alternative to your method to getting this done:
_V_("example_video_1").ready(function(){
var myPlayer = this;
// EXAMPLE: Start playing the video.
myPlayer.play();
});
This is likely a problem with timing:
myPlayer.ready(function() {});
myPlayer.myPublicFunction();
Your first line here hands off a function to myPlayer to call whenever the player is ready. This doesn't happen immediately in most cases, so there is most likely a delay. This means your public function isn't added to the myPlayer object immediately, but rather this task will be accomplished whenever the video player is ready.
All of this means that when JavaScript moves on to the second line, the appropriate response from a browser is that the method doesn't exist - because it doesn't. It won't exist until the video player is ready, which isn't until later.
You could use more of a feature-detection approach, and only call the method if it exists:
if (myPlayer.myPublicFunction) {
myPlayer.myPublicFunction();
}
You could also just add the method before-hand:
myPlayer.myPublicFunction = function () { alert("Foo"); };
myPlayer.ready(callback);
myPlayer.myPublicFunction(); // 'Foo'
In the end, I've found that Internet Explorer is not as forgiving (which is good) as some other browsers. If it's acting up today, it's likely because there's a problem in the code.

Use of reference to calling object (this) using HTML5 audio and jQuery

I'm creating my own HTML5 audio player capable of handling playlists. I have created a custom myPlaylist object with includes all play(), pause(), stop() and other needed functionality. This is all working correctly, but moreover, I need to be aware about when an audio file has ended in order to automatically start playing the next one.
Here's the relevant parts of the code I'm using:
function myPlaylist(){
var player = document.createElement('audio');
var audio = $(player).get(0);
this.next = function next(){
// Picks next song in the playlist and plays it
...
};
$(audio).bind('ended', function() {
alert("Song is finished!");
// Here I want to call to my next() function
});
}
I haven't been able to figure out how to do it. I've tried already several combinations, like $(this).next(), which seems the most reasonable and actually displays the alert, but then does nothing ¿?, also this.next(), which also displays the alert but then shows an error since this refers to the HTML5 audio element, which does not have a next() function.
I've also tried another approach, using
audio.onended = function(){
alert("Song is finished!");
$(this).next();
};
But those do not even trigger the alert. Also audio.ended does not work.
So, I'm basically clueless right now, does anyone have any idea what am I doing wrong? Thanks in advance.
Oh, and I've tested all this in the latest versions of Google Chrome and Safari in Mac OS X.
EDIT Following the advice given in HTML5 audio playlist - how to play a second audio file after the first has ended?, I've also tried the following code
player.addEventListener("ended", function() {
alert("Song is finished!");
$(this).next();
});
And
player.addEventListener("ended", next);
None of them work either, although the first one shows the alert properly.
EDIT 2 Using the search I came across this question, which might also have something to do with my problem, so in order to get rid of any possible troubles with the reference to this, I added a new variable referring to the object itself, so now I'm basically working with:
function myPlaylist(){
var player = document.createElement('audio');
var audio = $(player).get(0);
var me = $(this);
this.next = function next(){
// Picks next song in the playlist and plays it
...
};
$(audio).bind('ended', function() {
alert("Song is finished!");
me.next();
});
}
But then I get an error saying that the Object does not have a method next().
I don't know what else can I try... Any extra information will be highly appreciated, thank you!
there's an HTML5 playlist example handling the ended event here, if that helps?
in your event handler you reference this, but in this context this refers to the DOM element that caught the event, i.e. your audio element.. try this instead:
function myPlaylist(){
var self = this;
var player = document.createElement('audio');
this.next = function (){
// Picks next song in the playlist and plays it
...
};
player.addEventListener("ended", function() {
alert("Song is finished!");
self.next();
});
}
see the MDN for more info on the this keyword

Categories

Resources