Converted to 2 space indentation

This commit is contained in:
Sam Potts
2020-04-11 16:23:14 +10:00
parent 8f5b59c18c
commit 502d5977d7
125 changed files with 10693 additions and 10797 deletions
+570 -570
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+327 -327
View File
@@ -17,392 +17,392 @@ import { buildUrlParams } from '../utils/urls';
// Parse Vimeo ID from URL
function parseId(url) {
if (is.empty(url)) {
return null;
}
if (is.empty(url)) {
return null;
}
if (is.number(Number(url))) {
return url;
}
if (is.number(Number(url))) {
return url;
}
const regex = /^.*(vimeo.com\/|video\/)(\d+).*/;
return url.match(regex) ? RegExp.$2 : url;
const regex = /^.*(vimeo.com\/|video\/)(\d+).*/;
return url.match(regex) ? RegExp.$2 : url;
}
// 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');
}
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 vimeo = {
setup() {
const player = this;
setup() {
const player = this;
// Add embed class for responsive
toggleClass(player.elements.wrapper, player.config.classNames.embed, true);
// Add embed class for responsive
toggleClass(player.elements.wrapper, player.config.classNames.embed, true);
// Set speed options from config
player.options.speed = player.config.speed.options;
// Set speed options from config
player.options.speed = player.config.speed.options;
// Set intial ratio
setAspectRatio.call(player);
// Set intial ratio
setAspectRatio.call(player);
// Load the SDK if not already
if (!is.object(window.Vimeo)) {
loadScript(player.config.urls.vimeo.sdk)
.then(() => {
vimeo.ready.call(player);
})
.catch(error => {
player.debug.warn('Vimeo SDK (player.js) failed to load', error);
});
} else {
vimeo.ready.call(player);
}
},
// API Ready
ready() {
const player = this;
const config = player.config.vimeo;
// Get Vimeo params for the iframe
const params = buildUrlParams(
extend(
{},
{
loop: player.config.loop.active,
autoplay: player.autoplay,
muted: player.muted,
gesture: 'media',
playsinline: !this.config.fullscreen.iosNative,
},
config,
),
);
// Get the source URL or ID
let source = player.media.getAttribute('src');
// Get from <div> if needed
if (is.empty(source)) {
source = player.media.getAttribute(player.config.attributes.embed.id);
}
const id = parseId(source);
// Build an iframe
const iframe = createElement('iframe');
const src = format(player.config.urls.vimeo.iframe, id, params);
iframe.setAttribute('src', src);
iframe.setAttribute('allowfullscreen', '');
iframe.setAttribute('allowtransparency', '');
iframe.setAttribute('allow', 'autoplay');
// Set the referrer policy if required
if (!is.empty(config.referrerPolicy)) {
iframe.setAttribute('referrerPolicy', config.referrerPolicy);
}
// Get poster, if already set
const { poster } = player;
// Inject the package
const wrapper = createElement('div', { poster, class: player.config.classNames.embedContainer });
wrapper.appendChild(iframe);
player.media = replaceElement(wrapper, player.media);
// Get poster image
fetch(format(player.config.urls.vimeo.api, id), 'json').then(response => {
if (is.empty(response)) {
return;
}
// Get the URL for thumbnail
const url = new URL(response[0].thumbnail_large);
// Get original image
url.pathname = `${url.pathname.split('_')[0]}.jpg`;
// Set and show poster
ui.setPoster.call(player, url.href).catch(() => {});
// Load the SDK if not already
if (!is.object(window.Vimeo)) {
loadScript(player.config.urls.vimeo.sdk)
.then(() => {
vimeo.ready.call(player);
})
.catch(error => {
player.debug.warn('Vimeo SDK (player.js) failed to load', error);
});
} else {
vimeo.ready.call(player);
}
},
// Setup instance
// https://github.com/vimeo/player.js
player.embed = new window.Vimeo.Player(iframe, {
autopause: player.config.autopause,
muted: player.muted,
});
// API Ready
ready() {
const player = this;
const config = player.config.vimeo;
player.media.paused = true;
player.media.currentTime = 0;
// Get Vimeo params for the iframe
const params = buildUrlParams(
extend(
{},
{
loop: player.config.loop.active,
autoplay: player.autoplay,
muted: player.muted,
gesture: 'media',
playsinline: !this.config.fullscreen.iosNative,
},
config,
),
);
// Disable native text track rendering
if (player.supported.ui) {
player.embed.disableTextTrack();
}
// Get the source URL or ID
let source = player.media.getAttribute('src');
// Create a faux HTML5 API using the Vimeo API
player.media.play = () => {
assurePlaybackState.call(player, true);
return player.embed.play();
};
// Get from <div> if needed
if (is.empty(source)) {
source = player.media.getAttribute(player.config.attributes.embed.id);
}
player.media.pause = () => {
assurePlaybackState.call(player, false);
return player.embed.pause();
};
const id = parseId(source);
// Build an iframe
const iframe = createElement('iframe');
const src = format(player.config.urls.vimeo.iframe, id, params);
iframe.setAttribute('src', src);
iframe.setAttribute('allowfullscreen', '');
iframe.setAttribute('allowtransparency', '');
iframe.setAttribute('allow', 'autoplay');
player.media.stop = () => {
player.pause();
player.currentTime = 0;
};
// Set the referrer policy if required
if (!is.empty(config.referrerPolicy)) {
iframe.setAttribute('referrerPolicy', config.referrerPolicy);
}
// Seeking
let { currentTime } = player.media;
Object.defineProperty(player.media, 'currentTime', {
get() {
return currentTime;
},
set(time) {
// Vimeo will automatically play on seek if the video hasn't been played before
// Get poster, if already set
const { poster } = player;
// Inject the package
const wrapper = createElement('div', { poster, class: player.config.classNames.embedContainer });
wrapper.appendChild(iframe);
player.media = replaceElement(wrapper, player.media);
// Get current paused state and volume etc
const { embed, media, paused, volume } = player;
const restorePause = paused && !embed.hasPlayed;
// Get poster image
fetch(format(player.config.urls.vimeo.api, id), 'json').then(response => {
if (is.empty(response)) {
return;
}
// Set seeking state and trigger event
media.seeking = true;
triggerEvent.call(player, media, 'seeking');
// Get the URL for thumbnail
const url = new URL(response[0].thumbnail_large);
// If paused, mute until seek is complete
Promise.resolve(restorePause && embed.setVolume(0))
// Seek
.then(() => embed.setCurrentTime(time))
// Restore paused
.then(() => restorePause && embed.pause())
// Restore volume
.then(() => restorePause && embed.setVolume(volume))
.catch(() => {
// Do nothing
});
},
});
// Get original image
url.pathname = `${url.pathname.split('_')[0]}.jpg`;
// Playback speed
let speed = player.config.speed.selected;
Object.defineProperty(player.media, 'playbackRate', {
get() {
return speed;
},
set(input) {
player.embed
.setPlaybackRate(input)
.then(() => {
speed = input;
triggerEvent.call(player, player.media, 'ratechange');
})
.catch(() => {
// Cannot set Playback Rate, Video is probably not on Pro account
player.options.speed = [1];
});
},
});
// Set and show poster
ui.setPoster.call(player, url.href).catch(() => {});
});
// Volume
let { volume } = player.config;
Object.defineProperty(player.media, 'volume', {
get() {
return volume;
},
set(input) {
player.embed.setVolume(input).then(() => {
volume = input;
triggerEvent.call(player, player.media, 'volumechange');
});
},
});
// Setup instance
// https://github.com/vimeo/player.js
player.embed = new window.Vimeo.Player(iframe, {
autopause: player.config.autopause,
muted: player.muted,
});
// Muted
let { muted } = player.config;
Object.defineProperty(player.media, 'muted', {
get() {
return muted;
},
set(input) {
const toggle = is.boolean(input) ? input : false;
player.media.paused = true;
player.media.currentTime = 0;
player.embed.setVolume(toggle ? 0 : player.config.volume).then(() => {
muted = toggle;
triggerEvent.call(player, player.media, 'volumechange');
});
},
});
// Disable native text track rendering
if (player.supported.ui) {
player.embed.disableTextTrack();
}
// Loop
let { loop } = player.config;
Object.defineProperty(player.media, 'loop', {
get() {
return loop;
},
set(input) {
const toggle = is.boolean(input) ? input : player.config.loop.active;
// Create a faux HTML5 API using the Vimeo API
player.media.play = () => {
assurePlaybackState.call(player, true);
return player.embed.play();
};
player.embed.setLoop(toggle).then(() => {
loop = toggle;
});
},
});
player.media.pause = () => {
assurePlaybackState.call(player, false);
return player.embed.pause();
};
// Source
let currentSrc;
player.media.stop = () => {
player.pause();
player.currentTime = 0;
};
// Seeking
let { currentTime } = player.media;
Object.defineProperty(player.media, 'currentTime', {
get() {
return currentTime;
},
set(time) {
// Vimeo will automatically play on seek if the video hasn't been played before
// 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;
triggerEvent.call(player, media, 'seeking');
// If paused, mute until seek is complete
Promise.resolve(restorePause && embed.setVolume(0))
// Seek
.then(() => embed.setCurrentTime(time))
// Restore paused
.then(() => restorePause && embed.pause())
// Restore volume
.then(() => restorePause && embed.setVolume(volume))
.catch(() => {
// Do nothing
});
},
});
// Playback speed
let speed = player.config.speed.selected;
Object.defineProperty(player.media, 'playbackRate', {
get() {
return speed;
},
set(input) {
player.embed
.getVideoUrl()
.then(value => {
currentSrc = value;
controls.setDownloadUrl.call(player);
})
.catch(error => {
this.debug.warn(error);
});
.setPlaybackRate(input)
.then(() => {
speed = input;
triggerEvent.call(player, player.media, 'ratechange');
})
.catch(() => {
// Cannot set Playback Rate, Video is probably not on Pro account
player.options.speed = [1];
});
},
});
Object.defineProperty(player.media, 'currentSrc', {
get() {
return currentSrc;
},
// Volume
let { volume } = player.config;
Object.defineProperty(player.media, 'volume', {
get() {
return volume;
},
set(input) {
player.embed.setVolume(input).then(() => {
volume = input;
triggerEvent.call(player, player.media, 'volumechange');
});
},
});
// Ended
Object.defineProperty(player.media, 'ended', {
get() {
return player.currentTime === player.duration;
},
// Muted
let { muted } = player.config;
Object.defineProperty(player.media, 'muted', {
get() {
return muted;
},
set(input) {
const toggle = is.boolean(input) ? input : false;
player.embed.setVolume(toggle ? 0 : player.config.volume).then(() => {
muted = toggle;
triggerEvent.call(player, player.media, 'volumechange');
});
},
});
// Set aspect ratio based on video size
Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {
const [width, height] = dimensions;
player.embed.ratio = [width, height];
setAspectRatio.call(this);
// Loop
let { loop } = player.config;
Object.defineProperty(player.media, 'loop', {
get() {
return loop;
},
set(input) {
const toggle = is.boolean(input) ? input : player.config.loop.active;
player.embed.setLoop(toggle).then(() => {
loop = toggle;
});
},
});
// Set autopause
player.embed.setAutopause(player.config.autopause).then(state => {
player.config.autopause = state;
});
// Source
let currentSrc;
player.embed
.getVideoUrl()
.then(value => {
currentSrc = value;
controls.setDownloadUrl.call(player);
})
.catch(error => {
this.debug.warn(error);
});
// Get title
player.embed.getVideoTitle().then(title => {
player.config.title = title;
ui.setTitle.call(this);
});
Object.defineProperty(player.media, 'currentSrc', {
get() {
return currentSrc;
},
});
// Get current time
player.embed.getCurrentTime().then(value => {
currentTime = value;
triggerEvent.call(player, player.media, 'timeupdate');
});
// Ended
Object.defineProperty(player.media, 'ended', {
get() {
return player.currentTime === player.duration;
},
});
// Get duration
player.embed.getDuration().then(value => {
player.media.duration = value;
triggerEvent.call(player, player.media, 'durationchange');
});
// Set aspect ratio based on video size
Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {
const [width, height] = dimensions;
player.embed.ratio = [width, height];
setAspectRatio.call(this);
});
// Get captions
player.embed.getTextTracks().then(tracks => {
player.media.textTracks = tracks;
captions.setup.call(player);
});
// Set autopause
player.embed.setAutopause(player.config.autopause).then(state => {
player.config.autopause = state;
});
player.embed.on('cuechange', ({ cues = [] }) => {
const strippedCues = cues.map(cue => stripHTML(cue.text));
captions.updateCues.call(player, strippedCues);
});
// Get title
player.embed.getVideoTitle().then(title => {
player.config.title = title;
ui.setTitle.call(this);
});
player.embed.on('loaded', () => {
// Assure state and events are updated on autoplay
player.embed.getPaused().then(paused => {
assurePlaybackState.call(player, !paused);
if (!paused) {
triggerEvent.call(player, player.media, 'playing');
}
});
// Get current time
player.embed.getCurrentTime().then(value => {
currentTime = value;
triggerEvent.call(player, player.media, 'timeupdate');
});
if (is.element(player.embed.element) && player.supported.ui) {
const frame = player.embed.element;
// Get duration
player.embed.getDuration().then(value => {
player.media.duration = value;
triggerEvent.call(player, player.media, 'durationchange');
});
// Fix keyboard focus issues
// https://github.com/sampotts/plyr/issues/317
frame.setAttribute('tabindex', -1);
}
});
// Get captions
player.embed.getTextTracks().then(tracks => {
player.media.textTracks = tracks;
captions.setup.call(player);
});
player.embed.on('bufferstart', () => {
triggerEvent.call(player, player.media, 'waiting');
});
player.embed.on('cuechange', ({ cues = [] }) => {
const strippedCues = cues.map(cue => stripHTML(cue.text));
captions.updateCues.call(player, strippedCues);
});
player.embed.on('bufferend', () => {
triggerEvent.call(player, player.media, 'playing');
});
player.embed.on('loaded', () => {
// Assure state and events are updated on autoplay
player.embed.getPaused().then(paused => {
assurePlaybackState.call(player, !paused);
if (!paused) {
triggerEvent.call(player, player.media, 'playing');
}
});
player.embed.on('play', () => {
assurePlaybackState.call(player, true);
triggerEvent.call(player, player.media, 'playing');
});
if (is.element(player.embed.element) && player.supported.ui) {
const frame = player.embed.element;
player.embed.on('pause', () => {
assurePlaybackState.call(player, false);
});
// Fix keyboard focus issues
// https://github.com/sampotts/plyr/issues/317
frame.setAttribute('tabindex', -1);
}
});
player.embed.on('timeupdate', data => {
player.media.seeking = false;
currentTime = data.seconds;
triggerEvent.call(player, player.media, 'timeupdate');
});
player.embed.on('bufferstart', () => {
triggerEvent.call(player, player.media, 'waiting');
});
player.embed.on('progress', data => {
player.media.buffered = data.percent;
triggerEvent.call(player, player.media, 'progress');
player.embed.on('bufferend', () => {
triggerEvent.call(player, player.media, 'playing');
});
// Check all loaded
if (parseInt(data.percent, 10) === 1) {
triggerEvent.call(player, player.media, 'canplaythrough');
}
player.embed.on('play', () => {
assurePlaybackState.call(player, true);
triggerEvent.call(player, player.media, 'playing');
});
// Get duration as if we do it before load, it gives an incorrect value
// https://github.com/sampotts/plyr/issues/891
player.embed.getDuration().then(value => {
if (value !== player.media.duration) {
player.media.duration = value;
triggerEvent.call(player, player.media, 'durationchange');
}
});
});
player.embed.on('pause', () => {
assurePlaybackState.call(player, false);
});
player.embed.on('seeked', () => {
player.media.seeking = false;
triggerEvent.call(player, player.media, 'seeked');
});
player.embed.on('timeupdate', data => {
player.media.seeking = false;
currentTime = data.seconds;
triggerEvent.call(player, player.media, 'timeupdate');
});
player.embed.on('ended', () => {
player.media.paused = true;
triggerEvent.call(player, player.media, 'ended');
});
player.embed.on('progress', data => {
player.media.buffered = data.percent;
triggerEvent.call(player, player.media, 'progress');
player.embed.on('error', detail => {
player.media.error = detail;
triggerEvent.call(player, player.media, 'error');
});
// Check all loaded
if (parseInt(data.percent, 10) === 1) {
triggerEvent.call(player, player.media, 'canplaythrough');
}
// Rebuild UI
setTimeout(() => ui.build.call(player), 0);
},
// Get duration as if we do it before load, it gives an incorrect value
// https://github.com/sampotts/plyr/issues/891
player.embed.getDuration().then(value => {
if (value !== player.media.duration) {
player.media.duration = value;
triggerEvent.call(player, player.media, 'durationchange');
}
});
});
player.embed.on('seeked', () => {
player.media.seeking = false;
triggerEvent.call(player, player.media, 'seeked');
});
player.embed.on('ended', () => {
player.media.paused = true;
triggerEvent.call(player, player.media, 'ended');
});
player.embed.on('error', detail => {
player.media.error = detail;
triggerEvent.call(player, player.media, 'error');
});
// Rebuild UI
setTimeout(() => ui.build.call(player), 0);
},
};
export default vimeo;
+390 -390
View File
@@ -15,426 +15,426 @@ import { setAspectRatio } from '../utils/style';
// Parse YouTube ID from URL
function parseId(url) {
if (is.empty(url)) {
return null;
}
if (is.empty(url)) {
return null;
}
const regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
return url.match(regex) ? RegExp.$2 : url;
const regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
return url.match(regex) ? RegExp.$2 : url;
}
// 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');
}
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');
}
}
function getHost(config) {
if (config.noCookie) {
return 'https://www.youtube-nocookie.com';
}
if (config.noCookie) {
return 'https://www.youtube-nocookie.com';
}
if (window.location.protocol === 'http:') {
return 'http://www.youtube.com';
}
if (window.location.protocol === 'http:') {
return 'http://www.youtube.com';
}
// Use YouTube's default
return undefined;
// Use YouTube's default
return undefined;
}
const youtube = {
setup() {
// Add embed class for responsive
toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
setup() {
// Add embed class for responsive
toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
// Setup API
if (is.object(window.YT) && is.function(window.YT.Player)) {
youtube.ready.call(this);
} else {
// Reference current global callback
const callback = window.onYouTubeIframeAPIReady;
// Setup API
if (is.object(window.YT) && is.function(window.YT.Player)) {
youtube.ready.call(this);
} else {
// Reference current global callback
const callback = window.onYouTubeIframeAPIReady;
// Set callback to process queue
window.onYouTubeIframeAPIReady = () => {
// Call global callback if set
if (is.function(callback)) {
callback();
}
youtube.ready.call(this);
};
// Load the SDK
loadScript(this.config.urls.youtube.sdk).catch(error => {
this.debug.warn('YouTube API failed to load', error);
});
// Set callback to process queue
window.onYouTubeIframeAPIReady = () => {
// Call global callback if set
if (is.function(callback)) {
callback();
}
},
// Get the media title
getTitle(videoId) {
const url = format(this.config.urls.youtube.api, videoId);
youtube.ready.call(this);
};
fetch(url)
.then(data => {
if (is.object(data)) {
const { title, height, width } = data;
// Load the SDK
loadScript(this.config.urls.youtube.sdk).catch(error => {
this.debug.warn('YouTube API failed to load', error);
});
}
},
// Set title
this.config.title = title;
ui.setTitle.call(this);
// Get the media title
getTitle(videoId) {
const url = format(this.config.urls.youtube.api, videoId);
// Set aspect ratio
this.embed.ratio = [width, height];
}
fetch(url)
.then(data => {
if (is.object(data)) {
const { title, height, width } = data;
setAspectRatio.call(this);
})
.catch(() => {
// Set aspect ratio
setAspectRatio.call(this);
});
},
// Set title
this.config.title = title;
ui.setTitle.call(this);
// API ready
ready() {
const player = this;
// Ignore already setup (race condition)
const currentId = player.media && player.media.getAttribute('id');
if (!is.empty(currentId) && currentId.startsWith('youtube-')) {
// Set aspect ratio
this.embed.ratio = [width, height];
}
setAspectRatio.call(this);
})
.catch(() => {
// Set aspect ratio
setAspectRatio.call(this);
});
},
// API ready
ready() {
const player = this;
// Ignore already setup (race condition)
const currentId = player.media && 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 <div> if needed
if (is.empty(source)) {
source = player.media.getAttribute(this.config.attributes.embed.id);
}
// Replace the <iframe> with a <div> due to YouTube API issues
const videoId = parseId(source);
const id = generateId(player.provider);
// Get poster, if already set
const { poster } = player;
// Replace media element
const container = createElement('div', { id, poster });
player.media = replaceElement(container, player.media);
// Id to poster wrapper
const posterSrc = s => `https://i.ytimg.com/vi/${videoId}/${s}default.jpg`;
// Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded
.catch(() => loadImage(posterSrc('sd'), 121)) // 480p padded 4:3
.catch(() => loadImage(posterSrc('hq'))) // 360p padded 4:3. Always exists
.then(image => ui.setPoster.call(player, image.src))
.then(src => {
// If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
if (!src.includes('maxres')) {
player.elements.poster.style.backgroundSize = 'cover';
}
})
.catch(() => {});
const config = player.config.youtube;
// Setup instance
// https://developers.google.com/youtube/iframe_api_reference
player.embed = new window.YT.Player(id, {
videoId,
host: getHost(config),
playerVars: extend(
{},
{
autoplay: player.config.autoplay ? 1 : 0, // Autoplay
hl: player.config.hl, // iframe interface language
controls: player.supported.ui ? 0 : 1, // Only show controls if not fully supported
disablekb: 1, // Disable keyboard as we handle it
playsinline: !player.config.fullscreen.iosNative ? 1 : 0, // Allow iOS inline playback
// Captions are flaky on YouTube
cc_load_policy: player.captions.active ? 1 : 0,
cc_lang_pref: player.config.captions.language,
// Tracking for stats
widget_referrer: window ? window.location.href : null,
},
config,
),
events: {
onError(event) {
// YouTube may fire onError twice, so only handle it once
if (!player.media.error) {
const code = event.data;
// Messages copied from https://developers.google.com/youtube/iframe_api_reference#onError
const message =
{
2: 'The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.',
5: 'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.',
100: 'The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.',
101: 'The owner of the requested video does not allow it to be played in embedded players.',
150: 'The owner of the requested video does not allow it to be played in embedded players.',
}[code] || 'An unknown error occured';
player.media.error = { code, message };
triggerEvent.call(player, player.media, 'error');
}
},
onPlaybackRateChange(event) {
// Get the instance
const instance = event.target;
// Get current speed
player.media.playbackRate = instance.getPlaybackRate();
triggerEvent.call(player, player.media, 'ratechange');
},
onReady(event) {
// Bail if onReady has already been called. See issue #1108
if (is.function(player.media.play)) {
return;
}
}
// Get the instance
const instance = event.target;
// Get the source URL or ID
let source = player.media.getAttribute('src');
// Get the title
youtube.getTitle.call(player, videoId);
// Get from <div> if needed
if (is.empty(source)) {
source = player.media.getAttribute(this.config.attributes.embed.id);
}
// Create a faux HTML5 API using the YouTube API
player.media.play = () => {
assurePlaybackState.call(player, true);
instance.playVideo();
};
// Replace the <iframe> with a <div> due to YouTube API issues
const videoId = parseId(source);
const id = generateId(player.provider);
// Get poster, if already set
const { poster } = player;
// Replace media element
const container = createElement('div', { id, poster });
player.media = replaceElement(container, player.media);
player.media.pause = () => {
assurePlaybackState.call(player, false);
instance.pauseVideo();
};
// Id to poster wrapper
const posterSrc = s => `https://i.ytimg.com/vi/${videoId}/${s}default.jpg`;
player.media.stop = () => {
instance.stopVideo();
};
// Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded
.catch(() => loadImage(posterSrc('sd'), 121)) // 480p padded 4:3
.catch(() => loadImage(posterSrc('hq'))) // 360p padded 4:3. Always exists
.then(image => ui.setPoster.call(player, image.src))
.then(src => {
// If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
if (!src.includes('maxres')) {
player.elements.poster.style.backgroundSize = 'cover';
}
})
.catch(() => {});
player.media.duration = instance.getDuration();
player.media.paused = true;
const config = player.config.youtube;
// Setup instance
// https://developers.google.com/youtube/iframe_api_reference
player.embed = new window.YT.Player(id, {
videoId,
host: getHost(config),
playerVars: extend(
{},
{
autoplay: player.config.autoplay ? 1 : 0, // Autoplay
hl: player.config.hl, // iframe interface language
controls: player.supported.ui ? 0 : 1, // Only show controls if not fully supported
disablekb: 1, // Disable keyboard as we handle it
playsinline: !player.config.fullscreen.iosNative ? 1 : 0, // Allow iOS inline playback
// Captions are flaky on YouTube
cc_load_policy: player.captions.active ? 1 : 0,
cc_lang_pref: player.config.captions.language,
// Tracking for stats
widget_referrer: window ? window.location.href : null,
},
config,
),
events: {
onError(event) {
// YouTube may fire onError twice, so only handle it once
if (!player.media.error) {
const code = event.data;
// Messages copied from https://developers.google.com/youtube/iframe_api_reference#onError
const message =
{
2: 'The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.',
5: 'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.',
100: 'The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.',
101: 'The owner of the requested video does not allow it to be played in embedded players.',
150: 'The owner of the requested video does not allow it to be played in embedded players.',
}[code] || 'An unknown error occured';
player.media.error = { code, message };
triggerEvent.call(player, player.media, 'error');
}
},
onPlaybackRateChange(event) {
// Get the instance
const instance = event.target;
// Get current speed
player.media.playbackRate = instance.getPlaybackRate();
triggerEvent.call(player, player.media, 'ratechange');
},
onReady(event) {
// Bail if onReady has already been called. See issue #1108
if (is.function(player.media.play)) {
return;
}
// Get the instance
const instance = event.target;
// Get the title
youtube.getTitle.call(player, videoId);
// Create a faux HTML5 API using the YouTube API
player.media.play = () => {
assurePlaybackState.call(player, true);
instance.playVideo();
};
player.media.pause = () => {
assurePlaybackState.call(player, false);
instance.pauseVideo();
};
player.media.stop = () => {
instance.stopVideo();
};
player.media.duration = instance.getDuration();
player.media.paused = true;
// Seeking
player.media.currentTime = 0;
Object.defineProperty(player.media, 'currentTime', {
get() {
return Number(instance.getCurrentTime());
},
set(time) {
// If paused and never played, mute audio preventively (YouTube starts playing on seek if the video hasn't been played yet).
if (player.paused && !player.embed.hasPlayed) {
player.embed.mute();
}
// Set seeking state and trigger event
player.media.seeking = true;
triggerEvent.call(player, player.media, 'seeking');
// Seek after events sent
instance.seekTo(time);
},
});
// Playback speed
Object.defineProperty(player.media, 'playbackRate', {
get() {
return instance.getPlaybackRate();
},
set(input) {
instance.setPlaybackRate(input);
},
});
// Volume
let { volume } = player.config;
Object.defineProperty(player.media, 'volume', {
get() {
return volume;
},
set(input) {
volume = input;
instance.setVolume(volume * 100);
triggerEvent.call(player, player.media, 'volumechange');
},
});
// Muted
let { muted } = player.config;
Object.defineProperty(player.media, 'muted', {
get() {
return muted;
},
set(input) {
const toggle = is.boolean(input) ? input : muted;
muted = toggle;
instance[toggle ? 'mute' : 'unMute']();
triggerEvent.call(player, player.media, 'volumechange');
},
});
// Source
Object.defineProperty(player.media, 'currentSrc', {
get() {
return instance.getVideoUrl();
},
});
// Ended
Object.defineProperty(player.media, 'ended', {
get() {
return player.currentTime === player.duration;
},
});
// Get available speeds
const speeds = instance.getAvailablePlaybackRates();
// Filter based on config
player.options.speed = speeds.filter(s => player.config.speed.options.includes(s));
// Set the tabindex to avoid focus entering iframe
if (player.supported.ui) {
player.media.setAttribute('tabindex', -1);
}
triggerEvent.call(player, player.media, 'timeupdate');
triggerEvent.call(player, player.media, 'durationchange');
// Reset timer
clearInterval(player.timers.buffering);
// Setup buffering
player.timers.buffering = setInterval(() => {
// Get loaded % from YouTube
player.media.buffered = instance.getVideoLoadedFraction();
// Trigger progress only when we actually buffer something
if (player.media.lastBuffered === null || player.media.lastBuffered < player.media.buffered) {
triggerEvent.call(player, player.media, 'progress');
}
// Set last buffer point
player.media.lastBuffered = player.media.buffered;
// Bail if we're at 100%
if (player.media.buffered === 1) {
clearInterval(player.timers.buffering);
// Trigger event
triggerEvent.call(player, player.media, 'canplaythrough');
}
}, 200);
// Rebuild UI
setTimeout(() => ui.build.call(player), 50);
},
onStateChange(event) {
// Get the instance
const instance = event.target;
// Reset timer
clearInterval(player.timers.playing);
const seeked = player.media.seeking && [1, 2].includes(event.data);
if (seeked) {
// Unset seeking and fire seeked event
player.media.seeking = false;
triggerEvent.call(player, player.media, 'seeked');
}
// Handle events
// -1 Unstarted
// 0 Ended
// 1 Playing
// 2 Paused
// 3 Buffering
// 5 Video cued
switch (event.data) {
case -1:
// Update scrubber
triggerEvent.call(player, player.media, 'timeupdate');
// Get loaded % from YouTube
player.media.buffered = instance.getVideoLoadedFraction();
triggerEvent.call(player, player.media, 'progress');
break;
case 0:
assurePlaybackState.call(player, false);
// YouTube doesn't support loop for a single video, so mimick it.
if (player.media.loop) {
// YouTube needs a call to `stopVideo` before playing again
instance.stopVideo();
instance.playVideo();
} else {
triggerEvent.call(player, player.media, 'ended');
}
break;
case 1:
// Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
if (!player.config.autoplay && player.media.paused && !player.embed.hasPlayed) {
player.media.pause();
} else {
assurePlaybackState.call(player, true);
triggerEvent.call(player, player.media, 'playing');
// Poll to get playback progress
player.timers.playing = setInterval(() => {
triggerEvent.call(player, player.media, 'timeupdate');
}, 50);
// Check duration again due to YouTube bug
// https://github.com/sampotts/plyr/issues/374
// https://code.google.com/p/gdata-issues/issues/detail?id=8690
if (player.media.duration !== instance.getDuration()) {
player.media.duration = instance.getDuration();
triggerEvent.call(player, player.media, 'durationchange');
}
}
break;
case 2:
// Restore audio (YouTube starts playing on seek if the video hasn't been played yet)
if (!player.muted) {
player.embed.unMute();
}
assurePlaybackState.call(player, false);
break;
case 3:
// Trigger waiting event to add loading classes to container as the video buffers.
triggerEvent.call(player, player.media, 'waiting');
break;
default:
break;
}
triggerEvent.call(player, player.elements.container, 'statechange', false, {
code: event.data,
});
},
// Seeking
player.media.currentTime = 0;
Object.defineProperty(player.media, 'currentTime', {
get() {
return Number(instance.getCurrentTime());
},
});
},
set(time) {
// If paused and never played, mute audio preventively (YouTube starts playing on seek if the video hasn't been played yet).
if (player.paused && !player.embed.hasPlayed) {
player.embed.mute();
}
// Set seeking state and trigger event
player.media.seeking = true;
triggerEvent.call(player, player.media, 'seeking');
// Seek after events sent
instance.seekTo(time);
},
});
// Playback speed
Object.defineProperty(player.media, 'playbackRate', {
get() {
return instance.getPlaybackRate();
},
set(input) {
instance.setPlaybackRate(input);
},
});
// Volume
let { volume } = player.config;
Object.defineProperty(player.media, 'volume', {
get() {
return volume;
},
set(input) {
volume = input;
instance.setVolume(volume * 100);
triggerEvent.call(player, player.media, 'volumechange');
},
});
// Muted
let { muted } = player.config;
Object.defineProperty(player.media, 'muted', {
get() {
return muted;
},
set(input) {
const toggle = is.boolean(input) ? input : muted;
muted = toggle;
instance[toggle ? 'mute' : 'unMute']();
triggerEvent.call(player, player.media, 'volumechange');
},
});
// Source
Object.defineProperty(player.media, 'currentSrc', {
get() {
return instance.getVideoUrl();
},
});
// Ended
Object.defineProperty(player.media, 'ended', {
get() {
return player.currentTime === player.duration;
},
});
// Get available speeds
const speeds = instance.getAvailablePlaybackRates();
// Filter based on config
player.options.speed = speeds.filter(s => player.config.speed.options.includes(s));
// Set the tabindex to avoid focus entering iframe
if (player.supported.ui) {
player.media.setAttribute('tabindex', -1);
}
triggerEvent.call(player, player.media, 'timeupdate');
triggerEvent.call(player, player.media, 'durationchange');
// Reset timer
clearInterval(player.timers.buffering);
// Setup buffering
player.timers.buffering = setInterval(() => {
// Get loaded % from YouTube
player.media.buffered = instance.getVideoLoadedFraction();
// Trigger progress only when we actually buffer something
if (player.media.lastBuffered === null || player.media.lastBuffered < player.media.buffered) {
triggerEvent.call(player, player.media, 'progress');
}
// Set last buffer point
player.media.lastBuffered = player.media.buffered;
// Bail if we're at 100%
if (player.media.buffered === 1) {
clearInterval(player.timers.buffering);
// Trigger event
triggerEvent.call(player, player.media, 'canplaythrough');
}
}, 200);
// Rebuild UI
setTimeout(() => ui.build.call(player), 50);
},
onStateChange(event) {
// Get the instance
const instance = event.target;
// Reset timer
clearInterval(player.timers.playing);
const seeked = player.media.seeking && [1, 2].includes(event.data);
if (seeked) {
// Unset seeking and fire seeked event
player.media.seeking = false;
triggerEvent.call(player, player.media, 'seeked');
}
// Handle events
// -1 Unstarted
// 0 Ended
// 1 Playing
// 2 Paused
// 3 Buffering
// 5 Video cued
switch (event.data) {
case -1:
// Update scrubber
triggerEvent.call(player, player.media, 'timeupdate');
// Get loaded % from YouTube
player.media.buffered = instance.getVideoLoadedFraction();
triggerEvent.call(player, player.media, 'progress');
break;
case 0:
assurePlaybackState.call(player, false);
// YouTube doesn't support loop for a single video, so mimick it.
if (player.media.loop) {
// YouTube needs a call to `stopVideo` before playing again
instance.stopVideo();
instance.playVideo();
} else {
triggerEvent.call(player, player.media, 'ended');
}
break;
case 1:
// Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
if (!player.config.autoplay && player.media.paused && !player.embed.hasPlayed) {
player.media.pause();
} else {
assurePlaybackState.call(player, true);
triggerEvent.call(player, player.media, 'playing');
// Poll to get playback progress
player.timers.playing = setInterval(() => {
triggerEvent.call(player, player.media, 'timeupdate');
}, 50);
// Check duration again due to YouTube bug
// https://github.com/sampotts/plyr/issues/374
// https://code.google.com/p/gdata-issues/issues/detail?id=8690
if (player.media.duration !== instance.getDuration()) {
player.media.duration = instance.getDuration();
triggerEvent.call(player, player.media, 'durationchange');
}
}
break;
case 2:
// Restore audio (YouTube starts playing on seek if the video hasn't been played yet)
if (!player.muted) {
player.embed.unMute();
}
assurePlaybackState.call(player, false);
break;
case 3:
// Trigger waiting event to add loading classes to container as the video buffers.
triggerEvent.call(player, player.media, 'waiting');
break;
default:
break;
}
triggerEvent.call(player, player.elements.container, 'statechange', false, {
code: event.data,
});
},
},
});
},
};
export default youtube;