From d9ec1d1b8e251cf30509e88a76132c0e04f8c00d Mon Sep 17 00:00:00 2001
From: Sam Potts
Date: Fri, 12 Jan 2018 19:35:46 +1100
Subject: [PATCH] Progressively enhance
` tag and then call `plyr.setup()`. More info on `setup()` can be found under
@@ -120,7 +122,7 @@ Include the `plyr.js` script before the closing `` tag and then call `ply
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript, you can use the following:
```html
-
+
```
### CSS
@@ -134,13 +136,13 @@ Include the `plyr.css` stylsheet into your `
`
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
```html
-
+
```
### SVG Sprite
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
-reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.0.0-beta.1/plyr.svg`.
+reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.0.0-beta.2/plyr.svg`.
## Advanced
@@ -211,8 +213,7 @@ Passing a [NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList):
const player = new Plyr(document.querySelectorAll('.js-player'));
```
-The NodeList, HTMLElement or string selector can be the target ``, `` or `[data-plyr-provider]` (for embeds) element itself or a container
-element.
+The NodeList, HTMLElement or string selector can be the target ``, ``, or `` wrapper for embeds
The second argument for the constructor is the [#options](options) object:
diff --git a/src/js/console.js b/src/js/console.js
index c5389970..7c5ec1b4 100644
--- a/src/js/console.js
+++ b/src/js/console.js
@@ -5,8 +5,8 @@
const noop = () => {};
export default class Console {
- constructor(player) {
- this.enabled = window.console && player.config.debug;
+ constructor(enabled = false) {
+ this.enabled = window.console && enabled;
if (this.enabled) {
this.log('Debugging enabled');
diff --git a/src/js/media.js b/src/js/media.js
index 4019c1a7..3fbd9774 100644
--- a/src/js/media.js
+++ b/src/js/media.js
@@ -50,7 +50,7 @@ const media = {
}
// Inject the player wrapper
- if (this.isVideo || this.isYouTube || this.isVimeo) {
+ if (this.isVideo) {
// Create the wrapper div
this.elements.wrapper = utils.createElement('div', {
class: this.config.classNames.video,
diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js
index c77ecd20..48d46037 100644
--- a/src/js/plugins/vimeo.js
+++ b/src/js/plugins/vimeo.js
@@ -8,19 +8,12 @@ import ui from './../ui';
const vimeo = {
setup() {
- // Remove old containers
- const containers = utils.getElements.call(this, `[id^="${this.provider}-"]`);
- Array.from(containers).forEach(utils.removeElement);
-
// Add embed class for responsive
utils.toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
// Set intial ratio
vimeo.setAspectRatio.call(this);
- // Set ID
- this.media.setAttribute('id', utils.generateId(this.provider));
-
// Load the API if not already
if (!utils.is.object(window.Vimeo)) {
utils.loadScript(this.config.urls.vimeo.api, () => {
@@ -57,15 +50,21 @@ const vimeo = {
transparent: 0,
gesture: 'media',
};
- const params = utils.buildUrlParameters(options);
- const id = utils.parseVimeoId(player.embedId);
+ const params = utils.buildUrlParams(options);
+ const id = utils.parseVimeoId(player.media.getAttribute('src'));
// Build an iframe
const iframe = utils.createElement('iframe');
const src = `https://player.vimeo.com/video/${id}?${params}`;
iframe.setAttribute('src', src);
iframe.setAttribute('allowfullscreen', '');
- player.media.appendChild(iframe);
+ iframe.setAttribute('allowtransparency', '');
+ iframe.setAttribute('allow', 'autoplay');
+
+ // Inject the package
+ const wrapper = utils.createElement('div');
+ wrapper.appendChild(iframe);
+ player.media = utils.replaceElement(wrapper, player.media);
// Setup instance
// https://github.com/vimeo/player.js
diff --git a/src/js/plugins/youtube.js b/src/js/plugins/youtube.js
index 67f1ca95..bec342a7 100644
--- a/src/js/plugins/youtube.js
+++ b/src/js/plugins/youtube.js
@@ -8,24 +8,15 @@ import ui from './../ui';
const youtube = {
setup() {
- const videoId = utils.parseYouTubeId(this.embedId);
-
- // Remove old containers
- const containers = utils.getElements.call(this, `[id^="${this.provider}-"]`);
- Array.from(containers).forEach(utils.removeElement);
-
// Add embed class for responsive
utils.toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
// Set aspect ratio
youtube.setAspectRatio.call(this);
- // Set ID
- this.media.setAttribute('id', utils.generateId(this.provider));
-
// Setup API
- if (utils.is.object(window.YT)) {
- youtube.ready.call(this, videoId);
+ if (utils.is.object(window.YT) && utils.is.function(window.YT.Player)) {
+ youtube.ready.call(this);
} else {
// Load the API
utils.loadScript(this.config.urls.youtube.api);
@@ -36,7 +27,7 @@ const youtube = {
// Add to queue
window.onYouTubeReadyCallbacks.push(() => {
- youtube.ready.call(this, videoId);
+ youtube.ready.call(this);
});
// Set callback to process queue
@@ -49,7 +40,7 @@ const youtube = {
},
// Get the media title
- getTitle() {
+ getTitle(videoId) {
// Try via undocumented API method first
// This method disappears now and then though...
// https://github.com/sampotts/plyr/issues/709
@@ -65,7 +56,6 @@ const youtube = {
// Or via Google API
const key = this.config.keys.google;
- const videoId = utils.parseYouTubeId(this.embedId);
if (utils.is.string(key) && !utils.is.empty(key)) {
const url = `https://www.googleapis.com/youtube/v3/videos?id=${videoId}&key=${key}&fields=items(snippet(title))&part=snippet`;
@@ -88,12 +78,24 @@ const youtube = {
},
// API ready
- ready(videoId) {
+ ready() {
const player = this;
+ // Ignore already setup (race condition)
+ const currentId = player.media.getAttribute('id');
+ if (!utils.is.empty(currentId) && currentId.startsWith('youtube-')) {
+ return;
+ }
+
+ // Replace the
with a due to YouTube API issues
+ const videoId = utils.parseYouTubeId(player.media.getAttribute('src'));
+ const id = utils.generateId(player.provider);
+ const container = utils.createElement('div', { id });
+ player.media = utils.replaceElement(container, player.media);
+
// Setup instance
// https://developers.google.com/youtube/iframe_api_reference
- player.embed = new window.YT.Player(player.media.id, {
+ player.embed = new window.YT.Player(id, {
videoId,
playerVars: {
autoplay: player.config.autoplay ? 1 : 0, // Autoplay
@@ -110,8 +112,8 @@ const youtube = {
widget_referrer: window && window.location.href,
// Captions are flaky on YouTube
- cc_load_policy: this.captions.active ? 1 : 0,
- cc_lang_pref: this.config.captions.language,
+ cc_load_policy: player.captions.active ? 1 : 0,
+ cc_lang_pref: player.config.captions.language,
},
events: {
onError(event) {
@@ -179,7 +181,7 @@ const youtube = {
const instance = event.target;
// Get the title
- youtube.getTitle.call(player);
+ youtube.getTitle.call(player, videoId);
// Create a faux HTML5 API using the YouTube API
player.media.play = () => {
diff --git a/src/js/plyr.js b/src/js/plyr.js
index 0bb0a89c..dfb07302 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -1,6 +1,6 @@
// ==========================================================================
// Plyr
-// plyr.js v3.0.0-beta.1
+// plyr.js v3.0.0-beta.2
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================
@@ -66,7 +66,7 @@ class Plyr {
} catch (e) {
return {};
}
- })()
+ })(),
);
// Elements cache
@@ -103,7 +103,7 @@ class Plyr {
// Debugging
// TODO: move to globals
- this.debug = new Console(this);
+ this.debug = new Console(this.config.debug);
// Log config options and support
this.debug.log('Config', this.config);
@@ -141,35 +141,61 @@ class Plyr {
// Supported: video, audio, vimeo, youtube
const type = this.media.tagName.toLowerCase();
- // Embed attributes
- const attributes = {
- provider: 'data-plyr-provider',
- id: 'data-plyr-embed-id',
- };
+ // Embed properties
+ let iframe = null;
+ let url = null;
+ let params = null;
// Different setup based on type
switch (type) {
- // TODO: Handle passing an iframe for true progressive enhancement
- // case 'iframe':
case 'div':
- this.type = types.video; // Audio will come later for external providers
- this.provider = this.media.getAttribute(attributes.provider);
- this.embedId = this.media.getAttribute(attributes.id);
+ // Find the frame
+ iframe = this.media.querySelector('iframe');
+ // required
+ if (!utils.is.element(iframe)) {
+ this.debug.error('Setup failed: is missing');
+ return;
+ }
+
+ // Audio will come later for external providers
+ this.type = types.video;
+
+ // Detect provider
+ url = iframe.getAttribute('src');
+ this.provider = utils.getProviderByUrl(url);
+
+ // Get attributes from URL and set config
+ params = utils.getUrlParams(url);
+ if (!utils.is.empty(params)) {
+ const truthy = [
+ '1',
+ 'true',
+ ];
+
+ if (truthy.includes(params.autoplay)) {
+ this.config.autoplay = true;
+ }
+ if (truthy.includes(params.playsinline)) {
+ this.config.inline = true;
+ }
+ if (truthy.includes(params.loop)) {
+ this.config.loop.active = true;
+ }
+ }
+
+ // Unsupported provider
if (utils.is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) {
this.debug.error('Setup failed: Invalid provider');
return;
}
- // Try and get the embed id
- if (utils.is.empty(this.embedId)) {
- this.debug.error('Setup failed: Embed ID or URL missing');
- return;
- }
+ // Rework elements
+ this.elements.container = this.media;
+ this.media = iframe;
- // Clean up
- this.media.removeAttribute(attributes.provider);
- this.media.removeAttribute(attributes.id);
+ // Reset classname
+ this.elements.container.className = '';
break;
@@ -178,22 +204,19 @@ class Plyr {
this.type = type;
this.provider = providers.html5;
+ // Get config from attributes
if (this.media.hasAttribute('crossorigin')) {
this.config.crossorigin = true;
}
-
if (this.media.hasAttribute('autoplay')) {
this.config.autoplay = true;
}
-
if (this.media.hasAttribute('playsinline')) {
this.config.inline = true;
}
-
if (this.media.hasAttribute('muted')) {
this.config.muted = true;
}
-
if (this.media.hasAttribute('loop')) {
this.config.loop.active = true;
}
@@ -221,8 +244,10 @@ class Plyr {
this.media.plyr = this;
// Wrap media
- this.elements.container = utils.createElement('div');
- utils.wrap(this.media, this.elements.container);
+ if (!utils.is.element(this.elements.container)) {
+ this.elements.container = utils.createElement('div');
+ utils.wrap(this.media, this.elements.container);
+ }
// Allow focus to be captured
this.elements.container.setAttribute('tabindex', 0);
@@ -1054,7 +1079,6 @@ class Plyr {
// GC for embed
this.embed = null;
- this.embedId = null;
// If it's a soft destroy, make minimal changes
if (soft) {
@@ -1082,11 +1106,7 @@ class Plyr {
}
} else {
// Replace the container with the original element provided
- const parent = this.elements.container.parentNode;
-
- if (utils.is.element(parent)) {
- parent.replaceChild(this.elements.original, this.elements.container);
- }
+ utils.replaceElement(this.elements.original, this.elements.container);
// Event
utils.dispatchEvent.call(this, this.elements.original, 'destroyed', true);
@@ -1119,7 +1139,9 @@ class Plyr {
window.clearInterval(this.timers.playing);
// Destroy YouTube API
- this.embed.destroy();
+ if (this.embed !== null) {
+ this.embed.destroy();
+ }
// Clean up
done();
@@ -1129,7 +1151,9 @@ class Plyr {
case 'vimeo:video':
// Destroy Vimeo API
// then clean up (wait, to prevent postmessage errors)
- this.embed.unload().then(done);
+ if (this.embed !== null) {
+ this.embed.unload().then(done);
+ }
// Vimeo does not always return
window.setTimeout(done, 200);
diff --git a/src/js/source.js b/src/js/source.js
index 80620bdf..9a6b219c 100644
--- a/src/js/source.js
+++ b/src/js/source.js
@@ -67,8 +67,9 @@ const source = {
case 'youtube:video':
case 'vimeo:video':
- this.media = utils.createElement('div');
- this.embedId = input.sources[0].src;
+ this.media = utils.createElement('div', {
+ src: input.sources[0].src,
+ });
break;
default:
@@ -136,7 +137,7 @@ const source = {
ui.build.call(this);
}
},
- true
+ true,
);
},
};
diff --git a/src/js/utils.js b/src/js/utils.js
index d9dd3df1..930d3e9f 100644
--- a/src/js/utils.js
+++ b/src/js/utils.js
@@ -3,6 +3,7 @@
// ==========================================================================
import support from './support';
+import { providers } from './types';
const utils = {
// Check variable types
@@ -103,7 +104,7 @@ const utils = {
element.callbacks.forEach(cb => cb.call(null, event));
element.callbacks = null;
},
- false
+ false,
);
}
@@ -168,7 +169,7 @@ const utils = {
prefix + id,
JSON.stringify({
content: text,
- })
+ }),
);
}
@@ -274,6 +275,17 @@ const utils = {
}
},
+ // Replace element
+ replaceElement(newChild, oldChild) {
+ if (!utils.is.element(oldChild) || !utils.is.element(oldChild.parentNode) || !utils.is.element(newChild)) {
+ return null;
+ }
+
+ oldChild.parentNode.replaceChild(newChild, oldChild);
+
+ return newChild;
+ },
+
// Set attributes
setAttributes(element, attributes) {
if (!utils.is.element(element) || utils.is.empty(attributes)) {
@@ -491,7 +503,7 @@ const utils = {
event.preventDefault();
}
},
- false
+ false,
);
},
@@ -617,14 +629,37 @@ const utils = {
return utils.extend(target, ...sources);
},
+ // Get the provider for a given URL
+ getProviderByUrl(url) {
+ // YouTube
+ if (/^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.?be)\/.+$/.test(url)) {
+ return providers.youtube;
+ }
+
+ // Vimeo
+ if (/^https?:\/\/player.vimeo.com\/video\/\d{8,}(?=\b|\/)/.test(url)) {
+ return providers.vimeo;
+ }
+
+ return null;
+ },
+
// Parse YouTube ID from URL
parseYouTubeId(url) {
+ if (utils.is.empty(url)) {
+ return null;
+ }
+
const regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
return url.match(regex) ? RegExp.$2 : url;
},
// Parse Vimeo ID from URL
parseVimeoId(url) {
+ if (utils.is.empty(url)) {
+ return null;
+ }
+
if (utils.is.number(Number(url))) {
return url;
}
@@ -633,8 +668,40 @@ const utils = {
return url.match(regex) ? RegExp.$2 : url;
},
+ // Convert a URL to a location object
+ parseUrl(url) {
+ const parser = document.createElement('a');
+ parser.href = url;
+ return parser;
+ },
+
+ // Get URL query parameters
+ getUrlParams(input) {
+ let search = input;
+
+ // Parse URL if needed
+ if (input.startsWith('http://') || input.startsWith('https://')) {
+ ({ search } = this.parseUrl(input));
+ }
+
+ if (this.is.empty(search)) {
+ return null;
+ }
+
+ const hashes = search.slice(search.indexOf('?') + 1).split('&');
+
+ return hashes.reduce((params, hash) => {
+ const [
+ key,
+ val,
+ ] = hash.split('=');
+
+ return Object.assign(params, { [key]: decodeURIComponent(val) });
+ }, {});
+ },
+
// Convert object to URL parameters
- buildUrlParameters(input) {
+ buildUrlParams(input) {
if (!utils.is.object(input)) {
return '';
}
diff --git a/src/sass/components/embed.scss b/src/sass/components/embed.scss
index d9349052..56916f17 100644
--- a/src/sass/components/embed.scss
+++ b/src/sass/components/embed.scss
@@ -11,6 +11,7 @@
height: 0;
padding-bottom: to-percentage($padding);
+ position: relative;
iframe {
border: 0;