From b12eeb0eb7b59671bb887770fc787940e4659a21 Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Mon, 4 Jun 2018 14:22:09 +0200 Subject: [PATCH 1/7] Merge captions setText and setCue into updateCues (fixes #998 and vimeo cuechange event) --- src/js/captions.js | 93 +++++++++++++++++------------------------ src/js/plugins/vimeo.js | 11 ++--- src/js/plyr.js | 2 +- src/js/utils.js | 7 ++++ 4 files changed, 50 insertions(+), 63 deletions(-) diff --git a/src/js/captions.js b/src/js/captions.js index 30c4bc74..0baa0820 100644 --- a/src/js/captions.js +++ b/src/js/captions.js @@ -110,23 +110,16 @@ const captions = { if (this.isHTML5 && this.isVideo) { captions.getTracks.call(this).forEach(track => { // Show track - utils.on(track, 'cuechange', event => captions.setCue.call(this, event)); + utils.on(track, 'cuechange', () => captions.updateCues.call(this)); // 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); + // If we change the active track while a cue is already displayed we need to update it + captions.updateCues.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.isVimeo && this.captions.active) { this.embed.enableTextTrack(this.language); } @@ -193,56 +186,48 @@ const captions = { return i18n.get('disabled', this.config); }, - // 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 { activeCues } = track; - const active = activeCues.length && 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.setText.call(this, active.getCueAsHTML()); - } else { - captions.setText.call(this, null); - } - - utils.dispatchEvent.call(this, this.media, 'cuechange'); - }, - - // Set the current caption - setText(input) { + // Update captions using current track's active cues + // Also optional array argument in case there isn't any track (ex: vimeo) + updateCues(input) { // Requires UI if (!this.supported.ui) { return; } - if (utils.is.element(this.elements.captions)) { - const content = utils.createElement('span'); - - // Empty the container - utils.emptyElement(this.elements.captions); - - // Default to empty - const caption = !utils.is.nullOrUndefined(input) ? input : ''; - - // Set the span content - if (utils.is.string(caption)) { - content.innerText = caption.trim(); - } else { - content.appendChild(caption); - } - - // Set new caption text - this.elements.captions.appendChild(content); - } else { + if (!utils.is.element(this.elements.captions)) { this.debug.warn('No captions element to render to'); + return; + } + + // Only accept array or empty input + if (!utils.is.nullOrUndefined(input) && !Array.isArray(input)) { + this.debug.warn('updateCues: Invalid input', input); + return; + } + + let cues = input; + + // Get cues from track + if (!cues) { + const track = captions.getCurrentTrack.call(this); + cues = Array.from((track || {}).activeCues || []) + .map(cue => cue.getCueAsHTML()) + .map(utils.getHTML); + } + + // Set new caption text + const content = cues.map(cueText => cueText.trim()).join('\n'); + const changed = content !== this.elements.captions.innerHTML; + + if (changed) { + // Empty the container and create a new child element + utils.emptyElement(this.elements.captions); + const caption = utils.createElement('span'); + caption.innerHTML = content; + this.elements.captions.appendChild(caption); + + // Trigger event + utils.dispatchEvent.call(this, this.media, 'cuechange'); } }, }; diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index 46d4f3f9..190dd88c 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -301,14 +301,9 @@ const vimeo = { captions.setup.call(player); }); - player.embed.on('cuechange', data => { - let cue = null; - - if (data.cues.length) { - cue = utils.stripHTML(data.cues[0].text); - } - - captions.setText.call(player, cue); + player.embed.on('cuechange', ({ cues = [] }) => { + const strippedCues = cues.map(cue => utils.stripHTML(cue.text)); + captions.updateCues.call(player, strippedCues); }); player.embed.on('loaded', () => { diff --git a/src/js/plyr.js b/src/js/plyr.js index 5c51d617..caa9b55b 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -895,7 +895,7 @@ class Plyr { this.captions.language = language; // Clear caption - captions.setText.call(this, null); + captions.updateCues.call(this, []); // Update captions captions.setLanguage.call(this); diff --git a/src/js/utils.js b/src/js/utils.js index b6ba0941..a152e121 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -833,6 +833,13 @@ const utils = { return fragment.firstChild.innerText; }, + // Like outerHTML, but also works for DocumentFragment + getHTML(element) { + const wrapper = document.createElement('div'); + wrapper.appendChild(element); + return wrapper.innerHTML; + }, + // Get aspect ratio for dimensions getAspectRatio(width, height) { const getRatio = (w, h) => (h === 0 ? w : getRatio(h, w % h)); From a80b31bf98ca14279128ecbf32b13ba748e438fc Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Fri, 8 Jun 2018 13:31:29 +0200 Subject: [PATCH 2/7] Fix #1003: Formatted captions issue --- src/js/captions.js | 2 +- src/js/defaults.js | 1 + src/sass/components/captions.scss | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/js/captions.js b/src/js/captions.js index 0baa0820..b3723885 100644 --- a/src/js/captions.js +++ b/src/js/captions.js @@ -222,7 +222,7 @@ const captions = { if (changed) { // Empty the container and create a new child element utils.emptyElement(this.elements.captions); - const caption = utils.createElement('span'); + const caption = utils.createElement('span', utils.getAttributesFromSelector(this.config.selectors.caption)); caption.innerHTML = content; this.elements.captions.appendChild(caption); diff --git a/src/js/defaults.js b/src/js/defaults.js index 16df8624..b1bdaa65 100644 --- a/src/js/defaults.js +++ b/src/js/defaults.js @@ -328,6 +328,7 @@ const defaults = { }, progress: '.plyr__progress', captions: '.plyr__captions', + caption: '.plyr__caption', menu: { quality: '.js-plyr__menu__list--quality', }, diff --git a/src/sass/components/captions.scss b/src/sass/components/captions.scss index 9dfc2be8..8fce581a 100644 --- a/src/sass/components/captions.scss +++ b/src/sass/components/captions.scss @@ -21,7 +21,7 @@ transition: transform 0.4s ease-in-out; width: 100%; - span { + .plyr__caption { background: $plyr-captions-bg; border-radius: 2px; box-decoration-break: clone; From b57784d1a546e133779fa7918e928dbf4e30ce68 Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Fri, 8 Jun 2018 13:45:20 +0200 Subject: [PATCH 3/7] Change debug warn 'Unsupported language option' to log 'Language option doesn't yet exist' since it doesn't have to be an error --- src/js/plyr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/plyr.js b/src/js/plyr.js index caa9b55b..6387fd6c 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -874,7 +874,7 @@ class Plyr { // Check for support if (!this.options.captions.includes(language)) { - this.debug.warn(`Unsupported language option: ${language}`); + this.debug.log(`Language option: ${language} doesn't yet exist`); return; } From 9dc0f28800fd17eef442f868bd12c3017400a992 Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Fri, 8 Jun 2018 21:16:17 +0200 Subject: [PATCH 4/7] Avoid condition in getTracks --- src/js/captions.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/js/captions.js b/src/js/captions.js index b3723885..38167d7a 100644 --- a/src/js/captions.js +++ b/src/js/captions.js @@ -127,13 +127,10 @@ const captions = { // 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 => [ + // 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)); From 1fab4919c01347a29e11cbd78fedcddfabd1b814 Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Sat, 9 Jun 2018 14:14:34 +0200 Subject: [PATCH 5/7] controls.createMenuItem: Change input to object (too many params made it hard to read) --- src/js/controls.js | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/js/controls.js b/src/js/controls.js index 20518f9c..e9529e4e 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -376,7 +376,7 @@ const controls = { }, // Create a settings menu item - createMenuItem(value, list, type, title, badge = null, checked = false) { + createMenuItem({value, list, type, title, badge = null, checked = false}) { const item = utils.createElement('li'); const label = utils.createElement('label', { @@ -680,8 +680,13 @@ const controls = { return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1; }) .forEach(quality => { - const label = controls.getLabel.call(this, 'quality', quality); - controls.createMenuItem.call(this, quality, list, type, label, getBadge(quality)); + controls.createMenuItem.call(this, { + value: quality, + list, + type, + title: controls.getLabel.call(this, 'quality', quality), + badge: getBadge(quality), + }); }); controls.updateSetting.call(this, type, list); @@ -861,15 +866,14 @@ const controls = { // Generate options tracks.forEach(track => { - controls.createMenuItem.call( - this, - track.language, + controls.createMenuItem.call(this, { + value: track.language, list, - 'language', - track.label, - track.language !== 'enabled' ? controls.createBadge.call(this, track.language.toUpperCase()) : null, - track.language.toLowerCase() === this.language, - ); + type: 'language', + title: track.label, + badge: track.language !== 'enabled' ? controls.createBadge.call(this, track.language.toUpperCase()) : null, + checked: track.language.toLowerCase() === this.language, + }); }); controls.updateSetting.call(this, type, list); @@ -927,8 +931,12 @@ const controls = { // Create items this.options.speed.forEach(speed => { - const label = controls.getLabel.call(this, 'speed', speed); - controls.createMenuItem.call(this, speed, list, type, label); + controls.createMenuItem.call(this, { + value: speed, + list, + type, + title: controls.getLabel.call(this, 'speed', speed), + }); }); controls.updateSetting.call(this, type, list); From c83487a293d61d3a1add31018918e9c831bbcac2 Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Fri, 8 Jun 2018 14:49:35 +0200 Subject: [PATCH 6/7] Fix #1017, fix #980, fix #1014: Captions rewrite (use index internally) --- readme.md | 3 +- src/js/captions.js | 174 +++++++++++++++++++++++++++++--------------- src/js/controls.js | 49 +++++-------- src/js/listeners.js | 2 +- src/js/plyr.js | 76 +++++++------------ 5 files changed, 163 insertions(+), 141 deletions(-) diff --git a/readme.md b/readme.md index 9f4819e3..2558cbe7 100644 --- a/readme.md +++ b/readme.md @@ -407,7 +407,8 @@ player.fullscreen.active; // false; | `source` | ✓ | ✓ | Gets or sets the current source for the player. The setter accepts an object. See [source setter](#source-setter) below for examples. | | `poster` | ✓ | ✓ | Gets or sets the current poster image for the player. The setter accepts a string; the URL for the updated poster image. | | `autoplay` | ✓ | ✓ | Gets or sets the autoplay state of the player. The setter accepts a boolean. | -| `language` | ✓ | ✓ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include. | +| `currentTrack` | ✓ | ✓ | Gets or sets the caption track by index. `-1` means there track is missing or captions is not active | +| `language` | ✓ | ✓ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include. If your captions don't have any language, or if you have multiple tracks with the same language, you may want to use `currentTrack` instead. | | `fullscreen.active` | ✓ | - | Returns a boolean indicating if the current player is in fullscreen mode. | | `fullscreen.enabled` | ✓ | - | Returns a boolean indicating if the current player has fullscreen enabled. | | `pip` | ✓ | ✓ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ on MacOS Sierra+ and iOS 10+. | 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 + set(index, setLanguage = true, show = true) { + const tracks = captions.getTracks.call(this); + + // Disable captions if setting to -1 + if (index === -1) { + this.toggleCaptions(false); + return; + } + + if (!utils.is.number(index)) { + this.debug.warn('Invalid caption argument', index); + return; + } + + if (!(index in tracks)) { + this.debug.warn('Track not found', index); + return; + } + + if (this.captions.currentTrack !== index) { + this.captions.currentTrack = index; + const track = captions.getCurrentTrack.call(this); + 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) { + this.captions.language = language; + } + + // Handle Vimeo captions + if (this.isVimeo) { + this.embed.enableTextTrack(language); + } + + // Trigger event + utils.dispatchEvent.call(this, this.media, 'languagechange'); + } + if (this.isHTML5 && this.isVideo) { - captions.getTracks.call(this).forEach(track => { - // Show track - utils.on(track, 'cuechange', () => captions.updateCues.call(this)); - - // Turn off native caption rendering to avoid double captions - // eslint-disable-next-line - track.mode = 'hidden'; - }); - // If we change the active track while a cue is already displayed we need to update it captions.updateCues.call(this); + } - } else if (this.isVimeo && this.captions.active) { - this.embed.enableTextTrack(this.language); + // Show captions + if (show) { + this.toggleCaptions(true); } }, - // Get the tracks - getTracks() { + 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); + }, + + // 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 { textTracks } = this.media || {}; - // Filter out invalid tracks kinds (like metadata) - return Array.from(textTracks || []).filter(track => [ - 'captions', - 'subtitles', - ].includes(track.kind)); + 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() { + getCurrentTrack(fromLanguage = false) { const tracks = captions.getTracks.call(this); - - if (!tracks.length) { - return null; - } - - // Get track based on current language - let track = tracks.find(track => track.language.toLowerCase() === this.language); - - // Get the with default attribute - if (!track) { - track = utils.getElement.call(this, 'track[default]'); - } - - // Get the first track - if (!track) { - [track] = tracks; - } - - return track; + 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 diff --git a/src/js/controls.js b/src/js/controls.js index e9529e4e..058e636f 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -727,16 +727,7 @@ const controls = { switch (setting) { case 'captions': - if (this.captions.active) { - if (this.options.captions.length > 2 || !this.options.captions.some(lang => lang === 'enabled')) { - value = this.captions.language; - } else { - value = 'enabled'; - } - } else { - value = ''; - } - + value = this.currentTrack; break; default: @@ -836,10 +827,10 @@ const controls = { // TODO: Captions or language? Currently it's mixed const type = 'captions'; const list = this.elements.settings.panes.captions.querySelector('ul'); + const tracks = captions.getTracks.call(this); // Toggle the pane and tab - const toggle = captions.getTracks.call(this).length; - controls.toggleTab.call(this, type, toggle); + controls.toggleTab.call(this, type, tracks.length); // Empty the menu utils.emptyElement(list); @@ -848,33 +839,31 @@ const controls = { controls.checkMenu.call(this); // If there's no captions, bail - if (!toggle) { + if (!tracks.length) { return; } - // Re-map the tracks into just the data we need - const tracks = captions.getTracks.call(this).map(track => ({ - language: !utils.is.empty(track.language) ? track.language : 'enabled', - label: captions.getLabel.call(this, track), + // Generate options data + const options = tracks.map((track, value) => ({ + value, + checked: this.captions.active && this.currentTrack === value, + title: captions.getLabel.call(this, track), + badge: track.language && controls.createBadge.call(this, track.language.toUpperCase()), + list, + type: 'language', })); // Add the "Disabled" option to turn off captions - tracks.unshift({ - language: '', - label: i18n.get('disabled', this.config), + options.unshift({ + value: -1, + checked: !this.captions.active, + title: i18n.get('disabled', this.config), + list, + type: 'language', }); // Generate options - tracks.forEach(track => { - controls.createMenuItem.call(this, { - value: track.language, - list, - type: 'language', - title: track.label, - badge: track.language !== 'enabled' ? controls.createBadge.call(this, track.language.toUpperCase()) : null, - checked: track.language.toLowerCase() === this.language, - }); - }); + options.forEach(controls.createMenuItem.bind(this)); controls.updateSetting.call(this, type, list); }, diff --git a/src/js/listeners.js b/src/js/listeners.js index 81f5271c..72b60cc0 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -523,7 +523,7 @@ class Listeners { proxy( event, () => { - this.player.language = event.target.value; + this.player.currentTrack = Number(event.target.value); showHomeTab(); }, 'language', diff --git a/src/js/plyr.js b/src/js/plyr.js index 6387fd6c..b6f355ac 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -84,7 +84,8 @@ class Plyr { // Captions this.captions = { active: null, - currentTrack: null, + currentTrack: -1, + meta: new WeakMap(), }; // Fullscreen @@ -96,7 +97,6 @@ class Plyr { this.options = { speed: [], quality: [], - captions: [], }; // Debugging @@ -854,61 +854,35 @@ class Plyr { } /** - * Set the captions language - * @param {string} - Two character ISO language code (e.g. EN, FR, PT, etc) + * Set the caption track by index + * @param {number} - Caption index */ - set language(input) { - // Nothing specified - if (!utils.is.string(input)) { - return; - } - - // If empty string is passed, assume disable captions - if (utils.is.empty(input)) { - this.toggleCaptions(false); - return; - } - - // Normalize - const language = input.toLowerCase(); - - // Check for support - if (!this.options.captions.includes(language)) { - this.debug.log(`Language option: ${language} doesn't yet exist`); - return; - } - - // Ensure captions are enabled - this.toggleCaptions(true); - - // Enabled only - if (language === 'enabled') { - return; - } - - // If nothing to change, bail - if (this.language === language) { - return; - } - - // Update config - this.captions.language = language; - - // Clear caption - captions.updateCues.call(this, []); - - // Update captions - captions.setLanguage.call(this); - - // Trigger an event - utils.dispatchEvent.call(this, this.media, 'languagechange'); + set currentTrack(input) { + captions.set.call(this, input); } /** - * Get the current captions language + * Get the current caption track index (-1 if disabled) + */ + get currentTrack() { + const { active, currentTrack } = this.captions; + return active ? currentTrack : -1; + } + + /** + * Set the wanted language for captions + * Since tracks can be added later it won't update the actual caption track until there is a matching track + * @param {string} - Two character ISO language code (e.g. EN, FR, PT, etc) + */ + set language(input) { + captions.setLanguage.call(this, input); + } + + /** + * Get the current track's language */ get language() { - return this.captions.language; + return (captions.getCurrentTrack.call(this) || {}).language; } /** From 41012a9843558f67bac7969ffe5bf7161a10893c Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Sun, 10 Jun 2018 22:00:15 +0200 Subject: [PATCH 7/7] Typo --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 2558cbe7..248d324f 100644 --- a/readme.md +++ b/readme.md @@ -407,8 +407,8 @@ player.fullscreen.active; // false; | `source` | ✓ | ✓ | Gets or sets the current source for the player. The setter accepts an object. See [source setter](#source-setter) below for examples. | | `poster` | ✓ | ✓ | Gets or sets the current poster image for the player. The setter accepts a string; the URL for the updated poster image. | | `autoplay` | ✓ | ✓ | Gets or sets the autoplay state of the player. The setter accepts a boolean. | -| `currentTrack` | ✓ | ✓ | Gets or sets the caption track by index. `-1` means there track is missing or captions is not active | -| `language` | ✓ | ✓ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include. If your captions don't have any language, or if you have multiple tracks with the same language, you may want to use `currentTrack` instead. | +| `currentTrack` | ✓ | ✓ | Gets or sets the caption track by index. `-1` means the track is missing or captions is not active | +| `language` | ✓ | ✓ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include. If your captions don't have any language data, or if you have multiple tracks with the same language, you may want to use `currentTrack` instead. | | `fullscreen.active` | ✓ | - | Returns a boolean indicating if the current player is in fullscreen mode. | | `fullscreen.enabled` | ✓ | - | Returns a boolean indicating if the current player has fullscreen enabled. | | `pip` | ✓ | ✓ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ on MacOS Sierra+ and iOS 10+. |