SoundCloud Javascript SDK 2.0 is here but without documentation

soundcloud

SoundCloud’s deadline for switching to their Javascript SDK 2.0 is July 1st, 2015. Unfortunately, as of today, June 3nd, 2015, the new Javascript streaming SDK remains undocumented and has a few issues. Hopefully, they will address these before the final switch.

tl;dr

SoundCloud’s Javascript SDK 2.0 player is completely different than 1.0.
There is no documentation for it and you need to rewrite your code to use it.
This article explains how to use the new player.

First, the good news

Overall, the new Javascript SDK (outside of the streaming player) is a big improvement over the old way of doing things. It’s easier to code against (no more $.jsonp() required) and it no longer requires Flash to stream the audio, instead opting for HTML5.

One big benefit of the new player is you no longer have to wait to seek ahead. In the old player, if you tried to seek to a time further than what was loaded (or pass a position property further into the song), it would have to wait until the file had loaded up to that point. Now, when you call seek(), it jumps to the time you pass and starts playing right away.

Problem #1: Documentation is for SDK 1.0

The streaming section of their documentation for Javascript is still for SDK 1.0. It currently reads:

The streaming of the SDK is powered by the soundManager2 library. If soundManager2 isn’t already present it will be loaded by the SDK.

SDK 2.0 uses a new library called audiomanager. You can see a beautified version here.

Problem #2: Missing Documentation for AudioManager

Digging through their developers blog, they link to what they call a “guide” to Upgrading to Javascript SDK 2.0.0, where they state:

We’ve done our utmost to make the upgrade to version 2.0.0 as seamless as possible. Everything should work as it did before, but please be sure to test before deploying your application. The SC.stream function now returns a completely rewritten Player object that responds to the following functions:

There are 11 function names listed. This is the extent of the documentation for the new Javascript SDK AudioManager. Unfortunately, the upgrade is not seamless. You will need to rewrite all of your code.

Settings object parameter is no longer used

AudioManager has a completely different API than soundManager2. With soundManager2, you could pass a settings object to initialize the stream with things like volume, position, event listeners, etc. None of that works anymore.

Digging through audiomanager.js to figure out why exposed the fact that any settings properties you pass go completely unused. The only settings properties it reads are internal ones like bufferingDelay, updateInterval and audioObjectID. The code never looks for anything outside these internal properties on the settings object, so don’t bother passing one.

My guess is that they left the parameter in place for future functionality and backwards compatibility once they do start using it.

Volume is now a float

soundManager2’s setVolume() function expects an integer between 0-100. AudioManager expects a floating point number between 0-1. A seamless upgrade would have checked if the value you passed was greater than 1 and if so, divide by 100 for you, but I digress. 😎

SC.stream does not return anything

Their guide states that the SC.stream() function returns a completely rewritten Player object. This is not entirely accurate. The first time you call SC.stream(), it returns a reference to the HTML tag that it injected into the page to load the audiomanager.js file. Subsequent calls to SC.stream() return nothing. In other words, the SC.stream() function itself does not return the Player object.

The completely rewritten Player object they’re referring to is returned in the “optional” callback function parameter. Since we need to be able to control the Player, the callback function is required.

Once you get the returned player object, you can call the functions they list on that page.

SC.stream(id, function(player){
    player.seek(3000);
    player.setVolume(0.5);
});

If you want to control the player after the fact, you’ll need to create a reference to it in your current scope and set it inside the callback function’s scope, or use an underscore bind, jQuery proxy or equivalent as the callback function.

This works around the issue of not being able to pass position or volume in the settings object, but what about event listeners?

Event Listeners

The old way of listening to events was passing in listener functions in the settings object named after the event you wanted to listen to. While this mostly worked, it was poorly documented as SoundCloud’s implementation of soundManager2 was not exactly the same as the documentation they linked to. However, in the AudioManager, it’s completely different. The events are different, the way you listen to them is different, and not all the events that you could listen to in soundManager2 are available in AudioManager.

So much for a seamless upgrade. ¯\_(ツ)_/¯

Further complicating things, there is no way to listen for events on the Player object itself. Instead, you have to listen to them by accessing a “private” property on the Player object called _player, which thankfully uses standard on(“eventName”, callback) and off(“eventName”, callback) functions.

More than likely, you’re just going to listen to the stateChange event. Here are all of the states this event passes:

"playing", "loading", "seeking", "paused", "error", "idle", "initialize", "ended", "dead"

If you add an event listener using _player.on(“stateChange”, callback), don’t forget to call _player.off(“stateChange”) before you load a new stream or the garbage collector will never clean it up. You’ll need to do this when the “ended” state happens, as well.

The AudioManager disposes of the reference to the Player but does not clear the “stateChange” event (not even its internal one). This is a memory leak and you can mitigate it by calling _player.off(“stateChange”) with just the event name which will clear all listeners to that event.

Despite all this, it’s a step in the right direction. It’s industry standard to use on() and off() to listen to events, and I hope they add those functions directly to the player object that is returned in the callback to make it less “hacky”.

Problem #3: Uncaught Error on 404s

If you attempt to load a SoundCloud track id that was made private or deleted, the file load attempt will 404. However, when this happens the SDK currently throws an uncatchable error. You cannot wrap SC.stream() in a try…catch to catch it. Further, because of when the uncaught error is thrown, the code stops in its tracks and the callback function you pass to SC.stream() never gets called. No bueno.

Use a timeout to detect 404

Because the callback function never gets called, you don’t get a reference to the player object. Here’s how you can workaround this issue.

You can workaround this limitation with setTimeout(). Between each play, you should clear out your reference to the last player object so that when you create a new one, you can detect whether the callback was called or not, and whether it returned a valid player. Here is a pseudocode example:


var scPlayer = undefined;
var timeoutID = setTimeout(onCheckPlayer, 3000);
SC.stream(id, function(player)
{
    if (player)
    {
        clearTimeout(timeoutID);
        scPlayer = player;
        player.play();
        player.seek(3000);
        player.setVolume(0.5);
        player._player.on("stateChange", onStateChange);
    }
});
function onCheckPlayer()
{
    if (!scPlayer) console.log("player never loaded");
}
function onStateChange(state)
{
    console.log("stateChange:", state);
}

This will allow you to handle a variety of unhandled errors, including 404s (file deleted) and 401s (file privated), as well as 500 errors, which can happen sometimes. Speaking of errors…

Problem #4: No error capturing on HTML5 Audio Object

AudioManager does not provide any try…catch protection against errors that the HTML5 Audio Object can throw randomly. Sometimes, the same track will play fine one time and cause an error the next. Sometimes, even a 404 will cause these errors.

One of the errors that the HTML5 Audio Object can cause is whatever url you’re currently on will reload the page to the root domain with a protocol of http. For example, if you’re on “https://domain.com/some/page.html” it will redirect the browser to “http://domain.com”. Odd behavior, but, thankfully, not too common.

Get Ready!

SoundCloud has already started turning off SDK 1.0 functionality as they prepare for the switch. If you haven’t updated your code yet, don’t wait any longer. Hopefully, this article will help you get up and running with SoundCloud’s Javascript SDK 2.0 streaming player.

Learning Japanese using Fluent Forever

harajuku sake

I’ve recently decided to kick my Japanese study into high gear with Gabriel Wyner’s book “Fluent Forever“. I discovered it through Tim Ferris’s blog.

Gabe’s approach is novel in that instead of how language is traditionally taught, he uses what I would call a mise en place strategy. By that I mean that you do a bunch of prep work creating spaced repetition flash cards for rapid memorization and learning. Unlike every other language class or book I’ve ever taken, his technique is to not do translation (native language on one side, target language on the other), but instead exclusively use imagery and mnemonics.

At this point, I’m just beginning to use his approach which is (roughly):

  1. Pronunciation
  2. The 625 most common vocabulary words
  3. Grammar

He has an extra step for Japanese which you do concurrently with pronunciation. He provides a flash card deck for learning the 48 Kanji radicals that make up 75% of the most common Kanji to make learning Kanji a lot easier.

I’ll keep track of my progress here each week.

Tonight, I finished setting up my Kanji radical flash card deck and will be diving into it this week while also creating about 20 vocabulary word cards each day until I finish all 625 of the most common words. By the time I’m done memorizing the Kanji radicals, I hope to have about 250-350 vocabulary words ready to start studying.

My goal is to memorize 625 words within six weeks of starting the vocabulary deck (along with their Kanji, when present). Then, it’s on to grammar. I already have a decent foundation of vocabulary and grammar with my previous study and thus can practice conversation as I learn new vocabulary, which is a great help.

頑張る!