aboutsummaryrefslogtreecommitdiffstats
path: root/src/js/captions.js
diff options
context:
space:
mode:
authorAlbin Larsson <mail@albinlarsson.com>2018-06-08 14:49:35 +0200
committerAlbin Larsson <mail@albinlarsson.com>2018-06-10 19:00:07 +0200
commitc83487a293d61d3a1add31018918e9c831bbcac2 (patch)
tree00e8b3a177e7e79615ee5611aac560dffab53e65 /src/js/captions.js
parent1fab4919c01347a29e11cbd78fedcddfabd1b814 (diff)
downloadplyr-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.js170
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