14

Here is how a SoundCloud embedded player on a HTML page looks like on mobile device:

enter image description here

It's rather annoying, because the user has to click "Listen in browser", and then, often, it doesn't start like it should, and so the user has to click "Pause" button and "Play" again.

How to have the normal look, even on mobile devices? :

enter image description here


Here is example of embedding code:

<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/271188615&amp;color=ff5500&amp;auto_play=false&amp;hide_related=false&amp;show_comments=true&amp;show_user=true&amp;show_reposts=false"></iframe>
Basj
  • 41,386
  • 99
  • 383
  • 673
  • Setting WebView to show Desktop version of website helped. [Stackoverflow ref.](https://stackoverflow.com/questions/52899495/enable-only-desktop-mode-in-android-webview) – Tomaš Šturo Feb 11 '19 at 14:22

4 Answers4

14

I am going to suggest not using an embedded iframe for the player and instead use SoundCloud's HTTP API

My answer does not focus on any methods to trick the embedded iframe code into not thinking it is mobile. Instead I am showing an alternative path to how to do your own SoundCloud player.

Doing this guarantees:

  • You have full control over your UI
  • You have full control over playback

I've gone ahead and built a sample application in Android. Assuming you are looking for Android here because of the status bar in the posted question's image.

Also as requested there is a web project, that will work on mobile. The web project is using the SoundCloud's api JavaScript wrapper.

Update Oct 20th 2016: I did some research about autoplay on mobile in a web browser. It turns out there is lots of great questions answering this. Sadly I have come to the conclusion it is not possible. "Autoplay" HTML5 audio player on mobile browsers I've updated the javascript snippet to now not autoplay when loaded on mobile devices. It requires the user to press the play button.

Audio can not be played on page load, and requires at least one user interaction ( touch event ) with the page before it can be played. I would love to be proved wrong on this so if anyone knows anything else fire away!

You can find my example project here:

Web Project: https://github.com/davethomas11/stackoverlow_Q_39625513/tree/master/WebPlayer hosted here -> https://www.daveanthonythomas.com/remote/so39625513/

Android: https://github.com/davethomas11/stackoverlow_Q_39625513/SoundCloudPlayer

Check it out, and ask me any questions regarding implementation if anything is not clear. That goes for anyone reading this answer.

The solution is done natively in Java. But it could also be done in HTML and Javascript if that is what you prefer, because we are using their HTTP Rest API the platform does not matter.

Going completely custom, this way gives us full control over the UI. My UI isn't the most beautiful, but it can be as ugly or as beautiful as you want with this level of control ;) ->

SoundCloudPlayer

I will break down the basic steps of using sound cloud's api to accomplish this.

Luckily for us playback is very straight forward. You can skip all of the authentication requirements. As any endpoints you will be using do not require authentication.

All you need is a client id to make your requests. I recommend registering an app with sound cloud, but you can use the embedded player's client id like I did.

Note: the embedded player uses the client id -> cUa40O3Jg3Emvp6Tv4U6ymYYO50NUGpJ

The basis of this implementation is the tracks endpoint: https://developers.soundcloud.com/docs/api/reference#tracks

This endpoint gives us almost everything we need:

  • streaming url
  • title, artist name
  • artwork

But there is one thing missing and that is the waveform data points to display SoundCloud's brand identifying wave form.

The basics of getting this data requires a little bit of hacking. But the data is there in a pure enough form to use.

If you inspect the response of a call to get the embedded player, you'll notice a resource being loaded in the source code by the name of waveform_url. This url returns a nice json document with all the wave point information: https://wis.sndcdn.com/sTEoteC5oW3r_m.json

I've adapted my solution to parse the wave form data from the embedded player, by retrieving it from that url.

You'll notice I've made a very crude version. With a little elbow grease this can be turned into something nice, and even unique. But the basics are there for acquiring it.

enter image description here

Another endpoint I have implemented in my solution is the comments endpoint: https://developers.soundcloud.com/docs/api/reference#comments

I have not yet added it to the UI. But the API code should shed some light onto it's use.

The Android project uses the following libraries:

And for those not familiar, since it is semi new: - Android DataBinding https://developer.android.com/topic/libraries/data-binding/index.html

Please feel free to use my solution as a base, as I've released it under the GNU license. That goes to anyone reading this.

I'd like to consider adding a similar iOS solution to the git-hub repository too as well.

Here is the web project as a snippet: Edit I've updated it to use a waveform image as suggested in comments rather than taking on the complex task of rendering a waveform. It would be super cool if some one was able to reverse engineer the soundcloud canvas drawing. The JavaScript is available in that iframe.

/*!
 * jQuery UI Touch Punch 0.2.3
 *
 * Copyright 2011–2014, Dave Furfero
 * Dual licensed under the MIT or GPL Version 2 licenses.
 *
 * Depends:
 *  jquery.ui.widget.js
 *  jquery.ui.mouse.js
 */
!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);

function WaveForm(waveformPngUrl) {

 $('.track_waveform').append("<img src=\""+waveformPngUrl+"\" />");
 $('.track_waveform').append("<div class='wvprogress'></div>")

 this.setProgress = function (newProgress) {

  var width = $('.track_waveform').width();
  var progressPoint =  width - ((1 - newProgress) * width);
  $('.wvprogress').css({ width: "" + progressPoint + "px" });

 }
}

var player, mTrack, audio, seekBarInterval, waveForm;
var updatingSeekBar = false;
var clientId = 'cUa40O3Jg3Emvp6Tv4U6ymYYO50NUGpJ';

$(function () {

    SC.initialize({
        client_id: clientId
    });

    player = document.getElementById("SoundCloudPlayer");

    checkQueryURLForTrackId();
    loadTrackEnteredInInput();

    $("form button").button();
});

function loadTrackEnteredInInput() {

    loadTrack(getTrackId());
}

function loadTrack(trackId) {


    SC.get('/tracks/' + trackId).then(function (track) {

        // Inspect for info on track you want:
        console.log(track);
        mTrack = track;

        renderTrack(track);
        streamTrack(track);

        waveForm = new WaveForm(track.waveform_url);

    }, function () {

        alert("Sorry no track found for track id: "+ trackId)
    });
}

function renderTrack(track) {

    $(player).find(".track_artist").text(track.user.permalink);
    $(player).find(".track_title").text(track.title);
    $(player).find(".track_artwork").attr('src', track.artwork_url);
    $(player).find(".track_seek_bar").slider(
        {
            orientation: "horizontal",
            range: "min",
            max: track.duration,
            value: 0,
            change: seek
        });

}

function streamTrack(track) {

    var trackUrl = track.stream_url + "?client_id=" + clientId;

    audio = new Audio(trackUrl);
    console.log(trackUrl);

    if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
        
        // Sorry can not auto play on mobile =_(
        // https://stackoverflow.com/questions/26066062/autoplay-html5-audio-player-on-mobile-browsers
        $(player).find(".track_pause").hide();
        $(player).find(".track_play").fadeIn();
    } else {
        play();
    }
    
}

function play() {

    $(player).find(".track_play").hide();
    $(player).find(".track_pause").fadeIn();

    audio.play();

    seekBarInterval = setInterval(updateSeekBar, 500);
}

function pause() {

    $(player).find(".track_pause").hide();
    $(player).find(".track_play").fadeIn();

    audio.pause();

    clearInterval(seekBarInterval);
}

function seek(event) {

    if (event.originalEvent) {
        audio.currentTime = $(player).find(".track_seek_bar").slider("value") / 1000;
    }
    waveForm.setProgress((audio.currentTime * 1000) / mTrack.duration); 
}

function updateSeekBar() {

    var time = (audio.currentTime * 1000);
    $(player).find(".track_seek_bar").slider("value", time);
}

/**
 * Loads a different track id based on
 * url query
 */
function checkQueryURLForTrackId() {
    var query = getUrlVars();
    if (query.trackId) {
        $('[name=trackId]').val(query.trackId);
    }
}

//https://stackoverflow.com/questions/4656843/jquery-get-querystring-from-url
// Read a page's GET URL variables and return them as an associative array.
function getUrlVars()
{
    var vars = {}, hash;
    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
    for(var i = 0; i < hashes.length; i++)
    {
        hash = hashes[i].split('=');
        vars[hash[0]] = hash[1];
    }
    return vars;
}

function getTrackId() {
    return trackId = $('[name=trackId]').val();
}
body {
    font-family: 'Raleway', sans-serif;
}

#SoundCloudPlayer .track_artwork {
    float:left;
    margin-right: 6px;
}

#SoundCloudPlayer .track_artist {
    font-size: small;
    margin-bottom: 4px;
}

#SoundCloudPlayer .track_title {
    margin-top: 0px;
    font-weight: bold;
}

#SoundCloudPlayer .track_control {
    cursor: pointer;
    display: none;
}

#SoundCloudPlayer .track_seek_bar .ui-slider-range { background: orange; }
#SoundCloudPlayer .track_seek_bar .ui-slider-handle { border-color: orange; }

#SoundCloudPlayer .track_waveform {
    width: 100%;
    height: 80px;
    margin-top: 5px;
    margin-bottom: 5px;
    position: relative;
}

#SoundCloudPlayer .track_waveform img {
    
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    position: absolute;
    z-index: 0;
}

#SoundCloudPlayer .track_waveform .wvprogress{
    height: 100%;
    position: absolute;
    opacity: 0.25;
    background-color: #ed970e;
    width: 0px;
    z-index: 1;
    left: 0;
    top: 0;
}
<html>
<head>
    <meta name="viewport" content="initial-scale=1, maximum-scale=1">
    <title>SoundCloud API Web Player Demo</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
    <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.0/themes/smoothness/jquery-ui.css" />
    <script src="jquery.ui.touch-punch.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js"></script>
    <script src="https://connect.soundcloud.com/sdk/sdk-3.1.2.js"></script>
    <script src="waveformImage.js"></script>
    <script src="player.js"></script>
    <link href="https://fonts.googleapis.com/css?family=Raleway" rel="stylesheet" />
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
    <link href="style.css" rel="stylesheet" />
</head>
<body>

<form method="get">
    <label for="trackId">Load Track:</label>
    <input name="trackId" type="text" value="271188615" />
    <button>GO</button>
</form>

<section id="SoundCloudPlayer">

    <img class="track_artwork" />
    <p class="track_artist"></p>
    <p class="track_title"></p>
    <i class="material-icons track_play track_control" onClick="play()">play_circle_filled</i>
    <i class="material-icons track_pause track_control" onClick="pause()">pause_circle_filled</i>
    <br style="clear:both"/>
    <div class="track_waveform"></div>
    <div class="track_seek_bar" ></div>
</section>
</body>
</html>
Community
  • 1
  • 1
Dave Thomas
  • 3,667
  • 2
  • 33
  • 41
  • Yes of course no problem. Was thinking that was probably the case after I posted. I'll be back with that example later :). – Dave Thomas Sep 26 '16 at 13:47
  • Web project added. @Basj – Dave Thomas Sep 27 '16 at 06:06
  • Amazing answer! – Kas Elvirov Sep 30 '16 at 10:50
  • I did get a blurry waveform at one point. My final solution was not blurry for me? Just specifying the canvas' width and allowing the height to be auto got rid of the blur for me. The blur has to do with the size of the canvas element. Search blurry HTML canvas. – Dave Thomas Oct 01 '16 at 14:28
  • If you don't need to draw your own waveforms from scratch, version 1 of the API actually will return you an image of the waveform created by SoundCloud. Like this one. https://w1.sndcdn.com/gOCegnm5qXQm_m.png Just use api.soundcloud.com instead of api-v2.soundcloud.com and waveform_url will return you an image. – omygaudio Oct 15 '16 at 11:35
  • @Basj I will also come back to update my answer later tonight. Sorry for leaving you hanging. – Dave Thomas Oct 20 '16 at 16:17
  • @Basj Question updated. Bad news, autoplay is unavailable for mobile. Good news, waveform image is easier to work with, and I've updated my solution to use it. I tried a bit of debugging on the canvas blurry issue, but did not get anywhere useful tonight. – Dave Thomas Oct 21 '16 at 06:03
6

Mini Player (height=20) has similar look & feel for desktops and mobiles.

<iframe width="100%" height="20" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/271188615&color=ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false"></iframe>
Vadim Gulyakin
  • 1,415
  • 9
  • 7
  • Could you add a runnable code snippet and screenshot, for future reference @VadimGulyakin? – Basj Oct 16 '17 at 21:38
6

Use show_teaser=false as parameter to hide the overlay.

Markus
  • 76
  • 1
  • 3
1

You can set WebView to show desktop ver. of site:

WebView view = new WebView(this);
view.getSettings().setUserAgentString("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36");
Andronicus
  • 25,419
  • 17
  • 47
  • 88
Tomaš Šturo
  • 11
  • 1
  • 1