aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/js/controls.js16
-rw-r--r--src/js/defaults.js19
-rw-r--r--src/js/listeners.js357
-rw-r--r--src/js/media.js5
-rw-r--r--src/js/plugins/vimeo.js75
-rw-r--r--src/js/plugins/youtube.js15
-rw-r--r--src/js/plyr.js67
-rw-r--r--src/js/ui.js6
-rw-r--r--src/js/utils.js137
-rw-r--r--src/less/components/embed.less10
10 files changed, 403 insertions, 304 deletions
diff --git a/src/js/controls.js b/src/js/controls.js
index d40165e1..ac7ba2b6 100644
--- a/src/js/controls.js
+++ b/src/js/controls.js
@@ -6,11 +6,14 @@ import support from './support';
import utils from './utils';
import ui from './ui';
+// Sniff out the browser
+const browser = utils.getBrowser();
+
const controls = {
// Webkit polyfill for lower fill range
updateRangeFill(target) {
// WebKit only
- if (!this.browser.isWebkit) {
+ if (!browser.isWebkit) {
return;
}
@@ -49,7 +52,7 @@ const controls = {
getIconUrl() {
return {
url: this.config.iconUrl,
- absolute: this.config.iconUrl.indexOf('http') === 0 || (this.browser.isIE && !window.svg4everybody),
+ absolute: this.config.iconUrl.indexOf('http') === 0 || (browser.isIE && !window.svg4everybody),
};
},
@@ -1139,14 +1142,11 @@ const controls = {
inject() {
// Sprite
if (this.config.loadSprite) {
- const iconUrl = controls.getIconUrl.call(this);
+ const icon = controls.getIconUrl.call(this);
// Only load external sprite using AJAX
- if (iconUrl.absolute) {
- this.log(`AJAX loading absolute SVG sprite ${this.browser.isIE ? '(due to IE)' : ''}`);
- utils.loadSprite(iconUrl.url, 'sprite-plyr');
- } else {
- this.log('Sprite will be used as external resource directly');
+ if (icon.absolute) {
+ utils.loadSprite(icon.url, 'sprite-plyr');
}
}
diff --git a/src/js/defaults.js b/src/js/defaults.js
index 837b981b..ee863066 100644
--- a/src/js/defaults.js
+++ b/src/js/defaults.js
@@ -19,9 +19,18 @@ const defaults = {
volume: 1,
muted: false,
+ // Pass a custom duration
+ duration: null,
+
// Display the media duration
displayDuration: true,
+ // Aspect ratio (for embeds)
+ ratio: '16:9',
+
+ // Looping
+ loop: false,
+
// Click video to play
clickToPlay: true,
@@ -42,22 +51,12 @@ const defaults = {
// Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
- // Pass a custom duration
- duration: null,
-
// Quality default
quality: {
default: 'default',
options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'],
},
- // Set loops
- loop: {
- active: false,
- start: null,
- end: null,
- },
-
// Speed default and options to display
speed: {
default: 1,
diff --git a/src/js/listeners.js b/src/js/listeners.js
index 9b84a729..7a455c13 100644
--- a/src/js/listeners.js
+++ b/src/js/listeners.js
@@ -9,162 +9,14 @@ import fullscreen from './fullscreen';
import storage from './storage';
import ui from './ui';
-const listeners = {
- // Listen for media events
- media() {
- // Time change on media
- utils.on(this.media, 'timeupdate seeking', event => ui.timeUpdate.call(this, event));
-
- // Display duration
- utils.on(this.media, 'durationchange loadedmetadata', event => ui.displayDuration.call(this, event));
-
- // Handle the media finishing
- utils.on(this.media, 'ended', () => {
- // Show poster on end
- if (this.type === 'video' && this.config.showPosterOnEnd) {
- // Restart
- this.restart();
-
- // Re-load media
- this.media.load();
- }
- });
-
- // Check for buffer progress
- utils.on(this.media, 'progress playing', event => ui.updateProgress.call(this, event));
-
- // Handle native mute
- utils.on(this.media, 'volumechange', event => ui.updateVolume.call(this, event));
-
- // Handle native play/pause
- utils.on(this.media, 'play pause ended', event => ui.checkPlaying.call(this, event));
-
- // Loading
- utils.on(this.media, 'waiting canplay seeked', event => ui.checkLoading.call(this, event));
-
- // Click video
- if (this.supported.ui && this.config.clickToPlay && this.type !== 'audio') {
- // Re-fetch the wrapper
- const wrapper = utils.getElement.call(this, `.${this.config.classNames.video}`);
-
- // Bail if there's no wrapper (this should never happen)
- if (!wrapper) {
- return;
- }
-
- // Set cursor
- wrapper.style.cursor = 'pointer';
-
- // On click play, pause ore restart
- utils.on(wrapper, 'click', () => {
- // Touch devices will just show controls (if we're hiding controls)
- if (this.config.hideControls && support.touch && !this.media.paused) {
- return;
- }
-
- if (this.media.paused) {
- this.play();
- } else if (this.media.ended) {
- this.restart();
- this.play();
- } else {
- this.pause();
- }
- });
- }
-
- // Disable right click
- if (this.config.disableContextMenu) {
- utils.on(
- this.media,
- 'contextmenu',
- event => {
- event.preventDefault();
- },
- false
- );
- }
-
- // Speed change
- utils.on(this.media, 'ratechange', () => {
- // Update UI
- controls.updateSetting.call(this, 'speed');
-
- // Save speed to localStorage
- storage.set.call(this, { speed: this.speed });
- });
-
- // Quality change
- utils.on(this.media, 'qualitychange', () => {
- // Update UI
- controls.updateSetting.call(this, 'quality');
-
- // Save speed to localStorage
- storage.set.call(this, { quality: this.quality });
- });
-
- // Caption language change
- utils.on(this.media, 'captionchange', () => {
- // Save speed to localStorage
- storage.set.call(this, { language: this.language });
- });
+// Sniff out the browser
+const browser = utils.getBrowser();
- // Volume change
- utils.on(this.media, 'volumechange', () => {
- // Save speed to localStorage
- storage.set.call(this, { volume: this.volume });
- });
-
- // Captions toggle
- utils.on(this.media, 'captionsenabled captionsdisabled', () => {
- // Update UI
- controls.updateSetting.call(this, 'captions');
-
- // Save speed to localStorage
- storage.set.call(this, { captions: this.captions.enabled });
- });
-
- // Proxy events to container
- // Bubble up key events for Edge
- utils.on(this.media, this.config.events.concat(['keyup', 'keydown']).join(' '), event => {
- utils.dispatchEvent.call(this, this.elements.container, event.type, true);
- });
- },
-
- // Listen for control events
- controls() {
- // IE doesn't support input event, so we fallback to change
- const inputEvent = this.browser.isIE ? 'change' : 'input';
+const listeners = {
+ // Global listeners
+ global() {
let last = null;
- // Trigger custom and default handlers
- const proxy = (event, handlerKey, defaultHandler) => {
- const customHandler = this.config.listeners[handlerKey];
-
- // Execute custom handler
- if (utils.is.function(customHandler)) {
- customHandler.call(this, event);
- }
-
- // Only call default handler if not prevented in custom handler
- if (!event.defaultPrevented && utils.is.function(defaultHandler)) {
- defaultHandler.call(this, event);
- }
- };
-
- // Click play/pause helper
- const togglePlay = () => {
- const play = this.togglePlay();
-
- // Determine which buttons
- const target = this.elements.buttons[play ? 'pause' : 'play'];
-
- // Transfer focus
- if (utils.is.htmlElement(target)) {
- target.focus();
- }
- };
-
// Get the key code for an event
const getKeyCode = event => (event.keyCode ? event.keyCode : event.which);
@@ -249,6 +101,7 @@ const listeners = {
case 75:
// Space and K key
if (!held) {
+ this.warn('togglePlay', event.type);
this.togglePlay();
}
break;
@@ -322,10 +175,10 @@ const listeners = {
};
// Keyboard shortcuts
- if (this.config.keyboard.focused) {
- utils.on(this.elements.container, 'keydown keyup', handleKey, false);
- } else if (this.config.keyboard.global) {
+ if (this.config.keyboard.global) {
utils.on(window, 'keydown keyup', handleKey, false);
+ } else if (this.config.keyboard.focused) {
+ utils.on(this.elements.container, 'keydown keyup', handleKey, false);
}
// Detect tab focus
@@ -347,6 +200,180 @@ const listeners = {
}, 0);
});
+ // Toggle controls visibility based on mouse movement
+ if (this.config.hideControls) {
+ // Toggle controls on mouse events and entering fullscreen
+ utils.on(
+ this.elements.container,
+ 'mouseenter mouseleave mousemove touchstart touchend touchcancel touchmove enterfullscreen',
+ event => {
+ this.toggleControls(event);
+ }
+ );
+ }
+
+ // Handle user exiting fullscreen by escaping etc
+ if (fullscreen.enabled) {
+ utils.on(document, fullscreen.eventType, event => {
+ this.toggleFullscreen(event);
+ });
+ }
+ },
+
+ // Listen for media events
+ media() {
+ // Time change on media
+ utils.on(this.media, 'timeupdate seeking', event => ui.timeUpdate.call(this, event));
+
+ // Display duration
+ utils.on(this.media, 'durationchange loadedmetadata', event => ui.displayDuration.call(this, event));
+
+ // Handle the media finishing
+ utils.on(this.media, 'ended', () => {
+ // Show poster on end
+ if (this.type === 'video' && this.config.showPosterOnEnd) {
+ // Restart
+ this.restart();
+
+ // Re-load media
+ this.media.load();
+ }
+ });
+
+ // Check for buffer progress
+ utils.on(this.media, 'progress playing', event => ui.updateProgress.call(this, event));
+
+ // Handle native mute
+ utils.on(this.media, 'volumechange', event => ui.updateVolume.call(this, event));
+
+ // Handle native play/pause
+ utils.on(this.media, 'play pause ended', event => ui.checkPlaying.call(this, event));
+
+ // Loading
+ utils.on(this.media, 'waiting canplay seeked', event => ui.checkLoading.call(this, event));
+
+ // Click video
+ if (this.supported.ui && this.config.clickToPlay && this.type !== 'audio') {
+ // Re-fetch the wrapper
+ const wrapper = utils.getElement.call(this, `.${this.config.classNames.video}`);
+
+ // Bail if there's no wrapper (this should never happen)
+ if (!wrapper) {
+ return;
+ }
+
+ // Set cursor
+ wrapper.style.cursor = 'pointer';
+
+ // On click play, pause ore restart
+ utils.on(wrapper, 'click', () => {
+ // Touch devices will just show controls (if we're hiding controls)
+ if (this.config.hideControls && support.touch && !this.media.paused) {
+ return;
+ }
+
+ if (this.media.paused) {
+ this.play();
+ } else if (this.media.ended) {
+ this.restart();
+ this.play();
+ } else {
+ this.pause();
+ }
+ });
+ }
+
+ // Disable right click
+ if (this.config.disableContextMenu) {
+ utils.on(
+ this.media,
+ 'contextmenu',
+ event => {
+ event.preventDefault();
+ },
+ false
+ );
+ }
+
+ // Speed change
+ utils.on(this.media, 'ratechange', () => {
+ // Update UI
+ controls.updateSetting.call(this, 'speed');
+
+ // Save speed to localStorage
+ storage.set.call(this, { speed: this.speed });
+ });
+
+ // Quality change
+ utils.on(this.media, 'qualitychange', () => {
+ // Update UI
+ controls.updateSetting.call(this, 'quality');
+
+ // Save speed to localStorage
+ storage.set.call(this, { quality: this.quality });
+ });
+
+ // Caption language change
+ utils.on(this.media, 'captionchange', () => {
+ // Save speed to localStorage
+ storage.set.call(this, { language: this.language });
+ });
+
+ // Volume change
+ utils.on(this.media, 'volumechange', () => {
+ // Save speed to localStorage
+ storage.set.call(this, { volume: this.volume });
+ });
+
+ // Captions toggle
+ utils.on(this.media, 'captionsenabled captionsdisabled', () => {
+ // Update UI
+ controls.updateSetting.call(this, 'captions');
+
+ // Save speed to localStorage
+ storage.set.call(this, { captions: this.captions.enabled });
+ });
+
+ // Proxy events to container
+ // Bubble up key events for Edge
+ utils.on(this.media, this.config.events.concat(['keyup', 'keydown']).join(' '), event => {
+ utils.dispatchEvent.call(this, this.elements.container, event.type, true);
+ });
+ },
+
+ // Listen for control events
+ controls() {
+ // IE doesn't support input event, so we fallback to change
+ const inputEvent = browser.isIE ? 'change' : 'input';
+
+ // Trigger custom and default handlers
+ const proxy = (event, handlerKey, defaultHandler) => {
+ const customHandler = this.config.listeners[handlerKey];
+
+ // Execute custom handler
+ if (utils.is.function(customHandler)) {
+ customHandler.call(this, event);
+ }
+
+ // Only call default handler if not prevented in custom handler
+ if (!event.defaultPrevented && utils.is.function(defaultHandler)) {
+ defaultHandler.call(this, event);
+ }
+ };
+
+ // Click play/pause helper
+ const togglePlay = () => {
+ const play = this.togglePlay();
+
+ // Determine which buttons
+ const target = this.elements.buttons[play ? 'pause' : 'play'];
+
+ // Transfer focus
+ if (utils.is.htmlElement(target)) {
+ target.focus();
+ }
+ };
+
// Play
utils.on(this.elements.buttons.play, 'click', event => proxy(event, 'play', togglePlay));
@@ -468,7 +495,7 @@ const listeners = {
);
// Polyfill for lower fill in <input type="range"> for webkit
- if (this.browser.isWebkit) {
+ if (browser.isWebkit) {
utils.on(utils.getElements.call(this, 'input[type="range"]'), 'input', event => {
controls.updateRangeFill.call(this, event.target);
});
@@ -481,15 +508,6 @@ const listeners = {
// Toggle controls visibility based on mouse movement
if (this.config.hideControls) {
- // Toggle controls on mouse events and entering fullscreen
- utils.on(
- this.elements.container,
- 'mouseenter mouseleave mousemove touchstart touchend touchcancel touchmove enterfullscreen',
- event => {
- this.toggleControls(event);
- }
- );
-
// Watch for cursor over controls so they don't hide when trying to interact
utils.on(this.elements.controls, 'mouseenter mouseleave', event => {
this.elements.controls.hover = event.type === 'mouseenter';
@@ -553,13 +571,6 @@ const listeners = {
}),
false
);
-
- // Handle user exiting fullscreen by escaping etc
- if (fullscreen.enabled) {
- utils.on(document, fullscreen.eventType, event => {
- this.toggleFullscreen(event);
- });
- }
},
};
diff --git a/src/js/media.js b/src/js/media.js
index 9e53f5fc..46e6bec6 100644
--- a/src/js/media.js
+++ b/src/js/media.js
@@ -8,6 +8,9 @@ import youtube from './plugins/youtube';
import vimeo from './plugins/vimeo';
import ui from './ui';
+// Sniff out the browser
+const browser = utils.getBrowser();
+
const media = {
// Setup media
setup() {
@@ -45,7 +48,7 @@ const media = {
utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.config.autoplay);
// Add iOS class
- utils.toggleClass(this.elements.container, this.config.classNames.isIos, this.browser.isIos);
+ utils.toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
// Add touch class
utils.toggleClass(this.elements.container, this.config.classNames.isTouch, support.touch);
diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js
index 0b815fa5..83b6d942 100644
--- a/src/js/plugins/vimeo.js
+++ b/src/js/plugins/vimeo.js
@@ -4,6 +4,7 @@
import utils from './../utils';
import captions from './../captions';
+import controls from './../controls';
import ui from './../ui';
const vimeo = {
@@ -15,6 +16,9 @@ const vimeo = {
// 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.type));
@@ -33,21 +37,31 @@ const vimeo = {
}
},
+ // Set aspect ratio
+ setAspectRatio(input) {
+ const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':');
+ const padding = 100 / ratio[0] * ratio[1];
+ const offset = (300 - padding) / 6;
+ this.elements.wrapper.style.paddingBottom = `${padding}%`;
+ this.media.style.transform = `translateY(-${offset}%)`;
+ },
+
// API Ready
ready() {
const player = this;
// Get Vimeo params for the iframe
const options = {
- loop: this.config.loop.active,
- autoplay: this.config.autoplay,
+ loop: player.config.loop.active,
+ autoplay: player.config.autoplay,
byline: false,
portrait: false,
title: false,
+ speed: true,
transparent: 0,
};
const params = utils.buildUrlParameters(options);
- const id = utils.parseVimeoId(this.embedId);
+ const id = utils.parseVimeoId(player.embedId);
// Build an iframe
const iframe = utils.createElement('iframe');
@@ -57,7 +71,7 @@ const vimeo = {
player.media.appendChild(iframe);
// Setup instance
- // https://github.com/vimeo/this.js
+ // https://github.com/vimeo/player.js
player.embed = new window.Vimeo.Player(iframe);
// Create a faux HTML5 API using the Vimeo API
@@ -99,18 +113,22 @@ const vimeo = {
// Restore pause state
if (paused) {
- this.pause();
+ player.pause();
}
},
});
// Playback speed
- // Not currently supported in Vimeo
+ let { playbackRate } = player.media;
Object.defineProperty(player.media, 'playbackRate', {
get() {
- return null;
+ return playbackRate;
+ },
+ set(input) {
+ playbackRate = input;
+ player.embed.setPlaybackRate(input);
+ utils.dispatchEvent.call(player, player.media, 'ratechange');
},
- set() {},
});
// Volume
@@ -148,6 +166,17 @@ const vimeo = {
},
});
+ // Set aspect ratio based on video size
+ Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {
+ const ratio = utils.getAspectRatio(dimensions[0], dimensions[1]);
+ vimeo.setAspectRatio.call(this, ratio);
+ });
+
+ // Get available speeds
+ if (player.config.controls.includes('settings') && player.config.settings.includes('speed')) {
+ controls.setSpeedMenu.call(player);
+ }
+
// Get title
player.embed.getVideoTitle().then(title => {
player.config.title = title;
@@ -156,7 +185,7 @@ const vimeo = {
// Get current time
player.embed.getCurrentTime().then(value => {
currentTime = value;
- utils.dispatchEvent.call(this, this.media, 'timeupdate');
+ utils.dispatchEvent.call(player, player.media, 'timeupdate');
});
// Get duration
@@ -202,31 +231,31 @@ const vimeo = {
utils.dispatchEvent.call(player, player.media, 'pause');
});
- this.embed.on('timeupdate', data => {
- this.media.seeking = false;
+ player.embed.on('timeupdate', data => {
+ player.media.seeking = false;
currentTime = data.seconds;
- utils.dispatchEvent.call(this, this.media, 'timeupdate');
+ utils.dispatchEvent.call(player, player.media, 'timeupdate');
});
- this.embed.on('progress', data => {
- this.media.buffered = data.percent;
- utils.dispatchEvent.call(this, this.media, 'progress');
+ player.embed.on('progress', data => {
+ player.media.buffered = data.percent;
+ utils.dispatchEvent.call(player, player.media, 'progress');
if (parseInt(data.percent, 10) === 1) {
// Trigger event
- utils.dispatchEvent.call(this, this.media, 'canplaythrough');
+ utils.dispatchEvent.call(player, player.media, 'canplaythrough');
}
});
- this.embed.on('seeked', () => {
- this.media.seeking = false;
- utils.dispatchEvent.call(this, this.media, 'seeked');
- utils.dispatchEvent.call(this, this.media, 'play');
+ player.embed.on('seeked', () => {
+ player.media.seeking = false;
+ utils.dispatchEvent.call(player, player.media, 'seeked');
+ utils.dispatchEvent.call(player, player.media, 'play');
});
- this.embed.on('ended', () => {
- this.media.paused = true;
- utils.dispatchEvent.call(this, this.media, 'ended');
+ player.embed.on('ended', () => {
+ player.media.paused = true;
+ utils.dispatchEvent.call(player, player.media, 'ended');
});
// Rebuild UI
diff --git a/src/js/plugins/youtube.js b/src/js/plugins/youtube.js
index 38f649a5..84d16488 100644
--- a/src/js/plugins/youtube.js
+++ b/src/js/plugins/youtube.js
@@ -17,6 +17,9 @@ const youtube = {
// 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.type));
@@ -44,6 +47,12 @@ const youtube = {
}
},
+ // Set aspect ratio
+ setAspectRatio() {
+ const ratio = this.config.ratio.split(':');
+ this.elements.wrapper.style.paddingBottom = `${100 / ratio[0] * ratio[1]}%`;
+ },
+
// API ready
ready(videoId) {
const player = this;
@@ -66,9 +75,9 @@ const youtube = {
origin: window && window.location.hostname,
widget_referrer: window && window.location.href,
- // Captions is flaky on YouTube
- // cc_load_policy: (this.captions.active ? 1 : 0),
- // cc_lang_pref: 'en',
+ // Captions are flaky on YouTube
+ cc_load_policy: (this.captions.active ? 1 : 0),
+ cc_lang_pref: this.config.captions.language,
},
events: {
onError(event) {
diff --git a/src/js/plyr.js b/src/js/plyr.js
index 5c28887e..355fe5cb 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -13,6 +13,7 @@ import utils from './utils';
import captions from './captions';
import controls from './controls';
import fullscreen from './fullscreen';
+import listeners from './listeners';
import media from './media';
import storage from './storage';
import source from './source';
@@ -78,7 +79,6 @@ class Plyr {
};
// Captions
- // TODO: captions.enabled should be in config?
this.captions = {
enabled: null,
tracks: null,
@@ -192,10 +192,7 @@ class Plyr {
return;
}
- // Sniff out the browser
- this.browser = utils.getBrowser();
-
- // Load saved settings from localStorage
+ // Setup local storage for user settings
storage.setup.call(this);
// Check for support again but with type
@@ -217,6 +214,9 @@ class Plyr {
// Allow focus to be captured
this.elements.container.setAttribute('tabindex', 0);
+ // Global listeners
+ listeners.global.call(this);
+
// Add style hook
ui.addStyleHook.call(this);
@@ -237,17 +237,27 @@ class Plyr {
}
}
+ // ---------------------------------------
// API
// ---------------------------------------
+ /**
+ * If the player is HTML5
+ */
get isHTML5() {
return types.html5.includes(this.type);
}
+
+ /**
+ * If the player is an embed - e.g. YouTube or Vimeo
+ */
get isEmbed() {
return types.embed.includes(this.type);
}
- // Play
+ /**
+ * Play the media
+ */
play() {
if ('play' in this.media) {
this.media.play();
@@ -257,7 +267,9 @@ class Plyr {
return this;
}
- // Pause
+ /**
+ * Pause the media
+ */
pause() {
if ('pause' in this.media) {
this.media.pause();
@@ -267,7 +279,10 @@ class Plyr {
return this;
}
- // Toggle playback
+ /**
+ * Toggle playback based on current status
+ * @param {boolean} toggle
+ */
togglePlay(toggle) {
// True toggle if nothing passed
if ((!utils.is.boolean(toggle) && this.media.paused) || toggle) {
@@ -277,31 +292,43 @@ class Plyr {
return this.pause();
}
- // Stop
+ /**
+ * Stop playback
+ */
stop() {
return this.restart().pause();
}
- // Restart
+ /**
+ * Restart playback
+ */
restart() {
this.currentTime = 0;
return this;
}
- // Rewind
+ /**
+ * Rewind
+ * @param {number} seekTime - how far to rewind in seconds. Defaults to the config.seekTime
+ */
rewind(seekTime) {
this.currentTime = this.currentTime - (utils.is.number(seekTime) ? seekTime : this.config.seekTime);
return this;
}
- // Fast forward
+ /**
+ * Fast forward
+ * @param {number} seekTime - how far to fast forward in seconds. Defaults to the config.seekTime
+ */
forward(seekTime) {
this.currentTime = this.currentTime + (utils.is.number(seekTime) ? seekTime : this.config.seekTime);
return this;
}
- // Seek to time
- // The input parameter can be an event or a number
+ /**
+ * Seek to a time
+ * @param {number} input - where to seek to in seconds. Defaults to 0 (the start)
+ */
set currentTime(input) {
let targetTime = 0;
@@ -327,7 +354,9 @@ class Plyr {
return Number(this.media.currentTime);
}
- // Duration
+ /**
+ * Get the duration of the current media
+ */
get duration() {
// Faux duration set via config
const fauxDuration = parseInt(this.config.duration, 10);
@@ -339,7 +368,10 @@ class Plyr {
return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration;
}
- // Volume
+ /**
+ * Set the player volume
+ * @param {number} value - must be between 0 and 1. Defaults to the value from local storage and config.volume if not set in storage
+ */
set volume(value) {
let volume = value;
const max = 1;
@@ -377,6 +409,9 @@ class Plyr {
}
}
+ /**
+ * Get the current player volume
+ */
get volume() {
return this.media.volume;
}
diff --git a/src/js/ui.js b/src/js/ui.js
index c0448054..aa579d8d 100644
--- a/src/js/ui.js
+++ b/src/js/ui.js
@@ -84,15 +84,17 @@ const ui = {
// Update the UI
ui.checkPlaying.call(this);
+ // Ready for API calls
this.ready = true;
// Ready event at end of execution stack
utils.dispatchEvent.call(this, this.media, 'ready');
// Autoplay
- if (this.config.autoplay) {
+ // TODO: check we still need this?
+ /* if (this.isEmbed && this.config.autoplay) {
this.play();
- }
+ } */
},
// Show the duration on metadataloaded
diff --git a/src/js/utils.js b/src/js/utils.js
index 4296f345..1c3d6ed8 100644
--- a/src/js/utils.js
+++ b/src/js/utils.js
@@ -89,6 +89,73 @@ const utils = {
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
},
+ // Load an external SVG sprite
+ loadSprite(url, id) {
+ if (typeof url !== 'string') {
+ return;
+ }
+
+ const prefix = 'cache-';
+ const hasId = typeof id === 'string';
+ let isCached = false;
+
+ function updateSprite(data) {
+ // Inject content
+ this.innerHTML = data;
+
+ // Inject the SVG to the body
+ document.body.insertBefore(this, document.body.childNodes[0]);
+ }
+
+ // Only load once
+ if (!hasId || !document.querySelectorAll(`#${id}`).length) {
+ // Create container
+ const container = document.createElement('div');
+ container.setAttribute('hidden', '');
+
+ if (hasId) {
+ container.setAttribute('id', id);
+ }
+
+ // Check in cache
+ if (support.storage) {
+ const cached = window.localStorage.getItem(prefix + id);
+ isCached = cached !== null;
+
+ if (isCached) {
+ const data = JSON.parse(cached);
+ updateSprite.call(container, data.content);
+ }
+ }
+
+ // ReSharper disable once InconsistentNaming
+ const xhr = new XMLHttpRequest();
+
+ // XHR for Chrome/Firefox/Opera/Safari
+ if ('withCredentials' in xhr) {
+ xhr.open('GET', url, true);
+ } else {
+ return;
+ }
+
+ // Once loaded, inject to container and body
+ xhr.onload = () => {
+ if (support.storage) {
+ window.localStorage.setItem(
+ prefix + id,
+ JSON.stringify({
+ content: xhr.responseText,
+ })
+ );
+ }
+
+ updateSprite.call(container, xhr.responseText);
+ };
+
+ xhr.send();
+ }
+ },
+
// Generate a random ID
generateId(prefix) {
return `${prefix}-${Math.floor(Math.random() * 10000)}`;
@@ -564,71 +631,11 @@ const utils = {
return fragment.firstChild.innerText;
},
- // Load an SVG sprite
- loadSprite(url, id) {
- if (typeof url !== 'string') {
- return;
- }
-
- const prefix = 'cache-';
- const hasId = typeof id === 'string';
- let isCached = false;
-
- function updateSprite(data) {
- // Inject content
- this.innerHTML = data;
-
- // Inject the SVG to the body
- document.body.insertBefore(this, document.body.childNodes[0]);
- }
-
- // Only load once
- if (!hasId || !document.querySelectorAll(`#${id}`).length) {
- // Create container
- const container = document.createElement('div');
- container.setAttribute('hidden', '');
-
- if (hasId) {
- container.setAttribute('id', id);
- }
-
- // Check in cache
- if (support.storage) {
- const cached = window.localStorage.getItem(prefix + id);
- isCached = cached !== null;
-
- if (isCached) {
- const data = JSON.parse(cached);
- updateSprite.call(container, data.content);
- }
- }
-
- // ReSharper disable once InconsistentNaming
- const xhr = new XMLHttpRequest();
-
- // XHR for Chrome/Firefox/Opera/Safari
- if ('withCredentials' in xhr) {
- xhr.open('GET', url, true);
- } else {
- return;
- }
-
- // Once loaded, inject to container and body
- xhr.onload = () => {
- if (support.storage) {
- window.localStorage.setItem(
- prefix + id,
- JSON.stringify({
- content: xhr.responseText,
- })
- );
- }
-
- updateSprite.call(container, xhr.responseText);
- };
-
- xhr.send();
- }
+ // Get aspect ratio for dimensions
+ getAspectRatio(width, height) {
+ const getRatio = (w, h) => (h === 0 ? w : getRatio(h, w % h));
+ const ratio = getRatio(width, height);
+ return `${width / ratio}:${height / ratio}`;
},
// Get the transition end event
diff --git a/src/less/components/embed.less b/src/less/components/embed.less
index f6b5b307..d31e4770 100644
--- a/src/less/components/embed.less
+++ b/src/less/components/embed.less
@@ -4,7 +4,11 @@
// --------------------------------------------------------------
.plyr__video-embed {
- padding-bottom: 56.25%; /* 16:9 */
+ // Default to 16:9 ratio but this is set by JavaScript based on config
+ @padding: ((100 / 16) * 9);
+ @offset: unit((300 - @padding) / 6, ~'%');
+
+ padding-bottom: unit(@padding, ~'%');
height: 0;
iframe {
@@ -20,8 +24,8 @@
// Vimeo hack
> div {
position: relative;
- padding-bottom: 200%;
- transform: translateY(-35.95%);
+ padding-bottom: 300%;
+ transform: translateY(-@offset);
}
}
// To allow mouse events to be captured if full support