commit
8c44425665
8
.vscode/extensions.json
vendored
8
.vscode/extensions.json
vendored
@ -2,11 +2,11 @@
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
"dbaeumer.vscode-eslint",
|
||||
"wix.vscode-import-cost",
|
||||
"esbenp.prettier-vscode",
|
||||
"shinnn.stylelint",
|
||||
"wayou.vscode-todo-highlight"
|
||||
"wayou.vscode-todo-highlight",
|
||||
"wix.vscode-import-cost",
|
||||
"stylelint.vscode-stylelint",
|
||||
"pflannery.vscode-versionlens"
|
||||
]
|
||||
}
|
||||
|
40
changelog.md
40
changelog.md
@ -1,15 +1,43 @@
|
||||
## v3.5.6
|
||||
### v3.5.7
|
||||
|
||||
- Typescript typings (thanks @ondratra)
|
||||
- `togglePlay` now also returns a `Promise` (thanks @azizhk)
|
||||
- Documentation improvements and typo fixes (thanks @ffpetrovic, @skerbis, @ayunami2000, @pjbaert, @MaxGiting, @0xflotus and @thatrobotdev)
|
||||
- Accessibility tweak for the play button (thanks @lunika)
|
||||
- Fix for ads configuration (thanks @SoftCreatR)
|
||||
- Fix handling listener return value (thanks @taion)
|
||||
- Added localisation key for PIP (picture-in-picture) (thanks @lmislm)
|
||||
- Preserve viewBox attribute in SVG sprite symbols (thanks @bseib)
|
||||
- Fix being unable to unmute autoplayed video on iOS (thanks @sumanbh)
|
||||
- Fixed Plyr container not resizing responsively (thanks @shravan2x)
|
||||
- Change vimeo demo video (thanks @thatrobotdev)
|
||||
- Fix for `Uncaught RangeError: Maximum call stack size exceeded` (thanks @laukstein)
|
||||
- Improve fullscreen experience on some devices (thanks @savroff)
|
||||
- Improvements to buffering state for embedded players (thanks @doostinharrell)
|
||||
- Prevents IE11 with resetOnEnd option set to true to play video again (thanks @Felipe K. De Boni)
|
||||
- Fix for multiple poster image downloads (use the native poster only for HTML5 videos)
|
||||
- Various presentational fixes
|
||||
- Removed logic to hide/show volume controls based on audio track detection due to it's problematic nature. If you want to hide volume control, use the `controls` option to do so.
|
||||
- Fix preview thumbnail scrubbing not working on mobile touch devices (thanks @ydylla)
|
||||
- Add download attribute to download button (thanks @Code1110)
|
||||
- Trap keyboard focus only when fullscreen (thanks @k-jensen)
|
||||
- Improvements to speed options - you can now specify all options in the UI (YouTube and Vimeo only accept 0.5-2) (thanks @ydylla)
|
||||
- Improve/fix quality change state restoring (thanks @ydylla)
|
||||
|
||||
_Note:_ This update contains CSS changes.
|
||||
|
||||
### v3.5.6
|
||||
|
||||
- Another Edge fix (thanks Nick Hawk via Slack)
|
||||
|
||||
## v3.5.5
|
||||
### v3.5.5
|
||||
|
||||
- YouTube fix for when there are other embeds on the page (thanks @aFarkas)
|
||||
- Separated demo dependencies into their own package.json
|
||||
- Fix for Edge controls flexbox issue when resizing the player (thanks Nick Hawk via Slack)
|
||||
- More aspect ratio fixes
|
||||
|
||||
## v3.5.4
|
||||
### v3.5.4
|
||||
|
||||
- Added: Set download URL via new setter
|
||||
- Improvement: The order of the `controls` option now effects the order in the DOM - i.e. you can re-order the controls - Note: this may break any custom CSS you have setup. Please see the changes in the PR to the default SASS
|
||||
@ -22,7 +50,7 @@
|
||||
- Improvement: Automatic aspect ratio for YouTube is now supported, meaning all aspect ratios are set based on media content - Note: we're now using a different API to get YouTube video metadata so you may need to adjust any CSPs you have setup
|
||||
- Fix for menu in the Shadow DOM (thanks @emielbeinema)
|
||||
|
||||
## v3.5.3
|
||||
### v3.5.3
|
||||
|
||||
- Improved the usage of the `ratio` config option; it now works as expected and for all video types. The default has not changed, it is to dynamically, where possible (except YouTube where 16:9 is used) determine the ratio from the media source so this is not a breaking change.
|
||||
- Added new `ratio` getter and setter
|
||||
@ -30,11 +58,11 @@
|
||||
- Fix: Allow absolute paths in preview thumbnails
|
||||
- Improvement: Allow optional hours and ms in VTT parser in preview thumbnails
|
||||
|
||||
## v3.5.2
|
||||
### v3.5.2
|
||||
|
||||
- Fixed issue where the preview thumbnail was present while scrubbing
|
||||
|
||||
## v3.5.1
|
||||
### v3.5.1
|
||||
|
||||
- Fixed build issues with babel and browserslist
|
||||
|
||||
|
2
demo/dist/demo.css
vendored
2
demo/dist/demo.css
vendored
File diff suppressed because one or more lines are too long
3102
demo/dist/demo.js
vendored
3102
demo/dist/demo.js
vendored
File diff suppressed because it is too large
Load Diff
4
demo/dist/demo.min.js
vendored
4
demo/dist/demo.min.js
vendored
File diff suppressed because one or more lines are too long
2
demo/dist/demo.min.js.map
vendored
2
demo/dist/demo.min.js.map
vendored
File diff suppressed because one or more lines are too long
2
demo/dist/demo.svg
vendored
2
demo/dist/demo.svg
vendored
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.6 KiB |
2
demo/dist/error.css
vendored
2
demo/dist/error.css
vendored
File diff suppressed because one or more lines are too long
@ -68,7 +68,7 @@ const sources = {
|
||||
type: 'video',
|
||||
sources: [
|
||||
{
|
||||
src: 'https://vimeo.com/76979871',
|
||||
src: 'https://vimeo.com/383514704',
|
||||
provider: 'vimeo',
|
||||
},
|
||||
],
|
||||
|
2
dist/plyr.css
vendored
2
dist/plyr.css
vendored
File diff suppressed because one or more lines are too long
266
dist/plyr.js
vendored
266
dist/plyr.js
vendored
@ -2,7 +2,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define('Plyr', factory) :
|
||||
(global = global || self, global.Plyr = factory());
|
||||
}(this, function () { 'use strict';
|
||||
}(this, (function () { 'use strict';
|
||||
|
||||
function _classCallCheck(instance, Constructor) {
|
||||
if (!(instance instanceof Constructor)) {
|
||||
@ -41,6 +41,40 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
function ownKeys(object, enumerableOnly) {
|
||||
var keys = Object.keys(object);
|
||||
|
||||
if (Object.getOwnPropertySymbols) {
|
||||
var symbols = Object.getOwnPropertySymbols(object);
|
||||
if (enumerableOnly) symbols = symbols.filter(function (sym) {
|
||||
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
||||
});
|
||||
keys.push.apply(keys, symbols);
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
function _objectSpread2(target) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var source = arguments[i] != null ? arguments[i] : {};
|
||||
|
||||
if (i % 2) {
|
||||
ownKeys(Object(source), true).forEach(function (key) {
|
||||
_defineProperty(target, key, source[key]);
|
||||
});
|
||||
} else if (Object.getOwnPropertyDescriptors) {
|
||||
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
||||
} else {
|
||||
ownKeys(Object(source)).forEach(function (key) {
|
||||
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
function _slicedToArray(arr, i) {
|
||||
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();
|
||||
}
|
||||
@ -66,6 +100,10 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
}
|
||||
|
||||
function _iterableToArrayLimit(arr, i) {
|
||||
if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) {
|
||||
return;
|
||||
}
|
||||
|
||||
var _arr = [];
|
||||
var _n = true;
|
||||
var _d = false;
|
||||
@ -115,7 +153,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
return Array.from(document.querySelectorAll(selector)).includes(this);
|
||||
}
|
||||
|
||||
var matches = match;
|
||||
var matches = match;
|
||||
return matches.call(element, selector);
|
||||
}
|
||||
|
||||
@ -576,14 +614,13 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
var browser = {
|
||||
isIE:
|
||||
/* @cc_on!@ */
|
||||
!!document.documentMode,
|
||||
!!document.documentMode,
|
||||
isEdge: window.navigator.userAgent.includes('Edge'),
|
||||
isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent),
|
||||
isIPhone: /(iPhone|iPod)/gi.test(navigator.platform),
|
||||
isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform)
|
||||
};
|
||||
|
||||
// ==========================================================================
|
||||
// https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
|
||||
// https://www.youtube.com/watch?v=NPM6172J22g
|
||||
|
||||
@ -700,7 +737,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
|
||||
var event = new CustomEvent(type, {
|
||||
bubbles: bubbles,
|
||||
detail: Object.assign({}, detail, {
|
||||
detail: _objectSpread2({}, detail, {
|
||||
plyr: this
|
||||
})
|
||||
}); // Dispatch the event
|
||||
@ -929,9 +966,6 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
// Attribute selector
|
||||
attributes[key] = value;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
return extend(existing, attributes);
|
||||
@ -983,7 +1017,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
return Array.from(document.querySelectorAll(selector)).includes(this);
|
||||
}
|
||||
|
||||
var method = match;
|
||||
var method = match;
|
||||
return method.call(element, selector);
|
||||
} // Find all elements
|
||||
|
||||
@ -1202,6 +1236,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
return {};
|
||||
}
|
||||
|
||||
var wrapper = this.elements.wrapper;
|
||||
var ratio = getAspectRatio.call(this, input);
|
||||
|
||||
var _ref = is$1.array(ratio) ? ratio : [0, 0],
|
||||
@ -1210,14 +1245,14 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
h = _ref2[1];
|
||||
|
||||
var padding = 100 / w * h;
|
||||
this.elements.wrapper.style.paddingBottom = "".concat(padding, "%"); // For Vimeo we have an extra <div> to hide the standard controls and UI
|
||||
wrapper.style.paddingBottom = "".concat(padding, "%"); // For Vimeo we have an extra <div> to hide the standard controls and UI
|
||||
|
||||
if (this.isVimeo && this.supported.ui) {
|
||||
var height = 240;
|
||||
var offset = (height - padding) / (height / 50);
|
||||
this.media.style.transform = "translateY(-".concat(offset, "%)");
|
||||
} else if (this.isHTML5) {
|
||||
this.elements.wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
|
||||
wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
|
||||
}
|
||||
|
||||
return {
|
||||
@ -1249,7 +1284,12 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
},
|
||||
// Get quality levels
|
||||
getQualityOptions: function getQualityOptions() {
|
||||
// Get sizes from <source> elements
|
||||
// Whether we're forcing all options (e.g. for streaming)
|
||||
if (this.config.quality.forced) {
|
||||
return this.config.quality.options;
|
||||
} // Get sizes from <source> elements
|
||||
|
||||
|
||||
return html5.getSources.call(this).map(function (source) {
|
||||
return Number(source.getAttribute('size'));
|
||||
}).filter(Boolean);
|
||||
@ -1277,37 +1317,46 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
return source && Number(source.getAttribute('size'));
|
||||
},
|
||||
set: function set(input) {
|
||||
// Get sources
|
||||
var sources = html5.getSources.call(player); // Get first match for requested size
|
||||
// If we're using an an external handler...
|
||||
if (player.config.quality.forced && is$1.function(player.config.quality.onChange)) {
|
||||
player.config.quality.onChange(input);
|
||||
} else {
|
||||
// Get sources
|
||||
var sources = html5.getSources.call(player); // Get first match for requested size
|
||||
|
||||
var source = sources.find(function (s) {
|
||||
return Number(s.getAttribute('size')) === input;
|
||||
}); // No matching source found
|
||||
var source = sources.find(function (s) {
|
||||
return Number(s.getAttribute('size')) === input;
|
||||
}); // No matching source found
|
||||
|
||||
if (!source) {
|
||||
return;
|
||||
} // Get current state
|
||||
if (!source) {
|
||||
return;
|
||||
} // Get current state
|
||||
|
||||
|
||||
var _player$media = player.media,
|
||||
currentTime = _player$media.currentTime,
|
||||
paused = _player$media.paused,
|
||||
preload = _player$media.preload,
|
||||
readyState = _player$media.readyState; // Set new source
|
||||
var _player$media = player.media,
|
||||
currentTime = _player$media.currentTime,
|
||||
paused = _player$media.paused,
|
||||
preload = _player$media.preload,
|
||||
readyState = _player$media.readyState; // Set new source
|
||||
|
||||
player.media.src = source.getAttribute('src'); // Prevent loading if preload="none" and the current source isn't loaded (#1044)
|
||||
player.media.src = source.getAttribute('src'); // Prevent loading if preload="none" and the current source isn't loaded (#1044)
|
||||
|
||||
if (preload !== 'none' || readyState) {
|
||||
// Restore time
|
||||
player.once('loadedmetadata', function () {
|
||||
player.currentTime = currentTime; // Resume playing
|
||||
if (preload !== 'none' || readyState) {
|
||||
// Restore time
|
||||
player.once('loadedmetadata', function () {
|
||||
if (player.currentTime === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!paused) {
|
||||
player.play();
|
||||
}
|
||||
}); // Load new source
|
||||
player.currentTime = currentTime; // Resume playing
|
||||
|
||||
player.media.load();
|
||||
if (!paused) {
|
||||
player.play();
|
||||
}
|
||||
}); // Load new source
|
||||
|
||||
player.media.load();
|
||||
}
|
||||
} // Trigger change event
|
||||
|
||||
|
||||
@ -1678,7 +1727,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
|
||||
// Bail if the value isn't a number
|
||||
if (!is$1.number(time)) {
|
||||
return formatTime(null, displayHours, inverted);
|
||||
return formatTime(undefined, displayHours, inverted);
|
||||
} // Format time component to add leading zero
|
||||
|
||||
|
||||
@ -1787,9 +1836,11 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
createLabel: function createLabel(key) {
|
||||
var attr = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
var text = i18n.get(key, this.config);
|
||||
var attributes = Object.assign({}, attr, {
|
||||
|
||||
var attributes = _objectSpread2({}, attr, {
|
||||
class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' ')
|
||||
});
|
||||
|
||||
return createElement('span', attributes, text);
|
||||
},
|
||||
// Create a badge
|
||||
@ -1999,7 +2050,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
var _this2 = this;
|
||||
|
||||
// Navigate through menus via arrow keys and space
|
||||
on(menuItem, 'keydown keyup', function (event) {
|
||||
on.call(this, menuItem, 'keydown keyup', function (event) {
|
||||
// We only care about space and ⬆️ ⬇️️ ➡️
|
||||
if (![32, 38, 39, 40].includes(event.which)) {
|
||||
return;
|
||||
@ -2041,7 +2092,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
}, false); // Enter will fire a `click` event but we still need to manage focus
|
||||
// So we bind to keyup which fires after and set focus here
|
||||
|
||||
on(menuItem, 'keyup', function (event) {
|
||||
on.call(this, menuItem, 'keyup', function (event) {
|
||||
if (event.which !== 13) {
|
||||
return;
|
||||
}
|
||||
@ -2118,9 +2169,6 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
case 'speed':
|
||||
_this3.speed = parseFloat(value);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
controls.showMenuPanel.call(_this3, 'home', is$1.keyboardEvent(event));
|
||||
@ -2229,9 +2277,6 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
case 'progress':
|
||||
setProgress(this.elements.display.buffer, this.buffered * 100);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -2885,9 +2930,11 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
if (control === 'mute') {
|
||||
volume.appendChild(createButton.call(_this10, 'mute'));
|
||||
} // Volume range control
|
||||
// Ignored on iOS as it's handled globally
|
||||
// https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html
|
||||
|
||||
|
||||
if (control === 'volume') {
|
||||
if (control === 'volume' && !browser.isIos) {
|
||||
// Set the attributes
|
||||
var attributes = {
|
||||
max: 1,
|
||||
@ -2946,7 +2993,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
|
||||
bindMenuItemShortcuts.call(_this10, menuItem, type); // Show menu on click
|
||||
|
||||
on(menuItem, 'click', function () {
|
||||
on.call(_this10, menuItem, 'click', function () {
|
||||
showMenuPanel.call(_this10, type, false);
|
||||
});
|
||||
var flex = createElement('span', null, i18n.get(type, _this10.config));
|
||||
@ -2977,7 +3024,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
class: _this10.config.classNames.hidden
|
||||
}, i18n.get('menuBack', _this10.config))); // Go back via keyboard
|
||||
|
||||
on(pane, 'keydown', function (event) {
|
||||
on.call(_this10, pane, 'keydown', function (event) {
|
||||
// We only care about <-
|
||||
if (event.which !== 37) {
|
||||
return;
|
||||
@ -2990,7 +3037,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
showMenuPanel.call(_this10, 'home', true);
|
||||
}, false); // Go back via button click
|
||||
|
||||
on(backButton, 'click', function () {
|
||||
on.call(_this10, backButton, 'click', function () {
|
||||
showMenuPanel.call(_this10, 'home', false);
|
||||
}); // Add to pane
|
||||
|
||||
@ -3646,13 +3693,15 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
// Sprite (for icons)
|
||||
loadSprite: true,
|
||||
iconPrefix: 'plyr',
|
||||
iconUrl: 'https://cdn.plyr.io/3.5.6/plyr.svg',
|
||||
iconUrl: 'https://cdn.plyr.io/3.5.7-beta.0/plyr.svg',
|
||||
// Blank video (used to prevent errors on source change)
|
||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||
// Quality default
|
||||
quality: {
|
||||
default: 576,
|
||||
options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240]
|
||||
options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240],
|
||||
forced: false,
|
||||
onChange: null
|
||||
},
|
||||
// Set loops
|
||||
loop: {
|
||||
@ -3729,6 +3778,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
frameTitle: 'Player for {title}',
|
||||
captions: 'Captions',
|
||||
settings: 'Settings',
|
||||
pip: 'PIP',
|
||||
menuBack: 'Go back to previous menu',
|
||||
speed: 'Speed',
|
||||
normal: 'Normal',
|
||||
@ -4168,7 +4218,9 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
} else if (!Fullscreen.native || this.forceFallback) {
|
||||
toggleFallback.call(this, true);
|
||||
} else if (!this.prefix) {
|
||||
this.target.requestFullscreen();
|
||||
this.target.requestFullscreen({
|
||||
navigationUI: "hide"
|
||||
});
|
||||
} else if (!is$1.empty(this.prefix)) {
|
||||
this.target["".concat(this.prefix, "Request").concat(this.property)]();
|
||||
}
|
||||
@ -4475,6 +4527,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
Object.assign(target, {
|
||||
pressed: _this3.playing
|
||||
});
|
||||
target.setAttribute('aria-label', i18n.get(_this3.playing ? 'pause' : 'play', _this3.config));
|
||||
}); // Only update controls on non timeupdate events
|
||||
|
||||
if (is$1.event(event) && event.type === 'timeupdate') {
|
||||
@ -4655,19 +4708,6 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
// L key
|
||||
player.loop = !player.loop;
|
||||
break;
|
||||
|
||||
/* case 73:
|
||||
this.setLoop('start');
|
||||
break;
|
||||
case 76:
|
||||
this.setLoop();
|
||||
break;
|
||||
case 79:
|
||||
this.setLoop('end');
|
||||
break; */
|
||||
|
||||
default:
|
||||
break;
|
||||
} // Escape is handle natively when in full screen
|
||||
// So we only need to worry about non native
|
||||
|
||||
@ -4884,19 +4924,15 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
|
||||
on.call(player, player.media, 'durationchange loadeddata loadedmetadata', function (event) {
|
||||
return controls.durationUpdate.call(player, event);
|
||||
}); // Check for audio tracks on load
|
||||
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
|
||||
|
||||
on.call(player, player.media, 'canplay loadeddata', function () {
|
||||
toggleHidden(elements.volume, !player.hasAudio);
|
||||
toggleHidden(elements.buttons.mute, !player.hasAudio);
|
||||
}); // Handle the media finishing
|
||||
|
||||
on.call(player, player.media, 'ended', function () {
|
||||
// Show poster on end
|
||||
if (player.isHTML5 && player.isVideo && player.config.resetOnEnd) {
|
||||
// Restart
|
||||
player.restart();
|
||||
player.restart(); // Call pause otherwise IE11 will start playing the video again
|
||||
|
||||
player.pause();
|
||||
}
|
||||
}); // Check for buffer progress
|
||||
|
||||
@ -5009,7 +5045,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
} // Only call default handler if not prevented in custom handler
|
||||
|
||||
|
||||
if (returned && is$1.function(defaultHandler)) {
|
||||
if (returned !== false && is$1.function(defaultHandler)) {
|
||||
defaultHandler.call(player, event);
|
||||
}
|
||||
} // Trigger custom and default handlers
|
||||
@ -5397,12 +5433,13 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
async = args.async,
|
||||
maxTries = (args.numRetries || 0) + 1,
|
||||
beforeCallbackFn = args.before || devnull,
|
||||
pathname = path.replace(/[\?|#].*$/, ''),
|
||||
pathStripped = path.replace(/^(css|img)!/, ''),
|
||||
isLegacyIECss,
|
||||
e;
|
||||
numTries = numTries || 0;
|
||||
|
||||
if (/(^css!|\.css$)/.test(path)) {
|
||||
if (/(^css!|\.css$)/.test(pathname)) {
|
||||
// css
|
||||
e = doc.createElement('link');
|
||||
e.rel = 'stylesheet';
|
||||
@ -5415,7 +5452,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
e.rel = 'preload';
|
||||
e.as = 'style';
|
||||
}
|
||||
} else if (/(^img!|\.(png|gif|jpg|svg)$)/.test(path)) {
|
||||
} else if (/(^img!|\.(png|gif|jpg|svg|webp)$)/.test(pathname)) {
|
||||
// image
|
||||
e = doc.createElement('img');
|
||||
e.src = pathStripped;
|
||||
@ -5888,6 +5925,12 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
frame.setAttribute('tabindex', -1);
|
||||
}
|
||||
});
|
||||
player.embed.on('bufferstart', function () {
|
||||
triggerEvent.call(player, player.media, 'waiting');
|
||||
});
|
||||
player.embed.on('bufferend', function () {
|
||||
triggerEvent.call(player, player.media, 'playing');
|
||||
});
|
||||
player.embed.on('play', function () {
|
||||
assurePlaybackState.call(player, true);
|
||||
triggerEvent.call(player, player.media, 'playing');
|
||||
@ -6321,7 +6364,9 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
assurePlaybackState$1.call(player, false);
|
||||
break;
|
||||
|
||||
default:
|
||||
case 3:
|
||||
// Trigger waiting event to add loading classes to container as the video buffers.
|
||||
triggerEvent.call(player, player.media, 'waiting');
|
||||
break;
|
||||
}
|
||||
|
||||
@ -6363,10 +6408,12 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
|
||||
wrap(this.media, this.elements.wrapper); // Faux poster container
|
||||
|
||||
this.elements.poster = createElement('div', {
|
||||
class: this.config.classNames.poster
|
||||
});
|
||||
this.elements.wrapper.appendChild(this.elements.poster);
|
||||
if (this.isEmbed) {
|
||||
this.elements.poster = createElement('div', {
|
||||
class: this.config.classNames.poster
|
||||
});
|
||||
this.elements.wrapper.appendChild(this.elements.poster);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isHTML5) {
|
||||
@ -6736,9 +6783,6 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
@ -7010,7 +7054,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
cb: Date.now(),
|
||||
AV_WIDTH: 640,
|
||||
AV_HEIGHT: 480,
|
||||
AV_CDIM2: this.publisherId
|
||||
AV_CDIM2: config.publisherId
|
||||
};
|
||||
var base = 'https://go.aniview.com/api/adserver6/vast/';
|
||||
return "".concat(base, "?").concat(buildUrlParams(params));
|
||||
@ -7075,6 +7119,21 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
*/
|
||||
|
||||
|
||||
var fitRatio = function fitRatio(ratio, outer) {
|
||||
var targetRatio = outer.width / outer.height;
|
||||
var result = {};
|
||||
|
||||
if (ratio > targetRatio) {
|
||||
result.width = outer.width;
|
||||
result.height = 1 / ratio * outer.width;
|
||||
} else {
|
||||
result.height = outer.height;
|
||||
result.width = ratio * outer.height;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
var PreviewThumbnails =
|
||||
/*#__PURE__*/
|
||||
function () {
|
||||
@ -7619,9 +7678,15 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
}, {
|
||||
key: "setScrubbingContainerSize",
|
||||
value: function setScrubbingContainerSize() {
|
||||
this.elements.scrubbing.container.style.width = "".concat(this.player.media.clientWidth, "px"); // Can't use media.clientHeight - html5 video goes big and does black bars above and below
|
||||
var _fitRatio = fitRatio(this.thumbAspectRatio, {
|
||||
width: this.player.media.clientWidth,
|
||||
height: this.player.media.clientHeight
|
||||
}),
|
||||
width = _fitRatio.width,
|
||||
height = _fitRatio.height;
|
||||
|
||||
this.elements.scrubbing.container.style.height = "".concat(this.player.media.clientWidth / this.thumbAspectRatio, "px");
|
||||
this.elements.scrubbing.container.style.width = "".concat(width, "px");
|
||||
this.elements.scrubbing.container.style.height = "".concat(height, "px");
|
||||
} // Sprites need to be offset to the correct location
|
||||
|
||||
}, {
|
||||
@ -7634,9 +7699,9 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
|
||||
var multiplier = this.thumbContainerHeight / frame.h; // eslint-disable-next-line no-param-reassign
|
||||
|
||||
previewImage.style.height = "".concat(Math.floor(previewImage.naturalHeight * multiplier), "px"); // eslint-disable-next-line no-param-reassign
|
||||
previewImage.style.height = "".concat(previewImage.naturalHeight * multiplier, "px"); // eslint-disable-next-line no-param-reassign
|
||||
|
||||
previewImage.style.width = "".concat(Math.floor(previewImage.naturalWidth * multiplier), "px"); // eslint-disable-next-line no-param-reassign
|
||||
previewImage.style.width = "".concat(previewImage.naturalWidth * multiplier, "px"); // eslint-disable-next-line no-param-reassign
|
||||
|
||||
previewImage.style.left = "-".concat(frame.x * multiplier, "px"); // eslint-disable-next-line no-param-reassign
|
||||
|
||||
@ -7674,8 +7739,13 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
key: "thumbContainerHeight",
|
||||
get: function get() {
|
||||
if (this.mouseDown) {
|
||||
// Can't use media.clientHeight - HTML5 video goes big and does black bars above and below
|
||||
return Math.floor(this.player.media.clientWidth / this.thumbAspectRatio);
|
||||
var _fitRatio2 = fitRatio(this.thumbAspectRatio, {
|
||||
width: this.player.media.clientWidth,
|
||||
height: this.player.media.clientHeight
|
||||
}),
|
||||
height = _fitRatio2.height;
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
return Math.floor(this.player.media.clientWidth / this.thumbAspectRatio / 4);
|
||||
@ -8159,10 +8229,10 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
key: "pause",
|
||||
value: function pause() {
|
||||
if (!this.playing || !is$1.function(this.media.pause)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
this.media.pause();
|
||||
return this.media.pause();
|
||||
}
|
||||
/**
|
||||
* Get playing state
|
||||
@ -8180,10 +8250,10 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
var toggle = is$1.boolean(input) ? input : !this.playing;
|
||||
|
||||
if (toggle) {
|
||||
this.play();
|
||||
} else {
|
||||
this.pause();
|
||||
return this.play();
|
||||
}
|
||||
|
||||
return this.pause();
|
||||
}
|
||||
/**
|
||||
* Stop playback
|
||||
@ -8216,7 +8286,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
}, {
|
||||
key: "rewind",
|
||||
value: function rewind(seekTime) {
|
||||
this.currentTime = this.currentTime - (is$1.number(seekTime) ? seekTime : this.config.seekTime);
|
||||
this.currentTime -= is$1.number(seekTime) ? seekTime : this.config.seekTime;
|
||||
}
|
||||
/**
|
||||
* Fast forward
|
||||
@ -8226,7 +8296,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
}, {
|
||||
key: "forward",
|
||||
value: function forward(seekTime) {
|
||||
this.currentTime = this.currentTime + (is$1.number(seekTime) ? seekTime : this.config.seekTime);
|
||||
this.currentTime += is$1.number(seekTime) ? seekTime : this.config.seekTime;
|
||||
}
|
||||
/**
|
||||
* Seek to a time
|
||||
@ -9128,4 +9198,4 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
|
||||
return Plyr;
|
||||
|
||||
}));
|
||||
})));
|
||||
|
4
dist/plyr.min.js
vendored
4
dist/plyr.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.min.js.map
vendored
2
dist/plyr.min.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/plyr.min.mjs
vendored
4
dist/plyr.min.mjs
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.min.mjs.map
vendored
2
dist/plyr.min.mjs.map
vendored
File diff suppressed because one or more lines are too long
262
dist/plyr.mjs
vendored
262
dist/plyr.mjs
vendored
@ -35,6 +35,40 @@ function _defineProperty(obj, key, value) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
function ownKeys(object, enumerableOnly) {
|
||||
var keys = Object.keys(object);
|
||||
|
||||
if (Object.getOwnPropertySymbols) {
|
||||
var symbols = Object.getOwnPropertySymbols(object);
|
||||
if (enumerableOnly) symbols = symbols.filter(function (sym) {
|
||||
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
||||
});
|
||||
keys.push.apply(keys, symbols);
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
function _objectSpread2(target) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var source = arguments[i] != null ? arguments[i] : {};
|
||||
|
||||
if (i % 2) {
|
||||
ownKeys(Object(source), true).forEach(function (key) {
|
||||
_defineProperty(target, key, source[key]);
|
||||
});
|
||||
} else if (Object.getOwnPropertyDescriptors) {
|
||||
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
||||
} else {
|
||||
ownKeys(Object(source)).forEach(function (key) {
|
||||
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
function _slicedToArray(arr, i) {
|
||||
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();
|
||||
}
|
||||
@ -60,6 +94,10 @@ function _iterableToArray(iter) {
|
||||
}
|
||||
|
||||
function _iterableToArrayLimit(arr, i) {
|
||||
if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) {
|
||||
return;
|
||||
}
|
||||
|
||||
var _arr = [];
|
||||
var _n = true;
|
||||
var _d = false;
|
||||
@ -109,7 +147,7 @@ function matches(element, selector) {
|
||||
return Array.from(document.querySelectorAll(selector)).includes(this);
|
||||
}
|
||||
|
||||
var matches = match;
|
||||
var matches = match;
|
||||
return matches.call(element, selector);
|
||||
}
|
||||
|
||||
@ -570,14 +608,13 @@ function repaint(element, delay) {
|
||||
var browser = {
|
||||
isIE:
|
||||
/* @cc_on!@ */
|
||||
!!document.documentMode,
|
||||
!!document.documentMode,
|
||||
isEdge: window.navigator.userAgent.includes('Edge'),
|
||||
isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent),
|
||||
isIPhone: /(iPhone|iPod)/gi.test(navigator.platform),
|
||||
isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform)
|
||||
};
|
||||
|
||||
// ==========================================================================
|
||||
// https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
|
||||
// https://www.youtube.com/watch?v=NPM6172J22g
|
||||
|
||||
@ -694,7 +731,7 @@ function triggerEvent(element) {
|
||||
|
||||
var event = new CustomEvent(type, {
|
||||
bubbles: bubbles,
|
||||
detail: Object.assign({}, detail, {
|
||||
detail: _objectSpread2({}, detail, {
|
||||
plyr: this
|
||||
})
|
||||
}); // Dispatch the event
|
||||
@ -923,9 +960,6 @@ function getAttributesFromSelector(sel, existingAttributes) {
|
||||
// Attribute selector
|
||||
attributes[key] = value;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
return extend(existing, attributes);
|
||||
@ -977,7 +1011,7 @@ function matches$1(element, selector) {
|
||||
return Array.from(document.querySelectorAll(selector)).includes(this);
|
||||
}
|
||||
|
||||
var method = match;
|
||||
var method = match;
|
||||
return method.call(element, selector);
|
||||
} // Find all elements
|
||||
|
||||
@ -1196,6 +1230,7 @@ function setAspectRatio(input) {
|
||||
return {};
|
||||
}
|
||||
|
||||
var wrapper = this.elements.wrapper;
|
||||
var ratio = getAspectRatio.call(this, input);
|
||||
|
||||
var _ref = is$1.array(ratio) ? ratio : [0, 0],
|
||||
@ -1204,14 +1239,14 @@ function setAspectRatio(input) {
|
||||
h = _ref2[1];
|
||||
|
||||
var padding = 100 / w * h;
|
||||
this.elements.wrapper.style.paddingBottom = "".concat(padding, "%"); // For Vimeo we have an extra <div> to hide the standard controls and UI
|
||||
wrapper.style.paddingBottom = "".concat(padding, "%"); // For Vimeo we have an extra <div> to hide the standard controls and UI
|
||||
|
||||
if (this.isVimeo && this.supported.ui) {
|
||||
var height = 240;
|
||||
var offset = (height - padding) / (height / 50);
|
||||
this.media.style.transform = "translateY(-".concat(offset, "%)");
|
||||
} else if (this.isHTML5) {
|
||||
this.elements.wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
|
||||
wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
|
||||
}
|
||||
|
||||
return {
|
||||
@ -1243,7 +1278,12 @@ var html5 = {
|
||||
},
|
||||
// Get quality levels
|
||||
getQualityOptions: function getQualityOptions() {
|
||||
// Get sizes from <source> elements
|
||||
// Whether we're forcing all options (e.g. for streaming)
|
||||
if (this.config.quality.forced) {
|
||||
return this.config.quality.options;
|
||||
} // Get sizes from <source> elements
|
||||
|
||||
|
||||
return html5.getSources.call(this).map(function (source) {
|
||||
return Number(source.getAttribute('size'));
|
||||
}).filter(Boolean);
|
||||
@ -1271,37 +1311,46 @@ var html5 = {
|
||||
return source && Number(source.getAttribute('size'));
|
||||
},
|
||||
set: function set(input) {
|
||||
// Get sources
|
||||
var sources = html5.getSources.call(player); // Get first match for requested size
|
||||
// If we're using an an external handler...
|
||||
if (player.config.quality.forced && is$1.function(player.config.quality.onChange)) {
|
||||
player.config.quality.onChange(input);
|
||||
} else {
|
||||
// Get sources
|
||||
var sources = html5.getSources.call(player); // Get first match for requested size
|
||||
|
||||
var source = sources.find(function (s) {
|
||||
return Number(s.getAttribute('size')) === input;
|
||||
}); // No matching source found
|
||||
var source = sources.find(function (s) {
|
||||
return Number(s.getAttribute('size')) === input;
|
||||
}); // No matching source found
|
||||
|
||||
if (!source) {
|
||||
return;
|
||||
} // Get current state
|
||||
if (!source) {
|
||||
return;
|
||||
} // Get current state
|
||||
|
||||
|
||||
var _player$media = player.media,
|
||||
currentTime = _player$media.currentTime,
|
||||
paused = _player$media.paused,
|
||||
preload = _player$media.preload,
|
||||
readyState = _player$media.readyState; // Set new source
|
||||
var _player$media = player.media,
|
||||
currentTime = _player$media.currentTime,
|
||||
paused = _player$media.paused,
|
||||
preload = _player$media.preload,
|
||||
readyState = _player$media.readyState; // Set new source
|
||||
|
||||
player.media.src = source.getAttribute('src'); // Prevent loading if preload="none" and the current source isn't loaded (#1044)
|
||||
player.media.src = source.getAttribute('src'); // Prevent loading if preload="none" and the current source isn't loaded (#1044)
|
||||
|
||||
if (preload !== 'none' || readyState) {
|
||||
// Restore time
|
||||
player.once('loadedmetadata', function () {
|
||||
player.currentTime = currentTime; // Resume playing
|
||||
if (preload !== 'none' || readyState) {
|
||||
// Restore time
|
||||
player.once('loadedmetadata', function () {
|
||||
if (player.currentTime === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!paused) {
|
||||
player.play();
|
||||
}
|
||||
}); // Load new source
|
||||
player.currentTime = currentTime; // Resume playing
|
||||
|
||||
player.media.load();
|
||||
if (!paused) {
|
||||
player.play();
|
||||
}
|
||||
}); // Load new source
|
||||
|
||||
player.media.load();
|
||||
}
|
||||
} // Trigger change event
|
||||
|
||||
|
||||
@ -1672,7 +1721,7 @@ function formatTime() {
|
||||
|
||||
// Bail if the value isn't a number
|
||||
if (!is$1.number(time)) {
|
||||
return formatTime(null, displayHours, inverted);
|
||||
return formatTime(undefined, displayHours, inverted);
|
||||
} // Format time component to add leading zero
|
||||
|
||||
|
||||
@ -1781,9 +1830,11 @@ var controls = {
|
||||
createLabel: function createLabel(key) {
|
||||
var attr = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
var text = i18n.get(key, this.config);
|
||||
var attributes = Object.assign({}, attr, {
|
||||
|
||||
var attributes = _objectSpread2({}, attr, {
|
||||
class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' ')
|
||||
});
|
||||
|
||||
return createElement('span', attributes, text);
|
||||
},
|
||||
// Create a badge
|
||||
@ -1993,7 +2044,7 @@ var controls = {
|
||||
var _this2 = this;
|
||||
|
||||
// Navigate through menus via arrow keys and space
|
||||
on(menuItem, 'keydown keyup', function (event) {
|
||||
on.call(this, menuItem, 'keydown keyup', function (event) {
|
||||
// We only care about space and ⬆️ ⬇️️ ➡️
|
||||
if (![32, 38, 39, 40].includes(event.which)) {
|
||||
return;
|
||||
@ -2035,7 +2086,7 @@ var controls = {
|
||||
}, false); // Enter will fire a `click` event but we still need to manage focus
|
||||
// So we bind to keyup which fires after and set focus here
|
||||
|
||||
on(menuItem, 'keyup', function (event) {
|
||||
on.call(this, menuItem, 'keyup', function (event) {
|
||||
if (event.which !== 13) {
|
||||
return;
|
||||
}
|
||||
@ -2112,9 +2163,6 @@ var controls = {
|
||||
case 'speed':
|
||||
_this3.speed = parseFloat(value);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
controls.showMenuPanel.call(_this3, 'home', is$1.keyboardEvent(event));
|
||||
@ -2223,9 +2271,6 @@ var controls = {
|
||||
case 'progress':
|
||||
setProgress(this.elements.display.buffer, this.buffered * 100);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -2879,9 +2924,11 @@ var controls = {
|
||||
if (control === 'mute') {
|
||||
volume.appendChild(createButton.call(_this10, 'mute'));
|
||||
} // Volume range control
|
||||
// Ignored on iOS as it's handled globally
|
||||
// https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html
|
||||
|
||||
|
||||
if (control === 'volume') {
|
||||
if (control === 'volume' && !browser.isIos) {
|
||||
// Set the attributes
|
||||
var attributes = {
|
||||
max: 1,
|
||||
@ -2940,7 +2987,7 @@ var controls = {
|
||||
|
||||
bindMenuItemShortcuts.call(_this10, menuItem, type); // Show menu on click
|
||||
|
||||
on(menuItem, 'click', function () {
|
||||
on.call(_this10, menuItem, 'click', function () {
|
||||
showMenuPanel.call(_this10, type, false);
|
||||
});
|
||||
var flex = createElement('span', null, i18n.get(type, _this10.config));
|
||||
@ -2971,7 +3018,7 @@ var controls = {
|
||||
class: _this10.config.classNames.hidden
|
||||
}, i18n.get('menuBack', _this10.config))); // Go back via keyboard
|
||||
|
||||
on(pane, 'keydown', function (event) {
|
||||
on.call(_this10, pane, 'keydown', function (event) {
|
||||
// We only care about <-
|
||||
if (event.which !== 37) {
|
||||
return;
|
||||
@ -2984,7 +3031,7 @@ var controls = {
|
||||
showMenuPanel.call(_this10, 'home', true);
|
||||
}, false); // Go back via button click
|
||||
|
||||
on(backButton, 'click', function () {
|
||||
on.call(_this10, backButton, 'click', function () {
|
||||
showMenuPanel.call(_this10, 'home', false);
|
||||
}); // Add to pane
|
||||
|
||||
@ -3640,13 +3687,15 @@ var defaults$1 = {
|
||||
// Sprite (for icons)
|
||||
loadSprite: true,
|
||||
iconPrefix: 'plyr',
|
||||
iconUrl: 'https://cdn.plyr.io/3.5.6/plyr.svg',
|
||||
iconUrl: 'https://cdn.plyr.io/3.5.7-beta.0/plyr.svg',
|
||||
// Blank video (used to prevent errors on source change)
|
||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||
// Quality default
|
||||
quality: {
|
||||
default: 576,
|
||||
options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240]
|
||||
options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240],
|
||||
forced: false,
|
||||
onChange: null
|
||||
},
|
||||
// Set loops
|
||||
loop: {
|
||||
@ -3723,6 +3772,7 @@ var defaults$1 = {
|
||||
frameTitle: 'Player for {title}',
|
||||
captions: 'Captions',
|
||||
settings: 'Settings',
|
||||
pip: 'PIP',
|
||||
menuBack: 'Go back to previous menu',
|
||||
speed: 'Speed',
|
||||
normal: 'Normal',
|
||||
@ -4162,7 +4212,9 @@ function () {
|
||||
} else if (!Fullscreen.native || this.forceFallback) {
|
||||
toggleFallback.call(this, true);
|
||||
} else if (!this.prefix) {
|
||||
this.target.requestFullscreen();
|
||||
this.target.requestFullscreen({
|
||||
navigationUI: "hide"
|
||||
});
|
||||
} else if (!is$1.empty(this.prefix)) {
|
||||
this.target["".concat(this.prefix, "Request").concat(this.property)]();
|
||||
}
|
||||
@ -4469,6 +4521,7 @@ var ui = {
|
||||
Object.assign(target, {
|
||||
pressed: _this3.playing
|
||||
});
|
||||
target.setAttribute('aria-label', i18n.get(_this3.playing ? 'pause' : 'play', _this3.config));
|
||||
}); // Only update controls on non timeupdate events
|
||||
|
||||
if (is$1.event(event) && event.type === 'timeupdate') {
|
||||
@ -4649,19 +4702,6 @@ function () {
|
||||
// L key
|
||||
player.loop = !player.loop;
|
||||
break;
|
||||
|
||||
/* case 73:
|
||||
this.setLoop('start');
|
||||
break;
|
||||
case 76:
|
||||
this.setLoop();
|
||||
break;
|
||||
case 79:
|
||||
this.setLoop('end');
|
||||
break; */
|
||||
|
||||
default:
|
||||
break;
|
||||
} // Escape is handle natively when in full screen
|
||||
// So we only need to worry about non native
|
||||
|
||||
@ -4878,19 +4918,15 @@ function () {
|
||||
|
||||
on.call(player, player.media, 'durationchange loadeddata loadedmetadata', function (event) {
|
||||
return controls.durationUpdate.call(player, event);
|
||||
}); // Check for audio tracks on load
|
||||
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
|
||||
|
||||
on.call(player, player.media, 'canplay loadeddata', function () {
|
||||
toggleHidden(elements.volume, !player.hasAudio);
|
||||
toggleHidden(elements.buttons.mute, !player.hasAudio);
|
||||
}); // Handle the media finishing
|
||||
|
||||
on.call(player, player.media, 'ended', function () {
|
||||
// Show poster on end
|
||||
if (player.isHTML5 && player.isVideo && player.config.resetOnEnd) {
|
||||
// Restart
|
||||
player.restart();
|
||||
player.restart(); // Call pause otherwise IE11 will start playing the video again
|
||||
|
||||
player.pause();
|
||||
}
|
||||
}); // Check for buffer progress
|
||||
|
||||
@ -5003,7 +5039,7 @@ function () {
|
||||
} // Only call default handler if not prevented in custom handler
|
||||
|
||||
|
||||
if (returned && is$1.function(defaultHandler)) {
|
||||
if (returned !== false && is$1.function(defaultHandler)) {
|
||||
defaultHandler.call(player, event);
|
||||
}
|
||||
} // Trigger custom and default handlers
|
||||
@ -5391,12 +5427,13 @@ var loadjs_umd = createCommonjsModule(function (module, exports) {
|
||||
async = args.async,
|
||||
maxTries = (args.numRetries || 0) + 1,
|
||||
beforeCallbackFn = args.before || devnull,
|
||||
pathname = path.replace(/[\?|#].*$/, ''),
|
||||
pathStripped = path.replace(/^(css|img)!/, ''),
|
||||
isLegacyIECss,
|
||||
e;
|
||||
numTries = numTries || 0;
|
||||
|
||||
if (/(^css!|\.css$)/.test(path)) {
|
||||
if (/(^css!|\.css$)/.test(pathname)) {
|
||||
// css
|
||||
e = doc.createElement('link');
|
||||
e.rel = 'stylesheet';
|
||||
@ -5409,7 +5446,7 @@ var loadjs_umd = createCommonjsModule(function (module, exports) {
|
||||
e.rel = 'preload';
|
||||
e.as = 'style';
|
||||
}
|
||||
} else if (/(^img!|\.(png|gif|jpg|svg)$)/.test(path)) {
|
||||
} else if (/(^img!|\.(png|gif|jpg|svg|webp)$)/.test(pathname)) {
|
||||
// image
|
||||
e = doc.createElement('img');
|
||||
e.src = pathStripped;
|
||||
@ -5882,6 +5919,12 @@ var vimeo = {
|
||||
frame.setAttribute('tabindex', -1);
|
||||
}
|
||||
});
|
||||
player.embed.on('bufferstart', function () {
|
||||
triggerEvent.call(player, player.media, 'waiting');
|
||||
});
|
||||
player.embed.on('bufferend', function () {
|
||||
triggerEvent.call(player, player.media, 'playing');
|
||||
});
|
||||
player.embed.on('play', function () {
|
||||
assurePlaybackState.call(player, true);
|
||||
triggerEvent.call(player, player.media, 'playing');
|
||||
@ -6315,7 +6358,9 @@ var youtube = {
|
||||
assurePlaybackState$1.call(player, false);
|
||||
break;
|
||||
|
||||
default:
|
||||
case 3:
|
||||
// Trigger waiting event to add loading classes to container as the video buffers.
|
||||
triggerEvent.call(player, player.media, 'waiting');
|
||||
break;
|
||||
}
|
||||
|
||||
@ -6357,10 +6402,12 @@ var media = {
|
||||
|
||||
wrap(this.media, this.elements.wrapper); // Faux poster container
|
||||
|
||||
this.elements.poster = createElement('div', {
|
||||
class: this.config.classNames.poster
|
||||
});
|
||||
this.elements.wrapper.appendChild(this.elements.poster);
|
||||
if (this.isEmbed) {
|
||||
this.elements.poster = createElement('div', {
|
||||
class: this.config.classNames.poster
|
||||
});
|
||||
this.elements.wrapper.appendChild(this.elements.poster);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isHTML5) {
|
||||
@ -6730,9 +6777,6 @@ function () {
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
@ -7004,7 +7048,7 @@ function () {
|
||||
cb: Date.now(),
|
||||
AV_WIDTH: 640,
|
||||
AV_HEIGHT: 480,
|
||||
AV_CDIM2: this.publisherId
|
||||
AV_CDIM2: config.publisherId
|
||||
};
|
||||
var base = 'https://go.aniview.com/api/adserver6/vast/';
|
||||
return "".concat(base, "?").concat(buildUrlParams(params));
|
||||
@ -7069,6 +7113,21 @@ var parseVtt = function parseVtt(vttDataString) {
|
||||
*/
|
||||
|
||||
|
||||
var fitRatio = function fitRatio(ratio, outer) {
|
||||
var targetRatio = outer.width / outer.height;
|
||||
var result = {};
|
||||
|
||||
if (ratio > targetRatio) {
|
||||
result.width = outer.width;
|
||||
result.height = 1 / ratio * outer.width;
|
||||
} else {
|
||||
result.height = outer.height;
|
||||
result.width = ratio * outer.height;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
var PreviewThumbnails =
|
||||
/*#__PURE__*/
|
||||
function () {
|
||||
@ -7613,9 +7672,15 @@ function () {
|
||||
}, {
|
||||
key: "setScrubbingContainerSize",
|
||||
value: function setScrubbingContainerSize() {
|
||||
this.elements.scrubbing.container.style.width = "".concat(this.player.media.clientWidth, "px"); // Can't use media.clientHeight - html5 video goes big and does black bars above and below
|
||||
var _fitRatio = fitRatio(this.thumbAspectRatio, {
|
||||
width: this.player.media.clientWidth,
|
||||
height: this.player.media.clientHeight
|
||||
}),
|
||||
width = _fitRatio.width,
|
||||
height = _fitRatio.height;
|
||||
|
||||
this.elements.scrubbing.container.style.height = "".concat(this.player.media.clientWidth / this.thumbAspectRatio, "px");
|
||||
this.elements.scrubbing.container.style.width = "".concat(width, "px");
|
||||
this.elements.scrubbing.container.style.height = "".concat(height, "px");
|
||||
} // Sprites need to be offset to the correct location
|
||||
|
||||
}, {
|
||||
@ -7628,9 +7693,9 @@ function () {
|
||||
|
||||
var multiplier = this.thumbContainerHeight / frame.h; // eslint-disable-next-line no-param-reassign
|
||||
|
||||
previewImage.style.height = "".concat(Math.floor(previewImage.naturalHeight * multiplier), "px"); // eslint-disable-next-line no-param-reassign
|
||||
previewImage.style.height = "".concat(previewImage.naturalHeight * multiplier, "px"); // eslint-disable-next-line no-param-reassign
|
||||
|
||||
previewImage.style.width = "".concat(Math.floor(previewImage.naturalWidth * multiplier), "px"); // eslint-disable-next-line no-param-reassign
|
||||
previewImage.style.width = "".concat(previewImage.naturalWidth * multiplier, "px"); // eslint-disable-next-line no-param-reassign
|
||||
|
||||
previewImage.style.left = "-".concat(frame.x * multiplier, "px"); // eslint-disable-next-line no-param-reassign
|
||||
|
||||
@ -7668,8 +7733,13 @@ function () {
|
||||
key: "thumbContainerHeight",
|
||||
get: function get() {
|
||||
if (this.mouseDown) {
|
||||
// Can't use media.clientHeight - HTML5 video goes big and does black bars above and below
|
||||
return Math.floor(this.player.media.clientWidth / this.thumbAspectRatio);
|
||||
var _fitRatio2 = fitRatio(this.thumbAspectRatio, {
|
||||
width: this.player.media.clientWidth,
|
||||
height: this.player.media.clientHeight
|
||||
}),
|
||||
height = _fitRatio2.height;
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
return Math.floor(this.player.media.clientWidth / this.thumbAspectRatio / 4);
|
||||
@ -8153,10 +8223,10 @@ function () {
|
||||
key: "pause",
|
||||
value: function pause() {
|
||||
if (!this.playing || !is$1.function(this.media.pause)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
this.media.pause();
|
||||
return this.media.pause();
|
||||
}
|
||||
/**
|
||||
* Get playing state
|
||||
@ -8174,10 +8244,10 @@ function () {
|
||||
var toggle = is$1.boolean(input) ? input : !this.playing;
|
||||
|
||||
if (toggle) {
|
||||
this.play();
|
||||
} else {
|
||||
this.pause();
|
||||
return this.play();
|
||||
}
|
||||
|
||||
return this.pause();
|
||||
}
|
||||
/**
|
||||
* Stop playback
|
||||
@ -8210,7 +8280,7 @@ function () {
|
||||
}, {
|
||||
key: "rewind",
|
||||
value: function rewind(seekTime) {
|
||||
this.currentTime = this.currentTime - (is$1.number(seekTime) ? seekTime : this.config.seekTime);
|
||||
this.currentTime -= is$1.number(seekTime) ? seekTime : this.config.seekTime;
|
||||
}
|
||||
/**
|
||||
* Fast forward
|
||||
@ -8220,7 +8290,7 @@ function () {
|
||||
}, {
|
||||
key: "forward",
|
||||
value: function forward(seekTime) {
|
||||
this.currentTime = this.currentTime + (is$1.number(seekTime) ? seekTime : this.config.seekTime);
|
||||
this.currentTime += is$1.number(seekTime) ? seekTime : this.config.seekTime;
|
||||
}
|
||||
/**
|
||||
* Seek to a time
|
||||
|
1400
dist/plyr.polyfilled.js
vendored
1400
dist/plyr.polyfilled.js
vendored
File diff suppressed because it is too large
Load Diff
4
dist/plyr.polyfilled.min.js
vendored
4
dist/plyr.polyfilled.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.polyfilled.min.js.map
vendored
2
dist/plyr.polyfilled.min.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/plyr.polyfilled.min.mjs
vendored
4
dist/plyr.polyfilled.min.mjs
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.polyfilled.min.mjs.map
vendored
2
dist/plyr.polyfilled.min.mjs.map
vendored
File diff suppressed because one or more lines are too long
1396
dist/plyr.polyfilled.mjs
vendored
1396
dist/plyr.polyfilled.mjs
vendored
File diff suppressed because it is too large
Load Diff
2
dist/plyr.svg
vendored
2
dist/plyr.svg
vendored
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.6 KiB |
39
gulpfile.js
39
gulpfile.js
@ -1,7 +1,6 @@
|
||||
// ==========================================================================
|
||||
// Gulp build script
|
||||
// ==========================================================================
|
||||
/* global require, __dirname */
|
||||
/* eslint no-console: "off" */
|
||||
|
||||
const path = require('path');
|
||||
@ -41,6 +40,7 @@ const plumber = require('gulp-plumber');
|
||||
const size = require('gulp-size');
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
const through = require('through2');
|
||||
const browserSync = require('browser-sync').create();
|
||||
// ------------------------------------
|
||||
// Deployment
|
||||
// ------------------------------------
|
||||
@ -222,7 +222,13 @@ Object.entries(build.sprite).forEach(([filename, entry]) => {
|
||||
gulp
|
||||
.src(src)
|
||||
.pipe(plumber())
|
||||
.pipe(imagemin())
|
||||
.pipe(
|
||||
imagemin([
|
||||
imagemin.svgo({
|
||||
plugins: [{ removeViewBox: false }],
|
||||
}),
|
||||
]),
|
||||
)
|
||||
.pipe(svgstore())
|
||||
.pipe(rename({ basename: path.parse(filename).name }))
|
||||
.pipe(size(sizeOptions))
|
||||
@ -245,11 +251,23 @@ gulp.task('watch', () => {
|
||||
gulp.watch(paths.demo.src.sass, gulp.parallel(...tasks.css));
|
||||
});
|
||||
|
||||
// Serve via browser sync
|
||||
gulp.task('serve', () =>
|
||||
browserSync.init({
|
||||
server: {
|
||||
baseDir: paths.demo.root,
|
||||
},
|
||||
notify: false,
|
||||
watch: true,
|
||||
ghostMode: false,
|
||||
}),
|
||||
);
|
||||
|
||||
// Build distribution
|
||||
gulp.task('build', gulp.series(tasks.clean, gulp.parallel(...tasks.js, ...tasks.css, ...tasks.sprite)));
|
||||
|
||||
// Default gulp task
|
||||
gulp.task('default', gulp.series('build', 'watch'));
|
||||
gulp.task('default', gulp.series('build', gulp.parallel('serve', 'watch')));
|
||||
|
||||
// Publish a version to CDN and demo
|
||||
// --------------------------------------------
|
||||
@ -276,7 +294,7 @@ const options = {
|
||||
},
|
||||
},
|
||||
demo: {
|
||||
uploadPath: branch.current === branch.beta ? 'beta' : null,
|
||||
uploadPath: branch.current === branch.beta ? '/beta' : null,
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
|
||||
},
|
||||
@ -331,7 +349,10 @@ gulp.task('version', done => {
|
||||
const files = ['plyr.js', 'plyr.polyfilled.js', 'config/defaults.js'];
|
||||
|
||||
return gulp
|
||||
.src(files.map(file => path.join(__dirname, `src/js/${file}`)), { base: '.' })
|
||||
.src(
|
||||
files.map(file => path.join(__dirname, `src/js/${file}`)),
|
||||
{ base: '.' },
|
||||
)
|
||||
.pipe(replace(semver, `v${version}`))
|
||||
.pipe(replace(cdnpath, `${domain}/${version}/`))
|
||||
.pipe(gulp.dest('./'));
|
||||
@ -440,6 +461,14 @@ gulp.task('demo', done => {
|
||||
return gulp
|
||||
.src(pages)
|
||||
.pipe(replace(localPath, versionPath))
|
||||
.pipe(
|
||||
rename(p => {
|
||||
if (options.demo.uploadPath) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
p.dirname += options.demo.uploadPath;
|
||||
}
|
||||
}),
|
||||
)
|
||||
.pipe(publisher.publish(options.demo.headers))
|
||||
.pipe(publish.reporter());
|
||||
});
|
||||
|
70
package.json
70
package.json
@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "plyr",
|
||||
"version": "3.5.6",
|
||||
"version": "3.5.7",
|
||||
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
||||
"homepage": "https://plyr.io",
|
||||
"author": "Sam Potts <sam@potts.es>",
|
||||
"main": "dist/plyr.js",
|
||||
"types": "src/js/plyr.d.ts",
|
||||
"module": "dist/plyr.min.mjs",
|
||||
"jsnext:main": "dist/plyr.min.mjs",
|
||||
"browser": "dist/plyr.min.js",
|
||||
@ -36,61 +37,62 @@
|
||||
"deploy": "yarn lint && gulp deploy"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ansi-colors": "^4.0.1",
|
||||
"aws-sdk": "^2.478.0",
|
||||
"@babel/core": "^7.4.5",
|
||||
"@babel/preset-env": "^7.4.5",
|
||||
"babel-eslint": "^10.0.2",
|
||||
"del": "^4.1.1",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-airbnb-base": "^13.1.0",
|
||||
"eslint-config-prettier": "^5.0.0",
|
||||
"eslint-plugin-import": "^2.17.3",
|
||||
"eslint-plugin-simple-import-sort": "^4.0.0",
|
||||
"ansi-colors": "^4.1.1",
|
||||
"aws-sdk": "^2.614.0",
|
||||
"@babel/core": "^7.8.4",
|
||||
"@babel/preset-env": "^7.8.4",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"browser-sync": "^2.26.7",
|
||||
"del": "^5.1.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb-base": "^14.0.0",
|
||||
"eslint-config-prettier": "^6.10.0",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-simple-import-sort": "^5.0.1",
|
||||
"fancy-log": "^1.3.3",
|
||||
"fastly-purge": "^1.0.1",
|
||||
"git-branch": "^2.0.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-autoprefixer": "^6.1.0",
|
||||
"gulp-awspublish": "^4.0.0",
|
||||
"gulp-autoprefixer": "^7.0.1",
|
||||
"gulp-awspublish": "^4.1.1",
|
||||
"gulp-better-rollup": "^4.0.1",
|
||||
"gulp-clean-css": "^4.2.0",
|
||||
"gulp-filter": "^6.0.0",
|
||||
"gulp-header": "^2.0.7",
|
||||
"gulp-imagemin": "^6.0.0",
|
||||
"gulp-header": "^2.0.9",
|
||||
"gulp-imagemin": "^7.1.0",
|
||||
"gulp-open": "^3.0.1",
|
||||
"gulp-plumber": "^1.2.1",
|
||||
"gulp-postcss": "^8.0.0",
|
||||
"gulp-rename": "^1.4.0",
|
||||
"gulp-rename": "^2.0.0",
|
||||
"gulp-replace": "^1.0.0",
|
||||
"gulp-sass": "^4.0.2",
|
||||
"gulp-size": "^3.0.0",
|
||||
"gulp-sourcemaps": "^2.6.5",
|
||||
"gulp-svgstore": "^7.0.1",
|
||||
"gulp-terser": "^1.2.0",
|
||||
"postcss-custom-properties": "^9.0.1",
|
||||
"prettier-eslint": "^9.0.0",
|
||||
"postcss-custom-properties": "^9.0.2",
|
||||
"prettier-eslint": "^9.0.1",
|
||||
"prettier-stylelint": "^0.4.2",
|
||||
"remark-cli": "^6.0.1",
|
||||
"remark-validate-links": "^8.0.3",
|
||||
"rollup": "^1.15.6",
|
||||
"rollup-plugin-babel": "^4.3.2",
|
||||
"rollup-plugin-commonjs": "^10.0.0",
|
||||
"rollup-plugin-node-resolve": "^5.0.3",
|
||||
"stylelint": "^10.1.0",
|
||||
"stylelint-config-prettier": "^5.2.0",
|
||||
"stylelint-config-recommended": "^2.2.0",
|
||||
"stylelint-config-sass-guidelines": "^6.0.0",
|
||||
"stylelint-order": "^3.0.0",
|
||||
"stylelint-scss": "^3.8.0",
|
||||
"remark-cli": "^7.0.1",
|
||||
"remark-validate-links": "^9.2.0",
|
||||
"rollup": "^1.31.0",
|
||||
"rollup-plugin-babel": "^4.3.3",
|
||||
"rollup-plugin-commonjs": "^10.1.0",
|
||||
"rollup-plugin-node-resolve": "^5.2.0",
|
||||
"stylelint": "^13.1.0",
|
||||
"stylelint-config-prettier": "^8.0.1",
|
||||
"stylelint-config-recommended": "^3.0.0",
|
||||
"stylelint-config-sass-guidelines": "^7.0.0",
|
||||
"stylelint-order": "^4.0.0",
|
||||
"stylelint-scss": "^3.14.2",
|
||||
"stylelint-selector-bem-pattern": "^2.1.0",
|
||||
"through2": "^3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.1.4",
|
||||
"core-js": "^3.6.4",
|
||||
"custom-event-polyfill": "^1.0.7",
|
||||
"loadjs": "^3.6.1",
|
||||
"loadjs": "^4.2.0",
|
||||
"rangetouch": "^2.0.0",
|
||||
"url-polyfill": "^1.1.5"
|
||||
"url-polyfill": "^1.1.8"
|
||||
}
|
||||
}
|
||||
|
@ -9,22 +9,29 @@
|
||||
"**/node_modules": true,
|
||||
"**/dist": true
|
||||
},
|
||||
|
||||
// Linting
|
||||
"stylelint.enable": true,
|
||||
"css.validate": false,
|
||||
"scss.validate": false,
|
||||
"javascript.validate.enable": false,
|
||||
|
||||
// Prettier
|
||||
"prettier.eslintIntegration": true,
|
||||
"prettier.stylelintIntegration": true,
|
||||
|
||||
// Formatting
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.tabSize": 4,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.formatOnSave": true,
|
||||
|
||||
// Trim on save
|
||||
"files.trimTrailingWhitespace": true
|
||||
"files.trimTrailingWhitespace": true,
|
||||
|
||||
// Special file associations
|
||||
"files.associations": {
|
||||
".eslintrc": "jsonc"
|
||||
},
|
||||
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
56
readme.md
56
readme.md
@ -116,7 +116,7 @@ import Plyr from 'plyr';
|
||||
const player = new Plyr('#player');
|
||||
```
|
||||
|
||||
Alertnatively you can include the `plyr.js` script before the closing `</body>` tag and then in your JS create a new instance of Plyr as below.
|
||||
Alternatively you can include the `plyr.js` script before the closing `</body>` tag and then in your JS create a new instance of Plyr as below.
|
||||
|
||||
```html
|
||||
<script src="path/to/plyr.js"></script>
|
||||
@ -130,13 +130,13 @@ See [initialising](#initialising) for more information on advanced setups.
|
||||
You can use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript. There's 2 versions; one with and one without [polyfills](#polyfills). My recommendation would be to manage polyfills seperately as part of your application but to make life easier you can use the polyfilled build.
|
||||
|
||||
```html
|
||||
<script src="https://cdn.plyr.io/3.5.6/plyr.js"></script>
|
||||
<script src="https://cdn.plyr.io/3.5.7-beta.0/plyr.js"></script>
|
||||
```
|
||||
|
||||
...or...
|
||||
|
||||
```html
|
||||
<script src="https://cdn.plyr.io/3.5.6/plyr.polyfilled.js"></script>
|
||||
<script src="https://cdn.plyr.io/3.5.7-beta.0/plyr.polyfilled.js"></script>
|
||||
```
|
||||
|
||||
## CSS
|
||||
@ -150,13 +150,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.5.6/plyr.css" />
|
||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.5.7-beta.0/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.5.6/plyr.svg`.
|
||||
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.5.7-beta.0/plyr.svg`.
|
||||
|
||||
# Ads
|
||||
|
||||
@ -276,7 +276,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
||||
| `enabled` | Boolean | `true` | Completely disable Plyr. This would allow you to do a User Agent check or similar to programmatically enable or disable Plyr for a certain UA. Example below. |
|
||||
| `debug` | Boolean | `false` | Display debugging information in the console |
|
||||
| `controls` | Array, Function or Element | `['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen']` | If a function is passed, it is assumed your method will return either an element or HTML string for the controls. Three arguments will be passed to your function; `id` (the unique id for the player), `seektime` (the seektime step in seconds), and `title` (the media title). See [controls.md](controls.md) for more info on how the html needs to be structured. |
|
||||
| `settings` | Array | `['captions', 'quality', 'speed', 'loop']` | If the default controls are used, you can specify which settings to show in the menu |
|
||||
| `settings` | Array | `['captions', 'quality', 'speed', 'loop']` | If the default controls are used, you can specify which settings to show in the menu |
|
||||
| `i18n` | Object | See [defaults.js](/src/js/config/defaults.js) | Used for internationalization (i18n) of the text within the UI. |
|
||||
| `loadSprite` | Boolean | `true` | Load the SVG sprite specified as the `iconUrl` option (if a URL). If `false`, it is assumed you are handling sprite loading yourself. |
|
||||
| `iconUrl` | String | `null` | Specify a URL or path to the SVG sprite. See the [SVG section](#svg) for more info. |
|
||||
@ -302,7 +302,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
||||
| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution (`true`/`false`/`'force'`). `iosNative`: whether to use native iOS fullscreen when entering fullscreen (no custom controls) |
|
||||
| `ratio` | String | `null` | Force an aspect ratio for all videos. The format is `'w:h'` - e.g. `'16:9'` or `'4:3'`. If this is not specified then the default for HTML5 and Vimeo is to use the native resolution of the video. As dimensions are not available from YouTube via SDK, 16:9 is forced as a sensible default. |
|
||||
| `storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use. |
|
||||
| `speed` | Object | `{ selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] }` | `selected`: The default speed for playback. `options`: Options to display in the menu. Most browsers will refuse to play slower than 0.5. |
|
||||
| `speed` | Object | `{ selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] }` | `selected`: The default speed for playback. `options`: The speed options to display in the UI. YouTube and Vimeo will ignore any options outside of the 0.5-2 range, so options outside of this range will be hidden automatically. |
|
||||
| `quality` | Object | `{ default: 576, options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240] }` | `default` is the default quality level (if it exists in your sources). `options` are the options to display. This is used to filter the available sources. |
|
||||
| `loop` | Object | `{ active: false }` | `active`: Whether to loop the current video. If the `loop` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true This is an object to support future functionality. |
|
||||
| `ads` | Object | `{ enabled: false, publisherId: '' }` | `enabled`: Whether to enable advertisements. `publisherId`: Your unique [vi.ai](https://vi.ai/publisher-video-monetization/?aid=plyrio) publisher ID. |
|
||||
@ -629,16 +629,16 @@ Fullscreen in Plyr is supported by all browsers that [currently support it](http
|
||||
|
||||
Plyr supports the last 2 versions of most _modern_ browsers.
|
||||
|
||||
| Browser | Supported |
|
||||
| ------------- | ------------- |
|
||||
| Safari | ✓ |
|
||||
| Mobile Safari | ✓¹ |
|
||||
| Firefox | ✓ |
|
||||
| Chrome | ✓ |
|
||||
| Opera | ✓ |
|
||||
| Edge | ✓ |
|
||||
| IE11 | ✓³ |
|
||||
| IE10 | ✓²³ |
|
||||
| Browser | Supported |
|
||||
| ------------- | --------------- |
|
||||
| Safari | ✓ |
|
||||
| Mobile Safari | ✓¹ |
|
||||
| Firefox | ✓ |
|
||||
| Chrome | ✓ |
|
||||
| Opera | ✓ |
|
||||
| Edge | ✓ |
|
||||
| IE11 | ✓³ |
|
||||
| IE10 | ✓<sup>2,3</sup> |
|
||||
|
||||
1. Mobile Safari on the iPhone forces the native player for `<video>` unless the `playsinline` attribute is present. Volume controls are also disabled as they are handled device wide.
|
||||
2. Native player used (no support for `<progress>` or `<input type="range">`) but the API is supported. No native fullscreen support, fallback can be used (see [options](#options)).
|
||||
@ -678,15 +678,15 @@ If a User Agent is disabled but supports `<video>` and `<audio>` natively, it wi
|
||||
|
||||
Some awesome folks have made plugins for CMSs and Components for JavaScript frameworks:
|
||||
|
||||
| Type | Maintainer | Link |
|
||||
| --------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| WordPress | Brandon Lavigne ([@drrobotnik](https://github.com/drrobotnik)) | [https://wordpress.org/plugins/plyr/](https://wordpress.org/plugins/plyr/) |
|
||||
| Angular | Simon Bobrov ([@smnbbrv](https://github.com/smnbbrv)) | [https://github.com/smnbbrv/ngx-plyr](https://github.com/smnbbrv/ngx-plyr) |
|
||||
| React | Jose Miguel Bejarano ([@xDae](https://github.com/xDae)) | [https://github.com/xDae/react-plyr](https://github.com/xDae/react-plyr) |
|
||||
| Vue | Gabe Dunn ([@redxtech](https://github.com/redxtech)) | [https://github.com/redxtech/vue-plyr](https://github.com/redxtech/vue-plyr) |
|
||||
| Neos | Jon Uhlmann ([@jonnitto](https://github.com/jonnitto)) | [https://packagist.org/packages/jonnitto/plyr](https://packagist.org/packages/jonnitto/plyr) |
|
||||
| Kirby | Dominik Pschenitschni ([@dpschen](https://github.com/dpschen)) | [https://github.com/dpschen/kirby-plyrtag](https://github.com/dpschen/kirby-plyrtag) |
|
||||
| REDAXO | FriendsOfRedaxo / skerbis ([@skerbis](https://friendsofredaxo.github.io)) | [https://github.com/FriendsOfREDAXO/plyr](https://github.com/FriendsOfREDAXO/plyr) |
|
||||
| Type | Maintainer | Link |
|
||||
| --------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| WordPress | Brandon Lavigne ([@drrobotnik](https://github.com/drrobotnik)) | [https://wordpress.org/plugins/plyr/](https://wordpress.org/plugins/plyr/) |
|
||||
| Angular | Simon Bobrov ([@smnbbrv](https://github.com/smnbbrv)) | [https://github.com/smnbbrv/ngx-plyr](https://github.com/smnbbrv/ngx-plyr) |
|
||||
| React | Chintan Prajapati ([@chintan9](https://github.com/chintan9)) | [https://github.com/chintan9/plyr-react](https://github.com/chintan9/plyr-react) |
|
||||
| Vue | Gabe Dunn ([@redxtech](https://github.com/redxtech)) | [https://github.com/redxtech/vue-plyr](https://github.com/redxtech/vue-plyr) |
|
||||
| Neos | Jon Uhlmann ([@jonnitto](https://github.com/jonnitto)) | [https://packagist.org/packages/jonnitto/plyr](https://packagist.org/packages/jonnitto/plyr) |
|
||||
| Kirby | Dominik Pschenitschni ([@dpschen](https://github.com/dpschen)) | [https://github.com/dpschen/kirby-plyrtag](https://github.com/dpschen/kirby-plyrtag) |
|
||||
| REDAXO | FriendsOfRedaxo / skerbis ([@skerbis](https://friendsofredaxo.github.io)) | [https://github.com/FriendsOfREDAXO/plyr](https://github.com/FriendsOfREDAXO/plyr) |
|
||||
|
||||
# Issues
|
||||
|
||||
@ -735,9 +735,7 @@ If you want to be added to the list, open a pull request. It'd be awesome to see
|
||||
|
||||
# Useful links and credits
|
||||
|
||||
Credit to the PayPal HTML5 Video player from which Plyr's caption functionality was originally ported from:
|
||||
|
||||
- [PayPal's Accessible HTML5 Video Player](https://github.com/paypal/accessible-html5-video-player)
|
||||
- [PayPal's Accessible HTML5 Video Player (which Plyr was originally ported from)](https://github.com/paypal/accessible-html5-video-player)
|
||||
- [An awesome guide for Plyr in Japanese!](http://syncer.jp/how-to-use-plyr-io) by [@arayutw](https://twitter.com/arayutw)
|
||||
|
||||
# Thanks
|
||||
|
@ -61,7 +61,7 @@ const defaults = {
|
||||
// Sprite (for icons)
|
||||
loadSprite: true,
|
||||
iconPrefix: 'plyr',
|
||||
iconUrl: 'https://cdn.plyr.io/3.5.6/plyr.svg',
|
||||
iconUrl: 'https://cdn.plyr.io/3.5.7-beta.0/plyr.svg',
|
||||
|
||||
// Blank video (used to prevent errors on source change)
|
||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||
@ -69,7 +69,10 @@ const defaults = {
|
||||
// Quality default
|
||||
quality: {
|
||||
default: 576,
|
||||
// The options to display in the UI, if available for the source media
|
||||
options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240],
|
||||
forced: false,
|
||||
onChange: null,
|
||||
},
|
||||
|
||||
// Set loops
|
||||
@ -82,7 +85,8 @@ const defaults = {
|
||||
// Speed default and options to display
|
||||
speed: {
|
||||
selected: 1,
|
||||
options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2],
|
||||
// The options to display in the UI, if available for the source media (e.g. Vimeo and YouTube only support 0.5x-4x)
|
||||
options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 4],
|
||||
},
|
||||
|
||||
// Keyboard shortcut settings
|
||||
@ -164,6 +168,7 @@ const defaults = {
|
||||
frameTitle: 'Player for {title}',
|
||||
captions: 'Captions',
|
||||
settings: 'Settings',
|
||||
pip: 'PIP',
|
||||
menuBack: 'Go back to previous menu',
|
||||
speed: 'Speed',
|
||||
normal: 'Normal',
|
||||
|
44
src/js/controls.js
vendored
44
src/js/controls.js
vendored
@ -9,7 +9,7 @@ import captions from './captions';
|
||||
import html5 from './html5';
|
||||
import support from './support';
|
||||
import { repaint, transitionEndEvent } from './utils/animation';
|
||||
import { dedupe } from './utils/arrays';
|
||||
import { dedupe, fillRange } from './utils/arrays';
|
||||
import browser from './utils/browser';
|
||||
import {
|
||||
createElement,
|
||||
@ -139,10 +139,7 @@ const controls = {
|
||||
// Create hidden text label
|
||||
createLabel(key, attr = {}) {
|
||||
const text = i18n.get(key, this.config);
|
||||
|
||||
const attributes = Object.assign({}, attr, {
|
||||
class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' '),
|
||||
});
|
||||
const attributes = { ...attr, class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' ') };
|
||||
|
||||
return createElement('span', attributes, text);
|
||||
},
|
||||
@ -402,7 +399,8 @@ const controls = {
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1220143
|
||||
bindMenuItemShortcuts(menuItem, type) {
|
||||
// Navigate through menus via arrow keys and space
|
||||
on(
|
||||
on.call(
|
||||
this,
|
||||
menuItem,
|
||||
'keydown keyup',
|
||||
event => {
|
||||
@ -452,7 +450,7 @@ const controls = {
|
||||
|
||||
// Enter will fire a `click` event but we still need to manage focus
|
||||
// So we bind to keyup which fires after and set focus here
|
||||
on(menuItem, 'keyup', event => {
|
||||
on.call(this, menuItem, 'keyup', event => {
|
||||
if (event.which !== 13) {
|
||||
return;
|
||||
}
|
||||
@ -1046,7 +1044,7 @@ const controls = {
|
||||
},
|
||||
|
||||
// Set a list of available captions languages
|
||||
setSpeedMenu(options) {
|
||||
setSpeedMenu() {
|
||||
// Menu required
|
||||
if (!is.element(this.elements.settings.panels.speed)) {
|
||||
return;
|
||||
@ -1055,16 +1053,14 @@ const controls = {
|
||||
const type = 'speed';
|
||||
const list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
|
||||
|
||||
// Set the speed options
|
||||
if (is.array(options)) {
|
||||
this.options.speed = options;
|
||||
} else if (this.isHTML5 || this.isVimeo) {
|
||||
this.options.speed = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
|
||||
// Determine options to display
|
||||
// Vimeo and YouTube limit to 0.5x-2x
|
||||
if (this.isVimeo || this.isYouTube) {
|
||||
this.options.speed = fillRange(0.5, 2, 0.25).filter(s => this.config.speed.options.includes(s));
|
||||
} else {
|
||||
this.options.speed = this.config.speed.options;
|
||||
}
|
||||
|
||||
// Set options if passed and filter based on config
|
||||
this.options.speed = this.options.speed.filter(speed => this.config.speed.options.includes(speed));
|
||||
|
||||
// Toggle the pane and tab
|
||||
const toggle = !is.empty(this.options.speed) && this.options.speed.length > 1;
|
||||
controls.toggleMenuButton.call(this, type, toggle);
|
||||
@ -1380,7 +1376,9 @@ const controls = {
|
||||
}
|
||||
|
||||
// Volume range control
|
||||
if (control === 'volume') {
|
||||
// Ignored on iOS as it's handled globally
|
||||
// https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html
|
||||
if (control === 'volume' && !browser.isIos) {
|
||||
// Set the attributes
|
||||
const attributes = {
|
||||
max: 1,
|
||||
@ -1463,7 +1461,7 @@ const controls = {
|
||||
bindMenuItemShortcuts.call(this, menuItem, type);
|
||||
|
||||
// Show menu on click
|
||||
on(menuItem, 'click', () => {
|
||||
on.call(this, menuItem, 'click', () => {
|
||||
showMenuPanel.call(this, type, false);
|
||||
});
|
||||
|
||||
@ -1515,7 +1513,8 @@ const controls = {
|
||||
);
|
||||
|
||||
// Go back via keyboard
|
||||
on(
|
||||
on.call(
|
||||
this,
|
||||
pane,
|
||||
'keydown',
|
||||
event => {
|
||||
@ -1535,7 +1534,7 @@ const controls = {
|
||||
);
|
||||
|
||||
// Go back via button click
|
||||
on(backButton, 'click', () => {
|
||||
on.call(this, backButton, 'click', () => {
|
||||
showMenuPanel.call(this, 'home', false);
|
||||
});
|
||||
|
||||
@ -1581,6 +1580,11 @@ const controls = {
|
||||
target: '_blank',
|
||||
});
|
||||
|
||||
// Set download attribute for HTML5 only
|
||||
if (this.isHTML5) {
|
||||
attributes.download = '';
|
||||
}
|
||||
|
||||
const { download } = this.config.urls;
|
||||
|
||||
if (!is.url(download) && this.isEmbed) {
|
||||
|
@ -5,79 +5,10 @@
|
||||
// ==========================================================================
|
||||
|
||||
import browser from './utils/browser';
|
||||
import { hasClass, toggleClass, trapFocus } from './utils/elements';
|
||||
import { getElements, hasClass, toggleClass } from './utils/elements';
|
||||
import { on, triggerEvent } from './utils/events';
|
||||
import is from './utils/is';
|
||||
|
||||
function onChange() {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update toggle button
|
||||
const button = this.player.elements.buttons.fullscreen;
|
||||
if (is.element(button)) {
|
||||
button.pressed = this.active;
|
||||
}
|
||||
|
||||
// Trigger an event
|
||||
triggerEvent.call(this.player, this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);
|
||||
|
||||
// Trap focus in container
|
||||
if (!browser.isIos) {
|
||||
trapFocus.call(this.player, this.target, this.active);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleFallback(toggle = false) {
|
||||
// Store or restore scroll position
|
||||
if (toggle) {
|
||||
this.scrollPosition = {
|
||||
x: window.scrollX || 0,
|
||||
y: window.scrollY || 0,
|
||||
};
|
||||
} else {
|
||||
window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
|
||||
}
|
||||
|
||||
// Toggle scroll
|
||||
document.body.style.overflow = toggle ? 'hidden' : '';
|
||||
|
||||
// Toggle class hook
|
||||
toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
|
||||
|
||||
// Force full viewport on iPhone X+
|
||||
if (browser.isIos) {
|
||||
let viewport = document.head.querySelector('meta[name="viewport"]');
|
||||
const property = 'viewport-fit=cover';
|
||||
|
||||
// Inject the viewport meta if required
|
||||
if (!viewport) {
|
||||
viewport = document.createElement('meta');
|
||||
viewport.setAttribute('name', 'viewport');
|
||||
}
|
||||
|
||||
// Check if the property already exists
|
||||
const hasProperty = is.string(viewport.content) && viewport.content.includes(property);
|
||||
|
||||
if (toggle) {
|
||||
this.cleanupViewport = !hasProperty;
|
||||
|
||||
if (!hasProperty) {
|
||||
viewport.content += `,${property}`;
|
||||
}
|
||||
} else if (this.cleanupViewport) {
|
||||
viewport.content = viewport.content
|
||||
.split(',')
|
||||
.filter(part => part.trim() !== property)
|
||||
.join(',');
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle button and fire events
|
||||
onChange.call(this);
|
||||
}
|
||||
|
||||
class Fullscreen {
|
||||
constructor(player) {
|
||||
// Keep reference to parent
|
||||
@ -101,7 +32,7 @@ class Fullscreen {
|
||||
this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`,
|
||||
() => {
|
||||
// TODO: Filter for target??
|
||||
onChange.call(this);
|
||||
this.onChange();
|
||||
},
|
||||
);
|
||||
|
||||
@ -115,6 +46,9 @@ class Fullscreen {
|
||||
this.toggle();
|
||||
});
|
||||
|
||||
// Tap focus when in fullscreen
|
||||
on.call(this, this.player.elements.container, 'keydown', event => this.trapFocus(event));
|
||||
|
||||
// Update the UI
|
||||
this.update();
|
||||
}
|
||||
@ -194,6 +128,97 @@ class Fullscreen {
|
||||
: this.player.elements.container;
|
||||
}
|
||||
|
||||
onChange() {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update toggle button
|
||||
const button = this.player.elements.buttons.fullscreen;
|
||||
if (is.element(button)) {
|
||||
button.pressed = this.active;
|
||||
}
|
||||
|
||||
// Trigger an event
|
||||
triggerEvent.call(this.player, this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);
|
||||
}
|
||||
|
||||
toggleFallback(toggle = false) {
|
||||
// Store or restore scroll position
|
||||
if (toggle) {
|
||||
this.scrollPosition = {
|
||||
x: window.scrollX || 0,
|
||||
y: window.scrollY || 0,
|
||||
};
|
||||
} else {
|
||||
window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
|
||||
}
|
||||
|
||||
// Toggle scroll
|
||||
document.body.style.overflow = toggle ? 'hidden' : '';
|
||||
|
||||
// Toggle class hook
|
||||
toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
|
||||
|
||||
// Force full viewport on iPhone X+
|
||||
if (browser.isIos) {
|
||||
let viewport = document.head.querySelector('meta[name="viewport"]');
|
||||
const property = 'viewport-fit=cover';
|
||||
|
||||
// Inject the viewport meta if required
|
||||
if (!viewport) {
|
||||
viewport = document.createElement('meta');
|
||||
viewport.setAttribute('name', 'viewport');
|
||||
}
|
||||
|
||||
// Check if the property already exists
|
||||
const hasProperty = is.string(viewport.content) && viewport.content.includes(property);
|
||||
|
||||
if (toggle) {
|
||||
this.cleanupViewport = !hasProperty;
|
||||
|
||||
if (!hasProperty) {
|
||||
viewport.content += `,${property}`;
|
||||
}
|
||||
} else if (this.cleanupViewport) {
|
||||
viewport.content = viewport.content
|
||||
.split(',')
|
||||
.filter(part => part.trim() !== property)
|
||||
.join(',');
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle button and fire events
|
||||
this.onChange();
|
||||
}
|
||||
|
||||
// Trap focus inside container
|
||||
trapFocus(event) {
|
||||
// Bail if iOS, not active, not the tab key
|
||||
if (browser.isIos || !this.active || event.key !== 'Tab' || event.keyCode !== 9) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current focused element
|
||||
const focused = document.activeElement;
|
||||
const focusable = getElements.call(
|
||||
this.player,
|
||||
'a[href], button:not(:disabled), input:not(:disabled), [tabindex]',
|
||||
);
|
||||
const [first] = focusable;
|
||||
const last = focusable[focusable.length - 1];
|
||||
|
||||
if (focused === last && !event.shiftKey) {
|
||||
// Move focus to first element that can be tabbed if Shift isn't used
|
||||
first.focus();
|
||||
event.preventDefault();
|
||||
} else if (focused === first && event.shiftKey) {
|
||||
// Move focus to last element that can be tabbed if Shift is used
|
||||
last.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// Update UI
|
||||
update() {
|
||||
if (this.enabled) {
|
||||
@ -226,9 +251,9 @@ class Fullscreen {
|
||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||
this.target.webkitEnterFullscreen();
|
||||
} else if (!Fullscreen.native || this.forceFallback) {
|
||||
toggleFallback.call(this, true);
|
||||
this.toggleFallback(true);
|
||||
} else if (!this.prefix) {
|
||||
this.target.requestFullscreen();
|
||||
this.target.requestFullscreen({ navigationUI: 'hide' });
|
||||
} else if (!is.empty(this.prefix)) {
|
||||
this.target[`${this.prefix}Request${this.property}`]();
|
||||
}
|
||||
@ -245,7 +270,7 @@ class Fullscreen {
|
||||
this.target.webkitExitFullscreen();
|
||||
this.player.play();
|
||||
} else if (!Fullscreen.native || this.forceFallback) {
|
||||
toggleFallback.call(this, false);
|
||||
this.toggleFallback(false);
|
||||
} else if (!this.prefix) {
|
||||
(document.cancelFullScreen || document.exitFullscreen).call(document);
|
||||
} else if (!is.empty(this.prefix)) {
|
||||
|
@ -30,6 +30,11 @@ const html5 = {
|
||||
|
||||
// Get quality levels
|
||||
getQualityOptions() {
|
||||
// Whether we're forcing all options (e.g. for streaming)
|
||||
if (this.config.quality.forced) {
|
||||
return this.config.quality.options;
|
||||
}
|
||||
|
||||
// Get sizes from <source> elements
|
||||
return html5.getSources
|
||||
.call(this)
|
||||
@ -60,36 +65,47 @@ const html5 = {
|
||||
return source && Number(source.getAttribute('size'));
|
||||
},
|
||||
set(input) {
|
||||
// Get sources
|
||||
const sources = html5.getSources.call(player);
|
||||
// Get first match for requested size
|
||||
const source = sources.find(s => Number(s.getAttribute('size')) === input);
|
||||
|
||||
// No matching source found
|
||||
if (!source) {
|
||||
if (player.quality === input) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current state
|
||||
const { currentTime, paused, preload, readyState } = player.media;
|
||||
// If we're using an an external handler...
|
||||
if (player.config.quality.forced && is.function(player.config.quality.onChange)) {
|
||||
player.config.quality.onChange(input);
|
||||
} else {
|
||||
// Get sources
|
||||
const sources = html5.getSources.call(player);
|
||||
// Get first match for requested size
|
||||
const source = sources.find(s => Number(s.getAttribute('size')) === input);
|
||||
|
||||
// Set new source
|
||||
player.media.src = source.getAttribute('src');
|
||||
// No matching source found
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent loading if preload="none" and the current source isn't loaded (#1044)
|
||||
if (preload !== 'none' || readyState) {
|
||||
// Restore time
|
||||
player.once('loadedmetadata', () => {
|
||||
player.currentTime = currentTime;
|
||||
// Get current state
|
||||
const { currentTime, paused, preload, readyState, playbackRate } = player.media;
|
||||
|
||||
// Resume playing
|
||||
if (!paused) {
|
||||
player.play();
|
||||
}
|
||||
});
|
||||
// Set new source
|
||||
player.media.src = source.getAttribute('src');
|
||||
|
||||
// Load new source
|
||||
player.media.load();
|
||||
// Prevent loading if preload="none" and the current source isn't loaded (#1044)
|
||||
if (preload !== 'none' || readyState) {
|
||||
// Restore time
|
||||
player.once('loadedmetadata', () => {
|
||||
|
||||
player.speed = playbackRate;
|
||||
player.currentTime = currentTime;
|
||||
|
||||
// Resume playing
|
||||
if (!paused) {
|
||||
player.play();
|
||||
}
|
||||
});
|
||||
|
||||
// Load new source
|
||||
player.media.load();
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger change event
|
||||
|
@ -6,7 +6,7 @@ import controls from './controls';
|
||||
import ui from './ui';
|
||||
import { repaint } from './utils/animation';
|
||||
import browser from './utils/browser';
|
||||
import { getElement, getElements, matches, toggleClass, toggleHidden } from './utils/elements';
|
||||
import { getElement, getElements, matches, toggleClass } from './utils/elements';
|
||||
import { off, on, once, toggleListener, triggerEvent } from './utils/events';
|
||||
import is from './utils/is';
|
||||
import { getAspectRatio, setAspectRatio } from './utils/style';
|
||||
@ -377,19 +377,15 @@ class Listeners {
|
||||
controls.durationUpdate.call(player, event),
|
||||
);
|
||||
|
||||
// Check for audio tracks on load
|
||||
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
|
||||
on.call(player, player.media, 'canplay loadeddata', () => {
|
||||
toggleHidden(elements.volume, !player.hasAudio);
|
||||
toggleHidden(elements.buttons.mute, !player.hasAudio);
|
||||
});
|
||||
|
||||
// Handle the media finishing
|
||||
on.call(player, player.media, 'ended', () => {
|
||||
// Show poster on end
|
||||
if (player.isHTML5 && player.isVideo && player.config.resetOnEnd) {
|
||||
// Restart
|
||||
player.restart();
|
||||
|
||||
// Call pause otherwise IE11 will start playing the video again
|
||||
player.pause();
|
||||
}
|
||||
});
|
||||
|
||||
@ -603,12 +599,19 @@ class Listeners {
|
||||
this.bind(elements.buttons.airplay, 'click', player.airplay, 'airplay');
|
||||
|
||||
// Settings menu - click toggle
|
||||
this.bind(elements.buttons.settings, 'click', event => {
|
||||
// Prevent the document click listener closing the menu
|
||||
event.stopPropagation();
|
||||
this.bind(
|
||||
elements.buttons.settings,
|
||||
'click',
|
||||
event => {
|
||||
// Prevent the document click listener closing the menu
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
controls.toggleMenu.call(player, event);
|
||||
});
|
||||
controls.toggleMenu.call(player, event);
|
||||
},
|
||||
null,
|
||||
false
|
||||
); // Can't be passive as we're preventing default
|
||||
|
||||
// Settings menu - keyboard toggle
|
||||
// We have to bind to keyup otherwise Firefox triggers a click when a keydown event handler shifts focus
|
||||
@ -663,7 +666,7 @@ class Listeners {
|
||||
const code = event.keyCode ? event.keyCode : event.which;
|
||||
const attribute = 'play-on-seeked';
|
||||
|
||||
if (is.keyboardEvent(event) && (code !== 39 && code !== 37)) {
|
||||
if (is.keyboardEvent(event) && code !== 39 && code !== 37) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -729,7 +732,7 @@ class Listeners {
|
||||
});
|
||||
|
||||
// Hide thumbnail preview - on mouse click, mouse leave, and video play/seek. All four are required, e.g., for buffering
|
||||
this.bind(elements.progress, 'mouseleave click', () => {
|
||||
this.bind(elements.progress, 'mouseleave touchend click', () => {
|
||||
const { previewThumbnails } = player;
|
||||
|
||||
if (previewThumbnails && previewThumbnails.loaded) {
|
||||
|
@ -39,11 +39,13 @@ const media = {
|
||||
wrap(this.media, this.elements.wrapper);
|
||||
|
||||
// Faux poster container
|
||||
this.elements.poster = createElement('div', {
|
||||
class: this.config.classNames.poster,
|
||||
});
|
||||
if (this.isEmbed) {
|
||||
this.elements.poster = createElement('div', {
|
||||
class: this.config.classNames.poster,
|
||||
});
|
||||
|
||||
this.elements.wrapper.appendChild(this.elements.poster);
|
||||
this.elements.wrapper.appendChild(this.elements.poster);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isHTML5) {
|
||||
|
@ -136,7 +136,7 @@ class Ads {
|
||||
cb: Date.now(),
|
||||
AV_WIDTH: 640,
|
||||
AV_HEIGHT: 480,
|
||||
AV_CDIM2: this.publisherId,
|
||||
AV_CDIM2: config.publisherId,
|
||||
};
|
||||
|
||||
const base = 'https://go.aniview.com/api/adserver6/vast/';
|
||||
|
@ -63,6 +63,20 @@ const parseVtt = vttDataString => {
|
||||
* - This implementation uses multiple separate img elements. Other implementations use background-image on one element. This would be nice and simple, but Firefox and Safari have flickering issues with replacing backgrounds of larger images. It seems that YouTube perhaps only avoids this because they don't have the option for high-res previews (even the fullscreen ones, when mousedown/seeking). Images appear over the top of each other, and previous ones are discarded once the new ones have been rendered
|
||||
*/
|
||||
|
||||
const fitRatio = (ratio, outer) => {
|
||||
const targetRatio = outer.width / outer.height;
|
||||
const result = {};
|
||||
if (ratio > targetRatio) {
|
||||
result.width = outer.width;
|
||||
result.height = (1 / ratio) * outer.width;
|
||||
} else {
|
||||
result.height = outer.height;
|
||||
result.width = ratio * outer.height;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
class PreviewThumbnails {
|
||||
/**
|
||||
* PreviewThumbnails constructor.
|
||||
@ -225,8 +239,8 @@ class PreviewThumbnails {
|
||||
}
|
||||
|
||||
startScrubbing(event) {
|
||||
// Only act on left mouse button (0), or touch device (event.button is false)
|
||||
if (event.button === false || event.button === 0) {
|
||||
// Only act on left mouse button (0), or touch device (event.button does not exist or is false)
|
||||
if (is.nullOrUndefined(event.button) || event.button === false || event.button === 0) {
|
||||
this.mouseDown = true;
|
||||
|
||||
// Wait until media has a duration
|
||||
@ -540,8 +554,11 @@ class PreviewThumbnails {
|
||||
|
||||
get thumbContainerHeight() {
|
||||
if (this.mouseDown) {
|
||||
// Can't use media.clientHeight - HTML5 video goes big and does black bars above and below
|
||||
return Math.floor(this.player.media.clientWidth / this.thumbAspectRatio);
|
||||
const { height } = fitRatio(this.thumbAspectRatio, {
|
||||
width: this.player.media.clientWidth,
|
||||
height: this.player.media.clientHeight,
|
||||
});
|
||||
return height;
|
||||
}
|
||||
|
||||
return Math.floor(this.player.media.clientWidth / this.thumbAspectRatio / 4);
|
||||
@ -624,9 +641,12 @@ class PreviewThumbnails {
|
||||
|
||||
// Can't use 100% width, in case the video is a different aspect ratio to the video container
|
||||
setScrubbingContainerSize() {
|
||||
this.elements.scrubbing.container.style.width = `${this.player.media.clientWidth}px`;
|
||||
// Can't use media.clientHeight - html5 video goes big and does black bars above and below
|
||||
this.elements.scrubbing.container.style.height = `${this.player.media.clientWidth / this.thumbAspectRatio}px`;
|
||||
const { width, height } = fitRatio(this.thumbAspectRatio, {
|
||||
width: this.player.media.clientWidth,
|
||||
height: this.player.media.clientHeight,
|
||||
});
|
||||
this.elements.scrubbing.container.style.width = `${width}px`;
|
||||
this.elements.scrubbing.container.style.height = `${height}px`;
|
||||
}
|
||||
|
||||
// Sprites need to be offset to the correct location
|
||||
@ -639,9 +659,9 @@ class PreviewThumbnails {
|
||||
const multiplier = this.thumbContainerHeight / frame.h;
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
previewImage.style.height = `${Math.floor(previewImage.naturalHeight * multiplier)}px`;
|
||||
previewImage.style.height = `${previewImage.naturalHeight * multiplier}px`;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
previewImage.style.width = `${Math.floor(previewImage.naturalWidth * multiplier)}px`;
|
||||
previewImage.style.width = `${previewImage.naturalWidth * multiplier}px`;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
previewImage.style.left = `-${frame.x * multiplier}px`;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
|
@ -196,12 +196,6 @@ const vimeo = {
|
||||
.then(() => {
|
||||
speed = input;
|
||||
triggerEvent.call(player, player.media, 'ratechange');
|
||||
})
|
||||
.catch(error => {
|
||||
// Hide menu item (and menu if empty)
|
||||
if (error.name === 'Error') {
|
||||
controls.setSpeedMenu.call(player, []);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -335,6 +329,14 @@ const vimeo = {
|
||||
}
|
||||
});
|
||||
|
||||
player.embed.on('bufferstart', () => {
|
||||
triggerEvent.call(player, player.media, 'waiting');
|
||||
});
|
||||
|
||||
player.embed.on('bufferend', () => {
|
||||
triggerEvent.call(player, player.media, 'playing');
|
||||
});
|
||||
|
||||
player.embed.on('play', () => {
|
||||
assurePlaybackState.call(player, true);
|
||||
triggerEvent.call(player, player.media, 'playing');
|
||||
|
@ -416,6 +416,12 @@ const youtube = {
|
||||
|
||||
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;
|
||||
}
|
||||
|
595
src/js/plyr.d.ts
vendored
Normal file
595
src/js/plyr.d.ts
vendored
Normal file
@ -0,0 +1,595 @@
|
||||
// Type definitions for plyr 3.5
|
||||
// Project: https://plyr.io
|
||||
// Definitions by: ondratra <https://github.com/ondratra>
|
||||
// TypeScript Version: 3.0
|
||||
|
||||
export = Plyr;
|
||||
export as namespace Plyr;
|
||||
|
||||
declare class Plyr {
|
||||
/**
|
||||
* Setup a new instance
|
||||
*/
|
||||
static setup(targets: NodeList | HTMLElement | HTMLElement[] | string, options?: Plyr.Options): Plyr[];
|
||||
|
||||
/**
|
||||
* Check for support
|
||||
* @param mediaType
|
||||
* @param provider
|
||||
* @param playsInline Whether the player has the playsinline attribute (only applicable to iOS 10+)
|
||||
*/
|
||||
static supported(mediaType?: Plyr.MediaType, provider?: Plyr.Provider, playsInline?: boolean): Plyr.Support;
|
||||
|
||||
constructor(targets: NodeList | HTMLElement | HTMLElement[] | string, options?: Plyr.Options);
|
||||
|
||||
/**
|
||||
* Indicates if the current player is HTML5.
|
||||
*/
|
||||
readonly isHTML5: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if the current player is an embedded player.
|
||||
*/
|
||||
readonly isEmbed: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if the current player is playing.
|
||||
*/
|
||||
readonly playing: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if the current player is paused.
|
||||
*/
|
||||
readonly paused: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if the current player is stopped.
|
||||
*/
|
||||
readonly stopped: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if the current player has finished playback.
|
||||
*/
|
||||
readonly ended: boolean;
|
||||
|
||||
/**
|
||||
* Returns a float between 0 and 1 indicating how much of the media is buffered
|
||||
*/
|
||||
readonly buffered: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the currentTime for the player. The setter accepts a float in seconds.
|
||||
*/
|
||||
currentTime: number;
|
||||
|
||||
/**
|
||||
* Indicates if the current player is seeking.
|
||||
*/
|
||||
readonly seeking: boolean;
|
||||
|
||||
/**
|
||||
* Returns the duration for the current media.
|
||||
*/
|
||||
readonly duration: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the volume for the player. The setter accepts a float between 0 and 1.
|
||||
*/
|
||||
volume: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the muted state of the player. The setter accepts a boolean.
|
||||
*/
|
||||
muted: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if the current media has an audio track.
|
||||
*/
|
||||
readonly hasAudio: boolean;
|
||||
|
||||
/**
|
||||
* Gets or sets the speed for the player. The setter accepts a value in the options specified in your config. Generally the minimum should be 0.5.
|
||||
*/
|
||||
speed: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the quality for the player. The setter accepts a value from the options specified in your config.
|
||||
* Remarks: YouTube only. HTML5 will follow.
|
||||
*/
|
||||
quality: string;
|
||||
|
||||
/**
|
||||
* Gets or sets the current loop state of the player.
|
||||
*/
|
||||
loop: boolean;
|
||||
|
||||
/**
|
||||
* Gets or sets the current source for the player.
|
||||
*/
|
||||
source: Plyr.SourceInfo;
|
||||
|
||||
/**
|
||||
* Gets or sets the current poster image URL for the player.
|
||||
*/
|
||||
poster: string;
|
||||
|
||||
/**
|
||||
* Gets or sets the autoplay state of the player.
|
||||
*/
|
||||
autoplay: boolean;
|
||||
|
||||
/**
|
||||
* Gets or sets the caption track by index. 1 means the track is missing or captions is not active
|
||||
*/
|
||||
currentTrack: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the preferred captions language for the player. The setter accepts an ISO twoletter language code. Support for the languages is dependent on the captions you include.
|
||||
* If your captions don't have any language data, or if you have multiple tracks with the same language, you may want to use currentTrack instead.
|
||||
*/
|
||||
language: string;
|
||||
|
||||
/**
|
||||
* Gets or sets the picture-in-picture state of the player. This currently only supported on Safari 10+ on MacOS Sierra+ and iOS 10+.
|
||||
*/
|
||||
pip: boolean;
|
||||
|
||||
readonly fullscreen: Plyr.FullscreenControl;
|
||||
|
||||
/**
|
||||
* Start playback.
|
||||
* For HTML5 players, play() will return a Promise in some browsers - WebKit and Mozilla according to MDN at time of writing.
|
||||
*/
|
||||
play(): Promise<void> | void;
|
||||
|
||||
/**
|
||||
* Pause playback.
|
||||
*/
|
||||
pause(): void;
|
||||
|
||||
/**
|
||||
* Toggle playback, if no parameters are passed, it will toggle based on current status.
|
||||
*/
|
||||
togglePlay(toggle?: boolean): boolean;
|
||||
|
||||
/**
|
||||
* Stop playback and reset to start.
|
||||
*/
|
||||
stop(): void;
|
||||
|
||||
/**
|
||||
* Restart playback.
|
||||
*/
|
||||
restart(): void;
|
||||
|
||||
/**
|
||||
* Rewind playback by the specified seek time. If no parameter is passed, the default seek time will be used.
|
||||
*/
|
||||
rewind(seekTime?: number): void;
|
||||
|
||||
/**
|
||||
* Fast forward by the specified seek time. If no parameter is passed, the default seek time will be used.
|
||||
*/
|
||||
forward(seekTime?: number): void;
|
||||
|
||||
/**
|
||||
* Increase volume by the specified step. If no parameter is passed, the default step will be used.
|
||||
*/
|
||||
increaseVolume(step?: number): void;
|
||||
|
||||
/**
|
||||
* Increase volume by the specified step. If no parameter is passed, the default step will be used.
|
||||
*/
|
||||
decreaseVolume(step?: number): void;
|
||||
|
||||
/**
|
||||
* Toggle captions display. If no parameter is passed, it will toggle based on current status.
|
||||
*/
|
||||
toggleCaptions(toggle?: boolean): void;
|
||||
|
||||
/**
|
||||
* Trigger the airplay dialog on supported devices.
|
||||
*/
|
||||
airplay(): void;
|
||||
|
||||
/**
|
||||
* Toggle the controls (video only). Takes optional truthy value to force it on/off.
|
||||
*/
|
||||
toggleControls(toggle: boolean): void;
|
||||
|
||||
/**
|
||||
* Add an event listener for the specified event.
|
||||
*/
|
||||
on(
|
||||
event: Plyr.StandardEvent | Plyr.Html5Event | Plyr.YoutubeEvent,
|
||||
callback: (this: this, event: Plyr.PlyrEvent) => void,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Add an event listener for the specified event once.
|
||||
*/
|
||||
once(
|
||||
event: Plyr.StandardEvent | Plyr.Html5Event | Plyr.YoutubeEvent,
|
||||
callback: (this: this, event: Plyr.PlyrEvent) => void,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Remove an event listener for the specified event.
|
||||
*/
|
||||
off(
|
||||
event: Plyr.StandardEvent | Plyr.Html5Event | Plyr.YoutubeEvent,
|
||||
callback: (this: this, event: Plyr.PlyrEvent) => void,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Check support for a mime type.
|
||||
*/
|
||||
supports(type: string): boolean;
|
||||
|
||||
/**
|
||||
* Destroy lib instance
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
declare namespace Plyr {
|
||||
type MediaType = 'audio' | 'video';
|
||||
type Provider = 'html5' | 'youtube' | 'vimeo';
|
||||
type StandardEvent =
|
||||
| 'progress'
|
||||
| 'playing'
|
||||
| 'play'
|
||||
| 'pause'
|
||||
| 'timeupdate'
|
||||
| 'volumechange'
|
||||
| 'seeking'
|
||||
| 'seeked'
|
||||
| 'ratechange'
|
||||
| 'ended'
|
||||
| 'enterfullscreen'
|
||||
| 'exitfullscreen'
|
||||
| 'captionsenabled'
|
||||
| 'captionsdisabled'
|
||||
| 'languagechange'
|
||||
| 'controlshidden'
|
||||
| 'controlsshown'
|
||||
| 'ready';
|
||||
type Html5Event =
|
||||
| 'loadstart'
|
||||
| 'loadeddata'
|
||||
| 'loadedmetadata'
|
||||
| 'canplay'
|
||||
| 'canplaythrough'
|
||||
| 'stalled'
|
||||
| 'waiting'
|
||||
| 'emptied'
|
||||
| 'cuechange'
|
||||
| 'error';
|
||||
type YoutubeEvent = 'statechange' | 'qualitychange' | 'qualityrequested';
|
||||
|
||||
interface FullscreenControl {
|
||||
/**
|
||||
* Indicates if the current player is in fullscreen mode.
|
||||
*/
|
||||
readonly active: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if the current player has fullscreen enabled.
|
||||
*/
|
||||
readonly enabled: boolean;
|
||||
|
||||
/**
|
||||
* Enter fullscreen. If fullscreen is not supported, a fallback ""full window/viewport"" is used instead.
|
||||
*/
|
||||
enter(): void;
|
||||
|
||||
/**
|
||||
* Exit fullscreen.
|
||||
*/
|
||||
exit(): void;
|
||||
|
||||
/**
|
||||
* Toggle fullscreen.
|
||||
*/
|
||||
toggle(): void;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
/**
|
||||
* Completely disable Plyr. This would allow you to do a User Agent check or similar to programmatically enable or disable Plyr for a certain UA. Example below.
|
||||
*/
|
||||
enabled?: boolean;
|
||||
|
||||
/**
|
||||
* Display debugging information in the console
|
||||
*/
|
||||
debug?: boolean;
|
||||
|
||||
/**
|
||||
* If a function is passed, it is assumed your method will return either an element or HTML string for the controls. Three arguments will be passed to your function;
|
||||
* id (the unique id for the player), seektime (the seektime step in seconds), and title (the media title). See controls.md for more info on how the html needs to be structured.
|
||||
* Defaults to ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen']
|
||||
*/
|
||||
controls?: string[] | ((id: string, seektime: number, title: string) => unknown) | Element;
|
||||
|
||||
/**
|
||||
* If you're using the default controls are used then you can specify which settings to show in the menu
|
||||
* Defaults to ['captions', 'quality', 'speed', 'loop']
|
||||
*/
|
||||
settings?: string[];
|
||||
|
||||
/**
|
||||
* Used for internationalization (i18n) of the text within the UI.
|
||||
*/
|
||||
i18n?: any;
|
||||
|
||||
/**
|
||||
* Load the SVG sprite specified as the iconUrl option (if a URL). If false, it is assumed you are handling sprite loading yourself.
|
||||
*/
|
||||
loadSprite?: boolean;
|
||||
|
||||
/**
|
||||
* Specify a URL or path to the SVG sprite. See the SVG section for more info.
|
||||
*/
|
||||
iconUrl?: string;
|
||||
|
||||
/**
|
||||
* Specify the id prefix for the icons used in the default controls (e.g. plyr-play would be plyr).
|
||||
* This is to prevent clashes if you're using your own SVG sprite but with the default controls.
|
||||
* Most people can ignore this option.
|
||||
*/
|
||||
iconPrefix?: string;
|
||||
|
||||
/**
|
||||
* Specify a URL or path to a blank video file used to properly cancel network requests.
|
||||
*/
|
||||
blankUrl?: string;
|
||||
|
||||
/**
|
||||
* Autoplay the media on load. This is generally advised against on UX grounds. It is also disabled by default in some browsers.
|
||||
* If the autoplay attribute is present on a <video> or <audio> element, this will be automatically set to true.
|
||||
*/
|
||||
autoplay?: boolean;
|
||||
|
||||
/**
|
||||
* Only allow one player playing at once.
|
||||
*/
|
||||
autopause?: boolean;
|
||||
|
||||
/**
|
||||
* The time, in seconds, to seek when a user hits fast forward or rewind.
|
||||
*/
|
||||
seekTime?: number;
|
||||
|
||||
/**
|
||||
* A number, between 0 and 1, representing the initial volume of the player.
|
||||
*/
|
||||
volume?: number;
|
||||
|
||||
/**
|
||||
* Whether to start playback muted. If the muted attribute is present on a <video> or <audio> element, this will be automatically set to true.
|
||||
*/
|
||||
muted?: boolean;
|
||||
|
||||
/**
|
||||
* Click (or tap) of the video container will toggle play/pause.
|
||||
*/
|
||||
clickToPlay?: boolean;
|
||||
|
||||
/**
|
||||
* Disable right click menu on video to help as very primitive obfuscation to prevent downloads of content.
|
||||
*/
|
||||
disableContextMenu?: boolean;
|
||||
|
||||
/**
|
||||
* Hide video controls automatically after 2s of no mouse or focus movement, on control element blur (tab out), on playback start or entering fullscreen.
|
||||
* As soon as the mouse is moved, a control element is focused or playback is paused, the controls reappear instantly.
|
||||
*/
|
||||
hideControls?: boolean;
|
||||
|
||||
/**
|
||||
* Reset the playback to the start once playback is complete.
|
||||
*/
|
||||
resetOnEnd?: boolean;
|
||||
|
||||
/**
|
||||
* Enable keyboard shortcuts for focused players only or globally
|
||||
*/
|
||||
keyboard?: KeyboardOptions;
|
||||
|
||||
/**
|
||||
* controls: Display control labels as tooltips on :hover & :focus (by default, the labels are screen reader only).
|
||||
* seek: Display a seek tooltip to indicate on click where the media would seek to.
|
||||
*/
|
||||
tooltips?: TooltipOptions;
|
||||
|
||||
/**
|
||||
* Specify a custom duration for media.
|
||||
*/
|
||||
duration?: number;
|
||||
|
||||
/**
|
||||
* Displays the duration of the media on the metadataloaded event (on startup) in the current time display.
|
||||
* This will only work if the preload attribute is not set to none (or is not set at all) and you choose not to display the duration (see controls option).
|
||||
*/
|
||||
displayDuration?: boolean;
|
||||
|
||||
/**
|
||||
* Display the current time as a countdown rather than an incremental counter.
|
||||
*/
|
||||
invertTime?: boolean;
|
||||
|
||||
/**
|
||||
* Allow users to click to toggle the above.
|
||||
*/
|
||||
toggleInvert?: boolean;
|
||||
|
||||
/**
|
||||
* Allows binding of event listeners to the controls before the default handlers. See the defaults.js for available listeners.
|
||||
* If your handler prevents default on the event (event.preventDefault()), the default handler will not fire.
|
||||
*/
|
||||
listeners?: { [key: string]: (error: PlyrEvent) => void };
|
||||
|
||||
/**
|
||||
* active: Toggles if captions should be active by default. language: Sets the default language to load (if available). 'auto' uses the browser language.
|
||||
* update: Listen to changes to tracks and update menu. This is needed for some streaming libraries, but can result in unselectable language options).
|
||||
*/
|
||||
captions?: CaptionOptions;
|
||||
|
||||
/**
|
||||
* enabled: Toggles whether fullscreen should be enabled. fallback: Allow fallback to a full-window solution.
|
||||
* iosNative: whether to use native iOS fullscreen when entering fullscreen (no custom controls)
|
||||
*/
|
||||
fullscreen?: FullScreenOptions;
|
||||
|
||||
/**
|
||||
* The aspect ratio you want to use for embedded players.
|
||||
*/
|
||||
ratio?: string;
|
||||
|
||||
/**
|
||||
* enabled: Allow use of local storage to store user settings. key: The key name to use.
|
||||
*/
|
||||
storage?: StorageOptions;
|
||||
|
||||
/**
|
||||
* selected: The default speed for playback. options: The speed options to display in the UI. YouTube and Vimeo will ignore any options outside of the 0.5-2 range, so options outside of this range will be hidden automatically.
|
||||
*/
|
||||
speed?: SpeedOptions;
|
||||
|
||||
/**
|
||||
* Currently only supported by YouTube. default is the default quality level, determined by YouTube. options are the options to display.
|
||||
*/
|
||||
quality?: QualityOptions;
|
||||
|
||||
/**
|
||||
* active: Whether to loop the current video. If the loop attribute is present on a <video> or <audio> element,
|
||||
* this will be automatically set to true This is an object to support future functionality.
|
||||
*/
|
||||
loop?: LoopOptions;
|
||||
|
||||
/**
|
||||
* enabled: Whether to enable vi.ai ads. publisherId: Your unique vi.ai publisher ID.
|
||||
*/
|
||||
ads?: AdOptions;
|
||||
}
|
||||
|
||||
interface QualityOptions {
|
||||
default: string;
|
||||
options: string[];
|
||||
}
|
||||
|
||||
interface LoopOptions {
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
interface AdOptions {
|
||||
enabled: boolean;
|
||||
publisherId: string;
|
||||
}
|
||||
|
||||
interface SpeedOptions {
|
||||
selected: number;
|
||||
options: number[];
|
||||
}
|
||||
|
||||
interface KeyboardOptions {
|
||||
focused?: boolean;
|
||||
global?: boolean;
|
||||
}
|
||||
|
||||
interface TooltipOptions {
|
||||
controls?: boolean;
|
||||
seek?: boolean;
|
||||
}
|
||||
|
||||
interface FullScreenOptions {
|
||||
enabled?: boolean;
|
||||
fallback?: boolean;
|
||||
allowAudio?: boolean;
|
||||
}
|
||||
|
||||
interface CaptionOptions {
|
||||
active?: boolean;
|
||||
language?: string;
|
||||
update?: boolean;
|
||||
}
|
||||
|
||||
interface StorageOptions {
|
||||
enabled?: boolean;
|
||||
key?: string;
|
||||
}
|
||||
|
||||
interface SourceInfo {
|
||||
/**
|
||||
* Note: YouTube and Vimeo are currently not supported as audio sources.
|
||||
*/
|
||||
type: MediaType;
|
||||
|
||||
/**
|
||||
* Title of the new media. Used for the aria-label attribute on the play button, and outer container. YouTube and Vimeo are populated automatically.
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* This is an array of sources. For HTML5 media, the properties of this object are mapped directly to HTML attributes so more can be added to the object if required.
|
||||
*/
|
||||
sources: Source[];
|
||||
|
||||
/**
|
||||
* The URL for the poster image (HTML5 video only).
|
||||
*/
|
||||
poster?: string;
|
||||
|
||||
/**
|
||||
* An array of track objects. Each element in the array is mapped directly to a track element and any keys mapped directly to HTML attributes so as in the example above,
|
||||
* it will render as <track kind="captions" label="English" srclang="en" src="https://cdn.selz.com/plyr/1.0/example_captions_en.vtt" default> and similar for the French version.
|
||||
* Booleans are converted to HTML5 value-less attributes.
|
||||
*/
|
||||
tracks?: Track[];
|
||||
}
|
||||
|
||||
interface Source {
|
||||
/**
|
||||
* The URL of the media file (or YouTube/Vimeo URL).
|
||||
*/
|
||||
src: string;
|
||||
/**
|
||||
* The MIME type of the media file (if HTML5).
|
||||
*/
|
||||
type?: string;
|
||||
provider?: Provider;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
type TrackKind = 'subtitles' | 'captions' | 'descriptions' | 'chapters' | 'metadata';
|
||||
interface Track {
|
||||
/**
|
||||
* Indicates how the text track is meant to be used
|
||||
*/
|
||||
kind: TrackKind;
|
||||
/**
|
||||
* Indicates a user-readable title for the track
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* The language of the track text data. It must be a valid BCP 47 language tag. If the kind attribute is set to subtitles, then srclang must be defined.
|
||||
*/
|
||||
srcLang?: string;
|
||||
/**
|
||||
* The URL of the track (.vtt file).
|
||||
*/
|
||||
src: string;
|
||||
|
||||
default?: boolean;
|
||||
}
|
||||
|
||||
interface PlyrEvent extends CustomEvent {
|
||||
readonly detail: { readonly plyr: Plyr };
|
||||
}
|
||||
|
||||
interface Support {
|
||||
api: boolean;
|
||||
ui: boolean;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
// ==========================================================================
|
||||
// Plyr
|
||||
// plyr.js v3.5.6
|
||||
// plyr.js v3.5.7-beta.0
|
||||
// https://github.com/sampotts/plyr
|
||||
// License: The MIT License (MIT)
|
||||
// ==========================================================================
|
||||
@ -368,10 +368,10 @@ class Plyr {
|
||||
*/
|
||||
pause() {
|
||||
if (!this.playing || !is.function(this.media.pause)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
this.media.pause();
|
||||
return this.media.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -411,10 +411,10 @@ class Plyr {
|
||||
const toggle = is.boolean(input) ? input : !this.playing;
|
||||
|
||||
if (toggle) {
|
||||
this.play();
|
||||
} else {
|
||||
this.pause();
|
||||
return this.play();
|
||||
}
|
||||
|
||||
return this.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -441,7 +441,7 @@ class Plyr {
|
||||
* @param {Number} seekTime - how far to rewind in seconds. Defaults to the config.seekTime
|
||||
*/
|
||||
rewind(seekTime) {
|
||||
this.currentTime = this.currentTime - (is.number(seekTime) ? seekTime : this.config.seekTime);
|
||||
this.currentTime -= is.number(seekTime) ? seekTime : this.config.seekTime;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -449,7 +449,7 @@ class Plyr {
|
||||
* @param {Number} seekTime - how far to fast forward in seconds. Defaults to the config.seekTime
|
||||
*/
|
||||
forward(seekTime) {
|
||||
this.currentTime = this.currentTime + (is.number(seekTime) ? seekTime : this.config.seekTime);
|
||||
this.currentTime += is.number(seekTime) ? seekTime : this.config.seekTime;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,6 @@
|
||||
// ==========================================================================
|
||||
// Plyr Polyfilled Build
|
||||
// plyr.js v3.5.6
|
||||
// plyr.js v3.5.7-beta.0
|
||||
// https://github.com/sampotts/plyr
|
||||
// License: The MIT License (MIT)
|
||||
// ==========================================================================
|
||||
|
@ -198,7 +198,9 @@ const ui = {
|
||||
// Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube)
|
||||
backgroundSize: '',
|
||||
});
|
||||
|
||||
ui.togglePoster.call(this, true);
|
||||
|
||||
return poster;
|
||||
})
|
||||
);
|
||||
@ -214,6 +216,7 @@ const ui = {
|
||||
// Set state
|
||||
Array.from(this.elements.buttons.play || []).forEach(target => {
|
||||
Object.assign(target, { pressed: this.playing });
|
||||
target.setAttribute('aria-label', i18n.get(this.playing ? 'pause' : 'play', this.config));
|
||||
});
|
||||
|
||||
// Only update controls on non timeupdate events
|
||||
|
@ -21,3 +21,11 @@ export function closest(array, value) {
|
||||
|
||||
return array.reduce((prev, curr) => (Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev));
|
||||
}
|
||||
|
||||
export function fillRange(start, end, step = 1) {
|
||||
const len = Math.floor((end - start) / step) + 1;
|
||||
|
||||
return Array(len)
|
||||
.fill()
|
||||
.map((_, idx) => start + idx * step);
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
// Element utils
|
||||
// ==========================================================================
|
||||
|
||||
import { toggleListener } from './events';
|
||||
import is from './is';
|
||||
import { extend } from './objects';
|
||||
|
||||
@ -248,39 +247,6 @@ export function getElement(selector) {
|
||||
return this.elements.container.querySelector(selector);
|
||||
}
|
||||
|
||||
// Trap focus inside container
|
||||
export function trapFocus(element = null, toggle = false) {
|
||||
if (!is.element(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focusable = getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]');
|
||||
const first = focusable[0];
|
||||
const last = focusable[focusable.length - 1];
|
||||
|
||||
const trap = event => {
|
||||
// Bail if not tab key or not fullscreen
|
||||
if (event.key !== 'Tab' || event.keyCode !== 9) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current focused element
|
||||
const focused = document.activeElement;
|
||||
|
||||
if (focused === last && !event.shiftKey) {
|
||||
// Move focus to first element that can be tabbed if Shift isn't used
|
||||
first.focus();
|
||||
event.preventDefault();
|
||||
} else if (focused === first && event.shiftKey) {
|
||||
// Move focus to last element that can be tabbed if Shift is used
|
||||
last.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false);
|
||||
}
|
||||
|
||||
// Set focus and tab focus class
|
||||
export function setFocus(element = null, tabFocus = false) {
|
||||
if (!is.element(element)) {
|
||||
|
@ -90,9 +90,7 @@ export function triggerEvent(element, type = '', bubbles = false, detail = {}) {
|
||||
// Create and dispatch the event
|
||||
const event = new CustomEvent(type, {
|
||||
bubbles,
|
||||
detail: Object.assign({}, detail, {
|
||||
plyr: this,
|
||||
}),
|
||||
detail: { ...detail, plyr: this,},
|
||||
});
|
||||
|
||||
// Dispatch the event
|
||||
|
@ -56,11 +56,12 @@ export function setAspectRatio(input) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const { wrapper } = this.elements;
|
||||
const ratio = getAspectRatio.call(this, input);
|
||||
const [w, h] = is.array(ratio) ? ratio : [0, 0];
|
||||
const padding = (100 / w) * h;
|
||||
|
||||
this.elements.wrapper.style.paddingBottom = `${padding}%`;
|
||||
wrapper.style.paddingBottom = `${padding}%`;
|
||||
|
||||
// For Vimeo we have an extra <div> to hide the standard controls and UI
|
||||
if (this.isVimeo && this.supported.ui) {
|
||||
@ -68,7 +69,7 @@ export function setAspectRatio(input) {
|
||||
const offset = (height - padding) / (height / 50);
|
||||
this.media.style.transform = `translateY(-${offset}%)`;
|
||||
} else if (this.isHTML5) {
|
||||
this.elements.wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
|
||||
wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
|
||||
}
|
||||
|
||||
return { padding, ratio };
|
||||
|
@ -13,7 +13,7 @@ export const getSeconds = value => Math.trunc(value % 60, 10);
|
||||
export function formatTime(time = 0, displayHours = false, inverted = false) {
|
||||
// Bail if the value isn't a number
|
||||
if (!is.number(time)) {
|
||||
return formatTime(null, displayHours, inverted);
|
||||
return formatTime(undefined, displayHours, inverted);
|
||||
}
|
||||
|
||||
// Format time component to add leading zero
|
||||
|
@ -5,24 +5,27 @@
|
||||
// Base
|
||||
.plyr {
|
||||
@include plyr-font-smoothing($plyr-font-smoothing);
|
||||
|
||||
align-items: center;
|
||||
direction: ltr;
|
||||
display: flex;
|
||||
font-family: $plyr-font-family;
|
||||
font-variant-numeric: tabular-nums; // Force monosace-esque number widths
|
||||
font-weight: $plyr-font-weight-regular;
|
||||
height: 100%;
|
||||
line-height: $plyr-line-height;
|
||||
max-width: 100%;
|
||||
min-width: 200px;
|
||||
position: relative;
|
||||
text-shadow: none;
|
||||
transition: box-shadow 0.3s ease;
|
||||
z-index: 0; // Force any border radius
|
||||
|
||||
// Media elements
|
||||
video,
|
||||
audio {
|
||||
border-radius: inherit;
|
||||
height: auto;
|
||||
vertical-align: middle;
|
||||
audio,
|
||||
iframe {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
7
src/sass/components/audio.scss
Normal file
7
src/sass/components/audio.scss
Normal file
@ -0,0 +1,7 @@
|
||||
// --------------------------------------------------------------
|
||||
// Audio styles
|
||||
// --------------------------------------------------------------
|
||||
|
||||
.plyr--audio {
|
||||
display: block;
|
||||
}
|
@ -41,14 +41,6 @@
|
||||
&.plyr__time + .plyr__time {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&.plyr__volume {
|
||||
padding-right: ($plyr-control-spacing / 2);
|
||||
}
|
||||
|
||||
&.plyr__volume:first-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide empty controls
|
||||
|
@ -14,11 +14,10 @@
|
||||
|
||||
.plyr__video-wrapper {
|
||||
background: #000;
|
||||
border-radius: inherit;
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
// Require z-index to force border-radius
|
||||
z-index: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Default to 16:9 ratio but this is set by JavaScript based on config
|
||||
@ -33,12 +32,9 @@ $embed-padding: ((100 / 16) * 9);
|
||||
.plyr__video-embed iframe,
|
||||
.plyr__video-wrapper--fixed-ratio video {
|
||||
border: 0;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// If the full custom UI is supported
|
||||
|
@ -5,11 +5,11 @@
|
||||
.plyr__volume {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
input[type='range'] {
|
||||
margin-left: ($plyr-control-spacing / 2);
|
||||
margin-right: ($plyr-control-spacing / 2);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
@ -22,16 +22,3 @@
|
||||
max-width: 110px;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide sound controls on iOS
|
||||
// It's not supported to change volume using JavaScript:
|
||||
// https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html
|
||||
.plyr--is-ios .plyr__volume {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// Vimeo has no toggle mute method so hide mute button
|
||||
// https://github.com/vimeo/player.js/issues/236#issuecomment-384663183
|
||||
.plyr--is-ios.plyr--vimeo [data-plyr='mute'] {
|
||||
display: none !important;
|
||||
}
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
@import 'base';
|
||||
|
||||
@import 'components/audio';
|
||||
@import 'components/badges';
|
||||
@import 'components/captions';
|
||||
@import 'components/control';
|
||||
|
@ -24,8 +24,8 @@
|
||||
// Fallback for unsupported browsers
|
||||
.plyr--fullscreen-fallback {
|
||||
@include plyr-fullscreen-active();
|
||||
|
||||
bottom: 0;
|
||||
display: block;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
|
Loading…
x
Reference in New Issue
Block a user