PageSourceSearch

https://www.mobydickbigread.com/wp-content/themes/toolbox/soundcloud/js/sc-player.js

js mobydickbigread.com collected 2026-10-02 03:05:25 UTC 26,557 bytes, 725 lines download raw bytes

1/*
2*   SoundCloud Custom Player jQuery Plugin
3*   Author: Matas Petrikas, [email protected]
4*   Copyright (c) 2009  SoundCloud Ltd.
5*   Licensed under the MIT license:
6*   http://www.opensource.org/licenses/mit-license.php
7*
8*   Usage:
9*   <a href="http://soundcloud.com/matas/hobnotropic" class="sc-player">My new dub track</a>
10*   The link will be automatically replaced by the HTML based player
11*/
12(function($) {
13  // Convert milliseconds into Hours (h), Minutes (m), and Seconds (s)
14  var timecode = function(ms) {
15    var hms = function(ms) {
16          return {
17            h: Math.floor(ms/(60*60*1000)),
18            m: Math.floor((ms/60000) % 60),
19            s: Math.floor((ms/1000) % 60)
20          };
21        }(ms),
22        tc = []; // Timecode array to be joined with '.'
23
24    if (hms.h > 0) {
25      tc.push(hms.h);
26    }
27
28    tc.push((hms.m < 10 && hms.h > 0 ? "0" + hms.m : hms.m));
29    tc.push((hms.s < 10  ? "0" + hms.s : hms.s));
30
31    return tc.join('.');
32  };
33  // shuffle the array
34  var shuffle = function(arr) {
35    arr.sort(function() { return 1 - Math.floor(Math.random() * 3); } );
36    return arr;
37  };
38
39  var debug = true,
40      useSandBox = false,
41      $doc = $(document),
42      log = function(args) {
43        try {
44          if(debug && window.console && window.console.log){
45            window.console.log.apply(window.console, arguments);
46          }
47        } catch (e) {
48          // no console available
49        }
50      },
51      domain = useSandBox ? 'sandbox-soundcloud.com' : 'soundcloud.com',
52      secureDocument = (document.location.protocol === 'https:'),
53      // convert a SoundCloud resource URL to an API URL
54      scApiUrl = function(url, apiKey) {
55        var resolver = ( secureDocument || (/^https/i).test(url) ? 'https' : 'http') + '://api.' + domain + '/resolve?url=',
56            params = 'format=json&consumer_key=' + apiKey +'&callback=?';
57
58        // force the secure url in the secure environment
59        if( secureDocument ) {
60          url = url.replace(/^http:/, 'https:');
61        }
62
63        // check if it's already a resolved api url
64        if ( (/api\./).test(url) ) {
65          return url + '?' + params;
66        } else {
67          return resolver + url + '&' + params;
68        }
69      };
70
71  // TODO Expose the audio engine, so it can be unit-tested
72  var audioEngine = function() {
73    var html5AudioAvailable = function() {
74        var state = false;
75        try{
76          var a = new Audio();
77          state = a.canPlayType && (/maybe|probably/).test(a.canPlayType('audio/mpeg'));
78          // uncomment the following line, if you want to enable the html5 audio only on mobile devices
79          // state = state && (/iPad|iphone|mobile|pre\//i).test(navigator.userAgent);
80        }catch(e){
81          // there's no audio support here sadly
82        }
83
84        return state;
85    }(),
86    callbacks = {
87      onReady: function() {
88        $doc.trigger('scPlayer:onAudioReady');
89      },
90      onPlay: function() {
91        $doc.trigger('scPlayer:onMediaPlay');
92      },
93      onPause: function() {
94        $doc.trigger('scPlayer:onMediaPause');
95      },
96      onEnd: function() {
97        $doc.trigger('scPlayer:onMediaEnd');
98      },
99      onBuffer: function(percent) {
100        $doc.trigger({type: 'scPlayer:onMediaBuffering', percent: percent});
101      }
102    };
103
104    var html5Driver = function() {
105      var player = new Audio(),
106          onTimeUpdate = function(event){
107            var obj = event.target,
108                buffer = ((obj.buffered.length && obj.buffered.end(0)) / obj.duration) * 100;
109            // ipad has no progress events implemented yet
110            callbacks.onBuffer(buffer);
111            // anounce if it's finished for the clients without 'ended' events implementation
112            if (obj.currentTime === obj.duration) { callbacks.onEnd(); }
113          },
114          onProgress = function(event) {
115            var obj = event.target,
116                buffer = ((obj.buffered.length && obj.buffered.end(0)) / obj.duration) * 100;
117            callbacks.onBuffer(buffer);
118          };
119
120      $('<div class="sc-player-engine-container"></div>').appendTo(document.body).append(player);
121
122      // prepare the listeners
123      player.addEventListener('play', callbacks.onPlay, false);
124      player.addEventListener('pause', callbacks.onPause, false);
125      // handled in the onTimeUpdate for now untill all the browsers support 'ended' event
126      // player.addEventListener('ended', callbacks.onEnd, false);
127      player.addEventListener('timeupdate', onTimeUpdate, false);
128      player.addEventListener('progress', onProgress, false);
129
130
131      return {
132        load: function(track, apiKey) {
133          player.pause();
134          player.src = track.stream_url + (/\?/.test(track.stream_url) ? '&' : '?') + 'consumer_key=' + apiKey;
135          player.load();
136          player.play();
137        },
138        play: function() {
139          player.play();
140        },
141        pause: function() {
142          player.pause();
143        },
144        stop: function(){
145          if (player.currentTime) {
146            player.currentTime = 0;
147            player.pause();
148          }
149        },
150        seek: function(relative){
151          player.currentTime = player.duration * relative;
152          player.play();
153        },
154        getDuration: function() {
155          return player.duration * 1000;
156        },
157        getPosition: function() {
158          return player.currentTime * 1000;
159        },
160        setVolume: function(val) {
161          player.volume = val / 100;
162        }
163      };
164
165    };
166
167
168
169    var flashDriver = function() {
170      var engineId = 'scPlayerEngine',
171          player,
172          flashHtml = function(url) {
173            var swf = (secureDocument ? 'https' : 'http') + '://player.' + domain +'/player.swf?url=' + url +'&amp;enable_api=true&amp;player_type=engine&amp;object_id=' + engineId;
174            if ($.browser.msie) {
175              return '<object height="100%" width="100%" id="' + engineId + '" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" data="' + swf + '">
175'+
176                '<param name="movie" value="' + swf + '" />'+
177                '<param name="allowscriptaccess" value="always" />'+
178                '</object>';
179            } else {
180              return '<object height="100%" width="100%" id="' + engineId + '">'+
181                '<embed allowscriptaccess="always" height="100%" width="100%" src="' + swf + '" type="application/x-shockwave-flash" name="' + engineId + '" />'+
182                '</object>';
183            }
184          };
185
186
187      // listen to audio engine events
188      // when the loaded track is ready to play
189      soundcloud.addEventListener('onPlayerReady', function(flashId, data) {
190        player = soundcloud.getPlayer(engineId);
191        callbacks.onReady();
192      });
193
194      // when the loaded track finished playing
195      soundcloud.addEventListener('onMediaEnd', callbacks.onEnd);
196
197      // when the loaded track is still buffering
198      soundcloud.addEventListener('onMediaBuffering', function(flashId, data) {
199        callbacks.onBuffer(data.percent);
200      });
201
202      // when the loaded track started to play
203      soundcloud.addEventListener('onMediaPlay', callbacks.onPlay);
204
205      // when the loaded track is was paused
206      soundcloud.addEventListener('onMediaPause', callbacks.onPause);
207
208      return {
209        load: function(track) {
210          var url = track.uri;
211          if(player){
212            player.api_load(url);
213          }else{
214            // create a container for the flash engine (IE needs this to operate properly)
215            $('<div class="sc-player-engine-container"></div>').appendTo(document.body).html(flashHtml(url));
216          }
217        },
218        play: function() {
219          player && player.api_play();
220        },
221        pause: function() {
222          player && player.api_pause();
223        },
224        stop: function(){
225          player && player.api_stop();
226        },
227        seek: function(relative){
228          player && player.api_seekTo((player.api_getTrackDuration() * relative));
229        },
230        getDuration: function() {
231          return player && player.api_getTrackDuration && player.api_getTrackDuration() * 1000;
232        },
233        getPosition: function() {
234          return player && player.api_getTrackPosition && player.api_getTrackPosition() * 1000;
235        },
236        setVolume: function(val) {
237          if(player && player.api_setVolume){
238            player.api_setVolume(val);
239          }
240        }
241
242      };
243    };
244
245    return html5AudioAvailable? html5Driver() : flashDriver();
246
247  }();
248
249
250
251  var apiKey,
252      didAutoPlay = false,
253      players = [],
254      updates = {},
255      currentUrl,
256      loadTracksData = function($player, links, key) {
257        var index = 0,
258            playerObj = {node: $player, tracks: []},
259            loadUrl = function(link) {
260              var apiUrl = scApiUrl(link.url, apiKey);
261              $.getJSON(apiUrl, function(data) {
262                // log('data loaded', link.url, data);
263                index += 1;
264                if(data.tracks){
265                  // log('data.tracks', data.tracks);
266                  playerObj.tracks = playerObj.tracks.concat(data.tracks);
267                }else if(data.duration){
268                  // a secret link fix, till the SC API returns permalink with secret on secret response
269                  data.permalink_url = link.url;
270                  // if track, add to player
271                  playerObj.tracks.push(data);
272                }else if(data.creator){
273                  // it's a group!
274                  links.push({url:data.uri + '/tracks'});
275                }else if(data.username){
276                  // if user, get his tracks or favorites
277                  if(/favorites/.test(link.url)){
278                    links.push({url:data.uri + '/favorites'});
279                  }else{
280                    links.push({url:data.uri + '/tracks'});
281                  }
282                }else if($.isArray(data)){
283                  playerObj.tracks = playerObj.tracks.concat(data);
284                }
285                if(links[index]){
286                  // if there are more track to load, get them from the api
287                  loadUrl(links[index]);
288                }else{
289                  // if loading finishes, anounce it to the GUI
290                  playerObj.node.trigger({type:'onTrackDataLoaded', playerObj: playerObj, url: apiUrl});
291                }
292             });
293           };
294        // update current API key
295        apiKey = key;
296        // update the players queue
297        players.push(playerObj);
298        // load first tracks
299        loadUrl(links[index]);
300      },
301      artworkImage = function(track, usePlaceholder) {
302        if(usePlaceholder){
303          return '<div class="sc-loading-artwork">Loading Artwork</div>';
304        }else if (track.artwork_url) {
305          return '<img src="' + track.artwork_url.replace('-large', '-t300x300') + '"/>';
306        }else{
307          return '<div class="sc-no-artwork">No Artwork</div>';
308        }
309      },
310      updateTrackInfo = function($player, track) {
311        // update the current track info in the player
312        // log('updateTrackInfo', track);
313        $('.sc-info', $player).each(function(index) {
314          $('h3', this).html('<a href="' + track.permalink_url +'">' + track.title + '</a>');
315          $('h4', this).html('by <a href="' + track.user.permalink_url +'">' + track.user.username + '</a>');
316          $('p', this).html(track.description || 'no Description');
317        });
318        // update the artwork
319        $('.sc-artwork-list li', $player).each(function(index) {
320          var $item = $(this),
321              itemTrack = $item.data('sc-track');
322
323          if (itemTrack === track) {
324            // show track artwork
325            $item
326              .addClass('active')
327              .find('.sc-loading-artwork')
328                .each(function(index) {
329                  // if the image isn't loaded yet, do it now
330                  $(this).removeClass('sc-loading-artwork').html(artworkImage(track, false));
331                });
332          }else{
333            // reset other artworks
334            $item.removeClass('active');
335          }
336        });
337        // update the track duration in the progress bar
338        $('.sc-duration', $player).html(timecode(track.duration));
339        // put the waveform into the progress bar
340        $('.sc-waveform-container', $player).html('<img src="' + track.waveform_url +'" />');
341
342        $player.trigger('onPlayerTrackSwitch.scPlayer', [track]);
343      },
344      play = function(track) {
345        var url = track.permalink_url;
346        if(currentUrl === url){
347          // log('will play');
348          audioEngine.play();
349        }else{
350          currentUrl = url;
351          // log('will load', url);
352          audioEngine.load(track, apiKey);
353        }
354      },
355      getPlayerData = function(node) {
356        return players[$(node).data('sc-player').id];
357      },
358      updatePlayStatus = function(player, status) {
359        if(status){
360          // reset all other players playing status
361          $('div.sc-player.playing').removeClass('playing');
362        }
363        $(player)
364          .toggleClass('playing', status)
365          .trigger((status ? 'onPlayerPlay' : 'onPlayerPause'));
366      },
367      onPlay = function(player, id) {
368        var track = getPlayerData(player).tracks[id || 0];
369        updateTrackInfo(player, track);
370        // cache the references to most updated DOM nodes in the progress bar
371        updates = {
372          $buffer: $('.sc-buffer', player),
373          $played: $('.sc-played', player),
374          position:  $('.sc-position', player)[0]
375        };
376        updatePlayStatus(player, true);
377        play(track);
378      },
379      onPause = function(player) {
380        updatePlayStatus(player, false);
381        audioEngine.pause();
382      },
383      onFinish = function() {
384        var $player = updates.$played.closest('.sc-player'),
385            $nextItem;
386        // update the scrubber width
387        updates.$played.css('width', '0%');
388        // show the position in the track position counter
389        updates.position.innerHTML = timecode(0);
390        // reset the player state
391        updatePlayStatus($player, false);
392        // stop the audio
393        audioEngine.stop();
394        $player.trigger('onPlayerTrackFinish');
395      },
396      onSeek = function(player, relative) {
397        audioEngine.seek(relative);
398        $(player).trigger('onPlayerSeek');
399      },
400      onSkip = function(player) {
401        var $player = $(player);
402        // continue playing through all players
403        log('track finished get the next one');
404        $nextItem = $('.sc-trackslist li.active', $player).next('li');
405        // try to find the next track in other player
406        if(!$nextItem.length){
407          $nextItem = $player.nextAll('div.sc-player:first').find('.sc-trackslist li.active');
408        }
409        $nextItem.click();
410      },
411      soundVolume = function() {
412        var vol = 80,
413            cooks = document.cookie.split(';'),
414            volRx = new RegExp('scPlayer_volume=(\\d+)');
415        for(var i in cooks){
416          if(volRx.test(cooks[i])){
417            vol = parseInt(cooks[i].match(volRx)[1], 10);
418            break;
419          }
420        }
421        return vol;
422      }(),
423      onVolume = function(volume) {
424        var vol = Math.floor(volume);
425        // save the volume in the cookie
426        var date = new Date();
427        date.setTime(date.getTime() + (365 * 24 * 60 * 60 * 1000));
428        soundVolume = vol;
429        document.cookie = ['scPlayer_volume=', vol, '; expires=', date.toUTCString(), '; path="/"'].join('');
430        // update the volume in the engine
431        audioEngine.setVolume(soundVolume);
432      },
433      positionPoll;
434
435    // listen to audio engine events
436    $doc
437      .bind('scPlayer:onAudioReady', function(event) {
438        log('onPlayerReady: audio engine is ready');
439        audioEngine.play();
440        // set initial volume
441        onVolume(soundVolume);
442      })
443      // when the loaded track started to play
444      .bind('scPlayer:onMediaPlay', function(event) {
445        clearInterval(positionPoll);
446        positionPoll = setInterval(function() {
447          var duration = audioEngine.getDuration(),
448              position = audioEngine.getPosition(),
449              relative = (position / duration);
450
451          // update the scrubber width
452          updates.$played.css('width', (100 * relative) + '%');
453          // show the position in the track position counter
454          updates.position.innerHTML = timecode(position);
455          // announce the track position to the DOM
456          $doc.trigger({
457            type: 'onMediaTimeUpdate.scPlayer',
458            duration: duration,
459            position: position,
460            relative: relative
461          });
462        }, 500);
463      })
464      // when the loaded track is was paused
465      .bind('scPlayer:onMediaPause', function(event) {
466        clearInterval(positionPoll);
467        positionPoll = null;
468      })
469      // change the volume
470      .bind('scPlayer:onVolumeChange', function(event) {
471        onVolume(event.volume);
472      })
473      .bind('scPlayer:onMediaEnd', function(event) {
474        onFinish();
475      })
476      .bind('scPlayer:onMediaBuffering', function(event) {
477        updates.$buffer.css('width', event.percent + '%');
478      });
479
480
481  // Generate custom skinnable HTML/CSS/JavaScript based SoundCloud players from links to SoundCloud resources
482  $.scPlayer = function(options, node) {
483    var opts = $.extend({}, $.scPlayer.defaults, options),
484        playerId = players.length,
485        $source = node && $(node),
486        sourceClasses = $source[0].className.replace('sc-player', ''),
487        links = opts.links || $.map($('a', $source).add($source.filter('a')), function(val) { return {url: val.href, title: val.innerHTML}; }),
488        $player = $('<div class="sc-player loading"></div>').data('sc-player', {id: playerId}),
489        $artworks = $('<ol class="sc-artwork-list"></ol>').appendTo($player),
490        $info = $('<div class="sc-info"><h3></h3><h4></h4><p></p><a href="#" class="sc-info-close">X</a></div>').appendTo($player),
491        $controls = $('<div class="sc-controls"></div>').appendTo($player),
492        $list = $('<ol class="sc-trackslist"></ol>').appendTo($player);
493
494        // add the classes of the source node to the player itself
495        // the players can be indvidually styled this way
496        if(sourceClasses || opts.customClass){
497          $player.addClass(sourceClasses).addClass(opts.customClass);
498        }
499
500
501        // adding controls to the player
502        $player
503          .find('.sc-controls')
504            .append('<a href="#play" class="sc-play">Play</a> <a href="#pause" class="sc-pause hidden">Pause</a>')
505          .end()
506          .append('<a href="#info" class="sc-info-toggle">Info</a>')
507          .append('<div class="sc-scrubber"></div>')
508            .find('.sc-scrubber')
509              .append('<div class="sc-volume-slider"><span class="sc-volume-status" style="width:' + soundVolume +'%"></span></div>')
510              .append('<div class="sc-time-span"><div class="sc-waveform-container"></div><div class="sc-buffer"></div><div class="sc-played"></div></div>')
511              .append('<div class="sc-time-indicators"><span class="sc-position"></span> | <span class="sc-duration"></span></div>');
512
513        // load and parse the track data from SoundCloud API
514        loadTracksData($player, links, opts.apiKey);
515        // init the player GUI, when the tracks data was laoded
516        $player.bind('onTrackDataLoaded.scPlayer', function(event) {
517          // log('onTrackDataLoaded.scPlayer', event.playerObj, playerId, event.target);
518          var tracks = event.playerObj.tracks;
519          if (opts.randomize) {
520            tracks = shuffle(tracks);
521          }
522          // create the playlist
523          $.each(tracks, function(index, track) {
524            var active = index === 0;
525            // create an item in the playlist
526            $('<li><a href="' + track.permalink_url +'">' + track.title + '</a><span class="sc-track-duration">
526' + timecode(track.duration) + '</span></li>').data('sc-track', {id:index}).toggleClass('active', active).appendTo($list);
527            // create an item in the artwork list
528            $('<li></li>')
529              .append(artworkImage(track, index >= opts.loadArtworks))
530              .appendTo($artworks)
531              .toggleClass('active', active)
532              .data('sc-track', track);
533          });
534          // update the element before rendering it in the DOM
535          $player.each(function() {
536            if($.isFunction(opts.beforeRender)){
537              opts.beforeRender.call(this, tracks);
538            }
539          });
540          // set the first track's duration
541          $('.sc-duration', $player)[0].innerHTML = timecode(tracks[0].duration);
542          $('.sc-position', $player)[0].innerHTML = timecode(0);
543          // set up the first track info
544          updateTrackInfo($player, tracks[0]);
545
546          // if continous play enabled always skip to the next track after one finishes
547          if (opts.continuePlayback) {
548            $player.bind('onPlayerTrackFinish', function(event) {
549              onSkip($player);
550            });
551          }
552
553          // announce the succesful initialization
554          $player
555            .removeClass('loading')
556            .trigger('onPlayerInit');
557
558          // if auto play is enabled and it's the first player, start playing
559          if(opts.autoPlay && !didAutoPlay){
560            onPlay($player);
561            didAutoPlay = true;
562          }
563        });
564
565
566    // replace the DOM source (if there's one)
567    $source.each(function(index) {
568      $(this).replaceWith($player);
569    });
570
571    return $player;
572  };
573
574  // stop all players, might be useful, before replacing the player dynamically
575  $.scPlayer.stopAll = function() {
576    $('.sc-player.playing a.sc-pause').click();
577  };
578
579  // destroy all the players and audio engine, usefull when reloading part of the page and audio has to stop
580  $.scPlayer.destroy = function() {
581    $('.sc-player, .sc-player-engine-container').remove();
582  };
583
584  // plugin wrapper
585  $.fn.scPlayer = function(options) {
586    // reset the auto play
587    didAutoPlay = false;
588    // create the players
589    this.each(function() {
590      $.scPlayer(options, this);
591    });
592    return this;
593  };
594
595  // default plugin options
596  $.scPlayer.defaults = $.fn.scPlayer.defaults = {
597    customClass: null,
598    // do something with the dom object before you render it, add nodes, get more data from the services etc.
599    beforeRender  :   function(tracksData) {
600      var $player = $(this);
601    },
602    // initialization, when dom is ready
603    onDomReady  : function() {
604      $('a.sc-player, div.sc-player').scPlayer();
605    },
606    autoPlay: false,
607    continuePlayback: true,
608    randomize: false,
609    loadArtworks: 5,
610    // the default Api key should be replaced by your own one
611    // get it here http://soundcloud.com/you/apps/new
612    apiKey: 'htuiRd1JP11Ww0X72T1C3g'
613  };
614
615
616  // the GUI event bindings
617  //--------------------------------------------------------
618
619  // toggling play/pause
620  $('a.sc-play, a.sc-pause').live('click', function(event) {
621    var $list = $(this).closest('.sc-player').find('ol.sc-trackslist');
622    // simulate the click in the tracklist
623    $list.find('li.active').click();
624    return false;
625  });
626
627  // displaying the info panel in the player
628  $('a.sc-info-toggle, a.sc-info-close').live('click', function(event) {
629    var $link = $(this);
630    $link.closest('.sc-player')
631      .find('.sc-info').toggleClass('active').end()
632      .find('a.sc-info-toggle').toggleClass('active');
633    return false;
634  });
635
636  // selecting tracks in the playlist
637  $('.sc-trackslist li').live('click', function(event) {
638    var $track = $(this),
639        $player = $track.closest('.sc-player'),
640        trackId = $track.data('sc-track').id,
641        play = $player.is(':not(.playing)') || $track.is(':not(.active)');
642    if (play) {
643      onPlay($player, trackId);
644    }else{
645      onPause($player);
646    }
647    $track.addClass('active').siblings('li').removeClass('active');
648    $('.artworks li', $player).each(function(index) {
649      $(this).toggleClass('active', index === trackId);
650    });
651    return false;
652  });
653
654  var scrub = function(node, xPos) {
655    var $scrubber = $(node).closest('.sc-time-span'),
656        $buffer = $scrubber.find('.sc-buffer'),
657        $available = $scrubber.find('.sc-waveform-container img'),
658        $player = $scrubber.closest('.sc-player'),
659        relative = Math.min($buffer.width(), (xPos  - $available.offset().left)) / $available.width();
660    onSeek($player, relative);
661  };
662
663  var onTouchMove = function(ev) {
664    if (ev.targetTouches.length === 1) {
665      scrub(ev.target, ev.targetTouches && ev.targetTouches.length && ev.targetTouches[0].clientX);
666      ev.preventDefault();
667    }
668  };
669
670
671  // seeking in the loaded track buffer
672  $('.sc-time-span')
673    .live('click', function(event) {
674      scrub(this, event.pageX);
675      return false;
676    })
677    .live('touchstart', function(event) {
678      this.addEventListener('touchmove', onTouchMove, false);
679      event.originalEvent.preventDefault();
680    })
681    .live('touchend', function(event) {
682      this.removeEventListener('touchmove', onTouchMove, false);
683      event.originalEvent.preventDefault();
684    });
685
686  // changing volume in the player
687  var startVolumeTracking = function(node, startEvent) {
688    var $node = $(node),
689        originX = $node.offset().left,
690        originWidth = $node.width(),
691        getVolume = function(x) {
692          return Math.floor(((x - originX)/originWidth)*100);
693        },
694        update = function(event) {
695          $doc.trigger({type: 'scPlayer:onVolumeChange', volume: getVolume(event.pageX)});
696        };
697    $node.bind('mousemove.sc-player', update);
698    update(startEvent);
699  };
700
701  var stopVolumeTracking = function(node, event) {
702    $(node).unbind('mousemove.sc-player');
703  };
704
705  $('.sc-volume-slider')
706    .live('mousedown', function(event) {
707      startVolumeTracking(this, event);
708    })
709    .live('mouseup', function(event) {
710      stopVolumeTracking(this, event);
711    });
712
713  $doc.bind('scPlayer:onVolumeChange', function(event) {
714    $('span.sc-volume-status').css({width: event.volume + '%'});
715  });
716  // -------------------------------------------------------------------
717
718  // the default Auto-Initialization
719  $(function() {
720    if($.isFunction($.scPlayer.defaults.onDomReady)){
721      $.scPlayer.defaults.onDomReady();
722    }
723  });
724
725})(jQuery);

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.