From 5e3f026007bff528c0de9e588302b9d16abeb054 Mon Sep 17 00:00:00 2001 From: Mohammed Nagdy <42354467+MohammedNagdy@users.noreply.github.com> Date: Sun, 7 Jan 2024 19:45:43 +0000 Subject: [PATCH 1/2] Allow jwplayer JS scripts to be loaded asynchronously --- coverage/coverage-summary.json | 10 +++++----- lib/jwplayer-react.js | 2 +- src/jwplayer.jsx | 3 ++- src/util.js | 8 +++++--- test/jwplayer-react.test.js | 2 +- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/coverage/coverage-summary.json b/coverage/coverage-summary.json index f2794a6..fe30535 100644 --- a/coverage/coverage-summary.json +++ b/coverage/coverage-summary.json @@ -1,6 +1,6 @@ -{"total": {"lines":{"total":79,"covered":79,"skipped":0,"pct":100},"statements":{"total":85,"covered":85,"skipped":0,"pct":100},"functions":{"total":23,"covered":23,"skipped":0,"pct":100},"branches":{"total":36,"covered":36,"skipped":0,"pct":100},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"}} -,"/home/runner/work/jwplayer-react/jwplayer-react/src/config-props.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} -,"/home/runner/work/jwplayer-react/jwplayer-react/src/const.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} -,"/home/runner/work/jwplayer-react/jwplayer-react/src/jwplayer.jsx": {"lines":{"total":56,"covered":56,"skipped":0,"pct":100},"functions":{"total":16,"covered":16,"skipped":0,"pct":100},"statements":{"total":59,"covered":59,"skipped":0,"pct":100},"branches":{"total":26,"covered":26,"skipped":0,"pct":100}} -,"/home/runner/work/jwplayer-react/jwplayer-react/src/util.js": {"lines":{"total":19,"covered":19,"skipped":0,"pct":100},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":22,"covered":22,"skipped":0,"pct":100},"branches":{"total":10,"covered":10,"skipped":0,"pct":100}} +{"total": {"lines":{"total":81,"covered":81,"skipped":0,"pct":100},"statements":{"total":86,"covered":85,"skipped":0,"pct":98.83},"functions":{"total":22,"covered":22,"skipped":0,"pct":100},"branches":{"total":36,"covered":35,"skipped":0,"pct":97.22},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"}} +,"/workspaces/jwplayer-react/src/config-props.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} +,"/workspaces/jwplayer-react/src/const.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} +,"/workspaces/jwplayer-react/src/jwplayer.jsx": {"lines":{"total":57,"covered":57,"skipped":0,"pct":100},"functions":{"total":15,"covered":15,"skipped":0,"pct":100},"statements":{"total":59,"covered":59,"skipped":0,"pct":100},"branches":{"total":26,"covered":26,"skipped":0,"pct":100}} +,"/workspaces/jwplayer-react/src/util.js": {"lines":{"total":20,"covered":20,"skipped":0,"pct":100},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":23,"covered":22,"skipped":0,"pct":95.65},"branches":{"total":10,"covered":9,"skipped":0,"pct":90}} } diff --git a/lib/jwplayer-react.js b/lib/jwplayer-react.js index 0af3bf8..2c8c766 100644 --- a/lib/jwplayer-react.js +++ b/lib/jwplayer-react.js @@ -1,2 +1,2 @@ /*! For license information please see jwplayer-react.js.LICENSE.txt */ -(()=>{var e={262:e=>{e.exports=new Set(["hlsjsProgressive","__abSendDomainToFeeds","_abZoomThumbnail","advertising","aboutlink","abouttext","aestoken","allowFullscreen","analytics","androidhls","aspectratio","autoPause","autostart","base","captions","cast","controls","defaultBandwidthEstimate","description","displaydescription","displayHeading","displayPlaybackLabel","displaytitle","drm","duration","enableDefaultCaptions","events","file","forceLocalizationDefaults","fwassetid","floating","ga","generateSEOMetadata","height","hlsjsConfig","hlsjsdefault","horizontalVolumeSlider","image","intl","key","listbar","liveSyncDuration","liveTimeout","localization","logo","mediaid","mute","nextUpDisplay","nextupoffset","pad","ph","pid","pipIcon","playbackRateControls","playbackRates","playlist","playlistIndex","plugins","preload","qualityLabel","qualityLabels","recommendations","related","renderCaptionsNatively","repeat","safarihlsjs","sdkplatform","selectedBitrate","setTimeEvents","skin","sharing","sources","stagevideo","streamtype","stretching","title","tracks","type","variations","volume","width","withCredentials","doNotTrack","doNotTrackCookies","images"])},418:e=>{"use strict";var t=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,n=Object.prototype.propertyIsEnumerable;e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},r=0;r<10;r++)t["_"+String.fromCharCode(r)]=r;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var n={};return"abcdefghijklmnopqrst".split("").forEach((function(e){n[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},n)).join("")}catch(e){return!1}}()?Object.assign:function(e,o){for(var a,i,s=function(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}(e),u=1;u{"use strict";r(418);var n=r(294),o=60103;if("function"==typeof Symbol&&Symbol.for){var a=Symbol.for;o=a("react.element"),a("react.fragment")}var i=n.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,s=Object.prototype.hasOwnProperty,u={key:!0,ref:!0,__self:!0,__source:!0};t.jsx=function(e,t,r){var n,a={},l=null,c=null;for(n in void 0!==r&&(l=""+r),void 0!==t.key&&(l=""+t.key),void 0!==t.ref&&(c=t.ref),t)s.call(t,n)&&!u.hasOwnProperty(n)&&(a[n]=t[n]);if(e&&e.defaultProps)for(n in t=e.defaultProps)void 0===a[n]&&(a[n]=t[n]);return{$$typeof:o,type:e,key:l,ref:c,props:a,_owner:i.current}}},408:(e,t,r)=>{"use strict";var n=r(418),o=60103,a=60106;t.Fragment=60107,t.StrictMode=60108,t.Profiler=60114;var i=60109,s=60110,u=60112;t.Suspense=60113;var l=60115,c=60116;if("function"==typeof Symbol&&Symbol.for){var f=Symbol.for;o=f("react.element"),a=f("react.portal"),t.Fragment=f("react.fragment"),t.StrictMode=f("react.strict_mode"),t.Profiler=f("react.profiler"),i=f("react.provider"),s=f("react.context"),u=f("react.forward_ref"),t.Suspense=f("react.suspense"),l=f("react.memo"),c=f("react.lazy")}var p="function"==typeof Symbol&&Symbol.iterator;function d(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,r=1;r{"use strict";e.exports=r(408)},893:(e,t,r)=>{"use strict";e.exports=r(251)}},t={};function r(n){var o=t[n];if(void 0!==o)return o.exports;var a=t[n]={exports:{}};return e[n](a,a.exports,r),a.exports}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var n={};(()=>{"use strict";r.r(n),r.d(n,{default:()=>p});var e=r(294);const t="all",o="^on(.*)";var a=r(262),i=r.n(a);let s=-1;function u(e,t){const r=e.match(t)||["",""];return r[1].charAt(0).toLowerCase()+r[1].slice(1)}var l=r(893);function c(e){return(r,n)=>{Object.keys(e).forEach((a=>{const i=u(a,o);i===r&&e[a](n),i===t&&e[a](r,n)}))}}class f extends e.Component{constructor(t){super(t),this.ref=t.ref||e.createRef(),this.config=function(e){const t={};return Object.keys(e).forEach((r=>{i().has(r)&&(t[r]=e[r])})),{...e.config,...t,isReactComponent:!0}}(t),this.player=null,this.didMountCallback=t.didMountCallback,this.willUnmountCallback=t.willUnmountCallback,this.id=t.id||(s++,`jwplayer-${s}`),this.onHandler=null,this.library=t.library}async componentDidMount(){if(await function(e){if(!window.jwplayer&&!e)throw new Error("jwplayer-react requires either a library prop, or a library script");return window.jwplayer?Promise.resolve():function(e){return new Promise(((t,r)=>{const n=document.createElement("script");n.onload=t,n.onerror=r,n.src=e,document.body.append(n)}))}(e)}(this.library),this.player=this.createPlayer(),this.createEventListeners(),this.didMountCallback){const{player:e,id:t}=this;this.didMountCallback({player:e,id:t})}}shouldComponentUpdate(e){return!(!this.player||this.didOnEventsChange(e)&&(this.updateOnEventListener(e),1))}componentWillUnmount(){if(this.willUnmountCallback){const{player:e,id:t}=this;this.willUnmountCallback({player:e,id:t})}this.player&&(this.player.off(),this.player.remove(),this.player=null)}createPlayer(){const{config:e,ref:t}=this,r={...window.jwDefaults,...e},n=t.current;return window.jwplayer(n.id).setup(r)}didOnEventsChange(e){const t=e=>e.match(o),r=Object.keys(this.props).filter(t).sort(),n=Object.keys(e).filter(t).sort();return n.length!==r.length||n.some(((t,n)=>r[n]!==t||e[t]!==this.props[t]))}createEventListeners(){Object.keys(this.props).forEach((e=>{const t=u(e,"^once(.*)");t&&this.player.once(t,this.props[e])})),this.onHandler=c(this.props),this.player.on(t,this.onHandler)}updateOnEventListener(e){this.onHandler&&this.player.off(t,this.onHandler),this.onHandler=c(e),this.player.on(t,this.onHandler)}render(){return(0,l.jsx)("div",{id:this.id,ref:this.ref})}}const p=f})(),module.exports=n})(); \ No newline at end of file +(()=>{var e={262:e=>{e.exports=new Set(["hlsjsProgressive","__abSendDomainToFeeds","_abZoomThumbnail","advertising","aboutlink","abouttext","aestoken","allowFullscreen","analytics","androidhls","aspectratio","autoPause","autostart","base","captions","cast","controls","defaultBandwidthEstimate","description","displaydescription","displayHeading","displayPlaybackLabel","displaytitle","drm","duration","enableDefaultCaptions","events","file","forceLocalizationDefaults","fwassetid","floating","ga","generateSEOMetadata","height","hlsjsConfig","hlsjsdefault","horizontalVolumeSlider","image","intl","key","listbar","liveSyncDuration","liveTimeout","localization","logo","mediaid","mute","nextUpDisplay","nextupoffset","pad","ph","pid","pipIcon","playbackRateControls","playbackRates","playlist","playlistIndex","plugins","preload","qualityLabel","qualityLabels","recommendations","related","renderCaptionsNatively","repeat","safarihlsjs","sdkplatform","selectedBitrate","setTimeEvents","skin","sharing","sources","stagevideo","streamtype","stretching","title","tracks","type","variations","volume","width","withCredentials","doNotTrack","doNotTrackCookies","images"])},418:e=>{"use strict";var t=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,n=Object.prototype.propertyIsEnumerable;e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},r=0;r<10;r++)t["_"+String.fromCharCode(r)]=r;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var n={};return"abcdefghijklmnopqrst".split("").forEach((function(e){n[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},n)).join("")}catch(e){return!1}}()?Object.assign:function(e,o){for(var a,i,s=function(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}(e),u=1;u{"use strict";r(418);var n=r(294),o=60103;if("function"==typeof Symbol&&Symbol.for){var a=Symbol.for;o=a("react.element"),a("react.fragment")}var i=n.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,s=Object.prototype.hasOwnProperty,u={key:!0,ref:!0,__self:!0,__source:!0};t.jsx=function(e,t,r){var n,a={},l=null,c=null;for(n in void 0!==r&&(l=""+r),void 0!==t.key&&(l=""+t.key),void 0!==t.ref&&(c=t.ref),t)s.call(t,n)&&!u.hasOwnProperty(n)&&(a[n]=t[n]);if(e&&e.defaultProps)for(n in t=e.defaultProps)void 0===a[n]&&(a[n]=t[n]);return{$$typeof:o,type:e,key:l,ref:c,props:a,_owner:i.current}}},408:(e,t,r)=>{"use strict";var n=r(418),o=60103,a=60106;t.Fragment=60107,t.StrictMode=60108,t.Profiler=60114;var i=60109,s=60110,u=60112;t.Suspense=60113;var l=60115,c=60116;if("function"==typeof Symbol&&Symbol.for){var f=Symbol.for;o=f("react.element"),a=f("react.portal"),t.Fragment=f("react.fragment"),t.StrictMode=f("react.strict_mode"),t.Profiler=f("react.profiler"),i=f("react.provider"),s=f("react.context"),u=f("react.forward_ref"),t.Suspense=f("react.suspense"),l=f("react.memo"),c=f("react.lazy")}var p="function"==typeof Symbol&&Symbol.iterator;function d(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,r=1;r{"use strict";e.exports=r(408)},893:(e,t,r)=>{"use strict";e.exports=r(251)}},t={};function r(n){var o=t[n];if(void 0!==o)return o.exports;var a=t[n]={exports:{}};return e[n](a,a.exports,r),a.exports}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var n={};(()=>{"use strict";r.r(n),r.d(n,{default:()=>p});var e=r(294);const t="all",o="^on(.*)";var a=r(262),i=r.n(a);let s=-1;function u(e,t){const r=e.match(t)||["",""];return r[1].charAt(0).toLowerCase()+r[1].slice(1)}var l=r(893);function c(e){return(r,n)=>{Object.keys(e).forEach((a=>{const i=u(a,o);i===r&&e[a](n),i===t&&e[a](r,n)}))}}class f extends e.Component{constructor(t){super(t),this.ref=t.ref||e.createRef(),this.config=function(e){const t={};return Object.keys(e).forEach((r=>{i().has(r)&&(t[r]=e[r])})),{...e.config,...t,isReactComponent:!0}}(t),this.player=null,this.didMountCallback=t.didMountCallback,this.willUnmountCallback=t.willUnmountCallback,this.id=t.id||(s++,`jwplayer-${s}`),this.onHandler=null,this.library=t.library,this.async=t.async}async componentDidMount(){if(await function(e,t=!1){if(!window.jwplayer&&!e)throw new Error("jwplayer-react requires either a library prop, or a library script");return window.jwplayer?Promise.resolve():function(e,t){return new Promise(((r,n)=>{const o=document.createElement("script");o.onload=r,o.onerror=n,o.src=e,t&&o.setAttribute("async",""),document.body.append(o)}))}(e,t)}(this.library,this.async),this.player=this.createPlayer(),this.createEventListeners(),this.didMountCallback){const{player:e,id:t}=this;this.didMountCallback({player:e,id:t})}}shouldComponentUpdate(e){return!(!this.player||this.didOnEventsChange(e)&&(this.updateOnEventListener(e),1))}componentWillUnmount(){if(this.willUnmountCallback){const{player:e,id:t}=this;this.willUnmountCallback({player:e,id:t})}this.player&&(this.player.off(),this.player.remove(),this.player=null)}createPlayer(){const{config:e,ref:t}=this,r={...window.jwDefaults,...e},n=t.current;return window.jwplayer(n.id).setup(r)}didOnEventsChange(e){const t=e=>e.match(o),r=Object.keys(this.props).filter(t).sort(),n=Object.keys(e).filter(t).sort();return n.length!==r.length||n.some(((t,n)=>r[n]!==t||e[t]!==this.props[t]))}createEventListeners(){Object.keys(this.props).forEach((e=>{const t=u(e,"^once(.*)");t&&this.player.once(t,this.props[e])})),this.onHandler=c(this.props),this.player.on(t,this.onHandler)}updateOnEventListener(e){this.onHandler&&this.player.off(t,this.onHandler),this.onHandler=c(e),this.player.on(t,this.onHandler)}render(){return(0,l.jsx)("div",{id:this.id,ref:this.ref})}}const p=f})(),module.exports=n})(); \ No newline at end of file diff --git a/src/jwplayer.jsx b/src/jwplayer.jsx index 80d518c..18bd07d 100644 --- a/src/jwplayer.jsx +++ b/src/jwplayer.jsx @@ -30,10 +30,11 @@ class JWPlayer extends React.Component { this.id = props.id || generateUniqueId(); this.onHandler = null; this.library = props.library; + this.async = props.async; } async componentDidMount() { - await loadPlayer(this.library); + await loadPlayer(this.library, this.async); this.player = this.createPlayer(); this.createEventListeners(); diff --git a/src/util.js b/src/util.js index ee3e4cf..d614c16 100644 --- a/src/util.js +++ b/src/util.js @@ -7,22 +7,24 @@ export function generateUniqueId() { return id; } -export function createPlayerLoadPromise(url) { +export function createPlayerLoadPromise(url, isAsync) { return new Promise((res, rej) => { const script = document.createElement('script'); script.onload = res; script.onerror = rej; script.src = url; + // Optional to add script async for better performance + if (isAsync) script.setAttribute('async', ''); document.body.append(script); }); } -export function loadPlayer(url) { +export function loadPlayer(url, isScriptAsync = false) { if (!window.jwplayer && !url) throw new Error('jwplayer-react requires either a library prop, or a library script'); if (window.jwplayer) return Promise.resolve(); - return createPlayerLoadPromise(url); + return createPlayerLoadPromise(url, isScriptAsync); } export function generateConfig(props) { diff --git a/test/jwplayer-react.test.js b/test/jwplayer-react.test.js index fe7a27e..f5bebe5 100644 --- a/test/jwplayer-react.test.js +++ b/test/jwplayer-react.test.js @@ -13,7 +13,7 @@ Enzyme.configure({ adapter: new Adapter() }); const noop = () => {}; const playlist = 'https://cdn.jwplayer.com/v2/media/1g8jjku3'; -const library = 'https://cdn.jwplayer.com/libraries/lqsWlr4Z.js'; +const library = 'https://cdn.jwplayer.com/libraries/IDzF9Zmk.js'; let expectedInstance = -1; beforeEach(() => { From c3d5a839d7b3d565994c5b864a8dc7c0f75314ca Mon Sep 17 00:00:00 2001 From: Mohammed Nagdy <42354467+MohammedNagdy@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:31:32 +0000 Subject: [PATCH 2/2] Added async and defer in the options variable in the constructor --- src/jwplayer.jsx | 7 +++++-- src/util.js | 11 ++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/jwplayer.jsx b/src/jwplayer.jsx index 18bd07d..0b682b8 100644 --- a/src/jwplayer.jsx +++ b/src/jwplayer.jsx @@ -30,11 +30,14 @@ class JWPlayer extends React.Component { this.id = props.id || generateUniqueId(); this.onHandler = null; this.library = props.library; - this.async = props.async; + this.options = { + async: props.async || false, + defer: props.defer || false + }; } async componentDidMount() { - await loadPlayer(this.library, this.async); + await loadPlayer(this.library, this.options); this.player = this.createPlayer(); this.createEventListeners(); diff --git a/src/util.js b/src/util.js index d614c16..453e75b 100644 --- a/src/util.js +++ b/src/util.js @@ -7,24 +7,25 @@ export function generateUniqueId() { return id; } -export function createPlayerLoadPromise(url, isAsync) { +export function createPlayerLoadPromise(url, scriptOptions) { return new Promise((res, rej) => { const script = document.createElement('script'); script.onload = res; script.onerror = rej; script.src = url; - // Optional to add script async for better performance - if (isAsync) script.setAttribute('async', ''); + // Optional to add script async or defer for better performance + script.async = scriptOptions.async; + script.defer = scriptOptions.defer; document.body.append(script); }); } -export function loadPlayer(url, isScriptAsync = false) { +export function loadPlayer(url, options) { if (!window.jwplayer && !url) throw new Error('jwplayer-react requires either a library prop, or a library script'); if (window.jwplayer) return Promise.resolve(); - return createPlayerLoadPromise(url, isScriptAsync); + return createPlayerLoadPromise(url, options); } export function generateConfig(props) {