您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

jquery.acornmediaplayer.js 36KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157
  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. acorn.$self[0].play();
  201. }
  202. // We return false to stop the followup click event on tablets
  203. return false;
  204. };
  205. /*
  206. * Functions for native playback events (Play, Pause, Ended)
  207. * These are attached to the native media events.
  208. *
  209. * Even if the user is still using some form of native playback control (such as using the Context Menu)
  210. * it will not break the behviour of our player.
  211. */
  212. var startPlayback = function() {
  213. acorn.$playBtn.text(text.pause).attr('title', text.pauseTitle);
  214. acorn.$playBtn.addClass('acorn-paused-button');
  215. // if the metadata is not loaded yet, add the loading class
  216. if (!loadedMetadata) $wrapper.addClass('show-loading');
  217. };
  218. var stopPlayback = function() {
  219. acorn.$playBtn.text(text.play).attr('title', text.playTitle);
  220. acorn.$playBtn.removeClass('acorn-paused-button');
  221. };
  222. /*
  223. * SEEK SLIDER Behaviour
  224. *
  225. * Updates the Timer and Seek Slider values
  226. * Is called on each "timeupdate"
  227. */
  228. var seekUpdate = function() {
  229. var currenttime = acorn.$self.prop('currentTime');
  230. acorn.$timer.text(timeFormat(currenttime));
  231. // If the user is not manualy seeking
  232. if(!seeking) {
  233. // Check type of sliders (Range <input> or jQuery UI)
  234. if(options.nativeSliders) {
  235. acorn.$seek.attr('value', currenttime);
  236. } else {
  237. acorn.$seek.slider('value', currenttime);
  238. }
  239. }
  240. // If captions are active, update them
  241. if(captionsActive) {
  242. updateCaption();
  243. }
  244. };
  245. /*
  246. * Time formatting function
  247. * Takes the number of seconds as a paramenter
  248. *
  249. * Used with "aria-valuetext" on the Seek Slider to provide a human readable time format to AT
  250. * Returns "X minutes Y seconds"
  251. */
  252. var ariaTimeFormat = function(sec) {
  253. var m = Math.floor(sec/60)<10?"" + Math.floor(sec/60):Math.floor(sec/60);
  254. var s = Math.floor(sec-(m*60))<10?"" + Math.floor(sec-(m*60)):Math.floor(sec-(m*60));
  255. var formatedTime;
  256. var mins = 'minutes';
  257. var secs = 'seconds';
  258. if(m == 1) {
  259. min = 'minute';
  260. }
  261. if(s == 1) {
  262. sec = 'second';
  263. }
  264. if(m === 0) {
  265. formatedTime = s + ' ' + secs;
  266. } else {
  267. formatedTime = m + ' ' + mins + ' ' + s + ' ' + secs;
  268. }
  269. return formatedTime;
  270. };
  271. /*
  272. * jQuery UI slider uses preventDefault when clicking any element
  273. * so it stops the Blur event from being fired.
  274. * This causes problems with the Caption Selector.
  275. * We trigger the Blur event manually.
  276. */
  277. var blurCaptionBtn = function() {
  278. acorn.$captionBtn.trigger('blur');
  279. };
  280. /*
  281. * Triggered when the user starts to seek manually
  282. * Pauses the media during seek and changes the "currentTime" to the slider's value
  283. */
  284. var startSeek = function(e, ui) {
  285. if(!acorn.$self.attr('paused')) {
  286. wasPlaying = true;
  287. }
  288. acorn.$self.trigger('pause');
  289. seeking = true;
  290. var seekLocation;
  291. if(options.nativeSliders) {
  292. seekLocation = acorn.$seek.val();
  293. } else {
  294. seekLocation = ui.value;
  295. }
  296. acorn.$self[0].currentTime = seekLocation;
  297. // manually blur the Caption Button
  298. blurCaptionBtn();
  299. };
  300. /*
  301. * Triggered when user stoped manual seek
  302. * If the media was playing when seek started, it triggeres the playback,
  303. * and updates ARIA attributes
  304. */
  305. var endSeek = function(e, ui) {
  306. if(wasPlaying) {
  307. acorn.$self.trigger('play');
  308. wasPlaying = false;
  309. }
  310. seeking = false;
  311. var sliderUI = $(ui.handle);
  312. sliderUI.attr("aria-valuenow", parseInt(ui.value, 10));
  313. sliderUI.attr("aria-valuetext", ariaTimeFormat(ui.value));
  314. };
  315. /*
  316. * Transforms element into ARIA Slider adding attributes and "tabindex"
  317. * Used on jQuery UI sliders
  318. *
  319. * Will not needed once the jQuery UI slider gets built-in ARIA
  320. */
  321. var initSliderAccess = function (elem, opts) {
  322. var accessDefaults = {
  323. 'role': 'slider',
  324. 'aria-valuenow': parseInt(opts.value, 10),
  325. 'aria-valuemin': parseInt(opts.min, 10),
  326. 'aria-valuemax': parseInt(opts.max, 10),
  327. 'aria-valuetext': opts.valuetext
  328. };
  329. elem.attr(accessDefaults);
  330. };
  331. /*
  332. * Init jQuery UI slider
  333. */
  334. var initSeek = function() {
  335. // get existing classes
  336. var seekClass = acorn.$seek.attr('class');
  337. // create the new markup
  338. var divSeek = '<div class="' + seekClass + '" title="' + text.seekTitle + '"></div>';
  339. acorn.$seek.after(divSeek).remove();
  340. // get the newly created DOM node
  341. acorn.$seek = $('.' + seekClass, acorn.$container);
  342. // create the buffer element
  343. var bufferBar = '<div class="ui-slider-range acorn-buffer"></div>';
  344. acorn.$seek.append(bufferBar);
  345. // get the buffer element DOM node
  346. acorn.$buffer = $('.acorn-buffer', acorn.$container);
  347. // set up the slider options for the jQuery UI slider
  348. var sliderOptions = {
  349. value: 0,
  350. step: 1,
  351. orientation: 'horizontal',
  352. range: 'min',
  353. min: 0,
  354. max: 100
  355. };
  356. // init the jQuery UI slider
  357. acorn.$seek.slider(sliderOptions);
  358. };
  359. /*
  360. * Seek slider update, after metadata is loaded
  361. * Attach events, add the "duration" attribute and generate the jQuery UI Seek Slider
  362. */
  363. var updateSeek = function() {
  364. // Get the duration of the media
  365. var duration = acorn.$self[0].duration;
  366. // Check for the nativeSliders option
  367. if(options.nativeSliders) {
  368. acorn.$seek.attr('max', duration);
  369. acorn.$seek.bind('change', startSeek);
  370. acorn.$seek.bind('mousedown', startSeek);
  371. acorn.$seek.bind('mouseup', endSeek);
  372. } else {
  373. // set up the slider options for the jQuery UI slider
  374. var sliderOptions = {
  375. value: 0,
  376. step: 1,
  377. orientation: 'horizontal',
  378. range: 'min',
  379. min: 0,
  380. max: duration,
  381. slide: startSeek,
  382. stop: endSeek
  383. };
  384. // init the jQuery UI slider
  385. acorn.$seek.slider('option', sliderOptions);
  386. // add valuetext value to the slider options for better ARIA values
  387. sliderOptions.valuetext = ariaTimeFormat(sliderOptions.value);
  388. // accessify the slider
  389. initSliderAccess(acorn.$seek.find('.ui-slider-handle'), sliderOptions);
  390. // manully blur the Caption Button when clicking the handle
  391. $('.ui-slider-handle', acorn.$seek).click(blurCaptionBtn);
  392. // set the tab index
  393. $('.ui-slider-handle', acorn.$seek).attr("tabindex", "2");
  394. // show buffering progress on progress
  395. acorn.$self.bind('progress', showBuffer);
  396. }
  397. $wrapper.removeClass('show-loading');
  398. // remove the loading element
  399. //acorn.$self.next('.loading-media').remove();
  400. };
  401. /*
  402. * Show buffering progress
  403. */
  404. var showBuffer = function(e) {
  405. var max = parseInt(acorn.$self.prop('duration'), 10);
  406. var tr = this.buffered;
  407. if(tr && tr.length) {
  408. var buffer = parseInt(this.buffered.end(0)-this.buffered.start(0), 10);
  409. var bufferWidth = (buffer*100)/max;
  410. acorn.$buffer.css('width', bufferWidth + '%');
  411. }
  412. };
  413. /*
  414. * VOLUME BUTTON and SLIDER Behaviour
  415. *
  416. * Change volume using the Volume Slider
  417. * Also update ARIA attributes and set the volume value as a localStorage item
  418. */
  419. var changeVolume = function(e, ui) {
  420. // get the slider value
  421. volume = ui.value;
  422. // set the value as a localStorage item
  423. localStorage.setItem('acornvolume', volume);
  424. // check if the volume was muted before
  425. if(acorn.$self.prop('muted')) {
  426. acorn.$self.prop('muted', false);
  427. acorn.$volumeBtn.removeClass('acorn-volume-mute');
  428. acorn.$volumeBtn.text(text.mute).attr('title', text.mute);
  429. }
  430. // set the new volume on the media
  431. acorn.$self.prop('volume', volume);
  432. // set the ARIA attributes
  433. acorn.$volume.$handle.attr("aria-valuenow", Math.round(volume*100));
  434. acorn.$volume.$handle.attr("aria-valuetext", Math.round(volume*100) + ' percent');
  435. // manually trigger the Blur event on the Caption Button
  436. blurCaptionBtn();
  437. };
  438. /*
  439. * Mute and Unmute volume
  440. * Also add classes and change label on the Volume Button
  441. */
  442. var muteVolume = function() {
  443. if(acorn.$self.prop('muted') === true) {
  444. acorn.$self.prop('muted', false);
  445. if(options.nativeSliders) {
  446. acorn.$volume.val(volume);
  447. } else {
  448. acorn.$volume.slider('value', volume);
  449. }
  450. acorn.$volumeBtn.removeClass('acorn-volume-mute');
  451. acorn.$volumeBtn.text(text.mute).attr('title', text.mute);
  452. } else {
  453. acorn.$self.prop('muted', true);
  454. if(options.nativeSliders) {
  455. acorn.$volume.val('0');
  456. } else {
  457. acorn.$volume.slider('value', '0');
  458. }
  459. acorn.$volumeBtn.addClass('acorn-volume-mute');
  460. acorn.$volumeBtn.text(text.unmute).attr('title', text.unmute);
  461. }
  462. };
  463. /*
  464. * Init the Volume Button and Slider
  465. *
  466. * Attach events, create the jQuery UI Slider for the Volume Slider and add ARIA support
  467. */
  468. var initVolume = function() {
  469. if(options.nativeSliders) {
  470. acorn.$volume.bind('change', function() {
  471. acorn.$self.prop('muted',false);
  472. volume = acorn.$volume.val();
  473. acorn.$self.prop('volume', volume);
  474. });
  475. } else {
  476. var volumeClass = acorn.$volume.attr('class');
  477. var divVolume = '<div class="' + volumeClass + '" title="' + text.volumeTitle + '"></div>';
  478. acorn.$volume.after(divVolume).remove();
  479. acorn.$volume = $('.' + volumeClass, acorn.$container);
  480. var volumeSliderOptions = {
  481. value: volume,
  482. orientation: options.volumeSlider,
  483. range: "min",
  484. max: 1,
  485. min: 0,
  486. step: 0.1,
  487. animate: false,
  488. slide: changeVolume
  489. };
  490. acorn.$volume.slider(volumeSliderOptions);
  491. acorn.$volume.$handle = acorn.$volume.find('.ui-slider-handle');
  492. // change and add values to volumeSliderOptions for better values in the ARIA attributes
  493. volumeSliderOptions.max = 100;
  494. volumeSliderOptions.value = volumeSliderOptions.value * 100;
  495. volumeSliderOptions.valuetext = volumeSliderOptions.value + ' percent';
  496. initSliderAccess(acorn.$volume.$handle, volumeSliderOptions);
  497. acorn.$volume.$handle.attr("tabindex", "6");
  498. // show the volume slider when it is tabbed into
  499. acorn.$volume.$handle.focus(function(){
  500. if (!acorn.$volume.parent().is(":hover")) {
  501. acorn.$volume.addClass("handle-focused");
  502. }
  503. });
  504. acorn.$volume.$handle.blur(function(){
  505. acorn.$volume.removeClass("handle-focused");
  506. });
  507. // manully blur the Caption Button when clicking the handle
  508. $('.ui-slider-handle', acorn.$volume).click(blurCaptionBtn);
  509. }
  510. acorn.$volumeBtn.click(muteVolume);
  511. };
  512. /*
  513. * FULLSCREEN Behviour
  514. *
  515. * Resize the video while in Fullscreen Mode
  516. * Attached to window.resize
  517. */
  518. var resizeFullscreenVideo = function() {
  519. acorn.$self.attr({
  520. 'width': $(window).width(),
  521. 'height': $(window).height()
  522. });
  523. };
  524. /*
  525. * Enter and exit Fullscreen Mode
  526. *
  527. * Resizes the Width & Height of the <video> element
  528. * and add classes to the controls and wrapper
  529. */
  530. var goFullscreen = function() {
  531. if(fullscreenMode) {
  532. if(acorn.$self[0].webkitSupportsFullscreen) {
  533. acorn.$self[0].webkitExitFullScreen();
  534. } else {
  535. $('body').css('overflow', 'auto');
  536. var w = acorn.$self.attr('data-width');
  537. var h = acorn.$self.attr('data-height');
  538. acorn.$self.removeClass('fullscreen-video').attr({
  539. 'width': w,
  540. 'height': h
  541. });
  542. $(window).unbind('resize');
  543. acorn.$controls.removeClass('fullscreen-controls');
  544. }
  545. fullscreenMode = false;
  546. } else {
  547. if(acorn.$self[0].webkitSupportsFullscreen) {
  548. acorn.$self[0].webkitEnterFullScreen();
  549. } else if (acorn.$self[0].mozRequestFullScreen) {
  550. acorn.$self[0].mozRequestFullScreen();
  551. acorn.$self.attr('controls', 'controls');
  552. document.addEventListener('mozfullscreenchange', function() {
  553. console.log('screenchange event found');
  554. if (!document.mozFullScreenElement) {
  555. acorn.$self.removeAttr('controls');
  556. //document.removeEventListener('mozfullscreenchange');
  557. }
  558. });
  559. } else {
  560. $('body').css('overflow', 'hidden');
  561. acorn.$self.addClass('fullscreen-video').attr({
  562. width: $(window).width(),
  563. height: $(window).height()
  564. });
  565. $(window).resize(resizeFullscreenVideo);
  566. acorn.$controls.addClass('fullscreen-controls');
  567. }
  568. fullscreenMode = true;
  569. }
  570. };
  571. /*
  572. * Swap the video and presentation areas
  573. *
  574. * Resizes and moves based on hard coded numbers
  575. * Uses css to move it
  576. */
  577. var swapped = false;
  578. var goSwap = function() {
  579. if (swapped === false) {
  580. $('#slide').css("width", "400px");
  581. $('#slide').css("height", "300px");
  582. $('#slide > object').attr("width", "400px");
  583. $('#slide > object').attr("height", "300px");
  584. var svgfile = $('#slide > object')[0].contentDocument.getElementById("svgfile");
  585. svgfile.style.width = "400px";
  586. svgfile.style.height = "300px";
  587. var slide = document.getElementById("slide");
  588. var cursor = document.getElementById("cursor");
  589. var slideT = document.getElementById("slideText");
  590. var video = document.getElementById("video");
  591. video.style.width = "800px";
  592. video.style.height = "600px";
  593. $('#videoRecordingWrapper').position({
  594. "my": "left top",
  595. "at": "right top",
  596. "of": '#thumbnails',
  597. "collision": "none none",
  598. "offset": "10 0"
  599. });
  600. $('#videoRecordingWrapper').width("800px");
  601. $('#videoRecordingWrapper').height("600px");
  602. $('#presentation').position({
  603. "my": "left top",
  604. "at": "left bottom",
  605. "of": '#chat',
  606. "collision": "none none"
  607. });
  608. $('#presentation').width("400px");
  609. $('#presentation').height("300px");
  610. $('.acorn-controls').position({
  611. "my": "left top",
  612. "at": "left bottom",
  613. "of": '#videoRecordingWrapper',
  614. "collision": "none none",
  615. "offset": "10 7"
  616. });
  617. draw(0,0);
  618. swapped = true;
  619. } else {
  620. $('#slide').css("width", "800px");
  621. $('#slide').css("height", "600px");
  622. $('#slide > object').attr("width", "800px");
  623. $('#slide > object').attr("height", "600px");
  624. var svgfile = $('#slide > object')[0].contentDocument.getElementById("svgfile");
  625. svgfile.style.width = "800px";
  626. svgfile.style.height = "600px";
  627. var video = document.getElementById("video");
  628. video.style.width = "400px";
  629. video.style.height = "300px";
  630. $('#presentation').position({
  631. "my": "left top",
  632. "at": "right top",
  633. "of": '#thumbnails',
  634. "collision": "none none",
  635. "offset": "10 0"
  636. });
  637. $('#presentation').width("800px");
  638. $('#presentation').height("600px");
  639. $('#videoRecordingWrapper').position({
  640. "my": "left top",
  641. "at": "left bottom",
  642. "of": '#chat',
  643. "collision": "none none"
  644. });
  645. $('#videoRecordingWrapper').width("400px");
  646. $('#videoRecordingWrapper').height("300px");
  647. $('.acorn-controls').position({
  648. "my": "left top",
  649. "at": "left bottom",
  650. "of": '#presentation',
  651. "collision": "none none",
  652. "offset": "10 7"
  653. });
  654. swapped = false;
  655. }
  656. }
  657. /*
  658. * CAPTIONS Behaviour
  659. *
  660. * Turning off the captions
  661. * When selecting "None" from the Caption Selector or when the caption fails to load
  662. */
  663. var captionBtnActiveClass = 'acorn-caption-active';
  664. var captionBtnLoadingClass = 'acorn-caption-loading';
  665. var transcriptBtnActiveClass = 'acorn-transcript-active';
  666. var captionRadioName = 'acornCaptions' + uniqueID();
  667. var captionOff = function() {
  668. captions = '';
  669. acorn.$caption.hide();
  670. activeCaptions = false;
  671. acorn.$transcriptBtn.removeClass(transcriptBtnActiveClass).hide();
  672. acorn.$transcript.hide();
  673. acorn.$captionBtn.removeClass(captionBtnActiveClass);
  674. };
  675. /*
  676. * Update caption based on "currentTime"
  677. * Borrowed and adapted from Bruce Lawson's “Accessible HTML5 Video with JavaScripted captions”
  678. * http://dev.opera.com/articles/view/accessible-html5-video-with-javascripted-captions/
  679. */
  680. var updateCaption = function() {
  681. var now = acorn.$self[0].currentTime; // how soon is now?
  682. var text = "";
  683. for (var i = 0; i < captions.length; i++) {
  684. if (now >= captions[i].start && now <= captions[i].end) {
  685. text = captions[i].content; // yes? then load it into a variable called text
  686. break;
  687. }
  688. }
  689. acorn.$caption.html(text); // and put contents of text into caption div
  690. };
  691. /*
  692. * Initialize the Caption Selector
  693. * Used when multiple <track>s are present
  694. */
  695. var initCaptionSelector = function() {
  696. // calculate the position relative to the parent controls element
  697. var setUpCaptionSelector = function() {
  698. var pos = acorn.$captionBtn.offset();
  699. var top = pos.top - acorn.$captionSelector.outerHeight(true);
  700. var left = pos.left - ((acorn.$captionSelector.outerWidth(true) - acorn.$captionBtn.outerWidth(true))/2);
  701. var parentPos = acorn.$controls.offset();
  702. left = left - parentPos.left;
  703. top = top - parentPos.top;
  704. acorn.$captionSelector.css({
  705. 'top': top,
  706. 'left': left
  707. });
  708. };
  709. acorn.$fullscreenBtn.click(setUpCaptionSelector);
  710. $(window).resize(function() {
  711. setUpCaptionSelector();
  712. });
  713. setUpCaptionSelector();
  714. /*
  715. * Show and hide the caption selector based on focus rather than hover.
  716. * This benefits both touchscreen and AT users.
  717. */
  718. var hideSelector; // timeout for hiding the Caption Selector
  719. var showCaptionSelector = function() {
  720. if(hideSelector) {
  721. clearTimeout(hideSelector);
  722. }
  723. acorn.$captionSelector.show();
  724. };
  725. var hideCaptionSelector = function() {
  726. hideSelector = setTimeout(function() {
  727. acorn.$captionSelector.hide();
  728. }, 200);
  729. };
  730. /* Little TEMPORARY hack to focus the caption button on click
  731. This is because Webkit does not focus the button on click */
  732. acorn.$captionBtn.click(function() {
  733. $(this).focus();
  734. });
  735. acorn.$captionBtn.bind('focus', showCaptionSelector);
  736. acorn.$captionBtn.bind('blur', hideCaptionSelector);
  737. $('input[name=' + captionRadioName + ']', acorn.$container).bind('focus', showCaptionSelector);
  738. $('input[name=' + captionRadioName + ']', acorn.$container).bind('blur', hideCaptionSelector);
  739. /*
  740. * Make the Caption Selector focusable and attach events to it
  741. * If we wouldn't do this, when we'd use the scroll on the Caption Selector, it would dissapear
  742. */
  743. acorn.$captionSelector.attr('tabindex', '-1');
  744. acorn.$captionSelector.bind('focus', showCaptionSelector);
  745. acorn.$captionSelector.bind('blur', hideCaptionSelector);
  746. };
  747. /*
  748. * Current caption loader
  749. * Loads a SRT file and uses it as captions
  750. * Takes the url as a parameter
  751. */
  752. var loadCaption = function(url) {
  753. // add a loading class to the Caption Button when starting to load the caption
  754. acorn.$captionBtn.addClass(captionBtnLoadingClass);
  755. // make an AJAX request to load the file
  756. $.ajax({
  757. url: url,
  758. success: function(data) {
  759. /*
  760. * On success use a SRT parser on the loaded data
  761. * Using JavaScript SRT parser by Silvia Pfeiffer <silvia@siliva-pfeiffer.de>
  762. * parseSrt included at the end of this file
  763. */
  764. captions = parseSrt(data);
  765. // show the Transcript Button
  766. acorn.$transcriptBtn.show();
  767. /*
  768. * Generate the markup for the transcript
  769. * Markup based on Bruce Lawson's “Accessible HTML5 Video with JavaScripted captions”
  770. * http://dev.opera.com/articles/view/accessible-html5-video-with-javascripted-captions/
  771. */
  772. var transcriptText = '';
  773. $(captions).each(function() {
  774. transcriptText += '<span data-begin="' + parseInt(this.start, 10) + '" data-end=' + parseInt(this.end, 10) + '>' + this.content.replace("'","") + '</span>';
  775. });
  776. // append the generated markup
  777. acorn.$transcript.html(transcriptText);
  778. // show caption
  779. acorn.$caption.show();
  780. captionsActive = true;
  781. // in case the media is paused and timeUpdate is not triggered, trigger it
  782. if(acorn.$self.prop('paused')) {
  783. updateCaption();
  784. }
  785. acorn.$captionBtn.addClass(captionBtnActiveClass).removeClass(captionBtnLoadingClass);
  786. },
  787. error: function() {
  788. // if an error occurs while loading the caption, turn captions off
  789. captionOff();
  790. // if a console is available, log error
  791. if(console) {
  792. console.log('Error loading captions');
  793. }
  794. }
  795. });
  796. };
  797. /*
  798. * Show or hide the Transcript based on the presence of the active class
  799. */
  800. var showTranscript = function() {
  801. if($(this).hasClass(transcriptBtnActiveClass)) {
  802. acorn.$transcript.hide();
  803. } else {
  804. acorn.$transcript.show();
  805. }
  806. $(this).toggleClass(transcriptBtnActiveClass);
  807. };
  808. /*
  809. * Caption loading and initialization
  810. */
  811. var initCaption = function() {
  812. // get all <track> elements
  813. acorn.$track = $('track', acorn.$self);
  814. // if there is at least one <track> element, show the Caption Button
  815. if(acorn.$track.length) {
  816. acorn.$captionBtn.show();
  817. }
  818. // check if there is more than one <track> element
  819. // if there is more than one track element we'll create the Caption Selector
  820. if(acorn.$track.length>1) {
  821. // set a different "title" attribute
  822. acorn.$captionBtn.attr('title', text.captionsChoose);
  823. // markup for the Caption Selector
  824. var captionList = '<ul><li><label><input type="radio" name="' + captionRadioName + '" checked="true" />None</label></li>';
  825. acorn.$track.each(function() {
  826. var tracksrc = $(this).attr('src');
  827. captionList += '<li><label><input type="radio" name="' + captionRadioName + '" data-url="' + $(this).attr('src') + '" />' + $(this).attr('label') + '</label></li>';
  828. });
  829. captionList += '</ul>';
  830. // append the generated markup
  831. acorn.$captionSelector.html(captionList);
  832. // change selected caption
  833. var changeCaption = function() {
  834. // get the original <track> "src" attribute from the custom "data-url" attribute of the radio input
  835. var tracksrc = $(this).attr('data-url');
  836. if(tracksrc) {
  837. loadCaption(tracksrc);
  838. } else {
  839. // if there's not "data-url" attribute, turn off the caption
  840. captionOff();
  841. }
  842. };
  843. // attach event handler
  844. $('input[name=' + captionRadioName + ']', acorn.$container).change(changeCaption);
  845. // initialize Caption Selector
  846. initCaptionSelector();
  847. // load first caption if captionsOn is true
  848. var firstCaption = acorn.$track.first().attr('src');
  849. if(options.captionsOn) {
  850. loadCaption(firstCaption);
  851. $('input[name=' + captionRadioName + ']', acorn.$container).removeAttr('checked');
  852. $('input[name=' + captionRadioName + ']:eq(1)', acorn.$container).attr('checked', 'true');
  853. };
  854. } else if(acorn.$track.length) {
  855. // if there's only one <track> element
  856. // load the specific caption when activating the Caption Button
  857. var tracksrc = acorn.$track.attr('src');
  858. acorn.$captionBtn.bind('click', function() {
  859. if($(this).hasClass(captionBtnActiveClass)) {
  860. captionOff();
  861. } else {
  862. loadCaption(tracksrc);
  863. }
  864. $(this).toggleClass(captionBtnActiveClass);
  865. });
  866. // load default caption if captionsOn is true
  867. if(options.captionsOn) loadCaption(tracksrc);
  868. }
  869. // attach event to Transcript Button
  870. acorn.$transcriptBtn.bind('click', showTranscript);
  871. };
  872. /*
  873. * Initialization self-invoking function
  874. * Runs other initialization functions, attaches events, removes native controls
  875. */
  876. var init = function() {
  877. // attach playback handlers
  878. acorn.$playBtn.bind( 'touchstart click', playMedia);
  879. acorn.$self.bind( 'touchstart click' , playMedia);
  880. acorn.$self.bind('play', startPlayback);
  881. acorn.$self.bind('pause', stopPlayback);
  882. acorn.$self.bind('ended', stopPlayback);
  883. // update the Seek Slider when timeupdate is triggered
  884. acorn.$self.bind('timeupdate', seekUpdate);
  885. // bind Fullscreen Button
  886. acorn.$fullscreenBtn.click(goFullscreen);
  887. // bind Swap Button
  888. acorn.$swapBtn.click(goSwap);
  889. // initialize volume controls
  890. initVolume();
  891. // add the loading class
  892. $wrapper.addClass('');
  893. if(!options.nativeSliders) initSeek();
  894. // once the metadata has loaded
  895. acorn.$self.bind('loadedmetadata', function() {
  896. /* I use an interval to make sure the video has the right readyState
  897. * to bypass a known webkit bug that causes loadedmetadata to be triggered
  898. * before the duration is available
  899. */
  900. var t = window.setInterval(function() {
  901. if (acorn.$self[0].readyState > 0) {
  902. loadedMetadata = true;
  903. updateSeek();
  904. clearInterval(t);
  905. }
  906. }, 500);
  907. initCaption();
  908. });
  909. // trigger update seek manualy for the first time, for iOS support
  910. updateSeek();
  911. // remove the native controls
  912. acorn.$self.removeAttr('controls');
  913. if(acorn.$self.is('audio')) {
  914. /*
  915. * If the media is <audio>, we're adding the 'audio-player' class to the element.
  916. * This is because Opera 10.62 does not allow the <audio> element to be targeted by CSS
  917. * and this can cause problems with themeing.
  918. */
  919. acorn.$container.addClass('audio-player');
  920. }
  921. }();
  922. };
  923. // iterate and reformat each matched element
  924. return this.each(acornPlayer);
  925. };
  926. })(jQuery);
  927. /*
  928. * parseSrt function
  929. * JavaScript SRT parser by Silvia Pfeiffer <silvia@siliva-pfeiffer.de>
  930. * http://silvia-pfeiffer.de/
  931. *
  932. * Tri-licensed under MPL 1.1/GPL 2.0/LGPL 2.1
  933. * http://www.gnu.org/licenses/gpl.html
  934. * http://www.gnu.org/licenses/lgpl.html
  935. * http://www.mozilla.org/MPL/
  936. *
  937. * The Initial Developer of the Original Code is Mozilla Corporation.
  938. * Portions created by the Initial Developer are Copyright (C) 2009
  939. * the Initial Developer. All Rights Reserved.
  940. *
  941. * Contributor(s):
  942. * Silvia Pfeiffer <silvia@siliva-pfeiffer.de>
  943. *
  944. *
  945. */
  946. function parseSrt(data) {
  947. var srt = data.replace(/\r+/g, ''); // remove dos newlines
  948. srt = srt.replace(/^\s+|\s+$/g, ''); // trim white space start and end
  949. srt = srt.replace(/<[a-zA-Z\/][^>]*>/g, ''); // remove all html tags for security reasons
  950. // get captions
  951. var captions = [];
  952. var caplist = srt.split('\n\n');
  953. for (var i = 0; i < caplist.length; i=i+1) {
  954. var caption = "";
  955. var content, start, end, s;
  956. caption = caplist[i];
  957. s = caption.split(/\n/);
  958. if (s[0].match(/^\d+$/) && s[1].match(/\d+:\d+:\d+/)) {
  959. // ignore caption number in s[0]
  960. // parse time string
  961. var m = s[1].match(/(\d+):(\d+):(\d+)(?:,(\d+))?\s*--?>\s*(\d+):(\d+):(\d+)(?:,(\d+))?/);
  962. if (m) {
  963. start =
  964. (parseInt(m[1], 10) * 60 * 60) +
  965. (parseInt(m[2], 10) * 60) +
  966. (parseInt(m[3], 10)) +
  967. (parseInt(m[4], 10) / 1000);
  968. end =
  969. (parseInt(m[5], 10) * 60 * 60) +
  970. (parseInt(m[6], 10) * 60) +
  971. (parseInt(m[7], 10)) +
  972. (parseInt(m[8], 10) / 1000);
  973. } else {
  974. // Unrecognized timestring
  975. continue;
  976. }
  977. // concatenate text lines to html text
  978. content = s.slice(2).join("<br>");
  979. } else {
  980. // file format error or comment lines
  981. continue;
  982. }
  983. captions.push({start: start, end: end, content: content});
  984. }
  985. return captions;
  986. }