/* // JavaScript for Able Player // HTML5 Media API: // http://www.w3.org/TR/html5/embedded-content-0.html#htmlmediaelement // http://dev.w3.org/html5/spec-author-view/video.html // W3C API Test Page: // http://www.w3.org/2010/05/video/mediaevents.html // YouTube Player API for iframe Embeds https://developers.google.com/youtube/iframe_api_reference // YouTube Player Parameters https://developers.google.com/youtube/player_parameters?playerVersion=HTML5 // YouTube Data API https://developers.google.com/youtube/v3 // Vimeo Player API https://github.com/vimeo/player.js // Google API Client Library for JavaScript https://developers.google.com/api-client-library/javascript/dev/dev_jscript // Google API Explorer: YouTube services and methods https://developers.google.com/apis-explorer/#s/youtube/v3/ // Web Speech API (Speech Synthesis) // https://w3c.github.io/speech-api/#tts-section // https://developer.mozilla.org/en-US/docs/Web/API/Window/speechSynthesis */ /*jslint node: true, browser: true, white: true, indent: 2, unparam: true, plusplus: true */ /*global $, jQuery */ "use strict"; // maintain an array of Able Player instances for use globally (e.g., for keeping prefs in sync) var AblePlayerInstances = []; (function ($) { $(document).ready(function () { $('video, audio').each(function (index, element) { if ($(element).data('able-player') !== undefined) { AblePlayerInstances.push(new AblePlayer($(this),$(element))); } }); }); // YouTube player support; pass ready event to jQuery so we can catch in player. window.onYouTubeIframeAPIReady = function() { AblePlayer.youtubeIframeAPIReady = true; $('body').trigger('youtubeIframeAPIReady', []); }; // If there is only one player on the page, dispatch global keydown events to it // Otherwise, keydowwn events are handled locally (see event.js > handleEventListeners()) $(window).keydown(function(e) { if (AblePlayer.nextIndex === 1) { AblePlayer.lastCreated.onPlayerKeyPress(e); } }); // Construct an AblePlayer object // Parameters are: // media - jQuery selector or element identifying the media. window.AblePlayer = function(media) { // Keep track of the last player created for use with global events. AblePlayer.lastCreated = this; this.media = media; if ($(media).length === 0) { this.provideFallback(); return; } /////////////////////////////// // // Default variables assignment // /////////////////////////////// // The following variables CAN be overridden with HTML attributes // autoplay (Boolean; if present always resolves to true, regardless of value) if ($(media).attr('autoplay') !== undefined) { this.autoplay = true; // this value remains constant this.okToPlay = true; // this value can change dynamically } else { this.autoplay = false; this.okToPlay = false; } // loop (Boolean; if present always resolves to true, regardless of value) if ($(media).attr('loop') !== undefined) { this.loop = true; } else { this.loop = false; } // playsinline (Boolean; if present always resolves to true, regardless of value) if ($(media).attr('playsinline') !== undefined) { this.playsInline = '1'; // this value gets passed to YT.Player contructor in youtube.js } else { this.playsInline = '0'; } // poster (Boolean, indicating whether media element has a poster attribute) if ($(media).attr('poster')) { this.hasPoster = true; } else { this.hasPoster = false; } // start-time if ($(media).data('start-time') !== undefined && $.isNumeric($(media).data('start-time'))) { this.startTime = $(media).data('start-time'); } else { this.startTime = 0; } // debug if ($(media).data('debug') !== undefined && $(media).data('debug') !== false) { this.debug = true; } else { this.debug = false; } // Path to root directory of Able Player code if ($(media).data('root-path') !== undefined) { // add a trailing slash if there is none this.rootPath = $(media).data('root-path').replace(/\/?$/, '/'); } else { this.rootPath = this.getRootPath(); } // Volume // Range is 0 to 10. Best not to crank it to avoid overpowering screen readers this.defaultVolume = 7; if ($(media).data('volume') !== undefined && $(media).data('volume') !== "") { var volume = $(media).data('volume'); if (volume >= 0 && volume <= 10) { this.defaultVolume = volume; } } this.volume = this.defaultVolume; // Optional Buttons // Buttons are added to the player controller if relevant media is present // However, in some applications it might be undesirable to show buttons // (e.g., if chapters or transcripts are provided in an external container) if ($(media).data('use-chapters-button') !== undefined && $(media).data('use-chapters-button') === false) { this.useChaptersButton = false; } else { this.useChaptersButton = true; } if ($(media).data('use-descriptions-button') !== undefined && $(media).data('use-descriptions-button') === false) { this.useDescriptionsButton = false; } else { this.useDescriptionsButton = true; } // Silence audio description // set to "false" if the sole purposes of the WebVTT descriptions file // is to display description text visibly and to integrate it into the transcript if ($(media).data('descriptions-audible') !== undefined && $(media).data('descriptions-audible') === false) { this.exposeTextDescriptions = false; } else if ($(media).data('description-audible') !== undefined && $(media).data('description-audible') === false) { // support both singular and plural spelling of attribute this.exposeTextDescriptions = false; } else { this.exposeTextDescriptions = true; } // Headings // By default, an off-screen heading is automatically added to the top of the media player // It is intelligently assigned a heading level based on context, via misc.js > getNextHeadingLevel() // Authors can override this behavior by manually assigning a heading level using data-heading-level // Accepted values are 1-6, or 0 which indicates "no heading" // (i.e., author has already hard-coded a heading before the media player; Able Player doesn't need to do this) if ($(media).data('heading-level') !== undefined && $(media).data('heading-level') !== "") { var headingLevel = $(media).data('heading-level'); if (/^[0-6]*$/.test(headingLevel)) { // must be a valid HTML heading level 1-6; or 0 this.playerHeadingLevel = headingLevel; } } // Transcripts // There are three types of interactive transcripts. // In descending of order of precedence (in case there are conflicting tags), they are: // 1. "manual" - A manually coded external transcript (requires data-transcript-src) // 2. "external" - Automatically generated, written to an external div (requires data-transcript-div) // 3. "popup" - Automatically generated, written to a draggable, resizable popup window that can be toggled on/off with a button // If data-include-transcript="false", there is no "popup" transcript if ($(media).data('transcript-div') !== undefined && $(media).data('transcript-div') !== "") { this.transcriptDivLocation = $(media).data('transcript-div'); } else { this.transcriptDivLocation = null; } if ($(media).data('include-transcript') !== undefined && $(media).data('include-transcript') === false) { this.hideTranscriptButton = true; } else { this.hideTranscriptButton = null; } this.transcriptType = null; if ($(media).data('transcript-src') !== undefined) { this.transcriptSrc = $(media).data('transcript-src'); if (this.transcriptSrcHasRequiredParts()) { this.transcriptType = 'manual'; } } else if ($(media).find('track[kind="captions"], track[kind="subtitles"]').length > 0) { // required tracks are present. COULD automatically generate a transcript if (this.transcriptDivLocation) { this.transcriptType = 'external'; } else { this.transcriptType = 'popup'; } } // In "Lyrics Mode", line breaks in WebVTT caption files are supported in the transcript // If false (default), line breaks are are removed from transcripts in order to provide a more seamless reading experience // If true, line breaks are preserved, so content can be presented karaoke-style, or as lines in a poem if ($(media).data('lyrics-mode') !== undefined && $(media).data('lyrics-mode') !== false) { this.lyricsMode = true; } else { this.lyricsMode = false; } // Transcript Title if ($(media).data('transcript-title') !== undefined && $(media).data('transcript-title') !== "") { this.transcriptTitle = $(media).data('transcript-title'); } else { // do nothing. The default title will be defined later (see transcript.js) } // Captions // data-captions-position can be used to set the default captions position // this is only the default, and can be overridden by user preferences // valid values of data-captions-position are 'below' and 'overlay' if ($(media).data('captions-position') === 'overlay') { this.defaultCaptionsPosition = 'overlay'; } else { // the default, even if not specified this.defaultCaptionsPosition = 'below'; } // Chapters if ($(media).data('chapters-div') !== undefined && $(media).data('chapters-div') !== "") { this.chaptersDivLocation = $(media).data('chapters-div'); } if ($(media).data('chapters-title') !== undefined) { // NOTE: empty string is valid; results in no title being displayed this.chaptersTitle = $(media).data('chapters-title'); } if ($(media).data('chapters-default') !== undefined && $(media).data('chapters-default') !== "") { this.defaultChapter = $(media).data('chapters-default'); } else { this.defaultChapter = null; } // Slower/Faster buttons // valid values of data-speed-icons are 'animals' (default) and 'arrows' // 'animals' uses turtle and rabbit; 'arrows' uses up/down arrows if ($(media).data('speed-icons') === 'arrows') { this.speedIcons = 'arrows'; } else { this.speedIcons = 'animals'; } // Seekbar // valid values of data-seekbar-scope are 'chapter' and 'video'; will also accept 'chapters' if ($(media).data('seekbar-scope') === 'chapter' || $(media).data('seekbar-scope') === 'chapters') { this.seekbarScope = 'chapter'; } else { this.seekbarScope = 'video'; } // YouTube if ($(media).data('youtube-id') !== undefined && $(media).data('youtube-id') !== "") { this.youTubeId = $(media).data('youtube-id'); } if ($(media).data('youtube-desc-id') !== undefined && $(media).data('youtube-desc-id') !== "") { this.youTubeDescId = $(media).data('youtube-desc-id'); } if ($(media).data('youtube-nocookie') !== undefined && $(media).data('youtube-nocookie')) { this.youTubeNoCookie = true; } else { this.youTubeNoCookie = false; } // Vimeo if ($(media).data('vimeo-id') !== undefined && $(media).data('vimeo-id') !== "") { this.vimeoId = $(media).data('vimeo-id'); } if ($(media).data('vimeo-desc-id') !== undefined && $(media).data('vimeo-desc-id') !== "") { this.vimeoDescId = $(media).data('vimeo-desc-id'); } // Skin // valid values of data-skin are: // 'legacy' (default), two rows of controls; seekbar positioned in available space within top row // '2020', all buttons in one row beneath a full-width seekbar if ($(media).data('skin') == '2020') { this.skin = '2020'; } else { this.skin = 'legacy'; } // Icon type // By default, AblePlayer 3.0.33 and higher uses SVG icons for the player controls // Fallback for browsers that don't support SVG is scalable icomoon fonts // Ultimate fallback is images, if the user has a custom style sheet that overrides font-family // Use data-icon-type to force controls to use either 'svg', 'font', or 'images' this.iconType = 'font'; this.forceIconType = false; if ($(media).data('icon-type') !== undefined && $(media).data('icon-type') !== "") { var iconType = $(media).data('icon-type'); if (iconType === 'font' || iconType == 'image' || iconType == 'svg') { this.iconType = iconType; this.forceIconType = true; } } if ($(media).data('allow-fullscreen') !== undefined && $(media).data('allow-fullscreen') === false) { this.allowFullScreen = false; } else { this.allowFullScreen = true; } // Seek interval // Number of seconds to seek forward or back with Rewind & Forward buttons // Unless specified with data-seek-interval, the default value is re-calculated in initialize.js > setSeekInterval(); // Calculation attempts to intelligently assign a reasonable interval based on media length this.defaultSeekInterval = 10; this.useFixedSeekInterval = false; // will change to true if media has valid data-seek-interval attribute if ($(media).data('seek-interval') !== undefined && $(media).data('seek-interval') !== "") { var seekInterval = $(media).data('seek-interval'); if (/^[1-9][0-9]*$/.test(seekInterval)) { // must be a whole number greater than 0 this.seekInterval = seekInterval; this.useFixedSeekInterval = true; // do not override with calculuation } } // Now Playing // Shows "Now Playing:" plus the title of the current track above player // Only used if there is a playlist if ($(media).data('show-now-playing') !== undefined && $(media).data('show-now-playing') === false) { this.showNowPlaying = false; } else { this.showNowPlaying = true; } // TTML support (experimental); enabled for testing with data-use-ttml (Boolean) if ($(media).data('use-ttml') !== undefined) { this.useTtml = true; // The following may result in a console error. this.convert = require('xml-js'); } else { this.useTtml = false; } // Fallback // The only supported fallback content as of version 4.0 is: // 1. Content nested within the