audiojs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in audiojs.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Alif Rachmawadi
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,29 @@
1
+ # Audiojs
2
+
3
+ [audio.js](http://kolber.github.com/audiojs/) is a drop-in javascript library that allows HTML5's <audio> tag to be used anywhere.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'audiojs'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install audiojs
18
+
19
+ Put on manifest file (default, `application.js`):
20
+
21
+ //= require audiojs
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/audiojs/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Alif Rachmawadi"]
6
+ gem.email = ["subosito@gmail.com"]
7
+ gem.description = %q{Audio.js + Rails Asset Pipeline}
8
+ gem.summary = %q{audio.js is a drop-in javascript library that allows HTML5's <audio> tag to be used anywhere.}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "audiojs"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Audiojs::VERSION
17
+ end
@@ -0,0 +1,8 @@
1
+ require "audiojs/version"
2
+
3
+ module Audiojs
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module Audiojs
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,703 @@
1
+ // A cross-browser javascript shim for html5 audio
2
+ (function(audiojs, audiojsInstance, container) {
3
+ // Use the path to the audio.js file to create relative paths to the swf and player graphics
4
+ // Remember that some systems (e.g. ruby on rails) append strings like '?1301478336' to asset paths
5
+ var path = (function() {
6
+ var re = new RegExp('(audio|application)(\.min)?\.js.*'),
7
+ scripts = document.getElementsByTagName('script');
8
+ for (var i = 0, ii = scripts.length; i < ii; i++) {
9
+ var path = scripts[i].getAttribute('src');
10
+ if(re.test(path)) return path.replace(re, '');
11
+ }
12
+ })();
13
+
14
+ // ##The audiojs interface
15
+ // This is the global object which provides an interface for creating new `audiojs` instances.
16
+ // It also stores all of the construction helper methods and variables.
17
+ container[audiojs] = {
18
+ instanceCount: 0,
19
+ instances: {},
20
+ // The markup for the swf. It is injected into the page if there is not support for the `<audio>` element. The `$n`s are placeholders.
21
+ // `$1` The name of the flash movie
22
+ // `$2` The path to the swf
23
+ // `$3` Cache invalidation
24
+ flashSource: '\
25
+ <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" id="$1" width="1" height="1" name="$1" style="position: absolute; left: -1px;"> \
26
+ <param name="movie" value="$2?playerInstance='+audiojs+'.instances[\'$1\']&datetime=$3"> \
27
+ <param name="allowscriptaccess" value="always"> \
28
+ <embed name="$1" src="$2?playerInstance='+audiojs+'.instances[\'$1\']&datetime=$3" width="1" height="1" allowscriptaccess="always"> \
29
+ </object>',
30
+
31
+ // ### The main settings object
32
+ // Where all the default settings are stored. Each of these variables and methods can be overwritten by the user-provided `options` object.
33
+ settings: {
34
+ autoplay: false,
35
+ loop: false,
36
+ preload: true,
37
+ imageLocation: path + 'player-graphics.gif',
38
+ swfLocation: path + 'audiojs.swf',
39
+ useFlash: (function() {
40
+ var a = document.createElement('audio');
41
+ return !(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, ''));
42
+ })(),
43
+ hasFlash: (function() {
44
+ if (navigator.plugins && navigator.plugins.length && navigator.plugins['Shockwave Flash']) {
45
+ return true;
46
+ } else if (navigator.mimeTypes && navigator.mimeTypes.length) {
47
+ var mimeType = navigator.mimeTypes['application/x-shockwave-flash'];
48
+ return mimeType && mimeType.enabledPlugin;
49
+ } else {
50
+ try {
51
+ var ax = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
52
+ return true;
53
+ } catch (e) {}
54
+ }
55
+ return false;
56
+ })(),
57
+ // The default markup and classes for creating the player:
58
+ createPlayer: {
59
+ markup: '\
60
+ <div class="play-pause"> \
61
+ <p class="play"></p> \
62
+ <p class="pause"></p> \
63
+ <p class="loading"></p> \
64
+ <p class="error"></p> \
65
+ </div> \
66
+ <div class="scrubber"> \
67
+ <div class="progress"></div> \
68
+ <div class="loaded"></div> \
69
+ </div> \
70
+ <div class="time"> \
71
+ <em class="played">00:00</em>/<strong class="duration">00:00</strong> \
72
+ </div> \
73
+ <div class="error-message"></div>',
74
+ playPauseClass: 'play-pause',
75
+ scrubberClass: 'scrubber',
76
+ progressClass: 'progress',
77
+ loaderClass: 'loaded',
78
+ timeClass: 'time',
79
+ durationClass: 'duration',
80
+ playedClass: 'played',
81
+ errorMessageClass: 'error-message',
82
+ playingClass: 'playing',
83
+ loadingClass: 'loading',
84
+ errorClass: 'error'
85
+ },
86
+ // The css used by the default player. This is is dynamically injected into a `<style>` tag in the top of the head.
87
+ css: '\
88
+ .audiojs audio { position: absolute; left: -1px; } \
89
+ .audiojs { width: 460px; height: 36px; background: #404040; overflow: hidden; font-family: monospace; font-size: 12px; \
90
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #444), color-stop(0.5, #555), color-stop(0.51, #444), color-stop(1, #444)); \
91
+ background-image: -moz-linear-gradient(center top, #444 0%, #555 50%, #444 51%, #444 100%); \
92
+ -webkit-box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.3); -moz-box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.3); \
93
+ -o-box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.3); box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.3); } \
94
+ .audiojs .play-pause { width: 25px; height: 40px; padding: 4px 6px; margin: 0px; float: left; overflow: hidden; border-right: 1px solid #000; } \
95
+ .audiojs p { display: none; width: 25px; height: 40px; margin: 0px; cursor: pointer; } \
96
+ .audiojs .play { display: block; } \
97
+ .audiojs .scrubber { position: relative; float: left; width: 280px; background: #5a5a5a; height: 14px; margin: 10px; border-top: 1px solid #3f3f3f; border-left: 0px; border-bottom: 0px; overflow: hidden; } \
98
+ .audiojs .progress { position: absolute; top: 0px; left: 0px; height: 14px; width: 0px; background: #ccc; z-index: 1; \
99
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #ccc), color-stop(0.5, #ddd), color-stop(0.51, #ccc), color-stop(1, #ccc)); \
100
+ background-image: -moz-linear-gradient(center top, #ccc 0%, #ddd 50%, #ccc 51%, #ccc 100%); } \
101
+ .audiojs .loaded { position: absolute; top: 0px; left: 0px; height: 14px; width: 0px; background: #000; \
102
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #222), color-stop(0.5, #333), color-stop(0.51, #222), color-stop(1, #222)); \
103
+ background-image: -moz-linear-gradient(center top, #222 0%, #333 50%, #222 51%, #222 100%); } \
104
+ .audiojs .time { float: left; height: 36px; line-height: 36px; margin: 0px 0px 0px 6px; padding: 0px 6px 0px 12px; border-left: 1px solid #000; color: #ddd; text-shadow: 1px 1px 0px rgba(0, 0, 0, 0.5); } \
105
+ .audiojs .time em { padding: 0px 2px 0px 0px; color: #f9f9f9; font-style: normal; } \
106
+ .audiojs .time strong { padding: 0px 0px 0px 2px; font-weight: normal; } \
107
+ .audiojs .error-message { float: left; display: none; margin: 0px 10px; height: 36px; width: 400px; overflow: hidden; line-height: 36px; white-space: nowrap; color: #fff; \
108
+ text-overflow: ellipsis; -o-text-overflow: ellipsis; -icab-text-overflow: ellipsis; -khtml-text-overflow: ellipsis; -moz-text-overflow: ellipsis; -webkit-text-overflow: ellipsis; } \
109
+ .audiojs .error-message a { color: #eee; text-decoration: none; padding-bottom: 1px; border-bottom: 1px solid #999; white-space: wrap; } \
110
+ \
111
+ .audiojs .play { background: url("$1") -2px -1px no-repeat; } \
112
+ .audiojs .loading { background: url("$1") -2px -31px no-repeat; } \
113
+ .audiojs .error { background: url("$1") -2px -61px no-repeat; } \
114
+ .audiojs .pause { background: url("$1") -2px -91px no-repeat; } \
115
+ \
116
+ .playing .play, .playing .loading, .playing .error { display: none; } \
117
+ .playing .pause { display: block; } \
118
+ \
119
+ .loading .play, .loading .pause, .loading .error { display: none; } \
120
+ .loading .loading { display: block; } \
121
+ \
122
+ .error .time, .error .play, .error .pause, .error .scrubber, .error .loading { display: none; } \
123
+ .error .error { display: block; } \
124
+ .error .play-pause p { cursor: auto; } \
125
+ .error .error-message { display: block; }',
126
+ // The default event callbacks:
127
+ trackEnded: function(e) {},
128
+ flashError: function() {
129
+ var player = this.settings.createPlayer,
130
+ errorMessage = getByClass(player.errorMessageClass, this.wrapper),
131
+ html = 'Missing <a href="http://get.adobe.com/flashplayer/">flash player</a> plugin.';
132
+ if (this.mp3) html += ' <a href="'+this.mp3+'">Download audio file</a>.';
133
+ container[audiojs].helpers.removeClass(this.wrapper, player.loadingClass);
134
+ container[audiojs].helpers.addClass(this.wrapper, player.errorClass);
135
+ errorMessage.innerHTML = html;
136
+ },
137
+ loadError: function(e) {
138
+ var player = this.settings.createPlayer,
139
+ errorMessage = getByClass(player.errorMessageClass, this.wrapper);
140
+ container[audiojs].helpers.removeClass(this.wrapper, player.loadingClass);
141
+ container[audiojs].helpers.addClass(this.wrapper, player.errorClass);
142
+ errorMessage.innerHTML = 'Error loading: "'+this.mp3+'"';
143
+ },
144
+ init: function() {
145
+ var player = this.settings.createPlayer;
146
+ container[audiojs].helpers.addClass(this.wrapper, player.loadingClass);
147
+ },
148
+ loadStarted: function() {
149
+ var player = this.settings.createPlayer,
150
+ duration = getByClass(player.durationClass, this.wrapper),
151
+ m = Math.floor(this.duration / 60),
152
+ s = Math.floor(this.duration % 60);
153
+ container[audiojs].helpers.removeClass(this.wrapper, player.loadingClass);
154
+ duration.innerHTML = ((m<10?'0':'')+m+':'+(s<10?'0':'')+s);
155
+ },
156
+ loadProgress: function(percent) {
157
+ var player = this.settings.createPlayer,
158
+ scrubber = getByClass(player.scrubberClass, this.wrapper),
159
+ loaded = getByClass(player.loaderClass, this.wrapper);
160
+ loaded.style.width = (scrubber.offsetWidth * percent) + 'px';
161
+ },
162
+ playPause: function() {
163
+ if (this.playing) this.settings.play();
164
+ else this.settings.pause();
165
+ },
166
+ play: function() {
167
+ var player = this.settings.createPlayer;
168
+ container[audiojs].helpers.addClass(this.wrapper, player.playingClass);
169
+ },
170
+ pause: function() {
171
+ var player = this.settings.createPlayer;
172
+ container[audiojs].helpers.removeClass(this.wrapper, player.playingClass);
173
+ },
174
+ updatePlayhead: function(percent) {
175
+ var player = this.settings.createPlayer,
176
+ scrubber = getByClass(player.scrubberClass, this.wrapper),
177
+ progress = getByClass(player.progressClass, this.wrapper);
178
+ progress.style.width = (scrubber.offsetWidth * percent) + 'px';
179
+
180
+ var played = getByClass(player.playedClass, this.wrapper),
181
+ p = this.duration * percent,
182
+ m = Math.floor(p / 60),
183
+ s = Math.floor(p % 60);
184
+ played.innerHTML = ((m<10?'0':'')+m+':'+(s<10?'0':'')+s);
185
+ }
186
+ },
187
+
188
+ // ### Contructor functions
189
+
190
+ // `create()`
191
+ // Used to create a single `audiojs` instance.
192
+ // If an array is passed then it calls back to `createAll()`.
193
+ // Otherwise, it creates a single instance and returns it.
194
+ create: function(element, options) {
195
+ var options = options || {}
196
+ if (element.length) {
197
+ return this.createAll(options, element);
198
+ } else {
199
+ return this.newInstance(element, options);
200
+ }
201
+ },
202
+
203
+ // `createAll()`
204
+ // Creates multiple `audiojs` instances.
205
+ // If `elements` is `null`, then automatically find any `<audio>` tags on the page and create `audiojs` instances for them.
206
+ createAll: function(options, elements) {
207
+ var audioElements = elements || document.getElementsByTagName('audio'),
208
+ instances = []
209
+ options = options || {};
210
+ for (var i = 0, ii = audioElements.length; i < ii; i++) {
211
+ instances.push(this.newInstance(audioElements[i], options));
212
+ }
213
+ return instances;
214
+ },
215
+
216
+ // ### Creating and returning a new instance
217
+ // This goes through all the steps required to build out a usable `audiojs` instance.
218
+ newInstance: function(element, options) {
219
+ var element = element,
220
+ s = this.helpers.clone(this.settings),
221
+ id = 'audiojs'+this.instanceCount,
222
+ wrapperId = 'audiojs_wrapper'+this.instanceCount,
223
+ instanceCount = this.instanceCount++;
224
+
225
+ // Check for `autoplay`, `loop` and `preload` attributes and write them into the settings.
226
+ if (element.getAttribute('autoplay') != null) s.autoplay = true;
227
+ if (element.getAttribute('loop') != null) s.loop = true;
228
+ if (element.getAttribute('preload') == 'none') s.preload = false;
229
+ // Merge the default settings with the user-defined `options`.
230
+ if (options) this.helpers.merge(s, options);
231
+
232
+ // Inject the player html if required.
233
+ if (s.createPlayer.markup) element = this.createPlayer(element, s.createPlayer, wrapperId);
234
+ else element.parentNode.setAttribute('id', wrapperId);
235
+
236
+ // Return a new `audiojs` instance.
237
+ var audio = new container[audiojsInstance](element, s);
238
+
239
+ // If css has been passed in, dynamically inject it into the `<head>`.
240
+ if (s.css) this.helpers.injectCss(audio, s.css);
241
+
242
+ // If `<audio>` or mp3 playback isn't supported, insert the swf & attach the required events for it.
243
+ if (s.useFlash && s.hasFlash) {
244
+ this.injectFlash(audio, id);
245
+ this.attachFlashEvents(audio.wrapper, audio);
246
+ } else if (s.useFlash && !s.hasFlash) {
247
+ this.settings.flashError.apply(audio);
248
+ }
249
+
250
+ // Attach event callbacks to the new audiojs instance.
251
+ if (!s.useFlash || (s.useFlash && s.hasFlash)) this.attachEvents(audio.wrapper, audio);
252
+
253
+ // Store the newly-created `audiojs` instance.
254
+ this.instances[id] = audio;
255
+ return audio;
256
+ },
257
+
258
+ // ### Helper methods for constructing a working player
259
+ // Inject a wrapping div and the markup for the html player.
260
+ createPlayer: function(element, player, id) {
261
+ var wrapper = document.createElement('div'),
262
+ newElement = element.cloneNode(true);
263
+ wrapper.setAttribute('class', 'audiojs');
264
+ wrapper.setAttribute('className', 'audiojs');
265
+ wrapper.setAttribute('id', id);
266
+
267
+ // Fix IE's broken implementation of `innerHTML` & `cloneNode` for HTML5 elements.
268
+ if (newElement.outerHTML && !document.createElement('audio').canPlayType) {
269
+ newElement = this.helpers.cloneHtml5Node(element);
270
+ wrapper.innerHTML = player.markup;
271
+ wrapper.appendChild(newElement);
272
+ element.outerHTML = wrapper.outerHTML;
273
+ wrapper = document.getElementById(id);
274
+ } else {
275
+ wrapper.appendChild(newElement);
276
+ wrapper.innerHTML = wrapper.innerHTML + player.markup;
277
+ element.parentNode.replaceChild(wrapper, element);
278
+ }
279
+ return wrapper.getElementsByTagName('audio')[0];
280
+ },
281
+
282
+ // Attaches useful event callbacks to an `audiojs` instance.
283
+ attachEvents: function(wrapper, audio) {
284
+ if (!audio.settings.createPlayer) return;
285
+ var player = audio.settings.createPlayer,
286
+ playPause = getByClass(player.playPauseClass, wrapper),
287
+ scrubber = getByClass(player.scrubberClass, wrapper),
288
+ leftPos = function(elem) {
289
+ var curleft = 0;
290
+ if (elem.offsetParent) {
291
+ do { curleft += elem.offsetLeft; } while (elem = elem.offsetParent);
292
+ }
293
+ return curleft;
294
+ };
295
+
296
+ container[audiojs].events.addListener(playPause, 'click', function(e) {
297
+ audio.playPause.apply(audio);
298
+ });
299
+
300
+ container[audiojs].events.addListener(scrubber, 'click', function(e) {
301
+ var relativeLeft = e.clientX - leftPos(this);
302
+ audio.skipTo(relativeLeft / scrubber.offsetWidth);
303
+ });
304
+
305
+ // _If flash is being used, then the following handlers don't need to be registered._
306
+ if (audio.settings.useFlash) return;
307
+
308
+ // Start tracking the load progress of the track.
309
+ container[audiojs].events.trackLoadProgress(audio);
310
+
311
+ container[audiojs].events.addListener(audio.element, 'timeupdate', function(e) {
312
+ audio.updatePlayhead.apply(audio);
313
+ });
314
+
315
+ container[audiojs].events.addListener(audio.element, 'ended', function(e) {
316
+ audio.trackEnded.apply(audio);
317
+ });
318
+
319
+ container[audiojs].events.addListener(audio.source, 'error', function(e) {
320
+ // on error, cancel any load timers that are running.
321
+ clearInterval(audio.readyTimer);
322
+ clearInterval(audio.loadTimer);
323
+ audio.settings.loadError.apply(audio);
324
+ });
325
+
326
+ },
327
+
328
+ // Flash requires a slightly different API to the `<audio>` element, so this method is used to overwrite the standard event handlers.
329
+ attachFlashEvents: function(element, audio) {
330
+ audio['swfReady'] = false;
331
+ audio['load'] = function(mp3) {
332
+ // If the swf isn't ready yet then just set `audio.mp3`. `init()` will load it in once the swf is ready.
333
+ audio.mp3 = mp3;
334
+ if (audio.swfReady) audio.element.load(mp3);
335
+ }
336
+ audio['loadProgress'] = function(percent, duration) {
337
+ audio.loadedPercent = percent;
338
+ audio.duration = duration;
339
+ audio.settings.loadStarted.apply(audio);
340
+ audio.settings.loadProgress.apply(audio, [percent]);
341
+ }
342
+ audio['skipTo'] = function(percent) {
343
+ if (percent > audio.loadedPercent) return;
344
+ audio.updatePlayhead.call(audio, [percent])
345
+ audio.element.skipTo(percent);
346
+ }
347
+ audio['updatePlayhead'] = function(percent) {
348
+ audio.settings.updatePlayhead.apply(audio, [percent]);
349
+ }
350
+ audio['play'] = function() {
351
+ // If the audio hasn't started preloading, then start it now.
352
+ // Then set `preload` to `true`, so that any tracks loaded in subsequently are loaded straight away.
353
+ if (!audio.settings.preload) {
354
+ audio.settings.preload = true;
355
+ audio.element.init(audio.mp3);
356
+ }
357
+ audio.playing = true;
358
+ // IE doesn't allow a method named `play()` to be exposed through `ExternalInterface`, so lets go with `pplay()`.
359
+ // <http://dev.nuclearrooster.com/2008/07/27/externalinterfaceaddcallback-can-cause-ie-js-errors-with-certain-keyworkds/>
360
+ audio.element.pplay();
361
+ audio.settings.play.apply(audio);
362
+ }
363
+ audio['pause'] = function() {
364
+ audio.playing = false;
365
+ // Use `ppause()` for consistency with `pplay()`, even though it isn't really required.
366
+ audio.element.ppause();
367
+ audio.settings.pause.apply(audio);
368
+ }
369
+ audio['setVolume'] = function(v) {
370
+ audio.element.setVolume(v);
371
+ }
372
+ audio['loadStarted'] = function() {
373
+ // Load the mp3 specified by the audio element into the swf.
374
+ audio.swfReady = true;
375
+ if (audio.settings.preload) audio.element.init(audio.mp3);
376
+ if (audio.settings.autoplay) audio.play.apply(audio);
377
+ }
378
+ },
379
+
380
+ // ### Injecting an swf from a string
381
+ // Build up the swf source by replacing the `$keys` and then inject the markup into the page.
382
+ injectFlash: function(audio, id) {
383
+ var flashSource = this.flashSource.replace(/\$1/g, id);
384
+ flashSource = flashSource.replace(/\$2/g, audio.settings.swfLocation);
385
+ // `(+new Date)` ensures the swf is not pulled out of cache. The fixes an issue with Firefox running multiple players on the same page.
386
+ flashSource = flashSource.replace(/\$3/g, (+new Date + Math.random()));
387
+ // Inject the player markup using a more verbose `innerHTML` insertion technique that works with IE.
388
+ var html = audio.wrapper.innerHTML,
389
+ div = document.createElement('div');
390
+ div.innerHTML = flashSource + html;
391
+ audio.wrapper.innerHTML = div.innerHTML;
392
+ audio.element = this.helpers.getSwf(id);
393
+ },
394
+
395
+ // ## Helper functions
396
+ helpers: {
397
+ // **Merge two objects, with `obj2` overwriting `obj1`**
398
+ // The merge is shallow, but that's all that is required for our purposes.
399
+ merge: function(obj1, obj2) {
400
+ for (attr in obj2) {
401
+ if (obj1.hasOwnProperty(attr) || obj2.hasOwnProperty(attr)) {
402
+ obj1[attr] = obj2[attr];
403
+ }
404
+ }
405
+ },
406
+ // **Clone a javascript object (recursively)**
407
+ clone: function(obj){
408
+ if (obj == null || typeof(obj) !== 'object') return obj;
409
+ var temp = new obj.constructor();
410
+ for (var key in obj) temp[key] = arguments.callee(obj[key]);
411
+ return temp;
412
+ },
413
+ // **Adding/removing classnames from elements**
414
+ addClass: function(element, className) {
415
+ var re = new RegExp('(\\s|^)'+className+'(\\s|$)');
416
+ if (re.test(element.className)) return;
417
+ element.className += ' ' + className;
418
+ },
419
+ removeClass: function(element, className) {
420
+ var re = new RegExp('(\\s|^)'+className+'(\\s|$)');
421
+ element.className = element.className.replace(re,' ');
422
+ },
423
+ // **Dynamic CSS injection**
424
+ // Takes a string of css, inserts it into a `<style>`, then injects it in at the very top of the `<head>`. This ensures any user-defined styles will take precedence.
425
+ injectCss: function(audio, string) {
426
+
427
+ // If an `audiojs` `<style>` tag already exists, then append to it rather than creating a whole new `<style>`.
428
+ var prepend = '',
429
+ styles = document.getElementsByTagName('style'),
430
+ css = string.replace(/\$1/g, audio.settings.imageLocation);
431
+
432
+ for (var i = 0, ii = styles.length; i < ii; i++) {
433
+ var title = styles[i].getAttribute('title');
434
+ if (title && ~title.indexOf('audiojs')) {
435
+ style = styles[i];
436
+ if (style.innerHTML === css) return;
437
+ prepend = style.innerHTML;
438
+ break;
439
+ }
440
+ };
441
+
442
+ var head = document.getElementsByTagName('head')[0],
443
+ firstchild = head.firstChild,
444
+ style = document.createElement('style');
445
+
446
+ if (!head) return;
447
+
448
+ style.setAttribute('type', 'text/css');
449
+ style.setAttribute('title', 'audiojs');
450
+
451
+ if (style.styleSheet) style.styleSheet.cssText = prepend + css;
452
+ else style.appendChild(document.createTextNode(prepend + css));
453
+
454
+ if (firstchild) head.insertBefore(style, firstchild);
455
+ else head.appendChild(styleElement);
456
+ },
457
+ // **Handle all the IE6+7 requirements for cloning `<audio>` nodes**
458
+ // Create a html5-safe document fragment by injecting an `<audio>` element into the document fragment.
459
+ cloneHtml5Node: function(audioTag) {
460
+ var fragment = document.createDocumentFragment(),
461
+ doc = fragment.createElement ? fragment : document;
462
+ doc.createElement('audio');
463
+ var div = doc.createElement('div');
464
+ fragment.appendChild(div);
465
+ div.innerHTML = audioTag.outerHTML;
466
+ return div.firstChild;
467
+ },
468
+ // **Cross-browser `<object>` / `<embed>` element selection**
469
+ getSwf: function(name) {
470
+ var swf = document[name] || window[name];
471
+ return swf.length > 1 ? swf[swf.length - 1] : swf;
472
+ }
473
+ },
474
+ // ## Event-handling
475
+ events: {
476
+ memoryLeaking: false,
477
+ listeners: [],
478
+ // **A simple cross-browser event handler abstraction**
479
+ addListener: function(element, eventName, func) {
480
+ // For modern browsers use the standard DOM-compliant `addEventListener`.
481
+ if (element.addEventListener) {
482
+ element.addEventListener(eventName, func, false);
483
+ // For older versions of Internet Explorer, use `attachEvent`.
484
+ // Also provide a fix for scoping `this` to the calling element and register each listener so the containing elements can be purged on page unload.
485
+ } else if (element.attachEvent) {
486
+ this.listeners.push(element);
487
+ if (!this.memoryLeaking) {
488
+ window.attachEvent('onunload', function() {
489
+ if(this.listeners) {
490
+ for (var i = 0, ii = this.listeners.length; i < ii; i++) {
491
+ container[audiojs].events.purge(this.listeners[i]);
492
+ }
493
+ }
494
+ });
495
+ this.memoryLeaking = true;
496
+ }
497
+ element.attachEvent('on' + eventName, function() {
498
+ func.call(element, window.event);
499
+ });
500
+ }
501
+ },
502
+
503
+ trackLoadProgress: function(audio) {
504
+ // If `preload` has been set to `none`, then we don't want to start loading the track yet.
505
+ if (!audio.settings.preload) return;
506
+
507
+ var readyTimer,
508
+ loadTimer,
509
+ audio = audio,
510
+ ios = (/(ipod|iphone|ipad)/i).test(navigator.userAgent);
511
+
512
+ // Use timers here rather than the official `progress` event, as Chrome has issues calling `progress` when loading mp3 files from cache.
513
+ readyTimer = setInterval(function() {
514
+ if (audio.element.readyState > -1) {
515
+ // iOS doesn't start preloading the mp3 until the user interacts manually, so this stops the loader being displayed prematurely.
516
+ if (!ios) audio.init.apply(audio);
517
+ }
518
+ if (audio.element.readyState > 1) {
519
+ if (audio.settings.autoplay) audio.play.apply(audio);
520
+ clearInterval(readyTimer);
521
+ // Once we have data, start tracking the load progress.
522
+ loadTimer = setInterval(function() {
523
+ audio.loadProgress.apply(audio);
524
+ if (audio.loadedPercent >= 1) clearInterval(loadTimer);
525
+ });
526
+ }
527
+ }, 10);
528
+ audio.readyTimer = readyTimer;
529
+ audio.loadTimer = loadTimer;
530
+ },
531
+
532
+ // **Douglas Crockford's IE6 memory leak fix**
533
+ // <http://javascript.crockford.com/memory/leak.html>
534
+ // This is used to release the memory leak created by the circular references created when fixing `this` scoping for IE. It is called on page unload.
535
+ purge: function(d) {
536
+ var a = d.attributes, i;
537
+ if (a) {
538
+ for (i = 0; i < a.length; i += 1) {
539
+ if (typeof d[a[i].name] === 'function') d[a[i].name] = null;
540
+ }
541
+ }
542
+ a = d.childNodes;
543
+ if (a) {
544
+ for (i = 0; i < a.length; i += 1) purge(d.childNodes[i]);
545
+ }
546
+ },
547
+
548
+ // **DOMready function**
549
+ // As seen here: <https://github.com/dperini/ContentLoaded/>.
550
+ ready: (function() { return function(fn) {
551
+ var win = window, done = false, top = true,
552
+ doc = win.document, root = doc.documentElement,
553
+ add = doc.addEventListener ? 'addEventListener' : 'attachEvent',
554
+ rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent',
555
+ pre = doc.addEventListener ? '' : 'on',
556
+ init = function(e) {
557
+ if (e.type == 'readystatechange' && doc.readyState != 'complete') return;
558
+ (e.type == 'load' ? win : doc)[rem](pre + e.type, init, false);
559
+ if (!done && (done = true)) fn.call(win, e.type || e);
560
+ },
561
+ poll = function() {
562
+ try { root.doScroll('left'); } catch(e) { setTimeout(poll, 50); return; }
563
+ init('poll');
564
+ };
565
+ if (doc.readyState == 'complete') fn.call(win, 'lazy');
566
+ else {
567
+ if (doc.createEventObject && root.doScroll) {
568
+ try { top = !win.frameElement; } catch(e) { }
569
+ if (top) poll();
570
+ }
571
+ doc[add](pre + 'DOMContentLoaded', init, false);
572
+ doc[add](pre + 'readystatechange', init, false);
573
+ win[add](pre + 'load', init, false);
574
+ }
575
+ }
576
+ })()
577
+
578
+ }
579
+ }
580
+
581
+ // ## The audiojs class
582
+ // We create one of these per `<audio>` and then push them into `audiojs['instances']`.
583
+ container[audiojsInstance] = function(element, settings) {
584
+ // Each audio instance returns an object which contains an API back into the `<audio>` element.
585
+ this.element = element;
586
+ this.wrapper = element.parentNode;
587
+ this.source = element.getElementsByTagName('source')[0] || element;
588
+ // First check the `<audio>` element directly for a src and if one is not found, look for a `<source>` element.
589
+ this.mp3 = (function(element) {
590
+ var source = element.getElementsByTagName('source')[0];
591
+ return element.getAttribute('src') || (source ? source.getAttribute('src') : null);
592
+ })(element);
593
+ this.settings = settings;
594
+ this.loadStartedCalled = false;
595
+ this.loadedPercent = 0;
596
+ this.duration = 1;
597
+ this.playing = false;
598
+ }
599
+
600
+ container[audiojsInstance].prototype = {
601
+ // API access events:
602
+ // Each of these do what they need do and then call the matching methods defined in the settings object.
603
+ updatePlayhead: function() {
604
+ var percent = this.element.currentTime / this.duration;
605
+ this.settings.updatePlayhead.apply(this, [percent]);
606
+ },
607
+ skipTo: function(percent) {
608
+ if (percent > this.loadedPercent) return;
609
+ this.element.currentTime = this.duration * percent;
610
+ this.updatePlayhead();
611
+ },
612
+ load: function(mp3) {
613
+ this.loadStartedCalled = false;
614
+ this.source.setAttribute('src', mp3);
615
+ // The now outdated `load()` method is required for Safari 4
616
+ this.element.load();
617
+ this.mp3 = mp3;
618
+ container[audiojs].events.trackLoadProgress(this);
619
+ },
620
+ loadError: function() {
621
+ this.settings.loadError.apply(this);
622
+ },
623
+ init: function() {
624
+ this.settings.init.apply(this);
625
+ },
626
+ loadStarted: function() {
627
+ // Wait until `element.duration` exists before setting up the audio player.
628
+ if (!this.element.duration) return false;
629
+
630
+ this.duration = this.element.duration;
631
+ this.updatePlayhead();
632
+ this.settings.loadStarted.apply(this);
633
+ },
634
+ loadProgress: function() {
635
+ if (this.element.buffered != null && this.element.buffered.length) {
636
+ // Ensure `loadStarted()` is only called once.
637
+ if (!this.loadStartedCalled) {
638
+ this.loadStartedCalled = this.loadStarted();
639
+ }
640
+ var durationLoaded = this.element.buffered.end(this.element.buffered.length - 1);
641
+ this.loadedPercent = durationLoaded / this.duration;
642
+
643
+ this.settings.loadProgress.apply(this, [this.loadedPercent]);
644
+ }
645
+ },
646
+ playPause: function() {
647
+ if (this.playing) this.pause();
648
+ else this.play();
649
+ },
650
+ play: function() {
651
+ var ios = (/(ipod|iphone|ipad)/i).test(navigator.userAgent);
652
+ // On iOS this interaction will trigger loading the mp3, so run `init()`.
653
+ if (ios && this.element.readyState == 0) this.init.apply(this);
654
+ // If the audio hasn't started preloading, then start it now.
655
+ // Then set `preload` to `true`, so that any tracks loaded in subsequently are loaded straight away.
656
+ if (!this.settings.preload) {
657
+ this.settings.preload = true;
658
+ this.element.setAttribute('preload', 'auto');
659
+ container[audiojs].events.trackLoadProgress(this);
660
+ }
661
+ this.playing = true;
662
+ this.element.play();
663
+ this.settings.play.apply(this);
664
+ },
665
+ pause: function() {
666
+ this.playing = false;
667
+ this.element.pause();
668
+ this.settings.pause.apply(this);
669
+ },
670
+ setVolume: function(v) {
671
+ this.element.volume = v;
672
+ },
673
+ trackEnded: function(e) {
674
+ this.skipTo.apply(this, [0]);
675
+ if (!this.settings.loop) this.pause.apply(this);
676
+ this.settings.trackEnded.apply(this);
677
+ }
678
+ }
679
+
680
+ // **getElementsByClassName**
681
+ // Having to rely on `getElementsByTagName` is pretty inflexible internally, so a modified version of Dustin Diaz's `getElementsByClassName` has been included.
682
+ // This version cleans things up and prefers the native DOM method if it's available.
683
+ var getByClass = function(searchClass, node) {
684
+ var matches = [];
685
+ node = node || document;
686
+
687
+ if (node.getElementsByClassName) {
688
+ matches = node.getElementsByClassName(searchClass);
689
+ } else {
690
+ var i, l,
691
+ els = node.getElementsByTagName("*"),
692
+ pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
693
+
694
+ for (i = 0, l = els.length; i < l; i++) {
695
+ if (pattern.test(els[i].className)) {
696
+ matches.push(els[i]);
697
+ }
698
+ }
699
+ }
700
+ return matches.length > 1 ? matches : matches[0];
701
+ };
702
+ // The global variable names are passed in here and can be changed if they conflict with anything else.
703
+ })('audiojs', 'audiojsInstance', this);
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: audiojs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Alif Rachmawadi
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-10 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Audio.js + Rails Asset Pipeline
15
+ email:
16
+ - subosito@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - LICENSE
24
+ - README.md
25
+ - Rakefile
26
+ - audiojs.gemspec
27
+ - lib/audiojs.rb
28
+ - lib/audiojs/version.rb
29
+ - vendor/assets/javascripts/audiojs.js
30
+ - vendor/assets/javascripts/audiojs.swf
31
+ - vendor/assets/javascripts/player-graphics.gif
32
+ homepage: ''
33
+ licenses: []
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ segments:
45
+ - 0
46
+ hash: 3059527199559388368
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ segments:
54
+ - 0
55
+ hash: 3059527199559388368
56
+ requirements: []
57
+ rubyforge_project:
58
+ rubygems_version: 1.8.11
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: audio.js is a drop-in javascript library that allows HTML5's <audio> tag
62
+ to be used anywhere.
63
+ test_files: []