Compare commits

...

15 Commits

Author SHA1 Message Date
dda8e30b92 Merge branch 'master' of github.com:sampotts/plyr 2018-03-28 22:45:18 +11:00
c4e2e24643 Bug fixes 2018-03-28 22:45:11 +11:00
e020a105a3 Update readme.md 2018-03-28 00:55:55 +11:00
2b7fe9a4f9 v3.0.6 2018-03-28 00:17:15 +11:00
951df64b7f v3.0.5 2018-03-27 23:52:26 +11:00
0976afe282 v3.0.4 2018-03-27 23:47:58 +11:00
7b1e4abda7 Controls fixes 2018-03-27 23:43:38 +11:00
0cf75eed3f Revert API method change 2018-03-27 21:15:11 +11:00
d96957d086 Allow fullscreen in iframe 2018-03-27 21:13:22 +11:00
1a032ea498 Fix for seeking issue 2018-03-27 21:10:06 +11:00
5d079da1b8 Use object.entries 2018-03-27 10:41:06 +11:00
9c1bc6ab08 Fixes for fast forward and issues with event.preventDefault() 2018-03-27 10:36:08 +11:00
3d2ba8c009 Update readme.md 2018-03-22 09:11:42 +11:00
e872ce3f77 Update readme.md 2018-03-22 09:10:50 +11:00
b77756da04 Typo 2018-03-22 01:15:10 +11:00
33 changed files with 1059 additions and 596 deletions

View File

@ -1,19 +1,38 @@
# v3.0.3
## v3.0.7
* Fix for keyboard shortcut error with fast forward
* Fix for Vimeo trying to set playback rate when not allowed
## v3.0.6
* Improved the logic for the custom handlers preventing default handlers
## v3.0.5
* Removed console messages
## v3.0.4
* Fixes for fullscreen not working inside iframes
* Fixes for custom handlers being able to prevent default
* Fixes for controls not hiding/showing correctly on Mobile Safari
## v3.0.3
* Vimeo offset tweak (fixes #826)
* Fix for .stop() method (fixes #819)
* Check for array for speed options (fixes #252)
* Check for array for speed options (fixes #817)
* Restore as float (fixes #828)
* Fix for Firefox fullscreen oddness (Fixes #821)
* Improve Sprite checking (fixes #827)
* Fix fast-forward control (thanks @saadshahd)
* Fix the options link in the readme (thanks @DanielRuf)
# v3.0.2
## v3.0.2
* Fix for Safari not firing error events when trying to load blocked scripts
# v3.0.1
## v3.0.1
* Fix for trying to accessing local storage when it's blocked

2
demo/dist/demo.css vendored

File diff suppressed because one or more lines are too long

21
demo/dist/demo.js vendored
View File

@ -3820,6 +3820,22 @@ var singleton = Raven;
tooltips: {
controls: true
},
/* controls: [
'play-large',
'restart',
'rewind',
'play',
'fast-forward',
'progress',
'current-time',
'mute',
'volume',
'captions',
'settings',
'pip',
'airplay',
'fullscreen',
], */
captions: {
active: true
},
@ -3830,6 +3846,9 @@ var singleton = Raven;
enabled: true,
publisherId: '918848828995742'
}
/* listeners: {
seek: () => false,
}, */
});
// Expose for tinkering in the console
@ -3917,7 +3936,7 @@ var singleton = Raven;
player.source = {
type: 'video',
sources: [{
src: 'https://vimeo.com/76979871',
src: 'https://vimeo.com/25345658',
provider: 'vimeo'
}]
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -57,6 +57,22 @@ import Raven from 'raven-js';
tooltips: {
controls: true,
},
/* controls: [
'play-large',
'restart',
'rewind',
'play',
'fast-forward',
'progress',
'current-time',
'mute',
'volume',
'captions',
'settings',
'pip',
'airplay',
'fullscreen',
], */
captions: {
active: true,
},
@ -67,6 +83,9 @@ import Raven from 'raven-js';
enabled: true,
publisherId: '918848828995742',
},
/* listeners: {
seek: () => false,
}, */
});
// Expose for tinkering in the console
@ -160,7 +179,7 @@ import Raven from 'raven-js';
player.source = {
type: 'video',
sources: [{
src: 'https://vimeo.com/76979871',
src: 'https://vimeo.com/25345658',
provider: 'vimeo',
}],
};

2
dist/plyr.css vendored

File diff suppressed because one or more lines are too long

522
dist/plyr.js vendored

File diff suppressed because it is too large Load Diff

2
dist/plyr.js.map vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"name": "plyr",
"version": "3.0.3",
"version": "3.0.7",
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
"homepage": "https://plyr.io",
"main": "./dist/plyr.js",

View File

@ -2,7 +2,7 @@
A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo media player that supports [_modern_](#browser-support) browsers.
[Checkout the demo](https://plyr.io) - [Donate to support Plyr](#donate) - [Chat on Slack](https://bit.ly/plyr-slack)
[Checkout the demo](https://plyr.io) - [Donate to support Plyr](#donate) - [Chat on Slack](https://bit.ly/plyr-chat)
[![Image of Plyr](https://cdn.plyr.io/static/demo/screenshot.png?v=3)](https://plyr.io)
@ -10,7 +10,7 @@ A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo medi
* **Accessible** - full support for VTT captions and screen readers
* **[Customisable](#html)** - make the player look how you want with the markup you want
* **Semantic** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
* **Good HTML** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
`<span>` or `<a href="#">` button hacks
* **Responsive** - works with any screen size
* **HTML Video & Audio** - support for both formats
@ -21,6 +21,10 @@ A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo medi
* **[Events](#events)** - no messing around with Vimeo and YouTube APIs, all events are standardized across formats
* **[Fullscreen](#fullscreen)** - supports native fullscreen with fallback to "full window" modes
* **[Shortcuts](#shortcuts)** - supports keyboard shortcuts
* **Picture-in-Picture** - supports Safari's picture-in-picture mode
* **Playsinline** - supports the `playsinline` attribute
* **Speed controls** - adjust speed on the fly
* **Multiple captions** - support for multiple caption tracks
* **i18n support** - support for internationalization of controls
* **No dependencies** - written in "vanilla" ES6 JavaScript, no jQuery required
* **SASS** - to include in your build processes
@ -124,7 +128,7 @@ See [initialising](#initialising) for more information on advanced setups.
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript, you can use the following:
```html
<script src="https://cdn.plyr.io/3.0.3/plyr.js"></script>
<script src="https://cdn.plyr.io/3.0.7/plyr.js"></script>
```
_Note_: Be sure to read the [polyfills](#polyfills) section below about browser compatibility
@ -140,13 +144,13 @@ Include the `plyr.css` stylsheet into your `<head>`
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
```html
<link rel="stylesheet" href="https://cdn.plyr.io/3.0.3/plyr.css">
<link rel="stylesheet" href="https://cdn.plyr.io/3.0.7/plyr.css">
```
### 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.3/plyr.svg`.
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.0.7/plyr.svg`.
## Ads

79
src/js/controls.js vendored
View File

@ -5,6 +5,7 @@
import support from './support';
import utils from './utils';
import ui from './ui';
import i18n from './i18n';
import captions from './captions';
// Sniff out the browser
@ -74,7 +75,7 @@ const controls = {
// Create hidden text label
createLabel(type, attr) {
let text = this.config.i18n[type];
let text = i18n.get(type, this.config);
const attributes = Object.assign({}, attr);
switch (type) {
@ -126,7 +127,7 @@ const controls = {
createButton(buttonType, attr) {
const button = utils.createElement('button');
const attributes = Object.assign({}, attr);
let type = buttonType;
let type = utils.toCamelCase(buttonType);
let toggle = false;
let label;
@ -147,7 +148,7 @@ const controls = {
}
// Large play button
switch (type) {
switch (buttonType) {
case 'play':
toggle = true;
label = 'play';
@ -189,7 +190,7 @@ const controls = {
default:
label = type;
icon = type;
icon = buttonType;
}
// Setup toggle icon and labels
@ -204,7 +205,7 @@ const controls = {
// Add aria attributes
attributes['aria-pressed'] = false;
attributes['aria-label'] = this.config.i18n[label];
attributes['aria-label'] = i18n.get(label, this.config);
} else {
button.appendChild(controls.createIcon.call(this, icon));
button.appendChild(controls.createLabel.call(this, label));
@ -238,7 +239,7 @@ const controls = {
for: attributes.id,
class: this.config.classNames.hidden,
},
this.config.i18n[type],
i18n.get(type, this.config),
);
// Seek input
@ -291,11 +292,11 @@ const controls = {
let suffix = '';
switch (type) {
case 'played':
suffix = this.config.i18n.played;
suffix = i18n.get('played', this.config);
break;
case 'buffer':
suffix = this.config.i18n.buffered;
suffix = i18n.get('buffered', this.config);
break;
default:
@ -322,7 +323,7 @@ const controls = {
{
class: this.config.classNames.hidden,
},
this.config.i18n[type],
i18n.get(type, this.config),
),
);
@ -383,6 +384,16 @@ const controls = {
const clientRect = this.elements.inputs.seek.getBoundingClientRect();
const visible = `${this.config.classNames.tooltip}--visible`;
const toggle = toggle => {
utils.toggleClass(this.elements.display.seekTooltip, visible, toggle);
};
// Hide on touch
if (this.touch) {
toggle(false);
return;
}
// Determine percentage, if already visible
if (utils.is.event(event)) {
percent = 100 / clientRect.width * (event.pageX - clientRect.left);
@ -411,7 +422,7 @@ const controls = {
'mouseenter',
'mouseleave',
].includes(event.type)) {
utils.toggleClass(this.elements.display.seekTooltip, visible, event.type === 'mouseenter');
toggle(event.type === 'mouseenter');
}
},
@ -540,7 +551,7 @@ const controls = {
switch (setting) {
case 'captions':
value = this.captions.active ? this.captions.language : '';
value = this.captions.active ? this.captions.language : i18n.get('disabled', this.config);
break;
default:
@ -617,7 +628,7 @@ const controls = {
class: this.config.classNames.control,
'data-plyr-loop-action': option,
}),
this.config.i18n[option]
i18n.get(option, this.config)
);
if (['start', 'end'].includes(option)) {
@ -637,11 +648,7 @@ const controls = {
return null;
}
if (!support.textTracks || !captions.getTracks.call(this).length) {
return this.config.i18n.none;
}
if (this.captions.active) {
if (support.textTracks && captions.getTracks.call(this).length && this.captions.active) {
const currentTrack = captions.getCurrentTrack.call(this);
if (utils.is.track(currentTrack)) {
@ -649,7 +656,7 @@ const controls = {
}
}
return this.config.i18n.disabled;
return i18n.get('disabled', this.config);
},
// Set a list of available captions languages
@ -676,10 +683,10 @@ const controls = {
label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase(),
}));
// Add the "None" option to turn off captions
// Add the "Disabled" option to turn off captions
tracks.unshift({
language: '',
label: this.config.i18n.none,
label: i18n.get('disabled', this.config),
});
// Generate options
@ -699,7 +706,12 @@ const controls = {
},
// Set a list of available captions languages
setSpeedMenu() {
setSpeedMenu(options) {
// Do nothing if not selected
if (!this.config.controls.includes('settings') || !this.config.settings.includes('speed')) {
return;
}
// Menu required
if (!utils.is.element(this.elements.settings.panes.speed)) {
return;
@ -707,8 +719,8 @@ const controls = {
const type = 'speed';
// Set the default speeds
if (!utils.is.array(this.options.speed) || !this.options.speed.length) {
// Set the speed options
if (!utils.is.array(options)) {
this.options.speed = [
0.5,
0.75,
@ -718,6 +730,8 @@ const controls = {
1.75,
2,
];
} else {
this.options.speed = options;
}
// Set options if passed and filter based on config
@ -727,6 +741,9 @@ const controls = {
const toggle = !utils.is.empty(this.options.speed);
controls.toggleTab.call(this, type, toggle);
// Check if we need to toggle the parent
controls.checkMenu.call(this);
// If we're hiding, nothing more to do
if (!toggle) {
return;
@ -748,6 +765,14 @@ const controls = {
controls.updateSetting.call(this, type, list);
},
// Check if we need to hide/show the settings menu
checkMenu() {
const speedHidden = this.elements.settings.tabs.speed.getAttribute('hidden') !== null;
const languageHidden = this.elements.settings.tabs.captions.getAttribute('hidden') !== null;
utils.toggleHidden(this.elements.settings.menu, speedHidden && languageHidden);
},
// Show/hide menu
toggleMenu(event) {
const { form } = this.elements.settings;
@ -927,7 +952,7 @@ const controls = {
// Fast forward button
if (this.config.controls.includes('fast-forward')) {
container.appendChild(controls.createButton.call(this, 'fastForward'));
container.appendChild(controls.createButton.call(this, 'fast-forward'));
}
// Progress
@ -1069,7 +1094,7 @@ const controls = {
'aria-controls': `plyr-settings-${data.id}-${type}`,
'aria-expanded': false,
}),
this.config.i18n[type],
i18n.get(type, this.config),
);
const value = utils.createElement('span', {
@ -1109,7 +1134,7 @@ const controls = {
'aria-controls': `plyr-settings-${data.id}-home`,
'aria-expanded': false,
},
this.config.i18n[type],
i18n.get(type, this.config),
);
pane.appendChild(back);
@ -1152,7 +1177,7 @@ const controls = {
this.elements.controls = container;
if (this.config.controls.includes('settings') && this.config.settings.includes('speed')) {
if (this.isHTML5) {
controls.setSpeedMenu.call(this);
}

View File

@ -56,7 +56,7 @@ const defaults = {
// Sprite (for icons)
loadSprite: true,
iconPrefix: 'plyr',
iconUrl: 'https://cdn.plyr.io/3.0.3/plyr.svg',
iconUrl: 'https://cdn.plyr.io/3.0.7/plyr.svg',
// Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@ -132,7 +132,10 @@ const defaults = {
// Default controls
controls: [
'play-large',
// 'restart',
// 'rewind',
'play',
// 'fast-forward',
'progress',
'current-time',
'mute',
@ -155,7 +158,7 @@ const defaults = {
rewind: 'Rewind {seektime} secs',
play: 'Play',
pause: 'Pause',
forward: 'Forward {seektime} secs',
fastForward: 'Forward {seektime} secs',
seek: 'Seek',
played: 'Played',
buffered: 'Buffered',
@ -178,7 +181,6 @@ const defaults = {
end: 'End',
all: 'All',
reset: 'Reset',
none: 'None',
disabled: 'Disabled',
advertisement: 'Ad',
},
@ -203,7 +205,7 @@ const defaults = {
pause: null,
restart: null,
rewind: null,
forward: null,
fastForward: null,
mute: null,
volume: null,
captions: null,

View File

@ -117,9 +117,12 @@ class Fullscreen {
// Determine if fullscreen is enabled
get enabled() {
const fallback = this.player.config.fullscreen.fallback && !utils.inFrame();
return (Fullscreen.native || fallback) && this.player.config.fullscreen.enabled && this.player.supported.ui && this.player.isVideo;
return (
(Fullscreen.native || this.player.config.fullscreen.fallback) &&
this.player.config.fullscreen.enabled &&
this.player.supported.ui &&
this.player.isVideo
);
}
// Get active state
@ -169,7 +172,7 @@ class Fullscreen {
} else if (!Fullscreen.native) {
toggleFallback.call(this, true);
} else if (!this.prefix) {
this.target.requestFullScreen();
this.target.requestFullscreen();
} else if (!utils.is.empty(this.prefix)) {
this.target[`${this.prefix}Request${this.name}`]();
}

31
src/js/i18n.js Normal file
View File

@ -0,0 +1,31 @@
// ==========================================================================
// Plyr internationalization
// ==========================================================================
import utils from './utils';
const i18n = {
get(key = '', config = {}) {
if (utils.is.empty(key) || utils.is.empty(config) || !Object.keys(config.i18n).includes(key)) {
return '';
}
let string = config.i18n[key];
const replace = {
'{seektime}': config.seekTime,
'{title}': config.title,
};
Object.entries(replace).forEach(([
key,
value,
]) => {
string = utils.replaceAll(string, key, value);
});
return string;
},
};
export default i18n;

View File

@ -17,6 +17,7 @@ class Listeners {
this.handleKey = this.handleKey.bind(this);
this.toggleMenu = this.toggleMenu.bind(this);
this.firstTouch = this.firstTouch.bind(this);
}
// Handle key presses
@ -187,6 +188,17 @@ class Listeners {
controls.toggleMenu.call(this.player, event);
}
// Device is touch enabled
firstTouch() {
this.player.touch = true;
// Add touch class
utils.toggleClass(this.player.elements.container, this.player.config.classNames.isTouch, true);
// Clean up
utils.off(document.body, 'touchstart', this.firstTouch);
}
// Global window & document listeners
global(toggle = true) {
// Keyboard shortcuts
@ -196,6 +208,9 @@ class Listeners {
// Click anywhere closes menu
utils.toggleListener(document.body, 'click', this.toggleMenu, toggle);
// Detect touch by events
utils.on(document.body, 'touchstart', this.firstTouch);
}
// Container listeners
@ -288,7 +303,7 @@ class Listeners {
// On click play, pause ore restart
utils.on(wrapper, 'click', () => {
// Touch devices will just show controls (if we're hiding controls)
if (this.player.config.hideControls && support.touch && !this.player.paused) {
if (this.player.config.hideControls && this.player.touch && !this.player.paused) {
return;
}
@ -379,122 +394,132 @@ class Listeners {
// 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.player.config.listeners[handlerKey];
// Run default and custom handlers
const proxy = (event, defaultHandler, customHandlerKey) => {
const customHandler = this.player.config.listeners[customHandlerKey];
const hasCustomHandler = utils.is.function(customHandler);
let returned = true;
// Execute custom handler
if (utils.is.function(customHandler)) {
customHandler.call(this.player, event);
if (hasCustomHandler) {
returned = customHandler.call(this.player, event);
}
// Only call default handler if not prevented in custom handler
if (!event.defaultPrevented && utils.is.function(defaultHandler)) {
if (returned && utils.is.function(defaultHandler)) {
defaultHandler.call(this.player, event);
}
};
// Trigger custom and default handlers
const on = (element, type, defaultHandler, customHandlerKey, passive = true) => {
const customHandler = this.player.config.listeners[customHandlerKey];
const hasCustomHandler = utils.is.function(customHandler);
utils.on(element, type, event => proxy(event, defaultHandler, customHandlerKey), passive && !hasCustomHandler);
};
// Play/pause toggle
utils.on(this.player.elements.buttons.play, 'click', event =>
proxy(event, 'play', () => {
this.player.togglePlay();
}),
);
on(this.player.elements.buttons.play, 'click', this.player.togglePlay, 'play');
// Pause
utils.on(this.player.elements.buttons.restart, 'click', event =>
proxy(event, 'restart', () => {
this.player.restart();
}),
);
on(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
// Rewind
utils.on(this.player.elements.buttons.rewind, 'click', event =>
proxy(event, 'rewind', () => {
this.player.rewind();
}),
);
on(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
// Rewind
utils.on(this.player.elements.buttons.forward, 'click', event =>
proxy(event, 'forward', () => {
this.player.forward();
}),
);
on(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
// Mute toggle
utils.on(this.player.elements.buttons.mute, 'click', event =>
proxy(event, 'mute', () => {
on(
this.player.elements.buttons.mute,
'click',
() => {
this.player.muted = !this.player.muted;
}),
},
'mute',
);
// Captions toggle
utils.on(this.player.elements.buttons.captions, 'click', event =>
proxy(event, 'captions', () => {
this.player.toggleCaptions();
}),
);
on(this.player.elements.buttons.captions, 'click', this.player.toggleCaptions);
// Fullscreen toggle
utils.on(this.player.elements.buttons.fullscreen, 'click', event =>
proxy(event, 'fullscreen', () => {
on(
this.player.elements.buttons.fullscreen,
'click',
() => {
this.player.fullscreen.toggle();
}),
},
'fullscreen',
);
// Picture-in-Picture
utils.on(this.player.elements.buttons.pip, 'click', event =>
proxy(event, 'pip', () => {
on(
this.player.elements.buttons.pip,
'click',
() => {
this.player.pip = 'toggle';
}),
},
'pip',
);
// Airplay
utils.on(this.player.elements.buttons.airplay, 'click', event =>
proxy(event, 'airplay', () => {
this.player.airplay();
}),
);
on(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
// Settings menu
utils.on(this.player.elements.buttons.settings, 'click', event => {
on(this.player.elements.buttons.settings, 'click', event => {
controls.toggleMenu.call(this.player, event);
});
// Settings menu
utils.on(this.player.elements.settings.form, 'click', event => {
on(this.player.elements.settings.form, 'click', event => {
event.stopPropagation();
// Settings menu items - use event delegation as items are added/removed
if (utils.matches(event.target, this.player.config.selectors.inputs.language)) {
proxy(event, 'language', () => {
this.player.language = event.target.value;
});
proxy(
event,
() => {
this.player.language = event.target.value;
},
'language',
);
} else if (utils.matches(event.target, this.player.config.selectors.inputs.quality)) {
proxy(event, 'quality', () => {
this.player.quality = event.target.value;
});
proxy(
event,
() => {
this.player.quality = event.target.value;
},
'quality',
);
} else if (utils.matches(event.target, this.player.config.selectors.inputs.speed)) {
proxy(event, 'speed', () => {
this.player.speed = parseFloat(event.target.value);
});
proxy(
event,
() => {
this.player.speed = parseFloat(event.target.value);
},
'speed',
);
} else {
controls.showTab.call(this.player, event);
}
});
// Seek
utils.on(this.player.elements.inputs.seek, inputEvent, event =>
proxy(event, 'seek', () => {
on(
this.player.elements.inputs.seek,
inputEvent,
event => {
this.player.currentTime = event.target.value / event.target.max * this.player.duration;
}),
},
'seek',
);
// Current time invert
// Only if one time element is used for both currentTime and duration
if (this.player.config.toggleInvert && !utils.is.element(this.player.elements.display.duration)) {
utils.on(this.player.elements.display.currentTime, 'click', () => {
on(this.player.elements.display.currentTime, 'click', () => {
// Do nothing if we're at the start
if (this.player.currentTime === 0) {
return;
@ -506,31 +531,34 @@ class Listeners {
}
// Volume
utils.on(this.player.elements.inputs.volume, inputEvent, event =>
proxy(event, 'volume', () => {
on(
this.player.elements.inputs.volume,
inputEvent,
event => {
this.player.volume = event.target.value;
}),
},
'volume',
);
// Polyfill for lower fill in <input type="range"> for webkit
if (browser.isWebkit) {
utils.on(utils.getElements.call(this.player, 'input[type="range"]'), 'input', event => {
on(utils.getElements.call(this.player, 'input[type="range"]'), 'input', event => {
controls.updateRangeFill.call(this.player, event.target);
});
}
// Seek tooltip
utils.on(this.player.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this.player, event));
on(this.player.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this.player, event));
// Toggle controls visibility based on mouse movement
if (this.player.config.hideControls) {
// Watch for cursor over controls so they don't hide when trying to interact
utils.on(this.player.elements.controls, 'mouseenter mouseleave', event => {
this.player.elements.controls.hover = event.type === 'mouseenter';
on(this.player.elements.controls, 'mouseenter mouseleave', event => {
this.player.elements.controls.hover = !this.player.touch && event.type === 'mouseenter';
});
// Watch for cursor over controls so they don't hide when trying to interact
utils.on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
this.player.elements.controls.pressed = [
'mousedown',
'touchstart',
@ -538,50 +566,50 @@ class Listeners {
});
// Focus in/out on controls
utils.on(this.player.elements.controls, 'focusin focusout', event => {
on(this.player.elements.controls, 'focusin focusout', event => {
this.player.toggleControls(event);
});
}
// Mouse wheel for volume
utils.on(
on(
this.player.elements.inputs.volume,
'wheel',
event =>
proxy(event, 'volume', () => {
// Detect "natural" scroll - suppored on OS X Safari only
// Other browsers on OS X will be inverted until support improves
const inverted = event.webkitDirectionInvertedFromDevice;
const step = 1 / 50;
let direction = 0;
event => {
// Detect "natural" scroll - suppored on OS X Safari only
// Other browsers on OS X will be inverted until support improves
const inverted = event.webkitDirectionInvertedFromDevice;
const step = 1 / 50;
let direction = 0;
// Scroll down (or up on natural) to decrease
if (event.deltaY < 0 || event.deltaX > 0) {
if (inverted) {
this.player.decreaseVolume(step);
direction = -1;
} else {
this.player.increaseVolume(step);
direction = 1;
}
// Scroll down (or up on natural) to decrease
if (event.deltaY < 0 || event.deltaX > 0) {
if (inverted) {
this.player.decreaseVolume(step);
direction = -1;
} else {
this.player.increaseVolume(step);
direction = 1;
}
}
// Scroll up (or down on natural) to increase
if (event.deltaY > 0 || event.deltaX < 0) {
if (inverted) {
this.player.increaseVolume(step);
direction = 1;
} else {
this.player.decreaseVolume(step);
direction = -1;
}
// Scroll up (or down on natural) to increase
if (event.deltaY > 0 || event.deltaX < 0) {
if (inverted) {
this.player.increaseVolume(step);
direction = 1;
} else {
this.player.decreaseVolume(step);
direction = -1;
}
}
// Don't break page scrolling at max and min
if ((direction === 1 && this.player.media.volume < 1) || (direction === -1 && this.player.media.volume > 0)) {
event.preventDefault();
}
}),
// Don't break page scrolling at max and min
if ((direction === 1 && this.player.media.volume < 1) || (direction === -1 && this.player.media.volume > 0)) {
event.preventDefault();
}
},
'volume',
false,
);
}

View File

@ -46,7 +46,7 @@ const media = {
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);
utils.toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch);
}
// Inject the player wrapper

View File

@ -7,6 +7,7 @@
/* global google */
import utils from '../utils';
import i18n from '../i18n';
class Ads {
/**
@ -178,7 +179,7 @@ class Ads {
const update = () => {
const time = utils.formatTime(Math.max(this.manager.getRemainingTime(), 0));
const label = `${this.player.config.i18n.advertisement} - ${time}`;
const label = `${i18n.get('advertisement', this.player.config)} - ${time}`;
this.elements.container.setAttribute('data-badge-text', label);
};

View File

@ -4,6 +4,7 @@
import utils from './../utils';
import captions from './../captions';
import controls from './../controls';
import ui from './../ui';
const vimeo = {
@ -139,10 +140,18 @@ const vimeo = {
return speed;
},
set(input) {
player.embed.setPlaybackRate(input).then(() => {
speed = input;
utils.dispatchEvent.call(player, player.media, 'ratechange');
});
player.embed
.setPlaybackRate(input)
.then(() => {
speed = input;
utils.dispatchEvent.call(player, player.media, 'ratechange');
})
.catch(error => {
// Hide menu item (and menu if empty)
if (error.name === 'Error') {
controls.setSpeedMenu.call(player, []);
}
});
},
});

View File

@ -294,7 +294,8 @@ const youtube = {
});
// Get available speeds
player.options.speed = instance.getAvailablePlaybackRates();
const options = instance.getAvailablePlaybackRates();
controls.setSpeedMenu.call(player, options);
// Set the tabindex to avoid focus entering iframe
if (player.supported.ui) {
@ -339,8 +340,6 @@ const youtube = {
// Reset timer
clearInterval(player.timers.playing);
console.warn(event.data);
// Handle events
// -1 Unstarted
// 0 Ended

View File

@ -1,6 +1,6 @@
// ==========================================================================
// Plyr
// plyr.js v3.0.3
// plyr.js v3.0.7
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================
@ -36,6 +36,9 @@ class Plyr {
this.loading = false;
this.failed = false;
// Touch device
this.touch = support.touch;
// Set the media element
this.media = target;
@ -954,26 +957,32 @@ class Plyr {
// Is the enter fullscreen event
isEnterFullscreen = toggle.type === 'enterfullscreen';
// Whether to show controls
show = [
'mouseenter',
'mousemove',
// Events that show the controls
const showEvents = [
'touchstart',
'touchmove',
'focusin',
].includes(toggle.type);
// Delay hiding on move events
if ([
'mouseenter',
'mousemove',
'focusin',
];
// Events that delay hiding
const delayEvents = [
'touchmove',
'touchend',
].includes(toggle.type)) {
'mousemove',
];
// Whether to show controls
show = showEvents.includes(toggle.type);
// Delay hiding on move events
if (delayEvents.includes(toggle.type)) {
delay = 2000;
}
// Delay a little more for keyboard users
if (toggle.type === 'focusin') {
if (!this.touch && toggle.type === 'focusin') {
delay = 3000;
utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, true);
}
@ -1001,7 +1010,7 @@ class Plyr {
}
// Delay for hiding on touch
if (support.touch) {
if (this.touch) {
delay = 3000;
}
}

View File

@ -1,6 +1,6 @@
// ==========================================================================
// Plyr Polyfilled Build
// plyr.js v3.0.3
// plyr.js v3.0.7
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================

View File

@ -143,7 +143,7 @@ const support = {
})(),
// Touch
// Remember a device can be moust + touch enabled
// NOTE: Remember a device can be mouse + touch enabled so we check on first touch event
touch: 'ontouchstart' in document.documentElement,
// Detect transitions support

View File

@ -5,6 +5,7 @@
import utils from './utils';
import captions from './captions';
import controls from './controls';
import i18n from './i18n';
const ui = {
addStyleHook() {
@ -94,7 +95,7 @@ const ui = {
// Setup aria attribute for play and iframe title
setTitle() {
// Find the current text
let label = this.config.i18n.play;
let label = i18n.get('play', this.config);
// If there's a media title set, use that for the label
if (utils.is.string(this.config.title) && !utils.is.empty(this.config.title)) {
@ -123,7 +124,7 @@ const ui = {
// Default to media type
const title = !utils.is.empty(this.config.title) ? this.config.title : 'video';
iframe.setAttribute('title', this.config.i18n.frameTitle.replace('{title}', title));
iframe.setAttribute('title', i18n.get('frameTitle', this.config));
}
},

View File

@ -208,15 +208,6 @@ const utils = {
return `${prefix}-${Math.floor(Math.random() * 10000)}`;
},
// Determine if we're in an iframe
inFrame() {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
},
// Wrap an element
wrap(elements, wrapper) {
// Convert `elements` to an array, if necessary.
@ -319,8 +310,11 @@ const utils = {
return;
}
Object.keys(attributes).forEach(key => {
element.setAttribute(key, attributes[key]);
Object.entries(attributes).forEach(([
key,
value,
]) => {
element.setAttribute(key, value);
});
},
@ -540,7 +534,7 @@ const utils = {
},
// Toggle event listener
toggleListener(elements, event, callback, toggle, passive, capture) {
toggleListener(elements, event, callback, toggle = false, passive = true, capture = false) {
// Bail if no elemetns, event, or callback
if (utils.is.empty(elements) || utils.is.empty(event) || !utils.is.function(callback)) {
return;
@ -562,16 +556,16 @@ const utils = {
const events = event.split(' ');
// Build options
// Default to just capture boolean
let options = utils.is.boolean(capture) ? capture : false;
// Default to just the capture boolean for browsers with no passive listener support
let options = capture;
// If passive events listeners are supported
if (support.passiveListeners) {
options = {
// Whether the listener can be passive (i.e. default never prevented)
passive: utils.is.boolean(passive) ? passive : true,
passive,
// Whether the listener is a capturing listener or not
capture: utils.is.boolean(capture) ? capture : false,
capture,
};
}
@ -582,12 +576,12 @@ const utils = {
},
// Bind event handler
on(element, events, callback, passive, capture) {
on(element, events = '', callback, passive = true, capture = false) {
utils.toggleListener(element, events, callback, true, passive, capture);
},
// Unbind event handler
off(element, events, callback, passive, capture) {
off(element, events = '', callback, passive = true, capture = false) {
utils.toggleListener(element, events, callback, false, passive, capture);
},
@ -678,6 +672,44 @@ const utils = {
return `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
},
// Replace all occurances of a string in a string
replaceAll(input = '', find = '', replace = '') {
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
},
// Convert to title case
toTitleCase(input = '') {
return input.toString().replace(/\w\S*/g, text => text.charAt(0).toUpperCase() + text.substr(1).toLowerCase());
},
// Convert string to pascalCase
toPascalCase(input = '') {
let string = input.toString();
// Convert kebab case
string = utils.replaceAll(string, '-', ' ');
// Convert snake case
string = utils.replaceAll(string, '_', ' ');
// Convert to title case
string = utils.toTitleCase(string);
// Convert to pascal case
return utils.replaceAll(string, ' ', '');
},
// Convert string to pascalCase
toCamelCase(input = '') {
let string = input.toString();
// Convert to pascal case
string = utils.toPascalCase(string);
// Convert first character to lowercase
return string.charAt(0).toLowerCase() + string.slice(1);
},
// Deep extend destination object with N more objects
extend(target = {}, ...sources) {
if (!sources.length) {

View File

@ -84,7 +84,6 @@
position: absolute;
top: 50%;
transform: translateY(-50%);
transition: border-color 0.2s ease;
}
&--forward {
@ -108,7 +107,6 @@
margin-bottom: floor($plyr-control-padding / 2);
padding-left: ceil($plyr-control-padding * 4);
position: relative;
width: calc(100% - #{$horizontal-padding});
&::after {