diff options
author | Albin Larsson <mail@albinlarsson.com> | 2018-06-08 14:49:35 +0200 |
---|---|---|
committer | Albin Larsson <mail@albinlarsson.com> | 2018-06-10 19:00:07 +0200 |
commit | c83487a293d61d3a1add31018918e9c831bbcac2 (patch) | |
tree | 00e8b3a177e7e79615ee5611aac560dffab53e65 /src/js/captions.js | |
parent | 1fab4919c01347a29e11cbd78fedcddfabd1b814 (diff) | |
download | plyr-c83487a293d61d3a1add31018918e9c831bbcac2.tar.lz plyr-c83487a293d61d3a1add31018918e9c831bbcac2.tar.xz plyr-c83487a293d61d3a1add31018918e9c831bbcac2.zip |
Fix #1017, fix #980, fix #1014: Captions rewrite (use index internally)
Diffstat (limited to 'src/js/captions.js')
-rw-r--r-- | src/js/captions.js | 170 |
1 files changed, 114 insertions, 56 deletions
diff --git a/src/js/captions.js b/src/js/captions.js index 38167d7a..bafcf87e 100644 --- a/src/js/captions.js +++ b/src/js/captions.js @@ -69,12 +69,18 @@ const captions = { ({ active } = this.config.captions); } - // Set toggled state - this.toggleCaptions(active); + // Get language from storage, fallback to config + let language = this.storage.get('language') || this.config.captions.language; + if (language === 'auto') { + [ language ] = (navigator.language || navigator.userLanguage).split('-'); + } + // Set language and show if active + captions.setLanguage.call(this, language, active); // Watch changes to textTracks and update captions menu - if (this.config.captions.update) { - utils.on(this.media.textTracks, 'addtrack removetrack', captions.update.bind(this)); + if (this.isHTML5) { + const trackEvents = this.config.captions.update ? 'addtrack removetrack' : 'removetrack'; + utils.on(this.media.textTracks, trackEvents, captions.update.bind(this)); } // Update available languages in list next tick (the event must not be triggered before the listeners) @@ -82,21 +88,39 @@ const captions = { }, update() { - // Update tracks - const tracks = captions.getTracks.call(this); - this.options.captions = tracks.map(({language}) => language); + const tracks = captions.getTracks.call(this, true); + // Get the wanted language + const { language, meta } = this.captions; - // Set language if it hasn't been set already - if (!this.language) { - let { language } = this.config.captions; - if (language === 'auto') { - [ language ] = (navigator.language || navigator.userLanguage).split('-'); - } - this.language = this.storage.get('language') || (language || '').toLowerCase(); + // Handle tracks (add event listener and "pseudo"-default) + if (this.isHTML5 && this.isVideo) { + tracks + .filter(track => !meta.get(track)) + .forEach(track => { + this.debug.log('Track added', track); + // Attempt to store if the original dom element was "default" + meta.set(track, { + default: track.mode === 'showing', + }); + + // Turn off native caption rendering to avoid double captions + track.mode = 'hidden'; + + // Add event listener for cue changes + utils.on(track, 'cuechange', () => captions.updateCues.call(this)); + }); } - // Toggle the class hooks - utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this))); + const trackRemoved = !tracks.find(track => track === this.captions.currentTrackNode); + const firstMatch = this.language !== language && tracks.find(track => track.language === language); + + // Update language if removed or first matching track added + if (trackRemoved || firstMatch) { + captions.setLanguage.call(this, language, this.config.captions.active); + } + + // Enable or disable captions based on track length + utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(tracks)); // Update available languages in list if ((this.config.controls || []).includes('settings') && this.config.settings.includes('captions')) { @@ -104,60 +128,94 @@ const captions = { } }, - // Set the captions language - setLanguage() { - // Setup HTML5 track rendering - if (this.isHTML5 && this.isVideo) { - captions.getTracks.call(this).forEach(track => { - // Show track - utils.on(track, 'cuechange', () => captions.updateCues.call(this)); + set(index, setLanguage = true, show = true) { + const tracks = captions.getTracks.call(this); - // Turn off native caption rendering to avoid double captions - // eslint-disable-next-line - track.mode = 'hidden'; - }); + // Disable captions if setting to -1 + if (index === -1) { + this.toggleCaptions(false); + return; + } - // If we change the active track while a cue is already displayed we need to update it - captions.updateCues.call(this); + if (!utils.is.number(index)) { + this.debug.warn('Invalid caption argument', index); + return; + } - } else if (this.isVimeo && this.captions.active) { - this.embed.enableTextTrack(this.language); + if (!(index in tracks)) { + this.debug.warn('Track not found', index); + return; } - }, - // Get the tracks - getTracks() { - // Handle media or textTracks missing or null - const { textTracks } = this.media || {}; - // Filter out invalid tracks kinds (like metadata) - return Array.from(textTracks || []).filter(track => [ - 'captions', - 'subtitles', - ].includes(track.kind)); - }, + if (this.captions.currentTrack !== index) { + this.captions.currentTrack = index; + const track = captions.getCurrentTrack.call(this); + const { language } = track || {}; - // Get the current track for the current language - getCurrentTrack() { - const tracks = captions.getTracks.call(this); + // Store reference to node for invalidation on remove + this.captions.currentTrackNode = track; + + // Prevent setting language in some cases, since it can violate user's intentions + if (setLanguage) { + this.captions.language = language; + } + + // Handle Vimeo captions + if (this.isVimeo) { + this.embed.enableTextTrack(language); + } - if (!tracks.length) { - return null; + // Trigger event + utils.dispatchEvent.call(this, this.media, 'languagechange'); } - // Get track based on current language - let track = tracks.find(track => track.language.toLowerCase() === this.language); + if (this.isHTML5 && this.isVideo) { + // If we change the active track while a cue is already displayed we need to update it + captions.updateCues.call(this); + } - // Get the <track> with default attribute - if (!track) { - track = utils.getElement.call(this, 'track[default]'); + // Show captions + if (show) { + this.toggleCaptions(true); } + }, - // Get the first track - if (!track) { - [track] = tracks; + setLanguage(language, show = true) { + if (!utils.is.string(language)) { + this.debug.warn('Invalid language argument', language); + return; } + // Normalize + this.captions.language = language.toLowerCase(); + + // Set currentTrack + const tracks = captions.getTracks.call(this); + const track = captions.getCurrentTrack.call(this, true); + captions.set.call(this, tracks.indexOf(track), false, show); + }, - return track; + // Get current valid caption tracks + // If update is false it will also ignore tracks without metadata + // This is used to "freeze" the language options when captions.update is false + getTracks(update = false) { + // Handle media or textTracks missing or null + const tracks = Array.from((this.media || {}).textTracks || []); + // For HTML5, use cache instead of current tracks when it exists (if captions.update is false) + // Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata) + return tracks + .filter(track => !this.isHTML5 || update || this.captions.meta.has(track)) + .filter(track => [ + 'captions', + 'subtitles', + ].includes(track.kind)); + }, + + // Get the current track for the current language + getCurrentTrack(fromLanguage = false) { + const tracks = captions.getTracks.call(this); + const sortIsDefault = track => Number((this.captions.meta.get(track) || {}).default); + const sorted = Array.from(tracks).sort((a, b) => sortIsDefault(b) - sortIsDefault(a)); + return (!fromLanguage && tracks[this.currentTrack]) || sorted.find(track => track.language === this.captions.language) || sorted[0]; }, // Get UI label for track |