Search code examples
iossoundcloud

SoundCloud Widget External Control iOS issue


I have found a bug using the external controls for the html5 widget on iOS, i have tested with iphone and ipad. The controls inside the widget work ok. However on my clients site http://www.bushytunes.net and the widget api playground http://w.soundcloud.com/player/api_playground.html the external controls run an error.

Here is what the console from the widget playground prints:

SC.Widget.Events.PLAY {"loadedProgress":null,"currentPosition":0,"relativePosition":0}
SC.Widget.Events.PAUSE {"loadedProgress":null,"currentPosition":0,"relativePosition":0}
SC.Widget.Events.PLAY {"loadedProgress":null,"currentPosition":0,"relativePosition":0}
SC.Widget.Events.PLAY {"loadedProgress":null,"currentPosition":0,"relativePosition":0}
SC.Widget.Events.PLAY {"loadedProgress":null,"currentPosition":0,"relativePosition":0}
SC.Widget.Events.READY {}
Loading...

Thanks, James


Solution

  • TL;DR: iOS 6 should work with a limitation of a call to play to be init by user action, iOS5 and below probably won't get a fix.

    We'll try to resolve this with some kind of a hack, but most probably this won't get a proper solution any time soon.

    SoundCloud Widget is using HTML5 Audio to play sounds on iOS. There are two limitations on starting playback in iOS:

    1. Playback must be started by user action (so within event handler)
    2. The call to audio.play method has to be within synchronous call stack of that event handler.

    This means, that this works:

    document.querySelector('.play-button').addEventListener('click', function () {
        audioController.play();
    }, false);
    

    But this doesn't:

    document.querySelector('.play-button').addEventListener('click', function () {
        setTimeout(function () {
            audioController.play();
        }, 500);
    }, false);
    

    Cross-domain communication between iframes is only possible in asynchronous manner. Our implementation is using postMessage DOM API, but even older techniques such as Fragment Identifier Messaging (FIM) that is using location.hash are asynchronous. This means we don't currently see a way of enabling playback through API for iOS5 and below (as there is also no Flash fallback).

    However, there are also good news: iOS6 has dropped the limitation No2. This means that as long as the API call to play is called by user action, you'll be able to play sounds. Even though the communication between your parent window and widgets' iframe is still async. Unfortunately iOS hasn't dropped this limitation.

    I hope this helps and I'll update this answer in case we find a proper workaround.

    My suggestion would be to build your own custom player with a library like Audio5JS or SoundManager2 in case controlling playback on iOS is crucial for your app.

    You can see vague description of what I am talking about on developer.apple.com resource.