diff --git a/@types/global.d.ts b/@types/global.d.ts new file mode 100644 index 0000000..755e0e0 --- /dev/null +++ b/@types/global.d.ts @@ -0,0 +1,21 @@ +import type { RemotePlaybackState, WebKitPlaybackTargetAvailabilityEvent } from './remote-playback'; + +interface RemotePlayback extends EventTarget { + readonly state: RemotePlaybackState; + watchAvailability(callback: (available: boolean) => void): Promise; + cancelWatchAvailability(id?: number): Promise; + prompt(): Promise; +} + +interface HTMLVideoElement { + readonly remote?: RemotePlayback; +} + +declare global { + interface Window { + WebKitPlaybackTargetAvailabilityEvent?: { + prototype: WebKitPlaybackTargetAvailabilityEvent; + new(type: string, eventInitDict?: EventInit): WebKitPlaybackTargetAvailabilityEvent; + }; + } +} diff --git a/@types/remote-playback.ts b/@types/remote-playback.ts new file mode 100644 index 0000000..eb0b4b0 --- /dev/null +++ b/@types/remote-playback.ts @@ -0,0 +1,19 @@ +export type RemotePlaybackState = 'connecting' | 'connected' | 'disconnected'; + +export interface RemotePlaybackAvailabilityEvent extends Event { + availability: 'available' | 'not-available'; +} + +export interface RemotePlaybackPlugin { + getState(): RemotePlaybackState | null; + isConnected(): boolean; +} + +export interface WebKitPlaybackTargetAvailabilityEvent extends Event { + availability: 'available' | 'not-available'; +} + +export interface HTMLVideoElementWithAirPlay extends HTMLVideoElement { + webkitCurrentPlaybackTargetIsWireless: boolean; + webkitShowPlaybackTargetPicker(): void; +} diff --git a/global.d.ts b/@types/silvermine-videojs.d.ts similarity index 100% rename from global.d.ts rename to @types/silvermine-videojs.d.ts diff --git a/@types/videojs.ts b/@types/videojs.ts new file mode 100644 index 0000000..d4cba09 --- /dev/null +++ b/@types/videojs.ts @@ -0,0 +1,25 @@ +import videojs from '@silvermine/video.js'; +import type { AirPlayManager } from '../src/js/airplay/interfaces/Airplay.interfaces'; +import type { RemotePlaybackPlugin } from '../src/RemotePlaybackPlugin'; + +export type VideoJs = typeof videojs; + +export interface VideoJsPlayer extends videojs.Player { + airPlay?: AirPlayManager; + remotePlayback?: RemotePlaybackPlugin; +} +export interface VideoJsButton { + el(): Element; + show(): void; + hide(): void; + addClass(className: string): void; + removeClass(className: string): void; + controlText(text?: string): string | void; + localize(text: string): string; + on(event: string, callback: () => void): void; + buildCSSClass(): string; +} + +export interface VideoJsButtonConstructor { + new (player: VideoJsPlayer, options?: Record): VideoJsButton; +} diff --git a/README.md b/README.md index c496b15..b67c6a7 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ for casting to external devices. This plugin bridges that gap by: * **Future-Proofing**: Built with modern web standards and TypeScript for maintainability - ## License This software is released under the MIT license. See [the license file](LICENSE) for diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 0000000..1a6f52e --- /dev/null +++ b/examples/index.html @@ -0,0 +1,47 @@ + + + + + + Video.js Remote Playback Plugin Test + + + + +
+ +
+ + + + + diff --git a/examples/main.js b/examples/main.js new file mode 100644 index 0000000..7851eb6 --- /dev/null +++ b/examples/main.js @@ -0,0 +1,2 @@ +// Intentionally left minimal. The real dev entrypoint is main.ts, which +// is loaded as an ES module by Vite during development. diff --git a/examples/main.ts b/examples/main.ts new file mode 100644 index 0000000..ea9bd33 --- /dev/null +++ b/examples/main.ts @@ -0,0 +1,37 @@ +import initializePlugin from '../src/index'; + +declare global { + interface Window { + videojs?: { + getAllPlayers: () => Array<{ remotePlayback?: () => void }>; + } & ((...args: unknown[]) => unknown); + } +} + +function initializeRemotePlaybackPlugin(): void { + if (!window.videojs) { + return; + } + + // Register the plugin with video.js + initializePlugin(window.videojs as any); + + // Ensure existing players (e.g. created via data-setup) have the plugin enabled + const players = window.videojs.getAllPlayers(); + + players.forEach((player) => { + if (typeof player.remotePlayback === 'function') { + player.remotePlayback(); + } + }); +} + +if (typeof window.videojs === 'function') { + initializeRemotePlaybackPlugin(); +} else { + document.addEventListener('DOMContentLoaded', function() { + if (typeof window.videojs === 'function') { + initializeRemotePlaybackPlugin(); + } + }); +} diff --git a/package-lock.json b/package-lock.json index 83cf41f..9a7b921 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,6 @@ "name": "@silvermine/videojs-remoteplayback", "version": "0.0.1", "license": "MIT", - "dependencies": { - "@silvermine/video.js": "7.19.0-1" - }, "devDependencies": { "@silvermine/eslint-config": "3.2.1", "@silvermine/standardization": "2.2.3", @@ -18,9 +15,14 @@ "@types/video.js": "7.3.58", "@vitest/ui": "3.2.4", "jsdom": "26.1.0", + "sass": "^1.77.0", "typescript": "5.9.2", "vite": "6.3.6", + "vite-plugin-dts": "^4.5.4", "vitest": "3.2.4" + }, + "peerDependencies": { + "@silvermine/video.js": "7.19.0-1" } }, "node_modules/@ampproject/remapping": { @@ -222,9 +224,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "engines": { "node": ">=6.9.0" @@ -253,12 +255,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "dev": true, "dependencies": { - "@babel/types": "^7.28.0" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -271,6 +273,7 @@ "version": "7.28.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", + "peer": true, "engines": { "node": ">=6.9.0" } @@ -308,13 +311,13 @@ } }, "node_modules/@babel/types": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", - "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -762,6 +765,27 @@ "node": ">=6.9.0" } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.12", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", @@ -797,6 +821,124 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@microsoft/api-extractor": { + "version": "7.53.3", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.53.3.tgz", + "integrity": "sha512-p2HmQaMSVqMBj3bH3643f8xApKAqrF1jNpPsMCTQOYCYgfwLnvzsve8c+bgBWzCOBBgLK54PB6ZLIWMGLg8CZA==", + "dev": true, + "dependencies": { + "@microsoft/api-extractor-model": "7.31.3", + "@microsoft/tsdoc": "~0.15.1", + "@microsoft/tsdoc-config": "~0.17.1", + "@rushstack/node-core-library": "5.18.0", + "@rushstack/rig-package": "0.6.0", + "@rushstack/terminal": "0.19.3", + "@rushstack/ts-command-line": "5.1.3", + "lodash": "~4.17.15", + "minimatch": "10.0.3", + "resolve": "~1.22.1", + "semver": "~7.5.4", + "source-map": "~0.6.1", + "typescript": "5.8.2" + }, + "bin": { + "api-extractor": "bin/api-extractor" + } + }, + "node_modules/@microsoft/api-extractor-model": { + "version": "7.31.3", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.31.3.tgz", + "integrity": "sha512-dv4quQI46p0U03TCEpasUf6JrJL3qjMN7JUAobsPElxBv4xayYYvWW9aPpfYV+Jx6hqUcVaLVOeV7+5hxsyoFQ==", + "dev": true, + "dependencies": { + "@microsoft/tsdoc": "~0.15.1", + "@microsoft/tsdoc-config": "~0.17.1", + "@rushstack/node-core-library": "5.18.0" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dev": true, + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@microsoft/tsdoc": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", + "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==", + "dev": true + }, + "node_modules/@microsoft/tsdoc-config": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.1.tgz", + "integrity": "sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==", + "dev": true, + "dependencies": { + "@microsoft/tsdoc": "0.15.1", + "ajv": "~8.12.0", + "jju": "~1.4.0", + "resolve": "~1.22.2" + } + }, + "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -832,12 +974,96 @@ "node": ">= 8" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", "dev": true }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.46.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", @@ -851,6 +1077,174 @@ "darwin" ] }, + "node_modules/@rushstack/node-core-library": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.18.0.tgz", + "integrity": "sha512-XDebtBdw5S3SuZIt+Ra2NieT8kQ3D2Ow1HxhDQ/2soinswnOu9e7S69VSwTOLlQnx5mpWbONu+5JJjDxMAb6Fw==", + "dev": true, + "dependencies": { + "ajv": "~8.13.0", + "ajv-draft-04": "~1.0.0", + "ajv-formats": "~3.0.1", + "fs-extra": "~11.3.0", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.22.1", + "semver": "~7.5.4" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/node-core-library/node_modules/ajv": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "dev": true, + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@rushstack/node-core-library/node_modules/fs-extra": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/@rushstack/node-core-library/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rushstack/problem-matcher": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@rushstack/problem-matcher/-/problem-matcher-0.1.1.tgz", + "integrity": "sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA==", + "dev": true, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/rig-package": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.6.0.tgz", + "integrity": "sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw==", + "dev": true, + "dependencies": { + "resolve": "~1.22.1", + "strip-json-comments": "~3.1.1" + } + }, + "node_modules/@rushstack/terminal": { + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.19.3.tgz", + "integrity": "sha512-0P8G18gK9STyO+CNBvkKPnWGMxESxecTYqOcikHOVIHXa9uAuTK+Fw8TJq2Gng1w7W6wTC9uPX6hGNvrMll2wA==", + "dev": true, + "dependencies": { + "@rushstack/node-core-library": "5.18.0", + "@rushstack/problem-matcher": "0.1.1", + "supports-color": "~8.1.1" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/terminal/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@rushstack/ts-command-line": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.1.3.tgz", + "integrity": "sha512-Kdv0k/BnnxIYFlMVC1IxrIS0oGQd4T4b7vKfx52Y2+wk2WZSDFIvedr7JrhenzSlm3ou5KwtoTGTGd5nbODRug==", + "dev": true, + "dependencies": { + "@rushstack/terminal": "0.19.3", + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "string-argv": "~0.3.1" + } + }, + "node_modules/@rushstack/ts-command-line/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/@silvermine/eslint-config": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@silvermine/eslint-config/-/eslint-config-3.2.1.tgz", @@ -932,6 +1326,7 @@ "version": "7.19.0-1", "resolved": "https://registry.npmjs.org/@silvermine/video.js/-/video.js-7.19.0-1.tgz", "integrity": "sha512-2anmfBvJsUx8wZGFV6aOvmqEiWpnQ6tio3EQqu32N/HOO/YcFk7avzgnDNvfsr0TpLLUsxmKhDzrPMItf1EbWA==", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "@videojs/http-streaming": "2.14.0", @@ -952,6 +1347,7 @@ "version": "2.14.0", "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.14.0.tgz", "integrity": "sha512-hsCy9QBsC753AEfy1AUKSv9MuztkAKKofGhSfXj+ToHFlqyutNMYk5B8PUkz2M21sOufuYpMMq5G3bwy4KAnxQ==", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "@videojs/vhs-utils": "3.0.4", @@ -974,6 +1370,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.4.tgz", "integrity": "sha512-hui4zOj2I1kLzDgf8QDVxD3IzrwjS/43KiS8IHQO0OeeSsb4pB/lgNt1NG7Dv0wMQfCccUpMVLGcK618s890Yg==", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "global": "^4.4.0", @@ -988,6 +1385,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz", "integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "global": "^4.4.0", @@ -1002,6 +1400,7 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.6.0.tgz", "integrity": "sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==", + "peer": true, "dependencies": { "@babel/runtime": "^7.5.5", "global": "~4.4.0", @@ -1013,6 +1412,7 @@ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz", "integrity": "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==", "deprecated": "this version is no longer supported, please update to at least 0.8.*", + "peer": true, "engines": { "node": ">=10.0.0" } @@ -1021,6 +1421,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.2.tgz", "integrity": "sha512-42nRwfQuPRj9R1zqZBdoxnaAmnIFyDi0MNyTVhjdFOd8fifXKKRfwIHIZ6AMn1or4x5WONzjwRTbTWcsIQ0O4A==", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "@videojs/vhs-utils": "^3.0.0", @@ -1032,6 +1433,7 @@ "version": "4.7.0", "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.7.0.tgz", "integrity": "sha512-48l/OwRyjBm+QhNNigEEcRcgbRvnUjL7rxs597HmW9QSNbyNvt+RcZ9T/d9vxi9A9z7EZrB1POtZYhdRlwYQkQ==", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "@videojs/vhs-utils": "^3.0.0", @@ -1042,6 +1444,7 @@ "version": "0.21.0", "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.21.0.tgz", "integrity": "sha512-NbpMJ57qQzFmfCiP1pbL7cGMbVTD0X1hqNgL0VYP1wLlZXLf/HtmvQpNkOA1AHkPVeGQng+7/jEtSvNUzV7Gdg==", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "@videojs/vhs-utils": "^3.0.2", @@ -1056,6 +1459,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-6.0.1.tgz", "integrity": "sha512-22CHb59rH8pWGcPGW5Og7JngJ9s+z4XuSlYvnxhLuc58cA1WqGDQPzuG8I+sPm1/p0CdgpzVTaKW408k5DNn8w==", + "peer": true, "dependencies": { "@babel/runtime": "^7.11.2", "global": "^4.4.0" @@ -1072,6 +1476,7 @@ "version": "7.21.7", "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.21.7.tgz", "integrity": "sha512-T2s3WFAht7Zjr2OSJamND9x9Dn2O+Z5WuHGdh8jI5SYh5mkMdVTQ7vSRmA5PYpjXJ2ycch6jpMjkJEIEU2xxqw==", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "@videojs/http-streaming": "2.16.3", @@ -1092,6 +1497,7 @@ "version": "2.16.3", "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.16.3.tgz", "integrity": "sha512-91CJv5PnFBzNBvyEjt+9cPzTK/xoVixARj2g7ZAvItA+5bx8VKdk5RxCz/PP2kdzz9W+NiDUMPkdmTsosmy69Q==", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "@videojs/vhs-utils": "3.0.5", @@ -1114,6 +1520,7 @@ "version": "0.8.11", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "peer": true, "engines": { "node": ">=10.0.0" } @@ -1122,6 +1529,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.3.tgz", "integrity": "sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "@videojs/vhs-utils": "^3.0.5", @@ -1133,6 +1541,7 @@ "version": "4.8.0", "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.8.0.tgz", "integrity": "sha512-UqA2a/Pw3liR6Df3gwxrqghCP17OpPlQj6RBPLYygf/ZSQ4MoSgvdvhvt35qV+3NaaA0FSZx93Ix+2brT1U7cA==", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "@videojs/vhs-utils": "^3.0.5", @@ -1143,6 +1552,7 @@ "version": "0.22.1", "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.22.1.tgz", "integrity": "sha512-fwBebvpyPUU8bOzvhX0VQZgSohncbgYwUyJJoTSNpmy7ccD2ryiCvM7oRkn/xQH5cv73/xU7rJSNCLjdGFor0Q==", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "@videojs/vhs-utils": "^3.0.5", @@ -1156,7 +1566,8 @@ "node_modules/@silvermine/video.js/node_modules/videojs-font": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-3.2.0.tgz", - "integrity": "sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA==" + "integrity": "sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA==", + "peer": true }, "node_modules/@stylelint/postcss-css-in-js": { "version": "0.37.3", @@ -1187,6 +1598,12 @@ "postcss-syntax": ">=0.36.2" } }, + "node_modules/@types/argparse": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", + "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", + "dev": true + }, "node_modules/@types/chai": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", @@ -1573,6 +1990,137 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@volar/language-core": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.23.tgz", + "integrity": "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==", + "dev": true, + "dependencies": { + "@volar/source-map": "2.4.23" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.23.tgz", + "integrity": "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==", + "dev": true + }, + "node_modules/@volar/typescript": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.23.tgz", + "integrity": "sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==", + "dev": true, + "dependencies": { + "@volar/language-core": "2.4.23", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.22.tgz", + "integrity": "sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.28.4", + "@vue/shared": "3.5.22", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz", + "integrity": "sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==", + "dev": true, + "dependencies": { + "@vue/compiler-core": "3.5.22", + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.0.tgz", + "integrity": "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==", + "dev": true, + "dependencies": { + "@volar/language-core": "~2.4.11", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^0.4.9", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.22.tgz", + "integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==", + "dev": true + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -1626,6 +2174,51 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/alien-signals": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.4.14.tgz", + "integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==", + "dev": true + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2011,6 +2604,21 @@ "semver": "bin/semver.js" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/class.extend": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/class.extend/-/class.extend-0.9.1.tgz", @@ -2077,6 +2685,12 @@ "dot-prop": "^5.1.0" } }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2098,6 +2712,12 @@ "typedarray": "^0.0.6" } }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true + }, "node_modules/conventional-changelog": { "version": "3.1.25", "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.25.tgz", @@ -2461,6 +3081,12 @@ "node": "*" } }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -2534,6 +3160,19 @@ "dev": true, "peer": true }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2593,7 +3232,8 @@ "node_modules/dom-walk": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", + "peer": true }, "node_modules/domelementtype": { "version": "1.3.1", @@ -2998,6 +3638,12 @@ "node": ">=12.0.0" } }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "dev": true + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -3408,6 +4054,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "peer": true, "dependencies": { "min-document": "^2.19.0", "process": "^0.11.10" @@ -3584,6 +4231,15 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, "node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -3687,6 +4343,12 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "dev": true + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -3742,7 +4404,8 @@ "node_modules/individual": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz", - "integrity": "sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g==" + "integrity": "sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g==", + "peer": true }, "node_modules/inflight": { "version": "1.0.6", @@ -3866,7 +4529,8 @@ "node_modules/is-function": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", - "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "peer": true }, "node_modules/is-glob": { "version": "4.0.3", @@ -3984,6 +4648,12 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4144,7 +4814,8 @@ "node_modules/keycode": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.1.tgz", - "integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==" + "integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==", + "peer": true }, "node_modules/keyv": { "version": "4.5.4", @@ -4170,6 +4841,12 @@ "integrity": "sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw==", "dev": true }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4236,6 +4913,23 @@ "node": ">=4" } }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4796,6 +5490,7 @@ "version": "2.19.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "peer": true, "dependencies": { "dom-walk": "^0.1.0" } @@ -4844,6 +5539,35 @@ "node": ">= 6" } }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", @@ -4868,6 +5592,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -4898,6 +5628,13 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "optional": true + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -5102,6 +5839,12 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5191,6 +5934,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz", "integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==", + "peer": true, "dependencies": { "@babel/runtime": "^7.5.5" }, @@ -5198,6 +5942,17 @@ "pkcs7": "bin/cli.js" } }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -5431,6 +6186,7 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "peer": true, "engines": { "node": ">= 0.6.0" } @@ -5461,6 +6217,22 @@ "teleport": ">=0.2.0" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ] + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5646,6 +6418,19 @@ "node": ">= 6" } }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -5878,6 +6663,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz", "integrity": "sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==", + "peer": true, "dependencies": { "individual": "^2.0.0" } @@ -5906,6 +6692,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-4.0.0.tgz", "integrity": "sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==", + "peer": true, "dependencies": { "rust-result": "^1.0.0" } @@ -5916,6 +6703,26 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/sass": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz", + "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==", + "dev": true, + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -6098,6 +6905,12 @@ "readable-stream": "^3.0.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -6119,6 +6932,15 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -6171,7 +6993,6 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, - "peer": true, "engines": { "node": ">=8" }, @@ -6860,6 +7681,12 @@ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", "dev": true }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -6986,7 +7813,6 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "peer": true, "dependencies": { "punycode": "^2.1.0" } @@ -6994,7 +7820,8 @@ "node_modules/url-toolkit": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz", - "integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==" + "integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==", + "peer": true }, "node_modules/util-deprecate": { "version": "1.0.2", @@ -7052,6 +7879,7 @@ "version": "0.15.5", "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz", "integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==", + "peer": true, "dependencies": { "global": "^4.3.1" } @@ -7152,6 +7980,32 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-plugin-dts": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-4.5.4.tgz", + "integrity": "sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==", + "dev": true, + "dependencies": { + "@microsoft/api-extractor": "^7.50.1", + "@rollup/pluginutils": "^5.1.4", + "@volar/typescript": "^2.4.11", + "@vue/language-core": "2.2.0", + "compare-versions": "^6.1.1", + "debug": "^4.4.0", + "kolorist": "^1.8.0", + "local-pkg": "^1.0.0", + "magic-string": "^0.30.17" + }, + "peerDependencies": { + "typescript": "*", + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, "node_modules/vitest": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", @@ -7224,6 +8078,12 @@ } } }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true + }, "node_modules/vue-eslint-parser": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz", diff --git a/package.json b/package.json index ed97962..4a12c7b 100644 --- a/package.json +++ b/package.json @@ -2,12 +2,26 @@ "name": "@silvermine/videojs-remoteplayback", "version": "0.0.1", "description": "videojs plugin for casting to chromecast and airplay", - "main": "src/index.ts", + "main": "dist/videojs-remoteplayback.umd.js", + "module": "dist/videojs-remoteplayback.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/videojs-remoteplayback.mjs", + "require": "./dist/videojs-remoteplayback.umd.js" + }, + "./dist/videojs-remoteplayback.css": "./dist/videojs-remoteplayback.css" + }, + "files": [ + "dist/" + ], "scripts": { "build": "vite build", + "dev": "vite --config vite.dev.config.ts", "test": "vitest run", "check-node-version": "check-node-version --node $(cat .nvmrc) --npm 10.5.0 --print", - "stylelint": "stylelint './styles/**/*.scss'", + "stylelint": "stylelint './src/styles/**/*.scss'", "commitlint": "commitlint --from 793bcc7", "eslint": "eslint .", "markdownlint": "markdownlint-cli2", @@ -43,11 +57,13 @@ "@types/video.js": "7.3.58", "@vitest/ui": "3.2.4", "jsdom": "26.1.0", + "sass": "^1.77.0", "typescript": "5.9.2", "vite": "6.3.6", + "vite-plugin-dts": "^4.5.4", "vitest": "3.2.4" }, - "dependencies": { + "peerDependencies": { "@silvermine/video.js": "7.19.0-1" } } diff --git a/src/RemotePlaybackPlugin.ts b/src/RemotePlaybackPlugin.ts new file mode 100644 index 0000000..8430777 --- /dev/null +++ b/src/RemotePlaybackPlugin.ts @@ -0,0 +1,94 @@ +import type { RemotePlaybackState } from '../@types/remote-playback'; +import type { VideoJsPlayer } from '../@types/videojs'; +import { AirPlayManager, AirPlayButton } from './js/airplay/AirPlayManager'; +import { logError, logInfo } from './lib/logging'; +import { COMPONENT_NAMES } from './js/airplay/constants/component-names'; +import { EVENTS } from './constants/remote-playback'; +import { LOG_MESSAGES } from './constants/log-messages'; + +class RemotePlaybackPlugin { + private readonly _player: VideoJsPlayer; + private readonly _airPlayManager: AirPlayManager; + + public constructor(player: VideoJsPlayer) { + this._player = player; + this._airPlayManager = new AirPlayManager(player); + + this._player.airPlay = this._airPlayManager; + + this._initialize(); + } + + public getState(): RemotePlaybackState | null { + return this._airPlayManager.getState(); + } + + public isConnected(): boolean { + return this._airPlayManager.isConnected(); + } + + private _initialize(): void { + this._addAirPlayButton(); + this._checkAvailability(); + } + + private _addAirPlayButton(): void { + logInfo('Initializing AirPlay button...'); + + this._player.ready(() => { + logInfo(LOG_MESSAGES.PLAYER_READY); + this._addButtonToControlBar(); + }); + } + + private _addButtonToControlBar(): void { + const controlBar = this._player.getChild(COMPONENT_NAMES.CONTROL_BAR); + + if (!controlBar) { + logError(LOG_MESSAGES.CONTROL_BAR_NOT_FOUND); + return; + } + + try { + const airPlayButton = new AirPlayButton(this._player, { addAirPlayLabelToButton: true }); + + logInfo(LOG_MESSAGES.BUTTON_CREATED); + + this._insertButtonInControlBar(controlBar, airPlayButton); + logInfo(LOG_MESSAGES.BUTTON_ADDED); + } catch(error) { + logError('Failed to create or add AirPlay button', error); + } + } + + private _insertButtonInControlBar(controlBar: unknown, airPlayButton: AirPlayButton): void { + const fullscreenToggle = (controlBar as { getChild: (name: string) => unknown }).getChild(COMPONENT_NAMES.FULLSCREEN_TOGGLE); + + if (fullscreenToggle) { + const children = (controlBar as { children: () => unknown[] }).children(); + + const insertIndex = children.indexOf(fullscreenToggle); + + (controlBar as { addChild: (component: unknown, options?: Record, index?: number) => void }) + .addChild(airPlayButton, {}, insertIndex); + } else { + (controlBar as { addChild: (component: unknown) => void }).addChild(airPlayButton); + } + } + + private async _checkAvailability(): Promise { + try { + const available = await this._airPlayManager.isAvailable(); + + if (available) { + this._player.trigger(EVENTS.REMOTE_PLAYBACK.AVAILABLE); + } else { + this._player.trigger(EVENTS.REMOTE_PLAYBACK.UNAVAILABLE); + } + } catch(error) { + logError('Error checking remote playback availability', error); + } + } +} + +export { RemotePlaybackPlugin }; diff --git a/src/assets/ic_airplay_white_24px.svg b/src/assets/ic_airplay_white_24px.svg new file mode 100644 index 0000000..aa50dc5 --- /dev/null +++ b/src/assets/ic_airplay_white_24px.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/constants/log-messages.ts b/src/constants/log-messages.ts new file mode 100644 index 0000000..ea2f35a --- /dev/null +++ b/src/constants/log-messages.ts @@ -0,0 +1,13 @@ +export const LOG_MESSAGES = { + PLUGIN_INIT: 'Remote playback plugin initialized', + BUTTON_CREATED: 'AirPlay button created successfully', + BUTTON_ADDED: 'AirPlay button added to control bar', + PLAYER_READY: 'Player ready, adding AirPlay button', + API_NOT_SUPPORTED: 'Remote Playback API not supported', + AIRPLAY_NOT_SUPPORTED: 'AirPlay not supported on this device', + AIRPLAY_SUPPORTED: 'AirPlay support detected', + WEBKIT_AIRPLAY_SUPPORTED: 'WebKit AirPlay API supported - full device compatibility', + WEBKIT_AIRPLAY_PREFERRED: 'Using WebKit AirPlay API for full device list', + CONTROL_BAR_NOT_FOUND: 'Control bar not found', + PICKER_OPENED: 'Remote playback picker opened successfully', +} as const; diff --git a/src/constants/plugin-name.ts b/src/constants/plugin-name.ts new file mode 100644 index 0000000..3904717 --- /dev/null +++ b/src/constants/plugin-name.ts @@ -0,0 +1 @@ +export const PLUGIN_NAME = 'remotePlayback'; diff --git a/src/constants/remote-playback.ts b/src/constants/remote-playback.ts new file mode 100644 index 0000000..0d01325 --- /dev/null +++ b/src/constants/remote-playback.ts @@ -0,0 +1,22 @@ +export const EVENTS = { + REMOTE_PLAYBACK: { + AVAILABLE: 'remoteplayback:available', + UNAVAILABLE: 'remoteplayback:unavailable', + }, + AIRPLAY: { + AVAILABILITY_CHANGE: 'airplay:availabilitychange', + CONNECTING: 'airplay:connecting', + CONNECTED: 'airplay:connected', + DISCONNECTED: 'airplay:disconnected', + }, +} as const; + +export const ARIA_ATTRIBUTES = { + HIDDEN: 'aria-hidden', + LIVE: 'aria-live', +} as const; + +export const AVAILABILITY_STATES = { + AVAILABLE: 'available', + NOT_AVAILABLE: 'not-available', +} as const; diff --git a/src/index.ts b/src/index.ts index 596d715..f32def1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,10 @@ -import remotePlayback from './plugin'; -import { VideoJs } from './types'; +import remotePlayback from './registerPlugin'; +import type { VideoJs } from '../@types/videojs'; +import { logInfo } from './lib/logging'; +import { LOG_MESSAGES } from './constants/log-messages'; +import './styles/airplay.scss'; export default function initializePlugin(videojs: VideoJs): void { - videojs.log('init remote playback plugin'); + logInfo(LOG_MESSAGES.PLUGIN_INIT); remotePlayback(videojs); } diff --git a/src/js/airplay/AirPlayButton.ts b/src/js/airplay/AirPlayButton.ts new file mode 100644 index 0000000..10676d2 --- /dev/null +++ b/src/js/airplay/AirPlayButton.ts @@ -0,0 +1,132 @@ +import videojs from '@silvermine/video.js'; +import type { VideoJsPlayer } from '../../../@types/videojs'; +import type { RemotePlaybackAvailabilityEvent } from '../../../@types/remote-playback'; +import { AirPlayButtonOptions } from './interfaces/Airplay.interfaces'; +import { logError, logInfo } from '../../lib/logging'; +import { getMediaElement } from '../../lib/get-media-element'; +import { hasAirPlaySupport } from './lib/hasAirplaySupport'; +import { hasRemotePlaybackSupport } from './lib/hasRemotePlaybackSupport'; +import { CSS_CLASSES } from './constants/css-classes'; +import { COMPONENT_NAMES } from './constants/component-names'; +import { LOG_MESSAGES } from '../../constants/log-messages'; +import { AVAILABILITY_STATES, EVENTS } from '../../constants/remote-playback'; + +class AirPlayButton extends videojs.getComponent('Button') { + private readonly _player: VideoJsPlayer; + private _labelEl?: HTMLSpanElement; + + public constructor(player: VideoJsPlayer, options: AirPlayButtonOptions = {}) { + super(player, options as Record); + + this._player = player; + + this._initializeButton(options); + this._setupEventListeners(); + } + + public buildCSSClass(): string { + return `${CSS_CLASSES.AIRPLAY_BUTTON} ${super.buildCSSClass()}`; + } + + public handleClick(): void { + if (!this._player.airPlay) { + logError('AirPlay manager not available'); + return; + } + + this._player.airPlay.prompt() + .then(() => { + logInfo(LOG_MESSAGES.PICKER_OPENED); + }) + .catch((error: Error) => { + logError('Failed to open AirPlay picker', error); + }); + } + + + private _initializeButton(options: AirPlayButtonOptions): void { + if (hasAirPlaySupport()) { + logInfo(LOG_MESSAGES.AIRPLAY_SUPPORTED); + } else { + this.hide(); + logInfo(LOG_MESSAGES.AIRPLAY_NOT_SUPPORTED); + } + + if (options.addAirPlayLabelToButton) { + this.el().classList.add(CSS_CLASSES.AIRPLAY_BUTTON_LARGE); + + this._labelEl = document.createElement('span'); + this._labelEl.classList.add(CSS_CLASSES.AIRPLAY_BUTTON_LABEL); + this._labelEl.textContent = this.localize('AirPlay'); + + this.el().appendChild(this._labelEl); + } else { + this.controlText('Start AirPlay'); + } + } + + private _setupEventListeners(): void { + this._setupAvailabilityListeners(); + this._setupStateEventListeners(); + } + + private _setupAvailabilityListeners(): void { + this._player.on(EVENTS.REMOTE_PLAYBACK.AVAILABLE, () => { + this.show(); + }); + + this._player.on(EVENTS.REMOTE_PLAYBACK.UNAVAILABLE, () => { + this.hide(); + }); + + this._setupDirectAPIListeners(); + } + + private _setupDirectAPIListeners(): void { + const mediaEl = getMediaElement(this._player); + + if (!mediaEl || !hasAirPlaySupport()) { + return; + } + + const onAvailabilityChange = (event: Event): void => { + const remoteEvent = event as RemotePlaybackAvailabilityEvent; + + if (remoteEvent.availability === AVAILABILITY_STATES.AVAILABLE) { + this.show(); + } else { + this.hide(); + } + }; + + if (hasRemotePlaybackSupport() && mediaEl.remote) { + mediaEl.remote.addEventListener('availabilitychange', onAvailabilityChange); + this.on('dispose', () => { + if (mediaEl.remote) { + mediaEl.remote.removeEventListener('availabilitychange', onAvailabilityChange); + } + }); + } + } + + private _setupStateEventListeners(): void { + this._player.on(EVENTS.AIRPLAY.CONNECTING, () => { + this.removeClass(CSS_CLASSES.AIRPLAY_CONNECTED); + this.addClass(CSS_CLASSES.AIRPLAY_CONNECTING); + }); + + this._player.on(EVENTS.AIRPLAY.CONNECTED, () => { + this.removeClass(CSS_CLASSES.AIRPLAY_CONNECTING); + this.addClass(CSS_CLASSES.AIRPLAY_CONNECTED); + }); + + this._player.on(EVENTS.AIRPLAY.DISCONNECTED, () => { + this.removeClass(CSS_CLASSES.AIRPLAY_CONNECTING); + this.removeClass(CSS_CLASSES.AIRPLAY_CONNECTED); + }); + } +} + +videojs.registerComponent(COMPONENT_NAMES.AIRPLAY_BUTTON, AirPlayButton); + +export { AirPlayButton, hasRemotePlaybackSupport }; diff --git a/src/js/airplay/AirPlayManager.ts b/src/js/airplay/AirPlayManager.ts new file mode 100644 index 0000000..185e005 --- /dev/null +++ b/src/js/airplay/AirPlayManager.ts @@ -0,0 +1,160 @@ +import type { VideoJsPlayer } from '../../../@types/videojs'; +import type { + RemotePlaybackState, + HTMLVideoElementWithAirPlay, + WebKitPlaybackTargetAvailabilityEvent, +} from '../../../@types/remote-playback'; +import { getMediaElement } from '../../lib/get-media-element'; +import { logInfo, logError } from '../../lib/logging'; +import { EVENTS } from '../../constants/remote-playback'; +import { LOG_MESSAGES } from '../../constants/log-messages'; +import { hasAirPlaySupport } from './lib/hasAirplaySupport'; +import { hasRemotePlaybackSupport } from './lib/hasRemotePlaybackSupport'; +import { hasWebKitAirPlaySupport } from './lib/hasWebkitAirplaySupport'; + +class AirPlayManager { + private readonly _player: VideoJsPlayer; + private _remotePlayback: RemotePlayback | null = null; + private _webkitAirPlaySupported = false; + + public constructor(player: VideoJsPlayer) { + this._player = player; + this._initialize(); + } + + public async isAvailable(): Promise { + if (!hasAirPlaySupport()) { + logInfo(LOG_MESSAGES.AIRPLAY_NOT_SUPPORTED); + return false; + } + + logInfo(LOG_MESSAGES.AIRPLAY_SUPPORTED); + + if (this._remotePlayback) { + try { + await this._remotePlayback.watchAvailability((available: boolean) => { + this._player.trigger(EVENTS.AIRPLAY.AVAILABILITY_CHANGE, { available }); + }); + return true; + } catch(error) { + logError('Error checking Remote Playback availability', error); + } + } + + if (this._webkitAirPlaySupported) { + return this._checkWebKitAirPlayAvailability(); + } + + return false; + } + + public getState(): RemotePlaybackState | null { + return this._remotePlayback?.state || null; + } + + public isConnected(): boolean { + return this._remotePlayback?.state === 'connected'; + } + + public async prompt(): Promise { + if (this._webkitAirPlaySupported) { + const videoElement = getMediaElement(this._player) as HTMLVideoElementWithAirPlay; + + if (videoElement?.webkitShowPlaybackTargetPicker) { + logInfo(LOG_MESSAGES.WEBKIT_AIRPLAY_PREFERRED); + videoElement.webkitShowPlaybackTargetPicker(); + return Promise.resolve(); + } + } + + if (this._remotePlayback) { + logInfo('Using Remote Playback API - limited device compatibility'); + return this._remotePlayback.prompt(); + } + + throw new Error('No AirPlay API available'); + } + + private _initialize(): void { + const videoElement = getMediaElement(this._player); + + if (!videoElement) { + logError('Video element not found'); + return; + } + + if (hasRemotePlaybackSupport() && 'remote' in videoElement) { + this._remotePlayback = videoElement.remote || null; + logInfo('Remote Playback API initialized'); + } + + if (hasWebKitAirPlaySupport()) { + this._webkitAirPlaySupported = true; + logInfo(LOG_MESSAGES.WEBKIT_AIRPLAY_SUPPORTED); + } + + this._setupEventListeners(); + } + + private _checkWebKitAirPlayAvailability(): boolean { + const videoElement = getMediaElement(this._player) as HTMLVideoElementWithAirPlay; + + if (!videoElement) { + return false; + } + + const onAvailabilityChange = (event: Event): void => { + const webkitEvent = event as WebKitPlaybackTargetAvailabilityEvent; + + const available = webkitEvent.availability === 'available'; + + this._player.trigger(EVENTS.AIRPLAY.AVAILABILITY_CHANGE, { available }); + }; + + videoElement.addEventListener('webkitplaybacktargetavailabilitychanged', onAvailabilityChange); + + return true; + } + + private _setupEventListeners(): void { + if (this._remotePlayback) { + this._remotePlayback.addEventListener('connect', () => { + this._player.trigger(EVENTS.AIRPLAY.CONNECTED); + }); + + this._remotePlayback.addEventListener('connecting', () => { + this._player.trigger(EVENTS.AIRPLAY.CONNECTING); + }); + + this._remotePlayback.addEventListener('disconnect', () => { + this._player.trigger(EVENTS.AIRPLAY.DISCONNECTED); + }); + } + + if (this._webkitAirPlaySupported) { + this._setupWebKitEventListeners(); + } + } + + private _setupWebKitEventListeners(): void { + const videoElement = getMediaElement(this._player) as HTMLVideoElementWithAirPlay; + + if (!videoElement) { + return; + } + + const onPlaybackTargetChange = (): void => { + if (videoElement.webkitCurrentPlaybackTargetIsWireless) { + this._player.trigger(EVENTS.AIRPLAY.CONNECTED); + } else { + this._player.trigger(EVENTS.AIRPLAY.DISCONNECTED); + } + }; + + videoElement.addEventListener('webkitcurrentplaybacktargetiswirelesschanged', onPlaybackTargetChange); + } + +} + +export { AirPlayManager }; +export { AirPlayButton } from './AirPlayButton'; diff --git a/src/js/airplay/constants/component-names.ts b/src/js/airplay/constants/component-names.ts new file mode 100644 index 0000000..1eb89d1 --- /dev/null +++ b/src/js/airplay/constants/component-names.ts @@ -0,0 +1,5 @@ +export const COMPONENT_NAMES = { + AIRPLAY_BUTTON: 'AirPlayButton', + CONTROL_BAR: 'controlBar', + FULLSCREEN_TOGGLE: 'fullscreenToggle', +} as const; diff --git a/src/js/airplay/constants/css-classes.ts b/src/js/airplay/constants/css-classes.ts new file mode 100644 index 0000000..85173c8 --- /dev/null +++ b/src/js/airplay/constants/css-classes.ts @@ -0,0 +1,9 @@ +export const CSS_CLASSES = { + AIRPLAY_BUTTON: 'vjs-airplay-button', + AIRPLAY_BUTTON_LARGE: 'vjs-airplay-button-lg', + AIRPLAY_BUTTON_LABEL: 'vjs-airplay-button-label', + AIRPLAY_CONNECTED: 'vjs-airplay-connected', + AIRPLAY_CONNECTING: 'vjs-airplay-connecting', + ICON_PLACEHOLDER: 'vjs-icon-placeholder', + CONTROL_TEXT: 'vjs-control-text', +} as const; diff --git a/src/js/airplay/interfaces/Airplay.interfaces.ts b/src/js/airplay/interfaces/Airplay.interfaces.ts new file mode 100644 index 0000000..317492c --- /dev/null +++ b/src/js/airplay/interfaces/Airplay.interfaces.ts @@ -0,0 +1,17 @@ +export interface ControlBarChild { + name(): string; + addChild(component: unknown, options?: Record, index?: number): void; + getChild(name: string): unknown; + children(): unknown[]; +} + +export interface AirPlayManager { + isAvailable(): Promise; + getState(): RemotePlaybackState | null; + isConnected(): boolean; + prompt(): Promise; +} + +export interface AirPlayButtonOptions extends Record { + addAirPlayLabelToButton?: boolean; +} diff --git a/src/js/airplay/lib/hasAirplaySupport.ts b/src/js/airplay/lib/hasAirplaySupport.ts new file mode 100644 index 0000000..c5d821a --- /dev/null +++ b/src/js/airplay/lib/hasAirplaySupport.ts @@ -0,0 +1,14 @@ +export function hasAirPlaySupport(): boolean { + if (typeof window === 'undefined') { + return false; + } + + const hasVideoElement = 'HTMLVideoElement' in window; + + const hasPickerMethod = hasVideoElement && + 'webkitShowPlaybackTargetPicker' in (HTMLVideoElement.prototype as unknown as Record); + + const hasAvailabilityEvent = 'WebKitPlaybackTargetAvailabilityEvent' in window; + + return hasPickerMethod || hasAvailabilityEvent; +} diff --git a/src/js/airplay/lib/hasRemotePlaybackSupport.ts b/src/js/airplay/lib/hasRemotePlaybackSupport.ts new file mode 100644 index 0000000..090945b --- /dev/null +++ b/src/js/airplay/lib/hasRemotePlaybackSupport.ts @@ -0,0 +1,5 @@ +export function hasRemotePlaybackSupport(): boolean { + return typeof window !== 'undefined' && + 'HTMLVideoElement' in window && + 'remote' in HTMLVideoElement.prototype; +} diff --git a/src/js/airplay/lib/hasWebkitAirplaySupport.ts b/src/js/airplay/lib/hasWebkitAirplaySupport.ts new file mode 100644 index 0000000..3b1a043 --- /dev/null +++ b/src/js/airplay/lib/hasWebkitAirplaySupport.ts @@ -0,0 +1,4 @@ +export function hasWebKitAirPlaySupport(): boolean { + return typeof window !== 'undefined' && + 'WebKitPlaybackTargetAvailabilityEvent' in window; +} diff --git a/src/lib/get-media-element.ts b/src/lib/get-media-element.ts new file mode 100644 index 0000000..1cba129 --- /dev/null +++ b/src/lib/get-media-element.ts @@ -0,0 +1,7 @@ +import type { VideoJsPlayer } from '../../@types/videojs'; + +export function getMediaElement(player: VideoJsPlayer): HTMLVideoElement | HTMLAudioElement | null { + const playerEl = player.el(); + + return playerEl.querySelector('video, audio') as HTMLVideoElement | HTMLAudioElement | null; +} diff --git a/src/lib/logging.ts b/src/lib/logging.ts new file mode 100644 index 0000000..a9d223d --- /dev/null +++ b/src/lib/logging.ts @@ -0,0 +1,9 @@ +import videojs from '@silvermine/video.js'; + +export function logError(message: string, error?: unknown): void { + videojs.log(`ERROR: ${message}`, error); +} + +export function logInfo(message: string): void { + videojs.log(message); +} diff --git a/src/lib/safely-execute.ts b/src/lib/safely-execute.ts new file mode 100644 index 0000000..0b8b0f8 --- /dev/null +++ b/src/lib/safely-execute.ts @@ -0,0 +1,27 @@ +import { logError } from './logging'; + +export function safelyExecute( + operation: () => T, + errorMessage: string, + fallbackValue?: T +): T | undefined { + try { + return operation(); + } catch(error) { + logError(errorMessage, error); + return fallbackValue; + } +} + +export async function safelyExecuteAsync( + operation: () => Promise, + errorMessage: string, + fallbackValue?: T +): Promise { + try { + return await operation(); + } catch(error) { + logError(errorMessage, error); + return fallbackValue; + } +} diff --git a/src/plugin.ts b/src/plugin.ts deleted file mode 100644 index ac796f0..0000000 --- a/src/plugin.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { VideoJs } from './types'; - -/** - * Main plugin function to initialize remote playback - * - * @param videojs - Video.js constructor - * @return void - */ -export default function remotePlayback(videojs: VideoJs): void { - videojs.registerPlugin('remotePlayback', function() { - videojs.log('RemotePlaybackPlugin registered'); - }); -} diff --git a/src/registerPlugin.ts b/src/registerPlugin.ts new file mode 100644 index 0000000..c88e66f --- /dev/null +++ b/src/registerPlugin.ts @@ -0,0 +1,21 @@ +import type { VideoJs, VideoJsPlayer } from '../@types/videojs'; +import { RemotePlaybackPlugin } from './RemotePlaybackPlugin'; +import { PLUGIN_NAME } from './constants/plugin-name'; + +export default function registerPlugin(videojs: VideoJs): void { + videojs.registerPlugin(PLUGIN_NAME, function(this: VideoJsPlayer) { + const plugin = new RemotePlaybackPlugin(this); + + this.remotePlayback = plugin; + }); + + videojs.hook('setup', function(player: VideoJsPlayer & { remotePlayback?: () => void }) { + player.ready(() => { + if (typeof player.remotePlayback === 'function') { + player.remotePlayback(); + } + }); + }); +} + +export { RemotePlaybackPlugin }; diff --git a/src/styles/airplay.scss b/src/styles/airplay.scss new file mode 100644 index 0000000..2ba9265 --- /dev/null +++ b/src/styles/airplay.scss @@ -0,0 +1,61 @@ +$icon-airplay--default: '../assets/ic_airplay_white_24px.svg' !default; +$icon-airplay--hover: '../assets/ic_airplay_white_24px.svg' !default; + +$airplay-icon-size: 12px !default; +$airplay-button-spacing: 4px !default; + +.vjs-airplay-button { + .vjs-icon-placeholder { + background: url($icon-airplay--default) center center no-repeat; + background-size: contain; + display: inline-block; + width: $airplay-icon-size; + height: $airplay-icon-size; + } + &:hover { + cursor: pointer; + .vjs-icon-placeholder { + background-image: url($icon-airplay--hover); + } + } +} + +.vjs-airplay-button.vjs-airplay-button-lg:not(.vjs-hidden) { + display: flex; + align-items: center; + width: auto; + padding: 0 $airplay-button-spacing; + .vjs-airplay-button-label { + flex-grow: 1; + margin-left: $airplay-button-spacing; + } + .vjs-icon-placeholder { + flex-grow: 1; + } +} + +.vjs-airplay-button.vjs-airplay-connected .vjs-icon-placeholder { + opacity: 1; + filter: brightness(1.2); +} + +.vjs-airplay-button.vjs-airplay-connecting .vjs-icon-placeholder { + opacity: 0.6; + animation: vjs-airplay-pulse 1.5s ease-in-out infinite; +} + +@keyframes vjs-airplay-pulse { + 0% { + opacity: 0.6; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.6; + } +} + +.vjs-airplay-button:not(.vjs-hidden) { + display: block; +} diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index 0ebfa79..0000000 --- a/src/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import videojs from '@silvermine/video.js'; - -import type { VideoJsPlayer as VjsPlayer } from 'video.js'; - -export type VideoJs = typeof videojs; - -export type VideoJsPlayer = VjsPlayer; diff --git a/styles/remote-playback.scss b/styles/remote-playback.scss deleted file mode 100644 index 20ab47f..0000000 --- a/styles/remote-playback.scss +++ /dev/null @@ -1 +0,0 @@ -// Remote Playback Plugin Styles diff --git a/tests/airplay.test.ts b/tests/airplay.test.ts new file mode 100644 index 0000000..dad36c9 --- /dev/null +++ b/tests/airplay.test.ts @@ -0,0 +1,94 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { VideoJsPlayer } from '../@types/videojs'; +import type { AirPlayManager as AirPlayManagerType } from '../src/js/airplay/interfaces/Airplay.interfaces'; +import { AirPlayManager } from '../src/js/airplay/AirPlayManager'; + +describe('AirPlayManager', () => { + let mockPlayer: VideoJsPlayer, + mockRemotePlayback: any, + airPlayManager: AirPlayManagerType; + + beforeEach(() => { + // Mock browser APIs for AirPlay detection + Object.defineProperty(HTMLVideoElement.prototype, 'remote', { + value: {}, + writable: true, + configurable: true, + }); + + Object.defineProperty(HTMLVideoElement.prototype, 'webkitShowPlaybackTargetPicker', { + value: vi.fn(), + writable: true, + configurable: true, + }); + + Object.defineProperty(window, 'WebKitPlaybackTargetAvailabilityEvent', { + value: class MockWebKitPlaybackTargetAvailabilityEvent extends Event { + public availability: 'available' | 'not-available' = 'available'; + }, + writable: true, + configurable: true, + }); + + mockRemotePlayback = { + state: 'disconnected', + watchAvailability: vi.fn(), + addEventListener: vi.fn(), + }; + + const mockVideoElement = { + remote: mockRemotePlayback, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + webkitShowPlaybackTargetPicker: vi.fn(), + webkitCurrentPlaybackTargetIsWireless: false, + }; + + mockPlayer = { + el: vi.fn().mockReturnValue({ + querySelector: vi.fn().mockReturnValue(mockVideoElement), + }), + trigger: vi.fn(), + on: vi.fn(), + } as unknown as VideoJsPlayer; + + airPlayManager = new AirPlayManager(mockPlayer); + }); + + it('should initialize without errors', () => { + expect(airPlayManager).toBeDefined(); + }); + + it('should return true when RemotePlayback is available', async () => { + mockRemotePlayback.watchAvailability.mockResolvedValue(undefined); + + const result = await airPlayManager.isAvailable(); + + expect(result).toBe(true); + }); + + it('should fallback to WebKit AirPlay when RemotePlayback throws error', async () => { + mockRemotePlayback.watchAvailability.mockRejectedValue(new Error('Not supported')); + + const result = await airPlayManager.isAvailable(); + + // Should return true because WebKit AirPlay API is available as fallback + expect(result).toBe(true); + }); + + it('should return current state', () => { + mockRemotePlayback.state = 'connected'; + + const state = airPlayManager.getState(); + + expect(state).toBe('connected'); + }); + + it('should return connection status', () => { + mockRemotePlayback.state = 'connected'; + + const connected = airPlayManager.isConnected(); + + expect(connected).toBe(true); + }); +}); diff --git a/tests/index.test.ts b/tests/index.test.ts index 5c06ac9..c22f4d2 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import initializePlugin from '../src/index'; +import initializePlugin from '../src'; import { mockVideoJs } from './mocks/video-js-mock'; describe('Remote Playback Plugin', () => { @@ -10,7 +10,7 @@ describe('Remote Playback Plugin', () => { expect(initFunction).not.toThrow(); - expect(mockVideoJs.log) - .toHaveBeenCalledWith('init remote playback plugin'); + expect(mockVideoJs.registerPlugin).toHaveBeenCalled(); + expect(mockVideoJs.hook).toHaveBeenCalled(); }); }); diff --git a/tests/mocks/video-js-mock.ts b/tests/mocks/video-js-mock.ts index e38e87c..cde0433 100644 --- a/tests/mocks/video-js-mock.ts +++ b/tests/mocks/video-js-mock.ts @@ -1,8 +1,9 @@ import { vi } from 'vitest'; -import { VideoJs } from '../../src/types'; +import { VideoJs } from '../../@types/videojs'; export const mockVideoJs: VideoJs = { log: vi.fn(), registerPlugin: vi.fn(), getPlugin: vi.fn(), + hook: vi.fn(), } as unknown as VideoJs; diff --git a/tsconfig.json b/tsconfig.json index d51cf64..43ba542 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,11 @@ { "extends": "@silvermine/typescript-config/tsconfig.json", "compilerOptions": { - "lib": ["DOM", "ES2020"] - } + "lib": ["DOM", "ES2020"], + "typeRoots": ["./node_modules/@types", "./@types"] + }, + "include": [ + "src/**/*", + "@types/**/*" + ] } diff --git a/vite.config.ts b/vite.config.ts index 7b4fdd2..c5f186c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,8 +1,18 @@ // eslint-disable-next-line @typescript-eslint/triple-slash-reference, spaced-comment /// import { defineConfig } from 'vite'; +import dts from 'vite-plugin-dts'; export default defineConfig({ + plugins: [ + dts({ + insertTypesEntry: true, + outDir: 'dist', + include: [ 'src/**/*', '@types/**/*' ], + exclude: [ 'tests/**/*', 'examples/**/*', '**/*.test.*', '**/*.spec.*' ], + rollupTypes: true, + }), + ], build: { lib: { entry: 'src/index.ts', @@ -10,13 +20,15 @@ export default defineConfig({ fileName: 'videojs-remoteplayback', }, rollupOptions: { - external: [ 'video.js' ], + external: [ '@silvermine/video.js' ], output: { globals: { - 'video.js': 'videojs', + '@silvermine/video.js': 'videojs', }, + assetFileNames: 'videojs-remoteplayback.css', }, }, + assetsInlineLimit: 10000, // Inline assets smaller than 10KB (our SVG is ~623 bytes) }, test: { globals: true, diff --git a/vite.dev.config.ts b/vite.dev.config.ts new file mode 100644 index 0000000..ba3d890 --- /dev/null +++ b/vite.dev.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + root: 'examples', + server: { + port: 3000, + open: true, + fs: { + // Allow serving files from parent directory + allow: [ '..' ], + }, + }, + // Serve dist files at root path (makes /videojs-remoteplayback.* available) + publicDir: '../dist', + build: { + outDir: '../dev-dist', + emptyOutDir: true, + }, +});