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
This commit is contained in:
DispatchCommit 2021-06-30 09:29:00 -07:00 committed by jessopb
parent 0de6be77de
commit b777669a7e
7 changed files with 316 additions and 83 deletions

View file

@ -35,6 +35,7 @@
"object-curly-spacing": 0, "object-curly-spacing": 0,
"one-var": 0, "one-var": 0,
"prefer-promise-reject-errors": 0, "prefer-promise-reject-errors": 0,
"promise/param-names": 0,
"react/jsx-indent": 0, "react/jsx-indent": 0,
"react/jsx-no-comment-textnodes": 0, "react/jsx-no-comment-textnodes": 0,
"react-hooks/exhaustive-deps": "warn", "react-hooks/exhaustive-deps": "warn",

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,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;

View file

@ -13,8 +13,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,
@ -54,6 +59,7 @@ type Props = {
adUrl: ?string, adUrl: ?string,
claimId: ?string, claimId: ?string,
userId: ?number, userId: ?number,
allowPreRoll: ?boolean,
}; };
type VideoJSOptions = { type VideoJSOptions = {
@ -78,7 +84,7 @@ const VIDEO_JS_OPTIONS: VideoJSOptions = {
responsive: true, responsive: true,
controls: true, controls: true,
html5: { html5: {
hls: { vhs: {
overrideNative: !videojs.browser.IS_ANY_SAFARI, overrideNative: !videojs.browser.IS_ANY_SAFARI,
}, },
}, },
@ -190,6 +196,7 @@ export default React.memo<Props>(function VideoJs(props: Props) {
adUrl, adUrl,
claimId, claimId,
userId, userId,
allowPreRoll,
} = props; } = props;
const [reload, setReload] = useState('initial'); const [reload, setReload] = useState('initial');
@ -321,7 +328,7 @@ export default React.memo<Props>(function VideoJs(props: Props) {
// clashes if we add a new button in the future. // clashes if we add a new button in the future.
// (2) We'll have to get 'makeSelectClientSetting(SETTINGS.VIDEO_THEATER_MODE)' // (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 // 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)')); controlBar.getChild('Button').controlText(__('Toggle Theater mode (t)'));
break; break;
default: default:
@ -337,6 +344,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);
} }
} }
@ -351,6 +360,10 @@ 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
// $FlowFixMe
player.one('play', onInitialPlay);
if (player && player.loadingSpinner) { if (player && player.loadingSpinner) {
player.loadingSpinner.hide(); player.loadingSpinner.hide();
} }
@ -473,9 +486,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');
@ -489,19 +500,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);
@ -517,13 +560,45 @@ 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.
// 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 // I think this is a callback function
onPlayerReady(player); 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) // 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 // 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()); vjs.on('fullscreenchange', () => document.activeElement && document.activeElement.blur());
@ -534,6 +609,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
@ -544,6 +623,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 () => {
@ -561,51 +641,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
if (SIMPLE_SITE) {
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()) {
@ -614,6 +671,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);
};
});
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

@ -178,7 +178,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(player) { function onPlay(player) {
setIsLoading(false); setIsLoading(false);
@ -187,22 +189,18 @@ function VideoViewer(props: Props) {
setIsEndededEmbed(false); setIsEndededEmbed(false);
} }
function onPause(player) { function onPause(event, player) {
setIsPlaying(false); setIsPlaying(false);
handlePosition(player); handlePosition(player);
} }
function onDispose(player) { function onDispose(event, player) {
handlePosition(player); handlePosition(player);
} }
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) {
@ -233,18 +231,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') {
// Autoplay disallowed by browser if (player.autoplay() && !player.muted()) {
player.play(); // player.muted(true);
// another version had player.play()
}
} }
// Autoplay failed
if (PLAY_TIMEOUT_ERROR) {
setIsLoading(false); setIsLoading(false);
setIsPlaying(false); setIsPlaying(false);
} else {
setIsLoading(false);
setIsPlaying(false);
}
}); });
} }
@ -259,10 +252,9 @@ function VideoViewer(props: Props) {
player.on('tracking:buffered', doTrackingBuffered); player.on('tracking:buffered', doTrackingBuffered);
player.on('tracking:firstplay', doTrackingFirstPlay); player.on('tracking:firstplay', doTrackingFirstPlay);
player.on('ended', onEnded); player.on('ended', onEnded);
player.on('play', () => onPlay(player)); player.on('play', onPlay);
player.on('pause', () => onPause(player)); player.on('pause', (event) => onPause(event, player));
player.on('dispose', () => onDispose(player)); player.on('dispose', (event) => onDispose(event, player));
player.on('error', () => { player.on('error', () => {
const error = player.error(); const error = player.error();
if (error) { if (error) {
@ -348,6 +340,7 @@ function VideoViewer(props: Props) {
autoplay={!embedded || autoplayIfEmbedded} autoplay={!embedded || autoplayIfEmbedded}
claimId={claimId} claimId={claimId}
userId={userId} userId={userId}
allowPreRoll={!embedded && !authenticated}
/> />
)} )}
</div> </div>

View file

@ -571,9 +571,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"
@ -3014,6 +3033,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"
@ -5157,7 +5181,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"
@ -7455,6 +7479,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"
@ -7504,6 +7533,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"
@ -7516,7 +7560,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==
@ -12144,6 +12188,14 @@ video.js@^7.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"
@ -12164,6 +12216,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"