/*
// 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”;
(function ($) {
$(document).ready(function () { $('video, audio').each(function (index, element) { if ($(element).data('able-player') !== undefined) { 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; } // 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 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 ($(media).data('transcript-div') !== undefined && $(media).data('transcript-div') !== "") { this.transcriptDivLocation = $(media).data('transcript-div'); this.transcriptType = 'external'; } else if ($(media).data('include-transcript') !== undefined) { if ($(media).data('include-transcript') !== false) { this.transcriptType = 'popup'; } } 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; } // Previous/Next buttons // valid values of data-prevnext-unit are 'playlist' and 'chapter'; will also accept 'chapters' if ($(media).data('prevnext-unit') === 'chapter' || $(media).data('prevnext-unit') === 'chapters') { this.prevNextUnit = 'chapter'; } else if ($(media).data('prevnext-unit') === 'playlist') { this.prevNextUnit = 'playlist'; } else { this.prevNextUnit = false; } // 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'); } // 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; 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 <audio> or <video> element. // 2. A standard localized message (see buildplayer.js > provideFallback() // The data-test-fallback attribute can be used to test the fallback solution in any browser if ($(media).data('test-fallback') !== undefined && $(media).data('test-fallback') !== false) { this.testFallback = true; } // Language this.lang = 'en'; if ($(media).data('lang') !== undefined && $(media).data('lang') !== "") { var lang = $(media).data('lang'); if (lang.length == 2) { this.lang = lang; } } // Player language is determined as follows (in translation.js > getTranslationText() ): // 1. Lang attributes on <html> or <body>, if a matching translation file is available // 2. The value of this.lang, if a matching translation file is available // 3. English // To override this formula and force #2 to take precedence over #1, set data-force-lang="true" if ($(media).data('force-lang') !== undefined && $(media).data('force-lang') !== false) { this.forceLang = true; } else { this.forceLang = false; } // Metadata Tracks if ($(media).data('meta-type') !== undefined && $(media).data('meta-type') !== "") { this.metaType = $(media).data('meta-type'); } if ($(media).data('meta-div') !== undefined && $(media).data('meta-div') !== "") { this.metaDiv = $(media).data('meta-div'); } // Search if ($(media).data('search') !== undefined && $(media).data('search') !== "") { // conducting a search currently requires an external div in which to write the results if ($(media).data('search-div') !== undefined && $(media).data('search-div') !== "") { this.searchString = $(media).data('search'); this.searchDiv = $(media).data('search-div'); } // Search Language if ($(media).data('search-lang') !== undefined && $(media).data('search-lang') !== "") { this.searchLang = $(media).data('search-lang'); } else { this.searchLang = null; // will change to final value of this.lang in translation.js > getTranslationText() } // conducting a search currently requires an external div in which to write the results if ($(media).data('search-div') !== undefined && $(media).data('search-div') !== "") { this.searchString = $(media).data('search'); this.searchDiv = $(media).data('search-div'); } } // Hide controls when video starts playing // They will reappear again when user presses a key or moves the mouse // As of v4.0, controls are hidden automatically on playback in fullscreen mode if ($(media).data('hide-controls') !== undefined && $(media).data('hide-controls') !== false) { this.hideControls = true; this.hideControlsOriginal = true; // a copy of hideControls, since the former may change if user enters full screen mode } else { this.hideControls = false; this.hideControlsOriginal = false; } // Define built-in variables that CANNOT be overridden with HTML attributes this.setDefaults(); //////////////////////////////////////// // // End assignment of default variables // //////////////////////////////////////// this.ableIndex = AblePlayer.nextIndex; AblePlayer.nextIndex += 1; this.title = $(media).attr('title'); // populate translation object with localized versions of all labels and prompts // use defer method to defer additional processing until text is retrieved this.tt = {}; var thisObj = this; $.when(this.getTranslationText()).then( function () { if (thisObj.countProperties(thisObj.tt) > 50) { // close enough to ensure that most text variables are populated thisObj.setup(); } else { // can't continue loading player with no text thisObj.provideFallback(); } } ); }; // Index to increment every time new player is created. AblePlayer.nextIndex = 0; AblePlayer.prototype.setup = function() { var thisObj = this; this.initializing = true; // will remain true until entire sequence of function calls is complete this.reinitialize().then(function () { if (!thisObj.player) { // No player for this media, show last-line fallback. thisObj.provideFallback(); } else { thisObj.setupInstance().then(function () { thisObj.setupInstancePlaylist(); if (!thisObj.hasPlaylist) { // for playlists, recreatePlayer() is called from within cuePlaylistItem() thisObj.recreatePlayer(); } thisObj.initializing = false; thisObj.playerCreated = true; // remains true until browser is refreshed }); } }); }; AblePlayer.getActiveDOMElement = function () { var activeElement = document.activeElement; // For shadow DOMs we need to keep digging down through the DOMs while (activeElement.shadowRoot && activeElement.shadowRoot.activeElement) { activeElement = activeElement.shadowRoot.activeElement; } return activeElement; }; AblePlayer.localGetElementById = function(element, id) { if (element.getRootNode) { // Use getRootNode() and querySelector() where supported (for shadow DOM support) return $(element.getRootNode().querySelector('#' + id)); } else { // If getRootNode is not supported it should be safe to use document.getElementById (since there is no shadow DOM support) return $(document.getElementById(id)); } }; AblePlayer.youtubeIframeAPIReady = false; AblePlayer.loadingYoutubeIframeAPI = false;
})(jQuery);