mediaelement_rails 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/.gitignore +8 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +97 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +29 -0
  6. data/Rakefile +39 -0
  7. data/app/assets/flash/mediaelement_rails/flashmediaelement.swf +0 -0
  8. data/app/assets/images/mediaelement_rails/background.png +0 -0
  9. data/app/assets/images/mediaelement_rails/bigplay.png +0 -0
  10. data/app/assets/images/mediaelement_rails/controls-ted.png +0 -0
  11. data/app/assets/images/mediaelement_rails/controls-wmp-bg.png +0 -0
  12. data/app/assets/images/mediaelement_rails/controls-wmp.png +0 -0
  13. data/app/assets/images/mediaelement_rails/controls.png +0 -0
  14. data/app/assets/images/mediaelement_rails/loading.gif +0 -0
  15. data/app/assets/javascripts/mediaelement_rails/index.js +3 -0
  16. data/app/assets/javascripts/mediaelement_rails/mediaelement.js +943 -0
  17. data/app/assets/javascripts/mediaelement_rails/mediaelementplayer.js +1703 -0
  18. data/app/assets/javascripts/mediaelement_rails/rails.js.erb +5 -0
  19. data/app/assets/silverlight/mediaelement_rails/silverlightmediaelement.xap +0 -0
  20. data/app/assets/stylesheets/mediaelement_rails/index.css +1 -0
  21. data/app/assets/stylesheets/mediaelement_rails/mediaelementplayer.css.erb +571 -0
  22. data/app/assets/stylesheets/mediaelement_rails/mejs-skins.css.erb +283 -0
  23. data/app/controllers/mediaelement_rails/application_controller.rb +4 -0
  24. data/app/helpers/mediaelement_rails/application_helper.rb +4 -0
  25. data/app/views/layouts/application.html.erb +14 -0
  26. data/app/views/layouts/mediaelement_rails/application.html.erb +14 -0
  27. data/config/routes.rb +2 -0
  28. data/lib/generators/mediaelement_update/USAGE +7 -0
  29. data/lib/generators/mediaelement_update/mediaelement_update_generator.rb +46 -0
  30. data/lib/mediaelement_rails/engine.rb +5 -0
  31. data/lib/mediaelement_rails/version.rb +3 -0
  32. data/lib/mediaelement_rails.rb +4 -0
  33. data/lib/tasks/mediaelement_rails_tasks.rake +4 -0
  34. data/mediaelement_rails.gemspec +20 -0
  35. data/script/rails +6 -0
  36. data/test/dummy/Rakefile +7 -0
  37. data/test/dummy/app/assets/javascripts/application.js +9 -0
  38. data/test/dummy/app/assets/stylesheets/application.css +7 -0
  39. data/test/dummy/app/controllers/application_controller.rb +3 -0
  40. data/test/dummy/app/helpers/application_helper.rb +2 -0
  41. data/test/dummy/app/mailers/.gitkeep +0 -0
  42. data/test/dummy/app/models/.gitkeep +0 -0
  43. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  44. data/test/dummy/config/application.rb +45 -0
  45. data/test/dummy/config/boot.rb +10 -0
  46. data/test/dummy/config/database.yml +25 -0
  47. data/test/dummy/config/environment.rb +5 -0
  48. data/test/dummy/config/environments/development.rb +24 -0
  49. data/test/dummy/config/environments/production.rb +51 -0
  50. data/test/dummy/config/environments/test.rb +34 -0
  51. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  52. data/test/dummy/config/initializers/inflections.rb +10 -0
  53. data/test/dummy/config/initializers/mime_types.rb +5 -0
  54. data/test/dummy/config/initializers/secret_token.rb +7 -0
  55. data/test/dummy/config/initializers/session_store.rb +8 -0
  56. data/test/dummy/config/initializers/wrap_parameters.rb +12 -0
  57. data/test/dummy/config/locales/en.yml +5 -0
  58. data/test/dummy/config/routes.rb +4 -0
  59. data/test/dummy/config.ru +4 -0
  60. data/test/dummy/lib/assets/.gitkeep +0 -0
  61. data/test/dummy/log/.gitkeep +0 -0
  62. data/test/dummy/public/404.html +26 -0
  63. data/test/dummy/public/422.html +26 -0
  64. data/test/dummy/public/500.html +26 -0
  65. data/test/dummy/public/favicon.ico +0 -0
  66. data/test/dummy/script/rails +6 -0
  67. data/test/integration/navigation_test.rb +10 -0
  68. data/test/mediaelement_rails_test.rb +7 -0
  69. data/test/test_helper.rb +10 -0
  70. metadata +170 -0
@@ -0,0 +1,943 @@
1
+ /*!
2
+ * MediaElement.js
3
+ * HTML5 <video> and <audio> shim and player
4
+ * http://mediaelementjs.com/
5
+ *
6
+ * Creates a JavaScript object that mimics HTML5 MediaElement API
7
+ * for browsers that don't understand HTML5 or can't play the provided codec
8
+ * Can play MP4 (H.264), Ogg, WebM, FLV, WMV, WMA, ACC, and MP3
9
+ *
10
+ * Copyright 2010-2011, John Dyer (http://j.hn)
11
+ * Dual licensed under the MIT or GPL Version 2 licenses.
12
+ *
13
+ */
14
+ // Namespace
15
+ var mejs = mejs || {};
16
+
17
+ // version number
18
+ mejs.version = '2.1.9';
19
+
20
+ // player number (for missing, same id attr)
21
+ mejs.meIndex = 0;
22
+
23
+ // media types accepted by plugins
24
+ mejs.plugins = {
25
+ silverlight: [
26
+ {version: [3,0], types: ['video/mp4','video/m4v','video/mov','video/wmv','audio/wma','audio/m4a','audio/mp3','audio/wav','audio/mpeg']}
27
+ ],
28
+ flash: [
29
+ {version: [9,0,124], types: ['video/mp4','video/m4v','video/mov','video/flv','video/x-flv','audio/flv','audio/x-flv','audio/mp3','audio/m4a','audio/mpeg']}
30
+ //,{version: [11,0], types: ['video/webm']} // for future reference
31
+ ]
32
+ };
33
+
34
+ /*
35
+ Utility methods
36
+ */
37
+ mejs.Utility = {
38
+ encodeUrl: function(url) {
39
+ return encodeURIComponent(url); //.replace(/\?/gi,'%3F').replace(/=/gi,'%3D').replace(/&/gi,'%26');
40
+ },
41
+ escapeHTML: function(s) {
42
+ return s.toString().split('&').join('&amp;').split('<').join('&lt;').split('"').join('&quot;');
43
+ },
44
+ absolutizeUrl: function(url) {
45
+ var el = document.createElement('div');
46
+ el.innerHTML = '<a href="' + this.escapeHTML(url) + '">x</a>';
47
+ return el.firstChild.href;
48
+ },
49
+ getScriptPath: function(scriptNames) {
50
+ var
51
+ i = 0,
52
+ j,
53
+ path = '',
54
+ name = '',
55
+ script,
56
+ scripts = document.getElementsByTagName('script');
57
+
58
+ for (; i < scripts.length; i++) {
59
+ script = scripts[i].src;
60
+ for (j = 0; j < scriptNames.length; j++) {
61
+ name = scriptNames[j];
62
+ if (script.indexOf(name) > -1) {
63
+ path = script.substring(0, script.indexOf(name));
64
+ break;
65
+ }
66
+ }
67
+ if (path !== '') {
68
+ break;
69
+ }
70
+ }
71
+ return path;
72
+ },
73
+ secondsToTimeCode: function(seconds,forceHours) {
74
+ seconds = Math.round(seconds);
75
+ var hours,
76
+ minutes = Math.floor(seconds / 60);
77
+ if (minutes >= 60) {
78
+ hours = Math.floor(minutes / 60);
79
+ minutes = minutes % 60;
80
+ }
81
+ hours = hours === undefined ? "00" : (hours >= 10) ? hours : "0" + hours;
82
+ minutes = (minutes >= 10) ? minutes : "0" + minutes;
83
+ seconds = Math.floor(seconds % 60);
84
+ seconds = (seconds >= 10) ? seconds : "0" + seconds;
85
+ return ((hours > 0 || forceHours === true) ? hours + ":" :'') + minutes + ":" + seconds;
86
+ },
87
+ timeCodeToSeconds: function(timecode){
88
+ var tab = timecode.split(':');
89
+ return tab[0]*60*60 + tab[1]*60 + parseFloat(tab[2].replace(',','.'));
90
+ }
91
+ };
92
+
93
+
94
+ // Core detector, plugins are added below
95
+ mejs.PluginDetector = {
96
+
97
+ // main public function to test a plug version number PluginDetector.hasPluginVersion('flash',[9,0,125]);
98
+ hasPluginVersion: function(plugin, v) {
99
+ var pv = this.plugins[plugin];
100
+ v[1] = v[1] || 0;
101
+ v[2] = v[2] || 0;
102
+ return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false;
103
+ },
104
+
105
+ // cached values
106
+ nav: window.navigator,
107
+ ua: window.navigator.userAgent.toLowerCase(),
108
+
109
+ // stored version numbers
110
+ plugins: [],
111
+
112
+ // runs detectPlugin() and stores the version number
113
+ addPlugin: function(p, pluginName, mimeType, activeX, axDetect) {
114
+ this.plugins[p] = this.detectPlugin(pluginName, mimeType, activeX, axDetect);
115
+ },
116
+
117
+ // get the version number from the mimetype (all but IE) or ActiveX (IE)
118
+ detectPlugin: function(pluginName, mimeType, activeX, axDetect) {
119
+
120
+ var version = [0,0,0],
121
+ description,
122
+ i,
123
+ ax;
124
+
125
+ // Firefox, Webkit, Opera
126
+ if (typeof(this.nav.plugins) != 'undefined' && typeof this.nav.plugins[pluginName] == 'object') {
127
+ description = this.nav.plugins[pluginName].description;
128
+ if (description && !(typeof this.nav.mimeTypes != 'undefined' && this.nav.mimeTypes[mimeType] && !this.nav.mimeTypes[mimeType].enabledPlugin)) {
129
+ version = description.replace(pluginName, '').replace(/^\s+/,'').replace(/\sr/gi,'.').split('.');
130
+ for (i=0; i<version.length; i++) {
131
+ version[i] = parseInt(version[i].match(/\d+/), 10);
132
+ }
133
+ }
134
+ // Internet Explorer / ActiveX
135
+ } else if (typeof(window.ActiveXObject) != 'undefined') {
136
+ try {
137
+ ax = new ActiveXObject(activeX);
138
+ if (ax) {
139
+ version = axDetect(ax);
140
+ }
141
+ }
142
+ catch (e) { }
143
+ }
144
+ return version;
145
+ }
146
+ };
147
+
148
+ // Add Flash detection
149
+ mejs.PluginDetector.addPlugin('flash','Shockwave Flash','application/x-shockwave-flash','ShockwaveFlash.ShockwaveFlash', function(ax) {
150
+ // adapted from SWFObject
151
+ var version = [],
152
+ d = ax.GetVariable("$version");
153
+ if (d) {
154
+ d = d.split(" ")[1].split(",");
155
+ version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
156
+ }
157
+ return version;
158
+ });
159
+
160
+ // Add Silverlight detection
161
+ mejs.PluginDetector.addPlugin('silverlight','Silverlight Plug-In','application/x-silverlight-2','AgControl.AgControl', function (ax) {
162
+ // Silverlight cannot report its version number to IE
163
+ // but it does have a isVersionSupported function, so we have to loop through it to get a version number.
164
+ // adapted from http://www.silverlightversion.com/
165
+ var v = [0,0,0,0],
166
+ loopMatch = function(ax, v, i, n) {
167
+ while(ax.isVersionSupported(v[0]+ "."+ v[1] + "." + v[2] + "." + v[3])){
168
+ v[i]+=n;
169
+ }
170
+ v[i] -= n;
171
+ };
172
+ loopMatch(ax, v, 0, 1);
173
+ loopMatch(ax, v, 1, 1);
174
+ loopMatch(ax, v, 2, 10000); // the third place in the version number is usually 5 digits (4.0.xxxxx)
175
+ loopMatch(ax, v, 2, 1000);
176
+ loopMatch(ax, v, 2, 100);
177
+ loopMatch(ax, v, 2, 10);
178
+ loopMatch(ax, v, 2, 1);
179
+ loopMatch(ax, v, 3, 1);
180
+
181
+ return v;
182
+ });
183
+ // add adobe acrobat
184
+ /*
185
+ PluginDetector.addPlugin('acrobat','Adobe Acrobat','application/pdf','AcroPDF.PDF', function (ax) {
186
+ var version = [],
187
+ d = ax.GetVersions().split(',')[0].split('=')[1].split('.');
188
+
189
+ if (d) {
190
+ version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
191
+ }
192
+ return version;
193
+ });
194
+ */
195
+ // necessary detection (fixes for <IE9)
196
+ mejs.MediaFeatures = {
197
+ init: function() {
198
+ var
199
+ nav = mejs.PluginDetector.nav,
200
+ ua = mejs.PluginDetector.ua.toLowerCase(),
201
+ i,
202
+ v,
203
+ html5Elements = ['source','track','audio','video'];
204
+
205
+ // detect browsers (only the ones that have some kind of quirk we need to work around)
206
+ this.isiPad = (ua.match(/ipad/i) !== null);
207
+ this.isiPhone = (ua.match(/iphone/i) !== null);
208
+ this.isAndroid = (ua.match(/android/i) !== null);
209
+ this.isBustedAndroid = (ua.match(/android 2\.[12]/) !== null);
210
+ this.isIE = (nav.appName.toLowerCase().indexOf("microsoft") != -1);
211
+ this.isChrome = (ua.match(/chrome/gi) !== null);
212
+ this.isFirefox = (ua.match(/firefox/gi) !== null);
213
+
214
+ // create HTML5 media elements for IE before 9, get a <video> element for fullscreen detection
215
+ for (i=0; i<html5Elements.length; i++) {
216
+ v = document.createElement(html5Elements[i]);
217
+ }
218
+
219
+ // detect native JavaScript fullscreen (Safari only, Chrome fails)
220
+ this.hasNativeFullScreen = (typeof v.webkitRequestFullScreen !== 'undefined');
221
+ if (this.isChrome) {
222
+ this.hasNativeFullScreen = false;
223
+ }
224
+ // OS X 10.5 can't do this even if it says it can :(
225
+ if (this.hasNativeFullScreen && ua.match(/mac os x 10_5/i)) {
226
+ this.hasNativeFullScreen = false;
227
+ }
228
+ }
229
+ };
230
+ mejs.MediaFeatures.init();
231
+
232
+
233
+ /*
234
+ extension methods to <video> or <audio> object to bring it into parity with PluginMediaElement (see below)
235
+ */
236
+ mejs.HtmlMediaElement = {
237
+ pluginType: 'native',
238
+ isFullScreen: false,
239
+
240
+ setCurrentTime: function (time) {
241
+ this.currentTime = time;
242
+ },
243
+
244
+ setMuted: function (muted) {
245
+ this.muted = muted;
246
+ },
247
+
248
+ setVolume: function (volume) {
249
+ this.volume = volume;
250
+ },
251
+
252
+ // for parity with the plugin versions
253
+ stop: function () {
254
+ this.pause();
255
+ },
256
+
257
+ // This can be a url string
258
+ // or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}]
259
+ setSrc: function (url) {
260
+ if (typeof url == 'string') {
261
+ this.src = url;
262
+ } else {
263
+ var i, media;
264
+
265
+ for (i=0; i<url.length; i++) {
266
+ media = url[i];
267
+ if (this.canPlayType(media.type)) {
268
+ this.src = media.src;
269
+ }
270
+ }
271
+ }
272
+ },
273
+
274
+ setVideoSize: function (width, height) {
275
+ this.width = width;
276
+ this.height = height;
277
+ }
278
+ };
279
+
280
+ /*
281
+ Mimics the <video/audio> element by calling Flash's External Interface or Silverlights [ScriptableMember]
282
+ */
283
+ mejs.PluginMediaElement = function (pluginid, pluginType, mediaUrl) {
284
+ this.id = pluginid;
285
+ this.pluginType = pluginType;
286
+ this.src = mediaUrl;
287
+ this.events = {};
288
+ };
289
+
290
+ // JavaScript values and ExternalInterface methods that match HTML5 video properties methods
291
+ // http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/fl/video/FLVPlayback.html
292
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html
293
+ mejs.PluginMediaElement.prototype = {
294
+
295
+ // special
296
+ pluginElement: null,
297
+ pluginType: '',
298
+ isFullScreen: false,
299
+
300
+ // not implemented :(
301
+ playbackRate: -1,
302
+ defaultPlaybackRate: -1,
303
+ seekable: [],
304
+ played: [],
305
+
306
+ // HTML5 read-only properties
307
+ paused: true,
308
+ ended: false,
309
+ seeking: false,
310
+ duration: 0,
311
+ error: null,
312
+
313
+ // HTML5 get/set properties, but only set (updated by event handlers)
314
+ muted: false,
315
+ volume: 1,
316
+ currentTime: 0,
317
+
318
+ // HTML5 methods
319
+ play: function () {
320
+ if (this.pluginApi != null) {
321
+ this.pluginApi.playMedia();
322
+ this.paused = false;
323
+ }
324
+ },
325
+ load: function () {
326
+ if (this.pluginApi != null) {
327
+ this.pluginApi.loadMedia();
328
+ this.paused = false;
329
+ }
330
+ },
331
+ pause: function () {
332
+ if (this.pluginApi != null) {
333
+ this.pluginApi.pauseMedia();
334
+ this.paused = true;
335
+ }
336
+ },
337
+ stop: function () {
338
+ if (this.pluginApi != null) {
339
+ this.pluginApi.stopMedia();
340
+ this.paused = true;
341
+ }
342
+ },
343
+ canPlayType: function(type) {
344
+ var i,
345
+ j,
346
+ pluginInfo,
347
+ pluginVersions = mejs.plugins[this.pluginType];
348
+
349
+ for (i=0; i<pluginVersions.length; i++) {
350
+ pluginInfo = pluginVersions[i];
351
+
352
+ // test if user has the correct plugin version
353
+ if (mejs.PluginDetector.hasPluginVersion(this.pluginType, pluginInfo.version)) {
354
+
355
+ // test for plugin playback types
356
+ for (j=0; j<pluginInfo.types.length; j++) {
357
+ // find plugin that can play the type
358
+ if (type == pluginInfo.types[j]) {
359
+ return true;
360
+ }
361
+ }
362
+ }
363
+ }
364
+
365
+ return false;
366
+ },
367
+
368
+ // custom methods since not all JavaScript implementations support get/set
369
+
370
+ // This can be a url string
371
+ // or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}]
372
+ setSrc: function (url) {
373
+ if (typeof url == 'string') {
374
+ this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(url));
375
+ this.src = mejs.Utility.absolutizeUrl(url);
376
+ } else {
377
+ var i, media;
378
+
379
+ for (i=0; i<url.length; i++) {
380
+ media = url[i];
381
+ if (this.canPlayType(media.type)) {
382
+ this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(media.src));
383
+ this.src = mejs.Utility.absolutizeUrl(url);
384
+ }
385
+ }
386
+ }
387
+
388
+ },
389
+ setCurrentTime: function (time) {
390
+ if (this.pluginApi != null) {
391
+ this.pluginApi.setCurrentTime(time);
392
+ this.currentTime = time;
393
+ }
394
+ },
395
+ setVolume: function (volume) {
396
+ if (this.pluginApi != null) {
397
+ this.pluginApi.setVolume(volume);
398
+ this.volume = volume;
399
+ }
400
+ },
401
+ setMuted: function (muted) {
402
+ if (this.pluginApi != null) {
403
+ this.pluginApi.setMuted(muted);
404
+ this.muted = muted;
405
+ }
406
+ },
407
+
408
+ // additional non-HTML5 methods
409
+ setVideoSize: function (width, height) {
410
+ if ( this.pluginElement.style) {
411
+ this.pluginElement.style.width = width + 'px';
412
+ this.pluginElement.style.height = height + 'px';
413
+ }
414
+ if (this.pluginApi != null) {
415
+ this.pluginApi.setVideoSize(width, height);
416
+ }
417
+ },
418
+
419
+ setFullscreen: function (fullscreen) {
420
+ if (this.pluginApi != null) {
421
+ this.pluginApi.setFullscreen(fullscreen);
422
+ }
423
+ },
424
+
425
+ // start: fake events
426
+ addEventListener: function (eventName, callback, bubble) {
427
+ this.events[eventName] = this.events[eventName] || [];
428
+ this.events[eventName].push(callback);
429
+ },
430
+ removeEventListener: function (eventName, callback) {
431
+ if (!eventName) { this.events = {}; return true; }
432
+ var callbacks = this.events[eventName];
433
+ if (!callbacks) return true;
434
+ if (!callback) { this.events[eventName] = []; return true; }
435
+ for (i = 0; i < callbacks.length; i++) {
436
+ if (callbacks[i] === callback) {
437
+ this.events[eventName].splice(i, 1);
438
+ return true;
439
+ }
440
+ }
441
+ return false;
442
+ },
443
+ dispatchEvent: function (eventName) {
444
+ var i,
445
+ args,
446
+ callbacks = this.events[eventName];
447
+
448
+ if (callbacks) {
449
+ args = Array.prototype.slice.call(arguments, 1);
450
+ for (i = 0; i < callbacks.length; i++) {
451
+ callbacks[i].apply(null, args);
452
+ }
453
+ }
454
+ }
455
+ // end: fake events
456
+ };
457
+
458
+
459
+ // Handles calls from Flash/Silverlight and reports them as native <video/audio> events and properties
460
+ mejs.MediaPluginBridge = {
461
+
462
+ pluginMediaElements:{},
463
+ htmlMediaElements:{},
464
+
465
+ registerPluginElement: function (id, pluginMediaElement, htmlMediaElement) {
466
+ this.pluginMediaElements[id] = pluginMediaElement;
467
+ this.htmlMediaElements[id] = htmlMediaElement;
468
+ },
469
+
470
+ // when Flash/Silverlight is ready, it calls out to this method
471
+ initPlugin: function (id) {
472
+
473
+ var pluginMediaElement = this.pluginMediaElements[id],
474
+ htmlMediaElement = this.htmlMediaElements[id];
475
+
476
+ // find the javascript bridge
477
+ switch (pluginMediaElement.pluginType) {
478
+ case "flash":
479
+ pluginMediaElement.pluginElement = pluginMediaElement.pluginApi = document.getElementById(id);
480
+ break;
481
+ case "silverlight":
482
+ pluginMediaElement.pluginElement = document.getElementById(pluginMediaElement.id);
483
+ pluginMediaElement.pluginApi = pluginMediaElement.pluginElement.Content.MediaElementJS;
484
+ break;
485
+ }
486
+
487
+ if (pluginMediaElement.pluginApi != null && pluginMediaElement.success) {
488
+ pluginMediaElement.success(pluginMediaElement, htmlMediaElement);
489
+ }
490
+ },
491
+
492
+ // receives events from Flash/Silverlight and sends them out as HTML5 media events
493
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html
494
+ fireEvent: function (id, eventName, values) {
495
+
496
+ var
497
+ e,
498
+ i,
499
+ bufferedTime,
500
+ pluginMediaElement = this.pluginMediaElements[id];
501
+
502
+ pluginMediaElement.ended = false;
503
+ pluginMediaElement.paused = true;
504
+
505
+ // fake event object to mimic real HTML media event.
506
+ e = {
507
+ type: eventName,
508
+ target: pluginMediaElement
509
+ };
510
+
511
+ // attach all values to element and event object
512
+ for (i in values) {
513
+ pluginMediaElement[i] = values[i];
514
+ e[i] = values[i];
515
+ }
516
+
517
+ // fake the newer W3C buffered TimeRange (loaded and total have been removed)
518
+ bufferedTime = values.bufferedTime || 0;
519
+
520
+ e.target.buffered = e.buffered = {
521
+ start: function(index) {
522
+ return 0;
523
+ },
524
+ end: function (index) {
525
+ return bufferedTime;
526
+ },
527
+ length: 1
528
+ };
529
+
530
+ pluginMediaElement.dispatchEvent(e.type, e);
531
+ }
532
+ };
533
+
534
+ /*
535
+ Default options
536
+ */
537
+ mejs.MediaElementDefaults = {
538
+ // allows testing on HTML5, flash, silverlight
539
+ // auto: attempts to detect what the browser can do
540
+ // native: forces HTML5 playback
541
+ // shim: disallows HTML5, will attempt either Flash or Silverlight
542
+ // none: forces fallback view
543
+ mode: 'auto',
544
+ // remove or reorder to change plugin priority and availability
545
+ plugins: ['flash','silverlight'],
546
+ // shows debug errors on screen
547
+ enablePluginDebug: false,
548
+ // overrides the type specified, useful for dynamic instantiation
549
+ type: '',
550
+ // path to Flash and Silverlight plugins
551
+ pluginPath: mejs.Utility.getScriptPath(['mediaelement.js','mediaelement.min.js','mediaelement-and-player.js','mediaelement-and-player.min.js']),
552
+ // name of flash file
553
+ flashName: 'flashmediaelement.swf',
554
+ // turns on the smoothing filter in Flash
555
+ enablePluginSmoothing: false,
556
+ // name of silverlight file
557
+ silverlightName: 'silverlightmediaelement.xap',
558
+ // default if the <video width> is not specified
559
+ defaultVideoWidth: 480,
560
+ // default if the <video height> is not specified
561
+ defaultVideoHeight: 270,
562
+ // overrides <video width>
563
+ pluginWidth: -1,
564
+ // overrides <video height>
565
+ pluginHeight: -1,
566
+ // rate in milliseconds for Flash and Silverlight to fire the timeupdate event
567
+ // larger number is less accurate, but less strain on plugin->JavaScript bridge
568
+ timerRate: 250,
569
+ success: function () { },
570
+ error: function () { }
571
+ };
572
+
573
+ /*
574
+ Determines if a browser supports the <video> or <audio> element
575
+ and returns either the native element or a Flash/Silverlight version that
576
+ mimics HTML5 MediaElement
577
+ */
578
+ mejs.MediaElement = function (el, o) {
579
+ return mejs.HtmlMediaElementShim.create(el,o);
580
+ };
581
+
582
+ mejs.HtmlMediaElementShim = {
583
+
584
+ create: function(el, o) {
585
+ var
586
+ options = mejs.MediaElementDefaults,
587
+ htmlMediaElement = (typeof(el) == 'string') ? document.getElementById(el) : el,
588
+ isVideo = (htmlMediaElement.tagName.toLowerCase() == 'video'),
589
+ supportsMediaTag = (typeof(htmlMediaElement.canPlayType) != 'undefined'),
590
+ playback = {method:'', url:''},
591
+ poster = htmlMediaElement.getAttribute('poster'),
592
+ autoplay = htmlMediaElement.getAttribute('autoplay'),
593
+ preload = htmlMediaElement.getAttribute('preload'),
594
+ controls = htmlMediaElement.getAttribute('controls'),
595
+ prop;
596
+
597
+ // extend options
598
+ for (prop in o) {
599
+ options[prop] = o[prop];
600
+ }
601
+
602
+ // check for real poster
603
+ poster = (typeof poster == 'undefined' || poster === null) ? '' : poster;
604
+ preload = (typeof preload == 'undefined' || preload === null || preload === 'false') ? 'none' : preload;
605
+ autoplay = !(typeof autoplay == 'undefined' || autoplay === null || autoplay === 'false');
606
+ controls = !(typeof controls == 'undefined' || controls === null || controls === 'false');
607
+
608
+ // test for HTML5 and plugin capabilities
609
+ playback = this.determinePlayback(htmlMediaElement, options, isVideo, supportsMediaTag);
610
+
611
+ if (playback.method == 'native') {
612
+ // second fix for android
613
+ if (mejs.MediaFeatures.isBustedAndroid) {
614
+ htmlMediaElement.src = playback.url;
615
+ htmlMediaElement.addEventListener('click', function() {
616
+ htmlMediaElement.play();
617
+ }, true);
618
+ }
619
+
620
+ // add methods to native HTMLMediaElement
621
+ return this.updateNative( htmlMediaElement, options, autoplay, preload, playback);
622
+ } else if (playback.method !== '') {
623
+ // create plugin to mimic HTMLMediaElement
624
+ return this.createPlugin( htmlMediaElement, options, isVideo, playback.method, (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : '', poster, autoplay, preload, controls);
625
+ } else {
626
+ // boo, no HTML5, no Flash, no Silverlight.
627
+ this.createErrorMessage( htmlMediaElement, options, (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : '', poster );
628
+ }
629
+ },
630
+
631
+ determinePlayback: function(htmlMediaElement, options, isVideo, supportsMediaTag) {
632
+ var
633
+ mediaFiles = [],
634
+ i,
635
+ j,
636
+ k,
637
+ l,
638
+ n,
639
+ type,
640
+ result = { method: '', url: ''},
641
+ src = htmlMediaElement.getAttribute('src'),
642
+ pluginName,
643
+ pluginVersions,
644
+ pluginInfo;
645
+
646
+ // clean up src attr
647
+ if (src == 'undefined' || src == '' || src === null)
648
+ src = null;
649
+
650
+ // STEP 1: Get URL and type from <video src> or <source src>
651
+
652
+ // supplied type overrides all HTML
653
+ if (typeof (options.type) != 'undefined' && options.type !== '') {
654
+ mediaFiles.push({type:options.type, url:src});
655
+
656
+ // test for src attribute first
657
+ } else if (src !== null) {
658
+ type = this.checkType(src, htmlMediaElement.getAttribute('type'), isVideo);
659
+ mediaFiles.push({type:type, url:src});
660
+
661
+ // then test for <source> elements
662
+ } else {
663
+ // test <source> types to see if they are usable
664
+ for (i = 0; i < htmlMediaElement.childNodes.length; i++) {
665
+ n = htmlMediaElement.childNodes[i];
666
+ if (n.nodeType == 1 && n.tagName.toLowerCase() == 'source') {
667
+ src = n.getAttribute('src');
668
+ type = this.checkType(src, n.getAttribute('type'), isVideo);
669
+ mediaFiles.push({type:type, url:src});
670
+ }
671
+ }
672
+ }
673
+
674
+ // STEP 2: Test for playback method
675
+
676
+ // special case for Android which sadly doesn't implement the canPlayType function (always returns '')
677
+ if (mejs.MediaFeatures.isBustedAndroid) {
678
+ htmlMediaElement.canPlayType = function(type) {
679
+ return (type.match(/video\/(mp4|m4v)/gi) !== null) ? 'maybe' : '';
680
+ };
681
+ }
682
+
683
+
684
+ // test for native playback first
685
+ if (supportsMediaTag && (options.mode === 'auto' || options.mode === 'native')) {
686
+ for (i=0; i<mediaFiles.length; i++) {
687
+ // normal check
688
+ if (htmlMediaElement.canPlayType(mediaFiles[i].type).replace(/no/, '') !== ''
689
+ // special case for Mac/Safari 5.0.3 which answers '' to canPlayType('audio/mp3') but 'maybe' to canPlayType('audio/mpeg')
690
+ || htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/mp3/,'mpeg')).replace(/no/, '') !== '') {
691
+ result.method = 'native';
692
+ result.url = mediaFiles[i].url;
693
+ return result;
694
+ }
695
+ }
696
+ }
697
+
698
+ // if native playback didn't work, then test plugins
699
+ if (options.mode === 'auto' || options.mode === 'shim') {
700
+ for (i=0; i<mediaFiles.length; i++) {
701
+ type = mediaFiles[i].type;
702
+
703
+ // test all plugins in order of preference [silverlight, flash]
704
+ for (j=0; j<options.plugins.length; j++) {
705
+
706
+ pluginName = options.plugins[j];
707
+
708
+ // test version of plugin (for future features)
709
+ pluginVersions = mejs.plugins[pluginName];
710
+ for (k=0; k<pluginVersions.length; k++) {
711
+ pluginInfo = pluginVersions[k];
712
+
713
+ // test if user has the correct plugin version
714
+ if (mejs.PluginDetector.hasPluginVersion(pluginName, pluginInfo.version)) {
715
+
716
+ // test for plugin playback types
717
+ for (l=0; l<pluginInfo.types.length; l++) {
718
+ // find plugin that can play the type
719
+ if (type == pluginInfo.types[l]) {
720
+ result.method = pluginName;
721
+ result.url = mediaFiles[i].url;
722
+ return result;
723
+ }
724
+ }
725
+ }
726
+ }
727
+ }
728
+ }
729
+ }
730
+
731
+ // what if there's nothing to play? just grab the first available
732
+ if (result.method === '') {
733
+ result.url = mediaFiles[0].url;
734
+ }
735
+
736
+ return result;
737
+ },
738
+
739
+ checkType: function(url, type, isVideo) {
740
+ var ext;
741
+
742
+ // if no type is supplied, fake it with the extension
743
+ if (url && !type) {
744
+ ext = url.substring(url.lastIndexOf('.') + 1);
745
+ return ((isVideo) ? 'video' : 'audio') + '/' + ext;
746
+ } else {
747
+ // only return the mime part of the type in case the attribute contains the codec
748
+ // see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#the-source-element
749
+ // `video/mp4; codecs="avc1.42E01E, mp4a.40.2"` becomes `video/mp4`
750
+
751
+ if (type && ~type.indexOf(';')) {
752
+ return type.substr(0, type.indexOf(';'));
753
+ } else {
754
+ return type;
755
+ }
756
+ }
757
+ },
758
+
759
+ createErrorMessage: function(htmlMediaElement, options, downloadUrl, poster) {
760
+ var errorContainer = document.createElement('div');
761
+ errorContainer.className = 'me-cannotplay';
762
+
763
+ try {
764
+ errorContainer.style.width = htmlMediaElement.width + 'px';
765
+ errorContainer.style.height = htmlMediaElement.height + 'px';
766
+ } catch (e) {}
767
+
768
+ errorContainer.innerHTML = (poster !== '') ?
769
+ '<a href="' + downloadUrl + '"><img src="' + poster + '" /></a>' :
770
+ '<a href="' + downloadUrl + '"><span>Download File</span></a>';
771
+
772
+ htmlMediaElement.parentNode.insertBefore(errorContainer, htmlMediaElement);
773
+ htmlMediaElement.style.display = 'none';
774
+
775
+ options.error(htmlMediaElement);
776
+ },
777
+
778
+ createPlugin:function(htmlMediaElement, options, isVideo, pluginType, mediaUrl, poster, autoplay, preload, controls) {
779
+ var width = 1,
780
+ height = 1,
781
+ pluginid = 'me_' + pluginType + '_' + (mejs.meIndex++),
782
+ pluginMediaElement = new mejs.PluginMediaElement(pluginid, pluginType, mediaUrl),
783
+ container = document.createElement('div'),
784
+ specialIEContainer,
785
+ node,
786
+ initVars;
787
+
788
+ // check for placement inside a <p> tag (sometimes WYSIWYG editors do this)
789
+ node = htmlMediaElement.parentNode;
790
+ while (node !== null && node.tagName.toLowerCase() != 'body') {
791
+ if (node.parentNode.tagName.toLowerCase() == 'p') {
792
+ node.parentNode.parentNode.insertBefore(node, node.parentNode);
793
+ break;
794
+ }
795
+ node = node.parentNode;
796
+ }
797
+
798
+ if (isVideo) {
799
+ width = (options.videoWidth > 0) ? options.videoWidth : (htmlMediaElement.getAttribute('width') !== null) ? htmlMediaElement.getAttribute('width') : options.defaultVideoWidth;
800
+ height = (options.videoHeight > 0) ? options.videoHeight : (htmlMediaElement.getAttribute('height') !== null) ? htmlMediaElement.getAttribute('height') : options.defaultVideoHeight;
801
+ } else {
802
+ if (options.enablePluginDebug) {
803
+ width = 320;
804
+ height = 240;
805
+ }
806
+ }
807
+
808
+ // register plugin
809
+ pluginMediaElement.success = options.success;
810
+ mejs.MediaPluginBridge.registerPluginElement(pluginid, pluginMediaElement, htmlMediaElement);
811
+
812
+ // add container (must be added to DOM before inserting HTML for IE)
813
+ container.className = 'me-plugin';
814
+ htmlMediaElement.parentNode.insertBefore(container, htmlMediaElement);
815
+
816
+ // flash/silverlight vars
817
+ initVars = [
818
+ 'id=' + pluginid,
819
+ 'isvideo=' + ((isVideo) ? "true" : "false"),
820
+ 'autoplay=' + ((autoplay) ? "true" : "false"),
821
+ 'preload=' + preload,
822
+ 'width=' + width,
823
+ 'startvolume=' + options.startVolume,
824
+ 'timerrate=' + options.timerRate,
825
+ 'height=' + height];
826
+
827
+ if (mediaUrl !== null) {
828
+ if (pluginType == 'flash') {
829
+ initVars.push('file=' + mejs.Utility.encodeUrl(mediaUrl));
830
+ } else {
831
+ initVars.push('file=' + mediaUrl);
832
+ }
833
+ }
834
+ if (options.enablePluginDebug) {
835
+ initVars.push('debug=true');
836
+ }
837
+ if (options.enablePluginSmoothing) {
838
+ initVars.push('smoothing=true');
839
+ }
840
+ if (controls) {
841
+ initVars.push('controls=true'); // shows controls in the plugin if desired
842
+ }
843
+
844
+ switch (pluginType) {
845
+ case 'silverlight':
846
+ container.innerHTML =
847
+ '<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="' + pluginid + '" name="' + pluginid + '" width="' + width + '" height="' + height + '">' +
848
+ '<param name="initParams" value="' + initVars.join(',') + '" />' +
849
+ '<param name="windowless" value="true" />' +
850
+ '<param name="background" value="black" />' +
851
+ '<param name="minRuntimeVersion" value="3.0.0.0" />' +
852
+ '<param name="autoUpgrade" value="true" />' +
853
+ '<param name="source" value="' + options.pluginPath + options.silverlightName + '" />' +
854
+ '</object>';
855
+ break;
856
+
857
+ case 'flash':
858
+
859
+ if (mejs.MediaFeatures.isIE) {
860
+ specialIEContainer = document.createElement('div');
861
+ container.appendChild(specialIEContainer);
862
+ specialIEContainer.outerHTML =
863
+ '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' +
864
+ 'id="' + pluginid + '" width="' + width + '" height="' + height + '">' +
865
+ '<param name="movie" value="' + options.pluginPath + options.flashName + '?x=' + (new Date()) + '" />' +
866
+ '<param name="flashvars" value="' + initVars.join('&amp;') + '" />' +
867
+ '<param name="quality" value="high" />' +
868
+ '<param name="bgcolor" value="#000000" />' +
869
+ '<param name="wmode" value="transparent" />' +
870
+ '<param name="allowScriptAccess" value="always" />' +
871
+ '<param name="allowFullScreen" value="true" />' +
872
+ '</object>';
873
+
874
+ } else {
875
+
876
+ container.innerHTML =
877
+ '<embed id="' + pluginid + '" name="' + pluginid + '" ' +
878
+ 'play="true" ' +
879
+ 'loop="false" ' +
880
+ 'quality="high" ' +
881
+ 'bgcolor="#000000" ' +
882
+ 'wmode="transparent" ' +
883
+ 'allowScriptAccess="always" ' +
884
+ 'allowFullScreen="true" ' +
885
+ 'type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" ' +
886
+ 'src="' + options.pluginPath + options.flashName + '" ' +
887
+ 'flashvars="' + initVars.join('&') + '" ' +
888
+ 'width="' + width + '" ' +
889
+ 'height="' + height + '"></embed>';
890
+ }
891
+ break;
892
+ }
893
+ // hide original element
894
+ htmlMediaElement.style.display = 'none';
895
+
896
+ // FYI: options.success will be fired by the MediaPluginBridge
897
+
898
+ return pluginMediaElement;
899
+ },
900
+
901
+ updateNative: function(htmlMediaElement, options, autoplay, preload, playback) {
902
+ // add methods to video object to bring it into parity with Flash Object
903
+ for (var m in mejs.HtmlMediaElement) {
904
+ htmlMediaElement[m] = mejs.HtmlMediaElement[m];
905
+ }
906
+
907
+ /*
908
+ Chrome now supports preload="none"
909
+ if (mejs.MediaFeatures.isChrome) {
910
+
911
+ // special case to enforce preload attribute (Chrome doesn't respect this)
912
+ if (preload === 'none' && !autoplay) {
913
+
914
+ // forces the browser to stop loading (note: fails in IE9)
915
+ htmlMediaElement.src = '';
916
+ htmlMediaElement.load();
917
+ htmlMediaElement.canceledPreload = true;
918
+
919
+ htmlMediaElement.addEventListener('play',function() {
920
+ if (htmlMediaElement.canceledPreload) {
921
+ htmlMediaElement.src = playback.url;
922
+ htmlMediaElement.load();
923
+ htmlMediaElement.play();
924
+ htmlMediaElement.canceledPreload = false;
925
+ }
926
+ }, false);
927
+ // for some reason Chrome forgets how to autoplay sometimes.
928
+ } else if (autoplay) {
929
+ htmlMediaElement.load();
930
+ htmlMediaElement.play();
931
+ }
932
+ }
933
+ */
934
+
935
+ // fire success code
936
+ options.success(htmlMediaElement, htmlMediaElement);
937
+
938
+ return htmlMediaElement;
939
+ }
940
+ };
941
+
942
+ window.mejs = mejs;
943
+ window.MediaElement = mejs.MediaElement;