feat(plugin-md-power): add artplayer support (#393)

* feat(plugin-md-power): add `artplayer` support

* docs: update docs

* test: add unit test
This commit is contained in:
pengzhanbo 2024-12-22 00:28:01 +08:00 committed by GitHub
parent 2557af8e01
commit bd4c3506dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 726 additions and 12 deletions

View File

@ -95,6 +95,7 @@ export const themeGuide = defineNoteConfig({
'pdf',
'bilibili',
'youtube',
'artplayer',
],
},
],

View File

@ -30,6 +30,7 @@ export const theme: Theme = plumeTheme({
caniuse: true,
bilibili: true,
youtube: true,
artPlayer: true,
codepen: true,
replit: true,
codeSandbox: true,

View File

@ -0,0 +1,170 @@
---
title: artPlayer
icon: icon-park-outline:video
createTime: 2024/12/21 16:13:54
permalink: /guide/embed/video/artplayer/
---
## 概述
主题提供嵌入 自定义来源视频 的功能。
该功能由 [vuepress-plugin-md-power](../../config/plugins/markdownPower.md) 提供支持。
## 配置
该功能默认不启用。你需要在主题配置中开启。
::: code-tabs
@tab .vuepress/config.ts
```ts
export default defineUserConfig({
theme: plumeTheme({
plugins: {
markdownPower: {
artPlayer: true,
},
}
})
})
```
:::
## 安装
该功能依赖于 `artplayer` 播放器实现,主题默认不安装该依赖,在启用 `artPlayer` 功能时,需要
进行手动安装
::: npm-to
```sh
npm i artplayer
```
:::
artPlayer 播放器默认支持 `'mp4'`, `'mp3'`, `'webm'`, `'ogg'` 格式的视频格式。
同时还支持扩展其他格式的支持。
如果您的视频格式为 `'mpd'`, `'dash'`, 还需要再手动安装 `dashjs`
::: npm-to
```sh
npm i dashjs
```
:::
如果您的视频格式为 `'m3u8'`, `'hls'`, 还需要再手动安装 `hls.js`
::: npm-to
```sh
npm i hls.js
```
:::
如果您的视频格式为 `'ts'`, `'flv'`, 还需要再手动安装 `mpegts.js`
::: npm-to
```sh
npm i mpegts.js
```
:::
## markdown 语法
```md
@[artPlayer](src)
```
添加配置项:
```md
@[artPlayer muted autoplay loop width="100%" height="400px" ratio="16:9"](src)
```
- `src`: 视频链接地址
**配置项说明:**
- `width` 视频宽度
- `height` 视频高度
- `ratio` 视频比例,默认 `16:9`
- `type`: 视频格式,默认从视频链接的文件地址中解析获取
- `autoplay` 是否自动播放
- `muted`: 是否静音autoplay 时,默认为 `true`
- `volume` 音量大小,范围为 `0 - 1`
- `poster`:视频封面图链接地址
- `auto-mini`: 当播放器滚动到浏览器视口以外时,自动进入 `迷你播放` 模式
## 全局组件
主题提供了全局组件 `<ArtPlayer />` 以支持更灵活丰富的使用方式。
### Props
|字段 |类型 |描述 |
| -- | -- | -- |
| src | `string` | 必填,视频播放地址 |
| type | `string` | 选填,视频格式,默认从 `src` 中截取 |
| width | `string` | 选填, 宽度, 默认 `100%` |
| height | `string` | 选填,高度 |
| ratio | `string` | 选填,宽高比,默认 `16:9` |
更多 `Props` 请参考 [artPlayer 文档](https://artplayer.org/document/start/option.html) 主题支持所有选项。
## 示例
::: tip 说明
示例中的视频资源来自 [artplayer.org](https://artplayer.org) 。
:::
**输入:**
```md
@[artPlayer](https://artplayer.org/assets/sample/video.mp4)
```
**输出:**
@[artPlayer](https://artplayer.org/assets/sample/video.mp4)
**输入:**
```md
<ArtPlayer
src="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
fullscreen
/>
```
**输出:**
<ArtPlayer
src="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
fullscreen
/>
## 说明
markdown 语法 `@[artPlayer](src)` 在主题内部转换为了 `<ArtPlayer />` 组件,它等价于
```md
<ArtPlayer
src="src"
fullscreen
flip
playback-rate
aspect-ratio
setting
pip
/>
```

View File

@ -1,6 +1,5 @@
---
title: Bilibili 视频
author: pengzhanbo
icon: ri:bilibili-fill
createTime: 2024/03/28 12:26:47
permalink: /guide/embed/video/bilibili/
@ -8,11 +7,9 @@ permalink: /guide/embed/video/bilibili/
## 概述
有时候,你想在你的文档中嵌入视频,以提高内容的表达能力。
主题提供了 嵌入 Bilibili 视频 的功能。
该功能由 [vuepress-plugin-md-power](/) 提供支持。
该功能由 [vuepress-plugin-md-power](../../config/plugins/markdownPower.md) 提供支持。
## 配置

View File

@ -1,6 +1,5 @@
---
title: Youtube 视频
author: pengzhanbo
icon: mdi:youtube
createTime: 2024/03/28 14:30:33
permalink: /guide/embed/video/youtube/
@ -8,11 +7,9 @@ permalink: /guide/embed/video/youtube/
## 概述
有时候,你想在你的文档中嵌入视频,以提高内容的表达能力。
主题提供了 嵌入 Youtube 视频 的功能。
该功能由 [vuepress-plugin-md-power](/) 提供支持。
该功能由 [vuepress-plugin-md-power](../../config/plugins/markdownPower.md) 提供支持。
## 配置

View File

@ -0,0 +1,10 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`artPlayerPlugin > should not work 1`] = `
"<ArtPlayer src="" fullscreen flip playback-rate aspect-ratio setting pip :volume="0.75" width="100%"/><p>@[artPlayer]xxx</p>
<p>@[ artPlayer]123456</p>
<p>@<a href="/xxx.mp4"> artPlayer</a></p>
<ArtPlayer src="/xxx.xxx" fullscreen flip playback-rate aspect-ratio setting pip type="xxx" :volume="0.75" width="100%"/>"
`;
exports[`artPlayerPlugin > should work 1`] = `"<ArtPlayer src="/xxx.mp4" fullscreen flip playback-rate aspect-ratio setting pip :volume="0.75" width="100%"/><ArtPlayer src="/xxx.m3u8" fullscreen flip playback-rate aspect-ratio setting pip loop autoplay muted :volume="0.75" width="100%"/><ArtPlayer src="/xxx.flv" fullscreen flip playback-rate aspect-ratio setting pip autoplay muted :volume="0.55" width="100%"/><ArtPlayer src="/xxx.mpd" fullscreen flip playback-rate aspect-ratio setting pip auto-min poster="xx.jpg" :volume="0.75" width="100%" height="600px" ratio="16:9"/><ArtPlayer src="/xxx" fullscreen flip playback-rate aspect-ratio setting pip type="mp3" :volume="0.75" width="100%"/>"`;

View File

@ -0,0 +1,46 @@
import MarkdownIt from 'markdown-it'
import { describe, expect, it } from 'vitest'
import { artPlayerPlugin } from '../src/node/embed/video/artPlayer.js'
function createMarkdown() {
return MarkdownIt().use((md) => {
md.block.ruler.before('code', 'import_code', () => false)
md.renderer.rules.import_code = () => ''
}).use(artPlayerPlugin)
}
describe('artPlayerPlugin', () => {
it('should work', () => {
const md = createMarkdown()
const code = `\
@[artPlayer](/xxx.mp4)
@[artPlayer loop autoplay](/xxx.m3u8)
@[artPlayer autoplay muted volume="0.55"](/xxx.flv)
@[artPlayer width="100%" height="600px" ratio="16:9" auto-mini poster="xx.jpg"](/xxx.mpd)
@[artPlayer type="mp3"](/xxx)
`
expect(md.render(code)).toMatchSnapshot()
})
it('should not work', () => {
const md = createMarkdown()
const code = `\
@[artPlayer]()
@[artPlayer]xxx
@[ artPlayer]123456
@[ artPlayer](/xxx.mp4)
@[artPlayer type="xxx"](/xxx.xxx)
`
expect(md.render(code)).toMatchSnapshot()
})
})

View File

@ -37,12 +37,28 @@
"tsup": "tsup --config tsup.config.ts"
},
"peerDependencies": {
"artplayer": "^5.2.0",
"dashjs": "^4.7.4",
"hls.js": "^1.5.18",
"markdown-it": "^14.0.0",
"mpegts.js": "^1.7.3",
"vuepress": "catalog:"
},
"peerDependenciesMeta": {
"artplayer": {
"optional": true
},
"dashjs": {
"optional": true
},
"hls.js": {
"optional": true
},
"markdown-it": {
"optional": true
},
"mpegts.js": {
"optional": true
}
},
"dependencies": {
@ -65,7 +81,11 @@
"vue": "catalog:"
},
"devDependencies": {
"@types/markdown-it": "^14.1.2"
"@types/markdown-it": "^14.1.2",
"artplayer": "^5.2.1",
"dashjs": "^4.7.4",
"hls.js": "^1.5.18",
"mpegts.js": "^1.7.3"
},
"publishConfig": {
"access": "public"

View File

@ -0,0 +1,155 @@
<script setup lang="ts">
import type ArtPlayer from 'artplayer'
import type { Option as ArtPlayerInitOptions } from 'artplayer/types/option.js'
import { isLinkHttp } from '@vuepress/helper/client'
import { useCssVar } from '@vueuse/core'
import { onMounted, onUnmounted, ref, toRefs } from 'vue'
import { usePageLang, withBase } from 'vuepress/client'
import { useSize } from '../composables/size.js'
import { ART_PLAYER_SUPPORTED_VIDEO_TYPES, installed } from '../options.js'
import Loading from './icons/Loading.vue'
type CustomType = Record<string, (this: ArtPlayer, video: HTMLVideoElement, url: string, art: ArtPlayer) => any>
const props = withDefaults(defineProps<{
src: string
type?: string
width?: string
height?: string
ratio?: string
} & Omit<ArtPlayerInitOptions, 'container' | 'url' | 'type'>>(), {
hotkey: true,
mutex: true,
playsInline: true,
})
const options = toRefs(props)
const loaded = ref(false)
const lang = usePageLang()
const brandColor = useCssVar('--vp-c-brand-1')
const { el, width, height, resize } = useSize<HTMLDivElement>(options)
let player: ArtPlayer | null = null
async function createPlayer() {
loaded.value = false
const { default: ArtPlayer } = await import(
/* webpackChunkName: "artplayer" */ 'artplayer'
)
loaded.value = true
const { src, type: _t, width: _w, height: _h, ratio: _r, ...opt } = props
const { customType = {}, ...options } = opt
Object.keys(options).forEach((key) => {
if (typeof options[key] === 'undefined') {
delete options[key]
}
})
const type = props.type ?? src.split('.').pop() ?? ''
const url = isLinkHttp(src) ? src : withBase(src)
if (!ART_PLAYER_SUPPORTED_VIDEO_TYPES.includes(type)) {
console.error(`Unsupported video type: ${type}`)
return
}
player = new ArtPlayer({
container: el.value!,
url,
type,
...{
lang: lang.value.split('-')[0] === 'zh' ? 'zh-cn' : 'en',
volume: 0.75,
useSSR: false,
theme: brandColor.value ?? '#5086a1',
},
...options,
customType: {
...initCustomType(type),
...customType,
},
})
}
function initCustomType(type: string): CustomType {
const customType: CustomType = {}
if ((type === 'mpd' || type === 'dash') && installed.dashjs) {
customType[type] = async function (video, url, art) {
const dashjs = (await import(/* webpackChunkName: "dashjs" */ 'dashjs')).default
if (dashjs.supportsMediaSource()) {
const dashPlayer = dashjs.MediaPlayer().create()
dashPlayer.initialize(video, url, props.autoplay, 0)
art.on('destroy', () => dashPlayer.destroy())
}
}
}
if (type === 'm3u8' || type === 'hls') {
customType[type] = async function (video, url, art) {
if (video.canPlayType('application/x-mpegURL')
|| video.canPlayType('application/vnd.apple.mpegURL')) {
video.src = url
return
}
if (!installed.hlsjs)
return
const Hls = (await import(/* webpackChunkName: "hls.js" */ 'hls.js')).default
if (Hls.isSupported()) {
const hls = new Hls()
hls.attachMedia(video)
hls.on(Hls.Events.MEDIA_ATTACHED, () => hls.loadSource(url))
art.on('destroy', () => hls.destroy())
}
}
}
if ((type === 'ts' || type === 'flv') && installed.mpegtsjs) {
customType[type] = async function (video, url, art) {
const mpegts = (await import(/* webpackChunkName: "mpegts.js" */ 'mpegts.js/dist/mpegts.js')).default
if (mpegts.isSupported()) {
const flv = mpegts.createPlayer({
type: 'flv',
url,
})
flv.attachMediaElement(video)
flv.load()
art.on('destroy', () => flv.destroy())
}
}
}
return customType
}
onMounted(async () => {
await createPlayer()
resize()
})
onUnmounted(() => {
player?.destroy()
player = null
})
</script>
<template>
<div ref="el" class="vp-artplayer" :style="{ width, height }">
<Loading v-if="!loaded" height="100%" />
</div>
</template>
<style>
.vp-artplayer {
margin: 16px 0;
}
@media (min-width: 768px) {
.vp-artplayer {
overflow: hidden;
border-radius: 8px;
box-shadow: var(--vp-shadow-2);
}
}
</style>

View File

@ -1,5 +1,28 @@
import type { MarkdownPowerPluginOptions } from '../shared/index.js'
declare const __MD_POWER_INJECT_OPTIONS__: MarkdownPowerPluginOptions
declare const __MD_POWER_DASHJS_INSTALLED__: boolean
declare const __MD_POWER_HLSJS_INSTALLED__: boolean
declare const __MD_POWER_MPEGTSJS_INSTALLED__: boolean
export const pluginOptions = __MD_POWER_INJECT_OPTIONS__
export const installed = {
dashjs: __MD_POWER_DASHJS_INSTALLED__,
hlsjs: __MD_POWER_HLSJS_INSTALLED__,
mpegtsjs: __MD_POWER_MPEGTSJS_INSTALLED__,
}
export const ART_PLAYER_SUPPORTED_VIDEO_TYPES = ['mp4', 'mp3', 'webm', 'ogg']
if (installed.dashjs) {
ART_PLAYER_SUPPORTED_VIDEO_TYPES.push('mpd', 'dash')
}
if (installed.hlsjs) {
ART_PLAYER_SUPPORTED_VIDEO_TYPES.push('m3u8', 'hls')
}
if (installed.mpegtsjs) {
ART_PLAYER_SUPPORTED_VIDEO_TYPES.push('ts', 'flv')
}

View File

@ -6,6 +6,7 @@ import { codeSandboxPlugin } from './code/codeSandbox.js'
import { jsfiddlePlugin } from './code/jsfiddle.js'
import { replitPlugin } from './code/replit.js'
import { pdfPlugin } from './pdf.js'
import { artPlayerPlugin } from './video/artPlayer.js'
import { bilibiliPlugin } from './video/bilibili.js'
import { youtubePlugin } from './video/youtube.js'
@ -33,6 +34,11 @@ export function embedSyntaxPlugin(md: Markdown, options: MarkdownPowerPluginOpti
md.use(youtubePlugin)
}
if (options.artPlayer) {
// @[artPlayer](url)
md.use(artPlayerPlugin)
}
if (options.codepen) {
// @[codepen](user/slash)
md.use(codepenPlugin)

View File

@ -0,0 +1,97 @@
/**
* @[artPlayer](url)
* @[artPlayer muted autoplay autoMini loop volume=1 poster="xxxx"](url)
*/
import type { PluginWithOptions } from 'markdown-it'
import type { ArtPlayerTokenMeta } from '../../../shared/index.js'
import { isPackageExists } from 'local-pkg'
import { colors } from 'vuepress/utils'
import { parseRect } from '../../utils/parseRect.js'
import { resolveAttrs } from '../../utils/resolveAttrs.js'
import { createEmbedRuleBlock } from '../createEmbedRuleBlock.js'
const installed = {
dashjs: isPackageExists('dashjs'),
hlsjs: isPackageExists('hls.js'),
mpegtsjs: isPackageExists('mpegts.js'),
}
const SUPPORTED_VIDEO_TYPES = ['mp4', 'mp3', 'webm', 'ogg', 'mpd', 'dash', 'm3u8', 'hls', 'ts', 'flv']
export const artPlayerPlugin: PluginWithOptions<never> = (md) => {
createEmbedRuleBlock<ArtPlayerTokenMeta>(md, {
type: 'artPlayer',
name: 'video_artPlayer',
syntaxPattern: /^@\[artPlayer([^\]]*)\]\(([^)]*)\)/,
meta([, info, source]) {
const { attrs } = resolveAttrs<ArtPlayerTokenMeta>(info)
const url = source.trim()
checkSupportType(attrs.type ?? url.split('.').pop())
return {
autoplay: attrs.autoplay ?? false,
muted: attrs.muted ?? attrs.autoplay ?? false,
autoMini: attrs.autoMini ?? false,
loop: attrs.loop ?? false,
volume: typeof attrs.volume !== 'undefined' ? Number(attrs.volume) : 0.75,
poster: attrs.poster,
width: attrs.width ? parseRect(attrs.width) : '100%',
height: attrs.height ? parseRect(attrs.height) : '',
ratio: attrs.ratio ? parseRect(`${attrs.ratio}`) : '',
type: attrs.type,
url,
}
},
content({ autoMini, autoplay, loop, muted, poster, url, type, volume, width, height, ratio }) {
return `<ArtPlayer src="${url}" fullscreen flip playback-rate aspect-ratio setting pip ${
loop ? ' loop' : ''
}${
type ? ` type="${type}"` : ''
}${
autoMini ? ' auto-min' : ''
}${autoplay ? ' autoplay' : ''}${
muted || autoplay ? ' muted' : ''
}${
poster ? ` poster="${poster}"` : ''
} :volume="${volume}" width="${width}"${
height ? ` height="${height}"` : ''
}${
ratio ? ` ratio="${ratio}"` : ''
}/>`
},
})
}
function checkSupportType(type?: string) {
if (!type)
return
/* istanbul ignore if -- @preserve */
if (SUPPORTED_VIDEO_TYPES.includes(type)) {
let name = ''
switch (type.toLowerCase()) {
case 'm3u8':
case 'hls':
name = !installed.hlsjs ? 'hls.js' : ''
break
case 'flv':
case 'ts': {
name = !installed.mpegtsjs ? 'mpegts.js' : ''
break
}
case 'mpd':
case 'dash':
name = !installed.dashjs ? 'dashjs' : ''
break
}
/* istanbul ignore if -- @preserve */
if (name) {
console.warn(`${colors.yellow('[vuepress-plugin-md-power] artPlayer: ')} ${colors.cyan(name)} is not installed, please install it via npm or yarn or pnpm`)
}
}
else {
/* istanbul ignore next -- @preserve */
console.warn(`${colors.yellow('[vuepress-plugin-md-power] artPlayer: ')} unsupported video type: ${colors.cyan(type)}`)
}
}

View File

@ -1,6 +1,7 @@
import type { Plugin } from 'vuepress/core'
import type { MarkdownPowerPluginOptions } from '../shared/index.js'
import { addViteOptimizeDepsInclude } from '@vuepress/helper'
import { isPackageExists } from 'local-pkg'
import { containerPlugin } from './container/index.js'
import { embedSyntaxPlugin } from './embed/index.js'
import { docsTitlePlugin } from './enhance/docsTitle.js'
@ -18,6 +19,9 @@ export function markdownPowerPlugin(
define: {
__MD_POWER_INJECT_OPTIONS__: options,
__MD_POWER_DASHJS_INSTALLED__: isPackageExists('dashjs'),
__MD_POWER_HLSJS_INSTALLED__: isPackageExists('hls.js'),
__MD_POWER_MPEGTSJS_INSTALLED__: isPackageExists('mpegts.js'),
},
extendsBundlerOptions(bundlerOptions, app) {
@ -28,6 +32,13 @@ export function markdownPowerPlugin(
['shiki/core', 'shiki/wasm', 'shiki/engine/oniguruma'],
)
}
if (options.artPlayer) {
addViteOptimizeDepsInclude(
bundlerOptions,
app,
['artplayer', 'dashjs', 'hls.js', 'mpegts.js'],
)
}
},
extendsMarkdown: async (md, app) => {

View File

@ -65,6 +65,11 @@ export async function prepareConfigFile(app: App, options: MarkdownPowerPluginOp
enhances.add(`app.component('FileTreeItem', FileTreeItem)`)
}
if (options.artPlayer) {
imports.add(`import ArtPlayer from '${CLIENT_FOLDER}components/ArtPlayer.vue'`)
enhances.add(`app.component('ArtPlayer', ArtPlayer)`)
}
return app.writeTemp(
'md-power/config.js',
`\

View File

@ -63,6 +63,13 @@ export interface MarkdownPowerPluginOptions {
*/
youtube?: boolean
/**
* artPlayer
*
* `@[artPlayer](url)`
*/
artPlayer?: boolean
// code embed
/**
* codepen

View File

@ -23,3 +23,14 @@ export interface YoutubeTokenMeta extends SizeOptions {
start?: string | number
end?: string | number
}
export interface ArtPlayerTokenMeta extends SizeOptions {
muted?: boolean
autoplay?: boolean
autoMini?: boolean
loop?: boolean
volume?: number // 0-1
poster?: string
url: string
type?: string
}

163
pnpm-lock.yaml generated
View File

@ -325,6 +325,18 @@ importers:
'@types/markdown-it':
specifier: ^14.1.2
version: 14.1.2
artplayer:
specifier: ^5.2.1
version: 5.2.1
dashjs:
specifier: ^4.7.4
version: 4.7.4
hls.js:
specifier: ^1.5.18
version: 1.5.18
mpegts.js:
specifier: ^1.7.3
version: 1.7.3
plugins/plugin-search:
dependencies:
@ -2610,6 +2622,9 @@ packages:
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
engines: {node: '>=8'}
artplayer@5.2.1:
resolution: {integrity: sha512-xhNE9zSLT2z4O2CJwcMSWADsS3Cvq0PwNanvMzRsqgfUhsMOpum8HlMtIUPOTyMGjIhKV8sl109HFUalQK6WJQ==}
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
@ -2654,6 +2669,15 @@ packages:
resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==}
engines: {node: '>= 0.8'}
bcp-47-match@1.0.3:
resolution: {integrity: sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w==}
bcp-47-normalize@1.1.1:
resolution: {integrity: sha512-jWZ1Jdu3cs0EZdfCkS0UE9Gg01PtxnChjEBySeB+Zo6nkqtFfnvtoQQgP1qU1Oo4qgJgxhTI6Sf9y/pZIhPs0A==}
bcp-47@1.0.8:
resolution: {integrity: sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==}
bcrypt-ts@5.0.3:
resolution: {integrity: sha512-2FcgD12xPbwCoe5i9/HK0jJ1xA1m+QfC1e6htG9Bl/hNOnLyaFmQSlqLKcfe3QdnoMPKpKEGFCbESBTg+SJNOw==}
engines: {node: '>=18'}
@ -2843,6 +2867,9 @@ packages:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
codem-isoboxer@0.3.9:
resolution: {integrity: sha512-4XOTqEzBWrGOZaMd+sTED2hLpzfBbiQCf1W6OBGkIHqk1D8uwy8WFLazVbdQwfDpQ+vf39lqTGPa9IhWW0roTA==}
color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
@ -2908,7 +2935,7 @@ packages:
resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==}
concat-map@0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
confbox@0.1.7:
resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==}
@ -3263,6 +3290,9 @@ packages:
resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==}
engines: {node: '>=12'}
dashjs@4.7.4:
resolution: {integrity: sha512-+hldo25QPP3H/NOwqUrvt4uKdMse60/Gsz9AUAnoYfhga8qHWq4nWiojUosOiigbigkDTCAn9ORcvUaKCvmfCA==}
dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
@ -3446,6 +3476,9 @@ packages:
es-module-lexer@1.5.4:
resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==}
es6-promise@4.2.8:
resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==}
esbuild@0.24.0:
resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==}
engines: {node: '>=18'}
@ -3667,7 +3700,7 @@ packages:
engines: {node: '>=0.10.0'}
eve-raphael@0.5.0:
resolution: {integrity: sha1-F8dUt5K+7z+maE15z1pHxjxM2jA=}
resolution: {integrity: sha512-jrxnPsCGqng1UZuEp9DecX/AuSyAszATSjf4oEcRxvfxa1Oux4KkIPKBAAWWnpdwfARtr+Q0o9aPYWjsROD7ug==}
eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
@ -3699,6 +3732,9 @@ packages:
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
engines: {node: '>=4'}
fast-deep-equal@2.0.1:
resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@ -3910,7 +3946,7 @@ packages:
engines: {node: '>=10.13.0'}
glob2base@0.0.12:
resolution: {integrity: sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=}
resolution: {integrity: sha512-ZyqlgowMbfj2NPjxaZZ/EtsXlOch28FRXgMd64vqZWk1bT9+wvSRLYD1om9M7QfQru51zJPAT17qXm4/zd+9QA==}
engines: {node: '>= 0.10'}
glob@10.4.5:
@ -4030,6 +4066,9 @@ packages:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
hls.js@1.5.18:
resolution: {integrity: sha512-znxR+2jecWluu/0KOBqUcvVyAB5tLff10vjMGrpAlz1eFY+ZhF1bY3r82V+Bk7WJdk03iTjtja9KFFz5BrqjSA==}
homedir-polyfill@1.0.3:
resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==}
engines: {node: '>=0.10.0'}
@ -4048,6 +4087,9 @@ packages:
resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==}
engines: {node: '>=12'}
html-entities@1.4.0:
resolution: {integrity: sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==}
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
@ -4114,6 +4156,9 @@ packages:
engines: {node: '>=16.x'}
hasBin: true
immediate@3.0.6:
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
immutable@5.0.2:
resolution: {integrity: sha512-1NU7hWZDkV7hJ4PJ9dur9gTNQ4ePNPN4k9/0YhwjzykTi/+3Q5pF93YU5QoVj8BuOnhLgaY8gs0U2pj4kSYVcw==}
@ -4124,6 +4169,9 @@ packages:
import-meta-resolve@4.1.0:
resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==}
imsc@1.1.5:
resolution: {integrity: sha512-V8je+CGkcvGhgl2C1GlhqFFiUOIEdwXbXLiu1Fcubvvbo+g9inauqT3l0pNYXGoLPBj3jxtZz9t+wCopMkwadQ==}
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
@ -4161,6 +4209,12 @@ packages:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
is-alphabetical@1.0.4:
resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==}
is-alphanumerical@1.0.4:
resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==}
is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
@ -4176,6 +4230,9 @@ packages:
resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
engines: {node: '>= 0.4'}
is-decimal@1.0.4:
resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==}
is-extendable@0.1.1:
resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
engines: {node: '>=0.10.0'}
@ -4415,6 +4472,9 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
lie@3.1.1:
resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==}
lilconfig@3.1.2:
resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==}
engines: {node: '>=14'}
@ -4455,6 +4515,9 @@ packages:
resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==}
engines: {node: '>=14'}
localforage@1.10.0:
resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
@ -4831,6 +4894,9 @@ packages:
mlly@1.7.3:
resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==}
mpegts.js@1.7.3:
resolution: {integrity: sha512-kqZ1C1IsbAQN72cK8vMrzKeM7hwrwSBbFAwVAc7PPweOeoZxCANrc7fAVDKMfYUzxdNkMTnec9tVmlxmKZB0TQ==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@ -4939,6 +5005,9 @@ packages:
resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
hasBin: true
option-validator@2.0.6:
resolution: {integrity: sha512-tmZDan2LRIRQyhUGvkff68/O0R8UmF+Btmiiz0SmSw2ng3CfPZB9wJlIjHpe/MKUZqyIZkVIXCrwr1tIN+0Dzg==}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@ -5672,6 +5741,9 @@ packages:
engines: {node: '>=14.0.0'}
hasBin: true
sax@1.2.1:
resolution: {integrity: sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==}
sax@1.4.1:
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
@ -6195,6 +6267,10 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
ua-parser-js@1.0.39:
resolution: {integrity: sha512-k24RCVWlEcjkdOxYmVJgeD/0a1TiSpqLg+ZalVGV9lsnr4yqu0w7tX/x2xX6G4zpkgQnRf89lxuZ1wsbjXM8lw==}
hasBin: true
uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
@ -6505,6 +6581,9 @@ packages:
webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
webworkify-webpack@2.1.5:
resolution: {integrity: sha512-2akF8FIyUvbiBBdD+RoHpoTbHMQF2HwjcxfDvgztAX5YwbZNyrtfUMgvfgFVsgDhDPVTlkbb5vyasqDHfIDPQw==}
whatwg-encoding@2.0.0:
resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
engines: {node: '>=12'}
@ -8914,6 +8993,10 @@ snapshots:
array-union@2.1.0: {}
artplayer@5.2.1:
dependencies:
option-validator: 2.0.6
assertion-error@2.0.1: {}
astral-regex@2.0.0: {}
@ -8956,6 +9039,19 @@ snapshots:
dependencies:
safe-buffer: 5.1.2
bcp-47-match@1.0.3: {}
bcp-47-normalize@1.1.1:
dependencies:
bcp-47: 1.0.8
bcp-47-match: 1.0.3
bcp-47@1.0.8:
dependencies:
is-alphabetical: 1.0.4
is-alphanumerical: 1.0.4
is-decimal: 1.0.4
bcrypt-ts@5.0.3: {}
binary-extensions@2.3.0: {}
@ -9180,6 +9276,8 @@ snapshots:
clone@1.0.4: {}
codem-isoboxer@0.3.9: {}
color-convert@1.9.3:
dependencies:
color-name: 1.1.3
@ -9658,6 +9756,19 @@ snapshots:
dargs@8.1.0: {}
dashjs@4.7.4:
dependencies:
bcp-47-match: 1.0.3
bcp-47-normalize: 1.1.1
codem-isoboxer: 0.3.9
es6-promise: 4.2.8
fast-deep-equal: 2.0.1
html-entities: 1.4.0
imsc: 1.1.5
localforage: 1.10.0
path-browserify: 1.0.1
ua-parser-js: 1.0.39
dayjs@1.11.13: {}
de-indent@1.0.2: {}
@ -9804,6 +9915,8 @@ snapshots:
es-module-lexer@1.5.4: {}
es6-promise@4.2.8: {}
esbuild@0.24.0:
optionalDependencies:
'@esbuild/aix-ppc64': 0.24.0
@ -10181,6 +10294,8 @@ snapshots:
iconv-lite: 0.4.24
tmp: 0.0.33
fast-deep-equal@2.0.1: {}
fast-deep-equal@3.1.3: {}
fast-glob@3.3.2:
@ -10553,6 +10668,8 @@ snapshots:
he@1.2.0: {}
hls.js@1.5.18: {}
homedir-polyfill@1.0.3:
dependencies:
parse-passwd: 1.0.0
@ -10569,6 +10686,8 @@ snapshots:
dependencies:
whatwg-encoding: 2.0.0
html-entities@1.4.0: {}
html-escaper@2.0.2: {}
html-tags@3.3.1: {}
@ -10642,6 +10761,8 @@ snapshots:
dependencies:
queue: 6.0.2
immediate@3.0.6: {}
immutable@5.0.2: {}
import-fresh@3.3.0:
@ -10651,6 +10772,10 @@ snapshots:
import-meta-resolve@4.1.0: {}
imsc@1.1.5:
dependencies:
sax: 1.2.1
imurmurhash@0.1.4: {}
indent-string@4.0.0: {}
@ -10690,6 +10815,13 @@ snapshots:
internmap@2.0.3: {}
is-alphabetical@1.0.4: {}
is-alphanumerical@1.0.4:
dependencies:
is-alphabetical: 1.0.4
is-decimal: 1.0.4
is-arrayish@0.2.1: {}
is-binary-path@2.1.0:
@ -10704,6 +10836,8 @@ snapshots:
dependencies:
hasown: 2.0.2
is-decimal@1.0.4: {}
is-extendable@0.1.1: {}
is-extglob@2.1.1: {}
@ -10902,6 +11036,10 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
lie@3.1.1:
dependencies:
immediate: 3.0.6
lilconfig@3.1.2: {}
lilconfig@3.1.3: {}
@ -10959,6 +11097,10 @@ snapshots:
mlly: 1.7.3
pkg-types: 1.2.1
localforage@1.10.0:
dependencies:
lie: 3.1.1
locate-path@5.0.0:
dependencies:
p-locate: 4.1.0
@ -11508,6 +11650,11 @@ snapshots:
pkg-types: 1.2.1
ufo: 1.5.4
mpegts.js@1.7.3:
dependencies:
es6-promise: 4.2.8
webworkify-webpack: 2.1.5
ms@2.1.3: {}
muggle-string@0.4.1: {}
@ -11608,6 +11755,10 @@ snapshots:
opener@1.5.2: {}
option-validator@2.0.6:
dependencies:
kind-of: 6.0.3
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@ -12297,6 +12448,8 @@ snapshots:
optionalDependencies:
'@parcel/watcher': 2.4.1
sax@1.2.1: {}
sax@1.4.1: {}
scslre@0.3.0:
@ -12821,6 +12974,8 @@ snapshots:
typescript@5.7.2: {}
ua-parser-js@1.0.39: {}
uc.micro@2.1.0: {}
ufo@1.5.4: {}
@ -13125,6 +13280,8 @@ snapshots:
webidl-conversions@4.0.2: {}
webworkify-webpack@2.1.5: {}
whatwg-encoding@2.0.0:
dependencies:
iconv-lite: 0.6.3