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"