Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

jquery.acornmediaplayer.js 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956
  1. /*
  2. * Acorn Media Player - jQuery plugin 1.6
  3. *
  4. * Copyright (C) 2012 Ionut Cristian Colceriu
  5. *
  6. * Dual licensed under the MIT and GPL licenses:
  7. * http://www.opensource.org/licenses/mit-license.php
  8. * http://www.gnu.org/licenses/gpl.html
  9. *
  10. * www.ghinda.net
  11. * contact@ghinda.net
  12. *
  13. */
  14. (function($) {
  15. $.fn.acornMediaPlayer = function(options) {
  16. /*
  17. * Define default plugin options
  18. */
  19. var defaults = {
  20. theme: 'access',
  21. nativeSliders: false,
  22. volumeSlider: 'horizontal',
  23. captionsOn: false
  24. };
  25. options = $.extend(defaults, options);
  26. /*
  27. * Function for generating a unique identifier using the current date and time
  28. * Used for generating an ID for the media elmenet when none is available
  29. */
  30. var uniqueID = function() {
  31. var currentDate = new Date();
  32. return currentDate.getTime();
  33. };
  34. /*
  35. * Detect support for localStorage
  36. */
  37. function supports_local_storage() {
  38. try {
  39. return 'localStorage' in window && window.localStorage !== null;
  40. } catch(e){
  41. return false;
  42. }
  43. }
  44. /*
  45. * Get the volume value from localStorage
  46. * If no value is present, define as maximum
  47. */
  48. var volume = (supports_local_storage) ? localStorage.getItem('acornvolume') : 1;
  49. if(!volume) {
  50. volume = 1;
  51. }
  52. /*
  53. * Main plugin function
  54. * It will be called on each element in the matched set
  55. */
  56. var acornPlayer = function() {
  57. // set the acorn object, will contain the needed DOM nodes and others
  58. var acorn = {
  59. $self: $(this)
  60. };
  61. var loadedMetadata; // Is the metadata loaded
  62. var seeking; // The user is seeking the media
  63. var wasPlaying; // Media was playing when the seeking started
  64. var fullscreenMode; // The media is in fullscreen mode
  65. var captionsActive; // Captions are active
  66. /* Define all the texts used
  67. * This makes it easier to maintain, make translations, etc.
  68. */
  69. var text = {
  70. play: 'Play',
  71. playTitle: 'Start the playback',
  72. pause: 'Pause',
  73. pauseTitle: 'Pause the playback',
  74. mute: 'Mute',
  75. unmute: 'Unmute',
  76. fullscreen: 'Fullscreen',
  77. fullscreenTitle: 'Toggle fullscreen mode',
  78. swap: 'Swap',
  79. swapTitle: 'Toggle video and presention swap',
  80. volumeTitle: 'Volume control',
  81. seekTitle: 'Video seek control',
  82. captions: 'Captions',
  83. captionsTitle: 'Show captions',
  84. captionsChoose: 'Choose caption',
  85. transcript: 'Transcript',
  86. transcriptTitle: 'Show transcript'
  87. };
  88. // main wrapper element
  89. var $wrapper = $('<div class="acorn-player" role="application"></div>').addClass(options.theme);
  90. /*
  91. * Define attribute tabindex on the main element to make it readchable by keyboard
  92. * Useful when "aria-describedby" is present
  93. *
  94. * It makes more sense for screen reader users to first reach the actual <video> or <audio> elment and read of description of it,
  95. * than directly reach the Media Player controls, without knowing what they control.
  96. */
  97. acorn.$self.attr('tabindex', '0');
  98. /*
  99. * Check if the main element has an ID attribute
  100. * If not present, generate one
  101. */
  102. acorn.id = acorn.$self.attr('id');
  103. if(!acorn.id) {
  104. acorn.id = 'acorn' + uniqueID();
  105. acorn.$self.attr('id', acorn.id);
  106. }
  107. /*
  108. * Markup for the fullscreen button
  109. * If the element is not <video> we leave if blank, as the button if useless on <audio> elements
  110. */
  111. var fullscreenBtnMarkup = (acorn.$self.is('video')) ? '<button class="acorn-fullscreen-button" title="' + text.fullscreenTitle + '" aria-controls="' + acorn.id + '" tabindex="3">' + text.fullscreen + '</button>' : '';
  112. /*
  113. * Markup for the swap button
  114. * If the element is not <video> we leave if blank, as the button if useless on <audio> elements
  115. */
  116. var swapBtnMarkup = (acorn.$self.is('video')) ? '<button class="acorn-swap-button" title="' + text.swapTitle + '" aria-controls="' + acorn.id + '" tabindex="4" >' + text.swap + '</button>' : '';
  117. /*
  118. * Complete markup
  119. */
  120. var template = '<div class="acorn-controls">' +
  121. '<button class="acorn-play-button" title="' + text.playTitle + '" aria-controls="' + acorn.id + '" tabindex="1">' + text.play + '</button>' +
  122. '<input type="range" class="acorn-seek-slider" title="' + text.seekTitle + '" value="0" min="0" max="150" step="0.1" aria-controls="' + acorn.id + '" tabindex="2" />' +
  123. '<span class="acorn-timer">00:00</span>' +
  124. '<div class="acorn-volume-box">' +
  125. '<button class="acorn-volume-button" title="' + text.mute + '" aria-controls="' + acorn.id + '" tabindex="5" >' + text.mute + '</button>' +
  126. '<input type="range" class="acorn-volume-slider" title="' + text.volumeTitle + '" value="1" min="0" max="1" step="0.05" aria-controls="' + acorn.id + '" tabindex="6" />' +
  127. '</div>' +
  128. swapBtnMarkup +
  129. fullscreenBtnMarkup +
  130. '<button class="acorn-caption-button" title="' + text.captionsTitle + '" aria-controls="' + acorn.id + '">' + text.captions + '</button>' +
  131. '<div class="acorn-caption-selector"></div>' +
  132. '<button class="acorn-transcript-button" title="' + text.transcriptTitle + '">' + text.transcript + '</button>' +
  133. '</div>';
  134. var captionMarkup = '<div class="acorn-caption"></div>';
  135. var transcriptMarkup = '<div class="acorn-transcript" role="region" aria-live="assertive"></div>';
  136. /*
  137. * Append the HTML markup
  138. */
  139. // append the wrapper
  140. acorn.$self.after($wrapper);
  141. // For iOS support, I have to clone the node, remove the original, and get a reference to the new one.
  142. // This is because iOS doesn't want to play videos that have just been `moved around`.
  143. // More details on the issue: http://bugs.jquery.com/ticket/8015
  144. $wrapper[0].appendChild( acorn.$self[0].cloneNode(true) );
  145. acorn.$self.trigger('pause');
  146. acorn.$self.remove();
  147. acorn.$self = $wrapper.find('video, audio');
  148. // append the controls and loading mask
  149. acorn.$self.after(template).after('<div class="loading-media"></div>');
  150. /*
  151. * Define the newly created DOM nodes
  152. */
  153. acorn.$container = acorn.$self.parent('.acorn-player');
  154. acorn.$controls = $('.acorn-controls', acorn.$container);
  155. acorn.$playBtn = $('.acorn-play-button', acorn.$container);
  156. acorn.$seek = $('.acorn-seek-slider', acorn.$container);
  157. acorn.$timer = $('.acorn-timer', acorn.$container);
  158. acorn.$volume = $('.acorn-volume-slider', acorn.$container);
  159. acorn.$volumeBtn = $('.acorn-volume-button', acorn.$container);
  160. acorn.$fullscreenBtn = $('.acorn-fullscreen-button', acorn.$container);
  161. acorn.$swapBtn = $('.acorn-swap-button', acorn.$container);
  162. /*
  163. * Append the markup for the Captions and Transcript
  164. * and define newly created DOM nodes for these
  165. */
  166. acorn.$controls.after(captionMarkup);
  167. acorn.$container.after(transcriptMarkup);
  168. acorn.$transcript = acorn.$container.next('.acorn-transcript');
  169. acorn.$transcriptBtn = $('.acorn-transcript-button', acorn.$container);
  170. acorn.$caption = $('.acorn-caption', acorn.$container);
  171. acorn.$captionBtn = $('.acorn-caption-button', acorn.$container);
  172. acorn.$captionSelector = $('.acorn-caption-selector', acorn.$container);
  173. /*
  174. * Use HTML5 "data-" attributes to set the original Width&Height for the <video>
  175. * These are used when returning from Fullscreen Mode
  176. */
  177. acorn.$self.attr('data-width', acorn.$self.width());
  178. acorn.$self.attr('data-height', acorn.$self.height());
  179. /*
  180. * Time formatting function
  181. * Takes the number of seconds as a parameter and return a readable format "minutes:seconds"
  182. * Used with the number of seconds returned by "currentTime"
  183. */
  184. var timeFormat = function(sec) {
  185. var m = Math.floor(sec/60)<10?"0" + Math.floor(sec/60):Math.floor(sec/60);
  186. var s = Math.floor(sec-(m*60))<10?"0" + Math.floor(sec-(m*60)):Math.floor(sec-(m*60));
  187. return m + ":" + s;
  188. };
  189. /*
  190. * PLAY/PAUSE Behaviour
  191. *
  192. * Function for the Play button
  193. * It triggers the native Play or Pause events
  194. */
  195. var playMedia = function() {
  196. if(!acorn.$self.prop('paused')) {
  197. acorn.$self.trigger('pause');
  198. } else {
  199. acorn.$self.trigger('play');
  200. }
  201. // We return false to stop the followup click event on tablets
  202. return false;
  203. };
  204. /*
  205. * Functions for native playback events (Play, Pause, Ended)
  206. * These are attached to the native media events.
  207. *
  208. * Even if the user is still using some form of native playback control (such as using the Context Menu)
  209. * it will not break the behviour of our player.
  210. */
  211. var startPlayback = function() {
  212. acorn.$playBtn.text(text.pause).attr('title', text.pauseTitle);
  213. acorn.$playBtn.addClass('acorn-paused-button');
  214. // if the metadata is not loaded yet, add the loading class
  215. if (!loadedMetadata) $wrapper.addClass('show-loading');
  216. };
  217. var stopPlayback = function() {
  218. acorn.$playBtn.text(text.play).attr('title', text.playTitle);
  219. acorn.$playBtn.removeClass('acorn-paused-button');
  220. };
  221. /*
  222. * SEEK SLIDER Behaviour
  223. *
  224. * Updates the Timer and Seek Slider values
  225. * Is called on each "timeupdate"
  226. */
  227. var seekUpdate = function() {
  228. var currenttime = acorn.$self.prop('currentTime');
  229. acorn.$timer.text(timeFormat(currenttime));
  230. // If the user is not manualy seeking
  231. if(!seeking) {
  232. // Check type of sliders (Range <input> or jQuery UI)
  233. if(options.nativeSliders) {
  234. acorn.$seek.attr('value', currenttime);
  235. } else {
  236. acorn.$seek.slider('value', currenttime);
  237. }
  238. }
  239. };
  240. /*
  241. * Time formatting function
  242. * Takes the number of seconds as a paramenter
  243. *
  244. * Used with "aria-valuetext" on the Seek Slider to provide a human readable time format to AT
  245. * Returns "X minutes Y seconds"
  246. */
  247. var ariaTimeFormat = function(sec) {
  248. var m = Math.floor(sec/60)<10?"" + Math.floor(sec/60):Math.floor(sec/60);
  249. var s = Math.floor(sec-(m*60))<10?"" + Math.floor(sec-(m*60)):Math.floor(sec-(m*60));
  250. var formatedTime;
  251. var mins = 'minutes';
  252. var secs = 'seconds';
  253. if(m == 1) {
  254. min = 'minute';
  255. }
  256. if(s == 1) {
  257. sec = 'second';
  258. }
  259. if(m === 0) {
  260. formatedTime = s + ' ' + secs;
  261. } else {
  262. formatedTime = m + ' ' + mins + ' ' + s + ' ' + secs;
  263. }
  264. return formatedTime;
  265. };
  266. /*
  267. * jQuery UI slider uses preventDefault when clicking any element
  268. * so it stops the Blur event from being fired.
  269. * This causes problems with the Caption Selector.
  270. * We trigger the Blur event manually.
  271. */
  272. var blurCaptionBtn = function() {
  273. acorn.$captionBtn.trigger('blur');
  274. };
  275. /*
  276. * Triggered when the user starts to seek manually
  277. * Pauses the media during seek and changes the "currentTime" to the slider's value
  278. */
  279. var startSeek = function(e, ui) {
  280. acorn.$playBtn.prop('disabled', true);
  281. acorn.$self.trigger('pause');
  282. seeking = true;
  283. var seekLocation;
  284. if(options.nativeSliders) {
  285. seekLocation = acorn.$seek.val();
  286. } else {
  287. seekLocation = ui.value;
  288. }
  289. acorn.$self[0].currentTime = seekLocation;
  290. // manually blur the Caption Button
  291. blurCaptionBtn();
  292. };
  293. /*
  294. * Triggered when user stoped manual seek
  295. * If the media was playing when seek started, it triggeres the playback,
  296. * and updates ARIA attributes
  297. */
  298. var endSeek = function(e, ui) {
  299. seeking = false;
  300. var sliderUI = $(ui.handle);
  301. sliderUI.attr("aria-valuenow", parseInt(ui.value, 10));
  302. sliderUI.attr("aria-valuetext", ariaTimeFormat(ui.value));
  303. acorn.$playBtn.prop('disabled', false);
  304. };
  305. /*
  306. * Transforms element into ARIA Slider adding attributes and "tabindex"
  307. * Used on jQuery UI sliders
  308. *
  309. * Will not needed once the jQuery UI slider gets built-in ARIA
  310. */
  311. var initSliderAccess = function (elem, opts) {
  312. var accessDefaults = {
  313. 'role': 'slider',
  314. 'aria-valuenow': parseInt(opts.value, 10),
  315. 'aria-valuemin': parseInt(opts.min, 10),
  316. 'aria-valuemax': parseInt(opts.max, 10),
  317. 'aria-valuetext': opts.valuetext
  318. };
  319. elem.attr(accessDefaults);
  320. };
  321. /*
  322. * Init jQuery UI slider
  323. */
  324. var initSeek = function() {
  325. // get existing classes
  326. var seekClass = acorn.$seek.attr('class');
  327. // create the new markup
  328. var divSeek = '<div class="' + seekClass + '" title="' + text.seekTitle + '"></div>';
  329. acorn.$seek.after(divSeek).remove();
  330. // get the newly created DOM node
  331. acorn.$seek = $('.' + seekClass, acorn.$container);
  332. // create the buffer element
  333. var bufferBar = '<div class="ui-slider-range acorn-buffer"></div>';
  334. acorn.$seek.append(bufferBar);
  335. // get the buffer element DOM node
  336. acorn.$buffer = $('.acorn-buffer', acorn.$container);
  337. // set up the slider options for the jQuery UI slider
  338. var sliderOptions = {
  339. value: 0,
  340. step: 1,
  341. orientation: 'horizontal',
  342. range: 'min',
  343. min: 0,
  344. max: 100
  345. };
  346. // init the jQuery UI slider
  347. acorn.$seek.slider(sliderOptions);
  348. };
  349. /*
  350. * Seek slider update, after metadata is loaded
  351. * Attach events, add the "duration" attribute and generate the jQuery UI Seek Slider
  352. */
  353. var updateSeek = function() {
  354. // Get the duration of the media
  355. var duration = acorn.$self[0].duration;
  356. // Check for the nativeSliders option
  357. if(options.nativeSliders) {
  358. acorn.$seek.attr('max', duration);
  359. acorn.$seek.bind('change', startSeek);
  360. acorn.$seek.bind('mousedown', startSeek);
  361. acorn.$seek.bind('mouseup', endSeek);
  362. } else {
  363. // set up the slider options for the jQuery UI slider
  364. var sliderOptions = {
  365. value: 0,
  366. step: 1,
  367. orientation: 'horizontal',
  368. range: 'min',
  369. min: 0,
  370. max: duration,
  371. slide: startSeek,
  372. stop: endSeek
  373. };
  374. // init the jQuery UI slider
  375. acorn.$seek.slider('option', sliderOptions);
  376. // add valuetext value to the slider options for better ARIA values
  377. sliderOptions.valuetext = ariaTimeFormat(sliderOptions.value);
  378. // accessify the slider
  379. initSliderAccess(acorn.$seek.find('.ui-slider-handle'), sliderOptions);
  380. // manully blur the Caption Button when clicking the handle
  381. $('.ui-slider-handle', acorn.$seek).click(blurCaptionBtn);
  382. // set the tab index
  383. $('.ui-slider-handle', acorn.$seek).attr("tabindex", "2");
  384. // show buffering progress on progress
  385. acorn.$self.bind('progress', showBuffer);
  386. }
  387. $wrapper.removeClass('show-loading');
  388. // remove the loading element
  389. //acorn.$self.next('.loading-media').remove();
  390. };
  391. /*
  392. * Show buffering progress
  393. */
  394. var showBuffer = function(e) {
  395. var max = parseInt(acorn.$self.prop('duration'), 10);
  396. var tr = this.buffered;
  397. if(tr && tr.length) {
  398. var buffer = parseInt(this.buffered.end(0)-this.buffered.start(0), 10);
  399. var bufferWidth = (buffer*100)/max;
  400. acorn.$buffer.css('width', bufferWidth + '%');
  401. }
  402. };
  403. /*
  404. * VOLUME BUTTON and SLIDER Behaviour
  405. *
  406. * Change volume using the Volume Slider
  407. * Also update ARIA attributes and set the volume value as a localStorage item
  408. */
  409. var changeVolume = function(e, ui) {
  410. // get the slider value
  411. volume = ui.value;
  412. // set the value as a localStorage item
  413. localStorage.setItem('acornvolume', volume);
  414. // check if the volume was muted before
  415. if(acorn.$self.prop('muted')) {
  416. acorn.$self.prop('muted', false);
  417. acorn.$volumeBtn.removeClass('acorn-volume-mute');
  418. acorn.$volumeBtn.text(text.mute).attr('title', text.mute);
  419. }
  420. // set the new volume on the media
  421. acorn.$self.prop('volume', volume);
  422. // set the ARIA attributes
  423. acorn.$volume.$handle.attr("aria-valuenow", Math.round(volume*100));
  424. acorn.$volume.$handle.attr("aria-valuetext", Math.round(volume*100) + ' percent');
  425. // manually trigger the Blur event on the Caption Button
  426. blurCaptionBtn();
  427. };
  428. /*
  429. * Mute and Unmute volume
  430. * Also add classes and change label on the Volume Button
  431. */
  432. var muteVolume = function() {
  433. if(acorn.$self.prop('muted') === true) {
  434. acorn.$self.prop('muted', false);
  435. if(options.nativeSliders) {
  436. acorn.$volume.val(volume);
  437. } else {
  438. acorn.$volume.slider('value', volume);
  439. }
  440. acorn.$volumeBtn.removeClass('acorn-volume-mute');
  441. acorn.$volumeBtn.text(text.mute).attr('title', text.mute);
  442. } else {
  443. acorn.$self.prop('muted', true);
  444. if(options.nativeSliders) {
  445. acorn.$volume.val('0');
  446. } else {
  447. acorn.$volume.slider('value', '0');
  448. }
  449. acorn.$volumeBtn.addClass('acorn-volume-mute');
  450. acorn.$volumeBtn.text(text.unmute).attr('title', text.unmute);
  451. }
  452. };
  453. /*
  454. * Init the Volume Button and Slider
  455. *
  456. * Attach events, create the jQuery UI Slider for the Volume Slider and add ARIA support
  457. */
  458. var initVolume = function() {
  459. if(options.nativeSliders) {
  460. acorn.$volume.bind('change', function() {
  461. acorn.$self.prop('muted',false);
  462. volume = acorn.$volume.val();
  463. acorn.$self.prop('volume', volume);
  464. });
  465. } else {
  466. var volumeClass = acorn.$volume.attr('class');
  467. var divVolume = '<div class="' + volumeClass + '" title="' + text.volumeTitle + '"></div>';
  468. acorn.$volume.after(divVolume).remove();
  469. acorn.$volume = $('.' + volumeClass, acorn.$container);
  470. var volumeSliderOptions = {
  471. value: volume,
  472. orientation: options.volumeSlider,
  473. range: "min",
  474. max: 1,
  475. min: 0,
  476. step: 0.1,
  477. animate: false,
  478. slide: changeVolume
  479. };
  480. acorn.$volume.slider(volumeSliderOptions);
  481. acorn.$volume.$handle = acorn.$volume.find('.ui-slider-handle');
  482. // change and add values to volumeSliderOptions for better values in the ARIA attributes
  483. volumeSliderOptions.max = 100;
  484. volumeSliderOptions.value = volumeSliderOptions.value * 100;
  485. volumeSliderOptions.valuetext = volumeSliderOptions.value + ' percent';
  486. initSliderAccess(acorn.$volume.$handle, volumeSliderOptions);
  487. acorn.$volume.$handle.attr("tabindex", "6");
  488. // show the volume slider when it is tabbed into
  489. acorn.$volume.$handle.focus(function(){
  490. if (!acorn.$volume.parent().is(":hover")) {
  491. acorn.$volume.addClass("handle-focused");
  492. }
  493. });
  494. acorn.$volume.$handle.blur(function(){
  495. acorn.$volume.removeClass("handle-focused");
  496. });
  497. // manully blur the Caption Button when clicking the handle
  498. $('.ui-slider-handle', acorn.$volume).click(blurCaptionBtn);
  499. }
  500. acorn.$volumeBtn.click(muteVolume);
  501. };
  502. /*
  503. * FULLSCREEN Behviour
  504. *
  505. * Resize the video while in Fullscreen Mode
  506. * Attached to window.resize
  507. */
  508. var resizeFullscreenVideo = function() {
  509. acorn.$self.attr({
  510. 'width': $(window).width(),
  511. 'height': $(window).height()
  512. });
  513. };
  514. /*
  515. * Enter and exit Fullscreen Mode
  516. *
  517. * Resizes the Width & Height of the <video> element
  518. * and add classes to the controls and wrapper
  519. */
  520. var goFullscreen = function() {
  521. if(fullscreenMode) {
  522. if(acorn.$self[0].webkitSupportsFullscreen) {
  523. acorn.$self[0].webkitExitFullScreen();
  524. } else {
  525. $('body').css('overflow', 'auto');
  526. var w = acorn.$self.attr('data-width');
  527. var h = acorn.$self.attr('data-height');
  528. acorn.$self.removeClass('fullscreen-video').attr({
  529. 'width': w,
  530. 'height': h
  531. });
  532. $(window).unbind('resize');
  533. acorn.$controls.removeClass('fullscreen-controls');
  534. }
  535. fullscreenMode = false;
  536. } else {
  537. if(acorn.$self[0].webkitSupportsFullscreen) {
  538. acorn.$self[0].webkitEnterFullScreen();
  539. } else if (acorn.$self[0].mozRequestFullScreen) {
  540. acorn.$self[0].mozRequestFullScreen();
  541. acorn.$self.attr('controls', 'controls');
  542. document.addEventListener('mozfullscreenchange', function() {
  543. console.log('screenchange event found');
  544. if (!document.mozFullScreenElement) {
  545. acorn.$self.removeAttr('controls');
  546. //document.removeEventListener('mozfullscreenchange');
  547. }
  548. });
  549. } else {
  550. $('body').css('overflow', 'hidden');
  551. acorn.$self.addClass('fullscreen-video').attr({
  552. width: $(window).width(),
  553. height: $(window).height()
  554. });
  555. $(window).resize(resizeFullscreenVideo);
  556. acorn.$controls.addClass('fullscreen-controls');
  557. }
  558. fullscreenMode = true;
  559. }
  560. };
  561. /*
  562. * Swap the video and presentation areas
  563. *
  564. * Resizes and moves based on hard coded numbers
  565. * Uses css to move it
  566. */
  567. var goSwap = function() {
  568. acorn.$self.trigger('swap');
  569. }
  570. /*
  571. * CAPTIONS Behaviour
  572. *
  573. * Turning off the captions
  574. * When selecting "None" from the Caption Selector or when the caption fails to load
  575. */
  576. var captionBtnActiveClass = 'acorn-caption-active';
  577. var captionBtnLoadingClass = 'acorn-caption-loading';
  578. var transcriptBtnActiveClass = 'acorn-transcript-active';
  579. var captionRadioName = 'acornCaptions' + uniqueID();
  580. var captionOff = function() {
  581. for (var i = 0; i < acorn.$track.length; i++) {
  582. var track = acorn.$track[i];
  583. track.track.mode = "disabled";
  584. }
  585. acorn.$captionBtn.removeClass(captionBtnActiveClass);
  586. };
  587. /*
  588. * Initialize the Caption Selector
  589. * Used when multiple <track>s are present
  590. */
  591. var initCaptionSelector = function() {
  592. // calculate the position relative to the parent controls element
  593. var setUpCaptionSelector = function() {
  594. var pos = acorn.$captionBtn.offset();
  595. var top = pos.top - acorn.$captionSelector.outerHeight(true);
  596. var left = pos.left - ((acorn.$captionSelector.outerWidth(true) - acorn.$captionBtn.outerWidth(true))/2);
  597. var parentPos = acorn.$controls.offset();
  598. left = left - parentPos.left;
  599. top = top - parentPos.top;
  600. acorn.$captionSelector.css({
  601. 'top': top,
  602. 'left': left
  603. });
  604. };
  605. acorn.$fullscreenBtn.click(setUpCaptionSelector);
  606. $(window).resize(function() {
  607. setUpCaptionSelector();
  608. });
  609. setUpCaptionSelector();
  610. /*
  611. * Show and hide the caption selector based on focus rather than hover.
  612. * This benefits both touchscreen and AT users.
  613. */
  614. var hideSelector; // timeout for hiding the Caption Selector
  615. var showCaptionSelector = function() {
  616. if(hideSelector) {
  617. clearTimeout(hideSelector);
  618. }
  619. acorn.$captionSelector.show();
  620. };
  621. var hideCaptionSelector = function() {
  622. hideSelector = setTimeout(function() {
  623. acorn.$captionSelector.hide();
  624. }, 200);
  625. };
  626. /* Little TEMPORARY hack to focus the caption button on click
  627. This is because Webkit does not focus the button on click */
  628. acorn.$captionBtn.click(function() {
  629. $(this).focus();
  630. });
  631. acorn.$captionBtn.bind('focus', showCaptionSelector);
  632. acorn.$captionBtn.bind('blur', hideCaptionSelector);
  633. $('input[name=' + captionRadioName + ']', acorn.$container).bind('focus', showCaptionSelector);
  634. $('input[name=' + captionRadioName + ']', acorn.$container).bind('blur', hideCaptionSelector);
  635. /*
  636. * Make the Caption Selector focusable and attach events to it
  637. * If we wouldn't do this, when we'd use the scroll on the Caption Selector, it would dissapear
  638. */
  639. acorn.$captionSelector.attr('tabindex', '-1');
  640. acorn.$captionSelector.bind('focus', showCaptionSelector);
  641. acorn.$captionSelector.bind('blur', hideCaptionSelector);
  642. };
  643. /*
  644. * Current caption loader
  645. * Loads a SRT file and uses it as captions
  646. * Takes the url as a parameter
  647. */
  648. var loadCaption = function(url) {
  649. // Iterate through the available captions, and disable all but the selected one
  650. for (var i = 0; i < acorn.$track.length; i++) {
  651. var track = acorn.$track[i];
  652. if (track.getAttribute('src') == url) {
  653. track.track.mode = "showing";
  654. // TODO transcript markup?
  655. // show the Transcript Button
  656. //acorn.$transcriptBtn.show();
  657. /*
  658. * Generate the markup for the transcript
  659. * Markup based on Bruce Lawson's “Accessible HTML5 Video with JavaScripted captions”
  660. * http://dev.opera.com/articles/view/accessible-html5-video-with-javascripted-captions/
  661. */
  662. //var transcriptText = '';
  663. //$(captions).each(function() {
  664. // transcriptText += '<span data-begin="' + parseInt(this.start, 10) + '" data-end=' + parseInt(this.end, 10) + '>' + this.content.replace("'","") + '</span>';
  665. //});
  666. // append the generated markup
  667. //acorn.$transcript.html(transcriptText);
  668. } else {
  669. track.track.mode = "disabled";
  670. }
  671. }
  672. captionsActive = true;
  673. acorn.$captionBtn.addClass(captionBtnActiveClass);
  674. };
  675. /*
  676. * Show or hide the Transcript based on the presence of the active class
  677. */
  678. var showTranscript = function() {
  679. if($(this).hasClass(transcriptBtnActiveClass)) {
  680. acorn.$transcript.hide();
  681. } else {
  682. acorn.$transcript.show();
  683. }
  684. $(this).toggleClass(transcriptBtnActiveClass);
  685. };
  686. /*
  687. * Caption loading and initialization
  688. */
  689. var initCaption = function() {
  690. // Check if we have browser support for captions
  691. if (typeof(TextTrack) === "undefined") {
  692. return;
  693. }
  694. // get all <track> elements
  695. acorn.$track = $('track', acorn.$self);
  696. // if there is at least one <track> element, show the Caption Button
  697. if(acorn.$track.length) {
  698. acorn.$captionBtn.show();
  699. }
  700. // check if there is more than one <track> element
  701. // if there is more than one track element we'll create the Caption Selector
  702. if(acorn.$track.length>1) {
  703. // set a different "title" attribute
  704. acorn.$captionBtn.attr('title', text.captionsChoose);
  705. // markup for the Caption Selector
  706. var captionList = '<ul><li><label><input type="radio" name="' + captionRadioName + '" checked="true" />None</label></li>';
  707. acorn.$track.each(function() {
  708. var tracksrc = $(this).attr('src');
  709. captionList += '<li><label><input type="radio" name="' + captionRadioName + '" data-url="' + $(this).attr('src') + '" />' + $(this).attr('label') + '</label></li>';
  710. });
  711. captionList += '</ul>';
  712. // append the generated markup
  713. acorn.$captionSelector.html(captionList);
  714. // change selected caption
  715. var changeCaption = function() {
  716. // get the original <track> "src" attribute from the custom "data-url" attribute of the radio input
  717. var tracksrc = $(this).attr('data-url');
  718. if(tracksrc) {
  719. loadCaption(tracksrc);
  720. } else {
  721. // if there's not "data-url" attribute, turn off the caption
  722. captionOff();
  723. }
  724. };
  725. // attach event handler
  726. $('input[name=' + captionRadioName + ']', acorn.$container).change(changeCaption);
  727. // initialize Caption Selector
  728. initCaptionSelector();
  729. // load first caption if captionsOn is true
  730. var firstCaption = acorn.$track.first().attr('src');
  731. if(options.captionsOn) {
  732. loadCaption(firstCaption);
  733. $('input[name=' + captionRadioName + ']', acorn.$container).removeAttr('checked');
  734. $('input[name=' + captionRadioName + ']:eq(1)', acorn.$container).attr('checked', 'true');
  735. };
  736. } else if(acorn.$track.length) {
  737. // if there's only one <track> element
  738. // load the specific caption when activating the Caption Button
  739. var tracksrc = acorn.$track.attr('src');
  740. acorn.$captionBtn.bind('click', function() {
  741. if($(this).hasClass(captionBtnActiveClass)) {
  742. captionOff();
  743. } else {
  744. loadCaption(tracksrc);
  745. }
  746. });
  747. // load default caption if captionsOn is true
  748. if(options.captionsOn) loadCaption(tracksrc);
  749. }
  750. // attach event to Transcript Button
  751. acorn.$transcriptBtn.bind('click', showTranscript);
  752. };
  753. /*
  754. * Initialization self-invoking function
  755. * Runs other initialization functions, attaches events, removes native controls
  756. */
  757. var init = function() {
  758. // attach playback handlers
  759. acorn.$playBtn.bind( 'touchstart click', playMedia);
  760. acorn.$self.bind( 'touchstart click' , playMedia);
  761. acorn.$self.bind('play', startPlayback);
  762. acorn.$self.bind('pause', stopPlayback);
  763. acorn.$self.bind('ended', stopPlayback);
  764. // update the Seek Slider when timeupdate is triggered
  765. acorn.$self.bind('timeupdate', seekUpdate);
  766. // bind Fullscreen Button
  767. acorn.$fullscreenBtn.click(goFullscreen);
  768. // bind Swap Button
  769. acorn.$swapBtn.click(goSwap);
  770. // initialize volume controls
  771. initVolume();
  772. // add the loading class
  773. $wrapper.addClass('');
  774. if(!options.nativeSliders) initSeek();
  775. // once the metadata has loaded
  776. acorn.$self.bind('loadedmetadata', function() {
  777. /* I use an interval to make sure the video has the right readyState
  778. * to bypass a known webkit bug that causes loadedmetadata to be triggered
  779. * before the duration is available
  780. */
  781. var t = window.setInterval(function() {
  782. if (acorn.$self[0].readyState > 0) {
  783. loadedMetadata = true;
  784. updateSeek();
  785. clearInterval(t);
  786. }
  787. }, 500);
  788. initCaption();
  789. });
  790. // trigger update seek manualy for the first time, for iOS support
  791. updateSeek();
  792. // remove the native controls
  793. acorn.$self.removeAttr('controls');
  794. if(acorn.$self.is('audio')) {
  795. /*
  796. * If the media is <audio>, we're adding the 'audio-player' class to the element.
  797. * This is because Opera 10.62 does not allow the <audio> element to be targeted by CSS
  798. * and this can cause problems with themeing.
  799. */
  800. acorn.$container.addClass('audio-player');
  801. }
  802. }();
  803. };
  804. // iterate and reformat each matched element
  805. return this.each(acornPlayer);
  806. };
  807. })(jQuery);