Add pre-roll ads

change ad macro url
rework video.js load logic
fix autoplay and retry errors
yeet player.ended error
This commit is contained in:
DispatchCommit 2021-06-30 09:29:00 -07:00
parent 0fbf202f2b
commit a7e3bceb6c
10 changed files with 303 additions and 81 deletions

View file

@ -60,6 +60,8 @@
"remove-markdown": "^0.3.0", "remove-markdown": "^0.3.0",
"source-map-explorer": "^2.5.2", "source-map-explorer": "^2.5.2",
"tempy": "^0.6.0", "tempy": "^0.6.0",
"videojs-contrib-ads": "^6.9.0",
"videojs-ima": "^1.11.0",
"videojs-logo": "^2.1.4" "videojs-logo": "^2.1.4"
}, },
"devDependencies": { "devDependencies": {

View file

@ -0,0 +1,100 @@
// 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;
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;

View file

@ -1,4 +1,4 @@
import videojs from 'video.js/dist/video.min.js'; import videojs from 'video.js';
import './touchOverlay.js'; import './touchOverlay.js';
import window from 'global/window'; import window from 'global/window';
import './plugin.scss'; import './plugin.scss';

View file

@ -3,7 +3,7 @@
* Touch UI component * Touch UI component
*/ */
import videojs from 'video.js/dist/video.min.js'; import videojs from 'video.js';
import window from 'global/window'; import window from 'global/window';
const Component = videojs.getComponent('Component'); const Component = videojs.getComponent('Component');

View file

@ -1,4 +1,4 @@
import videojs from 'video.js/dist/video.min.js'; import videojs from 'video.js';
import window from 'global/window'; import window from 'global/window';
const VERSION = '2.1.4'; const VERSION = '2.1.4';

View file

@ -1,5 +1,5 @@
// Created by xander on 6/21/2021 // Created by xander on 6/21/2021
import videojs from 'video.js/dist/video.min.js'; import videojs from 'video.js';
import { v4 as uuidV4 } from 'uuid'; import { v4 as uuidV4 } from 'uuid';
const VERSION = '0.0.1'; const VERSION = '0.0.1';

View file

@ -3,7 +3,7 @@ import React, { useEffect, useRef, useState } from 'react';
import Button from 'component/button'; import Button from 'component/button';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import classnames from 'classnames'; import classnames from 'classnames';
import videojs from 'video.js/dist/video.min.js'; import videojs from 'video.js';
// import 'video.js/dist/alt/video-js-cdn.min.css'; --> 'scss/third-party.scss' // import 'video.js/dist/alt/video-js-cdn.min.css'; --> 'scss/third-party.scss'
import eventTracking from 'videojs-event-tracking'; import eventTracking from 'videojs-event-tracking';
import * as OVERLAY from './overlays'; import * as OVERLAY from './overlays';
@ -12,8 +12,13 @@ import hlsQualitySelector from './plugins/videojs-hls-quality-selector/plugin';
import recsys from './plugins/videojs-recsys/plugin'; import recsys from './plugins/videojs-recsys/plugin';
import qualityLevels from 'videojs-contrib-quality-levels'; import qualityLevels from 'videojs-contrib-quality-levels';
import isUserTyping from 'util/detect-typing'; 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 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 = { export type Player = {
on: (string, (any) => void) => void, on: (string, (any) => void) => void,
@ -77,7 +82,7 @@ const VIDEO_JS_OPTIONS = {
responsive: true, responsive: true,
controls: true, controls: true,
html5: { html5: {
hls: { vhs: {
overrideNative: !videojs.browser.IS_ANY_SAFARI, overrideNative: !videojs.browser.IS_ANY_SAFARI,
}, },
}, },
@ -336,6 +341,8 @@ export default React.memo<Props>(function VideoJs(props: Props) {
// The css starts as "hidden". We make it visible here without // The css starts as "hidden". We make it visible here without
// re-rendering the whole thing. // re-rendering the whole thing.
showTapButton(TAP.UNMUTE); showTapButton(TAP.UNMUTE);
} else {
showTapButton(TAP.NONE);
} }
} }
@ -350,6 +357,9 @@ export default React.memo<Props>(function VideoJs(props: Props) {
const player = playerRef.current; const player = playerRef.current;
showTapButton(TAP.RETRY); showTapButton(TAP.RETRY);
// reattach initial play listener in case we recover from error successfully
player.one('play', onInitialPlay);
if (player && player.loadingSpinner) { if (player && player.loadingSpinner) {
player.loadingSpinner.hide(); player.loadingSpinner.hide();
} }
@ -472,9 +482,7 @@ export default React.memo<Props>(function VideoJs(props: Props) {
// Create the video DOM element and wrapper // Create the video DOM element and wrapper
function createVideoPlayerDOM(container) { function createVideoPlayerDOM(container) {
if (!container) { if (!container) return;
return;
}
// This seems like a poor way to generate the DOM for video.js // This seems like a poor way to generate the DOM for video.js
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
@ -488,19 +496,51 @@ export default React.memo<Props>(function VideoJs(props: Props) {
return el; 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 // Initialize video.js
function initializeVideoPlayer(el) { function initializeVideoPlayer(el) {
if (!el) { if (!el) return;
return;
}
const vjs = videojs(el, videoJsOptions, () => { const vjs = videojs(el, videoJsOptions, () => {
const player = playerRef.current; const player = playerRef.current;
// this seems like a weird thing to have to check for here // this seems like a weird thing to have to check for here
if (!player) { if (!player) return;
return;
}
// Add various event listeners to player // Add various event listeners to player
player.one('play', onInitialPlay); player.one('play', onInitialPlay);
@ -516,11 +556,38 @@ export default React.memo<Props>(function VideoJs(props: Props) {
// Replace volume bar with custom LBRY volume bar // Replace volume bar with custom LBRY volume bar
LbryVolumeBarClass.replaceExisting(player); LbryVolumeBarClass.replaceExisting(player);
// Add reloadSourceOnError plugin
player.reloadSourceOnError({ errorInterval: 10 });
// initialize mobile UI // initialize mobile UI
player.mobileUi(); // Inits mobile version. No-op if Desktop. player.mobileUi(); // Inits mobile version. No-op if Desktop.
// I think this is a callback function // I think this is a callback function
onPlayerReady(player); onPlayerReady(player);
// 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,
});
// player.aniview();
player.ima({
// id: 'ad_content_video',
vpaidMode: google.ima.ImaSdkSettings.VpaidMode.INSECURE,
adTagUrl: macroUrl,
});
// set playsinline for mobile
// TODO: make this better
player.children_[0].setAttribute('playsinline', '');
}); });
// fixes #3498 (https://github.com/lbryio/lbry-desktop/issues/3498) // fixes #3498 (https://github.com/lbryio/lbry-desktop/issues/3498)
@ -533,6 +600,10 @@ export default React.memo<Props>(function VideoJs(props: Props) {
// This lifecycle hook is only called once (on mount), or when `isAudio` changes. // This lifecycle hook is only called once (on mount), or when `isAudio` changes.
useEffect(() => { useEffect(() => {
const vjsElement = createVideoPlayerDOM(containerRef.current); const vjsElement = createVideoPlayerDOM(containerRef.current);
// Detect source file type via pre-fetch (async)
detectFileType().then(() => {
// Initialize Video.js
const vjsPlayer = initializeVideoPlayer(vjsElement); const vjsPlayer = initializeVideoPlayer(vjsElement);
// Add reference to player to global scope // Add reference to player to global scope
@ -543,6 +614,7 @@ export default React.memo<Props>(function VideoJs(props: Props) {
// Add event listener for keyboard shortcuts // Add event listener for keyboard shortcuts
window.addEventListener('keydown', handleKeyDown); window.addEventListener('keydown', handleKeyDown);
});
// Cleanup // Cleanup
return () => { return () => {
@ -560,50 +632,28 @@ export default React.memo<Props>(function VideoJs(props: Props) {
useEffect(() => { useEffect(() => {
// For some reason the video player is responsible for detecting content type this way // For some reason the video player is responsible for detecting content type this way
fetch(source, { method: 'HEAD', cache: 'no-store' }).then((response) => { fetch(source, { method: 'HEAD', cache: 'no-store' }).then((response) => {
const player = playerRef.current; let finalType = sourceType;
if (!player) {
return;
}
let type = sourceType;
let finalSource = source; let finalSource = source;
// override type if we receive an .m3u8 (transcoded mp4) // 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')) { if (response && response.redirected && response.url && response.url.endsWith('m3u8')) {
type = 'application/x-mpegURL'; finalType = 'application/x-mpegURL';
finalSource = response.url; finalSource = response.url;
} }
// Update player poster // Modify video source in options
// note: the poster prop seems to return null usually. videoJsOptions.sources = [
if (poster) player.poster(poster); {
src: finalSource,
type: finalType,
},
];
// Update player source // Update player source
player.src({ const player = playerRef.current;
src: finalSource, if (!player) return;
type: type,
});
// set playsinline for mobile
player.children_[0].setAttribute('playsinline', '');
// 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,
});
// Update player source
player.src({
src: finalSource,
type: type,
});
// PR #5570: Temp workaround to avoid double Play button until the next re-architecture. // PR #5570: Temp workaround to avoid double Play button until the next re-architecture.
if (!player.paused()) { if (!player.paused()) {
@ -612,6 +662,20 @@ export default React.memo<Props>(function VideoJs(props: Props) {
}); });
}, [source, reload]); }, [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);
};
}, [source, reload]);
return ( return (
// $FlowFixMe // $FlowFixMe
<div className={classnames('video-js-parent', { 'video-js-parent--ios': IS_IOS })} ref={containerRef}> <div className={classnames('video-js-parent', { 'video-js-parent--ios': IS_IOS })} ref={containerRef}>

View file

@ -148,7 +148,10 @@ function VideoViewer(props: Props) {
}, [embedded, videoPlaybackRate]); }, [embedded, videoPlaybackRate]);
function doTrackingBuffered(e: Event, data: any) { function doTrackingBuffered(e: Event, data: any) {
console.log(`tracking buffered`, e);
console.log(`Source (tracking buffered): ${source}`);
fetch(source, { method: 'HEAD' }).then((response) => { fetch(source, { method: 'HEAD' }).then((response) => {
console.log(`Source Response (tracking buffered)`, response);
data.playerPoweredBy = response.headers.get('x-powered-by'); data.playerPoweredBy = response.headers.get('x-powered-by');
doAnalyticsBuffer(uri, data); doAnalyticsBuffer(uri, data);
}); });
@ -179,7 +182,9 @@ function VideoViewer(props: Props) {
} else if (autoplaySetting) { } else if (autoplaySetting) {
setShowAutoplayCountdown(true); setShowAutoplayCountdown(true);
} }
}, [embedded, setIsEndededEmbed, autoplaySetting, setShowAutoplayCountdown, adUrl, setAdUrl]);
clearPosition(uri);
}, [embedded, setIsEndededEmbed, autoplaySetting, setShowAutoplayCountdown, adUrl, setAdUrl, clearPosition, uri]);
function onPlay() { function onPlay() {
setIsLoading(false); setIsLoading(false);
@ -198,12 +203,8 @@ function VideoViewer(props: Props) {
} }
function handlePosition(player) { function handlePosition(player) {
if (player.ended()) {
clearPosition(uri);
} else {
savePosition(uri, player.currentTime()); savePosition(uri, player.currentTime());
} }
}
function restorePlaybackRate(player) { function restorePlaybackRate(player) {
if (!vjsCallbackDataRef.current.embedded) { if (!vjsCallbackDataRef.current.embedded) {
@ -235,22 +236,13 @@ function VideoViewer(props: Props) {
Promise.race([playPromise, timeoutPromise]).catch((error) => { Promise.race([playPromise, timeoutPromise]).catch((error) => {
if (typeof error === 'object' && error.name && error.name === 'NotAllowedError') { if (typeof error === 'object' && error.name && error.name === 'NotAllowedError') {
if (player.autoplay() && !player.muted()) { if (player.autoplay() && !player.muted()) {
player.muted(true); // player.muted(true);
// another version had player.play() // another version had player.play()
} }
} }
if (PLAY_TIMEOUT_ERROR) {
const retryPlayPromise = player.play();
Promise.race([retryPlayPromise, timeoutPromise]).catch((error) => {
setIsLoading(false); setIsLoading(false);
setIsPlaying(false); setIsPlaying(false);
}); });
} else {
setIsLoading(false);
setIsPlaying(false);
}
});
} }
setIsLoading(shouldPlay); // if we are here outside of an embed, we're playing setIsLoading(shouldPlay); // if we are here outside of an embed, we're playing

View file

@ -589,9 +589,9 @@ video::-internal-media-controls-overlay-cast-button {
.file-render { .file-render {
.video-js { .video-js {
display: flex; /*display: flex;*/
align-items: center; /*align-items: center;*/
justify-content: center; /*justify-content: center;*/
} }
.vjs-big-play-button { .vjs-big-play-button {

View file

@ -1266,6 +1266,25 @@
tough-cookie "^2.2.2" tough-cookie "^2.2.2"
tough-cookie-web-storage-store "^1.0.0" 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": "@hot-loader/react-dom@^16.13":
version "16.13.0" version "16.13.0"
resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.13.0.tgz#de245b42358110baf80aaf47a0592153d4047997" resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.13.0.tgz#de245b42358110baf80aaf47a0592153d4047997"
@ -2981,6 +3000,11 @@ camelcase@^5.0.0, camelcase@^5.2.0, camelcase@^5.3.1:
version "5.3.1" version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" 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: caniuse-api@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"
@ -5124,7 +5148,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
assign-symbols "^1.0.0" assign-symbols "^1.0.0"
is-extendable "^1.0.1" 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" version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
@ -7421,6 +7445,11 @@ lodash-es@^4.17.14, lodash-es@^4.2.1:
version "4.17.15" version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" 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: lodash.camelcase@^4.3.0:
version "4.3.0" version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
@ -7470,6 +7499,21 @@ lodash.snakecase@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" 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: lodash.toarray@^4.4.0:
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561"
@ -7482,7 +7526,7 @@ lodash.unset@^4.5.2:
version "4.5.2" version "4.5.2"
resolved "https://registry.yarnpkg.com/lodash.unset/-/lodash.unset-4.5.2.tgz#370d1d3e85b72a7e1b0cdf2d272121306f23e4ed" 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" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -12064,6 +12108,14 @@ vfile@^2.0.0:
videojs-font "3.2.0" videojs-font "3.2.0"
videojs-vtt.js "^0.15.2" 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: videojs-contrib-quality-levels@^2.0.9:
version "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" resolved "https://registry.yarnpkg.com/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-2.0.9.tgz#b5d533d5092a6fc7d29eae1b43e4597d89bd527b"
@ -12084,6 +12136,18 @@ videojs-font@3.2.0:
resolved "https://registry.yarnpkg.com/videojs-font/-/videojs-font-3.2.0.tgz#212c9d3f4e4ec3fa7345167d64316add35e92232" resolved "https://registry.yarnpkg.com/videojs-font/-/videojs-font-3.2.0.tgz#212c9d3f4e4ec3fa7345167d64316add35e92232"
integrity sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA== 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: videojs-logo@^2.1.4:
version "2.1.4" version "2.1.4"
resolved "https://registry.yarnpkg.com/videojs-logo/-/videojs-logo-2.1.4.tgz#56675b3f95949910bad3c217835ea57aa6fdf212" resolved "https://registry.yarnpkg.com/videojs-logo/-/videojs-logo-2.1.4.tgz#56675b3f95949910bad3c217835ea57aa6fdf212"