Merge pull request #1036 from friday/captions-passive-toggle
Captions fixes (again)
This commit is contained in:
commit
3cd2b9a6c3
@ -7,10 +7,11 @@ import controls from './controls';
|
|||||||
import i18n from './i18n';
|
import i18n from './i18n';
|
||||||
import support from './support';
|
import support from './support';
|
||||||
import browser from './utils/browser';
|
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 { on, triggerEvent } from './utils/events';
|
||||||
import fetch from './utils/fetch';
|
import fetch from './utils/fetch';
|
||||||
import is from './utils/is';
|
import is from './utils/is';
|
||||||
|
import { dedupe } from './utils/arrays';
|
||||||
import { getHTML } from './utils/strings';
|
import { getHTML } from './utils/strings';
|
||||||
import { parseUrl } from './utils/urls';
|
import { parseUrl } from './utils/urls';
|
||||||
|
|
||||||
@ -63,21 +64,34 @@ const captions = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to load the value from storage
|
// Get and set initial data
|
||||||
let active = this.storage.get('captions');
|
// 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
|
const languages = dedupe(Array.from(navigator.languages || navigator.userLanguage)
|
||||||
|
.map(language => language.split('-')[0]));
|
||||||
|
|
||||||
|
let language = this.storage.get('language') || this.config.captions.language;
|
||||||
|
|
||||||
|
// Use first browser language when language is 'auto'
|
||||||
|
if (language === 'auto') {
|
||||||
|
[language] = languages;
|
||||||
|
}
|
||||||
|
|
||||||
|
let active = this.storage.get('captions');
|
||||||
if (!is.boolean(active)) {
|
if (!is.boolean(active)) {
|
||||||
({ active } = this.config.captions);
|
({ active } = this.config.captions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get language from storage, fallback to config
|
Object.assign(this.captions, {
|
||||||
let language = this.storage.get('language') || this.config.captions.language;
|
toggled: false,
|
||||||
if (language === 'auto') {
|
active,
|
||||||
[language] = (navigator.language || navigator.userLanguage).split('-');
|
language,
|
||||||
}
|
languages,
|
||||||
// Set language and show if active
|
});
|
||||||
captions.setLanguage.call(this, language, active);
|
|
||||||
|
|
||||||
// Watch changes to textTracks and update captions menu
|
// Watch changes to textTracks and update captions menu
|
||||||
if (this.isHTML5) {
|
if (this.isHTML5) {
|
||||||
@ -89,10 +103,12 @@ const captions = {
|
|||||||
setTimeout(captions.update.bind(this), 0);
|
setTimeout(captions.update.bind(this), 0);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Update available language options in settings based on tracks
|
||||||
update() {
|
update() {
|
||||||
const tracks = captions.getTracks.call(this, true);
|
const tracks = captions.getTracks.call(this, true);
|
||||||
// Get the wanted language
|
// 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)
|
// Handle tracks (add event listener and "pseudo"-default)
|
||||||
if (this.isHTML5 && this.isVideo) {
|
if (this.isHTML5 && this.isVideo) {
|
||||||
@ -111,12 +127,10 @@ const captions = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const trackRemoved = !tracks.find(track => track === this.captions.currentTrackNode);
|
// Update language first time it matches, or if the previous matching track was removed
|
||||||
const firstMatch = this.language !== language && tracks.find(track => track.language === language);
|
if ((languageExists && this.language !== language) || !tracks.includes(currentTrackNode)) {
|
||||||
|
captions.setLanguage.call(this, language);
|
||||||
// Update language if removed or first matching track added
|
captions.toggle.call(this, active && languageExists);
|
||||||
if (trackRemoved || firstMatch) {
|
|
||||||
captions.setLanguage.call(this, language, this.config.captions.active);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable or disable captions based on track length
|
// Enable or disable captions based on track length
|
||||||
@ -128,12 +142,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);
|
const tracks = captions.getTracks.call(this);
|
||||||
|
|
||||||
// Disable captions if setting to -1
|
// Disable captions if setting to -1
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
this.toggleCaptions(false);
|
captions.toggle.call(this, false, passive);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,15 +220,19 @@ const captions = {
|
|||||||
|
|
||||||
if (this.captions.currentTrack !== index) {
|
if (this.captions.currentTrack !== index) {
|
||||||
this.captions.currentTrack = index;
|
this.captions.currentTrack = index;
|
||||||
const track = captions.getCurrentTrack.call(this);
|
const track = tracks[index];
|
||||||
const { language } = track || {};
|
const { language } = track || {};
|
||||||
|
|
||||||
// Store reference to node for invalidation on remove
|
// Store reference to node for invalidation on remove
|
||||||
this.captions.currentTrackNode = track;
|
this.captions.currentTrackNode = track;
|
||||||
|
|
||||||
// Prevent setting language in some cases, since it can violate user's intentions
|
// Update settings menu
|
||||||
if (setLanguage) {
|
controls.updateSetting.call(this, 'captions');
|
||||||
|
|
||||||
|
// When passive, don't override user preferences
|
||||||
|
if (!passive) {
|
||||||
this.captions.language = language;
|
this.captions.language = language;
|
||||||
|
this.storage.set({ language });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle Vimeo captions
|
// Handle Vimeo captions
|
||||||
@ -175,12 +250,12 @@ const captions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show captions
|
// Show captions
|
||||||
if (show) {
|
captions.toggle.call(this, true, passive);
|
||||||
this.toggleCaptions(true);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
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)) {
|
if (!is.string(language)) {
|
||||||
this.debug.warn('Invalid language argument', language);
|
this.debug.warn('Invalid language argument', language);
|
||||||
return;
|
return;
|
||||||
@ -190,8 +265,8 @@ const captions = {
|
|||||||
|
|
||||||
// Set currentTrack
|
// Set currentTrack
|
||||||
const tracks = captions.getTracks.call(this);
|
const tracks = captions.getTracks.call(this);
|
||||||
const track = captions.getCurrentTrack.call(this, true);
|
const track = captions.findTrack.call(this, [language]);
|
||||||
captions.set.call(this, tracks.indexOf(track), false, show);
|
captions.set.call(this, tracks.indexOf(track), passive);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Get current valid caption tracks
|
// Get current valid caption tracks
|
||||||
@ -208,19 +283,30 @@ const captions = {
|
|||||||
].includes(track.kind));
|
].includes(track.kind));
|
||||||
},
|
},
|
||||||
|
|
||||||
// Get the current track for the current language
|
// Match tracks based on languages and get the first
|
||||||
getCurrentTrack(fromLanguage = false) {
|
findTrack(languages, force = false) {
|
||||||
const tracks = captions.getTracks.call(this);
|
const tracks = captions.getTracks.call(this);
|
||||||
const sortIsDefault = track => Number((this.captions.meta.get(track) || {}).default);
|
const sortIsDefault = track => Number((this.captions.meta.get(track) || {}).default);
|
||||||
const sorted = Array.from(tracks).sort((a, b) => sortIsDefault(b) - sortIsDefault(a));
|
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
|
// Get UI label for track
|
||||||
getLabel(track) {
|
getLabel(track) {
|
||||||
let currentTrack = 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);
|
currentTrack = captions.getCurrentTrack.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
12
src/js/controls.js
vendored
12
src/js/controls.js
vendored
@ -10,7 +10,7 @@ import { repaint, transitionEndEvent } from './utils/animation';
|
|||||||
import { dedupe } from './utils/arrays';
|
import { dedupe } from './utils/arrays';
|
||||||
import browser from './utils/browser';
|
import browser from './utils/browser';
|
||||||
import { createElement, emptyElement, getAttributesFromSelector, getElement, getElements, hasClass, removeElement, setAttributes, toggleClass, toggleHidden, toggleState } from './utils/elements';
|
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 is from './utils/is';
|
||||||
import loadSprite from './utils/loadSprite';
|
import loadSprite from './utils/loadSprite';
|
||||||
import { extend } from './utils/objects';
|
import { extend } from './utils/objects';
|
||||||
@ -848,7 +848,7 @@ const controls = {
|
|||||||
// Generate options data
|
// Generate options data
|
||||||
const options = tracks.map((track, value) => ({
|
const options = tracks.map((track, value) => ({
|
||||||
value,
|
value,
|
||||||
checked: this.captions.active && this.currentTrack === value,
|
checked: this.captions.toggled && this.currentTrack === value,
|
||||||
title: captions.getLabel.call(this, track),
|
title: captions.getLabel.call(this, track),
|
||||||
badge: track.language && controls.createBadge.call(this, track.language.toUpperCase()),
|
badge: track.language && controls.createBadge.call(this, track.language.toUpperCase()),
|
||||||
list,
|
list,
|
||||||
@ -858,7 +858,7 @@ const controls = {
|
|||||||
// Add the "Disabled" option to turn off captions
|
// Add the "Disabled" option to turn off captions
|
||||||
options.unshift({
|
options.unshift({
|
||||||
value: -1,
|
value: -1,
|
||||||
checked: !this.captions.active,
|
checked: !this.captions.toggled,
|
||||||
title: i18n.get('disabled', this.config),
|
title: i18n.get('disabled', this.config),
|
||||||
list,
|
list,
|
||||||
type: 'language',
|
type: 'language',
|
||||||
@ -1026,7 +1026,7 @@ const controls = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we targetting a tab? If not, bail
|
// Are we targeting a tab? If not, bail
|
||||||
const isTab = pane.getAttribute('role') === 'tabpanel';
|
const isTab = pane.getAttribute('role') === 'tabpanel';
|
||||||
if (!isTab) {
|
if (!isTab) {
|
||||||
return;
|
return;
|
||||||
@ -1065,10 +1065,12 @@ const controls = {
|
|||||||
container.style.width = '';
|
container.style.width = '';
|
||||||
container.style.height = '';
|
container.style.height = '';
|
||||||
|
|
||||||
|
// Only listen once
|
||||||
|
off.call(this, container, transitionEndEvent, restore);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Listen for the transition finishing and restore auto height/width
|
// 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
|
// Set dimensions to target
|
||||||
container.style.width = `${size.width}px`;
|
container.style.width = `${size.width}px`;
|
||||||
|
@ -387,24 +387,6 @@ class Listeners {
|
|||||||
controls.updateSetting.call(this.player, 'quality', null, event.detail.quality);
|
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
|
// Proxy events to container
|
||||||
// Bubble up key events for Edge
|
// Bubble up key events for Edge
|
||||||
on.call(this.player, this.player.media, this.player.config.events.concat([
|
on.call(this.player, this.player.media, this.player.config.events.concat([
|
||||||
@ -477,7 +459,7 @@ class Listeners {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Captions toggle
|
// Captions toggle
|
||||||
bind(this.player.elements.buttons.captions, 'click', this.player.toggleCaptions);
|
bind(this.player.elements.buttons.captions, 'click', () => this.player.toggleCaptions());
|
||||||
|
|
||||||
// Fullscreen toggle
|
// Fullscreen toggle
|
||||||
bind(
|
bind(
|
||||||
|
@ -19,7 +19,7 @@ import Storage from './storage';
|
|||||||
import support from './support';
|
import support from './support';
|
||||||
import ui from './ui';
|
import ui from './ui';
|
||||||
import { closest } from './utils/arrays';
|
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 { off, on, once, triggerEvent, unbindListeners } from './utils/events';
|
||||||
import is from './utils/is';
|
import is from './utils/is';
|
||||||
import loadSprite from './utils/loadSprite';
|
import loadSprite from './utils/loadSprite';
|
||||||
@ -833,25 +833,7 @@ class Plyr {
|
|||||||
* @param {boolean} input - Whether to enable captions
|
* @param {boolean} input - Whether to enable captions
|
||||||
*/
|
*/
|
||||||
toggleCaptions(input) {
|
toggleCaptions(input) {
|
||||||
// If there's no full support
|
captions.toggle.call(this, input, false);
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -859,15 +841,15 @@ class Plyr {
|
|||||||
* @param {number} - Caption index
|
* @param {number} - Caption index
|
||||||
*/
|
*/
|
||||||
set currentTrack(input) {
|
set currentTrack(input) {
|
||||||
captions.set.call(this, input);
|
captions.set.call(this, input, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current caption track index (-1 if disabled)
|
* Get the current caption track index (-1 if disabled)
|
||||||
*/
|
*/
|
||||||
get currentTrack() {
|
get currentTrack() {
|
||||||
const { active, currentTrack } = this.captions;
|
const { toggled, currentTrack } = this.captions;
|
||||||
return active ? currentTrack : -1;
|
return toggled ? currentTrack : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -876,7 +858,7 @@ class Plyr {
|
|||||||
* @param {string} - Two character ISO language code (e.g. EN, FR, PT, etc)
|
* @param {string} - Two character ISO language code (e.g. EN, FR, PT, etc)
|
||||||
*/
|
*/
|
||||||
set language(input) {
|
set language(input) {
|
||||||
captions.setLanguage.call(this, input);
|
captions.setLanguage.call(this, input, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user