cuenation SCPlayer

Play cued mixes right from Soundcloud!

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        cuenation SCPlayer
// @locale      en
// @description Play cued mixes right from Soundcloud!
// @license     http://creativecommons.org/licenses/by-sa/3.0/
// @namespace   http://cuenation.com
// @include     http://cuenation.com/?page=tracklist*.cue
// @version     0.7.3
// ==/UserScript==

/**
 * Changelog
 * v0.1         Initial version
 * v0.2         switched to SC Widget API
 * v0.3         Added some style
 * v0.4         'Now playing...' caption
 * v0.4.1       Cursor changed to pointer
 * v0.5         Added cursor, play/pause fix
 * v0.6         Hidden/visible player
 * v0.6.1       Support for private tracks with secret_token
 * v0.6.2       LOAD_PROGRESS is not supported any more
 * v0.7         Chrome support, progress bar on tracks
 * v0.7.1       Bugfix at seeking
 * v0.7.2       Update cursor if seeking in paused state
 * v0.7.3       Typo fixes + SC ID
 *
 *
 * Todo list, known issues:
 * - volume control
 * - support for various cueNation layouts
 * - shuffle/reorder tracks
 * - document and optimize code
 *
 */

var doc = document;
var forEach = Array.prototype.forEach;

var $ = null;
var SC = null;
var widget = null;

var loadedPercent = 0.0;
var loadedMs = 0;
var duration = 0;
var lastLoadedTrack = 0;
var tracks = new Array();
var currentIdx = -1;
var paused = true;

/* General class for the Tracks */
function Track(_o, _l, _e, _clickCallback) {
    this.offset = _o;
    this.element = _e;
    this.length = _l;
    var self = this;
    this._addEvent('click', function(e){
        _clickCallback(self.offset);
        e.stopPropagation();
    });
}

Track.prototype.Length = function() {
    return this.length;
};

Track.prototype.Offset = function() {
    return this.offset;
};

Track.prototype.Element = function() {
    return this.element;
};

Track.prototype._addEvent = function(eventName, callback) {
    if (this.element.addEventListener) {
        this.element.addEventListener(eventName, callback, false);
    } else {
        this.element.attachEvent(eventName, callback, false);
    }
};

/***************************/
/******** Functions */

// Init Soundcloud player widget API
function initSC1() {
    var sc = doc.createElement('script');
    sc.src = 'https://w.soundcloud.com/player/api.js';
    sc.type = 'text/javascript';
    sc.id = 'api1';
    sc.onload = function () {
        initSC2();
    };
    doc.getElementsByTagName('head')[0].appendChild(sc);
}

// Init Soundcloud general API 
function initSC2() {
    var sc = doc.createElement('script');
    sc.src = 'https://connect.soundcloud.com/sdk.js';
    sc.type = 'text/javascript';
    sc.onload = function () {
        initJQ();
    };
    doc.getElementsByTagName('head')[0].appendChild(sc);
}

// Init JQuery 
function initJQ() {
    var jq = doc.createElement('script');
    jq.src = 'http://code.jquery.com/jquery.min.js';
    jq.type = 'text/javascript';
    jq.onload = function() {
        initWidget();
    };
    doc.getElementsByTagName('head')[0].appendChild(jq);
}


// Function to retrieve the SC url from the links divs 
function getSCUrl() {
    var anchors = doc.querySelectorAll('a.clear');
    var firstUrl = '';
    var found = false;
    forEach.call(anchors, function(anchor){
        if (anchor.href.indexOf('soundcloud.com') > 0 && !found) {
            firstUrl = anchor.href;
            found = true;
        }
    });
    if (firstUrl !== ''){
        var re = new RegExp('.*url=(.*soundcloud\.com.*)', 'g');
        var arr = re.exec(firstUrl);
        var url = unescape(arr[1]);
        if (url.indexOf('/download') > 0) {
            url = url.replace('download', '');
        }
        return url;
    }else {
        return '';
    }
}

// Init the Soundcloud widget
function initWidget() {
    $ = unsafeWindow.jQuery;
    SC = unsafeWindow.SC;

    var placeholder = $('h2.title');
    var track_url = getSCUrl();
    if (track_url !== '') {
        SC.initialize({
            client_id: '24fe22b60396d3664c297e47f0f27929'
        });
        SC.get('/resolve', {url: track_url},  function(track){
            if (track.id === null || track.id === undefined) {
                addNoSC(placeholder, 'Track is removed. Player is disabled.');
            } else {
                var minimized = false;
                var soundurl = track.uri;
                var woptions = '&auto_play=false&auto_advance=false&buying=true&liking=true';
                woptions += '&download=true&sharing=true&show_artwork=false&show_comments=false';
                woptions += '&show_playcount=true&show_user=false';

                placeholder.after('<div id="box"><table id="boxCaption"><tr><td id="showhide"></td><td id="current"></td></tr></table><iframe class="ifr" width="100%" height="160" scrolling="no" frameborder="no"></iframe></div>');

                var ifr = doc.querySelector('.ifr');
                ifr.style.transition = 'height 0.5s linear 0s';
                ifr.style.height = '160px';
                ifr.src = 'https://w.soundcloud.com/player/?url=' + soundurl + woptions;
                widget = SC.Widget(ifr);

                initEvents();
                var boxCaption = doc.querySelector('#boxCaption');
                boxCaption.style.color = '#aaa';
                boxCaption.style.margin = '0px';
                boxCaption.style.cursor = 'pointer';
                boxCaption.style.backgroundColor = '#f5f5f5';
                boxCaption.style.border = '1px solid #e5e5e5';
                boxCaption.style.width = '100%';
                boxCaption.style.height = '10px';
                boxCaption.style.tableLayout = 'fixed';

                var sh = doc.querySelector('#showhide');
                sh.style.paddingRight = '4px';
                sh.style.width = '90px';
                sh.innerHTML = '<< Hide Player';

                var box = doc.querySelector('#box');
                box.style.height = '170px';
                box.style.transition = 'height 0.5s linear 0s';

                boxCaption.addEventListener('click', function(e) {
                    if (!minimized){
                        box.style.height = '10px';
                        ifr.style.height = '0px';
                        minimized = true;
                        sh.innerHTML = '>> Show Player';
                    } else {
                        box.style.height = '170px';
                        ifr.style.height = '160px';
                        minimized = false;
                        sh.innerHTML = '<< Hide Player';
                    }
                });
            }
        });
    } else {
        addNoSC(placeholder, 'No soundcloud link is found. Player is disabled.');
    }
}

function addNoSC(placeholder, msg) {
    placeholder.after('<div class="noSC">'+msg+'</div>');
    var noSC = doc.querySelector('.noSC');
    noSC.style.color = '#999';
    noSC.style.paddingLeft = '0px';
    noSC.style.paddingBottom = '0px';
    noSC.style.fontStyle = 'italic';
}

function updateCurrent (playData)
{
  var res = {};
  res = getCurrentTrack(playData);
  if (res.ix != currentIdx) {
    removeCursor(currentIdx);
    addCursor(res.ix);
    currentIdx = res.ix;
  }
  updateProgressBar(res); //res.ix, res.progress
}

// bind to SC events
function initEvents(){
    if (widget === null) {
        return;
    }

    widget.bind(SC.Widget.Events['READY'], function(){
        widget.setVolume(20);
        widget.getDuration(function(val){
            duration = val;
        });
    });

    widget.bind(SC.Widget.Events['PLAY_PROGRESS'], function(eData){
        updateCurrent(eData);
        if (lastLoadedTrack == tracks.length-1)
        {
          return;
        }
        loadedPercent = eData.loadedProgress;
        loadedMs = duration * loadedPercent;
        for (var i = lastLoadedTrack; i < tracks.length; i++) {
            if (tracks[i].Offset() <= loadedMs) {
                tracks[i].Element().style.textDecoration = 'underline';
                tracks[i].Element().style.color = 'rgba(255,102,0,1.0)';
                tracks[i].Element().style.cursor = 'pointer';
                lastLoadedTrack = i;
            }
        }
    });

    widget.bind(SC.Widget.Events['PAUSE'], function(eData){
        var res = {};
        res = getCurrentTrack(eData);
        paused = true;
        var p = doc.querySelector('.pauseBtn');
        if (p !== null){
            p.innerHTML = 'play';
        }
    });

    widget.bind(SC.Widget.Events['PLAY'], function(eData){
        var res = {};
        res = getCurrentTrack(eData);
        paused = false;
        var p = doc.querySelector('.pauseBtn');
        if (p !== null){
            p.innerHTML = 'pause';
        }
    });

    widget.bind(SC.Widget.Events['FINISH'], function(eData){
        seekTo(0);
        widget.pause();
    });
}

function removeCursor(idx) {
    if (idx < 0) {
        return;
    }
    var idxElement = tracks[idx].Element().nextSibling;
    var titleElement = tracks[idx].Element().nextSibling.nextSibling;
    if (titleElement !== null && idxElement !== null) {
        idxElement.style.backgroundColor = 'rgba(255,255,255,0.0)';
        titleElement.style.backgroundColor = 'rgba(255,255,255,0.0)';
    }
    var toRemove = doc.querySelector('.pauseBtn');
    if (null !== toRemove) {
        toRemove.parentNode.removeChild(toRemove);
    }
}

function addCursor(idx) {
    var idxElement = tracks[idx].Element().nextSibling;
    var titleElement = tracks[idx].Element().nextSibling.nextSibling;
    if (titleElement !== null && idxElement !== null) {
        idxElement.style.backgroundColor = 'rgba(255,102,0,0.3)';
        titleElement.style.backgroundColor = 'rgba(255,102,0,0.3)';
        titleElement.style.height = '1.5pt';
    }
    var span = document.createElement('span');
    span.setAttribute('class', 'pauseBtn');
    span.innerHTML = paused ? 'play' : 'pause';
    span.style.paddingLeft = '7px';
    span.style.color = 'rgba(255,102,0,1.0)';
    span.style.fontWeight = 'bold';
    span.style.cursor = 'pointer';

    var cp = doc.querySelector('#current');
    cp.style.color = 'rgba(255,102,0,1.0)';
    cp.style.paddingLeft = '4px';
    cp.style.paddingRight = '4px';
    cp.style.borderLeft = '1px solid #e5e5e5';
    cp.innerHTML = '<marquee behavior="alternate" scrollamount=1 scrolldelay=5>Now playing: ' + titleElement.innerHTML + '</marquee>';

    span.addEventListener('click', function(e){
        if (!paused) {
            widget.pause();
        } else {
            widget.play();
        }
    });

    titleElement.appendChild(span);
}

function updateProgressBar(res)
{
  //res.ix, res.progress
  var toUpdate = doc.querySelector('.pBar');
  if (null !== toUpdate) {
      var i = toUpdate.getAttribute('ix');
      if (i != res.ix)
      {
        toUpdate.parentNode.removeChild(toUpdate);
      }
      toUpdate.style.width = '' + res.progress*100 + '%';
      return;
  }
  var toAdd = doc.querySelector('.pauseBtn');
  if (null !== toAdd) {
      var span = document.createElement('span');
      span.setAttribute('class', 'pBar');
      span.setAttribute('ix', res.ix);
      span.style.backgroundColor = 'rgba(255,102,0,1.0)';
      span.style.height = '2px';
      span.style.display = 'block';
      span.style.width = '' + res.progress*100 + '%';
      toAdd.parentNode.appendChild(span);
  }
}

function getCurrentTrack(eData) {
    var currPos = eData.currentPosition;
    var currIdx = 0;
    var found = false;
    var percent = 0;

    if (currPos >= tracks[tracks.length-1].Offset()) {
        currIdx = tracks.length-1;
        percent = (currPos - tracks[tracks.length-1].Offset())/(duration - tracks[tracks.length-1].Offset());
        found = true;
    }
    for (var i = 0; i < tracks.length - 1 && !found; i++) {
        if (tracks[i].Offset() <= currPos && tracks[i+1].Offset() > currPos) {
            currIdx = i;
            percent = (currPos - tracks[i].Offset())/(tracks[i+1].Offset() - tracks[i].Offset());
            found = true;
        }
    }
    return {ix: currIdx, progress: percent};
}

// seek to a certain position in milliseconds
function seekTo(ms) {
    if (null !== widget) {
        if (ms <= loadedMs){
            widget.seekTo(ms);
            var playData = {currentPosition: ms};
            updateCurrent(playData);
        } else {
            //???
        }
    }
}

// init the 'buttons': time offsets on the page
function initButtons() {
    var times = doc.querySelectorAll('.timeindex');

    for (var i = 0; i < times.length; i++) {
        var tracktimeParts = times[i].innerHTML.replace(/^.(\s+)?/, '').replace(/(\s+)?.$/, '').split(':');
        var ms = tracktimeParts[0]*60000 + tracktimeParts[1]*1000;
        var trackLengthElement = times[i].nextSibling.nextSibling.nextSibling;
        if (trackLengthElement !== null) {
            var l = trackLengthElement.innerHTML.replace(/^.(\s+)?/, '').replace(/(\s+)?.$/, '').split(':');
            var newTrack = new Track(ms, l, times[i], seekTo);
            tracks[i] = newTrack;
        }
    }
}


// MAIN
initButtons();
initSC1();