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:
parent
0de6be77de
commit
b777669a7e
7 changed files with 316 additions and 83 deletions
|
@ -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",
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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;
|
|
@ -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<Props>(function VideoJs(props: Props) {
|
|||
adUrl,
|
||||
claimId,
|
||||
userId,
|
||||
allowPreRoll,
|
||||
} = props;
|
||||
|
||||
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.
|
||||
// (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<Props>(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<Props>(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<Props>(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<Props>(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<Props>(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,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.
|
||||
useEffect(() => {
|
||||
const vjsElement = createVideoPlayerDOM(containerRef.current);
|
||||
|
||||
// Detect source file type via pre-fetch (async)
|
||||
detectFileType().then(() => {
|
||||
// Initialize Video.js
|
||||
const vjsPlayer = initializeVideoPlayer(vjsElement);
|
||||
|
||||
// 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
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
});
|
||||
|
||||
// Cleanup
|
||||
return () => {
|
||||
|
@ -561,51 +641,28 @@ export default React.memo<Props>(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<Props>(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
|
||||
<div className={classnames('video-js-parent', { 'video-js-parent--ios': IS_IOS })} ref={containerRef}>
|
||||
|
|
|
@ -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,22 +189,18 @@ 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());
|
||||
}
|
||||
}
|
||||
|
||||
function restorePlaybackRate(player) {
|
||||
if (!vjsCallbackDataRef.current.embedded) {
|
||||
|
@ -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();
|
||||
if (player.autoplay() && !player.muted()) {
|
||||
// player.muted(true);
|
||||
// another version had player.play()
|
||||
}
|
||||
}
|
||||
|
||||
// Autoplay failed
|
||||
if (PLAY_TIMEOUT_ERROR) {
|
||||
setIsLoading(false);
|
||||
setIsPlaying(false);
|
||||
} else {
|
||||
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}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -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 {
|
||||
|
|
68
yarn.lock
68
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"
|
||||
|
|
Loading…
Reference in a new issue