手机上的 Soundcloud 嵌入式播放器

Soundcloud embedded player on mobile

以下是 HTML 页面上的 SoundCloud 嵌入式播放器在移动设备上的样子:

这很烦人,因为用户必须单击 "Listen in browser",然后,它通常不会按应有的方式启动,因此用户必须单击 "Pause" 按钮和 "Play" 再次。

如何在移动设备上拥有正常的外观? :


这里是嵌入代码的例子:

<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>

我建议不要为播放器使用嵌入式 iframe,而是使用 SoundCloud's HTTP API

我的回答不关注任何欺骗嵌入式 iframe 代码使其不认为它是移动的方法。相反,我展示了另一种方法来制作您自己的 SoundCloud 播放器。

这样做可以保证:

  • 您可以完全控制您的 UI
  • 您可以完全控制播放

我已经在 Android 中构建了一个示例应用程序。假设您正在寻找 Android 是因为发布的问题图像中的状态栏。

另外还有一个 Web 项目,可以在移动设备上运行。 Web 项目正在使用 SoundCloud 的 api JavaScript 包装器。

2016 年 10 月 20 日更新: 我在网络浏览器中做了一些关于移动设备自动播放的研究。事实证明,有很多很好的问题可以回答这个问题。可悲的是,我得出的结论是不可能的。 "Autoplay" HTML5 audio player on mobile browsers 我已将 javascript 片段更新为现在在移动设备上加载时不会自动播放。它需要用户按下播放按钮。

音频无法在页面加载时播放,并且需要至少与页面进行一次用户交互(触摸事件)才能播放。我很乐意在这方面被证明是错误的,所以如果有人知道其他任何事情,请开火!

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

检查一下,如果有任何不清楚的地方,请问我有关实施的问题。这适用于阅读此答案的任何人。

解决方案是在 Java 中本地完成的。但它也可以在 HTML 和 Java 脚本中完成,如果你喜欢的话,因为我们使用的是他们的 HTTP Rest API 平台无关紧要。

完全自定义,这种方式让我们可以完全控制 UI。 我的 UI 不是最漂亮的,但在这种控制级别下,它可以随心所欲地丑陋或美丽 ;) ->

我将分解使用 sound cloud 的 api 来完成此操作的基本步骤。

幸运的是,我们的回放非常简单。您可以跳过所有身份验证要求。由于您将使用的任何端点都不需要身份验证。

您只需要一个客户端 ID 即可发出您的请求。 我建议使用声云注册一个应用程序,但您可以像我一样使用嵌入式播放器的客户端 ID。

注意:嵌入式播放器使用客户端id -> cUa40O3Jg3Emvp6Tv4U6ymYYO50NUGpJ

此实现的基础是 tracks 端点: https://developers.soundcloud.com/docs/api/reference#tracks

这个端点几乎提供了我们需要的一切:

  • 流媒体url
  • 标题,艺术家姓名
  • 艺术作品

但是缺少一件事,那就是显示 SoundCloud 品牌识别波形的波形数据点。

获取这些数据的基础知识需要一点黑客技巧。但是数据以足够纯粹的形式存在。

如果您检查获取嵌入式播放器的调用的响应,您会注意到源代码中正在加载名为 waveform_url 的资源。这个 url returns 一个很好的 json 文档,包含所有波点信息:https://wis.sndcdn.com/sTEoteC5oW3r_m.json

我已经调整了我的解决方案来解析来自嵌入式播放器的波形数据,方法是从 url.

中检索它

您会注意到我制作了一个非常粗糙的版本。用一点肘部润滑脂可以把它变成漂亮的东西,甚至是独一无二的。但是获得它的基础知识是存在的。

我在我的解决方案中实现的另一个端点是评论端点:https://developers.soundcloud.com/docs/api/reference#comments

我还没有将它添加到 UI。但是 API 代码应该可以说明它的用途。

Android 项目使用以下库:

对于那些不熟悉的人,因为它是半新的: - Android 数据绑定 https://developer.android.com/topic/libraries/data-binding/index.html

请随意使用我的解决方案作为基础,因为我是在 GNU 许可下发布的。这适用于阅读本文的任何人。

我也想考虑向 git-hub 存储库添加类似的 iOS 解决方案。

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://whosebug.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://whosebug.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>

迷你播放器(高度=20)在台式机和移动设备上的外观相似。

<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>

您可以设置WebView显示桌面版。网站:

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");

使用 show_teaser=false 作为参数隐藏叠加层。