diff options
Diffstat (limited to 'src/js')
-rw-r--r-- | src/js/console.js | 4 | ||||
-rw-r--r-- | src/js/media.js | 2 | ||||
-rw-r--r-- | src/js/plugins/vimeo.js | 19 | ||||
-rw-r--r-- | src/js/plugins/youtube.js | 40 | ||||
-rw-r--r-- | src/js/plyr.js | 94 | ||||
-rw-r--r-- | src/js/source.js | 7 | ||||
-rw-r--r-- | src/js/utils.js | 75 |
7 files changed, 167 insertions, 74 deletions
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 <iframe> with a <div> 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'); - if (utils.is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) { - this.debug.error('Setup failed: Invalid provider'); + // <iframe> required + if (!utils.is.element(iframe)) { + this.debug.error('Setup failed: <iframe> is missing'); return; } - // Try and get the embed id - if (utils.is.empty(this.embedId)) { - this.debug.error('Setup failed: Embed ID or URL missing'); + // 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; } - // Clean up - this.media.removeAttribute(attributes.provider); - this.media.removeAttribute(attributes.id); + // Rework elements + this.elements.container = this.media; + this.media = iframe; + + // 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 ''; } |