From b777669a7e4b2efde7b7c5644e9681d20161661b Mon Sep 17 00:00:00 2001 From: DispatchCommit Date: Wed, 30 Jun 2021 09:29:00 -0700 Subject: [PATCH] Add pre-roll ads change ad macro url rework video.js load logic fix autoplay and retry errors yeet player.ended error fix another race condition fix another error message add allowPreRoll restrictions fix some lint issues remove annoying lint rule remove video.js lazy loading more linting fixes --- .eslintrc | 1 + package.json | 2 + .../plugins/videojs-aniview/plugin.js | 102 ++++++++++ .../viewers/videoViewer/internal/videojs.jsx | 181 ++++++++++++------ ui/component/viewers/videoViewer/view.jsx | 39 ++-- ui/scss/component/_file-render.scss | 6 +- yarn.lock | 68 ++++++- 7 files changed, 316 insertions(+), 83 deletions(-) create mode 100644 ui/component/viewers/videoViewer/internal/plugins/videojs-aniview/plugin.js diff --git a/.eslintrc b/.eslintrc index 54ef341a3..4e61c4af0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -35,6 +35,7 @@ "object-curly-spacing": 0, "one-var": 0, "prefer-promise-reject-errors": 0, + "promise/param-names": 0, "react/jsx-indent": 0, "react/jsx-no-comment-textnodes": 0, "react-hooks/exhaustive-deps": "warn", diff --git a/package.json b/package.json index 9f2ad5ba8..51ccb5214 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,8 @@ "remove-markdown": "^0.3.0", "source-map-explorer": "^2.5.2", "tempy": "^0.6.0", + "videojs-contrib-ads": "^6.9.0", + "videojs-ima": "^1.11.0", "videojs-logo": "^2.1.4" }, "devDependencies": { diff --git a/ui/component/viewers/videoViewer/internal/plugins/videojs-aniview/plugin.js b/ui/component/viewers/videoViewer/internal/plugins/videojs-aniview/plugin.js new file mode 100644 index 000000000..24da49bcb --- /dev/null +++ b/ui/component/viewers/videoViewer/internal/plugins/videojs-aniview/plugin.js @@ -0,0 +1,102 @@ +// Created by xander on 6/21/2021 +import videojs from 'video.js'; +import 'videojs-ima'; +const VERSION = '0.0.1'; + +const macroUrl = + 'https://vast.aniview.com/api/adserver61/vast/?AV_PUBLISHERID=60afcbc58cfdb065440d2426&AV_CHANNELID=60b354389c7adb506d0bd9a4&AV_URL=[URL_MACRO]&cb=[TIMESTAMP_MACRO]&AV_WIDTH=[WIDTH_MACRO]&AV_HEIGHT=[HEIGHT_MACRO]&AV_SCHAIN=[SCHAIN_MACRO]&AV_CCPA=[CCPA_MACRO]&AV_GDPR=[GDPR_MACRO]&AV_CONSENT=[CONSENT_MACRO]&skip=true&skiptimer=5&usevslot=true&hidecontrols=false'; + +const defaults = { + adTagUrl: macroUrl, + debug: false, +}; + +const Component = videojs.getComponent('Component'); +const registerPlugin = videojs.registerPlugin || videojs.plugin; + +class AniviewPlugin extends Component { + constructor(player, options) { + super(player, options); + + // Plugin started + if (options.debug) { + this.log(`Created aniview plugin.`); + } + + // To help with debugging, we'll add a global vjs object with the video js player + window.aniview = player; + + this.player = player; + + const google = window.google; + + player.ima({ + // adTagUrl: macroUrl, + id: 'ad_content_video', + vpaidMode: google.ima.ImaSdkSettings.VpaidMode.INSECURE, + adTagUrl: + 'https://vast.aniview.com/api/adserver61/vast/?AV_PUBLISHERID=60afcbc58cfdb065440d2426&AV_CHANNELID=60b354389c7adb506d0bd9a4', + }); + + // this.player.ads(); + // const serverUrl = this.player.ads.adMacroReplacement(macroUrl); + // this.log(serverUrl); + + // request ads whenever there's new video content + player.on('contentchanged', () => { + // in a real plugin, you might fetch your ad inventory here + player.trigger('adsready'); + }); + + // Plugin event listeners + player.on('readyforpreroll', (event) => this.onReadyForPreroll(event)); + } + + onReadyForPreroll(event) { + this.player.ads.startLinearAdMode(); + + // play your linear ad content + // in this example, we use a static mp4 + this.player.src('kitteh.mp4'); + + // send event when ad is playing to remove loading spinner + this.player.one('adplaying', () => { + this.player.trigger('ads-ad-started'); + }); + + // resume content when all your linear ads have finished + this.player.one('adended', () => { + this.player.ads.endLinearAdMode(); + }); + } + + log(...args) { + if (this.options_.debug) { + console.log(`Aniview Debug:`, JSON.stringify(args)); + } + } +} + +videojs.registerComponent('recsys', AniviewPlugin); + +const onPlayerReady = (player, options) => { + player.aniview = new AniviewPlugin(player, options); +}; + +/** + * Initialize the plugin. + * + * @function plugin + * @param {Object} [options={}] + */ +const plugin = function (options) { + this.ready(() => { + onPlayerReady(this, videojs.mergeOptions(defaults, options)); + }); +}; + +plugin.VERSION = VERSION; + +registerPlugin('aniview', plugin); + +export default plugin; diff --git a/ui/component/viewers/videoViewer/internal/videojs.jsx b/ui/component/viewers/videoViewer/internal/videojs.jsx index 5c15fdd8f..941c8d146 100644 --- a/ui/component/viewers/videoViewer/internal/videojs.jsx +++ b/ui/component/viewers/videoViewer/internal/videojs.jsx @@ -13,8 +13,13 @@ import hlsQualitySelector from './plugins/videojs-hls-quality-selector/plugin'; import recsys from './plugins/videojs-recsys/plugin'; import qualityLevels from 'videojs-contrib-quality-levels'; import isUserTyping from 'util/detect-typing'; +import 'videojs-contrib-ads'; +import 'videojs-ima'; +// import aniview from './plugins/videojs-aniview/plugin'; const isDev = process.env.NODE_ENV !== 'production'; +const macroUrl = + 'https://vast.aniview.com/api/adserver61/vast/?AV_PUBLISHERID=60afcbc58cfdb065440d2426&AV_CHANNELID=60b354389c7adb506d0bd9a4&AV_URL=[URL_MACRO]&cb=[TIMESTAMP_MACRO]&AV_WIDTH=[WIDTH_MACRO]&AV_HEIGHT=[HEIGHT_MACRO]&AV_SCHAIN=[SCHAIN_MACRO]&AV_CCPA=[CCPA_MACRO]&AV_GDPR=[GDPR_MACRO]&AV_CONSENT=[CONSENT_MACRO]&skip=true&skiptimer=5&usevslot=true&hidecontrols=false'; export type Player = { on: (string, (any) => void) => void, @@ -54,6 +59,7 @@ type Props = { adUrl: ?string, claimId: ?string, userId: ?number, + allowPreRoll: ?boolean, }; type VideoJSOptions = { @@ -78,7 +84,7 @@ const VIDEO_JS_OPTIONS: VideoJSOptions = { responsive: true, controls: true, html5: { - hls: { + vhs: { overrideNative: !videojs.browser.IS_ANY_SAFARI, }, }, @@ -190,6 +196,7 @@ export default React.memo(function VideoJs(props: Props) { adUrl, claimId, userId, + allowPreRoll, } = props; const [reload, setReload] = useState('initial'); @@ -321,7 +328,7 @@ export default React.memo(function VideoJs(props: Props) { // clashes if we add a new button in the future. // (2) We'll have to get 'makeSelectClientSetting(SETTINGS.VIDEO_THEATER_MODE)' // as a prop here so we can say "Theater mode|Default mode" instead of - // "Toggle Theather mode". + // "Toggle Theater mode". controlBar.getChild('Button').controlText(__('Toggle Theater mode (t)')); break; default: @@ -337,6 +344,8 @@ export default React.memo(function VideoJs(props: Props) { // The css starts as "hidden". We make it visible here without // re-rendering the whole thing. showTapButton(TAP.UNMUTE); + } else { + showTapButton(TAP.NONE); } } @@ -351,6 +360,10 @@ export default React.memo(function VideoJs(props: Props) { const player = playerRef.current; showTapButton(TAP.RETRY); + // reattach initial play listener in case we recover from error successfully + // $FlowFixMe + player.one('play', onInitialPlay); + if (player && player.loadingSpinner) { player.loadingSpinner.hide(); } @@ -473,9 +486,7 @@ export default React.memo(function VideoJs(props: Props) { // Create the video DOM element and wrapper function createVideoPlayerDOM(container) { - if (!container) { - return; - } + if (!container) return; // This seems like a poor way to generate the DOM for video.js const wrapper = document.createElement('div'); @@ -489,19 +500,51 @@ export default React.memo(function VideoJs(props: Props) { return el; } + function detectFileType() { + console.log(`Detecting file type via pre-fetch...`); + return new Promise(async (res, rej) => { + try { + const response = await fetch(source, { method: 'HEAD', cache: 'no-store' }); + + // Temp variables to hold results + let finalType = sourceType; + let finalSource = source; + + // override type if we receive an .m3u8 (transcoded mp4) + // do we need to check if explicitly redirected + // or is checking extension only a safer method + if (response && response.redirected && response.url && response.url.endsWith('m3u8')) { + finalType = 'application/x-mpegURL'; + finalSource = response.url; + } + + console.log(`File type is: ${finalType}`); + + // Modify video source in options + videoJsOptions.sources = [ + { + src: finalSource, + type: finalType, + }, + ]; + + return res(videoJsOptions); + } catch (error) { + console.error(`Failed to pre-fetch video!`); + return rej(error); + } + }); + } + // Initialize video.js function initializeVideoPlayer(el) { - if (!el) { - return; - } + if (!el) return; const vjs = videojs(el, videoJsOptions, () => { const player = playerRef.current; // this seems like a weird thing to have to check for here - if (!player) { - return; - } + if (!player) return; // Add various event listeners to player player.one('play', onInitialPlay); @@ -517,13 +560,45 @@ export default React.memo(function VideoJs(props: Props) { // Replace volume bar with custom LBRY volume bar LbryVolumeBarClass.replaceExisting(player); + // Add reloadSourceOnError plugin + player.reloadSourceOnError({ errorInterval: 10 }); + // initialize mobile UI player.mobileUi(); // Inits mobile version. No-op if Desktop. + // Add quality selector to player + player.hlsQualitySelector({ + displayCurrentQuality: true, + }); + + // Add recsys plugin + // TODO: Add an if(odysee.com) around this function to only use recsys on odysee + player.recsys({ + videoId: claimId, + userId: userId, + }); + + // set playsinline for mobile + // TODO: make this better + player.children_[0].setAttribute('playsinline', ''); + // I think this is a callback function onPlayerReady(player); }); + // pre-roll ads + // This must be initialized earlier than everything else + // otherwise a race condition occurs if we place this in the onReady call back + if (allowPreRoll && !SIMPLE_SITE && window.google) { + const google = window.google; + // player.aniview(); + vjs.ima({ + // $FlowFixMe + vpaidMode: google.ima.ImaSdkSettings.VpaidMode.INSECURE, + adTagUrl: macroUrl, + }); + } + // fixes #3498 (https://github.com/lbryio/lbry-desktop/issues/3498) // summary: on firefox the focus would stick to the fullscreen button which caused buggy behavior with spacebar vjs.on('fullscreenchange', () => document.activeElement && document.activeElement.blur()); @@ -534,16 +609,21 @@ export default React.memo(function VideoJs(props: Props) { // This lifecycle hook is only called once (on mount), or when `isAudio` changes. useEffect(() => { const vjsElement = createVideoPlayerDOM(containerRef.current); - const vjsPlayer = initializeVideoPlayer(vjsElement); - // Add reference to player to global scope - window.player = vjsPlayer; + // Detect source file type via pre-fetch (async) + detectFileType().then(() => { + // Initialize Video.js + const vjsPlayer = initializeVideoPlayer(vjsElement); - // Set reference in component state - playerRef.current = vjsPlayer; + // Add reference to player to global scope + window.player = vjsPlayer; - // Add event listener for keyboard shortcuts - window.addEventListener('keydown', handleKeyDown); + // Set reference in component state + playerRef.current = vjsPlayer; + + // Add event listener for keyboard shortcuts + window.addEventListener('keydown', handleKeyDown); + }); // Cleanup return () => { @@ -561,51 +641,28 @@ export default React.memo(function VideoJs(props: Props) { useEffect(() => { // For some reason the video player is responsible for detecting content type this way fetch(source, { method: 'HEAD', cache: 'no-store' }).then((response) => { - const player = playerRef.current; - - if (!player) { - return; - } - - let type = sourceType; + let finalType = sourceType; let finalSource = source; + // override type if we receive an .m3u8 (transcoded mp4) + // do we need to check if explicitly redirected + // or is checking extension only a safer method if (response && response.redirected && response.url && response.url.endsWith('m3u8')) { - type = 'application/x-mpegURL'; + finalType = 'application/x-mpegURL'; finalSource = response.url; } - // Update player poster - // note: the poster prop seems to return null usually. - if (poster) player.poster(poster); + // Modify video source in options + videoJsOptions.sources = [ + { + src: finalSource, + type: finalType, + }, + ]; // Update player source - player.src({ - src: finalSource, - type: type, - }); - - // set playsinline for mobile - player.children_[0].setAttribute('playsinline', ''); - - // Add quality selector to player - player.hlsQualitySelector({ - displayCurrentQuality: true, - }); - - // Add recsys plugin - if (SIMPLE_SITE) { - player.recsys({ - videoId: claimId, - userId: userId, - }); - } - - // Update player source - player.src({ - src: finalSource, - type: type, - }); + const player = playerRef.current; + if (!player) return; // PR #5570: Temp workaround to avoid double Play button until the next re-architecture. if (!player.paused()) { @@ -614,6 +671,20 @@ export default React.memo(function VideoJs(props: Props) { }); }, [source, reload]); + // Load IMA3 SDK for aniview + useEffect(() => { + const script = document.createElement('script'); + script.src = `https://imasdk.googleapis.com/js/sdkloader/ima3.js`; + script.async = true; + // $FlowFixMe + document.body.appendChild(script); + + return () => { + // $FlowFixMe + document.body.removeChild(script); + }; + }); + return ( // $FlowFixMe
diff --git a/ui/component/viewers/videoViewer/view.jsx b/ui/component/viewers/videoViewer/view.jsx index b2aaf8a10..43727f8ea 100644 --- a/ui/component/viewers/videoViewer/view.jsx +++ b/ui/component/viewers/videoViewer/view.jsx @@ -178,7 +178,9 @@ function VideoViewer(props: Props) { } else if (autoplaySetting) { setShowAutoplayCountdown(true); } - }, [embedded, setIsEndededEmbed, autoplaySetting, setShowAutoplayCountdown, adUrl, setAdUrl]); + + clearPosition(uri); + }, [embedded, setIsEndededEmbed, autoplaySetting, setShowAutoplayCountdown, adUrl, setAdUrl, clearPosition, uri]); function onPlay(player) { setIsLoading(false); @@ -187,21 +189,17 @@ function VideoViewer(props: Props) { setIsEndededEmbed(false); } - function onPause(player) { + function onPause(event, player) { setIsPlaying(false); handlePosition(player); } - function onDispose(player) { + function onDispose(event, player) { handlePosition(player); } function handlePosition(player) { - if (player.ended()) { - clearPosition(uri); - } else { - savePosition(uri, player.currentTime()); - } + savePosition(uri, player.currentTime()); } function restorePlaybackRate(player) { @@ -233,18 +231,13 @@ function VideoViewer(props: Props) { Promise.race([playPromise, timeoutPromise]).catch((error) => { if (typeof error === 'object' && error.name && error.name === 'NotAllowedError') { - // Autoplay disallowed by browser - player.play(); - } - - // Autoplay failed - if (PLAY_TIMEOUT_ERROR) { - setIsLoading(false); - setIsPlaying(false); - } else { - setIsLoading(false); - setIsPlaying(false); + if (player.autoplay() && !player.muted()) { + // player.muted(true); + // another version had player.play() + } } + setIsLoading(false); + setIsPlaying(false); }); } @@ -259,10 +252,9 @@ function VideoViewer(props: Props) { player.on('tracking:buffered', doTrackingBuffered); player.on('tracking:firstplay', doTrackingFirstPlay); player.on('ended', onEnded); - player.on('play', () => onPlay(player)); - player.on('pause', () => onPause(player)); - player.on('dispose', () => onDispose(player)); - + player.on('play', onPlay); + player.on('pause', (event) => onPause(event, player)); + player.on('dispose', (event) => onDispose(event, player)); player.on('error', () => { const error = player.error(); if (error) { @@ -348,6 +340,7 @@ function VideoViewer(props: Props) { autoplay={!embedded || autoplayIfEmbedded} claimId={claimId} userId={userId} + allowPreRoll={!embedded && !authenticated} /> )}
diff --git a/ui/scss/component/_file-render.scss b/ui/scss/component/_file-render.scss index f6137b4a5..dea4ac989 100644 --- a/ui/scss/component/_file-render.scss +++ b/ui/scss/component/_file-render.scss @@ -571,9 +571,9 @@ video::-internal-media-controls-overlay-cast-button { .file-render { .video-js { - display: flex; - align-items: center; - justify-content: center; + /*display: flex;*/ + /*align-items: center;*/ + /*justify-content: center;*/ } .vjs-big-play-button { diff --git a/yarn.lock b/yarn.lock index 4a1511231..4f63b9351 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1266,6 +1266,25 @@ tough-cookie "^2.2.2" tough-cookie-web-storage-store "^1.0.0" +"@hapi/boom@9.x.x": + version "9.1.2" + resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.2.tgz#48bd41d67437164a2d636e3b5bc954f8c8dc5e38" + integrity sha512-uJEJtiNHzKw80JpngDGBCGAmWjBtzxDCz17A9NO2zCi8LLBlb5Frpq4pXwyN+2JQMod4pKz5BALwyneCgDg89Q== + dependencies: + "@hapi/hoek" "9.x.x" + +"@hapi/cryptiles@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/cryptiles/-/cryptiles-5.1.0.tgz#655de4cbbc052c947f696148c83b187fc2be8f43" + integrity sha512-fo9+d1Ba5/FIoMySfMqPBR/7Pa29J2RsiPrl7bkwo5W5o+AN1dAYQRi4SPrPwwVxVGKjgLOEWrsvt1BonJSfLA== + dependencies: + "@hapi/boom" "9.x.x" + +"@hapi/hoek@9.x.x": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131" + integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug== + "@hot-loader/react-dom@^16.13": version "16.13.0" resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.13.0.tgz#de245b42358110baf80aaf47a0592153d4047997" @@ -3014,6 +3033,11 @@ camelcase@^5.0.0, camelcase@^5.2.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" +can-autoplay@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/can-autoplay/-/can-autoplay-3.0.0.tgz#fc2de8f1d41b36f6d860d9336b66841d30f8b62d" + integrity sha512-qQXGGYPWgF8nPjEt305o3TJ/BkN15l6/wG+VU4N93YYXD3OtYkBBx+l5un7ihIk2UU1OLytAVJjW7ZR39j/CAQ== + caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" @@ -5157,7 +5181,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.1: +extend@>=3.0.2, extend@^3.0.0, extend@~3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -7455,6 +7479,11 @@ lodash-es@^4.17.14, lodash-es@^4.2.1: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" +lodash._reinterpolate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= + lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -7504,6 +7533,21 @@ lodash.snakecase@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" +lodash.template@>=4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" + integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.templatesettings "^4.0.0" + +lodash.templatesettings@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" + integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.toarray@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" @@ -7516,7 +7560,7 @@ lodash.unset@^4.5.2: version "4.5.2" resolved "https://registry.yarnpkg.com/lodash.unset/-/lodash.unset-4.5.2.tgz#370d1d3e85b72a7e1b0cdf2d272121306f23e4ed" -lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.6.1: +lodash@>=4.17.19, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.6.1: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -12144,6 +12188,14 @@ video.js@^7.0.0: videojs-font "3.2.0" videojs-vtt.js "^0.15.2" +videojs-contrib-ads@^6.6.5, videojs-contrib-ads@^6.9.0: + version "6.9.0" + resolved "https://registry.yarnpkg.com/videojs-contrib-ads/-/videojs-contrib-ads-6.9.0.tgz#c792d6fda77254b277545cc3222352fc653b5833" + integrity sha512-nzKz+jhCGMTYffSNVYrmp9p70s05v6jUMOY3Z7DpVk3iFrWK4Zi/BIkokDWrMoHpKjdmCdKzfJVBT+CrUj6Spw== + dependencies: + global "^4.3.2" + video.js "^6 || ^7" + videojs-contrib-quality-levels@^2.0.9: version "2.0.9" resolved "https://registry.yarnpkg.com/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-2.0.9.tgz#b5d533d5092a6fc7d29eae1b43e4597d89bd527b" @@ -12164,6 +12216,18 @@ videojs-font@3.2.0: resolved "https://registry.yarnpkg.com/videojs-font/-/videojs-font-3.2.0.tgz#212c9d3f4e4ec3fa7345167d64316add35e92232" integrity sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA== +videojs-ima@^1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/videojs-ima/-/videojs-ima-1.11.0.tgz#26ad385e388c3da72372298d7d755b001d05a91d" + integrity sha512-ZRoWuGyJ75zamwZgpr0i/gZ6q7Evda/Q6R46gpW88WN7u0ORU7apw/lM1MSG4c3YDXW8LDENgzMAvMZUdifWhg== + dependencies: + "@hapi/cryptiles" "^5.1.0" + can-autoplay "^3.0.0" + extend ">=3.0.2" + lodash ">=4.17.19" + lodash.template ">=4.5.0" + videojs-contrib-ads "^6.6.5" + videojs-logo@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/videojs-logo/-/videojs-logo-2.1.4.tgz#56675b3f95949910bad3c217835ea57aa6fdf212"