Captions fix
This commit is contained in:
@ -33,8 +33,6 @@ const captions = {
|
||||
|
||||
// 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);
|
||||
@ -49,117 +47,102 @@ const captions = {
|
||||
'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;
|
||||
utils.insertAfter(this.elements.captions, this.elements.wrapper);
|
||||
}
|
||||
|
||||
// Set the class hook
|
||||
utils.toggleClass(
|
||||
this.elements.container,
|
||||
this.config.classNames.captions.enabled,
|
||||
!utils.is.empty(this.captions.tracks)
|
||||
!utils.is.empty(captions.getTracks.call(this))
|
||||
);
|
||||
|
||||
// If no caption file exists, hide container for caption text
|
||||
if (utils.is.empty(this.captions.tracks)) {
|
||||
if (utils.is.empty(captions.getTracks.call(this))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set language
|
||||
captions.setLanguage.call(this);
|
||||
|
||||
// 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.toLowerCase() === this.language.toLowerCase()) {
|
||||
this.captions.currentTrack = track;
|
||||
console.warn(`Set current track to ${this.language}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 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
|
||||
// eslint-disable-next-line
|
||||
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) {
|
||||
captions.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);
|
||||
}
|
||||
},
|
||||
|
||||
// Set the captions language
|
||||
setLanguage() {
|
||||
// Setup HTML5 track rendering
|
||||
if (this.type === 'video') {
|
||||
captions.getTracks.call(this).forEach(track => {
|
||||
// Remove previous bindings
|
||||
utils.on(track, 'cuechange', event => captions.setCue.call(this, event));
|
||||
|
||||
// Turn off native caption rendering to avoid double captions
|
||||
// eslint-disable-next-line
|
||||
track.mode = 'hidden';
|
||||
});
|
||||
|
||||
// Get current track
|
||||
const currentTrack = captions.getCurrentTrack.call(this);
|
||||
|
||||
// Check if suported kind
|
||||
if (utils.is.track(currentTrack)) {
|
||||
// If we change the active track while a cue is already displayed we need to update it
|
||||
if (Array.from(currentTrack.activeCues || []).length) {
|
||||
captions.setCue.call(this, currentTrack);
|
||||
}
|
||||
}
|
||||
} else if (this.type === 'vimeo' && this.captions.active) {
|
||||
this.embed.enableTextTrack(this.language);
|
||||
}
|
||||
},
|
||||
|
||||
// Get the tracks
|
||||
getTracks() {
|
||||
// Return empty array at least
|
||||
if (utils.is.nullOrUndefined(this.media)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Only get accepted kinds
|
||||
return Array.from(this.media.textTracks || []).filter(track => ['captions', 'subtitles'].includes(track.kind));
|
||||
},
|
||||
|
||||
// Get the current track for the current language
|
||||
getCurrentTrack() {
|
||||
return captions.getTracks.call(this).find(track => track.language.toLowerCase() === this.language);
|
||||
},
|
||||
|
||||
// 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];
|
||||
const currentTrack = captions.getCurrentTrack.call(this);
|
||||
|
||||
// Only display current track
|
||||
if (track !== currentTrack) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Display a cue, if there is one
|
||||
if (utils.is.cue(active)) {
|
||||
captions.set.call(this, active.getCueAsHTML());
|
||||
captions.setText.call(this, active.getCueAsHTML());
|
||||
} else {
|
||||
captions.set.call(this);
|
||||
captions.setText.call(this, null);
|
||||
}
|
||||
|
||||
utils.dispatchEvent.call(this, this.media, 'cuechange');
|
||||
},
|
||||
|
||||
// Set the current caption
|
||||
set(input) {
|
||||
setText(input) {
|
||||
// Requires UI
|
||||
if (!this.supported.ui) {
|
||||
return;
|
||||
@ -172,7 +155,7 @@ const captions = {
|
||||
utils.emptyElement(this.elements.captions);
|
||||
|
||||
// Default to empty
|
||||
const caption = !utils.is.undefined(input) ? input : '';
|
||||
const caption = !utils.is.nullOrUndefined(input) ? input : '';
|
||||
|
||||
// Set the span content
|
||||
if (utils.is.string(caption)) {
|
||||
|
17
src/js/controls.js
vendored
17
src/js/controls.js
vendored
@ -5,6 +5,7 @@
|
||||
import support from './support';
|
||||
import utils from './utils';
|
||||
import ui from './ui';
|
||||
import captions from './captions';
|
||||
|
||||
// Sniff out the browser
|
||||
const browser = utils.getBrowser();
|
||||
@ -642,12 +643,16 @@ const controls = {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!support.textTracks || utils.is.empty(this.captions.tracks)) {
|
||||
if (!support.textTracks || !captions.getTracks.call(this).length) {
|
||||
return this.config.i18n.none;
|
||||
}
|
||||
|
||||
if (this.captions.enabled) {
|
||||
return this.captions.currentTrack.label;
|
||||
const currentTrack = captions.getCurrentTrack.call(this);
|
||||
|
||||
if (utils.is.track(currentTrack)) {
|
||||
return currentTrack.label;
|
||||
}
|
||||
}
|
||||
|
||||
return this.config.i18n.disabled;
|
||||
@ -660,19 +665,19 @@ const controls = {
|
||||
const list = this.elements.settings.panes.captions.querySelector('ul');
|
||||
|
||||
// Toggle the pane and tab
|
||||
const toggle = !utils.is.empty(this.captions.tracks);
|
||||
controls.toggleTab.call(this, type, toggle);
|
||||
const hasTracks = captions.getTracks.call(this).length;
|
||||
controls.toggleTab.call(this, type, hasTracks);
|
||||
|
||||
// Empty the menu
|
||||
utils.emptyElement(list);
|
||||
|
||||
// If there's no captions, bail
|
||||
if (utils.is.empty(this.captions.tracks)) {
|
||||
if (!hasTracks) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-map the tracks into just the data we need
|
||||
const tracks = Array.from(this.captions.tracks).map(track => ({
|
||||
const tracks = captions.getTracks.call(this).map(track => ({
|
||||
language: track.language,
|
||||
label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase(),
|
||||
}));
|
||||
|
@ -51,7 +51,7 @@ const fullscreen = {
|
||||
return false;
|
||||
}
|
||||
|
||||
const target = utils.is.undefined(element) ? document.body : element;
|
||||
const target = utils.is.nullOrUndefined(element) ? document.body : element;
|
||||
|
||||
switch (prefix) {
|
||||
case '':
|
||||
@ -71,7 +71,7 @@ const fullscreen = {
|
||||
return false;
|
||||
}
|
||||
|
||||
const target = utils.is.undefined(element) ? document.body : element;
|
||||
const target = utils.is.nullOrUndefined(element) ? document.body : element;
|
||||
|
||||
return !prefix.length
|
||||
? target.requestFullScreen()
|
||||
|
@ -459,41 +459,24 @@ const listeners = {
|
||||
|
||||
// Settings menu
|
||||
utils.on(this.elements.settings.form, 'click', event => {
|
||||
// Show tab in menu
|
||||
controls.showTab.call(this, event);
|
||||
event.stopPropagation();
|
||||
|
||||
// Settings menu items - use event delegation as items are added/removed
|
||||
if (utils.matches(event.target, this.config.selectors.inputs.language)) {
|
||||
// Settings - Language
|
||||
proxy(event, 'language', () => {
|
||||
const language = event.target.value;
|
||||
|
||||
this.toggleCaptions(!utils.is.empty(language));
|
||||
|
||||
if (!utils.is.empty(language)) {
|
||||
this.language = event.target.value.toLowerCase();
|
||||
}
|
||||
this.language = event.target.value;
|
||||
});
|
||||
} else if (utils.matches(event.target, this.config.selectors.inputs.quality)) {
|
||||
// Settings - Quality
|
||||
proxy(event, 'quality', () => {
|
||||
this.quality = event.target.value;
|
||||
});
|
||||
} else if (utils.matches(event.target, this.config.selectors.inputs.speed)) {
|
||||
// Settings - Speed
|
||||
proxy(event, 'speed', () => {
|
||||
this.speed = parseFloat(event.target.value);
|
||||
});
|
||||
} /* else if (utils.matches(event.target, this.config.selectors.buttons.loop)) {
|
||||
// Settings - Looping
|
||||
// TODO: use toggle buttons
|
||||
proxy(event, 'loop', () => {
|
||||
// TODO: This should be done in the method itself I think
|
||||
// var value = event.target.getAttribute('data-loop__value') || event.target.getAttribute('data-loop__type');
|
||||
|
||||
this.console.warn('Set loop');
|
||||
});
|
||||
} */
|
||||
} else {
|
||||
controls.showTab.call(this, event);
|
||||
}
|
||||
});
|
||||
|
||||
// Seek
|
||||
|
@ -221,7 +221,7 @@ const vimeo = {
|
||||
|
||||
// Get captions
|
||||
player.embed.getTextTracks().then(tracks => {
|
||||
player.captions.tracks = tracks;
|
||||
player.media.textTracks = tracks;
|
||||
captions.setup.call(player);
|
||||
});
|
||||
|
||||
@ -232,7 +232,7 @@ const vimeo = {
|
||||
cue = utils.stripHTML(data.cues[0].text);
|
||||
}
|
||||
|
||||
captions.set.call(player, cue);
|
||||
captions.setText.call(player, cue);
|
||||
});
|
||||
|
||||
player.embed.on('loaded', () => {
|
||||
|
@ -81,7 +81,6 @@ class Plyr {
|
||||
// Captions
|
||||
this.captions = {
|
||||
enabled: null,
|
||||
tracks: null,
|
||||
currentTrack: null,
|
||||
};
|
||||
|
||||
@ -116,7 +115,7 @@ class Plyr {
|
||||
this.console.log('Support', support);
|
||||
|
||||
// We need an element to setup
|
||||
if (this.media === null || utils.is.undefined(this.media) || !utils.is.htmlElement(this.media)) {
|
||||
if (utils.is.nullOrUndefined(this.media) || !utils.is.htmlElement(this.media)) {
|
||||
this.console.error('Setup failed: no suitable element passed');
|
||||
return;
|
||||
}
|
||||
@ -665,6 +664,14 @@ class Plyr {
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle captions based on input
|
||||
this.toggleCaptions(!utils.is.empty(input));
|
||||
|
||||
// If empty string is passed, assume disable captions
|
||||
if (utils.is.empty(input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalize
|
||||
const language = input.toLowerCase();
|
||||
|
||||
@ -673,20 +680,17 @@ class Plyr {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset UI
|
||||
this.toggleCaptions(true);
|
||||
|
||||
// Update config
|
||||
this.captions.language = language;
|
||||
|
||||
// Clear caption
|
||||
captions.setText.call(this, null);
|
||||
|
||||
// Update captions
|
||||
captions.setLanguage.call(this);
|
||||
|
||||
// Trigger an event
|
||||
utils.dispatchEvent.call(this, this.media, 'languagechange');
|
||||
|
||||
// Clear caption
|
||||
captions.set.call(this);
|
||||
|
||||
// Re-run setup
|
||||
captions.setup.call(this);
|
||||
}
|
||||
|
||||
get language() {
|
||||
|
@ -23,49 +23,42 @@ const utils = {
|
||||
return this.getConstructor(input) === Function;
|
||||
},
|
||||
array(input) {
|
||||
return !this.undefined(input) && Array.isArray(input);
|
||||
return !this.nullOrUndefined(input) && Array.isArray(input);
|
||||
},
|
||||
nodeList(input) {
|
||||
return !this.undefined(input) && input instanceof NodeList;
|
||||
return this.instanceof(input, window.NodeList);
|
||||
},
|
||||
htmlElement(input) {
|
||||
return !this.undefined(input) && input instanceof HTMLElement;
|
||||
return this.instanceof(input, window.HTMLElement);
|
||||
},
|
||||
textNode(input) {
|
||||
return this.getConstructor(input) === Text;
|
||||
},
|
||||
event(input) {
|
||||
return !this.undefined(input) && input instanceof Event;
|
||||
return this.instanceof(input, window.Event);
|
||||
},
|
||||
cue(input) {
|
||||
return this.instanceOf(input, window.TextTrackCue) || this.instanceOf(input, window.VTTCue);
|
||||
return this.instanceof(input, window.TextTrackCue) || this.instanceof(input, window.VTTCue);
|
||||
},
|
||||
track(input) {
|
||||
return (
|
||||
!this.undefined(input) && (this.instanceOf(input, window.TextTrack) || typeof input.kind === 'string')
|
||||
);
|
||||
return this.instanceof(input, window.TextTrack) || this.string(input.kind);
|
||||
},
|
||||
undefined(input) {
|
||||
return input !== null && typeof input === 'undefined';
|
||||
nullOrUndefined(input) {
|
||||
return input === null || typeof input === 'undefined';
|
||||
},
|
||||
empty(input) {
|
||||
return (
|
||||
input === null ||
|
||||
typeof input === 'undefined' ||
|
||||
this.nullOrUndefined(input) ||
|
||||
((this.string(input) || this.array(input) || this.nodeList(input)) && !input.length) ||
|
||||
(this.object(input) && !Object.keys(input).length)
|
||||
);
|
||||
},
|
||||
getConstructor(input) {
|
||||
if (input === null || typeof input === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return input.constructor;
|
||||
},
|
||||
instanceOf(input, constructor) {
|
||||
instanceof(input, constructor) {
|
||||
return Boolean(input && constructor && input instanceof constructor);
|
||||
},
|
||||
getConstructor(input) {
|
||||
return !this.nullOrUndefined(input) ? input.constructor : null;
|
||||
},
|
||||
},
|
||||
|
||||
// Unfortunately, due to mixed support, UA sniffing is required
|
||||
@ -474,7 +467,7 @@ const utils = {
|
||||
// Toggle event listener
|
||||
toggleListener(elements, event, callback, toggle, passive, capture) {
|
||||
// Bail if no elements
|
||||
if (elements === null || utils.is.undefined(elements)) {
|
||||
if (utils.is.nullOrUndefined(elements)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -131,28 +131,24 @@
|
||||
label.plyr__control {
|
||||
padding-left: @plyr-control-padding;
|
||||
|
||||
/*input[type='radio'] {
|
||||
position: relative;
|
||||
left: -@plyr-control-padding;
|
||||
}*/
|
||||
|
||||
input[type='radio'] + span {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
flex-shrink: 0;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
border-radius: 100%;
|
||||
background: fade(#000, 10%);
|
||||
margin-right: @plyr-control-spacing;
|
||||
box-shadow: inset 0 1px 1px fade(#000, 15%);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
background: #fff;
|
||||
@ -169,6 +165,11 @@
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.plyr__tab-focus input[type='radio'] + span,
|
||||
&:hover input[type='radio'] + span {
|
||||
background: fade(#000, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
// Option value
|
||||
|
Reference in New Issue
Block a user