From cb7bcdf21cd807d33b9efaefa553316140b822f0 Mon Sep 17 00:00:00 2001 From: rianrietveld Date: Mon, 9 Feb 2026 09:06:49 +0100 Subject: [PATCH 01/72] Update Dutch translation Additional: changes some existing text for better consistancy and a more fluent Dutch translation. Also fixed some typos. --- translations/nl.json | 156 +++++++++++++++++++++---------------------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/translations/nl.json b/translations/nl.json index 79d5bc47..ee50c8f4 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -2,17 +2,17 @@ "audio": "audio", "video": "video", "playerHeading": "Mediaspeler", - "audioPlayer": "Audio player", - "videoPlayer": "Video player", + "audioPlayer": "Audiospeler", + "videoPlayer": "Videospeler", "faster": "Sneller", "slower": "Langzamer", "play": "Afspelen", "pause": "Pauzeren", "stop": "Stoppen", "restart": "Herstarten", - "prevChapter": "Vorige Hoofdstuk", - "nextChapter": "Volgend Hoofdstuk", - "prevTrack": "Vorige Track", + "prevChapter": "Vorige hoofdstuk", + "nextChapter": "Volgend hoofdstuk", + "prevTrack": "Vorige track", "nextTrack": "Volgend track", "rewind": "Terug", "forward": "Verder", @@ -41,14 +41,14 @@ "enterFullScreen": "Ga naar volledig scherm", "exitFullScreen": "Verlaat volledig scherm", "fullScreen": "Volledig scherm", - "fullScreenTitle": "Full screen video player", + "fullScreenTitle": "Videospeler in volledig scherm", "speed": "Snelheid", "and": "en", "or": "of", "spacebar": "spatietoets", "transcriptTitle": "Transcript", "lyricsTitle": "Tekst", - "autoScroll": "Auto scroll", + "autoScroll": "Auto-scroll", "unknown": "Onbekend", "statusPlaying": "Aan het spelen", "statusPaused": "Gepauzeerd", @@ -61,29 +61,29 @@ "statusLoadingNoDesc": "Versie zonder beschrijving wordt geladen", "statusLoadingNext": "Volgende track wordt geladen", "statusEnd": "Einde van track", - "selectedTrack": "Geselecteerde Track", + "selectedTrack": "Geselecteerde track", "alertDescribedVersion": "Versie met audiobeschrijving wordt gebruikt", "alertNonDescribedVersion": "Versie zonder audiobeschrijving wordt gebruikt", "fallbackError1": "Sorry, je browser kan dit mediabestand niet afspelen", "fallbackError2": "De volgende browsers kunnen met deze mediaspeler overweg:", "orHigher": "of hoger", - "prefMenuCaptions": "Ondertitels", + "prefMenuCaptions": "Ondertiteling", "prefMenuDescriptions": "Beschrijvingen", "prefMenuKeyboard": "Toetsenbord", "prefMenuTranscript": "Transcript", - "prefTitleCaptions": "Ondertitels Voorkeuren", - "prefTitleDescriptions": "Audiobeschrijving Voorkeuren", - "prefTitleKeyboard": "Toetsenbord Voorkeuren", - "prefTitleTranscript": "Transcript Voorkeuren", - "prefIntroCaptions": "De volgende set voorkeuren regels hoe ondertitels getoond worden.", + "prefTitleCaptions": "Voorkeuren ondertiteling", + "prefTitleDescriptions": "Voorkeuren audiobeschrijving", + "prefTitleKeyboard": "Voorkeuren toetsenbord", + "prefTitleTranscript": "Voorkeuren transcript", + "prefIntroCaptions": "De volgende set voorkeuren regels hoe ondertiteling getoond worden.", "prefIntroDescription1": "Deze video speler ondersteund audiobeschrijving op twee manieren: ", "prefIntroDescription2": "De huidige video heeft ", "prefIntroDescriptionNone": "De huidige video heeft in beide formaten geen audiobeschrijving.", "prefIntroDescription3": "Gebruik het volgende formulier om je voorkeuren gerelateerd aan tekst-gebaseerde audiobeschrijving in te stellen.", - "prefIntroDescription4": "Na het opslaan van je instellingen, kan audiobeschrijving aan of uit gezet worden met de Beschrijving knop.", + "prefIntroDescription4": "Na het opslaan van je instellingen, kan audiobeschrijving aan of uit gezet worden met de Beschrijving-knop.", "prefIntroKeyboard1": "De mediaspeler op deze pagina kan vanaf elke plek binnen de pagina bestuurd worden met de toetsenbord sneltoetsen (zie de lijst hieronder).", "prefIntroKeyboard2": "Mutatie knoppen (Shift, Alt, and Control) kunnen hier onder toegekend worden.", - "prefIntroKeyboard3": "LET OP: Sommige toetsencombinaties kunnen conflicteren met de toetsen die gebruikt worden door de browser en/of andere software applicaties. Probeer verscheidene combinaties van mutatie knoppen om er een combinatie te vinden die voor jou werkt.", + "prefIntroKeyboard3": "LET OP: Sommige toetsencombinaties kunnen conflicteren met de toetsen die gebruikt worden door de browser en/of andere software applicaties. Probeer verscheidene combinaties van de mutatie knoppen om een combinatie te vinden die voor jou werkt.", "prefIntroTranscript": "De volgende voorkeuren beïnvloeden het interactieve transcript.", "prefCookieWarning": "Het opslaan van je voorkeuren vereist cookies.", "prefHeadingKeyboard1": "Mutatie knoppen gebruikt voor sneltoetsen", @@ -91,37 +91,37 @@ "prefHeadingDescription": "Audiobeschrijving", "prefHeadingTextDescription": "Tekst-gebaseerde audiobeschrijving", "prefHeadingCaptions": "Ondertiteling", - "prefHeadingTranscript": "Interactieve Transcript", + "prefHeadingTranscript": "Interactief transcript", "prefAltKey": "Alt", "prefCtrlKey": "Control", "prefShiftKey": "Shift", - "prefNoKeyShortcuts": "Disable keyboard shortcuts", + "prefNoKeyShortcuts": "Schakel sneltoetsen uit", "escapeKey": "Escape", - "escapeKeyFunction": "Sluit huidige dialoog of popup menu", - "prefDescFormat": "Voorkeurs formaat", + "escapeKeyFunction": "Sluit huidige dialoog of pop-up menu", + "prefDescFormat": "Voorkeur formaat", "prefDescFormatHelp": "Als beide formaten beschikbaar zijn zal er slechts een enkele gebruikt worden.", "prefDescFormatOption1": "Alternatieve beschreven versie van de video", "prefDescFormatOption1b": "een alternatief beschreven versie", - "prefDescFormatOption2": "op tekst gebaseerde beschrijving, aangekondigd door de screen reader", + "prefDescFormatOption2": "op tekst gebaseerde beschrijving, uitgesproken door de schermlezer", "prefDescFormatOption2b": "op tekst gebaseerde beschrijving", - "prefDescPause": "Pauzeer video automatisch als beschrijving aan wordt gezet", + "prefDescPause": "Pauzeer de video automatisch als de beschrijving aan wordt gezet", "prefDescVisible": "Als er een tekst-gebaseerde beschrijving is, maak deze dan zichtbaar", - "prefDescVoice": "Voice", - "prefDescRate": "Rate", - "prefDescPitch": "Pitch", - "prefDescPitch1": "Very low", - "prefDescPitch2": "Low", - "prefDescPitch3": "Default", - "prefDescPitch4": "High", - "prefDescPitch5": "Very high", - "sampleDescriptionText": "Adjust settings to hear this sample text.", - "prefHighlight": "Transcript highlighten terwijl media speelt", + "prefDescVoice": "Stem", + "prefDescRate": "Snelheid", + "prefDescPitch": "Toonhoogte", + "prefDescPitch1": "Zeer laag", + "prefDescPitch2": "Laag", + "prefDescPitch3": "Normaal", + "prefDescPitch4": "Hoof", + "prefDescPitch5": "Zeer hoog", + "sampleDescriptionText": "Pas de instellingen aan om deze voorbeeldtekst te beluisteren.", + "prefHighlight": "Highlight transcript terwijl media speelt", "prefTabbable": "Maak transcript bedienbaar met toetsenbord", "prefCaptionsFont": "Lettertype", - "prefCaptionsColor": "Tekst Kleur", - "prefCaptionsBGColor": "Achtergrond", - "prefCaptionsSize": "Lettertype Groote", - "prefCaptionsOpacity": "Doorzichtigheid", + "prefCaptionsColor": "Tekstkleur", + "prefCaptionsBGColor": "Achtergrondkleur", + "prefCaptionsSize": "Grootte lettertype ", + "prefCaptionsOpacity": "Ondoorzichtigheid", "prefCaptionsStyle": "Stijl", "serif": "serif", "sans": "sans-serif", @@ -143,7 +143,7 @@ "prefCaptionsPosition": "Positie", "captionsPositionOverlay": "Overlay", "captionsPositionBelow": "Onder video", - "sampleCaptionText": "Voorbeeld ondertitel tekst", + "sampleCaptionText": "Voorbeeld ondertiteling", "prefSuccess": "Je wijzigingen zijn opgeslagen.", "prefNoChange": "Je hebt geen wijzigingen gemaakt.", "help": "Help", @@ -153,56 +153,56 @@ "ok": "ok", "done": "Klaar", "closeButtonLabel": "Sluit venster", - "dismissButton": "Dismiss", - "windowButtonLabel": "Venster instellingen", + "dismissButton": "Verwijder", + "windowButtonLabel": "Instellingen venster", "windowMove": "Verplaats", - "windowMoveLeft": "Window moved left", - "windowMoveRight": "Window moved right", - "windowMoveUp": "Window moved up", - "windowMoveDown": "Window moved down", - "windowMoveStopped": "Window move stopped", - "transcriptControls": "Transcript Window Controls", - "signControls": "Sign Language Window Controls", + "windowMoveLeft": "Venster naar links verschoven", + "windowMoveRight": "Venster naar rechts verschoven", + "windowMoveUp": "Venster omhoog geschoven.", + "windowMoveDown": "Venster omlaag geschoven.", + "windowMoveStopped": "Verplaatsing venster gestopt", + "transcriptControls": "Bediening venster transcript", + "signControls": "Bediening venster gebarentaal", "windowMoveAlert": "Versleep of gebruik de pijltjestoetsen om te verplaatsen. Druk op Enter om te stoppen.", "windowResize": "Verkleinen of vergroten", - "windowResizeHeading": "Verander grootte van scherm met gebarentolk", + "windowResizeHeading": "Verander grootte van venster", "windowResizeAlert": "Het venster is van grootte veranderd.", "windowClose": "Sluit", "width": "Breedte", "height": "Hoogte", "windowSendBack": "Verplaats naar achteren", - "windowSendBackAlert": "Het scherm staat nu achter andere objecten op deze pagina.", + "windowSendBackAlert": "Het venster staat nu achter andere objecten op deze pagina.", "windowBringTop": "Verplaats naar voren", - "windowBringTopAlert": "Het scherm staat nu voor andere objecten op deze pagina.", - "resultsSummary1": "You searched for:", - "resultsSummary2": "Found %1 matching items.", - "resultsSummary3": "Click the time associated with any item to play the video from that point.", - "noResultsFound": "No results found.", - "searchButtonLabel": "Play at %1", - "hour": "hour", - "minute": "minute", - "second": "second", + "windowBringTopAlert": "Het venster staat nu voor andere objecten op deze pagina.", + "resultsSummary1": "Je zocht naar:", + "resultsSummary2": "Er zijn %1 overeenkomende items gevonden.", + "resultsSummary3": "Klik op de tijd die bij een item staat om de video vanaf dat punt af te spelen.", + "noResultsFound": "Geen resultaten gevonden.", + "searchButtonLabel": "Speel af op %1", + "hour": "uur", + "minute": "minuut", + "second": "seconde", "hours": "hours", - "minutes": "minutes", - "seconds": "seconds", - "enableKeyboardShortcuts": "Enable keyboard shortcuts", + "minutes": "minuten", + "seconds": "seconden", + "enableKeyboardShortcuts": "Sneltoetsen toetsenbord inschakelen", "vtsHeading": "Video Transcript Sorter", - "vtsInstructions1": "Use the Video Transcript Sorter to modify text tracks:", - "vtsInstructions2": "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.", - "vtsInstructions3": "Modify content or start/end times (all are directly editable within the table).", - "vtsInstructions4": "Add new content, such as chapters or descriptions.", - "vtsInstructions5": "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.", - "vtsSelectLanguage": "Select a language", - "vtsSave": "Generate new .vtt content", - "vtsReturn": "Return to Editor", - "vtsCancel": "Cancelling saving. Any edits you made have been restored in the VTS table.", - "vtsRow": "Row", - "vtsKind": "Kind", + "vtsInstructions1": "Gebruik de Video Transcript Sorter om teksttracks te wijzigen:", + "vtsInstructions2": "Herschik hoofdstukken, beschrijvingen, bijschriften en/of ondertiteling zodat ze in de juiste volgorde verschijnen in het automatisch gegenereerde transcript van Able Player.", + "vtsInstructions3": "Wijzig de inhoud of de begin-/eindtijden (alles is direct bewerkbaar in de tabel).", + "vtsInstructions4": "Voeg nieuwe inhoud toe, zoals hoofdstukken of beschrijvingen.", + "vtsInstructions5": "Klik na het bewerken op de knop 'Wijzigingen opslaan' om nieuwe inhoud te genereren voor alle relevante getimede tekstbestanden. De nieuwe tekst kan worden gekopieerd en geplakt in nieuwe WebVTT-bestanden.", + "vtsSelectLanguage": "Selecteer een taal", + "vtsSave": "Genereer nieuwe .vtt inhoud", + "vtsReturn": "Terug naar de editor", + "vtsCancel": "Opslaan annuleren. Alle bewerkingen die je hebt gedaan, zijn hersteld in de VTS-tabel", + "vtsRow": "Rij", + "vtsKind": "Sorteer", "vtsStart": "Start", - "vtsEnd": "End", - "vtsContent": "Content", - "vtsActions": "Actions", - "vtsNewRow": "A new row %1 has been inserted.", - "vtsDeletedRow": "Row %1 has been deleted.", - "vtsMovedRow": "Row %1 has been moved %2 and is now Row %3." -} \ No newline at end of file + "vtsEnd": "Eind", + "vtsContent": "Inhoud", + "vtsActions": "Acties", + "vtsNewRow": "Er is een nieuwe rij %1 ingevoegd.", + "vtsDeletedRow": "Rij %1 is verwijderd.", + "vtsMovedRow": "Rij %1 is verplaatst naar %2 en is nu rij %3." +} From 19d706397d008a7f2608ce1faadd2319adc77554 Mon Sep 17 00:00:00 2001 From: rianrietveld Date: Mon, 9 Feb 2026 09:16:13 +0100 Subject: [PATCH 02/72] Dutch translation Proper translation of prefIntroCaptions --- translations/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/nl.json b/translations/nl.json index ee50c8f4..352db728 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -75,7 +75,7 @@ "prefTitleDescriptions": "Voorkeuren audiobeschrijving", "prefTitleKeyboard": "Voorkeuren toetsenbord", "prefTitleTranscript": "Voorkeuren transcript", - "prefIntroCaptions": "De volgende set voorkeuren regels hoe ondertiteling getoond worden.", + "prefIntroCaptions": "De volgende voorkeuren bepalen hoe ondertiteling wordt weergegeven.", "prefIntroDescription1": "Deze video speler ondersteund audiobeschrijving op twee manieren: ", "prefIntroDescription2": "De huidige video heeft ", "prefIntroDescriptionNone": "De huidige video heeft in beide formaten geen audiobeschrijving.", From 373c1a5c196b8a3c38a34a87e7bf965fe0fff770 Mon Sep 17 00:00:00 2001 From: rianrietveld Date: Tue, 10 Feb 2026 06:47:40 +0100 Subject: [PATCH 03/72] Typos Dutch translation --- translations/nl.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/translations/nl.json b/translations/nl.json index 352db728..58282d11 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -76,7 +76,7 @@ "prefTitleKeyboard": "Voorkeuren toetsenbord", "prefTitleTranscript": "Voorkeuren transcript", "prefIntroCaptions": "De volgende voorkeuren bepalen hoe ondertiteling wordt weergegeven.", - "prefIntroDescription1": "Deze video speler ondersteund audiobeschrijving op twee manieren: ", + "prefIntroDescription1": "Deze videospeler ondersteund audiobeschrijving op twee manieren: ", "prefIntroDescription2": "De huidige video heeft ", "prefIntroDescriptionNone": "De huidige video heeft in beide formaten geen audiobeschrijving.", "prefIntroDescription3": "Gebruik het volgende formulier om je voorkeuren gerelateerd aan tekst-gebaseerde audiobeschrijving in te stellen.", @@ -112,7 +112,7 @@ "prefDescPitch1": "Zeer laag", "prefDescPitch2": "Laag", "prefDescPitch3": "Normaal", - "prefDescPitch4": "Hoof", + "prefDescPitch4": "Hoog", "prefDescPitch5": "Zeer hoog", "sampleDescriptionText": "Pas de instellingen aan om deze voorbeeldtekst te beluisteren.", "prefHighlight": "Highlight transcript terwijl media speelt", From 70cb4431c17b4b1d088ccc5c9f6be18e78532631 Mon Sep 17 00:00:00 2001 From: Vanessa Phipps Date: Fri, 27 Feb 2026 17:58:26 -0500 Subject: [PATCH 04/72] ableplayer-base: IIFE -> module --- scripts/ableplayer-base.js | 961 +++++++++++++++++++------------------ 1 file changed, 482 insertions(+), 479 deletions(-) diff --git a/scripts/ableplayer-base.js b/scripts/ableplayer-base.js index ba303400..ad858c29 100644 --- a/scripts/ableplayer-base.js +++ b/scripts/ableplayer-base.js @@ -1,17 +1,15 @@ /*jslint node: true, browser: true, white: true, indent: 2, unparam: true, plusplus: true */ -/*global $, jQuery */ -"use strict"; + +import $ from 'jquery'; // maintain an array of Able Player instances for use globally (e.g., for keeping prefs in sync) -var AblePlayerInstances = []; +const ablePlayerInstances = []; -(function ($) { - $(function () { - $('video, audio').each(function (index, element) { - if ($(element).data('able-player') !== undefined) { - AblePlayerInstances.push(new AblePlayer($(this),$(element))); - } - }); +function ablePlayerInitializeGlobals() { + $('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. @@ -19,6 +17,7 @@ var AblePlayerInstances = []; 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).on('keydown',function(e) { @@ -26,496 +25,500 @@ var AblePlayerInstances = []; AblePlayer.lastCreated.onPlayerKeyPress(e); } }); - - /** - * Construct the AblePlayer object. - * - * @param object media jQuery selector or element identifying the media. - */ - window.AblePlayer = function(media) { - - var thisObj = this; - - // 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) - this.loop = ($(media).attr('loop') !== undefined) ? true : false; - - // playsinline (Boolean; if present always resolves to true, regardless of value) - this.playsInline = ($(media).attr('playsinline') !== undefined) ? '1' : '0'; - - // poster (Boolean, indicating whether media element has a poster attribute) - this.hasPoster = ( $(media).attr('poster') || $(media).data('poster') ) ? true : false; - - this.audioPoster = $(media).data('poster'); - this.audioPosterAlt = $(media).data('poster-alt' ); - - // get height and width attributes, if present - // and add them to variables - // Not currently used, but might be useful for resizing player - this.width = $(media).attr('width') ?? 0; - this.height = $(media).attr('height') ?? 0; - - // start-time - var startTime = $(media).data('start-time'); - var isNumeric = ( typeof startTime === 'number' || ( typeof startTime === 'string' && value.trim() !== '' && ! isNaN(value) && isFinite( Number(value) ) ) ) ? true : false; - this.startTime = ( startTime !== undefined && isNumeric ) ? startTime : 0; - - // debug - this.debug = ($(media).data('debug') !== undefined && $(media).data('debug') !== false) ? true : 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; - } - - // Control whether text descriptions are read aloud - // set to "false" if the sole purpose of the WebVTT descriptions file - // is to integrate text description into the transcript - // set to "true" to write description text to a div - // This variable does *not* control the method by which description is read. - // For that, see below (this.descMethod) - if ($(media).data('descriptions-audible') !== undefined && $(media).data('descriptions-audible') === false) { - this.readDescriptionsAloud = false; - } else if ($(media).data('description-audible') !== undefined && $(media).data('description-audible') === false) { - // support both singular and plural spelling of attribute - this.readDescriptionsAloud = false; - } else { - this.readDescriptionsAloud = true; - } - - // setting initial this.descVoices to an empty array - // to be populated later by getBrowserVoices - this.descVoices = []; - - // Method by which text descriptions are read - // valid values of data-desc-reader are: - // 'brower' (default) - text-based audio description is handled by the browser, if supported - // 'screenreader' - text-based audio description is always handled by screen readers - // The latter may be preferable by owners of websites in languages that are not well supported - // by the Web Speech API - this.descReader = ($(media).data('desc-reader') == 'screenreader') ? 'screenreader' : 'browser'; - - // Default state of captions and descriptions - // This setting is overridden by user preferences, if they exist - // values for data-state-captions and data-state-descriptions are 'on' or 'off' - this.defaultStateCaptions = ($(media).data('state-captions') == 'off') ? 0 : 1; - this.defaultStateDescriptions = ($(media).data('state-descriptions') == 'on') ? 1 : 0; - - // Default setting for prefDescPause - // Extended description (i.e., pausing during description) is on by default - // but this settings give website owners control over that - // since they know the nature of their videos, and whether pausing is necessary - // This setting is overridden by user preferences, if they exist - this.defaultDescPause = ($(media).data('desc-pause-default') == 'off') ? 0 : 1; - - // 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 & a valid target element) - // 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 - var transcriptDivLocation = $(media).data('transcript-div'); - if ( transcriptDivLocation !== undefined && transcriptDivLocation !== "" && null !== document.getElementById( transcriptDivLocation ) ) { - this.transcriptDivLocation = transcriptDivLocation; - } else { - this.transcriptDivLocation = null; - } - var includeTranscript = $(media).data('include-transcript'); - this.hideTranscriptButton = ( includeTranscript !== undefined && includeTranscript === false) ? true : false; - - this.transcriptType = null; - if ($(media).data('transcript-src') !== undefined) { - this.transcriptSrc = $(media).data('transcript-src'); - if (this.transcriptSrcHasRequiredParts()) { - this.transcriptType = 'manual'; - } else { - console.log('ERROR: Able Player transcript is missing required parts'); - } - } else if ($(media).find('track[kind="captions"],track[kind="subtitles"],track:not([kind])').length > 0) { - // required tracks are present. COULD automatically generate a transcript - this.transcriptType = (this.transcriptDivLocation) ? 'external' : '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 for a more seamless reading experience - // If true, line breaks are preserved, so content can be presented karaoke-style, or as lines in a poem - this.lyricsMode = ($(media).data('lyrics-mode') !== undefined && $(media).data('lyrics-mode') !== false) ? true : false; - - // Set Transcript Title if defined explicitly. See transcript.js. - if ($(media).data('transcript-title') !== undefined && $(media).data('transcript-title') !== "") { - this.transcriptTitle = $(media).data('transcript-title'); - } - - // Sign Language - // sign language can be a modal (default) or assigned to a div on the page. - var signDivLocation = $(media).data('sign-div'); - if ( signDivLocation !== undefined && signDivLocation !== "" && null !== document.getElementById( signDivLocation ) ) { - this.$signDivLocation = $( '#' + signDivLocation ); +} + +/** + * Construct the AblePlayer object. + * + * @param object media jQuery selector or element identifying the media. + */ +function AblePlayer(media) { + + var thisObj = this; + + // 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) + this.loop = ($(media).attr('loop') !== undefined) ? true : false; + + // playsinline (Boolean; if present always resolves to true, regardless of value) + this.playsInline = ($(media).attr('playsinline') !== undefined) ? '1' : '0'; + + // poster (Boolean, indicating whether media element has a poster attribute) + this.hasPoster = ( $(media).attr('poster') || $(media).data('poster') ) ? true : false; + + this.audioPoster = $(media).data('poster'); + this.audioPosterAlt = $(media).data('poster-alt' ); + + // get height and width attributes, if present + // and add them to variables + // Not currently used, but might be useful for resizing player + this.width = $(media).attr('width') ?? 0; + this.height = $(media).attr('height') ?? 0; + + // start-time + var startTime = $(media).data('start-time'); + var isNumeric = ( typeof startTime === 'number' || ( typeof startTime === 'string' && value.trim() !== '' && ! isNaN(value) && isFinite( Number(value) ) ) ) ? true : false; + this.startTime = ( startTime !== undefined && isNumeric ) ? startTime : 0; + + // debug + this.debug = ($(media).data('debug') !== undefined && $(media).data('debug') !== false) ? true : 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; + } + + // Control whether text descriptions are read aloud + // set to "false" if the sole purpose of the WebVTT descriptions file + // is to integrate text description into the transcript + // set to "true" to write description text to a div + // This variable does *not* control the method by which description is read. + // For that, see below (this.descMethod) + if ($(media).data('descriptions-audible') !== undefined && $(media).data('descriptions-audible') === false) { + this.readDescriptionsAloud = false; + } else if ($(media).data('description-audible') !== undefined && $(media).data('description-audible') === false) { + // support both singular and plural spelling of attribute + this.readDescriptionsAloud = false; + } else { + this.readDescriptionsAloud = true; + } + + // setting initial this.descVoices to an empty array + // to be populated later by getBrowserVoices + this.descVoices = []; + + // Method by which text descriptions are read + // valid values of data-desc-reader are: + // 'brower' (default) - text-based audio description is handled by the browser, if supported + // 'screenreader' - text-based audio description is always handled by screen readers + // The latter may be preferable by owners of websites in languages that are not well supported + // by the Web Speech API + this.descReader = ($(media).data('desc-reader') == 'screenreader') ? 'screenreader' : 'browser'; + + // Default state of captions and descriptions + // This setting is overridden by user preferences, if they exist + // values for data-state-captions and data-state-descriptions are 'on' or 'off' + this.defaultStateCaptions = ($(media).data('state-captions') == 'off') ? 0 : 1; + this.defaultStateDescriptions = ($(media).data('state-descriptions') == 'on') ? 1 : 0; + + // Default setting for prefDescPause + // Extended description (i.e., pausing during description) is on by default + // but this settings give website owners control over that + // since they know the nature of their videos, and whether pausing is necessary + // This setting is overridden by user preferences, if they exist + this.defaultDescPause = ($(media).data('desc-pause-default') == 'off') ? 0 : 1; + + // 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 & a valid target element) + // 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 + var transcriptDivLocation = $(media).data('transcript-div'); + if ( transcriptDivLocation !== undefined && transcriptDivLocation !== "" && null !== document.getElementById( transcriptDivLocation ) ) { + this.transcriptDivLocation = transcriptDivLocation; + } else { + this.transcriptDivLocation = null; + } + var includeTranscript = $(media).data('include-transcript'); + this.hideTranscriptButton = ( includeTranscript !== undefined && includeTranscript === false) ? true : false; + + this.transcriptType = null; + if ($(media).data('transcript-src') !== undefined) { + this.transcriptSrc = $(media).data('transcript-src'); + if (this.transcriptSrcHasRequiredParts()) { + this.transcriptType = 'manual'; } else { - this.$signDivLocation = null; - } - - // 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' - this.defaultCaptionsPosition = ($(media).data('captions-position') === 'overlay') ? 'overlay' : 'below'; - - // Chapters - var chaptersDiv = $(media).data('chapters-div'); - if ( chaptersDiv !== undefined && chaptersDiv !== "") { - this.chaptersDivLocation = chaptersDiv; - } - - if ($(media).data('chapters-title') !== undefined) { - // NOTE: empty string is valid; results in no title being displayed - this.chaptersTitle = $(media).data('chapters-title'); - } - - var defaultChapter = $(media).data('chapters-default'); - this.defaultChapter = ( defaultChapter !== undefined && defaultChapter !== "") ? 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 - this.speedIcons = ($(media).data('speed-icons') === 'arrows') ? 'arrows' : 'animals'; - - // Seekbar - // valid values of data-seekbar-scope are 'chapter' and 'video'; will also accept 'chapters' - var seekbarScope = $(media).data('seekbar-scope'); - this.seekbarScope = ( seekbarScope === 'chapter' || seekbarScope === 'chapters') ? 'chapter' : 'video'; - - // YouTube - var youTubeId = $(media).data('youtube-id'); - if ( youTubeId !== undefined && youTubeId !== "") { - this.youTubeId = this.getYouTubeId(youTubeId); - if ( ! this.hasPoster ) { - let poster = this.getYouTubePosterUrl(this.youTubeId,'640'); - $(media).attr( 'poster', poster ); - } - } - - var youTubeDescId = $(media).data('youtube-desc-id'); - if ( youTubeDescId !== undefined && youTubeDescId !== "") { - this.youTubeDescId = this.getYouTubeId(youTubeDescId); - } - - var youTubeSignId = $(media).data('youtube-sign-src'); - if ( youTubeSignId !== undefined && youTubeSignId !== "") { - this.youTubeSignId = this.getYouTubeId(youTubeSignId); - } - - var youTubeNoCookie = $(media).data('youtube-nocookie'); - this.youTubeNoCookie = (youTubeNoCookie !== undefined && youTubeNoCookie) ? true : false; - - // Vimeo - var vimeoId = $(media).data('vimeo-id'); - if ( vimeoId !== undefined && vimeoId !== "") { - this.vimeoId = this.getVimeoId(vimeoId); - if ( ! this.hasPoster ) { - let poster = thisObj.getVimeoPosterUrl(this.vimeoId,'1200'); - $(media).attr( 'poster', poster ); + console.log('ERROR: Able Player transcript is missing required parts'); + } + } else if ($(media).find('track[kind="captions"],track[kind="subtitles"],track:not([kind])').length > 0) { + // required tracks are present. COULD automatically generate a transcript + this.transcriptType = (this.transcriptDivLocation) ? 'external' : '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 for a more seamless reading experience + // If true, line breaks are preserved, so content can be presented karaoke-style, or as lines in a poem + this.lyricsMode = ($(media).data('lyrics-mode') !== undefined && $(media).data('lyrics-mode') !== false) ? true : false; + + // Set Transcript Title if defined explicitly. See transcript.js. + if ($(media).data('transcript-title') !== undefined && $(media).data('transcript-title') !== "") { + this.transcriptTitle = $(media).data('transcript-title'); + } + + // Sign Language + // sign language can be a modal (default) or assigned to a div on the page. + var signDivLocation = $(media).data('sign-div'); + if ( signDivLocation !== undefined && signDivLocation !== "" && null !== document.getElementById( signDivLocation ) ) { + this.$signDivLocation = $( '#' + signDivLocation ); + } else { + this.$signDivLocation = null; + } + + // 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' + this.defaultCaptionsPosition = ($(media).data('captions-position') === 'overlay') ? 'overlay' : 'below'; + + // Chapters + var chaptersDiv = $(media).data('chapters-div'); + if ( chaptersDiv !== undefined && chaptersDiv !== "") { + this.chaptersDivLocation = chaptersDiv; + } + + if ($(media).data('chapters-title') !== undefined) { + // NOTE: empty string is valid; results in no title being displayed + this.chaptersTitle = $(media).data('chapters-title'); + } + + var defaultChapter = $(media).data('chapters-default'); + this.defaultChapter = ( defaultChapter !== undefined && defaultChapter !== "") ? 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 + this.speedIcons = ($(media).data('speed-icons') === 'arrows') ? 'arrows' : 'animals'; + + // Seekbar + // valid values of data-seekbar-scope are 'chapter' and 'video'; will also accept 'chapters' + var seekbarScope = $(media).data('seekbar-scope'); + this.seekbarScope = ( seekbarScope === 'chapter' || seekbarScope === 'chapters') ? 'chapter' : 'video'; + + // YouTube + var youTubeId = $(media).data('youtube-id'); + if ( youTubeId !== undefined && youTubeId !== "") { + this.youTubeId = this.getYouTubeId(youTubeId); + if ( ! this.hasPoster ) { + let poster = this.getYouTubePosterUrl(this.youTubeId,'640'); + $(media).attr( 'poster', poster ); + } + } + + var youTubeDescId = $(media).data('youtube-desc-id'); + if ( youTubeDescId !== undefined && youTubeDescId !== "") { + this.youTubeDescId = this.getYouTubeId(youTubeDescId); + } + + var youTubeSignId = $(media).data('youtube-sign-src'); + if ( youTubeSignId !== undefined && youTubeSignId !== "") { + this.youTubeSignId = this.getYouTubeId(youTubeSignId); + } + + var youTubeNoCookie = $(media).data('youtube-nocookie'); + this.youTubeNoCookie = (youTubeNoCookie !== undefined && youTubeNoCookie) ? true : false; + + // Vimeo + var vimeoId = $(media).data('vimeo-id'); + if ( vimeoId !== undefined && vimeoId !== "") { + this.vimeoId = this.getVimeoId(vimeoId); + if ( ! this.hasPoster ) { + let poster = thisObj.getVimeoPosterUrl(this.vimeoId,'1200'); + $(media).attr( 'poster', poster ); + } + } + var vimeoDescId = $(media).data('vimeo-desc-id'); + if ( vimeoDescId !== undefined && vimeoDescId !== "") { + this.vimeoDescId = this.getVimeoId(vimeoDescId); + } + + // Skin + // valid values of data-skin are: + // '2020' (default as of 4.6), all buttons in one row beneath a full-width seekbar + // 'legacy', two rows of controls; seekbar positioned in available space within top row + this.skin = ($(media).data('skin') == 'legacy') ? 'legacy' : '2020'; + + // Size + // width of Able Player is determined using the following order of precedence: + // 1. data-width attribute + // 2. width attribute (for video or audio, although it is not valid HTML for audio) + // 3. Intrinsic size from video (video only, determined later) + if ($(media).data('width') !== undefined) { + this.playerWidth = parseInt($(media).data('width')); + } else if ($(media)[0].getAttribute('width')) { + // NOTE: jQuery attr() returns null for all invalid HTML attributes + // (e.g., width on