From 44ef0bbc8754a11b0f6411e32e5383fd8573a88c Mon Sep 17 00:00:00 2001 From: Som Meaden Date: Sat, 4 Apr 2020 13:02:17 +1000 Subject: [PATCH 1/7] include `npm run serve` shortcut for `gulp serve` (useful where gulp isn't installed globally) --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 1267d0ea..707b86ec 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "browserslist": "> 1%", "scripts": { "build": "gulp build", + "serve": "gulp serve", "lint": "eslint src/js && npm run-script remark", "lint:fix": "eslint --fix src/js", "remark": "remark -f --use 'validate-links=repository:\"sampotts/plyr\"' '{,!(node_modules),.?**/}*.md'", From 49ed2cac4eff3ff3eae7a2c72e5280a302906f7d Mon Sep 17 00:00:00 2001 From: Som Meaden Date: Sat, 4 Apr 2020 13:43:51 +1000 Subject: [PATCH 2/7] 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. --- demo/dist/demo.js | 63 ++++++++++++++++++++---- demo/dist/demo.min.js | 2 +- demo/dist/demo.min.js.map | 2 +- demo/index.html | 84 ++++++++++++++++++++++++++++++++ demo/src/js/demo.js | 6 +++ dist/plyr.js | 57 ++++++++++++++++++---- dist/plyr.min.js | 2 +- dist/plyr.min.js.map | 2 +- dist/plyr.min.mjs | 2 +- dist/plyr.min.mjs.map | 2 +- dist/plyr.mjs | 57 ++++++++++++++++++---- dist/plyr.polyfilled.js | 57 ++++++++++++++++++---- dist/plyr.polyfilled.min.js | 4 +- dist/plyr.polyfilled.min.js.map | 2 +- dist/plyr.polyfilled.min.mjs | 2 +- dist/plyr.polyfilled.min.mjs.map | 2 +- dist/plyr.polyfilled.mjs | 57 ++++++++++++++++++---- readme.md | 2 +- src/js/config/defaults.js | 3 ++ src/js/fullscreen.js | 9 +++- src/js/listeners.js | 11 +++++ src/js/plyr.js | 7 +-- src/js/utils/elements.js | 22 +++++++++ 23 files changed, 390 insertions(+), 67 deletions(-) diff --git a/demo/dist/demo.js b/demo/dist/demo.js index 8c8b4152..e2d8f3ea 100644 --- a/demo/dist/demo.js +++ b/demo/dist/demo.js @@ -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 diff --git a/demo/dist/demo.min.js b/demo/dist/demo.min.js index a94e2f80..4dc44c56 100644 --- a/demo/dist/demo.min.js +++ b/demo/dist/demo.min.js @@ -1,4 +1,4 @@ "object"==typeof navigator&&function(){"use strict";var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function t(e,t){return e(t={exports:{}},t.exports),t.exports}var n=function(e){return e&&e.Math==Math&&e},r=n("object"==typeof globalThis&&globalThis)||n("object"==typeof window&&window)||n("object"==typeof self&&self)||n("object"==typeof e&&e)||Function("return this")(),i=function(e){try{return!!e()}catch(e){return!0}},o=!i((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]})),a={}.propertyIsEnumerable,s=Object.getOwnPropertyDescriptor,c={f:s&&!a.call({1:2},1)?function(e){var t=s(this,e);return!!t&&t.enumerable}:a},l=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}},u={}.toString,f=function(e){return u.call(e).slice(8,-1)},h="".split,d=i((function(){return!Object("z").propertyIsEnumerable(0)}))?function(e){return"String"==f(e)?h.call(e,""):Object(e)}:Object,p=function(e){if(null==e)throw TypeError("Can't call method on "+e);return e},g=function(e){return d(p(e))},m=function(e){return"object"==typeof e?null!==e:"function"==typeof e},v=function(e,t){if(!m(e))return e;var n,r;if(t&&"function"==typeof(n=e.toString)&&!m(r=n.call(e)))return r;if("function"==typeof(n=e.valueOf)&&!m(r=n.call(e)))return r;if(!t&&"function"==typeof(n=e.toString)&&!m(r=n.call(e)))return r;throw TypeError("Can't convert object to primitive value")},y={}.hasOwnProperty,b=function(e,t){return y.call(e,t)},w=r.document,k=m(w)&&m(w.createElement),S=function(e){return k?w.createElement(e):{}},E=!o&&!i((function(){return 7!=Object.defineProperty(S("div"),"a",{get:function(){return 7}}).a})),_=Object.getOwnPropertyDescriptor,T={f:o?_:function(e,t){if(e=g(e),t=v(t,!0),E)try{return _(e,t)}catch(e){}if(b(e,t))return l(!c.f.call(e,t),e[t])}},A=function(e){if(!m(e))throw TypeError(String(e)+" is not an object");return e},x=Object.defineProperty,O={f:o?x:function(e,t,n){if(A(e),t=v(t,!0),A(n),E)try{return x(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(e[t]=n.value),e}},C=o?function(e,t,n){return O.f(e,t,l(1,n))}:function(e,t,n){return e[t]=n,e},P=function(e,t){try{C(r,e,t)}catch(n){r[e]=t}return t},I=r["__core-js_shared__"]||P("__core-js_shared__",{}),R=Function.toString;"function"!=typeof I.inspectSource&&(I.inspectSource=function(e){return R.call(e)});var j,L,M,N=I.inspectSource,U=r.WeakMap,F="function"==typeof U&&/native code/.test(N(U)),D=t((function(e){(e.exports=function(e,t){return I[e]||(I[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.6.4",mode:"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})})),B=0,q=Math.random(),H=function(e){return"Symbol("+String(void 0===e?"":e)+")_"+(++B+q).toString(36)},V=D("keys"),z=function(e){return V[e]||(V[e]=H(e))},W={},$=r.WeakMap;if(F){var K=new $,Y=K.get,G=K.has,X=K.set;j=function(e,t){return X.call(K,e,t),t},L=function(e){return Y.call(K,e)||{}},M=function(e){return G.call(K,e)}}else{var J=z("state");W[J]=!0,j=function(e,t){return C(e,J,t),t},L=function(e){return b(e,J)?e[J]:{}},M=function(e){return b(e,J)}}var Q={set:j,get:L,has:M,enforce:function(e){return M(e)?L(e):j(e,{})},getterFor:function(e){return function(t){var n;if(!m(t)||(n=L(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return n}}},Z=t((function(e){var t=Q.get,n=Q.enforce,i=String(String).split("String");(e.exports=function(e,t,o,a){var s=!!a&&!!a.unsafe,c=!!a&&!!a.enumerable,l=!!a&&!!a.noTargetGet;"function"==typeof o&&("string"!=typeof t||b(o,"name")||C(o,"name",t),n(o).source=i.join("string"==typeof t?t:"")),e!==r?(s?!l&&e[t]&&(c=!0):delete e[t],c?e[t]=o:C(e,t,o)):c?e[t]=o:P(t,o)})(Function.prototype,"toString",(function(){return"function"==typeof this&&t(this).source||N(this)}))})),ee=r,te=function(e){return"function"==typeof e?e:void 0},ne=function(e,t){return arguments.length<2?te(ee[e])||te(r[e]):ee[e]&&ee[e][t]||r[e]&&r[e][t]},re=Math.ceil,ie=Math.floor,oe=function(e){return isNaN(e=+e)?0:(e>0?ie:re)(e)},ae=Math.min,se=function(e){return e>0?ae(oe(e),9007199254740991):0},ce=Math.max,le=Math.min,ue=function(e,t){var n=oe(e);return n<0?ce(n+t,0):le(n,t)},fe=function(e){return function(t,n,r){var i,o=g(t),a=se(o.length),s=ue(r,a);if(e&&n!=n){for(;a>s;)if((i=o[s++])!=i)return!0}else for(;a>s;s++)if((e||s in o)&&o[s]===n)return e||s||0;return!e&&-1}},he={includes:fe(!0),indexOf:fe(!1)},de=he.indexOf,pe=function(e,t){var n,r=g(e),i=0,o=[];for(n in r)!b(W,n)&&b(r,n)&&o.push(n);for(;t.length>i;)b(r,n=t[i++])&&(~de(o,n)||o.push(n));return o},ge=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],me=ge.concat("length","prototype"),ve={f:Object.getOwnPropertyNames||function(e){return pe(e,me)}},ye={f:Object.getOwnPropertySymbols},be=ne("Reflect","ownKeys")||function(e){var t=ve.f(A(e)),n=ye.f;return n?t.concat(n(e)):t},we=function(e,t){for(var n=be(t),r=O.f,i=T.f,o=0;oy;y++)if((a||y in g)&&(h=m(f=g[y],y,p),e))if(t)w[y]=h;else if(h)switch(e){case 3:return!0;case 5:return f;case 6:return y;case 2:He.call(w,f)}else if(i)return!1;return o?-1:r||i?i:w}},ze={forEach:Ve(0),map:Ve(1),filter:Ve(2),some:Ve(3),every:Ve(4),find:Ve(5),findIndex:Ve(6)},We=function(e,t){var n=[][e];return!!n&&i((function(){n.call(null,t||function(){throw 1},1)}))},$e=Object.defineProperty,Ke={},Ye=function(e){throw e},Ge=function(e,t){if(b(Ke,e))return Ke[e];t||(t={});var n=[][e],r=!!b(t,"ACCESSORS")&&t.ACCESSORS,a=b(t,0)?t[0]:Ye,s=b(t,1)?t[1]:void 0;return Ke[e]=!!n&&!i((function(){if(r&&!o)return!0;var e={length:-1};r?$e(e,1,{enumerable:!0,get:Ye}):e[1]=1,n.call(e,a,s)}))},Xe=ze.forEach,Je=We("forEach"),Qe=Ge("forEach"),Ze=Je&&Qe?[].forEach:function(e){return Xe(this,e,arguments.length>1?arguments[1]:void 0)};Ce({target:"Array",proto:!0,forced:[].forEach!=Ze},{forEach:Ze});var et=function(e,t,n,r){try{return r?t(A(n)[0],n[1]):t(n)}catch(t){var i=e.return;throw void 0!==i&&A(i.call(e)),t}},tt={},nt=De("iterator"),rt=Array.prototype,it=function(e){return void 0!==e&&(tt.Array===e||rt[nt]===e)},ot=function(e,t,n){var r=v(t);r in e?O.f(e,r,l(0,n)):e[r]=n},at={};at[De("toStringTag")]="z";var st="[object z]"===String(at),ct=De("toStringTag"),lt="Arguments"==f(function(){return arguments}()),ut=st?f:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),ct))?n:lt?f(t):"Object"==(r=f(t))&&"function"==typeof t.callee?"Arguments":r},ft=De("iterator"),ht=function(e){if(null!=e)return e[ft]||e["@@iterator"]||tt[ut(e)]},dt=function(e){var t,n,r,i,o,a,s=Re(e),c="function"==typeof this?this:Array,l=arguments.length,u=l>1?arguments[1]:void 0,f=void 0!==u,h=ht(s),d=0;if(f&&(u=Ie(u,l>2?arguments[2]:void 0,2)),null==h||c==Array&&it(h))for(n=new c(t=se(s.length));t>d;d++)a=f?u(s[d],d):s[d],ot(n,d,a);else for(o=(i=h.call(s)).next,n=new c;!(r=o.call(i)).done;d++)a=f?et(i,u,[r.value,d],!0):r.value,ot(n,d,a);return n.length=d,n},pt=De("iterator"),gt=!1;try{var mt=0,vt={next:function(){return{done:!!mt++}},return:function(){gt=!0}};vt[pt]=function(){return this},Array.from(vt,(function(){throw 2}))}catch(e){}var yt=function(e,t){if(!t&&!gt)return!1;var n=!1;try{var r={};r[pt]=function(){return{next:function(){return{done:n=!0}}}},e(r)}catch(e){}return n},bt=!yt((function(e){Array.from(e)}));Ce({target:"Array",stat:!0,forced:bt},{from:dt});var wt,kt=Object.keys||function(e){return pe(e,ge)},St=o?Object.defineProperties:function(e,t){A(e);for(var n,r=kt(t),i=r.length,o=0;i>o;)O.f(e,n=r[o++],t[n]);return e},Et=ne("document","documentElement"),_t=z("IE_PROTO"),Tt=function(){},At=function(e){return"