soundcloud-custom-player-rails 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.gitignore +20 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +35 -0
  5. data/Rakefile +1 -0
  6. data/lib/soundcloud-custom-player-rails.rb +12 -0
  7. data/lib/soundcloud-custom-player-rails/engine.rb +6 -0
  8. data/lib/soundcloud-custom-player-rails/railtie.rb +6 -0
  9. data/lib/soundcloud-custom-player-rails/version.rb +3 -0
  10. data/soundcloud-custom-player-rails.gemspec +17 -0
  11. data/vendor/assets/images/sc-player-artwork/pause-hover.png +0 -0
  12. data/vendor/assets/images/sc-player-artwork/pause.png +0 -0
  13. data/vendor/assets/images/sc-player-artwork/play-hover.png +0 -0
  14. data/vendor/assets/images/sc-player-artwork/play.png +0 -0
  15. data/vendor/assets/images/sc-player-minimal/pause-hover.png +0 -0
  16. data/vendor/assets/images/sc-player-minimal/pause.png +0 -0
  17. data/vendor/assets/images/sc-player-minimal/play-hover.png +0 -0
  18. data/vendor/assets/images/sc-player-minimal/play.png +0 -0
  19. data/vendor/assets/images/sc-player-red/pause-hover.png +0 -0
  20. data/vendor/assets/images/sc-player-red/pause.png +0 -0
  21. data/vendor/assets/images/sc-player-red/play-hover.png +0 -0
  22. data/vendor/assets/images/sc-player-red/play.png +0 -0
  23. data/vendor/assets/images/sc-player-standard/pause-blue-hover.png +0 -0
  24. data/vendor/assets/images/sc-player-standard/pause-blue.png +0 -0
  25. data/vendor/assets/images/sc-player-standard/pause-green-hover.png +0 -0
  26. data/vendor/assets/images/sc-player-standard/pause-green.png +0 -0
  27. data/vendor/assets/images/sc-player-standard/pause-orange-hover.png +0 -0
  28. data/vendor/assets/images/sc-player-standard/pause-orange.png +0 -0
  29. data/vendor/assets/images/sc-player-standard/play-blue-hover.png +0 -0
  30. data/vendor/assets/images/sc-player-standard/play-blue.png +0 -0
  31. data/vendor/assets/images/sc-player-standard/play-green-hover.png +0 -0
  32. data/vendor/assets/images/sc-player-standard/play-green.png +0 -0
  33. data/vendor/assets/images/sc-player-standard/play-orange-hover.png +0 -0
  34. data/vendor/assets/images/sc-player-standard/play-orange.png +0 -0
  35. data/vendor/assets/javascripts/sc-player.js +725 -0
  36. data/vendor/assets/javascripts/soundcloud.player.api.js +140 -0
  37. data/vendor/assets/stylesheets/sc-player-artwork/colors.css +130 -0
  38. data/vendor/assets/stylesheets/sc-player-artwork/standards.css +18 -0
  39. data/vendor/assets/stylesheets/sc-player-artwork/structure.css +173 -0
  40. data/vendor/assets/stylesheets/sc-player-minimal/colors.css +138 -0
  41. data/vendor/assets/stylesheets/sc-player-minimal/standards.css +17 -0
  42. data/vendor/assets/stylesheets/sc-player-minimal/structure.css +174 -0
  43. data/vendor/assets/stylesheets/sc-player-red/colors.css +141 -0
  44. data/vendor/assets/stylesheets/sc-player-red/standards.css +32 -0
  45. data/vendor/assets/stylesheets/sc-player-red/structure.css +171 -0
  46. data/vendor/assets/stylesheets/sc-player-standard/colors-blue.css +299 -0
  47. data/vendor/assets/stylesheets/sc-player-standard/colors-green.css +167 -0
  48. data/vendor/assets/stylesheets/sc-player-standard/colors-orange.css +165 -0
  49. data/vendor/assets/stylesheets/sc-player-standard/standards.css +33 -0
  50. data/vendor/assets/stylesheets/sc-player-standard/structure-horizontal.css +209 -0
  51. data/vendor/assets/stylesheets/sc-player-standard/structure-vertical.css +190 -0
  52. metadata +96 -0
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+ .DS_Store
20
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ..gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Daniël van de Burgt
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,35 @@
1
+ soundcloud-custom-player-rails
2
+ ==============================
3
+
4
+ Soundcloud Custom Player integration for Rails asset pipeline.
5
+ See https://github.com/soundcloud/soundcloud-custom-player for more info.
6
+
7
+ Installation
8
+ ------------
9
+
10
+ Gemfile:
11
+
12
+ group :assets do
13
+ gem 'soundcloud-custom-player-rails'
14
+ end
15
+
16
+ Or get the latest build:
17
+
18
+ group :assets do
19
+ gem 'soundcloud-custom-player-rails',
20
+ :require => 'soundcloud-custom-player-rails',
21
+ :git => 'git://github.com/thatdutchguy/soundcloud-custom-player-rails.git'
22
+ end
23
+
24
+ js:
25
+
26
+ //= require soundcloud.player.api
27
+ //= require sc-player
28
+
29
+ css:
30
+
31
+ @import "sc-player-standard/standards";
32
+ @import "sc-player-standard/structure-horizontal";
33
+ @import "sc-player-standard/colors-orange";
34
+
35
+ Included are multiple players/css combinations, see https://github.com/thatdutchguy/soundcloud-custom-player-rails/tree/master/vendor/assets/stylesheets too see what's available.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,12 @@
1
+ require "rails"
2
+ require "soundcloud-custom-player-rails/version"
3
+
4
+ module SoundcloudCustomPlayerRails
5
+ module Rails
6
+ if ::Rails.version < "3.1"
7
+ require "soundcloud-custom-player-rails/railtie"
8
+ else
9
+ require "soundcloud-custom-player-rails/engine"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ module SoundcloudCustomPlayerRails
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module SoundcloudCustomPlayerRails
2
+ module Rails
3
+ class Railtie < ::Rails::Railtie
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module SoundcloudCustomPlayerRails
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'soundcloud-custom-player-rails/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "soundcloud-custom-player-rails"
8
+ gem.version = SoundcloudCustomPlayerRails::VERSION
9
+ gem.authors = ["Daniël van de Burgt"]
10
+ gem.email = ["thatdutchguy@secretlymexico.com"]
11
+ gem.description = %q{Soundcloud Custom Player assets for Rails}
12
+ gem.summary = gem.description
13
+ gem.homepage = "http://github.com/thatdutchguy/soundcloud-custom-player-rails"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.require_paths = ["lib"]
17
+ end
@@ -0,0 +1,725 @@
1
+ /*
2
+ * SoundCloud Custom Player jQuery Plugin
3
+ * Author: Matas Petrikas, matas@soundcloud.com
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 + '">'+
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">' + 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);