From 82e9887f36a370594f258d8e6f60cda57593b2b7 Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Sat, 13 Apr 2024 11:44:16 +0200 Subject: [PATCH] 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 --- .../src/components/board/sections/content.tsx | 3 +- packages/definitions/src/widget.ts | 2 +- packages/translation/src/lang/en.ts | 25 +++ packages/widgets/package.json | 4 +- packages/widgets/src/index.tsx | 2 + .../widgets/src/video/component.module.css | 6 + packages/widgets/src/video/component.tsx | 98 +++++++++ packages/widgets/src/video/index.ts | 20 ++ pnpm-lock.yaml | 198 ++++++++++++++++++ 9 files changed, 355 insertions(+), 3 deletions(-) create mode 100644 packages/widgets/src/video/component.module.css create mode 100644 packages/widgets/src/video/component.tsx create mode 100644 packages/widgets/src/video/index.ts diff --git a/apps/nextjs/src/components/board/sections/content.tsx b/apps/nextjs/src/components/board/sections/content.tsx index cbf344828..bb6b7174e 100644 --- a/apps/nextjs/src/components/board/sections/content.tsx +++ b/apps/nextjs/src/components/board/sections/content.tsx @@ -98,7 +98,7 @@ const BoardItem = ({ item, ...dimensions }: ItemProps) => { return ( <> - + { pos="absolute" top={offset} right={offset} + style={{ zIndex: 1 }} > diff --git a/packages/definitions/src/widget.ts b/packages/definitions/src/widget.ts index 9a0ce8b0c..72f71fad4 100644 --- a/packages/definitions/src/widget.ts +++ b/packages/definitions/src/widget.ts @@ -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]; diff --git a/packages/translation/src/lang/en.ts b/packages/translation/src/lang/en.ts index 7766b1646..ba09f37df 100644 --- a/packages/translation/src/lang/en.ts +++ b/packages/translation/src/lang/en.ts @@ -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: { diff --git a/packages/widgets/package.json b/packages/widgets/package.json index da4a512bd..5ae8e29f3 100644 --- a/packages/widgets/package.json +++ b/packages/widgets/package.json @@ -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" } } diff --git a/packages/widgets/src/index.tsx b/packages/widgets/src/index.tsx index b12edfa77..6fece044d 100644 --- a/packages/widgets/src/index.tsx +++ b/packages/widgets/src/index.tsx @@ -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; diff --git a/packages/widgets/src/video/component.module.css b/packages/widgets/src/video/component.module.css new file mode 100644 index 000000000..e2e3bfbe1 --- /dev/null +++ b/packages/widgets/src/video/component.module.css @@ -0,0 +1,6 @@ +.video { + height: 100%; + width: 100%; + border-radius: var(--mantine-radius-md); + overflow: hidden; +} diff --git a/packages/widgets/src/video/component.tsx b/packages/widgets/src/video/component.tsx new file mode 100644 index 000000000..689b66ba3 --- /dev/null +++ b/packages/widgets/src/video/component.tsx @@ -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 ; + } + + if (options.feedUrl.trim().startsWith("https://www.youtube.com/watch")) { + return ; + } + + return ; +} + +const NoUrl = () => { + const t = useI18n(); + + return ( +
+ + + {t("widget.video.error.noUrl")} + +
+ ); +}; + +const ForYoutubeUseIframe = () => { + const t = useI18n(); + + return ( +
+ + + {t("widget.video.error.forYoutubeUseIframe")} + + {t("common.action.checkoutDocs")} + + +
+ ); +}; + +const Feed = ({ options }: Pick, "options">) => { + const videoRef = useRef(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 ( + + + + ); +}; diff --git a/packages/widgets/src/video/index.ts b/packages/widgets/src/video/index.ts new file mode 100644 index 000000000..c95278bf1 --- /dev/null +++ b/packages/widgets/src/video/index.ts @@ -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")); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79a8b78fa..5a303233d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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}