diff options
author | Sam Potts <me@sampotts.me> | 2017-11-04 14:25:28 +1100 |
---|---|---|
committer | Sam Potts <me@sampotts.me> | 2017-11-04 14:25:28 +1100 |
commit | 1cc2930dc0b81183bc47442f5ad9b5d8df94cc5f (patch) | |
tree | 349313769a5e3d786a51b45b0a5c849dc7e3211d /src/js/captions.js | |
parent | 3d50936b47fdd691816843de962d5699c3c8f596 (diff) | |
download | plyr-1cc2930dc0b81183bc47442f5ad9b5d8df94cc5f.tar.lz plyr-1cc2930dc0b81183bc47442f5ad9b5d8df94cc5f.tar.xz plyr-1cc2930dc0b81183bc47442f5ad9b5d8df94cc5f.zip |
ES6-ified
Diffstat (limited to 'src/js/captions.js')
-rw-r--r-- | src/js/captions.js | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/src/js/captions.js b/src/js/captions.js new file mode 100644 index 00000000..ed175530 --- /dev/null +++ b/src/js/captions.js @@ -0,0 +1,212 @@ +// ========================================================================== +// Plyr Captions +// ========================================================================== + +import support from './support'; +import utils from './utils'; +import controls from './controls'; + +const captions = { + // Setup captions + setup() { + // Requires UI support + if (!this.supported.ui) { + return; + } + + // Set default language if not set + if (!utils.is.empty(this.storage.language)) { + this.captions.language = this.storage.language; + } else if (utils.is.empty(this.captions.language)) { + this.captions.language = this.config.captions.language.toLowerCase(); + } + + // Set captions enabled state if not set + if (!utils.is.boolean(this.captions.enabled)) { + if (!utils.is.empty(this.storage.language)) { + this.captions.enabled = this.storage.captions; + } else { + this.captions.enabled = this.config.captions.active; + } + } + + // Only Vimeo and HTML5 video supported at this point + if (!['video', 'vimeo'].includes(this.type) || (this.type === 'video' && !support.textTracks)) { + this.captions.tracks = null; + + // Clear menu and hide + if (this.config.controls.includes('settings') && this.config.settings.includes('captions')) { + controls.setCaptionsMenu.call(this); + } + + return; + } + + // Inject the container + if (!utils.is.htmlElement(this.elements.captions)) { + this.elements.captions = utils.createElement( + 'div', + utils.getAttributesFromSelector(this.config.selectors.captions) + ); + utils.insertAfter(this.elements.captions, this.elements.wrapper); + } + + // Get tracks from HTML5 + if (this.type === 'video') { + this.captions.tracks = this.media.textTracks; + } + + // Set the class hook + utils.toggleClass( + this.elements.container, + this.config.classNames.captions.enabled, + !utils.is.empty(this.captions.tracks) + ); + + // If no caption file exists, hide container for caption text + if (utils.is.empty(this.captions.tracks)) { + return; + } + + // Enable UI + captions.show.call(this); + + // Get a track + const setCurrentTrack = () => { + // Reset by default + this.captions.currentTrack = null; + + // Filter doesn't seem to work for a TextTrackList :-( + Array.from(this.captions.tracks).forEach(track => { + if (track.language === this.captions.language.toLowerCase()) { + this.captions.currentTrack = track; + } + }); + }; + + // Get current track + setCurrentTrack(); + + // If we couldn't get the requested language, revert to default + if (!utils.is.track(this.captions.currentTrack)) { + const { language } = this.config.captions; + + // Reset to default + // We don't update user storage as the selected language could become available + this.captions.language = language; + + // Get fallback track + setCurrentTrack(); + + // If no match, disable captions + if (!utils.is.track(this.captions.currentTrack)) { + this.toggleCaptions(false); + } + + controls.updateSetting.call(this, 'captions'); + } + + // Setup HTML5 track rendering + if (this.type === 'video') { + // Turn off native caption rendering to avoid double captions + Array.from(this.captions.tracks).forEach(track => { + // Remove previous bindings (if we've changed source or language) + utils.off(track, 'cuechange', event => captions.setCue.call(this, event)); + + // Hide captions + track.mode = 'hidden'; + }); + + // Check if suported kind + const supported = + this.captions.currentTrack && ['captions', 'subtitles'].includes(this.captions.currentTrack.kind); + + if (utils.is.track(this.captions.currentTrack) && supported) { + utils.on(this.captions.currentTrack, 'cuechange', event => captions.setCue.call(this, event)); + + // If we change the active track while a cue is already displayed we need to update it + if (this.captions.currentTrack.activeCues && this.captions.currentTrack.activeCues.length > 0) { + controls.setCue.call(this, this.captions.currentTrack); + } + } + } else if (this.type === 'vimeo' && this.captions.active) { + this.embed.enableTextTrack(this.captions.language); + } + + // Set available languages in list + if (this.config.controls.includes('settings') && this.config.settings.includes('captions')) { + controls.setCaptionsMenu.call(this); + } + }, + + // Display active caption if it contains text + setCue(input) { + // Get the track from the event if needed + const track = utils.is.event(input) ? input.target : input; + const active = track.activeCues[0]; + + // Display a cue, if there is one + if (utils.is.cue(active)) { + captions.set.call(this, active.getCueAsHTML()); + } else { + captions.set.call(this); + } + + utils.dispatchEvent.call(this, this.media, 'cuechange'); + }, + + // Set the current caption + set(input) { + // Requires UI + if (!this.supported.ui) { + return; + } + + if (utils.is.htmlElement(this.elements.captions)) { + const content = utils.createElement('span'); + + // Empty the container + utils.emptyElement(this.elements.captions); + + // Default to empty + const caption = !utils.is.undefined(input) ? input : ''; + + // Set the span content + if (utils.is.string(caption)) { + content.textContent = caption.trim(); + } else { + content.appendChild(caption); + } + + // Set new caption text + this.elements.captions.appendChild(content); + } else { + this.warn('No captions element to render to'); + } + }, + + // Display captions container and button (for initialization) + show() { + // If there's no caption toggle, bail + if (!this.elements.buttons.captions) { + return; + } + + // Try to load the value from storage + let active = this.storage.captions; + + // Otherwise fall back to the default config + if (!utils.is.boolean(active)) { + ({ active } = this.captions); + } else { + this.captions.active = active; + } + + if (active) { + utils.toggleClass(this.elements.container, this.config.classNames.captions.active, true); + utils.toggleState(this.elements.buttons.captions, true); + } + }, +}; + +export default captions; |