rails-audiojs 0.0.1

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