This is a PR to allow for contextual content to be included in fullscreen (or fallback) mode. This means arbitrary elements (extensions to the basic player UI) can be overlaid and remain visible when the player switches to fullscreen.

Example use-cases include:
 - display of video title or other metadata (see the included demo)
 - alternative access to menu items, such as a searchable captions list (in cases where many hundreds of languages are available)
 - custom share dialogs
 - integrated playlists with 'playing next' overlays

This approach / PR is just an example of how this feature could work and aims to keep Plyr complexity to a minimum (while enabling some fairly interesting integrations). It utilises a single config option, and does away with the need for injecting bespoke APIs or elements into the player context on a per-project basis. Or trying to mess with what is a pretty slick, but tightly coupled system.

For the user: A new `fullscreen.container` attribute is used to provide a container selector. The container must be an ancestor of the player, otherwise it's ignored. When toggling fullscreen mode, this container is now used in place of the player. Hovering over any children of the container is the same as hovering over the controls. The exception is where the player and the child share a common ancestor (that's not the fullscreen container) ... sounds complex but it's not. You can also gain pretty fine control this way with pointer events.

Under the hood: it adds a `utils/elements/closest` helper method to find the right ancestor. If found this is returned as the fullscreen target in place of the player container. Fullscreen is instantiated slightly earlier in the setup so this container is available for the `listeners.controls` call. In here we add some more 'mouseenter/mouseleave' listeners to any direct descendants of the container, that aren't also ancestors of the player. And that's it. No extra classes, nothing else. There are some style changes to the demo (top margin on the player) but these would be project specific.

Thanks for reading.
This commit is contained in:
Som Meaden 2020-04-04 13:43:51 +10:00
parent 44ef0bbc87
commit 49ed2cac4e
23 changed files with 390 additions and 67 deletions

63
demo/dist/demo.js vendored
View File

@ -17736,6 +17736,25 @@ typeof navigator === "object" && (function () {
var method = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match;
return method.call(element, selector);
} // Closest ancestor element matching selector (also tests element itself)
function closest(element, selector) {
var _Element2 = Element,
prototype = _Element2.prototype; // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
function closestElement() {
var el = this;
do {
if (matches$2.matches(el, selector)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
}
var method = prototype.closest || closestElement;
return method.call(element, selector);
} // Find all elements
function getElements(selector) {
@ -18241,7 +18260,7 @@ typeof navigator === "object" && (function () {
});
} // Get the closest value in an array
function closest(array, value) {
function closest$1(array, value) {
if (!is$2.array(array) || !array.length) {
return null;
}
@ -20704,6 +20723,9 @@ typeof navigator === "object" && (function () {
fallback: true,
// Fallback using full viewport/window
iosNative: false // Use the native fullscreen in iOS (disables custom controls)
// Selector for the fullscreen container so contextual / non-player content can remain visible in fullscreen mode
// Non-ancestors of the player element will be ignored
// container: null, // defaults to the player element
},
// Local storage
@ -21060,7 +21082,10 @@ typeof navigator === "object" && (function () {
y: 0
}; // Force the use of 'full window/browser' rather than fullscreen
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Register event listeners
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Get the fullscreen element
// Checks container is an ancestor, defaults to null
this.player.elements.fullscreen = player.config.fullscreen.container && closest(this.player.elements.container, player.config.fullscreen.container); // Register event listeners
// Handle event (incase user presses escape etc)
on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : "".concat(this.prefix, "fullscreenchange"), function () {
@ -21286,7 +21311,7 @@ typeof navigator === "object" && (function () {
}, {
key: "target",
get: function get() {
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container;
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.fullscreen || this.player.elements.container;
}
}], [{
key: "native",
@ -22289,7 +22314,18 @@ typeof navigator === "object" && (function () {
this.bind(elements.controls, 'mouseenter mouseleave', function (event) {
elements.controls.hover = !player.touch && event.type === 'mouseenter';
}); // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
}); // Also update controls.hover state for any non-player children of fullscreen element (as above)
if (elements.fullscreen) {
for (var i = 0; i < elements.fullscreen.children.length; i++) {
if (!elements.fullscreen.children[i].contains(elements.container)) {
this.bind(elements.fullscreen.children[i], 'mouseenter mouseleave', function (event) {
elements.controls.hover = !player.touch && event.type === 'mouseenter';
});
}
}
} // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
@ -25148,6 +25184,7 @@ typeof navigator === "object" && (function () {
this.elements = {
container: null,
fullscreen: null,
captions: null,
buttons: {},
display: {},
@ -25333,10 +25370,12 @@ typeof navigator === "object" && (function () {
on.call(this, this.elements.container, this.config.events.join(' '), function (event) {
_this.debug.log("event: ".concat(event.type));
});
} // Setup interface
// If embed but not fully supported, build interface now to avoid flash of controls
} // Setup fullscreen
this.fullscreen = new Fullscreen(this); // Setup interface
// If embed but not fully supported, build interface now to avoid flash of controls
if (this.isHTML5 || this.isEmbed && !this.supported.ui) {
ui.build.call(this);
} // Container listeners
@ -25344,9 +25383,7 @@ typeof navigator === "object" && (function () {
this.listeners.container(); // Global listeners
this.listeners.global(); // Setup fullscreen
this.fullscreen = new Fullscreen(this); // Setup ads if provided
this.listeners.global(); // Setup ads if provided
if (this.config.ads.enabled) {
this.ads = new Ads(this);
@ -26045,7 +26082,7 @@ typeof navigator === "object" && (function () {
var updateStorage = true;
if (!options.includes(quality)) {
var value = closest(options, quality);
var value = closest$1(options, quality);
this.debug.warn("Unsupported quality option: ".concat(quality, ", using ").concat(value, " instead"));
quality = value; // Don't update storage if quality is not supported
@ -26486,6 +26523,12 @@ typeof navigator === "object" && (function () {
vimeo: {
// Prevent Vimeo blocking plyr.io demo site
referrerPolicy: 'no-referrer'
},
fullscreen: {
enabled: true,
fallback: true,
iosNative: false,
container: '#container'
}
}); // Expose for tinkering in the console

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -180,6 +180,90 @@
>Download</a
>
</video>
<!-- BEGIN FULLSCREEN OVERLAY EXAMPLE -->
<style>
#container {
position: relative;
margin-top: 16px;
overflow: hidden;
}
.plyr {
margin-top: 0;
}
.plyr--overlay {
position: absolute;
pointer-events: none;
top: 0;
left: 0;
margin: 0.4444em;
transition: 0.2s 0.3s opacity ease-in-out,
0.3s 0.3s visibility ease-in-out,
0.3s 0.3s transform ease-in-out;
}
.plyr--hide-controls ~ .plyr--overlay {
transition-delay: 0;
transform: translateX(-1em);
opacity: 0;
visibility: hidden;
}
.plyr--title {
padding: 0.125em 0.625em;
background: linear-gradient(to left top,#e0f6ff,#f5fcff);
color: #00b3ff;
z-index: 1;
border-radius: 4px;
margin: 0;
pointer-events: all;
}
.plyr--tags {
display: flex;
margin-top: 0.2em;
list-style: none;
transition: 0.3s 0.2s transform ease-in-out;
}
.plyr--hide-controls ~ .plyr--overlay .plyr--tags {
transition-delay: 0.2s;
transform: translateX(-1em);
}
.plyr--tags-item {
color: #fff;
background: #00b3ff;
padding: 0 0.4em 0.1em;
border: none;
border-radius: 4px;
margin-right: 0.2em;
pointer-events: all;
}
.plyr--tags-item:hover {
color: #00b3ff;
background: #fff;
transition: none;
}
.plyr--tags-item small {
display: inline;
margin: 0;
}
.plyr--tags-item::after {
content: none;
}
</style>
<div class="plyr--overlay">
<h2 class="plyr--title">View From A Blue Moon</h2>
<ul class="plyr--tags">
<li>
<a class="plyr--tags-item" href="#surf-film"><small>Surf film</small></a>
</li>
<li>
<a class="plyr--tags-item" href="#oceans"><small>Oceans</small></a>
</li>
<li>
<a class="plyr--tags-item" href="#bio"><small>Bio</small></a>
</li>
</ul>
</div>
<!-- / END FULLSCREEN OVERLAY EXAMPLE -->
</div>
<ul>

View File

@ -65,6 +65,12 @@ import toggleClass from './toggle-class';
// Prevent Vimeo blocking plyr.io demo site
referrerPolicy: 'no-referrer',
},
fullscreen: {
enabled: true,
fallback: true,
iosNative: false,
container: '#container',
},
});
// Expose for tinkering in the console

57
dist/plyr.js vendored
View File

@ -788,6 +788,25 @@ typeof navigator === "object" && (function (global, factory) {
var method = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match;
return method.call(element, selector);
} // Closest ancestor element matching selector (also tests element itself)
function closest(element, selector) {
var _Element2 = Element,
prototype = _Element2.prototype; // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
function closestElement() {
var el = this;
do {
if (matches$1.matches(el, selector)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
}
var method = prototype.closest || closestElement;
return method.call(element, selector);
} // Find all elements
function getElements(selector) {
@ -1296,7 +1315,7 @@ typeof navigator === "object" && (function (global, factory) {
});
} // Get the closest value in an array
function closest(array, value) {
function closest$1(array, value) {
if (!is$1.array(array) || !array.length) {
return null;
}
@ -3633,6 +3652,9 @@ typeof navigator === "object" && (function (global, factory) {
fallback: true,
// Fallback using full viewport/window
iosNative: false // Use the native fullscreen in iOS (disables custom controls)
// Selector for the fullscreen container so contextual / non-player content can remain visible in fullscreen mode
// Non-ancestors of the player element will be ignored
// container: null, // defaults to the player element
},
// Local storage
@ -3989,7 +4011,10 @@ typeof navigator === "object" && (function (global, factory) {
y: 0
}; // Force the use of 'full window/browser' rather than fullscreen
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Register event listeners
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Get the fullscreen element
// Checks container is an ancestor, defaults to null
this.player.elements.fullscreen = player.config.fullscreen.container && closest(this.player.elements.container, player.config.fullscreen.container); // Register event listeners
// Handle event (incase user presses escape etc)
on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : "".concat(this.prefix, "fullscreenchange"), function () {
@ -4215,7 +4240,7 @@ typeof navigator === "object" && (function (global, factory) {
}, {
key: "target",
get: function get() {
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container;
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.fullscreen || this.player.elements.container;
}
}], [{
key: "native",
@ -5206,7 +5231,18 @@ typeof navigator === "object" && (function (global, factory) {
this.bind(elements.controls, 'mouseenter mouseleave', function (event) {
elements.controls.hover = !player.touch && event.type === 'mouseenter';
}); // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
}); // Also update controls.hover state for any non-player children of fullscreen element (as above)
if (elements.fullscreen) {
for (var i = 0; i < elements.fullscreen.children.length; i++) {
if (!elements.fullscreen.children[i].contains(elements.container)) {
this.bind(elements.fullscreen.children[i], 'mouseenter mouseleave', function (event) {
elements.controls.hover = !player.touch && event.type === 'mouseenter';
});
}
}
} // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
@ -7964,6 +8000,7 @@ typeof navigator === "object" && (function (global, factory) {
this.elements = {
container: null,
fullscreen: null,
captions: null,
buttons: {},
display: {},
@ -8149,10 +8186,12 @@ typeof navigator === "object" && (function (global, factory) {
on.call(this, this.elements.container, this.config.events.join(' '), function (event) {
_this.debug.log("event: ".concat(event.type));
});
} // Setup interface
// If embed but not fully supported, build interface now to avoid flash of controls
} // Setup fullscreen
this.fullscreen = new Fullscreen(this); // Setup interface
// If embed but not fully supported, build interface now to avoid flash of controls
if (this.isHTML5 || this.isEmbed && !this.supported.ui) {
ui.build.call(this);
} // Container listeners
@ -8160,9 +8199,7 @@ typeof navigator === "object" && (function (global, factory) {
this.listeners.container(); // Global listeners
this.listeners.global(); // Setup fullscreen
this.fullscreen = new Fullscreen(this); // Setup ads if provided
this.listeners.global(); // Setup ads if provided
if (this.config.ads.enabled) {
this.ads = new Ads(this);
@ -8861,7 +8898,7 @@ typeof navigator === "object" && (function (global, factory) {
var updateStorage = true;
if (!options.includes(quality)) {
var value = closest(options, quality);
var value = closest$1(options, quality);
this.debug.warn("Unsupported quality option: ".concat(quality, ", using ").concat(value, " instead"));
quality = value; // Don't update storage if quality is not supported

2
dist/plyr.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/plyr.min.mjs vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

57
dist/plyr.mjs vendored
View File

@ -782,6 +782,25 @@ function matches$1(element, selector) {
var method = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match;
return method.call(element, selector);
} // Closest ancestor element matching selector (also tests element itself)
function closest(element, selector) {
var _Element2 = Element,
prototype = _Element2.prototype; // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
function closestElement() {
var el = this;
do {
if (matches$1.matches(el, selector)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
}
var method = prototype.closest || closestElement;
return method.call(element, selector);
} // Find all elements
function getElements(selector) {
@ -1290,7 +1309,7 @@ function dedupe(array) {
});
} // Get the closest value in an array
function closest(array, value) {
function closest$1(array, value) {
if (!is$1.array(array) || !array.length) {
return null;
}
@ -3627,6 +3646,9 @@ var defaults$1 = {
fallback: true,
// Fallback using full viewport/window
iosNative: false // Use the native fullscreen in iOS (disables custom controls)
// Selector for the fullscreen container so contextual / non-player content can remain visible in fullscreen mode
// Non-ancestors of the player element will be ignored
// container: null, // defaults to the player element
},
// Local storage
@ -3983,7 +4005,10 @@ var Fullscreen = /*#__PURE__*/function () {
y: 0
}; // Force the use of 'full window/browser' rather than fullscreen
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Register event listeners
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Get the fullscreen element
// Checks container is an ancestor, defaults to null
this.player.elements.fullscreen = player.config.fullscreen.container && closest(this.player.elements.container, player.config.fullscreen.container); // Register event listeners
// Handle event (incase user presses escape etc)
on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : "".concat(this.prefix, "fullscreenchange"), function () {
@ -4209,7 +4234,7 @@ var Fullscreen = /*#__PURE__*/function () {
}, {
key: "target",
get: function get() {
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container;
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.fullscreen || this.player.elements.container;
}
}], [{
key: "native",
@ -5200,7 +5225,18 @@ var Listeners = /*#__PURE__*/function () {
this.bind(elements.controls, 'mouseenter mouseleave', function (event) {
elements.controls.hover = !player.touch && event.type === 'mouseenter';
}); // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
}); // Also update controls.hover state for any non-player children of fullscreen element (as above)
if (elements.fullscreen) {
for (var i = 0; i < elements.fullscreen.children.length; i++) {
if (!elements.fullscreen.children[i].contains(elements.container)) {
this.bind(elements.fullscreen.children[i], 'mouseenter mouseleave', function (event) {
elements.controls.hover = !player.touch && event.type === 'mouseenter';
});
}
}
} // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
@ -7958,6 +7994,7 @@ var Plyr = /*#__PURE__*/function () {
this.elements = {
container: null,
fullscreen: null,
captions: null,
buttons: {},
display: {},
@ -8143,10 +8180,12 @@ var Plyr = /*#__PURE__*/function () {
on.call(this, this.elements.container, this.config.events.join(' '), function (event) {
_this.debug.log("event: ".concat(event.type));
});
} // Setup interface
// If embed but not fully supported, build interface now to avoid flash of controls
} // Setup fullscreen
this.fullscreen = new Fullscreen(this); // Setup interface
// If embed but not fully supported, build interface now to avoid flash of controls
if (this.isHTML5 || this.isEmbed && !this.supported.ui) {
ui.build.call(this);
} // Container listeners
@ -8154,9 +8193,7 @@ var Plyr = /*#__PURE__*/function () {
this.listeners.container(); // Global listeners
this.listeners.global(); // Setup fullscreen
this.fullscreen = new Fullscreen(this); // Setup ads if provided
this.listeners.global(); // Setup ads if provided
if (this.config.ads.enabled) {
this.ads = new Ads(this);
@ -8855,7 +8892,7 @@ var Plyr = /*#__PURE__*/function () {
var updateStorage = true;
if (!options.includes(quality)) {
var value = closest(options, quality);
var value = closest$1(options, quality);
this.debug.warn("Unsupported quality option: ".concat(quality, ", using ").concat(value, " instead"));
quality = value; // Don't update storage if quality is not supported

View File

@ -7018,6 +7018,25 @@ typeof navigator === "object" && (function (global, factory) {
var method = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match;
return method.call(element, selector);
} // Closest ancestor element matching selector (also tests element itself)
function closest(element, selector) {
var _Element2 = Element,
prototype = _Element2.prototype; // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
function closestElement() {
var el = this;
do {
if (matches$1.matches(el, selector)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
}
var method = prototype.closest || closestElement;
return method.call(element, selector);
} // Find all elements
function getElements(selector) {
@ -7523,7 +7542,7 @@ typeof navigator === "object" && (function (global, factory) {
});
} // Get the closest value in an array
function closest(array, value) {
function closest$1(array, value) {
if (!is$1.array(array) || !array.length) {
return null;
}
@ -9948,6 +9967,9 @@ typeof navigator === "object" && (function (global, factory) {
fallback: true,
// Fallback using full viewport/window
iosNative: false // Use the native fullscreen in iOS (disables custom controls)
// Selector for the fullscreen container so contextual / non-player content can remain visible in fullscreen mode
// Non-ancestors of the player element will be ignored
// container: null, // defaults to the player element
},
// Local storage
@ -10304,7 +10326,10 @@ typeof navigator === "object" && (function (global, factory) {
y: 0
}; // Force the use of 'full window/browser' rather than fullscreen
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Register event listeners
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Get the fullscreen element
// Checks container is an ancestor, defaults to null
this.player.elements.fullscreen = player.config.fullscreen.container && closest(this.player.elements.container, player.config.fullscreen.container); // Register event listeners
// Handle event (incase user presses escape etc)
on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : "".concat(this.prefix, "fullscreenchange"), function () {
@ -10530,7 +10555,7 @@ typeof navigator === "object" && (function (global, factory) {
}, {
key: "target",
get: function get() {
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container;
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.fullscreen || this.player.elements.container;
}
}], [{
key: "native",
@ -11533,7 +11558,18 @@ typeof navigator === "object" && (function (global, factory) {
this.bind(elements.controls, 'mouseenter mouseleave', function (event) {
elements.controls.hover = !player.touch && event.type === 'mouseenter';
}); // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
}); // Also update controls.hover state for any non-player children of fullscreen element (as above)
if (elements.fullscreen) {
for (var i = 0; i < elements.fullscreen.children.length; i++) {
if (!elements.fullscreen.children[i].contains(elements.container)) {
this.bind(elements.fullscreen.children[i], 'mouseenter mouseleave', function (event) {
elements.controls.hover = !player.touch && event.type === 'mouseenter';
});
}
}
} // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
@ -14392,6 +14428,7 @@ typeof navigator === "object" && (function (global, factory) {
this.elements = {
container: null,
fullscreen: null,
captions: null,
buttons: {},
display: {},
@ -14577,10 +14614,12 @@ typeof navigator === "object" && (function (global, factory) {
on.call(this, this.elements.container, this.config.events.join(' '), function (event) {
_this.debug.log("event: ".concat(event.type));
});
} // Setup interface
// If embed but not fully supported, build interface now to avoid flash of controls
} // Setup fullscreen
this.fullscreen = new Fullscreen(this); // Setup interface
// If embed but not fully supported, build interface now to avoid flash of controls
if (this.isHTML5 || this.isEmbed && !this.supported.ui) {
ui.build.call(this);
} // Container listeners
@ -14588,9 +14627,7 @@ typeof navigator === "object" && (function (global, factory) {
this.listeners.container(); // Global listeners
this.listeners.global(); // Setup fullscreen
this.fullscreen = new Fullscreen(this); // Setup ads if provided
this.listeners.global(); // Setup ads if provided
if (this.config.ads.enabled) {
this.ads = new Ads(this);
@ -15289,7 +15326,7 @@ typeof navigator === "object" && (function (global, factory) {
var updateStorage = true;
if (!options.includes(quality)) {
var value = closest(options, quality);
var value = closest$1(options, quality);
this.debug.warn("Unsupported quality option: ".concat(quality, ", using ").concat(value, " instead"));
quality = value; // Don't update storage if quality is not supported

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7012,6 +7012,25 @@ function matches$1(element, selector) {
var method = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match;
return method.call(element, selector);
} // Closest ancestor element matching selector (also tests element itself)
function closest(element, selector) {
var _Element2 = Element,
prototype = _Element2.prototype; // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
function closestElement() {
var el = this;
do {
if (matches$1.matches(el, selector)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
}
var method = prototype.closest || closestElement;
return method.call(element, selector);
} // Find all elements
function getElements(selector) {
@ -7517,7 +7536,7 @@ function dedupe(array) {
});
} // Get the closest value in an array
function closest(array, value) {
function closest$1(array, value) {
if (!is$1.array(array) || !array.length) {
return null;
}
@ -9942,6 +9961,9 @@ var defaults$1 = {
fallback: true,
// Fallback using full viewport/window
iosNative: false // Use the native fullscreen in iOS (disables custom controls)
// Selector for the fullscreen container so contextual / non-player content can remain visible in fullscreen mode
// Non-ancestors of the player element will be ignored
// container: null, // defaults to the player element
},
// Local storage
@ -10298,7 +10320,10 @@ var Fullscreen = /*#__PURE__*/function () {
y: 0
}; // Force the use of 'full window/browser' rather than fullscreen
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Register event listeners
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Get the fullscreen element
// Checks container is an ancestor, defaults to null
this.player.elements.fullscreen = player.config.fullscreen.container && closest(this.player.elements.container, player.config.fullscreen.container); // Register event listeners
// Handle event (incase user presses escape etc)
on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : "".concat(this.prefix, "fullscreenchange"), function () {
@ -10524,7 +10549,7 @@ var Fullscreen = /*#__PURE__*/function () {
}, {
key: "target",
get: function get() {
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container;
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.fullscreen || this.player.elements.container;
}
}], [{
key: "native",
@ -11527,7 +11552,18 @@ var Listeners = /*#__PURE__*/function () {
this.bind(elements.controls, 'mouseenter mouseleave', function (event) {
elements.controls.hover = !player.touch && event.type === 'mouseenter';
}); // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
}); // Also update controls.hover state for any non-player children of fullscreen element (as above)
if (elements.fullscreen) {
for (var i = 0; i < elements.fullscreen.children.length; i++) {
if (!elements.fullscreen.children[i].contains(elements.container)) {
this.bind(elements.fullscreen.children[i], 'mouseenter mouseleave', function (event) {
elements.controls.hover = !player.touch && event.type === 'mouseenter';
});
}
}
} // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
@ -14386,6 +14422,7 @@ var Plyr = /*#__PURE__*/function () {
this.elements = {
container: null,
fullscreen: null,
captions: null,
buttons: {},
display: {},
@ -14571,10 +14608,12 @@ var Plyr = /*#__PURE__*/function () {
on.call(this, this.elements.container, this.config.events.join(' '), function (event) {
_this.debug.log("event: ".concat(event.type));
});
} // Setup interface
// If embed but not fully supported, build interface now to avoid flash of controls
} // Setup fullscreen
this.fullscreen = new Fullscreen(this); // Setup interface
// If embed but not fully supported, build interface now to avoid flash of controls
if (this.isHTML5 || this.isEmbed && !this.supported.ui) {
ui.build.call(this);
} // Container listeners
@ -14582,9 +14621,7 @@ var Plyr = /*#__PURE__*/function () {
this.listeners.container(); // Global listeners
this.listeners.global(); // Setup fullscreen
this.fullscreen = new Fullscreen(this); // Setup ads if provided
this.listeners.global(); // Setup ads if provided
if (this.config.ads.enabled) {
this.ads = new Ads(this);
@ -15283,7 +15320,7 @@ var Plyr = /*#__PURE__*/function () {
var updateStorage = true;
if (!options.includes(quality)) {
var value = closest(options, quality);
var value = closest$1(options, quality);
this.debug.warn("Unsupported quality option: ".concat(quality, ", using ").concat(value, " instead"));
quality = value; // Don't update storage if quality is not supported

View File

@ -303,7 +303,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
| `toggleInvert` | Boolean | `true` | Allow users to click to toggle the above. |
| `listeners` | Object | `null` | 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. |
| `captions` | Object | `{ active: false, language: 'auto', update: false }` | `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). |
| `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) |
| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false, container: null }` | `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). `container`: A selector for an ancestor of the player element, allows contextual content to remain visual in fullscreen mode. Non-ancestors are ignored. |
| `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`: 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. |

View File

@ -115,6 +115,9 @@ const defaults = {
enabled: true, // Allow fullscreen?
fallback: true, // Fallback using full viewport/window
iosNative: false, // Use the native fullscreen in iOS (disables custom controls)
// Selector for the fullscreen container so contextual / non-player content can remain visible in fullscreen mode
// Non-ancestors of the player element will be ignored
// container: null, // defaults to the player element
},
// Local storage

View File

@ -5,7 +5,7 @@
// ==========================================================================
import browser from './utils/browser';
import { getElements, hasClass, toggleClass } from './utils/elements';
import { getElements, hasClass, toggleClass, closest } from './utils/elements';
import { on, triggerEvent } from './utils/events';
import is from './utils/is';
import { silencePromise } from './utils/promise';
@ -25,6 +25,11 @@ class Fullscreen {
// Force the use of 'full window/browser' rather than fullscreen
this.forceFallback = player.config.fullscreen.fallback === 'force';
// Get the fullscreen element
// Checks container is an ancestor, defaults to null
this.player.elements.fullscreen = player.config.fullscreen.container
&& closest(this.player.elements.container, player.config.fullscreen.container);
// Register event listeners
// Handle event (incase user presses escape etc)
on.call(
@ -126,7 +131,7 @@ class Fullscreen {
get target() {
return browser.isIos && this.player.config.fullscreen.iosNative
? this.player.media
: this.player.elements.container;
: this.player.elements.fullscreen || this.player.elements.container;
}
onChange() {

View File

@ -814,6 +814,17 @@ class Listeners {
elements.controls.hover = !player.touch && event.type === 'mouseenter';
});
// Also update controls.hover state for any non-player children of fullscreen element (as above)
if (elements.fullscreen) {
for (let i = 0; i < elements.fullscreen.children.length; i++) {
if (!elements.fullscreen.children[i].contains(elements.container)) {
this.bind(elements.fullscreen.children[i], 'mouseenter mouseleave', event => {
elements.controls.hover = !player.touch && event.type === 'mouseenter';
});
}
}
}
// Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);

View File

@ -80,6 +80,7 @@ class Plyr {
// Elements cache
this.elements = {
container: null,
fullscreen: null,
captions: null,
buttons: {},
display: {},
@ -282,6 +283,9 @@ class Plyr {
});
}
// Setup fullscreen
this.fullscreen = new Fullscreen(this);
// Setup interface
// If embed but not fully supported, build interface now to avoid flash of controls
if (this.isHTML5 || (this.isEmbed && !this.supported.ui)) {
@ -294,9 +298,6 @@ class Plyr {
// Global listeners
this.listeners.global();
// Setup fullscreen
this.fullscreen = new Fullscreen(this);
// Setup ads if provided
if (this.config.ads.enabled) {
this.ads = new Ads(this);

View File

@ -237,6 +237,28 @@ export function matches(element, selector) {
return method.call(element, selector);
}
// Closest ancestor element matching selector (also tests element itself)
export function closest(element, selector) {
const {prototype} = Element;
// https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
function closestElement() {
let el = this;
do {
if (matches.matches(el, selector)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
}
const method =
prototype.closest ||
closestElement;
return method.call(element, selector);
}
// Find all elements
export function getElements(selector) {
return this.elements.container.querySelectorAll(selector);