PageSourceSearch

https://www.nylaunchpod.com/wp-content/plugins/simple-podcast-pres…/js/bar-ui.js?ver=7.1.2

js nylaunchpod.com collected 2026-10-04 20:34:23 UTC 39,817 bytes, 1,652 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.