Advertisement
masslugi

Script JS bar ui Audio Player

Dec 4th, 2017
113
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 42.34 KB | None | 0 0
  1. (function(window) {
  2.  
  3. /**
  4. * SoundManager 2: "Bar UI" player
  5. * Copyright (c) 2014, Scott Schiller. All rights reserved.
  6. * http://www.schillmania.com/projects/soundmanager2/
  7. * Code provided under BSD license.
  8. * http://schillmania.com/projects/soundmanager2/license.txt
  9. */
  10.  
  11. /* global console, document, navigator, soundManager, window */
  12.  
  13. 'use strict';
  14.  
  15. var Player,
  16. players = [],
  17. // CSS selector that will get us the top-level DOM node for the player UI.
  18. playerSelector = '.sm2-bar-ui',
  19. playerOptions,
  20. utils;
  21.  
  22. /**
  23. * The following are player object event callback examples.
  24. * Override globally by setting window.sm2BarPlayers.on = {}, or individually by window.sm2BarPlayers[0].on = {} etc.
  25. * soundObject is provided for whileplaying() etc., but playback control should be done via the player object.
  26. */
  27. players.on = {
  28. /*
  29. play: function(player, soundObject) {
  30. console.log('playing', player);
  31. },
  32. whileplaying: function(player, soundObject) {
  33. console.log('whileplaying', player, soundObject);
  34. },
  35. finish: function(player, soundObject) {
  36. // each sound
  37. console.log('finish', player);
  38. },
  39. pause: function(player, soundObject) {
  40. console.log('pause', player);
  41. },
  42. error: function(player, soundObject) {
  43. console.log('error', player);
  44. },
  45. end: function(player, soundObject) {
  46. // end of playlist
  47. console.log('end', player);
  48. }
  49. */
  50. };
  51.  
  52. playerOptions = {
  53. // useful when multiple players are in use, or other SM2 sounds are active etc.
  54. stopOtherSounds: true,
  55. // CSS class to let the browser load the URL directly e.g., <a href="foo.mp3" class="sm2-exclude">download foo.mp3</a>
  56. excludeClass: 'sm2-exclude'
  57. };
  58.  
  59. soundManager.setup({
  60. // trade-off: higher UI responsiveness (play/progress bar), but may use more CPU.
  61. html5PollingInterval: 50,
  62. flashVersion: 9
  63. });
  64.  
  65. soundManager.onready(function() {
  66.  
  67. var nodes, i, j;
  68.  
  69. nodes = utils.dom.getAll(playerSelector);
  70.  
  71. if (nodes && nodes.length) {
  72. for (i = 0, j = nodes.length; i < j; i++) {
  73. players.push(new Player(nodes[i]));
  74. }
  75. }
  76.  
  77. });
  78.  
  79. /**
  80. * player bits
  81. */
  82.  
  83. Player = function(playerNode) {
  84.  
  85. var css, dom, extras, playlistController, soundObject, actions, actionData, defaultItem, defaultVolume, firstOpen, exports;
  86.  
  87. css = {
  88. disabled: 'disabled',
  89. selected: 'selected',
  90. active: 'active',
  91. legacy: 'legacy',
  92. noVolume: 'no-volume',
  93. playlistOpen: 'playlist-open'
  94. };
  95.  
  96. dom = {
  97. o: null,
  98. playlist: null,
  99. playlistTarget: null,
  100. playlistContainer: null,
  101. time: null,
  102. player: null,
  103. progress: null,
  104. progressTrack: null,
  105. progressBar: null,
  106. duration: null,
  107. volume: null
  108. };
  109.  
  110. // prepended to tracks when a sound fails to load/play
  111. extras = {
  112. loadFailedCharacter: '<span title="Failed to load/play." class="load-error">βœ–</span>'
  113. };
  114.  
  115. function stopOtherSounds() {
  116.  
  117. if (playerOptions.stopOtherSounds) {
  118. soundManager.stopAll();
  119. }
  120.  
  121. }
  122.  
  123. function callback(method, oSound) {
  124. if (method) {
  125. // fire callback, passing current player and sound objects
  126. if (exports.on && exports.on[method]) {
  127. exports.on[method](exports, oSound);
  128. } else if (players.on[method]) {
  129. players.on[method](exports, oSound);
  130. }
  131. }
  132. }
  133.  
  134. function getTime(msec, useString) {
  135.  
  136. // convert milliseconds to hh:mm:ss, return as object literal or string
  137.  
  138. var nSec = Math.floor(msec / 1000),
  139. hh = Math.floor(nSec / 3600),
  140. min = Math.floor(nSec / 60) - Math.floor(hh * 60),
  141. sec = Math.floor(nSec - (hh * 3600) - (min * 60));
  142.  
  143. // if (min === 0 && sec === 0) return null; // return 0:00 as null
  144.  
  145. return (useString ? ((hh ? hh + ':' : '') + (hh && min < 10 ? '0' + min : min) + ':' + (sec < 10 ? '0' + sec : sec)) : { min: min, sec: sec });
  146.  
  147. }
  148.  
  149. function setTitle(item) {
  150.  
  151. // given a link, update the "now playing" UI.
  152.  
  153. // if this is an <li> with an inner link, grab and use the text from that.
  154. var links = item.getElementsByTagName('a');
  155.  
  156. if (links.length) {
  157. item = links[0];
  158. }
  159.  
  160. // remove any failed character sequence, also
  161. dom.playlistTarget.innerHTML = '<ul class="sm2-playlist-bd"><li>' + item.innerHTML.replace(extras.loadFailedCharacter, '') + '</li></ul>';
  162.  
  163. if (dom.playlistTarget.getElementsByTagName('li')[0].scrollWidth > dom.playlistTarget.offsetWidth) {
  164. // this item can use <marquee>, in fact.
  165. dom.playlistTarget.innerHTML = '<ul class="sm2-playlist-bd"><li><marquee>' + item.innerHTML + '</marquee></li></ul>';
  166. }
  167.  
  168. }
  169.  
  170. function makeSound(url) {
  171.  
  172. var sound = soundManager.createSound({
  173.  
  174. url: url,
  175.  
  176. volume: defaultVolume,
  177.  
  178. whileplaying: function() {
  179.  
  180. var progressMaxLeft = 100,
  181. left,
  182. width;
  183.  
  184. left = Math.min(progressMaxLeft, Math.max(0, (progressMaxLeft * (this.position / this.durationEstimate)))) + '%';
  185. width = Math.min(100, Math.max(0, (100 * (this.position / this.durationEstimate)))) + '%';
  186.  
  187. if (this.duration) {
  188.  
  189. dom.progress.style.left = left;
  190. dom.progressBar.style.width = width;
  191.  
  192. // TODO: only write changes
  193. dom.time.innerHTML = getTime(this.position, true);
  194.  
  195. }
  196.  
  197. callback('whileplaying', this);
  198.  
  199. },
  200.  
  201. onbufferchange: function(isBuffering) {
  202.  
  203. if (isBuffering) {
  204. utils.css.add(dom.o, 'buffering');
  205. } else {
  206. utils.css.remove(dom.o, 'buffering');
  207. }
  208.  
  209. },
  210.  
  211. onplay: function() {
  212. utils.css.swap(dom.o, 'paused', 'playing');
  213. callback('play', this);
  214. },
  215.  
  216. onpause: function() {
  217. utils.css.swap(dom.o, 'playing', 'paused');
  218. callback('pause', this);
  219. },
  220.  
  221. onresume: function() {
  222. utils.css.swap(dom.o, 'paused', 'playing');
  223. },
  224.  
  225. whileloading: function() {
  226.  
  227. if (!this.isHTML5) {
  228. dom.duration.innerHTML = getTime(this.durationEstimate, true);
  229. }
  230.  
  231. },
  232.  
  233. onload: function(ok) {
  234.  
  235. if (ok) {
  236.  
  237. dom.duration.innerHTML = getTime(this.duration, true);
  238.  
  239. } else if (this._iO && this._iO.onerror) {
  240.  
  241. this._iO.onerror();
  242.  
  243. }
  244.  
  245. },
  246.  
  247. onerror: function() {
  248.  
  249. // sound failed to load.
  250. var item, element, html;
  251.  
  252. item = playlistController.getItem();
  253.  
  254. if (item) {
  255.  
  256. // note error, delay 2 seconds and advance?
  257. // playlistTarget.innerHTML = '<ul class="sm2-playlist-bd"><li>' + item.innerHTML + '</li></ul>';
  258.  
  259. if (extras.loadFailedCharacter) {
  260. dom.playlistTarget.innerHTML = dom.playlistTarget.innerHTML.replace('<li>', '<li>' + extras.loadFailedCharacter + ' ');
  261. if (playlistController.data.playlist && playlistController.data.playlist[playlistController.data.selectedIndex]) {
  262. element = playlistController.data.playlist[playlistController.data.selectedIndex].getElementsByTagName('a')[0];
  263. html = element.innerHTML;
  264. if (html.indexOf(extras.loadFailedCharacter) === -1) {
  265. element.innerHTML = extras.loadFailedCharacter + ' ' + html;
  266. }
  267. }
  268. }
  269.  
  270. }
  271.  
  272. callback('error', this);
  273.  
  274. // load next, possibly with delay.
  275.  
  276. if (navigator.userAgent.match(/mobile/i)) {
  277. // mobile will likely block the next play() call if there is a setTimeout() - so don't use one here.
  278. actions.next();
  279. } else {
  280. if (playlistController.data.timer) {
  281. window.clearTimeout(playlistController.data.timer);
  282. }
  283. playlistController.data.timer = window.setTimeout(actions.next, 2000);
  284. }
  285.  
  286. },
  287.  
  288. onstop: function() {
  289.  
  290. utils.css.remove(dom.o, 'playing');
  291.  
  292. },
  293.  
  294. onfinish: function() {
  295.  
  296. var lastIndex, item;
  297.  
  298. utils.css.remove(dom.o, 'playing');
  299.  
  300. dom.progress.style.left = '0%';
  301.  
  302. lastIndex = playlistController.data.selectedIndex;
  303.  
  304. callback('finish', this);
  305.  
  306. // next track?
  307. item = playlistController.getNext();
  308.  
  309. // don't play the same item over and over again, if at end of playlist (excluding single item case.)
  310. if (item && (playlistController.data.selectedIndex !== lastIndex || (playlistController.data.playlist.length === 1 && playlistController.data.loopMode))) {
  311.  
  312. playlistController.select(item);
  313.  
  314. setTitle(item);
  315.  
  316. stopOtherSounds();
  317.  
  318. // play next
  319. this.play({
  320. url: playlistController.getURL()
  321. });
  322.  
  323. } else {
  324.  
  325. // end of playlist case
  326.  
  327. // explicitly stop?
  328. // this.stop();
  329.  
  330. callback('end', this);
  331.  
  332. }
  333.  
  334. }
  335.  
  336. });
  337.  
  338. return sound;
  339.  
  340. }
  341.  
  342. function playLink(link) {
  343.  
  344. // if a link is OK, play it.
  345.  
  346. if (soundManager.canPlayURL(link.href)) {
  347.  
  348. // if there's a timer due to failure to play one track, cancel it.
  349. // catches case when user may use previous/next after an error.
  350. if (playlistController.data.timer) {
  351. window.clearTimeout(playlistController.data.timer);
  352. playlistController.data.timer = null;
  353. }
  354.  
  355. if (!soundObject) {
  356. soundObject = makeSound(link.href);
  357. }
  358.  
  359. // required to reset pause/play state on iOS so whileplaying() works? odd.
  360. soundObject.stop();
  361.  
  362. playlistController.select(link.parentNode);
  363.  
  364. setTitle(link.parentNode);
  365.  
  366. // reset the UI
  367. // TODO: function that also resets/hides timing info.
  368. dom.progress.style.left = '0px';
  369. dom.progressBar.style.width = '0px';
  370.  
  371. stopOtherSounds();
  372.  
  373. soundObject.play({
  374. url: link.href,
  375. position: 0
  376. });
  377.  
  378. }
  379.  
  380. }
  381.  
  382. function PlaylistController() {
  383.  
  384. var data;
  385.  
  386. data = {
  387.  
  388. // list of nodes?
  389. playlist: [],
  390.  
  391. // NOTE: not implemented yet.
  392. // shuffledIndex: [],
  393. // shuffleMode: false,
  394.  
  395. // selection
  396. selectedIndex: 0,
  397.  
  398. loopMode: false,
  399.  
  400. timer: null
  401.  
  402. };
  403.  
  404. function getPlaylist() {
  405.  
  406. return data.playlist;
  407.  
  408. }
  409.  
  410. function getItem(offset) {
  411.  
  412. var list,
  413. item;
  414.  
  415. // given the current selection (or an offset), return the current item.
  416.  
  417. // if currently null, may be end of list case. bail.
  418. if (data.selectedIndex === null) {
  419. return offset;
  420. }
  421.  
  422. list = getPlaylist();
  423.  
  424. // use offset if provided, otherwise take default selected.
  425. offset = (offset !== undefined ? offset : data.selectedIndex);
  426.  
  427. // safety check - limit to between 0 and list length
  428. offset = Math.max(0, Math.min(offset, list.length));
  429.  
  430. item = list[offset];
  431.  
  432. return item;
  433.  
  434. }
  435.  
  436. function findOffsetFromItem(item) {
  437.  
  438. // given an <li> item, find it in the playlist array and return the index.
  439. var list,
  440. i,
  441. j,
  442. offset;
  443.  
  444. offset = -1;
  445.  
  446. list = getPlaylist();
  447.  
  448. if (list) {
  449.  
  450. for (i = 0, j = list.length; i < j; i++) {
  451. if (list[i] === item) {
  452. offset = i;
  453. break;
  454. }
  455. }
  456.  
  457. }
  458.  
  459. return offset;
  460.  
  461. }
  462.  
  463. function getNext() {
  464.  
  465. // don't increment if null.
  466. if (data.selectedIndex !== null) {
  467. data.selectedIndex++;
  468. }
  469.  
  470. if (data.playlist.length > 1) {
  471.  
  472. if (data.selectedIndex >= data.playlist.length) {
  473.  
  474. if (data.loopMode) {
  475.  
  476. // loop to beginning
  477. data.selectedIndex = 0;
  478.  
  479. } else {
  480.  
  481. // no change
  482. data.selectedIndex--;
  483.  
  484. // end playback
  485. // data.selectedIndex = null;
  486.  
  487. }
  488.  
  489. }
  490.  
  491. } else {
  492.  
  493. data.selectedIndex = null;
  494.  
  495. }
  496.  
  497. return getItem();
  498.  
  499. }
  500.  
  501. function getPrevious() {
  502.  
  503. data.selectedIndex--;
  504.  
  505. if (data.selectedIndex < 0) {
  506. // wrapping around beginning of list? loop or exit.
  507. if (data.loopMode) {
  508. data.selectedIndex = data.playlist.length - 1;
  509. } else {
  510. // undo
  511. data.selectedIndex++;
  512. }
  513. }
  514.  
  515. return getItem();
  516.  
  517. }
  518.  
  519. function resetLastSelected() {
  520.  
  521. // remove UI highlight(s) on selected items.
  522. var items,
  523. i, j;
  524.  
  525. items = utils.dom.getAll(dom.playlist, '.' + css.selected);
  526.  
  527. for (i = 0, j = items.length; i < j; i++) {
  528. utils.css.remove(items[i], css.selected);
  529. }
  530.  
  531. }
  532.  
  533. function select(item) {
  534.  
  535. var offset,
  536. itemTop,
  537. itemBottom,
  538. containerHeight,
  539. scrollTop,
  540. itemPadding,
  541. liElement;
  542.  
  543. // remove last selected, if any
  544. resetLastSelected();
  545.  
  546. if (item) {
  547.  
  548. liElement = utils.dom.ancestor('li', item);
  549.  
  550. utils.css.add(liElement, css.selected);
  551.  
  552. itemTop = item.offsetTop;
  553. itemBottom = itemTop + item.offsetHeight;
  554. containerHeight = dom.playlistContainer.offsetHeight;
  555. scrollTop = dom.playlist.scrollTop;
  556. itemPadding = 8;
  557.  
  558. if (itemBottom > containerHeight + scrollTop) {
  559. // bottom-align
  560. dom.playlist.scrollTop = (itemBottom - containerHeight) + itemPadding;
  561. } else if (itemTop < scrollTop) {
  562. // top-align
  563. dom.playlist.scrollTop = item.offsetTop - itemPadding;
  564. }
  565.  
  566. }
  567.  
  568. // update selected offset, too.
  569. offset = findOffsetFromItem(liElement);
  570.  
  571. data.selectedIndex = offset;
  572.  
  573. }
  574.  
  575. function playItemByOffset(offset) {
  576.  
  577. var item;
  578.  
  579. offset = (offset || 0);
  580.  
  581. item = getItem(offset);
  582.  
  583. if (item) {
  584. playLink(item.getElementsByTagName('a')[0]);
  585. }
  586.  
  587. }
  588.  
  589. function getURL() {
  590.  
  591. // return URL of currently-selected item
  592. var item, url;
  593.  
  594. item = getItem();
  595.  
  596. if (item) {
  597. url = item.getElementsByTagName('a')[0].href;
  598. }
  599.  
  600. return url;
  601.  
  602. }
  603.  
  604. function refreshDOM() {
  605.  
  606. // get / update playlist from DOM
  607.  
  608. if (!dom.playlist) {
  609. if (window.console && console.warn) {
  610. console.warn('refreshDOM(): playlist node not found?');
  611. }
  612. return;
  613. }
  614.  
  615. data.playlist = dom.playlist.getElementsByTagName('li');
  616.  
  617. }
  618.  
  619. function initDOM() {
  620.  
  621. dom.playlistTarget = utils.dom.get(dom.o, '.sm2-playlist-target');
  622. dom.playlistContainer = utils.dom.get(dom.o, '.sm2-playlist-drawer');
  623. dom.playlist = utils.dom.get(dom.o, '.sm2-playlist-bd');
  624.  
  625. }
  626.  
  627. function initPlaylistController() {
  628.  
  629. // inherit the default SM2 volume
  630. defaultVolume = soundManager.defaultOptions.volume;
  631.  
  632. initDOM();
  633. refreshDOM();
  634.  
  635. // animate playlist open, if HTML classname indicates so.
  636. if (utils.css.has(dom.o, css.playlistOpen)) {
  637. // hackish: run this after API has returned
  638. window.setTimeout(function() {
  639. actions.menu(true);
  640. }, 1);
  641. }
  642.  
  643. }
  644.  
  645. initPlaylistController();
  646.  
  647. return {
  648. data: data,
  649. refresh: refreshDOM,
  650. getNext: getNext,
  651. getPrevious: getPrevious,
  652. getItem: getItem,
  653. getURL: getURL,
  654. playItemByOffset: playItemByOffset,
  655. select: select
  656. };
  657.  
  658. }
  659.  
  660. function isRightClick(e) {
  661.  
  662. // only pay attention to left clicks. old IE differs where there's no e.which, but e.button is 1 on left click.
  663. if (e && ((e.which && e.which === 2) || (e.which === undefined && e.button !== 1))) {
  664. // http://www.quirksmode.org/js/events_properties.html#button
  665. return true;
  666. }
  667.  
  668. return false;
  669.  
  670. }
  671.  
  672. function getActionData(target) {
  673.  
  674. // DOM measurements for volume slider
  675.  
  676. if (!target) {
  677. return;
  678. }
  679.  
  680. actionData.volume.x = utils.position.getOffX(target);
  681. actionData.volume.y = utils.position.getOffY(target);
  682.  
  683. actionData.volume.width = target.offsetWidth;
  684. actionData.volume.height = target.offsetHeight;
  685.  
  686. // potentially dangerous: this should, but may not be a percentage-based value.
  687. actionData.volume.backgroundSize = parseInt(utils.style.get(target, 'background-size'), 10);
  688.  
  689. // IE gives pixels even if background-size specified as % in CSS. Boourns.
  690. if (window.navigator.userAgent.match(/msie|trident/i)) {
  691. actionData.volume.backgroundSize = (actionData.volume.backgroundSize / actionData.volume.width) * 100;
  692. }
  693.  
  694. }
  695.  
  696. function handleMouseDown(e) {
  697.  
  698. var links,
  699. target;
  700.  
  701. target = e.target || e.srcElement;
  702.  
  703. if (isRightClick(e)) {
  704. return;
  705. }
  706.  
  707. // normalize to <a>, if applicable.
  708. if (target.nodeName.toLowerCase() !== 'a') {
  709.  
  710. links = target.getElementsByTagName('a');
  711. if (links && links.length) {
  712. target = target.getElementsByTagName('a')[0];
  713. }
  714.  
  715. }
  716.  
  717. if (utils.css.has(target, 'sm2-volume-control')) {
  718.  
  719. // drag case for volume
  720.  
  721. getActionData(target);
  722.  
  723. utils.events.add(document, 'mousemove', actions.adjustVolume);
  724. utils.events.add(document, 'touchmove', actions.adjustVolume);
  725. utils.events.add(document, 'mouseup', actions.releaseVolume);
  726. utils.events.add(document, 'touchend', actions.releaseVolume);
  727.  
  728. // and apply right away
  729. actions.adjustVolume(e);
  730.  
  731. }
  732.  
  733. }
  734.  
  735. function handleMouse(e) {
  736.  
  737. var target, barX, barWidth, x, clientX, newPosition, sound;
  738.  
  739. target = dom.progressTrack;
  740.  
  741. barX = utils.position.getOffX(target);
  742. barWidth = target.offsetWidth;
  743. clientX = utils.events.getClientX(e);
  744.  
  745. x = (clientX - barX);
  746.  
  747. newPosition = (x / barWidth);
  748.  
  749. sound = soundObject;
  750.  
  751. if (sound && sound.duration) {
  752.  
  753. sound.setPosition(sound.duration * newPosition);
  754.  
  755. // a little hackish: ensure UI updates immediately with current position, even if audio is buffering and hasn't moved there yet.
  756. if (sound._iO && sound._iO.whileplaying) {
  757. sound._iO.whileplaying.apply(sound);
  758. }
  759.  
  760. }
  761.  
  762. if (e.preventDefault) {
  763. e.preventDefault();
  764. }
  765.  
  766. return false;
  767.  
  768. }
  769.  
  770. function releaseMouse(e) {
  771.  
  772. utils.events.remove(document, 'mousemove', handleMouse);
  773. utils.events.remove(document, 'touchmove', handleMouse);
  774.  
  775. utils.css.remove(dom.o, 'grabbing');
  776.  
  777. utils.events.remove(document, 'mouseup', releaseMouse);
  778. utils.events.remove(document, 'touchend', releaseMouse);
  779.  
  780. utils.events.preventDefault(e);
  781.  
  782. return false;
  783.  
  784. }
  785.  
  786. function handleProgressMouseDown(e) {
  787.  
  788. if (isRightClick(e)) {
  789. return;
  790. }
  791.  
  792. utils.css.add(dom.o, 'grabbing');
  793.  
  794. utils.events.add(document, 'mousemove', handleMouse);
  795. utils.events.add(document, 'touchmove', handleMouse);
  796. utils.events.add(document, 'mouseup', releaseMouse);
  797. utils.events.add(document, 'touchend', releaseMouse);
  798.  
  799. handleMouse(e);
  800.  
  801. }
  802.  
  803. function handleClick(e) {
  804.  
  805. var evt,
  806. target,
  807. offset,
  808. targetNodeName,
  809. methodName,
  810. href,
  811. handled;
  812.  
  813. evt = (e || window.event);
  814.  
  815. target = evt.target || evt.srcElement;
  816.  
  817. if (target && target.nodeName) {
  818.  
  819. targetNodeName = target.nodeName.toLowerCase();
  820.  
  821. if (targetNodeName !== 'a') {
  822.  
  823. // old IE (IE 8) might return nested elements inside the <a>, eg., <b> etc. Try to find the parent <a>.
  824.  
  825. if (target.parentNode) {
  826.  
  827. do {
  828. target = target.parentNode;
  829. targetNodeName = target.nodeName.toLowerCase();
  830. } while (targetNodeName !== 'a' && target.parentNode);
  831.  
  832. if (!target) {
  833. // something went wrong. bail.
  834. return false;
  835. }
  836.  
  837. }
  838.  
  839. }
  840.  
  841. if (targetNodeName === 'a') {
  842.  
  843. // yep, it's a link.
  844.  
  845. href = target.href;
  846.  
  847. if (soundManager.canPlayURL(href)) {
  848.  
  849. // not excluded
  850. if (!utils.css.has(target, playerOptions.excludeClass)) {
  851.  
  852. // find this in the playlist
  853.  
  854. playLink(target);
  855.  
  856. handled = true;
  857.  
  858. }
  859.  
  860. } else {
  861.  
  862. // is this one of the action buttons, eg., play/pause, volume, etc.?
  863. offset = target.href.lastIndexOf('#');
  864.  
  865. if (offset !== -1) {
  866.  
  867. methodName = target.href.substr(offset + 1);
  868.  
  869. if (methodName && actions[methodName]) {
  870. handled = true;
  871. actions[methodName](e);
  872. }
  873.  
  874. }
  875.  
  876. }
  877.  
  878. // fall-through case
  879.  
  880. if (handled) {
  881. // prevent browser fall-through
  882. return utils.events.preventDefault(evt);
  883. }
  884.  
  885. }
  886.  
  887. }
  888.  
  889. return true;
  890.  
  891. }
  892.  
  893. function init() {
  894.  
  895. // init DOM?
  896.  
  897. if (!playerNode && window.console && console.warn) {
  898. console.warn('init(): No playerNode element?');
  899. }
  900.  
  901. dom.o = playerNode;
  902.  
  903. // are we dealing with a crap browser? apply legacy CSS if so.
  904. if (window.navigator.userAgent.match(/msie [678]/i)) {
  905. utils.css.add(dom.o, css.legacy);
  906. }
  907.  
  908. if (window.navigator.userAgent.match(/mobile/i)) {
  909. // majority of mobile devices don't let HTML5 audio set volume.
  910. utils.css.add(dom.o, css.noVolume);
  911. }
  912.  
  913. dom.progress = utils.dom.get(dom.o, '.sm2-progress-ball');
  914.  
  915. dom.progressTrack = utils.dom.get(dom.o, '.sm2-progress-track');
  916.  
  917. dom.progressBar = utils.dom.get(dom.o, '.sm2-progress-bar');
  918.  
  919. dom.volume = utils.dom.get(dom.o, 'a.sm2-volume-control');
  920.  
  921. // measure volume control dimensions
  922. if (dom.volume) {
  923. getActionData(dom.volume);
  924. }
  925.  
  926. dom.duration = utils.dom.get(dom.o, '.sm2-inline-duration');
  927.  
  928. dom.time = utils.dom.get(dom.o, '.sm2-inline-time');
  929.  
  930. playlistController = new PlaylistController();
  931.  
  932. defaultItem = playlistController.getItem(0);
  933.  
  934. playlistController.select(defaultItem);
  935.  
  936. if (defaultItem) {
  937. setTitle(defaultItem);
  938. }
  939.  
  940. utils.events.add(dom.o, 'mousedown', handleMouseDown);
  941. utils.events.add(dom.o, 'touchstart', handleMouseDown);
  942. utils.events.add(dom.o, 'click', handleClick);
  943. utils.events.add(dom.progressTrack, 'mousedown', handleProgressMouseDown);
  944. utils.events.add(dom.progressTrack, 'touchstart', handleProgressMouseDown);
  945.  
  946. }
  947.  
  948. // ---
  949.  
  950. actionData = {
  951.  
  952. volume: {
  953. x: 0,
  954. y: 0,
  955. width: 0,
  956. height: 0,
  957. backgroundSize: 0
  958. }
  959.  
  960. };
  961.  
  962. actions = {
  963.  
  964. play: function(offsetOrEvent) {
  965.  
  966. /**
  967. * This is an overloaded function that takes mouse/touch events or offset-based item indices.
  968. * Remember, "auto-play" will not work on mobile devices unless this function is called immediately from a touch or click event.
  969. * If you have the link but not the offset, you can also pass a fake event object with a target of an <a> inside the playlist - e.g. { target: someMP3Link }
  970. */
  971.  
  972. var target,
  973. href,
  974. e;
  975.  
  976. if (offsetOrEvent !== undefined && !isNaN(offsetOrEvent)) {
  977. // smells like a number.
  978. playlistController.playItemByOffset(offsetOrEvent);
  979. return;
  980. }
  981.  
  982. // DRY things a bit
  983. e = offsetOrEvent;
  984.  
  985. if (e && e.target) {
  986.  
  987. target = e.target || e.srcElement;
  988.  
  989. href = target.href;
  990.  
  991. }
  992.  
  993. // haaaack - if null due to no event, OR '#' due to play/pause link, get first link from playlist
  994. if (!href || href.indexOf('#') !== -1) {
  995. href = dom.playlist.getElementsByTagName('a')[0].href;
  996. }
  997.  
  998. if (!soundObject) {
  999. soundObject = makeSound(href);
  1000. }
  1001.  
  1002. // edge case: if the current sound is not playing, stop all others.
  1003. if (!soundObject.playState) {
  1004. stopOtherSounds();
  1005. }
  1006.  
  1007. // TODO: if user pauses + unpauses a sound that had an error, try to play next?
  1008. soundObject.togglePause();
  1009.  
  1010. // special case: clear "play next" timeout, if one exists.
  1011. // edge case: user pauses after a song failed to load.
  1012. if (soundObject.paused && playlistController.data.timer) {
  1013. window.clearTimeout(playlistController.data.timer);
  1014. playlistController.data.timer = null;
  1015. }
  1016.  
  1017. },
  1018.  
  1019. pause: function() {
  1020.  
  1021. if (soundObject && soundObject.readyState) {
  1022. soundObject.pause();
  1023. }
  1024.  
  1025. },
  1026.  
  1027. resume: function() {
  1028.  
  1029. if (soundObject && soundObject.readyState) {
  1030. soundObject.resume();
  1031. }
  1032.  
  1033. },
  1034.  
  1035. stop: function() {
  1036.  
  1037. // just an alias for pause, really.
  1038. // don't actually stop because that will mess up some UI state, i.e., dragging the slider.
  1039. return actions.pause();
  1040.  
  1041. },
  1042.  
  1043. next: function(/* e */) {
  1044.  
  1045. var item, lastIndex;
  1046.  
  1047. // special case: clear "play next" timeout, if one exists.
  1048. if (playlistController.data.timer) {
  1049. window.clearTimeout(playlistController.data.timer);
  1050. playlistController.data.timer = null;
  1051. }
  1052.  
  1053. lastIndex = playlistController.data.selectedIndex;
  1054.  
  1055. item = playlistController.getNext(true);
  1056.  
  1057. // don't play the same item again
  1058. if (item && playlistController.data.selectedIndex !== lastIndex) {
  1059. playLink(item.getElementsByTagName('a')[0]);
  1060. }
  1061.  
  1062. },
  1063.  
  1064. prev: function(/* e */) {
  1065.  
  1066. var item, lastIndex;
  1067.  
  1068. lastIndex = playlistController.data.selectedIndex;
  1069.  
  1070. item = playlistController.getPrevious();
  1071.  
  1072. // don't play the same item again
  1073. if (item && playlistController.data.selectedIndex !== lastIndex) {
  1074. playLink(item.getElementsByTagName('a')[0]);
  1075. }
  1076.  
  1077. },
  1078.  
  1079. shuffle: function(e) {
  1080.  
  1081. // NOTE: not implemented yet.
  1082.  
  1083. var target = (e ? e.target || e.srcElement : utils.dom.get(dom.o, '.shuffle'));
  1084.  
  1085. if (target && !utils.css.has(target, css.disabled)) {
  1086. utils.css.toggle(target.parentNode, css.active);
  1087. playlistController.data.shuffleMode = !playlistController.data.shuffleMode;
  1088. }
  1089.  
  1090. },
  1091.  
  1092. repeat: function(e) {
  1093.  
  1094. var target = (e ? e.target || e.srcElement : utils.dom.get(dom.o, '.repeat'));
  1095.  
  1096. if (target && !utils.css.has(target, css.disabled)) {
  1097. utils.css.toggle(target.parentNode, css.active);
  1098. playlistController.data.loopMode = !playlistController.data.loopMode;
  1099. }
  1100.  
  1101. },
  1102.  
  1103. menu: function(ignoreToggle) {
  1104.  
  1105. var isOpen;
  1106.  
  1107. isOpen = utils.css.has(dom.o, css.playlistOpen);
  1108.  
  1109. // hackish: reset scrollTop in default first open case. odd, but some browsers have a non-zero scroll offset the first time the playlist opens.
  1110. if (playlistController && !playlistController.data.selectedIndex && !firstOpen) {
  1111. dom.playlist.scrollTop = 0;
  1112. firstOpen = true;
  1113. }
  1114.  
  1115. // sniff out booleans from mouse events, as this is referenced directly by event handlers.
  1116. if (typeof ignoreToggle !== 'boolean' || !ignoreToggle) {
  1117.  
  1118. if (!isOpen) {
  1119. // explicitly set height:0, so the first closed -> open animation runs properly
  1120. dom.playlistContainer.style.height = '0px';
  1121. }
  1122.  
  1123. isOpen = utils.css.toggle(dom.o, css.playlistOpen);
  1124.  
  1125. }
  1126.  
  1127. // playlist
  1128. dom.playlistContainer.style.height = (isOpen ? dom.playlistContainer.scrollHeight : 0) + 'px';
  1129.  
  1130. },
  1131.  
  1132. adjustVolume: function(e) {
  1133.  
  1134. /**
  1135. * NOTE: this is the mousemove() event handler version.
  1136. * Use setVolume(50), etc., to assign volume directly.
  1137. */
  1138.  
  1139. var backgroundMargin,
  1140. pixelMargin,
  1141. target,
  1142. value,
  1143. volume;
  1144.  
  1145. value = 0;
  1146.  
  1147. target = dom.volume;
  1148.  
  1149. // safety net
  1150. if (e === undefined) {
  1151. return false;
  1152. }
  1153.  
  1154. // normalize between mouse and touch events
  1155. var clientX = utils.events.getClientX(e);
  1156.  
  1157. if (!e || clientX === undefined) {
  1158. // called directly or with a non-mouseEvent object, etc.
  1159. // proxy to the proper method.
  1160. if (arguments.length && window.console && window.console.warn) {
  1161. console.warn('Bar UI: call setVolume(' + e + ') instead of adjustVolume(' + e + ').');
  1162. }
  1163. return actions.setVolume.apply(this, arguments);
  1164. }
  1165.  
  1166. // based on getStyle() result
  1167. // figure out spacing around background image based on background size, eg. 60% background size.
  1168. // 60% wide means 20% margin on each side.
  1169. backgroundMargin = (100 - actionData.volume.backgroundSize) / 2;
  1170.  
  1171. // relative position of mouse over element
  1172. value = Math.max(0, Math.min(1, (clientX - actionData.volume.x) / actionData.volume.width));
  1173.  
  1174. target.style.clip = 'rect(0px, ' + (actionData.volume.width * value) + 'px, ' + actionData.volume.height + 'px, ' + (actionData.volume.width * (backgroundMargin / 100)) + 'px)';
  1175.  
  1176. // determine logical volume, including background margin
  1177. pixelMargin = ((backgroundMargin / 100) * actionData.volume.width);
  1178.  
  1179. volume = Math.max(0, Math.min(1, ((clientX - actionData.volume.x) - pixelMargin) / (actionData.volume.width - (pixelMargin * 2)))) * 100;
  1180.  
  1181. // set volume
  1182. if (soundObject) {
  1183. soundObject.setVolume(volume);
  1184. }
  1185.  
  1186. defaultVolume = volume;
  1187.  
  1188. return utils.events.preventDefault(e);
  1189.  
  1190. },
  1191.  
  1192. releaseVolume: function(/* e */) {
  1193.  
  1194. utils.events.remove(document, 'mousemove', actions.adjustVolume);
  1195. utils.events.remove(document, 'touchmove', actions.adjustVolume);
  1196. utils.events.remove(document, 'mouseup', actions.releaseVolume);
  1197. utils.events.remove(document, 'touchend', actions.releaseVolume);
  1198.  
  1199. },
  1200.  
  1201. setVolume: function(volume) {
  1202.  
  1203. // set volume (0-100) and update volume slider UI.
  1204.  
  1205. var backgroundSize,
  1206. backgroundMargin,
  1207. backgroundOffset,
  1208. target,
  1209. from,
  1210. to;
  1211.  
  1212. if (volume === undefined || isNaN(volume)) {
  1213. return;
  1214. }
  1215.  
  1216. if (dom.volume) {
  1217.  
  1218. target = dom.volume;
  1219.  
  1220. // based on getStyle() result
  1221. backgroundSize = actionData.volume.backgroundSize;
  1222.  
  1223. // figure out spacing around background image based on background size, eg. 60% background size.
  1224. // 60% wide means 20% margin on each side.
  1225. backgroundMargin = (100 - backgroundSize) / 2;
  1226.  
  1227. // margin as pixel value relative to width
  1228. backgroundOffset = actionData.volume.width * (backgroundMargin / 100);
  1229.  
  1230. from = backgroundOffset;
  1231. to = from + ((actionData.volume.width - (backgroundOffset * 2)) * (volume / 100));
  1232.  
  1233. target.style.clip = 'rect(0px, ' + to + 'px, ' + actionData.volume.height + 'px, ' + from + 'px)';
  1234.  
  1235. }
  1236.  
  1237. // apply volume to sound, as applicable
  1238. if (soundObject) {
  1239. soundObject.setVolume(volume);
  1240. }
  1241.  
  1242. defaultVolume = volume;
  1243.  
  1244. }
  1245.  
  1246. };
  1247.  
  1248. init();
  1249.  
  1250. // TODO: mixin actions -> exports
  1251.  
  1252. exports = {
  1253. // Per-instance events: window.sm2BarPlayers[0].on = { ... } etc. See global players.on example above for reference.
  1254. on: null,
  1255. actions: actions,
  1256. dom: dom,
  1257. playlistController: playlistController
  1258. };
  1259.  
  1260. return exports;
  1261.  
  1262. };
  1263.  
  1264. // barebones utilities for logic, CSS, DOM, events etc.
  1265.  
  1266. utils = {
  1267.  
  1268. array: (function() {
  1269.  
  1270. function compare(property) {
  1271.  
  1272. var result;
  1273.  
  1274. return function(a, b) {
  1275.  
  1276. if (a[property] < b[property]) {
  1277. result = -1;
  1278. } else if (a[property] > b[property]) {
  1279. result = 1;
  1280. } else {
  1281. result = 0;
  1282. }
  1283. return result;
  1284. };
  1285.  
  1286. }
  1287.  
  1288. function shuffle(array) {
  1289.  
  1290. // Fisher-Yates shuffle algo
  1291.  
  1292. var i, j, temp;
  1293.  
  1294. for (i = array.length - 1; i > 0; i--) {
  1295. j = Math.floor(Math.random() * (i + 1));
  1296. temp = array[i];
  1297. array[i] = array[j];
  1298. array[j] = temp;
  1299. }
  1300.  
  1301. return array;
  1302.  
  1303. }
  1304.  
  1305. return {
  1306. compare: compare,
  1307. shuffle: shuffle
  1308. };
  1309.  
  1310. }()),
  1311.  
  1312. css: (function() {
  1313.  
  1314. function hasClass(o, cStr) {
  1315.  
  1316. return (o.className !== undefined ? new RegExp('(^|\\s)' + cStr + '(\\s|$)').test(o.className) : false);
  1317.  
  1318. }
  1319.  
  1320. function addClass(o, cStr) {
  1321.  
  1322. if (!o || !cStr || hasClass(o, cStr)) {
  1323. return; // safety net
  1324. }
  1325. o.className = (o.className ? o.className + ' ' : '') + cStr;
  1326.  
  1327. }
  1328.  
  1329. function removeClass(o, cStr) {
  1330.  
  1331. if (!o || !cStr || !hasClass(o, cStr)) {
  1332. return;
  1333. }
  1334. o.className = o.className.replace(new RegExp('( ' + cStr + ')|(' + cStr + ')', 'g'), '');
  1335.  
  1336. }
  1337.  
  1338. function swapClass(o, cStr1, cStr2) {
  1339.  
  1340. var tmpClass = {
  1341. className: o.className
  1342. };
  1343.  
  1344. removeClass(tmpClass, cStr1);
  1345. addClass(tmpClass, cStr2);
  1346.  
  1347. o.className = tmpClass.className;
  1348.  
  1349. }
  1350.  
  1351. function toggleClass(o, cStr) {
  1352.  
  1353. var found,
  1354. method;
  1355.  
  1356. found = hasClass(o, cStr);
  1357.  
  1358. method = (found ? removeClass : addClass);
  1359.  
  1360. method(o, cStr);
  1361.  
  1362. // indicate the new state...
  1363. return !found;
  1364.  
  1365. }
  1366.  
  1367. return {
  1368. has: hasClass,
  1369. add: addClass,
  1370. remove: removeClass,
  1371. swap: swapClass,
  1372. toggle: toggleClass
  1373. };
  1374.  
  1375. }()),
  1376.  
  1377. dom: (function() {
  1378.  
  1379. function getAll(param1, param2) {
  1380.  
  1381. var node,
  1382. selector,
  1383. results;
  1384.  
  1385. if (arguments.length === 1) {
  1386.  
  1387. // .selector case
  1388. node = document.documentElement;
  1389. // first param is actually the selector
  1390. selector = param1;
  1391.  
  1392. } else {
  1393.  
  1394. // node, .selector
  1395. node = param1;
  1396. selector = param2;
  1397.  
  1398. }
  1399.  
  1400. // sorry, IE 7 users; IE 8+ required.
  1401. if (node && node.querySelectorAll) {
  1402.  
  1403. results = node.querySelectorAll(selector);
  1404.  
  1405. }
  1406.  
  1407. return results;
  1408.  
  1409. }
  1410.  
  1411. function get(/* parentNode, selector */) {
  1412.  
  1413. var results = getAll.apply(this, arguments);
  1414.  
  1415. // hackish: if an array, return the last item.
  1416. if (results && results.length) {
  1417. return results[results.length - 1];
  1418. }
  1419.  
  1420. // handle "not found" case
  1421. return results && results.length === 0 ? null : results;
  1422.  
  1423. }
  1424.  
  1425. function ancestor(nodeName, element, checkCurrent) {
  1426.  
  1427. if (!element || !nodeName) {
  1428. return element;
  1429. }
  1430.  
  1431. nodeName = nodeName.toUpperCase();
  1432.  
  1433. // return if current node matches.
  1434. if (checkCurrent && element && element.nodeName === nodeName) {
  1435. return element;
  1436. }
  1437.  
  1438. while (element && element.nodeName !== nodeName && element.parentNode) {
  1439. element = element.parentNode;
  1440. }
  1441.  
  1442. return (element && element.nodeName === nodeName ? element : null);
  1443.  
  1444. }
  1445.  
  1446. return {
  1447. ancestor: ancestor,
  1448. get: get,
  1449. getAll: getAll
  1450. };
  1451.  
  1452. }()),
  1453.  
  1454. position: (function() {
  1455.  
  1456. function getOffX(o) {
  1457.  
  1458. // http://www.xs4all.nl/~ppk/js/findpos.html
  1459. var curleft = 0;
  1460.  
  1461. if (o.offsetParent) {
  1462.  
  1463. while (o.offsetParent) {
  1464.  
  1465. curleft += o.offsetLeft;
  1466.  
  1467. o = o.offsetParent;
  1468.  
  1469. }
  1470.  
  1471. } else if (o.x) {
  1472.  
  1473. curleft += o.x;
  1474.  
  1475. }
  1476.  
  1477. return curleft;
  1478.  
  1479. }
  1480.  
  1481. function getOffY(o) {
  1482.  
  1483. // http://www.xs4all.nl/~ppk/js/findpos.html
  1484. var curtop = 0;
  1485.  
  1486. if (o.offsetParent) {
  1487.  
  1488. while (o.offsetParent) {
  1489.  
  1490. curtop += o.offsetTop;
  1491.  
  1492. o = o.offsetParent;
  1493.  
  1494. }
  1495.  
  1496. } else if (o.y) {
  1497.  
  1498. curtop += o.y;
  1499.  
  1500. }
  1501.  
  1502. return curtop;
  1503.  
  1504. }
  1505.  
  1506. return {
  1507. getOffX: getOffX,
  1508. getOffY: getOffY
  1509. };
  1510.  
  1511. }()),
  1512.  
  1513. style: (function() {
  1514.  
  1515. function get(node, styleProp) {
  1516.  
  1517. // http://www.quirksmode.org/dom/getstyles.html
  1518. var value;
  1519.  
  1520. if (node.currentStyle) {
  1521.  
  1522. value = node.currentStyle[styleProp];
  1523.  
  1524. } else if (window.getComputedStyle) {
  1525.  
  1526. value = document.defaultView.getComputedStyle(node, null).getPropertyValue(styleProp);
  1527.  
  1528. }
  1529.  
  1530. return value;
  1531.  
  1532. }
  1533.  
  1534. return {
  1535. get: get
  1536. };
  1537.  
  1538. }()),
  1539.  
  1540. events: (function() {
  1541.  
  1542. var add, remove, preventDefault, getClientX;
  1543.  
  1544. add = function(o, evtName, evtHandler) {
  1545. // return an object with a convenient detach method.
  1546. var eventObject = {
  1547. detach: function() {
  1548. return remove(o, evtName, evtHandler);
  1549. }
  1550. };
  1551. if (window.addEventListener) {
  1552. o.addEventListener(evtName, evtHandler, false);
  1553. } else {
  1554. o.attachEvent('on' + evtName, evtHandler);
  1555. }
  1556. return eventObject;
  1557. };
  1558.  
  1559. remove = (window.removeEventListener !== undefined ? function(o, evtName, evtHandler) {
  1560. return o.removeEventListener(evtName, evtHandler, false);
  1561. } : function(o, evtName, evtHandler) {
  1562. return o.detachEvent('on' + evtName, evtHandler);
  1563. });
  1564.  
  1565. preventDefault = function(e) {
  1566. if (e.preventDefault) {
  1567. e.preventDefault();
  1568. } else {
  1569. e.returnValue = false;
  1570. e.cancelBubble = true;
  1571. }
  1572. return false;
  1573. };
  1574.  
  1575. getClientX = function(e) {
  1576. // normalize between desktop (mouse) and touch (mobile/tablet/?) events.
  1577. // note pageX for touch, which normalizes zoom/scroll/pan vs. clientX.
  1578. return (e && (e.clientX || (e.touches && e.touches[0] && e.touches[0].pageX)));
  1579. };
  1580.  
  1581. return {
  1582. add: add,
  1583. preventDefault: preventDefault,
  1584. remove: remove,
  1585. getClientX: getClientX
  1586. };
  1587.  
  1588. }()),
  1589.  
  1590. features: (function() {
  1591.  
  1592. var getAnimationFrame,
  1593. localAnimationFrame,
  1594. localFeatures,
  1595. prop,
  1596. styles,
  1597. testDiv,
  1598. transform;
  1599.  
  1600. testDiv = document.createElement('div');
  1601.  
  1602. /**
  1603. * hat tip: paul irish
  1604. * http://paulirish.com/2011/requestanimationframe-for-smart-animating/
  1605. * https://gist.github.com/838785
  1606. */
  1607.  
  1608. localAnimationFrame = (window.requestAnimationFrame
  1609. || window.webkitRequestAnimationFrame
  1610. || window.mozRequestAnimationFrame
  1611. || window.oRequestAnimationFrame
  1612. || window.msRequestAnimationFrame
  1613. || null);
  1614.  
  1615. // apply to window, avoid "illegal invocation" errors in Chrome
  1616. getAnimationFrame = localAnimationFrame ? function() {
  1617. return localAnimationFrame.apply(window, arguments);
  1618. } : null;
  1619.  
  1620. function has(propName) {
  1621.  
  1622. // test for feature support
  1623. return (testDiv.style[propName] !== undefined ? propName : null);
  1624.  
  1625. }
  1626.  
  1627. // note local scope.
  1628. localFeatures = {
  1629.  
  1630. transform: {
  1631. ie: has('-ms-transform'),
  1632. moz: has('MozTransform'),
  1633. opera: has('OTransform'),
  1634. webkit: has('webkitTransform'),
  1635. w3: has('transform'),
  1636. prop: null // the normalized property value
  1637. },
  1638.  
  1639. rotate: {
  1640. has3D: false,
  1641. prop: null
  1642. },
  1643.  
  1644. getAnimationFrame: getAnimationFrame
  1645.  
  1646. };
  1647.  
  1648. localFeatures.transform.prop = (
  1649. localFeatures.transform.w3 ||
  1650. localFeatures.transform.moz ||
  1651. localFeatures.transform.webkit ||
  1652. localFeatures.transform.ie ||
  1653. localFeatures.transform.opera
  1654. );
  1655.  
  1656. function attempt(style) {
  1657.  
  1658. try {
  1659. testDiv.style[transform] = style;
  1660. } catch(e) {
  1661. // that *definitely* didn't work.
  1662. return false;
  1663. }
  1664. // if we can read back the style, it should be cool.
  1665. return !!testDiv.style[transform];
  1666.  
  1667. }
  1668.  
  1669. if (localFeatures.transform.prop) {
  1670.  
  1671. // try to derive the rotate/3D support.
  1672. transform = localFeatures.transform.prop;
  1673. styles = {
  1674. css_2d: 'rotate(0deg)',
  1675. css_3d: 'rotate3d(0,0,0,0deg)'
  1676. };
  1677.  
  1678. if (attempt(styles.css_3d)) {
  1679. localFeatures.rotate.has3D = true;
  1680. prop = 'rotate3d';
  1681. } else if (attempt(styles.css_2d)) {
  1682. prop = 'rotate';
  1683. }
  1684.  
  1685. localFeatures.rotate.prop = prop;
  1686.  
  1687. }
  1688.  
  1689. testDiv = null;
  1690.  
  1691. return localFeatures;
  1692.  
  1693. }())
  1694.  
  1695. };
  1696.  
  1697. // ---
  1698.  
  1699. // expose to global
  1700. window.sm2BarPlayers = players;
  1701. window.sm2BarPlayerOptions = playerOptions;
  1702. window.SM2BarPlayer = Player;
  1703.  
  1704. }(window));
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement