// ========================================================================== // YouTube plugin // ========================================================================== import controls from './../controls'; import ui from './../ui'; import { dedupe } from './../utils/arrays'; import { createElement, replaceElement, toggleClass } from './../utils/elements'; import { triggerEvent } from './../utils/events'; import fetch from './../utils/fetch'; import is from './../utils/is'; import loadImage from './../utils/loadImage'; import loadScript from './../utils/loadScript'; import { format, generateId } from './../utils/strings'; // Parse YouTube ID from URL function parseId(url) { if (is.empty(url)) { return null; } const regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/; return url.match(regex) ? RegExp.$2 : url; } // Standardise YouTube quality unit function mapQualityUnit(input) { const qualities = { hd2160: 2160, hd1440: 1440, hd1080: 1080, hd720: 720, large: 480, medium: 360, small: 240, tiny: 144, }; const entry = Object.entries(qualities).find(entry => entry.includes(input)); if (entry) { // Get the match corresponding to the input return entry.find(value => value !== input); } return 'default'; } function mapQualityUnits(levels) { if (is.empty(levels)) { return levels; } return dedupe(levels.map(level => mapQualityUnit(level))); } // 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; triggerEvent.call(this, this.media, play ? 'play' : 'pause'); } } const youtube = { setup() { // Add embed class for responsive toggleClass(this.elements.wrapper, this.config.classNames.embed, true); // Set aspect ratio youtube.setAspectRatio.call(this); // Setup API if (is.object(window.YT) && is.function(window.YT.Player)) { youtube.ready.call(this); } else { // Load the API loadScript(this.config.urls.youtube.sdk).catch(error => { this.debug.warn('YouTube API failed to load', error); }); // Setup callback for the API // YouTube has it's own system of course... window.onYouTubeReadyCallbacks = window.onYouTubeReadyCallbacks || []; // Add to queue window.onYouTubeReadyCallbacks.push(() => { youtube.ready.call(this); }); // Set callback to process queue window.onYouTubeIframeAPIReady = () => { window.onYouTubeReadyCallbacks.forEach(callback => { callback(); }); }; } }, // Get the media title getTitle(videoId) { // Try via undocumented API method first // This method disappears now and then though... // https://github.com/sampotts/plyr/issues/709 if (is.function(this.embed.getVideoData)) { const { title } = this.embed.getVideoData(); if (is.empty(title)) { this.config.title = title; ui.setTitle.call(this); return; } } // Or via Google API const key = this.config.keys.google; if (is.string(key) && !is.empty(key)) { const url = format(this.config.urls.youtube.api, videoId, key); fetch(url) .then(result => { if (is.object(result)) { this.config.title = result.items[0].snippet.title; ui.setTitle.call(this); } }) .catch(() => {}); } }, // Set aspect ratio setAspectRatio() { const ratio = this.config.ratio.split(':'); this.elements.wrapper.style.paddingBottom = `${100 / ratio[0] * ratio[1]}%`; }, // API ready ready() { const player = this; // Ignore already setup (race condition) const currentId = player.media.getAttribute('id'); if (!is.empty(currentId) && currentId.startsWith('youtube-')) { return; } // Get the source URL or ID let source = player.media.getAttribute('src'); // Get from