soundcloud-custom-player-rails 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +35 -0
- data/Rakefile +1 -0
- data/lib/soundcloud-custom-player-rails.rb +12 -0
- data/lib/soundcloud-custom-player-rails/engine.rb +6 -0
- data/lib/soundcloud-custom-player-rails/railtie.rb +6 -0
- data/lib/soundcloud-custom-player-rails/version.rb +3 -0
- data/soundcloud-custom-player-rails.gemspec +17 -0
- data/vendor/assets/images/sc-player-artwork/pause-hover.png +0 -0
- data/vendor/assets/images/sc-player-artwork/pause.png +0 -0
- data/vendor/assets/images/sc-player-artwork/play-hover.png +0 -0
- data/vendor/assets/images/sc-player-artwork/play.png +0 -0
- data/vendor/assets/images/sc-player-minimal/pause-hover.png +0 -0
- data/vendor/assets/images/sc-player-minimal/pause.png +0 -0
- data/vendor/assets/images/sc-player-minimal/play-hover.png +0 -0
- data/vendor/assets/images/sc-player-minimal/play.png +0 -0
- data/vendor/assets/images/sc-player-red/pause-hover.png +0 -0
- data/vendor/assets/images/sc-player-red/pause.png +0 -0
- data/vendor/assets/images/sc-player-red/play-hover.png +0 -0
- data/vendor/assets/images/sc-player-red/play.png +0 -0
- data/vendor/assets/images/sc-player-standard/pause-blue-hover.png +0 -0
- data/vendor/assets/images/sc-player-standard/pause-blue.png +0 -0
- data/vendor/assets/images/sc-player-standard/pause-green-hover.png +0 -0
- data/vendor/assets/images/sc-player-standard/pause-green.png +0 -0
- data/vendor/assets/images/sc-player-standard/pause-orange-hover.png +0 -0
- data/vendor/assets/images/sc-player-standard/pause-orange.png +0 -0
- data/vendor/assets/images/sc-player-standard/play-blue-hover.png +0 -0
- data/vendor/assets/images/sc-player-standard/play-blue.png +0 -0
- data/vendor/assets/images/sc-player-standard/play-green-hover.png +0 -0
- data/vendor/assets/images/sc-player-standard/play-green.png +0 -0
- data/vendor/assets/images/sc-player-standard/play-orange-hover.png +0 -0
- data/vendor/assets/images/sc-player-standard/play-orange.png +0 -0
- data/vendor/assets/javascripts/sc-player.js +725 -0
- data/vendor/assets/javascripts/soundcloud.player.api.js +140 -0
- data/vendor/assets/stylesheets/sc-player-artwork/colors.css +130 -0
- data/vendor/assets/stylesheets/sc-player-artwork/standards.css +18 -0
- data/vendor/assets/stylesheets/sc-player-artwork/structure.css +173 -0
- data/vendor/assets/stylesheets/sc-player-minimal/colors.css +138 -0
- data/vendor/assets/stylesheets/sc-player-minimal/standards.css +17 -0
- data/vendor/assets/stylesheets/sc-player-minimal/structure.css +174 -0
- data/vendor/assets/stylesheets/sc-player-red/colors.css +141 -0
- data/vendor/assets/stylesheets/sc-player-red/standards.css +32 -0
- data/vendor/assets/stylesheets/sc-player-red/structure.css +171 -0
- data/vendor/assets/stylesheets/sc-player-standard/colors-blue.css +299 -0
- data/vendor/assets/stylesheets/sc-player-standard/colors-green.css +167 -0
- data/vendor/assets/stylesheets/sc-player-standard/colors-orange.css +165 -0
- data/vendor/assets/stylesheets/sc-player-standard/standards.css +33 -0
- data/vendor/assets/stylesheets/sc-player-standard/structure-horizontal.css +209 -0
- data/vendor/assets/stylesheets/sc-player-standard/structure-vertical.css +190 -0
- metadata +96 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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,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
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -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 +'&enable_api=true&player_type=engine&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);
|