sc-player.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. /*
  2. * SoundCloud Pure Player
  3. *
  4. * The project to rewrite https://github.com/soundcloud/soundcloud-custom-player ((c) Matas Petrikas, MIT)
  5. * on a pure js code.
  6. * Original project source code:
  7. * https://github.com/OpenA/soundcloud-pure-player
  8. *
  9. * Usage:
  10. * <a href="https://soundcloud.com/ueffin-chatlan/reminiscences" class="sc-player">My new dub track</a>
  11. * The link will be automatically replaced by the HTML based player
  12. */
  13. function SoundCloudAPI() {
  14. var _$ = this;
  15. Object.defineProperties(this, {
  16. version: {
  17. enumerable: true,
  18. value: '1.0'
  19. },
  20. apiKey: {
  21. enumerable: true,
  22. writable: true,
  23. value: 'htuiRd1JP11Ww0X72T1C3g'
  24. },
  25. debug: {
  26. enumerable: true,
  27. writable: true,
  28. value: true
  29. },
  30. getTracks : { value: $getTracks },
  31. fetch : { value: $fetch }
  32. });
  33. function $fetch(url, callback, errorback) {
  34. if (!url) {
  35. return $panic('requested url is "'+ url +'"', errorback);
  36. }
  37. if (typeof callback !== 'function') {
  38. return (window.Promise ? new Promise(function(resolve, reject) {
  39. $fetch(url, resolve, reject)
  40. }) : null);
  41. }
  42. var protocol = (location.protocol === 'https:' ? 'https:' : 'http:'),
  43. resolve = protocol +'//api.soundcloud.com/resolve?url=',
  44. params = 'format=json&consumer_key='+ _$.apiKey, apiUrl;
  45. // force the secure url in unsecure environment
  46. url = url.replace(/^https?:/, protocol);
  47. // check if it's already a resolved api url
  48. if ((/api\.soundcloud\.com/).test(url)) {
  49. apiUrl = url + '?' + params;
  50. } else {
  51. apiUrl = resolve + url + '&' + params;
  52. }
  53. var xhr = new XMLHttpRequest;
  54. xhr.onreadystatechange = function() {
  55. if (this.readyState !== 4)
  56. return;
  57. if (this.status === 200) {
  58. try {
  59. var data = JSON.parse(this.responseText);
  60. } catch(log) {
  61. if (_$.debug && window.console) {
  62. console.error(log)
  63. }
  64. } finally {
  65. callback(data);
  66. }
  67. } else {
  68. return $panic('unable to GET '+ url +' ('+ this.status +
  69. (!this.statusText ? '' : ' '+ this.statusText) +')', errorback);
  70. }
  71. };
  72. xhr.open('GET', apiUrl, true);
  73. xhr.send(null);
  74. }
  75. function $panic(msg, errorback) {
  76. if (_$.debug && window.console) {
  77. console.error('SoundCloudAPI: '+ msg);
  78. }
  79. if (typeof errorback !== 'function') {
  80. return (window.Promise ? new Promise(function(resolve, reject) {
  81. reject(new EvalError(msg));
  82. }) : null);
  83. } else
  84. errorback(new EvalError(msg));
  85. }
  86. function $getTracks(url, callback, errorback) {
  87. if (!url) {
  88. return $panic('requested url is "'+ url +'"', errorback);
  89. }
  90. if (typeof callback !== 'function') {
  91. return (window.Promise ? new Promise(function(resolve, reject) {
  92. $getTracks(url, resolve, reject)
  93. }) : null);
  94. }
  95. var $bound = function(data) {
  96. if (data) {
  97. if (data.tracks) {
  98. // log('data.tracks', data.tracks);
  99. callback(data.tracks);
  100. } else if (Array.isArray(data)) {
  101. callback(data);
  102. } else if (data.duration){
  103. // a secret link fix, till the SC API returns permalink with secret on secret response
  104. data.permalink_url = url;
  105. // if track, add to player
  106. callback([data]);
  107. } else if (data.creator || data.username) {
  108. // get user or group tracks, or favorites
  109. $fetch(data.uri + (data.username && url.indexOf('favorites') != -1 ? '/favorites' : '/tracks'), $bound, errorback);
  110. }
  111. }
  112. }
  113. $fetch(url, $bound, errorback);
  114. }
  115. };
  116. (function() {
  117. var SC = {
  118. 'API': new SoundCloudAPI,
  119. 'Global': false,
  120. 'Volume': 0.8,
  121. 'Tracks': {},
  122. 'Object': {},
  123. get 'Progress'() {
  124. return 0;
  125. }
  126. }
  127. var _handler = 'ontouchstart' in window ? {
  128. start: 'touchstart',
  129. move: 'touchmove',
  130. end: 'touchend',
  131. getCoords: function(e) {
  132. return (e.touches.length === 1 ? { x: e.changedTouches[0].clientX, y: e.changedTouches[0].clientY } : null);
  133. }
  134. } : {
  135. start: 'mousedown',
  136. move: 'mousemove',
  137. end: 'mouseup',
  138. getCoords: function(e) {
  139. return (e.button === 0 ? { x: e.clientX, y: e.clientY } : null);
  140. }
  141. };
  142. var _Current_ = {
  143. SavedState : null,
  144. TrackLoaded: null,
  145. SelectTrack: null,
  146. PlayerNode : null,
  147. AudioDevice: createAudioDevice(),
  148. /* Complex */
  149. set 'Player Volume' (vol) {
  150. this.AudioDevice.volume = this.SavedState.Volume = vol;
  151. this.PlayerNode['_volume_'].firstElementChild.style['width'] = (vol * 100) +'%';
  152. },
  153. get 'Player Volume' () {
  154. return this.PlayerNode['_volume_'].firstElementChild.style['width'];
  155. },
  156. set 'Track Duration' (sec) {
  157. this.TrackLoaded.duration = sec;
  158. this.PlayerNode['_duration_'].textContent = (sec = timeCalc(sec));
  159. this.SelectTrack['_duration_'].textContent = sec;
  160. },
  161. get 'Track Duration' () {
  162. return this.SelectTrack['_duration_'].textContent;
  163. },
  164. set 'Track Progress' (sec) {
  165. this.SavedState.Progress = sec;
  166. this.PlayerNode['_position_'].textContent = timeCalc(sec);
  167. this.PlayerNode['_progress_'].style['width'] = (sec / this.TrackLoaded.duration * 100) +'%';
  168. },
  169. get 'Track Progress' () {
  170. return this.PlayerNode['_progress_'].style['width'];
  171. },
  172. set 'Track Buffered' (buf) {
  173. this.PlayerNode['_buffer_'].style['width'] = buf +'%';
  174. },
  175. get 'Track Buffered' () {
  176. return this.PlayerNode['_buffer_'].style['width'] === '100%';
  177. },
  178. invokeEvent: function(name) {
  179. this.PlayerNode.dispatchEvent(
  180. new CustomEvent(name, {
  181. bubbles: true, cancelable: true,
  182. detail: {
  183. track: this.TrackLoaded, device: this.AudioDevice
  184. }
  185. }));
  186. },
  187. connect: function(player_node, track_node, saved_state) {
  188. if (saved_state) {
  189. this.SavedState = saved_state;
  190. }
  191. if (player_node && player_node !== this.PlayerNode) {
  192. if (this.PlayerNode) {
  193. this.PlayerNode[ '_volume_' ]['on'+ _handler.start] = null;
  194. this.PlayerNode['_waveform_']['on'+ _handler.start] = null;
  195. }
  196. this.PlayerNode = ('_trackslist_' in player_node ? player_node : catchKeyElements('player', player_node));
  197. this.PlayerNode[ '_volume_' ]['on'+ _handler.start] = barChanger;
  198. this.PlayerNode['_waveform_']['on'+ _handler.start] = barChanger;
  199. this['Player Volume'] = this.SavedState.Volume;
  200. }
  201. if (!track_node) {
  202. track_node = this.PlayerNode.querySelector('.sc-track.active') || this.PlayerNode['_trackslist_'].firstElementChild;
  203. }
  204. if (track_node && track_node !== this.SelectTrack) {
  205. (this.PlayerNode.querySelector('.sc-track.active') || {}).className = 'sc-track';
  206. track_node.className = 'sc-track active';
  207. this.SelectTrack = ('_duration_' in track_node ? track_node : catchKeyElements('track', track_node));
  208. this.TrackLoaded = SC['Tracks'][track_node.id.slice(track_node.id.lastIndexOf('_') + 1)];
  209. this['Track Buffered'] = 0;
  210. updateTrackInfo(this.PlayerNode, this.TrackLoaded);
  211. this['AudioDevice'].src = this.TrackLoaded.stream_url + (this.TrackLoaded.stream_url.indexOf('?') >= 0 ? '&' : '?') +'consumer_key='+ SC['API'].apiKey;
  212. this['AudioDevice'].currentTime = this.SavedState.Progress;
  213. this['AudioDevice'].play();
  214. }
  215. }
  216. }
  217. var _fileDownload = 'download' in HTMLAnchorElement.prototype ? function() {
  218. var anchor = document.createElement('a');
  219. (_fileDownload = function(button) {
  220. var uri = button.href +'?consumer_key='+ SC['API'].apiKey,
  221. track = SC['Tracks'][uri.match(/\/(-?\d+)\//)[1]];
  222. if (!track.downloadable) {
  223. button.textContent = '0%';
  224. for (var i = 0, sd = document.querySelectorAll('.sc-download'); i < sd.length; i++) {
  225. sd[i].className = 'sc-disabled';
  226. }
  227. var wReq = new XMLHttpRequest;
  228. wReq.responseType = 'blob';
  229. wReq.onprogress = function(e) {
  230. var percent = Math.round(e.loaded / e.total * 100),
  231. progBar = percent +'% ';
  232. for (; percent > 10; percent -= 10)
  233. progBar += '»';
  234. button.textContent = progBar;
  235. };
  236. wReq.onload = function() {
  237. track.blob_uri = (anchor.href = window.URL.createObjectURL(wReq.response));
  238. track.blob_name = (anchor.download = track.title +'.'+ wReq.response.type.replace('audio/', '').replace('mpeg', 'mp3'));
  239. track.downloadable = !document.body.appendChild(anchor).click();
  240. button.textContent = '» Download «';
  241. while (i--) {
  242. sd[i].className = 'sc-download';
  243. }
  244. };
  245. wReq.open('GET', uri, true);
  246. wReq.send(null);
  247. } else {
  248. anchor.href = track.blob_uri || uri;
  249. anchor.download = track.blob_name || '';
  250. document.body.appendChild(anchor).click();
  251. }
  252. })(arguments[0]);
  253. } : function(a) {
  254. window.open(a.href +'?consumer_key='+ SC['API'].apiKey, '_blank', 'width=400,height=200');
  255. };
  256. if (SC['Global']) {
  257. window.addEventListener('click', onClickHandler, false);
  258. }
  259. window.SCPurePlayer = {
  260. create: _scCreate,
  261. createGroup: _scCreateGroup
  262. }
  263. if (document.readyState === 'loading') {
  264. document.addEventListener('DOMContentLoaded', function(e) {
  265. this.removeEventListener(e.type, arguments.callee, false);
  266. onDOMReady();
  267. }, false);
  268. } else
  269. onDOMReady();
  270. function _scCreateGroup(links) {
  271. var $hash = genGroupId(),
  272. inact = true,
  273. ibx = links.length,
  274. $node = createPlayerDOM(ibx, $hash);
  275. Array.prototype.slice.call(links, 0).forEach(function(link, it) {
  276. SC['API'].getTracks(link.href, function(tracks)
  277. { ibx--;
  278. var tNode = createTrackDOM(tracks[0], $hash),
  279. tChild = $node['_trackslist_'].children['ft_'+ $hash +'_'+ it];
  280. $node['_trackslist_'].replaceChild(tNode, tChild);
  281. for (var j = 1; j < tracks.length; j++) {
  282. tChild = tNode.nextSibling;
  283. tNode = createTrackDOM(tracks[j], $hash);
  284. $node['_trackslist_'].insertBefore(tNode, tChild);
  285. }
  286. if (it === 0) {
  287. inact = false;
  288. updateTrackInfo($node, tracks[0]);
  289. tNode.className += ' active';
  290. } else if (ibx === 0 && inact) {
  291. tNode = $node['_trackslist_'].firstElementChild;
  292. updateTrackInfo($node, SC['Tracks'][tNode.id.split('_')[2]]);
  293. tNode.className += ' active';
  294. }
  295. }, function(error)
  296. { ibx--;
  297. $node['_trackslist_'].children['ft_'+ $hash +'_'+ it].remove();
  298. if (ibx === 0) {
  299. var tNode = $node['_trackslist_'].firstElementChild;
  300. if (!tNode) {
  301. $node.removeAttribute('id');
  302. } else if (inact) {
  303. updateTrackInfo($node, SC['Tracks'][tNode.id.split('_')[2]]);
  304. tNode.className += ' active';
  305. }
  306. }
  307. });
  308. });
  309. return $node;
  310. }
  311. function _scCreate(link) {
  312. var $hash = genGroupId(),
  313. $node = createPlayerDOM(-1, $hash);
  314. SC['API'].getTracks(link.href, function(tracks){
  315. for (var j = 0; j < tracks.length; j++) {
  316. var tNode = createTrackDOM(tracks[j], $hash);
  317. $node['_trackslist_'].insertBefore(
  318. tNode, $node['_trackslist_'].children[j]
  319. );
  320. if (j === 0) {
  321. updateTrackInfo($node, tracks[j]);
  322. tNode.className += ' active';
  323. }
  324. }
  325. }, function(error) {
  326. $node.removeAttribute('id');
  327. });
  328. return $node;
  329. }
  330. function onDOMReady(e) {
  331. Array.prototype.slice.call(document.getElementsByClassName('sc-player'), 0).forEach(function(scp) {
  332. var node = scp.href ? _scCreate(scp) : _scCreateGroup(scp.querySelectorAll('a[href*="soundcloud.com/"]'));
  333. scp.parentNode.replaceChild(node, scp);
  334. });
  335. if (_Current_['AudioDevice'].tagName === 'OBJECT') {
  336. var engineContainer = document.createElement('scont');
  337. engineContainer.className = 'sc-engine-container';
  338. engineContainer.setAttribute('style', 'position: absolute; left: -9000px');
  339. engineContainer.appendChild(_Current_['AudioDevice']);
  340. document.body.appendChild(engineContainer);
  341. }
  342. }
  343. function onEnd(e) {
  344. var play_next;
  345. _Current_.SavedState.Progress = 0;
  346. _Current_.invokeEvent('ended');
  347. if ((play_next = _Current_.SelectTrack.nextElementSibling)) {
  348. _Current_.connect(null, play_next);
  349. } else {
  350. _Current_.PlayerNode['_button_'].className = 'sc-play';
  351. _Current_.PlayerNode['_button_'].textContent = 'Play';
  352. _Current_.PlayerNode.className = 'sc-player';
  353. _Current_.SelectTrack.className = 'sc-track';
  354. _Current_.PlayerNode['_trackslist_'].children[0].className = 'sc-track active';
  355. if ((play_next = _Current_.PlayerNode.nextElementSibling) && play_next.id &&
  356. play_next.className.substring(0, 9) === 'sc-player') {
  357. _Current_.connect(play_next, null, SC['Object'][play_next.id.slice(play_next.id.indexOf('_') + 1)]);
  358. }
  359. }
  360. }
  361. function onTimeUpdate(e) {
  362. _Current_['Track Progress'] = e.target.currentTime;
  363. _Current_.invokeEvent('timeupdate');
  364. }
  365. function onBufferLoad(e) {
  366. if (!_Current_['Track Buffered']) {
  367. _Current_['Track Buffered'] = this.bytesPercent;
  368. }
  369. }
  370. function onClickHandler(e) {
  371. if (e.button != 0 || !e.target.className)
  372. return;
  373. if (e.target.className.slice(0, 3) === 'sc-') {
  374. var $target = e.target,
  375. $class = $target.classList || $target.className.split(' '),
  376. $sc = $class[0].split('-');
  377. e.preventDefault();
  378. switch ($sc[1]) {
  379. case 'download':
  380. _fileDownload($target);
  381. break;
  382. case 'info':
  383. if ($sc[2] === 'close') {
  384. $target.parentNode.className = 'sc-info';
  385. } else if ($sc[2] === 'toggle') {
  386. $target.parentNode.children[1].className = 'sc-info active';
  387. }
  388. break;
  389. case 'track':
  390. var $player = $target.parentNode.parentNode;
  391. if ($sc[2]) {
  392. $player = $player.parentNode;
  393. $target = $target.parentNode;
  394. }
  395. var $obj = SC['Object'][$player.id.slice($player.id.indexOf('_') + 1)];
  396. $obj.Progress = 0;
  397. _Current_.connect($player, $target, $obj);
  398. break;
  399. case 'play':
  400. var $player = $target.parentNode.parentNode;
  401. if (!$player.id)
  402. return;
  403. _Current_.connect($player, null, SC['Object'][$player.id.slice($player.id.indexOf('_') + 1)]);
  404. case 'pause':
  405. _Current_.AudioDevice[$sc[1]]();
  406. case 'disabled':
  407. }
  408. }
  409. }
  410. function onPlayerAction(e) {
  411. for (var i = 0, el = document.querySelectorAll(
  412. '.sc-pause, .sc-player.played, .sc-player.paused'
  413. ); i < el.length; i++) {
  414. if (el[i].className === 'sc-pause') {
  415. el[i].className = 'sc-play';
  416. el[i].textContent = 'Play' ;
  417. } else {
  418. el[i].className = 'sc-player';
  419. }
  420. }
  421. var ype = (e.type === 'play' ? 'ause' : 'lay')
  422. _Current_.PlayerNode['_button_'].className = 'sc-p'+ ype;
  423. _Current_.PlayerNode['_button_'].textContent = 'P' + ype;
  424. _Current_.PlayerNode.className = 'sc-player '+ e.type + (e.type === 'play' ? 'ed' : 'd');
  425. _Current_.invokeEvent(e.type);
  426. }
  427. function barChanger(e) {
  428. var coords = _handler.getCoords(e);
  429. if (!coords) {
  430. return;
  431. }
  432. e.preventDefault();
  433. var barMove, barEnd;
  434. var rect = this.getBoundingClientRect(),
  435. x = (coords.x - rect.left) / ('width' in rect ? rect.width : (rect.width = rect.right - rect.left));
  436. if (this === _Current_.PlayerNode['_waveform_']) {
  437. var maxs = _Current_.TrackLoaded.duration,
  438. curT = _Current_['AudioDevice'].currentTime,
  439. seek = x > 1 ? maxs : x < 0 ? 0 : Math.floor(maxs * x * 1000000) / 1000000;
  440. _Current_['AudioDevice'].ontimeupdate = null;
  441. _Current_['Track Progress'] = seek;
  442. if (seek > curT || curT < seek) {
  443. _Current_.invokeEvent('seeking');
  444. }
  445. barMove = function(eM) {
  446. maxs = _Current_.TrackLoaded.duration;
  447. x = (_handler.getCoords(eM).x - rect.left) / rect.width;
  448. seek = x > 1 ? maxs : x < 0 ? 0 : Math.floor(maxs * x * 1000000) / 1000000;
  449. _Current_['Track Progress'] = seek;
  450. _Current_.invokeEvent('seeking');
  451. }
  452. barEnd = function(eE) {
  453. _Current_['AudioDevice'].ontimeupdate = onTimeUpdate;
  454. _Current_['AudioDevice'].currentTime = seek;
  455. _Current_.invokeEvent('seeked');
  456. window.removeEventListener(_handler.move, barMove, false);
  457. window.removeEventListener(eE.type, barEnd, false);
  458. }
  459. } else if (this === _Current_.PlayerNode['_volume_']) {
  460. var vol = x > 1 ? 1 : x < 0 ? 0 : Math.round(x * 1000) / 1000,
  461. sav = _Current_.SavedState.Volume;
  462. if (sav > vol || sav < vol) {
  463. _Current_.invokeEvent('volumechange');
  464. }
  465. _Current_['Player Volume'] = (sav = vol);
  466. barMove = function(eM) {
  467. x = (_handler.getCoords(eM).x - rect.left) / rect.width;
  468. vol = x > 1 ? 1 : x < 0 ? 0 : Math.round(x * 1000) / 1000;
  469. if (sav > vol || sav < vol) {
  470. _Current_.invokeEvent('volumechange');
  471. }
  472. _Current_['Player Volume'] = vol;
  473. }
  474. barEnd = function(eE) {
  475. window.removeEventListener(_handler.move, barMove, false);
  476. window.removeEventListener(eE.type, barEnd, false);
  477. }
  478. }
  479. window.addEventListener(_handler.move, barMove, false);
  480. window.addEventListener(_handler.end, barEnd, false);
  481. }
  482. function createAudioDevice(url) {
  483. var audio, html5, flash;
  484. if (typeof HTMLAudioElement !== 'undefined') {
  485. audio = new Audio;
  486. html5 = audio.canPlayType && (/maybe|probably/).test(audio.canPlayType('audio/mpeg'));
  487. }
  488. if (!html5) {
  489. audio = document.createElement('object');
  490. audio.id = 'scPlayerEngine';
  491. audio.height = 1;
  492. audio.width = 1;
  493. audio.type = 'application/x-shockwave-flash';
  494. audio.data = '/js/player_mp3_js.swf';
  495. audio.innerHTML = '<param name="movie" value="/js/player_mp3_js.swf" /><param name="AllowScriptAccess" value="always" /><param name="FlashVars" value="listener=flashBack2343191116fr_scEngine&interval=500" />';
  496. flash = (window['flashBack2343191116fr_scEngine'] = new Object);
  497. flash.onInit = function() {
  498. Object.defineProperties(audio, {
  499. play : { value: function() {
  500. flash.status = 'process';
  501. this.SetVariable('method:play', '');
  502. this.SetVariable('enabled', 'true');
  503. onPlayerAction({type: 'play'}); }},
  504. pause : { value: function() {
  505. flash.status = 'waiting';
  506. this.SetVariable('method:pause', '');
  507. onPlayerAction({type: 'pause'}); }},
  508. //stop : { value: function() { this.SetVariable('method:stop', '') }},
  509. src : { get: function() { return this.url },
  510. set: function(url) { this.SetVariable('method:setUrl', url) }},
  511. ended : { get: function() { return flash.status === 'ended' }},
  512. playing : { get: function() { return JSON.parse(flash.isPlaying); }},
  513. duration : { get: function() { return Number(flash.duration) / 1000 || 0 }},
  514. currentTime : { get: function() { return Number(flash.position) / 1000 || 0 },
  515. set: function(rel) { this.SetVariable('method:setPosition', (rel * 1000)) }},
  516. volume : { get: function() { return Number(flash.volume) / 100 },
  517. set: function(vol) { this.SetVariable('method:setVolume', (vol * 100)) }},
  518. ontimeupdate: { set: function(fn) { flash.onTimeUpdate = fn || function(){} }}
  519. });
  520. audio['volume'] = SC.Volume;
  521. this.position = 0;
  522. };
  523. flash.onTimeUpdate = onTimeUpdate;
  524. flash.onBufferLoad = onBufferLoad;
  525. flash.onUpdate = function() {
  526. switch (this.status) {
  527. case 'process':
  528. this.onTimeUpdate({target: audio});
  529. if (this.position == '0' && this.isPlaying == 'false') {
  530. this.status = 'ended';
  531. onEnd();
  532. }
  533. case 'waiting':
  534. this.onBufferLoad();
  535. }
  536. };
  537. } else {
  538. var _BufferLoad = null;
  539. audio['volume'] = SC.Volume;
  540. audio['onplay'] = audio['onpause'] = onPlayerAction;
  541. audio['onended'] = onEnd;
  542. audio['ontimeupdate'] = onTimeUpdate;
  543. audio['onerror'] = function(e) {
  544. clearInterval(_BufferLoad);
  545. _Current_.invokeEvent('error');
  546. };
  547. audio['onloadedmetadata'] = function(e) {
  548. clearInterval(_BufferLoad);
  549. if (_Current_.TrackLoaded.duration !== this.duration) {
  550. _Current_['Track Duration'] = this.duration;
  551. }
  552. _BufferLoad = setInterval(function() {
  553. if (audio.buffered.length > 0) {
  554. var bytesPercent = audio.buffered.end(audio.buffered.length - 1) / audio.duration;
  555. if (bytesPercent === 1) {
  556. clearInterval(_BufferLoad);
  557. }
  558. _Current_['Track Buffered'] = bytesPercent * 100;
  559. }
  560. }, 100);
  561. };
  562. }
  563. return audio;
  564. }
  565. function createTrackDOM(track, hash) {
  566. SC['Tracks'][track.id] = track;
  567. var li = document.createElement('li');
  568. li.id = 'sc-t_'+ hash +'_'+ track.id;
  569. li.className = 'sc-track';
  570. li.appendChild((
  571. li['_title_'] = document.createElement('a')));
  572. li['_title_'].href = track.permalink_url;
  573. li['_title_'].className = 'sc-track-title';
  574. li['_title_'].textContent = track.title;
  575. li.appendChild((
  576. li['_duration_'] = document.createElement('span')));
  577. li['_duration_'].className = 'sc-track-duration';
  578. li['_duration_'].textContent = timeCalc((track.duration /= 1000));
  579. return li;
  580. }
  581. function _li(h, l) {
  582. var li ='', i;
  583. for (i = 0; i < l; i++)
  584. li += '<span id="ft_'+h+'_'+i+'"></span>';
  585. return li;
  586. }
  587. function createPlayerDOM(len, hash) {
  588. var div = document.createElement('div');
  589. div.className = 'sc-player loading';
  590. div.innerHTML = '<ol class="sc-artwork-list"></ol>\n'+
  591. '<div class="sc-info"><h3></h3><h4></h4><p></p><a class="sc-download">&raquo; Download &laquo;</a>\n'+
  592. ' <div class="sc-info-close">X</div>\n'+
  593. '</div>\n'+
  594. '<div class="sc-controls">\n'+
  595. ' <div class="sc-play">Play</div>\n'+
  596. '</div>\n'+
  597. '<ol class="sc-trackslist">'+ _li(hash, len) +'</ol>\n'+
  598. '<div class="sc-info-toggle">Info</div>\n'+
  599. '<div class="sc-time-indicators">\n'+
  600. ' <span class="sc-position"></span>&nbsp;|&nbsp;<span class="sc-duration"></span>\n'+
  601. '</div>\n'+
  602. '<div class="sc-scrubber">\n'+
  603. ' <div class="sc-volume-slider">\n'+
  604. ' <span class="sc-volume-status" style="width: '+ (SC.Volume * 100) +'%;"></span>\n'+
  605. ' </div>\n'+
  606. ' <div class="sc-time-span">\n'+
  607. ' <div class="sc-buffer"></div>\n'+
  608. ' <div class="sc-played"></div>\n'+
  609. ' <div class="sc-waveform-container"></div>\n'+
  610. ' </div>\n'+
  611. '</div>';
  612. if (hash && len) {
  613. div.id = 'sc-obj_'+ hash;
  614. if (!SC['Global']) {
  615. SC['Object'][hash] = { Volume: SC.Volume, Progress: 0 }
  616. div.addEventListener('click', onClickHandler, false);
  617. } else {
  618. SC['Object'][hash] = SC;
  619. }
  620. }
  621. return catchKeyElements('player', div);
  622. }
  623. function catchKeyElements(name, _CN_) {
  624. switch(name) {
  625. case 'player':
  626. _CN_['_artwork_'] = _CN_.querySelector('.sc-artwork-list');
  627. _CN_['_info_'] = _CN_.querySelector('.sc-info');
  628. _CN_['_button_'] = _CN_.querySelector('.sc-controls').firstElementChild;
  629. _CN_['_trackslist_'] = _CN_.querySelector('.sc-trackslist');
  630. _CN_['_volume_'] = _CN_.querySelector('.sc-volume-slider');
  631. _CN_['_waveform_'] = _CN_.querySelector('.sc-waveform-container');
  632. _CN_['_buffer_'] = _CN_.querySelector('.sc-buffer');
  633. _CN_['_progress_'] = _CN_.querySelector('.sc-played');
  634. _CN_['_duration_'] = _CN_.querySelector('.sc-duration');
  635. _CN_['_position_'] = _CN_.querySelector('.sc-position');
  636. break;
  637. case 'track':
  638. _CN_['_duration_'] = _CN_.querySelector('.sc-track-duration');
  639. _CN_['_title_'] = _CN_.querySelector('.sc-track-title');
  640. }
  641. return _CN_;
  642. }
  643. function updateTrackInfo(node, track) {
  644. var artwork = track.artwork_url || track.user.avatar_url;
  645. if (artwork && !/\/(?:default_avatar_|avatars-000044695144-c5ssgx-)/.test(artwork)) {
  646. if (node['_artwork_'].clientWidth > 100) {
  647. var s = findBestMatch([200, 250, 300, 500], node['_artwork_'].clientWidth);
  648. artwork = artwork.replace('-large', '-t'+ s +'x'+ s +'')
  649. };
  650. (node['_artwork_'].firstElementChild || node['_artwork_'].appendChild( document.createElement('img'))).src = artwork;
  651. }
  652. node['_info_'].children[0].innerHTML = '<a href="'+ track.permalink_url +'">'+ track.title +'</a>';
  653. node['_info_'].children[1].innerHTML = 'by <a href="'+ track.user.permalink_url +'">'+ track.user.username +'</a>';
  654. node['_info_'].children[2].innerHTML = (track.description || 'no Description');
  655. node['_info_'].children[3].href = (track.downloadable ? track.download_url : track.stream_url);
  656. // update the track duration in the progress bar
  657. node['_duration_'].textContent = timeCalc(track.duration);
  658. node['_position_'].textContent = '00.00';
  659. // put the waveform into the progress bar
  660. (node['_waveform_'].firstElementChild || node['_waveform_'].appendChild( document.createElement('img'))).src = track.waveform_url;
  661. }
  662. function findBestMatch(list, toMatch) {
  663. var item, i = 0, len = list.length;
  664. while (i < len && (item = list[i]) < toMatch)
  665. i++;
  666. return item;
  667. }
  668. function timeCalc(secn) {
  669. var s, m, h;
  670. s = Math.floor(secn) % 60;
  671. m = Math.floor(secn / 60) % 60;
  672. h = Math.floor(secn / (60 * 60));
  673. return (h > 0 ? h +'.' : '') + (m < 10 && m > -1 ? '0'+ m : m) +'.'+ (s < 10 && s > -1 ? '0'+ s : s);
  674. }
  675. function genGroupId() {
  676. var n = Math.round(Math.random() * 12345679);
  677. while (n in SC['Object']) n++;
  678. return (SC['Object'][n] = n);
  679. }
  680. if (!('preventDefault' in Event.prototype)) {
  681. Event.prototype.preventDefault = function() {
  682. this.returnValue = false;
  683. };
  684. }
  685. })();