aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/js/captions.js152
-rw-r--r--src/js/controls.js12
-rw-r--r--src/js/listeners.js20
-rw-r--r--src/js/plyr.js30
4 files changed, 133 insertions, 81 deletions
diff --git a/src/js/captions.js b/src/js/captions.js
index 5f78e636..fd2692f7 100644
--- a/src/js/captions.js
+++ b/src/js/captions.js
@@ -7,10 +7,11 @@ import controls from './controls';
import i18n from './i18n';
import support from './support';
import browser from './utils/browser';
-import { createElement, emptyElement, getAttributesFromSelector, insertAfter, removeElement, toggleClass } from './utils/elements';
+import { createElement, emptyElement, getAttributesFromSelector, insertAfter, removeElement, toggleClass, toggleState } from './utils/elements';
import { on, triggerEvent } from './utils/events';
import fetch from './utils/fetch';
import is from './utils/is';
+import { dedupe } from './utils/arrays';
import { getHTML } from './utils/strings';
import { parseUrl } from './utils/urls';
@@ -60,21 +61,34 @@ const captions = {
});
}
- // Try to load the value from storage
- let active = this.storage.get('captions');
+ // Get and set initial data
+ // The "preferred" options are not realized unless / until the wanted language has a match
+ // * languages: Array of user's browser languages.
+ // * language: The language preferred by user settings or config
+ // * active: The state preferred by user settings or config
+ // * toggled: The real captions state
- // Otherwise fall back to the default config
- if (!is.boolean(active)) {
- ({ active } = this.config.captions);
- }
+ const languages = dedupe(Array.from(navigator.languages || navigator.userLanguage)
+ .map(language => language.split('-')[0]));
- // Get language from storage, fallback to config
let language = this.storage.get('language') || this.config.captions.language;
+
+ // Use first browser language when language is 'auto'
if (language === 'auto') {
- [language] = (navigator.language || navigator.userLanguage).split('-');
+ [language] = languages;
+ }
+
+ let active = this.storage.get('captions');
+ if (!is.boolean(active)) {
+ ({ active } = this.config.captions);
}
- // Set language and show if active
- captions.setLanguage.call(this, language, active);
+
+ Object.assign(this.captions, {
+ toggled: false,
+ active,
+ language,
+ languages,
+ });
// Watch changes to textTracks and update captions menu
if (this.isHTML5) {
@@ -86,10 +100,12 @@ const captions = {
setTimeout(captions.update.bind(this), 0);
},
+ // Update available language options in settings based on tracks
update() {
const tracks = captions.getTracks.call(this, true);
// Get the wanted language
- const { language, meta } = this.captions;
+ const { active, language, meta, currentTrackNode } = this.captions;
+ const languageExists = Boolean(tracks.find(track => track.language === language));
// Handle tracks (add event listener and "pseudo"-default)
if (this.isHTML5 && this.isVideo) {
@@ -108,12 +124,10 @@ const captions = {
});
}
- 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);
+ // Update language first time it matches, or if the previous matching track was removed
+ if ((languageExists && this.language !== language) || !tracks.includes(currentTrackNode)) {
+ captions.setLanguage.call(this, language);
+ captions.toggle.call(this, active && languageExists);
}
// Enable or disable captions based on track length
@@ -125,12 +139,69 @@ const captions = {
}
},
- set(index, setLanguage = true, show = true) {
+ // Toggle captions display
+ // Used internally for the toggleCaptions method, with the passive option forced to false
+ toggle(input, passive = true) {
+ // If there's no full support
+ if (!this.supported.ui) {
+ return;
+ }
+
+ const { toggled } = this.captions; // Current state
+ const activeClass = this.config.classNames.captions.active;
+
+ // Get the next state
+ // If the method is called without parameter, toggle based on current value
+ const active = is.nullOrUndefined(input) ? !toggled : input;
+
+ // Update state and trigger event
+ if (active !== toggled) {
+ // Force language if the call isn't passive and there is no matching language to toggle to
+ if (!this.language && active && !passive) {
+ const tracks = captions.getTracks.call(this);
+ const track = captions.findTrack.call(this, [
+ this.captions.language,
+ ...this.captions.languages,
+ ], true);
+
+ // Override user preferences to avoid switching languages if a matching track is added
+ this.captions.language = track.language;
+
+ // Set caption, but don't store in localStorage as user preference
+ captions.set.call(this, tracks.indexOf(track));
+ return;
+ }
+
+ // Toggle state
+ toggleState(this.elements.buttons.captions, active);
+
+ // Add class hook
+ toggleClass(this.elements.container, activeClass, active);
+
+ this.captions.toggled = active;
+
+ // Update settings menu
+ controls.updateSetting.call(this, 'captions');
+
+ // When passive, don't override user preferences
+ if (!passive) {
+ this.captions.active = active;
+ this.storage.set({ captions: active });
+ }
+
+ // Trigger event (not used internally)
+ triggerEvent.call(this, this.media, active ? 'captionsenabled' : 'captionsdisabled');
+ }
+ },
+
+ // Set captions by track index
+ // Used internally for the currentTrack setter with the passive option forced to false
+ set(index, passive = true) {
const tracks = captions.getTracks.call(this);
// Disable captions if setting to -1
if (index === -1) {
- this.toggleCaptions(false);
+ captions.toggle.call(this, false, passive);
return;
}
@@ -146,15 +217,19 @@ const captions = {
if (this.captions.currentTrack !== index) {
this.captions.currentTrack = index;
- const track = captions.getCurrentTrack.call(this);
+ const track = tracks[index];
const { language } = track || {};
// 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) {
+ // Update settings menu
+ controls.updateSetting.call(this, 'captions');
+
+ // When passive, don't override user preferences
+ if (!passive) {
this.captions.language = language;
+ this.storage.set({ language });
}
// Handle Vimeo captions
@@ -172,12 +247,12 @@ const captions = {
}
// Show captions
- if (show) {
- this.toggleCaptions(true);
- }
+ captions.toggle.call(this, true, passive);
},
- setLanguage(language, show = true) {
+ // Set captions by language
+ // Used internally for the language setter with the passive option forced to false
+ setLanguage(language, passive = true) {
if (!is.string(language)) {
this.debug.warn('Invalid language argument', language);
return;
@@ -187,8 +262,8 @@ const captions = {
// Set currentTrack
const tracks = captions.getTracks.call(this);
- const track = captions.getCurrentTrack.call(this, true);
- captions.set.call(this, tracks.indexOf(track), false, show);
+ const track = captions.findTrack.call(this, [language]);
+ captions.set.call(this, tracks.indexOf(track), passive);
},
// Get current valid caption tracks
@@ -204,19 +279,30 @@ const captions = {
.filter(track => ['captions', 'subtitles'].includes(track.kind));
},
- // Get the current track for the current language
- getCurrentTrack(fromLanguage = false) {
+ // Match tracks based on languages and get the first
+ findTrack(languages, force = 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];
+ let track;
+ languages.every(language => {
+ track = sorted.find(track => track.language === language);
+ return !track; // Break iteration if there is a match
+ });
+ // If no match is found but is required, get first
+ return track || (force ? sorted[0] : undefined);
+ },
+
+ // Get the current track
+ getCurrentTrack() {
+ return captions.getTracks.call(this)[this.currentTrack];
},
// Get UI label for track
getLabel(track) {
let currentTrack = track;
- if (!is.track(currentTrack) && support.textTracks && this.captions.active) {
+ if (!is.track(currentTrack) && support.textTracks && this.captions.toggled) {
currentTrack = captions.getCurrentTrack.call(this);
}
diff --git a/src/js/controls.js b/src/js/controls.js
index 39ddb79c..f091555f 100644
--- a/src/js/controls.js
+++ b/src/js/controls.js
@@ -10,7 +10,7 @@ import { repaint, transitionEndEvent } from './utils/animation';
import { dedupe } from './utils/arrays';
import browser from './utils/browser';
import { createElement, emptyElement, getAttributesFromSelector, getElement, getElements, hasClass, removeElement, setAttributes, toggleClass, toggleHidden, toggleState } from './utils/elements';
-import { once } from './utils/events';
+import { on, off } from './utils/events';
import is from './utils/is';
import loadSprite from './utils/loadSprite';
import { extend } from './utils/objects';
@@ -845,7 +845,7 @@ const controls = {
// Generate options data
const options = tracks.map((track, value) => ({
value,
- checked: this.captions.active && this.currentTrack === value,
+ checked: this.captions.toggled && this.currentTrack === value,
title: captions.getLabel.call(this, track),
badge: track.language && controls.createBadge.call(this, track.language.toUpperCase()),
list,
@@ -855,7 +855,7 @@ const controls = {
// Add the "Disabled" option to turn off captions
options.unshift({
value: -1,
- checked: !this.captions.active,
+ checked: !this.captions.toggled,
title: i18n.get('disabled', this.config),
list,
type: 'language',
@@ -1015,7 +1015,7 @@ const controls = {
return;
}
- // Are we targetting a tab? If not, bail
+ // Are we targeting a tab? If not, bail
const isTab = pane.getAttribute('role') === 'tabpanel';
if (!isTab) {
return;
@@ -1051,10 +1051,12 @@ const controls = {
container.style.width = '';
container.style.height = '';
+ // Only listen once
+ off.call(this, container, transitionEndEvent, restore);
};
// Listen for the transition finishing and restore auto height/width
- once.call(this, container, transitionEndEvent, restore);
+ on.call(this, container, transitionEndEvent, restore);
// Set dimensions to target
container.style.width = `${size.width}px`;
diff --git a/src/js/listeners.js b/src/js/listeners.js
index 3e691fe7..d962761c 100644
--- a/src/js/listeners.js
+++ b/src/js/listeners.js
@@ -361,24 +361,6 @@ class Listeners {
controls.updateSetting.call(this.player, 'quality', null, event.detail.quality);
});
- // Caption language change
- on.call(this.player, this.player.media, 'languagechange', () => {
- // Update UI
- controls.updateSetting.call(this.player, 'captions');
-
- // Save to storage
- this.player.storage.set({ language: this.player.language });
- });
-
- // Captions toggle
- on.call(this.player, this.player.media, 'captionsenabled captionsdisabled', () => {
- // Update UI
- controls.updateSetting.call(this.player, 'captions');
-
- // Save to storage
- this.player.storage.set({ captions: this.player.captions.active });
- });
-
// Proxy events to container
// Bubble up key events for Edge
const proxyEvents = this.player.config.events.concat(['keyup', 'keydown']).join(' ');
@@ -449,7 +431,7 @@ class Listeners {
);
// Captions toggle
- bind(this.player.elements.buttons.captions, 'click', this.player.toggleCaptions);
+ bind(this.player.elements.buttons.captions, 'click', () => this.player.toggleCaptions());
// Fullscreen toggle
bind(
diff --git a/src/js/plyr.js b/src/js/plyr.js
index 20f97974..753db775 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -19,7 +19,7 @@ import Storage from './storage';
import support from './support';
import ui from './ui';
import { closest } from './utils/arrays';
-import { createElement, hasClass, removeElement, replaceElement, toggleClass, toggleState, wrap } from './utils/elements';
+import { createElement, hasClass, removeElement, replaceElement, toggleClass, wrap } from './utils/elements';
import { off, on, once, triggerEvent, unbindListeners } from './utils/events';
import is from './utils/is';
import loadSprite from './utils/loadSprite';
@@ -825,25 +825,7 @@ class Plyr {
* @param {boolean} input - Whether to enable captions
*/
toggleCaptions(input) {
- // If there's no full support
- if (!this.supported.ui) {
- return;
- }
-
- // If the method is called without parameter, toggle based on current value
- const active = is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active);
-
- // Toggle state
- toggleState(this.elements.buttons.captions, active);
-
- // Add class hook
- toggleClass(this.elements.container, this.config.classNames.captions.active, active);
-
- // Update state and trigger event
- if (active !== this.captions.active) {
- this.captions.active = active;
- triggerEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled');
- }
+ captions.toggle.call(this, input, false);
}
/**
@@ -851,15 +833,15 @@ class Plyr {
* @param {number} - Caption index
*/
set currentTrack(input) {
- captions.set.call(this, input);
+ captions.set.call(this, input, false);
}
/**
* Get the current caption track index (-1 if disabled)
*/
get currentTrack() {
- const { active, currentTrack } = this.captions;
- return active ? currentTrack : -1;
+ const { toggled, currentTrack } = this.captions;
+ return toggled ? currentTrack : -1;
}
/**
@@ -868,7 +850,7 @@ class Plyr {
* @param {string} - Two character ISO language code (e.g. EN, FR, PT, etc)
*/
set language(input) {
- captions.setLanguage.call(this, input);
+ captions.setLanguage.call(this, input, false);
}
/**