feat: add video widget (#287)

* feat: add nestjs replacement, remove nestjs

* feat: add video widget

* feat: add notice about youtube not supported with video.js

* fix: format issue

* fix: format issue
This commit is contained in:
Meier Lukas
2024-04-13 11:44:16 +02:00
committed by GitHub
parent 80d2d485b8
commit 82e9887f36
9 changed files with 355 additions and 3 deletions

View File

@@ -98,7 +98,7 @@ const BoardItem = ({ item, ...dimensions }: ItemProps) => {
return (
<>
<ItemMenu offset={8} item={newItem} />
<ItemMenu offset={4} item={newItem} />
<Comp
options={options as never}
integrations={item.integrations}
@@ -155,6 +155,7 @@ const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => {
pos="absolute"
top={offset}
right={offset}
style={{ zIndex: 1 }}
>
<IconDotsVertical />
</ActionIcon>

View File

@@ -1,2 +1,2 @@
export const widgetKinds = ["clock", "weather", "app"] as const;
export const widgetKinds = ["clock", "weather", "app", "video"] as const;
export type WidgetKind = (typeof widgetKinds)[number];

View File

@@ -202,6 +202,7 @@ export default {
confirm: "Confirm",
previous: "Previous",
next: "Next",
checkoutDocs: "Check out the documentation",
},
multiSelect: {
placeholder: "Pick one or more values",
@@ -435,6 +436,30 @@ export default {
},
},
},
video: {
name: "Video Stream",
description: "Embed a video stream or video from a camera or a website",
option: {
feedUrl: {
label: "Feed URL",
},
hasAutoPlay: {
label: "Autoplay",
description:
"Autoplay only works when muted because of browser restrictions",
},
isMuted: {
label: "Muted",
},
hasControls: {
label: "Show controls",
},
},
error: {
noUrl: "No Video URL provided",
forYoutubeUseIframe: "For YouTube videos use the iframe option",
},
},
},
widgetPreview: {
toggle: {

View File

@@ -23,6 +23,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/video.js": "^7.3.57",
"eslint": "^8.57.0",
"typescript": "^5.4.5"
},
@@ -43,6 +44,7 @@
"@homarr/redis": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0"
"@homarr/validation": "workspace:^0.1.0",
"video.js": "^8.10.0"
}
}

View File

@@ -9,6 +9,7 @@ import * as app from "./app";
import * as clock from "./clock";
import type { WidgetComponentProps } from "./definition";
import type { WidgetImportRecord } from "./import";
import * as video from "./video";
import * as weather from "./weather";
export { reduceWidgetOptionsWithDefaultValues } from "./options";
@@ -21,6 +22,7 @@ export const widgetImports = {
clock,
weather,
app,
video,
} satisfies WidgetImportRecord;
export type WidgetImports = typeof widgetImports;

View File

@@ -0,0 +1,6 @@
.video {
height: 100%;
width: 100%;
border-radius: var(--mantine-radius-md);
overflow: hidden;
}

View File

@@ -0,0 +1,98 @@
"use client";
import { useEffect, useRef } from "react";
import combineClasses from "clsx";
import videojs from "video.js";
import { useI18n } from "@homarr/translation/client";
import {
Anchor,
Center,
Group,
IconBrandYoutube,
IconDeviceCctvOff,
Stack,
Title,
} from "@homarr/ui";
import type { WidgetComponentProps } from "../definition";
import classes from "./component.module.css";
import "video.js/dist/video-js.css";
export default function VideoWidget({
options,
}: WidgetComponentProps<"video">) {
if (options.feedUrl.trim() === "") {
return <NoUrl />;
}
if (options.feedUrl.trim().startsWith("https://www.youtube.com/watch")) {
return <ForYoutubeUseIframe />;
}
return <Feed options={options} />;
}
const NoUrl = () => {
const t = useI18n();
return (
<Center h="100%">
<Stack align="center">
<IconDeviceCctvOff />
<Title order={4}>{t("widget.video.error.noUrl")}</Title>
</Stack>
</Center>
);
};
const ForYoutubeUseIframe = () => {
const t = useI18n();
return (
<Center h="100%">
<Stack align="center" gap="xs">
<IconBrandYoutube />
<Title order={4}>{t("widget.video.error.forYoutubeUseIframe")}</Title>
<Anchor href="https://homarr.dev/docs/widgets/iframe/">
{t("common.action.checkoutDocs")}
</Anchor>
</Stack>
</Center>
);
};
const Feed = ({ options }: Pick<WidgetComponentProps<"video">, "options">) => {
const videoRef = useRef<HTMLVideoElement>(null);
useEffect(() => {
if (!videoRef.current) {
return;
}
// Initialize Video.js player if it's not already initialized
if (!("player" in videoRef.current)) {
videojs(
videoRef.current,
{
autoplay: options.hasAutoPlay,
muted: options.isMuted,
controls: options.hasControls,
},
() => undefined,
);
}
}, [videoRef]);
return (
<Group justify="center" w="100%" h="100%" pos="relative">
<video
className={combineClasses("video-js", classes.video)}
ref={videoRef}
>
<source src={options.feedUrl} />
</video>
</Group>
);
};

View File

@@ -0,0 +1,20 @@
import { IconDeviceCctv } from "@homarr/ui";
import { createWidgetDefinition } from "../definition";
import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("video", {
icon: IconDeviceCctv,
options: optionsBuilder.from((factory) => ({
feedUrl: factory.text({
defaultValue: "",
}),
hasAutoPlay: factory.switch({
withDescription: true,
}),
isMuted: factory.switch({
defaultValue: true,
}),
hasControls: factory.switch(),
})),
}).withDynamicImport(() => import("./component"));

198
pnpm-lock.yaml generated
View File

@@ -816,6 +816,9 @@ importers:
'@homarr/validation':
specifier: workspace:^0.1.0
version: link:../validation
video.js:
specifier: ^8.10.0
version: 8.10.0
devDependencies:
'@homarr/eslint-config':
specifier: workspace:^0.2.0
@@ -826,6 +829,9 @@ importers:
'@homarr/tsconfig':
specifier: workspace:^0.1.0
version: link:../../tooling/typescript
'@types/video.js':
specifier: ^7.3.57
version: 7.3.57
eslint:
specifier: ^8.57.0
version: 8.57.0
@@ -3253,6 +3259,10 @@ packages:
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
dev: false
/@types/video.js@7.3.57:
resolution: {integrity: sha512-CKAaJ9p/myadqT/FAnlzVvHMVj35ynl86eGgqPM9paTCWbFtq9JUZATUzOir+bLjRyXqrjA10e5KgHc7dRR38g==}
dev: true
/@types/ws@8.5.10:
resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
dependencies:
@@ -3394,6 +3404,48 @@ packages:
/@ungap/structured-clone@1.2.0:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
/@videojs/http-streaming@3.10.0(video.js@8.10.0):
resolution: {integrity: sha512-Lf1rmhTalV4Gw0bJqHmH4lfk/FlepUDs9smuMtorblAYnqDlE2tbUOb7sBXVYoXGdbWbdTW8jH2cnS+6HWYJ4Q==}
engines: {node: '>=8', npm: '>=5'}
peerDependencies:
video.js: ^7 || ^8
dependencies:
'@babel/runtime': 7.23.9
'@videojs/vhs-utils': 4.0.0
aes-decrypter: 4.0.1
global: 4.4.0
m3u8-parser: 7.1.0
mpd-parser: 1.3.0
mux.js: 7.0.2
video.js: 8.10.0
dev: false
/@videojs/vhs-utils@3.0.5:
resolution: {integrity: sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==}
engines: {node: '>=8', npm: '>=5'}
dependencies:
'@babel/runtime': 7.23.9
global: 4.4.0
url-toolkit: 2.2.5
dev: false
/@videojs/vhs-utils@4.0.0:
resolution: {integrity: sha512-xJp7Yd4jMLwje2vHCUmi8MOUU76nxiwII3z4Eg3Ucb+6rrkFVGosrXlMgGnaLjq724j3wzNElRZ71D/CKrTtxg==}
engines: {node: '>=8', npm: '>=5'}
dependencies:
'@babel/runtime': 7.23.9
global: 4.4.0
url-toolkit: 2.2.5
dev: false
/@videojs/xhr@2.6.0:
resolution: {integrity: sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==}
dependencies:
'@babel/runtime': 7.23.9
global: 4.4.0
is-function: 1.0.2
dev: false
/@vitejs/plugin-react@4.2.1(vite@5.2.6):
resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -3487,6 +3539,11 @@ packages:
pretty-format: 29.7.0
dev: true
/@xmldom/xmldom@0.8.10:
resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
engines: {node: '>=10.0.0'}
dev: false
/@xterm/addon-canvas@0.7.0(@xterm/xterm@5.5.0):
resolution: {integrity: sha512-LF5LYcfvefJuJ7QotNRdRSPc9YASAVDeoT5uyXS/nZshZXjYplGXRECBGiznwvhNL2I8bq1Lf5MzRwstsYQ2Iw==}
peerDependencies:
@@ -3528,6 +3585,15 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
/aes-decrypter@4.0.1:
resolution: {integrity: sha512-H1nh/P9VZXUf17AA5NQfJML88CFjVBDuGkp5zDHa7oEhYN9TTpNLJknRY1ie0iSKWlDf6JRnJKaZVDSQdPy6Cg==}
dependencies:
'@babel/runtime': 7.23.9
'@videojs/vhs-utils': 3.0.5
global: 4.4.0
pkcs7: 1.0.4
dev: false
/agent-base@6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
@@ -4465,6 +4531,10 @@ packages:
csstype: 3.1.3
dev: false
/dom-walk@0.1.2:
resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==}
dev: false
/dot-case@2.1.1:
resolution: {integrity: sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==}
dependencies:
@@ -5518,6 +5588,13 @@ packages:
once: 1.4.0
dev: false
/global@4.4.0:
resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==}
dependencies:
min-document: 2.19.0
process: 0.11.10
dev: false
/globals@11.12.0:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
engines: {node: '>=4'}
@@ -5752,6 +5829,10 @@ packages:
engines: {node: '>=8'}
dev: true
/individual@2.0.0:
resolution: {integrity: sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g==}
dev: false
/inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
dependencies:
@@ -5926,6 +6007,10 @@ packages:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
/is-function@1.0.2:
resolution: {integrity: sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==}
dev: false
/is-generator-function@1.0.10:
resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==}
engines: {node: '>= 0.4'}
@@ -6278,6 +6363,10 @@ packages:
object.values: 1.1.7
dev: false
/keycode@2.2.0:
resolution: {integrity: sha512-ps3I9jAdNtRpJrbBvQjpzyFbss/skHqzS+eu4RxKLaEAtFqkjZaB6TZMSivPbLxf4K7VI4SjR0P5mRCX5+Q25A==}
dev: false
/keygrip@1.1.0:
resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==}
engines: {node: '>= 0.6'}
@@ -6448,6 +6537,14 @@ packages:
es5-ext: 0.10.62
dev: false
/m3u8-parser@7.1.0:
resolution: {integrity: sha512-7N+pk79EH4oLKPEYdgRXgAsKDyA/VCo0qCHlUwacttQA0WqsjZQYmNfywMvjlY9MpEBVZEt0jKFd73Kv15EBYQ==}
dependencies:
'@babel/runtime': 7.23.9
'@videojs/vhs-utils': 3.0.5
global: 4.4.0
dev: false
/magic-string@0.30.7:
resolution: {integrity: sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==}
engines: {node: '>=12'}
@@ -6577,6 +6674,12 @@ packages:
engines: {node: '>=10'}
dev: false
/min-document@2.19.0:
resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==}
dependencies:
dom-walk: 0.1.2
dev: false
/minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
@@ -6656,6 +6759,16 @@ packages:
ufo: 1.4.0
dev: true
/mpd-parser@1.3.0:
resolution: {integrity: sha512-WgeIwxAqkmb9uTn4ClicXpEQYCEduDqRKfmUdp4X8vmghKfBNXZLYpREn9eqrDx/Tf5LhzRcJLSpi4ohfV742Q==}
hasBin: true
dependencies:
'@babel/runtime': 7.23.9
'@videojs/vhs-utils': 4.0.0
'@xmldom/xmldom': 0.8.10
global: 4.4.0
dev: false
/mrmime@2.0.0:
resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==}
engines: {node: '>=10'}
@@ -6672,6 +6785,24 @@ packages:
resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
dev: true
/mux.js@7.0.2:
resolution: {integrity: sha512-CM6+QuyDbc0qW1OfEjkd2+jVKzTXF+z5VOKH0eZxtZtnrG/ilkW/U7l7IXGtBNLASF9sKZMcK1u669cq50Qq0A==}
engines: {node: '>=8', npm: '>=5'}
hasBin: true
dependencies:
'@babel/runtime': 7.23.9
global: 4.4.0
dev: false
/mux.js@7.0.3:
resolution: {integrity: sha512-gzlzJVEGFYPtl2vvEiJneSWAWD4nfYRHD5XgxmB2gWvXraMPOYk+sxfvexmNfjQUFpmk6hwLR5C6iSFmuwCHdQ==}
engines: {node: '>=8', npm: '>=5'}
hasBin: true
dependencies:
'@babel/runtime': 7.23.9
global: 4.4.0
dev: false
/mysql2@3.9.2:
resolution: {integrity: sha512-3Cwg/UuRkAv/wm6RhtPE5L7JlPB877vwSF6gfLAS68H+zhH+u5oa3AieqEd0D0/kC3W7qIhYbH419f7O9i/5nw==}
engines: {node: '>= 8.0'}
@@ -7170,6 +7301,13 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
/pkcs7@1.0.4:
resolution: {integrity: sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==}
hasBin: true
dependencies:
'@babel/runtime': 7.23.9
dev: false
/pkg-types@1.0.3:
resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==}
dependencies:
@@ -7309,6 +7447,11 @@ packages:
resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==}
dev: false
/process@0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
dev: false
/prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
dependencies:
@@ -7817,6 +7960,12 @@ packages:
dependencies:
queue-microtask: 1.2.3
/rust-result@1.0.0:
resolution: {integrity: sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==}
dependencies:
individual: 2.0.0
dev: false
/rxjs@6.6.7:
resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==}
engines: {npm: '>=2.0.0'}
@@ -7843,6 +7992,12 @@ packages:
/safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
/safe-json-parse@4.0.0:
resolution: {integrity: sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==}
dependencies:
rust-result: 1.0.0
dev: false
/safe-regex-test@1.0.3:
resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==}
engines: {node: '>= 0.4'}
@@ -8723,6 +8878,10 @@ packages:
requires-port: 1.0.0
dev: true
/url-toolkit@2.2.5:
resolution: {integrity: sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==}
dev: false
/use-callback-ref@1.3.1(@types/react@18.2.76)(react@18.2.0):
resolution: {integrity: sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==}
engines: {node: '>=10'}
@@ -8819,6 +8978,45 @@ packages:
builtins: 5.0.1
dev: true
/video.js@8.10.0:
resolution: {integrity: sha512-7UeG/flj/pp8tNGW8WKPP1VJb3x2FgLoqUWzpZqkoq5YIyf6MNzmIrKtxprl438T5RVkcj+OzV8IX4jYSAn4Sw==}
dependencies:
'@babel/runtime': 7.23.9
'@videojs/http-streaming': 3.10.0(video.js@8.10.0)
'@videojs/vhs-utils': 4.0.0
'@videojs/xhr': 2.6.0
aes-decrypter: 4.0.1
global: 4.4.0
keycode: 2.2.0
m3u8-parser: 7.1.0
mpd-parser: 1.3.0
mux.js: 7.0.3
safe-json-parse: 4.0.0
videojs-contrib-quality-levels: 4.0.0(video.js@8.10.0)
videojs-font: 4.1.0
videojs-vtt.js: 0.15.5
dev: false
/videojs-contrib-quality-levels@4.0.0(video.js@8.10.0):
resolution: {integrity: sha512-u5rmd8BjLwANp7XwuQ0Q/me34bMe6zg9PQdHfTS7aXgiVRbNTb4djcmfG7aeSrkpZjg+XCLezFNenlJaCjBHKw==}
engines: {node: '>=14', npm: '>=6'}
peerDependencies:
video.js: ^8
dependencies:
global: 4.4.0
video.js: 8.10.0
dev: false
/videojs-font@4.1.0:
resolution: {integrity: sha512-X1LuPfLZPisPLrANIAKCknZbZu5obVM/ylfd1CN+SsCmPZQ3UMDPcvLTpPBJxcBuTpHQq2MO1QCFt7p8spnZ/w==}
dev: false
/videojs-vtt.js@0.15.5:
resolution: {integrity: sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==}
dependencies:
global: 4.4.0
dev: false
/vite-node@1.5.0(@types/node@20.12.7):
resolution: {integrity: sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==}
engines: {node: ^18.0.0 || >=20.0.0}