From 1d0cf16254d0e1a90c88f2e0733c6c9ec021fe6e Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Thu, 7 Jun 2018 11:47:34 +0200 Subject: [PATCH 01/18] Readme: Replace streaming section with codepen templates for all supported formats and libraries (and updated code) --- readme.md | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/readme.md b/readme.md index c86faba8..cf2e1915 100644 --- a/readme.md +++ b/readme.md @@ -51,6 +51,10 @@ Some awesome folks have made plugins for CMSs and Components for JavaScript fram Here's a quick run through on getting up and running. There's also a [demo on Codepen](http://codepen.io/sampotts/pen/jARJYp). You can grab all of the source with [NPM](https://www.npmjs.com/package/plyr) using `npm install plyr`. +### Try Plyr online + +You can try Plyr in Codepen using our minimal templates: [HTML5 video](https://codepen.io/pen?template=bKeqpr), [HTML5 audio](https://codepen.io/pen?template=rKLywR), [YouTube](https://codepen.io/pen?template=GGqbbJ), [Vimeo](https://codepen.io/pen?template=bKeXNq). For Streaming we also have example integrations with: [Dash.js](https://codepen.io/pen?template=zaBgBy), [Hls.js](https://codepen.io/pen?template=oyLKQb) and [Shaka Player](https://codepen.io/pen?template=ZRpzZO) + ### HTML Plyr extends upon the standard [HTML5 media element](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement) markup so that's all you need for those types. @@ -607,17 +611,6 @@ document then the shortcuts will work when any element has focus, apart from an | `C` | Toggle captions | | `L` | Toggle loop | -## Streaming - -Because Plyr is an extension of the standard HTML5 video and audio elements, third party streaming plugins can be used with Plyr. Massive thanks to Matias -Russitto ([@russitto](https://github.com/russitto)) for working on this. Here's a few examples: - -* Using [hls.js](https://github.com/dailymotion/hls.js) - [Demo](http://codepen.io/sampotts/pen/JKEMqB) -* Using [Shaka](https://github.com/google/shaka-player) - [Demo](http://codepen.io/sampotts/pen/zBNpVR) -* Using [dash.js](https://github.com/Dash-Industry-Forum/dash.js) - [Demo](http://codepen.io/sampotts/pen/BzpJXN) - -_Note_: These need updating to use the new v3 syntax but would still work. - ## Fullscreen Fullscreen in Plyr is supported by all browsers that [currently support it](http://caniuse.com/#feat=fullscreen). From ed14b656a8add7b266b2d76ceaacbff7bbf8e60e Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Thu, 7 Jun 2018 11:47:51 +0200 Subject: [PATCH 02/18] Add contributing document --- CONTRIBUTING.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..db0a9cd7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,46 @@ +# Contributing + +We welcome bug reports, feature requests and pull requests. If you want to help us out, please follow these guidelines, in order to avoid redundant work. + +## Reporting issues + +Our GitHub issue tracker is for bug reports and feature requests. Don't create support issues here. Use [Stack Overflow](https://stackoverflow.com/) or [our Slack](https://bit.ly/plyr-chat) for that. + +Please verify that your issue hasn't already been answered by our FAQ (https://github.com/sampotts/plyr/wiki/FAQ), or that there isn't already an open issue for it. + +When applicable, check that your problem doesn't happen without Plyr (see [FAQ#1](https://github.com/sampotts/plyr/wiki/FAQ#1-does-plyr-work-with--)). + +Verify that you are following the documentation, are using the latest version of Plyr, and aren't getting any errors in your own code, causing the issues. + +Describe the issue as detailed as possible, answering these questions: + +* Does it happen only with specific options and/or specific browsers? +* Does is happen only with HTML5 video, audio, youtube, vimeo or a specific library? +* Does the issue happen on [our demo](https://plyr.io/)? If not, please recreate it with a **minimal** example online. You can use our Codepen templates to get started: + * [HTML5 video](https://codepen.io/pen?template=bKeqpr) + * [HTML5 audio](https://codepen.io/pen?template=rKLywR) + * [YouTube](https://codepen.io/pen?template=GGqbbJ) + * [Vimeo](https://codepen.io/pen?template=bKeXNq) + * [Dash.js integration](https://codepen.io/pen?template=zaBgBy) + * [Hls.js integration](https://codepen.io/pen?template=oyLKQb) + * [Shaka Player integration](https://codepen.io/pen?template=ZRpzZO) + +## Requesting features and improvements + +If you are missing something in Plyr, you can create a GitHub issue for this as well. Since we prioritize fixing bugs first, and may have a lot of other suggestions and architectural changes to work on as well, these may not be at the top of our list. If it's important or urgent to you, you may want to first ensure it's something we want to have in Plyr, and then contribute it as a pull request. + +## Contributing features and documentation + +* Fork Plyr, and create a new branch in your fork, based on the **develop** branch + +* To test locally, you can use the demo. First make sure you have installed the dependencies with `npm install` or `yarn`. Run `gulp` to build while you are working, and run a local server from the repository root directory. If you have Python installed, this command should work: `python -m SimpleHTTPServer 8080`. Then go to `http://localhost:8080/demo/` + +* Develop and test your modifications. + +* Preferably commit your changes as independent logical chunks, with meaningful messages. Make sure you do not commit unnecessary files or changes, such as logging or breakpoints you added for testing, and the build output. + +* If your modifications changes the documented behavior or add new features, document these changes in readme.md. + +* When finished, push the changes to your GitHub repository and send a pull request to **develop**. Describe what your PR does. + +* If the Travis build fails, or if you get a code review with change requests, you can fix these by pushing new or rebased commits to the branch. From 16624b90d3310a80636ab80c56316bb778407ece Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Thu, 7 Jun 2018 14:30:05 +0200 Subject: [PATCH 03/18] Clarifications due to recent non-constructive comments in #1001 --- CONTRIBUTING.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index db0a9cd7..e6abc510 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,6 +25,12 @@ Describe the issue as detailed as possible, answering these questions: * [Hls.js integration](https://codepen.io/pen?template=oyLKQb) * [Shaka Player integration](https://codepen.io/pen?template=ZRpzZO) +It's important that you keep the issue description and replication demo **minimal**. If your implementation is using a framework, library or custom methods, which aren't needed to reproduce the issue, this makes it harder to debug and understand the issue. While it may be relevant to bring this up (ex: "I need Plyr to trigger the event sooner or it breaks Framework X") it also means that the person who is trying to fix the issue either has to know or learn your frameworks, libraries and custom methods, or that no one will try to fix your issue because it's too much work. + +In order to keep things on topic and to avoid bothering people with github notifications, please don't combine multiple problems or bugs into one issue, don't comment on issues unless your comment is related to that issue, and don't post "+1" or "I agree" type of comments. Use the emojis instead. + +Last but not least: Keep a civil tone in issues and comments. Non-constructive comments may be removed. + ## Requesting features and improvements If you are missing something in Plyr, you can create a GitHub issue for this as well. Since we prioritize fixing bugs first, and may have a lot of other suggestions and architectural changes to work on as well, these may not be at the top of our list. If it's important or urgent to you, you may want to first ensure it's something we want to have in Plyr, and then contribute it as a pull request. From b12eeb0eb7b59671bb887770fc787940e4659a21 Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Mon, 4 Jun 2018 14:22:09 +0200 Subject: [PATCH 04/18] 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 05/18] 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 06/18] 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 07/18] 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 08/18] 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 09/18] 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 10/18] 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+. | From d3e98eb27ef58b4a9e44c9aaecf4b420868a280c Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Sun, 10 Jun 2018 23:59:59 +0200 Subject: [PATCH 11/18] Vimeo: Assure state is updated with autoplay (fixes #1016) --- src/js/plugins/vimeo.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index 46d4f3f9..66ba97b7 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -312,6 +312,14 @@ const vimeo = { }); player.embed.on('loaded', () => { + // Assure state and events are updated on autoplay + player.embed.getPaused().then(paused => { + assurePlaybackState.call(player, !paused); + if (!paused) { + utils.dispatchEvent.call(player, player.media, 'playing'); + } + }); + if (utils.is.element(player.embed.element) && player.supported.ui) { const frame = player.embed.element; From 94699f325558e7e77d63010416e6c554ae87fef6 Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Mon, 11 Jun 2018 02:05:26 +0200 Subject: [PATCH 12/18] Fix problem with YouTube and Vimeo seeking while playing --- src/js/plugins/vimeo.js | 10 +++++++--- src/js/plugins/youtube.js | 5 ++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index 46d4f3f9..c7548e3d 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -9,6 +9,9 @@ import utils from './../utils'; // Set playback state and trigger change (only on actual change) function assurePlaybackState(play) { + if (play && !this.embed.hasPlayed) { + this.embed.hasPlayed = true; + } if (this.media.paused === play) { this.media.paused = !play; utils.dispatchEvent.call(this, this.media, play ? 'play' : 'pause'); @@ -153,19 +156,20 @@ const vimeo = { // Get current paused state and volume etc const { embed, media, paused, volume } = player; + const restorePause = paused && !embed.hasPlayed; // Set seeking state and trigger event media.seeking = true; utils.dispatchEvent.call(player, media, 'seeking'); // If paused, mute until seek is complete - Promise.resolve(paused && embed.setVolume(0)) + Promise.resolve(restorePause && embed.setVolume(0)) // Seek .then(() => embed.setCurrentTime(time)) // Restore paused - .then(() => paused && embed.pause()) + .then(() => restorePause && embed.pause()) // Restore volume - .then(() => paused && embed.setVolume(volume)) + .then(() => restorePause && embed.setVolume(volume)) .catch(() => { // Do nothing }); diff --git a/src/js/plugins/youtube.js b/src/js/plugins/youtube.js index 67b8093e..9b067c8a 100644 --- a/src/js/plugins/youtube.js +++ b/src/js/plugins/youtube.js @@ -66,6 +66,9 @@ function mapQualityUnits(levels) { // Set playback state and trigger change (only on actual change) function assurePlaybackState(play) { + if (play && !this.embed.hasPlayed) { + this.embed.hasPlayed = true; + } if (this.media.paused === play) { this.media.paused = !play; utils.dispatchEvent.call(this, this.media, play ? 'play' : 'pause'); @@ -469,7 +472,7 @@ const youtube = { case 1: // Restore paused state (YouTube starts playing on seek if the video hasn't been played yet) - if (player.media.paused) { + if (player.media.paused && !player.embed.hasPlayed) { player.media.pause(); } else { assurePlaybackState.call(player, true); From 16828e975a9a627fb60b0b7f266fe870521a3e08 Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Mon, 11 Jun 2018 05:12:34 +0200 Subject: [PATCH 13/18] Move utils.is.getConstructor() to utils.getConstructor() --- src/js/utils.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/js/utils.js b/src/js/utils.js index 109de031..54635739 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -11,19 +11,19 @@ const utils = { // Check variable types is: { object(input) { - return this.getConstructor(input) === Object; + return utils.getConstructor(input) === Object; }, number(input) { - return this.getConstructor(input) === Number && !Number.isNaN(input); + return utils.getConstructor(input) === Number && !Number.isNaN(input); }, string(input) { - return this.getConstructor(input) === String; + return utils.getConstructor(input) === String; }, boolean(input) { - return this.getConstructor(input) === Boolean; + return utils.getConstructor(input) === Boolean; }, function(input) { - return this.getConstructor(input) === Function; + return utils.getConstructor(input) === Function; }, array(input) { return !this.nullOrUndefined(input) && Array.isArray(input); @@ -38,7 +38,7 @@ const utils = { return this.instanceof(input, Element); }, textNode(input) { - return this.getConstructor(input) === Text; + return utils.getConstructor(input) === Text; }, event(input) { return this.instanceof(input, Event); @@ -65,9 +65,10 @@ const utils = { instanceof(input, constructor) { return Boolean(input && constructor && input instanceof constructor); }, - getConstructor(input) { - return !this.nullOrUndefined(input) ? input.constructor : null; - }, + }, + + getConstructor(input) { + return !utils.is.nullOrUndefined(input) ? input.constructor : null; }, // Unfortunately, due to mixed support, UA sniffing is required From b148adc0aff87e12e50c85fd25c40b80de570505 Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Mon, 11 Jun 2018 05:19:39 +0200 Subject: [PATCH 14/18] Avoid using this to refer to utils or utils.is, since that means methods can't be used statically --- src/js/utils.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/js/utils.js b/src/js/utils.js index 54635739..c36763dd 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -26,40 +26,40 @@ const utils = { return utils.getConstructor(input) === Function; }, array(input) { - return !this.nullOrUndefined(input) && Array.isArray(input); + return !utils.is.nullOrUndefined(input) && Array.isArray(input); }, weakMap(input) { - return this.instanceof(input, WeakMap); + return utils.is.instanceof(input, WeakMap); }, nodeList(input) { - return this.instanceof(input, NodeList); + return utils.is.instanceof(input, NodeList); }, element(input) { - return this.instanceof(input, Element); + return utils.is.instanceof(input, Element); }, textNode(input) { return utils.getConstructor(input) === Text; }, event(input) { - return this.instanceof(input, Event); + return utils.is.instanceof(input, Event); }, cue(input) { - return this.instanceof(input, window.TextTrackCue) || this.instanceof(input, window.VTTCue); + return utils.is.instanceof(input, window.TextTrackCue) || utils.is.instanceof(input, window.VTTCue); }, track(input) { - return this.instanceof(input, TextTrack) || (!this.nullOrUndefined(input) && this.string(input.kind)); + return utils.is.instanceof(input, TextTrack) || (!utils.is.nullOrUndefined(input) && utils.is.string(input.kind)); }, url(input) { - return !this.nullOrUndefined(input) && /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(input); + return !utils.is.nullOrUndefined(input) && /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(input); }, nullOrUndefined(input) { return input === null || typeof input === 'undefined'; }, empty(input) { return ( - this.nullOrUndefined(input) || - ((this.string(input) || this.array(input) || this.nodeList(input)) && !input.length) || - (this.object(input) && !Object.keys(input).length) + utils.is.nullOrUndefined(input) || + ((utils.is.string(input) || utils.is.array(input) || utils.is.nodeList(input)) && !input.length) || + (utils.is.object(input) && !Object.keys(input).length) ); }, instanceof(input, constructor) { @@ -626,16 +626,16 @@ const utils = { formatTime(time = 0, displayHours = false, inverted = false) { // Bail if the value isn't a number if (!utils.is.number(time)) { - return this.formatTime(null, displayHours, inverted); + return utils.formatTime(null, displayHours, inverted); } // Format time component to add leading zero const format = value => `0${value}`.slice(-2); // Breakdown to hours, mins, secs - let hours = this.getHours(time); - const mins = this.getMinutes(time); - const secs = this.getSeconds(time); + let hours = utils.getHours(time); + const mins = utils.getMinutes(time); + const secs = utils.getSeconds(time); // Do we need to display hours? if (displayHours || hours > 0) { @@ -793,10 +793,10 @@ const utils = { // Parse URL if needed if (input.startsWith('http://') || input.startsWith('https://')) { - ({ search } = this.parseUrl(input)); + ({ search } = utils.parseUrl(input)); } - if (this.is.empty(search)) { + if (utils.is.empty(search)) { return null; } From 37a3ab202ac3a3f980a7e5aed7a97a22a1aac5cb Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Mon, 11 Jun 2018 05:42:11 +0200 Subject: [PATCH 15/18] Remove wrapper function around utils.is.element in Plyr.setup() (no lnger needed) --- 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 b6f355ac..181eff9e 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -1133,7 +1133,7 @@ class Plyr { } else if (utils.is.nodeList(selector)) { targets = Array.from(selector); } else if (utils.is.array(selector)) { - targets = selector.filter(i => utils.is.element(i)); + targets = selector.filter(utils.is.element); } if (utils.is.empty(targets)) { From cc97d7be6a127076f4870235e4f8174021656d41 Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Mon, 11 Jun 2018 08:00:46 +0200 Subject: [PATCH 16/18] Fix synthetic event bubble/proxy loses detail --- src/js/listeners.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/listeners.js b/src/js/listeners.js index 72b60cc0..c391ea4c 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -414,7 +414,7 @@ class Listeners { 'keyup', 'keydown', ]).join(' '), event => { - let detail = {}; + let {detail = {}} = event; // Get error details from media if (event.type === 'error') { From efe70ab48efe5eb183c92673a0b321c3004b7f58 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 11 Jun 2018 16:39:35 +1000 Subject: [PATCH 17/18] v3.3.11 --- changelog.md | 816 ++++++++++++++++---------------- demo/dist/demo.js | 110 ++++- demo/dist/demo.js.map | 2 +- demo/dist/demo.min.js | 2 +- demo/dist/demo.min.js.map | 2 +- dist/plyr.js | 2 +- dist/plyr.js.map | 2 +- dist/plyr.min.js | 2 +- dist/plyr.min.js.map | 2 +- dist/plyr.polyfilled.js | 2 +- dist/plyr.polyfilled.js.map | 2 +- dist/plyr.polyfilled.min.js | 2 +- dist/plyr.polyfilled.min.js.map | 2 +- package.json | 4 +- readme.md | 8 +- src/js/defaults.js | 2 +- src/js/plyr.js | 2 +- src/js/plyr.polyfilled.js | 2 +- yarn.lock | 6 +- 19 files changed, 531 insertions(+), 441 deletions(-) diff --git a/changelog.md b/changelog.md index 8f46e701..a4531352 100644 --- a/changelog.md +++ b/changelog.md @@ -1,182 +1,192 @@ +# v3.3.11 + +- Fix synthetic event bubble/proxy loses detail (thanks @friday!) +- Make utils static (thanks @friday!) +- Fix for YouTube and Vimeo pausing after seek (thanks @friday!) +- Vimeo: Update playback state and assure events are triggered on load (thanks @friday!) +- Captions rewrite (use index internally to support missing or duplicate languages) (thanks @friday and @philipgiuliani!) +- Contributing document and codepen demo updates (thanks @friday!) +- Fix for after clicking on the progress bar, keyboard operations will not work (thanks @cky917!) + # v3.3.10 -* Fix for buffer display alignment and incorrect BEM classname -* Fix for playback not resuming position after quality swap (fixes #991, thanks @philipgiuliani!) -* Travis integration (thanks @friday!) -* Translate quality badges and quality names (thanks @philipgiuliani!) -* Improve captions handling for streaming (thanks @friday!) -* Call duration update method manually if user config has duration (thanks @friday!) +- Fix for buffer display alignment and incorrect BEM classname +- Fix for playback not resuming position after quality swap (fixes #991, thanks @philipgiuliani!) +- Travis integration (thanks @friday!) +- Translate quality badges and quality names (thanks @philipgiuliani!) +- Improve captions handling for streaming (thanks @friday!) +- Call duration update method manually if user config has duration (thanks @friday!) # v3.3.9 Again, more changes from @friday! -* Restore window reference in `utils.is.cue()` -* Fix InvalidStateError and IE11 issues -* Respect storage being disabled for storage getter +- Restore window reference in `utils.is.cue()` +- Fix InvalidStateError and IE11 issues +- Respect storage being disabled for storage getter # v3.3.8 Many changes here thanks to @friday: -* Added missing URL polyfill -* Pause while seeking to mimic default HTML5 behaviour -* Add `seeked` event listener to update progress (fixes #966) -* Trigger seeked event in youtube plugin if either playing or paused (fixes #921) -* Fix for YouTube and Vimeo autoplays on seek (fixes #876) -* Toggle controls improvements -* Cleanup unused code -* Poster image loading improvements -* Fix for seek tooltip vs click accuracy +- Added missing URL polyfill +- Pause while seeking to mimic default HTML5 behaviour +- Add `seeked` event listener to update progress (fixes #966) +- Trigger seeked event in youtube plugin if either playing or paused (fixes #921) +- Fix for YouTube and Vimeo autoplays on seek (fixes #876) +- Toggle controls improvements +- Cleanup unused code +- Poster image loading improvements +- Fix for seek tooltip vs click accuracy # v3.3.7 -* Poster fixes (thanks @friday) -* Grid tweak +- Poster fixes (thanks @friday) +- Grid tweak # v3.3.6 -* Vimeo fixes for mute state -* Vimeo ID fix (fixes #945) -* Use `
` for poster container -* Tooltip fixes for unicode languages (fixes #943) +- Vimeo fixes for mute state +- Vimeo ID fix (fixes #945) +- Use `
` for poster container +- Tooltip fixes for unicode languages (fixes #943) # v3.3.5 -* Removed `.load()` call as it breaks HLS (see #870) +- Removed `.load()` call as it breaks HLS (see #870) # v3.3.4 -* Fix for controls sometimes not showing while video is playing -* Fixed logic for show home tab on option select +- Fix for controls sometimes not showing while video is playing +- Fixed logic for show home tab on option select # v3.3.3 -* Reverted change to show home tab on option select due to usability regression +- Reverted change to show home tab on option select due to usability regression # v3.3.2 -* Fix for ads running in audio -* Fix for setting poster on source change +- Fix for ads running in audio +- Fix for setting poster on source change ## v3.3.0 -* Now using a custom poster image element to hide the YouTube play button and give more control over when the poster image shows -* Renamed `showPosterOnEnd` to `resetOnEnd` as it makes more sense and now works for all players and does not reload media -* Fix for same domain SVG URLs (raised by Jochem in Slack) -* [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/Window/URL) is polyfill now required -* Added pause className (fixes #941) -* Button height set in CSS (auto) (fixes #928) -* Don't autoplay cloned original media (fixes #936) -* Return to the home menu pane after selecting an option +- Now using a custom poster image element to hide the YouTube play button and give more control over when the poster image shows +- Renamed `showPosterOnEnd` to `resetOnEnd` as it makes more sense and now works for all players and does not reload media +- Fix for same domain SVG URLs (raised by Jochem in Slack) +- [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/Window/URL) is polyfill now required +- Added pause className (fixes #941) +- Button height set in CSS (auto) (fixes #928) +- Don't autoplay cloned original media (fixes #936) +- Return to the home menu pane after selecting an option ## v3.2.4 -* Fix issue wher player never reports as ready if controls is empty array -* Fix issue where screen reader labels were removed from time displays -* Fix issue where custom controls placeholders were not populated -* Custom controls HTML example updated -* Fix for aria-label being set to the initial state on toggle buttons, overriding the inner labels -* Fix for hidden mute button on iOS (not functional for Vimeo due to API limitations) (fixes #656) +- Fix issue wher player never reports as ready if controls is empty array +- Fix issue where screen reader labels were removed from time displays +- Fix issue where custom controls placeholders were not populated +- Custom controls HTML example updated +- Fix for aria-label being set to the initial state on toggle buttons, overriding the inner labels +- Fix for hidden mute button on iOS (not functional for Vimeo due to API limitations) (fixes #656) ## v3.2.3 -* Fix for iOS 9 throwing error for `name` property in fullscreen API (fixes #908) +- Fix for iOS 9 throwing error for `name` property in fullscreen API (fixes #908) ## v3.2.2 -* Fix for regression in 3.2.1 resulting in hidden buffer display (fixes #920) -* Cleaned up incorrect use of `aria-hidden` attribute +- Fix for regression in 3.2.1 resulting in hidden buffer display (fixes #920) +- Cleaned up incorrect use of `aria-hidden` attribute ## v3.2.1 -* Accessibility improvements for the controls (part of #905 fixes) -* Fix for context menu showing on YouTube (thanks Anthony Recenello in Slack) -* Vimeo fix for their API not returning the right duration until playback begins (fixes #891) +- Accessibility improvements for the controls (part of #905 fixes) +- Fix for context menu showing on YouTube (thanks Anthony Recenello in Slack) +- Vimeo fix for their API not returning the right duration until playback begins (fixes #891) ## v3.2.0 -* Fullscreen fixes (thanks @friday) -* Menu fix for if speed not in config -* Menu z-index fix (thanks @danielsarin) -* i18n fix for missing "Normal" string (thanks @danielsarin) -* Safer check for active caption (thanks @Antonio-Laguna) -* Add custom property fallback (thanks @friday) -* Fixed bug for captions with no srclang and labels and improved logic (fixes #875) -* Fix for `playing` false positive (fixes #898) -* Fix for IE issue with navigator.language (thanks @nicolasthy) (fixes #893) -* Fix for Vimeo controls missing on iOS (thanks @verde-io) (fixes #807) -* Fix for double vimeo caption rendering (fixes #877) +- Fullscreen fixes (thanks @friday) +- Menu fix for if speed not in config +- Menu z-index fix (thanks @danielsarin) +- i18n fix for missing "Normal" string (thanks @danielsarin) +- Safer check for active caption (thanks @Antonio-Laguna) +- Add custom property fallback (thanks @friday) +- Fixed bug for captions with no srclang and labels and improved logic (fixes #875) +- Fix for `playing` false positive (fixes #898) +- Fix for IE issue with navigator.language (thanks @nicolasthy) (fixes #893) +- Fix for Vimeo controls missing on iOS (thanks @verde-io) (fixes #807) +- Fix for double vimeo caption rendering (fixes #877) ## v3.1.0 -* Styling fixes +- Styling fixes ## v3.1.0-beta.2 -* YouTube playback speed fixes +- YouTube playback speed fixes ## v3.1.0-beta.1 -* HTML5 quality selection -* Improvements to the YouTube quality selection +- HTML5 quality selection +- Improvements to the YouTube quality selection ## v3.0.11 -* Muted and autoplay fixes -* Small bug fixes from Sentry logs +- Muted and autoplay fixes +- Small bug fixes from Sentry logs ## v3.0.10 -* Docs fix -* Package upgrades +- Docs fix +- Package upgrades ## v3.0.9 -* Demo fix -* Fix Vimeo regression +- Demo fix +- Fix Vimeo regression ## v3.0.8 -* Vimeo hotfix for private videos +- Vimeo hotfix for private videos ## v3.0.7 -* Fix for keyboard shortcut error with fast forward -* Fix for Vimeo trying to set playback rate when not allowed +- Fix for keyboard shortcut error with fast forward +- Fix for Vimeo trying to set playback rate when not allowed ## v3.0.6 -* Improved the logic for the custom handlers preventing default handlers +- Improved the logic for the custom handlers preventing default handlers ## v3.0.5 -* Removed console messages +- Removed console messages ## v3.0.4 -* Fixes for fullscreen not working inside iframes -* Fixes for custom handlers being able to prevent default -* Fixes for controls not hiding/showing correctly on Mobile Safari +- Fixes for fullscreen not working inside iframes +- Fixes for custom handlers being able to prevent default +- Fixes for controls not hiding/showing correctly on Mobile Safari ## v3.0.3 -* Vimeo offset tweak (fixes #826) -* Fix for .stop() method (fixes #819) -* Check for array for speed options (fixes #817) -* Restore as float (fixes #828) -* Fix for Firefox fullscreen oddness (Fixes #821) -* Improve Sprite checking (fixes #827) -* Fix fast-forward control (thanks @saadshahd) -* Fix the options link in the readme (thanks @DanielRuf) +- Vimeo offset tweak (fixes #826) +- Fix for .stop() method (fixes #819) +- Check for array for speed options (fixes #817) +- Restore as float (fixes #828) +- Fix for Firefox fullscreen oddness (Fixes #821) +- Improve Sprite checking (fixes #827) +- Fix fast-forward control (thanks @saadshahd) +- Fix the options link in the readme (thanks @DanielRuf) ## v3.0.2 -* Fix for Safari not firing error events when trying to load blocked scripts +- Fix for Safari not firing error events when trying to load blocked scripts ## v3.0.1 -* Fix for trying to accessing local storage when it's blocked +- Fix for trying to accessing local storage when it's blocked # v3.0.0 @@ -184,37 +194,37 @@ This is a massive release. A _mostly_ complete rewrite in ES6. What started out ### Big changes -* New settings menu complete with funky animations -* Ability to adjust speed of playback -* Ability to toggle caption language (HTML5 and Vimeo only) -* Ability to set YouTube quality (HTML5 will follow) -* Added support for Vimeo captions -* Added Picture-in-Picture support (Safari only) -* Added AirPlay support (again, Safari only) -* Added `playsinline` support for iOS 10+ -* Soundcloud removed until I can work on a plugin framework -* Embedded players are now progressively enhanced - no more empty `
`s! +- New settings menu complete with funky animations +- Ability to adjust speed of playback +- Ability to toggle caption language (HTML5 and Vimeo only) +- Ability to set YouTube quality (HTML5 will follow) +- Added support for Vimeo captions +- Added Picture-in-Picture support (Safari only) +- Added AirPlay support (again, Safari only) +- Added `playsinline` support for iOS 10+ +- Soundcloud removed until I can work on a plugin framework +- Embedded players are now progressively enhanced - no more empty `
`s! ### Other stuff -* Now using SASS exclusively. Sorry, LESS folk it just made sense to maintain one method as SASS is what the cool kids use. It may come back if we work out an automated way to convert the SASS -* Moved to ES6. All the rage these days. You'll need to look at polyfills. The demo uses [polyfill.io](https://polyfill.io) -* Added basic looping support -* Added an aspect ratio option for those that can't leave the 90s and want 4:3 -* `controlshidden` and `controlsshown` events added for when the controls show or hide -* `qualityrequested` and `qualitychange` events for YouTube quality control (HTML5 will follow) -* Volume is now `0` to `1` as per HTML5 spec -* No longer bodging a `` behind the `` to make up for WebKit's lack of lower fill styling -* Captions now render with line breaks as intended -* Captions now render without AJAX using the native events etc -* Added a fallback for getting YouTube video data incase `.getVideoData()` disappears when one of their developers randomly deletes it again -* Setup and building of the UI should be way "snappier" -* Click to toggle inverted time (e.g. 0:01 or -2:59 for a 3 minute video at 1 seconds) - new `toggleInvert` and `invertTime` options -* Added `autopause` option for Vimeo -* Added `muted` option for you guessed it, muted playback -* Restored the `.off()` API method -* `.play()` will now return a promise to prevent that pesky uncaught promise issue in Chrome etc -* Pressing and hold the seek bar no longer freezes all other updates of the UI +- Now using SASS exclusively. Sorry, LESS folk it just made sense to maintain one method as SASS is what the cool kids use. It may come back if we work out an automated way to convert the SASS +- Moved to ES6. All the rage these days. You'll need to look at polyfills. The demo uses [polyfill.io](https://polyfill.io) +- Added basic looping support +- Added an aspect ratio option for those that can't leave the 90s and want 4:3 +- `controlshidden` and `controlsshown` events added for when the controls show or hide +- `qualityrequested` and `qualitychange` events for YouTube quality control (HTML5 will follow) +- Volume is now `0` to `1` as per HTML5 spec +- No longer bodging a `` behind the `` to make up for WebKit's lack of lower fill styling +- Captions now render with line breaks as intended +- Captions now render without AJAX using the native events etc +- Added a fallback for getting YouTube video data incase `.getVideoData()` disappears when one of their developers randomly deletes it again +- Setup and building of the UI should be way "snappier" +- Click to toggle inverted time (e.g. 0:01 or -2:59 for a 3 minute video at 1 seconds) - new `toggleInvert` and `invertTime` options +- Added `autopause` option for Vimeo +- Added `muted` option for you guessed it, muted playback +- Restored the `.off()` API method +- `.play()` will now return a promise to prevent that pesky uncaught promise issue in Chrome etc +- Pressing and hold the seek bar no longer freezes all other updates of the UI ...plus loads of bug fixes. @@ -222,28 +232,28 @@ This is a massive release. A _mostly_ complete rewrite in ES6. What started out You gotta break eggs to make an omelette. Sadly, there's quite a few breaking changes: -* Setup now uses proper constructor, accepts a single selector/element/node and returns a single instance - much simpler than before -* Much of the API is now using getters and setters rather than methods (where it makes sense) to match the HTML5 API - see the docs for more info -* The data attributes for the embeds are now `data-plyr-provider` and `data-plyr-embed-id` to prevent compatibility issues. These can be changed under `config.attributes.embed` if required -* `blankUrl` -> `blankVideo` -* `volume` is now `0` to `1` as per HTML5 spec -* `keyboardShorcuts` (typo) is now just `keyboard` -* `loop` is now `loop.active` in preparation for loop enhancements later -* `html` option for custom controls removed in favour of the `controls` option which now accepts an array (to use built in controls) or a string of HTML for custom controls. -* `classes` -> `classNames` -* `classes.videoWrapper` -> `classNames.video` -* `classes.embedWrapper` -> `classNames.embed` -* `classes.ready` removed -* `classes.setup` removed -* `classes.muted` removed -* `classes.fullscreen.active` removed in favour of the `:fullscreen` selector -* `selectors.html5` removed -* `selectors.embed` removed -* `selectors.buttons.seek` -> `selectors.inputs.seek` -* `selectors.volume.input` -> `selectors.inputs.volume` -* `selectors.volume.display` -> `selectors.display.volume` -* `selectors.currentTime` -> `selectors.display.currentTime` -* `selectors.duration` -> `selectors.display.duration` +- Setup now uses proper constructor, accepts a single selector/element/node and returns a single instance - much simpler than before +- Much of the API is now using getters and setters rather than methods (where it makes sense) to match the HTML5 API - see the docs for more info +- The data attributes for the embeds are now `data-plyr-provider` and `data-plyr-embed-id` to prevent compatibility issues. These can be changed under `config.attributes.embed` if required +- `blankUrl` -> `blankVideo` +- `volume` is now `0` to `1` as per HTML5 spec +- `keyboardShorcuts` (typo) is now just `keyboard` +- `loop` is now `loop.active` in preparation for loop enhancements later +- `html` option for custom controls removed in favour of the `controls` option which now accepts an array (to use built in controls) or a string of HTML for custom controls. +- `classes` -> `classNames` +- `classes.videoWrapper` -> `classNames.video` +- `classes.embedWrapper` -> `classNames.embed` +- `classes.ready` removed +- `classes.setup` removed +- `classes.muted` removed +- `classes.fullscreen.active` removed in favour of the `:fullscreen` selector +- `selectors.html5` removed +- `selectors.embed` removed +- `selectors.buttons.seek` -> `selectors.inputs.seek` +- `selectors.volume.input` -> `selectors.inputs.volume` +- `selectors.volume.display` -> `selectors.display.volume` +- `selectors.currentTime` -> `selectors.display.currentTime` +- `selectors.duration` -> `selectors.display.duration` ### Polyfilling @@ -251,674 +261,674 @@ Because we're using the fancy new ES6 syntax, you will need to polyfill for vint ## v2.0.18 -* Fix for YouTube .getVideoData() issue (fixes #709) +- Fix for YouTube .getVideoData() issue (fixes #709) ## v2.0.17 -* Vimeo controls fix (fixes #697) -* SVG4everybody compatibility fix -* Allow Plyr.setup event listeners to be set up as separate event listeners (https://github.com/sampotts/plyr/pull/703) -* Added title to the layer html template (for custom controls) (https://github.com/sampotts/plyr/pull/649) -* Target is null bug fix (https://github.com/sampotts/plyr/pull/617) -* fix #684 memory leaks issues after destroy (https://github.com/sampotts/plyr/pull/700) +- Vimeo controls fix (fixes #697) +- SVG4everybody compatibility fix +- Allow Plyr.setup event listeners to be set up as separate event listeners (https://github.com/sampotts/plyr/pull/703) +- Added title to the layer html template (for custom controls) (https://github.com/sampotts/plyr/pull/649) +- Target is null bug fix (https://github.com/sampotts/plyr/pull/617) +- fix #684 memory leaks issues after destroy (https://github.com/sampotts/plyr/pull/700) ## v2.0.16 -* Fullscreen bug fix (fixes #664) +- Fullscreen bug fix (fixes #664) ## v2.0.15 -* Demo fix +- Demo fix ## v2.0.14 -* CDN URL updates. Sorry, still working on V3 as hard as I can... +- CDN URL updates. Sorry, still working on V3 as hard as I can... ## v2.0.13 -* Repo moved and Vimeo demo fix +- Repo moved and Vimeo demo fix ## v2.0.12 -* Ability to set custom `blankUrl` for source changes (https://github.com/sampotts/plyr/pull/504) -* Ability to set caption button listener (https://github.com/sampotts/plyr/pull/468) +- Ability to set custom `blankUrl` for source changes (https://github.com/sampotts/plyr/pull/504) +- Ability to set caption button listener (https://github.com/sampotts/plyr/pull/468) ## v2.0.11 -* Fix for `cleanUp` being called twice (thanks to @sebastiancarlsson) -* Fix for YouTube controls on iPad (fixes #391) +- Fix for `cleanUp` being called twice (thanks to @sebastiancarlsson) +- Fix for YouTube controls on iPad (fixes #391) ## v2.0.10 -* Added seek event fixes for Vimeo and YouTube (fixes #409) -* Added support for embed URLs rather than ID only (fixes #345) +- Added seek event fixes for Vimeo and YouTube (fixes #409) +- Added support for embed URLs rather than ID only (fixes #345) ## v2.0.9 -* Temporary patch for the YouTube API issues with `getDuration()` (relates to #374) +- Temporary patch for the YouTube API issues with `getDuration()` (relates to #374) ## v2.0.8 -* Added `isPaused()` API method (thanks to @darrena092) -* Allowed `.on()` API method to be chainable (thanks to @gurupras) (fixes #357) -* Improved the "awful" rendering of captions on small screens in fullscreen mode (fixes #390) -* Fix for Firefox VTT compatibility (thanks to @magourex) -* Fix for Firefox Developer Edition blank video due to `-webkit-mask-image` issue (fixes #392) -* Added Issue and PR templates with the aim of reducing duplicate or duff issues +- Added `isPaused()` API method (thanks to @darrena092) +- Allowed `.on()` API method to be chainable (thanks to @gurupras) (fixes #357) +- Improved the "awful" rendering of captions on small screens in fullscreen mode (fixes #390) +- Fix for Firefox VTT compatibility (thanks to @magourex) +- Fix for Firefox Developer Edition blank video due to `-webkit-mask-image` issue (fixes #392) +- Added Issue and PR templates with the aim of reducing duplicate or duff issues ## v2.0.7 -* Fixed `getCurrentTime()` method (fixes #351) -* Added `getVolume()` , `isMuted()` and `getDuration()` API methods (fixes #346) +- Fixed `getCurrentTime()` method (fixes #351) +- Added `getVolume()` , `isMuted()` and `getDuration()` API methods (fixes #346) ## v2.0.6 -* Fixed merge issue with `Updated define to work with AMD imports #326` PR -* Code formatting +- Fixed merge issue with `Updated define to work with AMD imports #326` PR +- Code formatting ## v2.0.5 -* Fix for Vimeo in IE9 & IE10 -* Fix for HTML5 elements not firing `ready` event +- Fix for Vimeo in IE9 & IE10 +- Fix for HTML5 elements not firing `ready` event ## v2.0.4 -* Fix for Firefox full screen (fixes #343) +- Fix for Firefox full screen (fixes #343) ## v2.0.3 -* Set 'global' keyboard shortcut option to false as default, added `