From 0a9aadf543d924ad75b6c9d4d192336a921d49c0 Mon Sep 17 00:00:00 2001 From: Harvey Zhao Date: Wed, 8 Oct 2025 09:55:44 +0800 Subject: [PATCH] feat: add link to LLMs resource in README Implement code changes to enhance functionality and improve performance chore: add initial empty files for artplayer.d.ts, llm.txt, and build-llm.js Refactor Artplayer examples for improved readability and consistency - Updated mobile.js to enhance code formatting and structure. - Refined mpegts.js for better readability and consistency in function usage. - Improved multiple.subtitles.js with clearer structure and modern syntax. - Enhanced setting.test.js for better organization and clarity in settings management. - Streamlined thumbnail.js for consistent formatting. - Updated vast.js for improved readability and consistency in plugin usage. - Refined vtt.thumbnail.js for better code organization. - Enhanced webtorrent.js for improved readability and consistency in async handling. - Updated TypeScript definitions for various plugins to maintain consistency in export statements. - Adjusted package.json linting script to include TypeScript files in the docs/assets/ts directory. - Removed outdated types.test.js file to clean up the test suite. refactor: remove outdated documentation files and update translation script - Deleted i18n.md and option.md documentation files as they are no longer needed. - Updated trans-docs.js to streamline the translation process and improve error handling. - Adjusted plugin template package.json for consistency. - Changed console.log to console.warn in various test files for better logging practices. feat: add danmaku plugin documentation and i18n support - Introduced comprehensive documentation for the Danmaku library, including installation, usage examples, and configuration options. - Added language settings documentation, highlighting the need for manual language imports starting from version 5.1.0. - Updated basic options documentation with detailed descriptions and examples for player configuration options. Refactor documentation for clarity and consistency; update translation script to handle large Markdown chunks refactor: improve documentation clarity and consistency across various components - Updated property descriptions in layers.md and setting.md for better readability. - Enhanced language and structure in index.md, danmuku.md, i18n.md, and option.md to ensure uniformity. - Corrected minor grammatical errors and improved phrasing throughout the documentation. - Ensured consistent formatting in tables and code snippets for better user experience. feat: add build script for generating llm.txt and update translation source directories Refactor build-llm.js to improve concurrency and enhance prompt clarity - Introduced a MAX_CONCURRENT_REQUESTS constant to manage concurrent API requests. - Added a runConcurrent function to handle task execution with a limit on concurrent requests. - Updated the askDeepSeek function to include more informative logging. - Enhanced prompts for summarizing documentation, TypeScript definitions, and JavaScript examples to improve clarity and maintain formatting. - Modified the main function to fetch files concurrently and streamline the summarization process. Increase text chunk size from 5000 to 8000 in summarizeSection function for improved processing of larger files. fix: update lint script to include .js files in the scripts directory --- README.md | 2 + docs/assets/example/ads.js | 68 +- docs/assets/example/ambilight.js | 26 +- docs/assets/example/asr.js | 101 +- docs/assets/example/auto.thumbnail.js | 18 +- docs/assets/example/canvas.js | 54 +- docs/assets/example/chapter.js | 50 +- docs/assets/example/chromecast.js | 24 +- docs/assets/example/danmuku.js | 96 +- docs/assets/example/danmuku.mask.js | 32 +- docs/assets/example/dash.control.js | 88 +- docs/assets/example/dash.js | 40 +- docs/assets/example/document.pip.js | 2 +- docs/assets/example/flv.js | 42 +- docs/assets/example/hls.control.js | 95 +- docs/assets/example/hls.js | 47 +- docs/assets/example/iframe.js | 71 +- docs/assets/example/index.js | 370 +- docs/assets/example/libass.js | 44 +- docs/assets/example/mobile.js | 182 +- docs/assets/example/mpegts.js | 50 +- docs/assets/example/multiple.subtitles.js | 180 +- docs/assets/example/setting.test.js | 262 +- docs/assets/example/thumbnail.js | 22 +- docs/assets/example/vast.js | 28 +- docs/assets/example/vtt.thumbnail.js | 18 +- docs/assets/example/webtorrent.js | 52 +- docs/assets/ts/artplayer-plugin-ads.d.ts | 4 +- .../assets/ts/artplayer-plugin-ambilight.d.ts | 4 +- docs/assets/ts/artplayer-plugin-asr.d.ts | 4 +- .../ts/artplayer-plugin-auto-thumbnail.d.ts | 4 +- docs/assets/ts/artplayer-plugin-chapter.d.ts | 4 +- .../ts/artplayer-plugin-chromecast.d.ts | 4 +- .../ts/artplayer-plugin-danmuku-mask.d.ts | 4 +- docs/assets/ts/artplayer-plugin-danmuku.d.ts | 4 +- .../ts/artplayer-plugin-dash-control.d.ts | 4 +- .../ts/artplayer-plugin-document-pip.d.ts | 4 +- .../ts/artplayer-plugin-hls-control.d.ts | 4 +- docs/assets/ts/artplayer-plugin-iframe.d.ts | 4 +- docs/assets/ts/artplayer-plugin-libass.d.ts | 4 +- .../artplayer-plugin-multiple-subtitles.d.ts | 4 +- docs/assets/ts/artplayer-plugin-vast.d.ts | 4 +- .../ts/artplayer-plugin-vtt-thumbnail.d.ts | 4 +- docs/assets/ts/artplayer.d.ts | 36 +- docs/index.html | 1 + docs/llms.txt | 8265 +++++++++++++++++ docs/uncompiled/artplayer/index.js | 2 +- package.json | 5 +- .../.vitepress/cache/deps/@theme_index.js | 60 +- .../docs/.vitepress/cache/deps/_metadata.json | 16 +- .../docs/en/advanced/built-in.md | 174 +- .../docs/en/advanced/class.md | 27 +- .../docs/en/advanced/event.md | 110 +- .../docs/en/advanced/global.md | 96 +- .../docs/en/advanced/plugin.md | 15 +- .../docs/en/advanced/property.md | 183 +- .../docs/en/component/contextmenu.md | 32 +- .../docs/en/component/controls.md | 38 +- .../docs/en/component/layers.md | 52 +- .../docs/en/component/setting.md | 81 +- packages/artplayer-vitepress/docs/en/index.md | 64 +- .../docs/en/plugin/danmuku.md | 555 ++ .../artplayer-vitepress/docs/en/start/i18n.md | 18 +- .../docs/en/start/option.md | 324 +- scripts/build-llm.js | 158 + scripts/plugin/template/package.json | 2 +- scripts/trans-docs.js | 267 +- test/esm.test.js | 4 +- test/i18n.test.js | 4 +- test/ssr.test.js | 2 +- test/types.test.js | 77 - 71 files changed, 10851 insertions(+), 1945 deletions(-) create mode 100644 docs/llms.txt create mode 100644 packages/artplayer-vitepress/docs/en/plugin/danmuku.md create mode 100644 scripts/build-llm.js delete mode 100644 test/types.test.js diff --git a/README.md b/README.md index fa90ee146..17f3f18c9 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ ・ Changelog ・ + 🤖LLMs + ・

diff --git a/docs/assets/example/ads.js b/docs/assets/example/ads.js index e1d2a983a..75897f065 100644 --- a/docs/assets/example/ads.js +++ b/docs/assets/example/ads.js @@ -1,48 +1,48 @@ // npm i artplayer-plugin-ads // import artplayerPluginAds from 'artplayer-plugin-ads'; -var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', - autoSize: true, - fullscreen: true, - fullscreenWeb: true, - plugins: [ - artplayerPluginAds({ - // html广告,假如是视频广告则忽略该值 - html: '', +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoSize: true, + fullscreen: true, + fullscreenWeb: true, + plugins: [ + artplayerPluginAds({ + // html广告,假如是视频广告则忽略该值 + html: '', - // 视频广告的地址 - video: '/assets/sample/test1.mp4', + // 视频广告的地址 + video: '/assets/sample/test1.mp4', - // 广告跳转网址,为空则不跳转 - url: 'http://artplayer.org', + // 广告跳转网址,为空则不跳转 + url: 'http://artplayer.org', - // 必须观看的时长,期间不能被跳过,单位为秒 - // 当该值大于或等于totalDuration时,不能提前关闭广告 - // 当该值等于或小于0时,则随时都可以关闭广告 - playDuration: 5, + // 必须观看的时长,期间不能被跳过,单位为秒 + // 当该值大于或等于totalDuration时,不能提前关闭广告 + // 当该值等于或小于0时,则随时都可以关闭广告 + playDuration: 5, - // 广告总时长,单位为秒 - totalDuration: 10, + // 广告总时长,单位为秒 + totalDuration: 10, - // 多语言支持 - i18n: { - close: '关闭广告', - countdown: '%s秒', - detail: '查看详情', - canBeClosed: '%s秒后可关闭广告', - }, - }), - ], -}); + // 多语言支持 + i18n: { + close: '关闭广告', + countdown: '%s秒', + detail: '查看详情', + canBeClosed: '%s秒后可关闭广告', + }, + }), + ], +}) // 广告被点击 art.on('artplayerPluginAds:click', (ads) => { - console.info('广告被点击', ads); -}); + console.info('广告被点击', ads) +}) // 广告被跳过 art.on('artplayerPluginAds:skip', (ads) => { - console.info('广告被跳过', ads); -}); + console.info('广告被跳过', ads) +}) diff --git a/docs/assets/example/ambilight.js b/docs/assets/example/ambilight.js index 4d97c14f1..a1a3e6c63 100644 --- a/docs/assets/example/ambilight.js +++ b/docs/assets/example/ambilight.js @@ -1,16 +1,16 @@ // npm i artplayer-plugin-ambilight // import artplayerPluginAmbilight from 'artplayer-plugin-ambilight'; -var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', - autoSize: true, - plugins: [ - artplayerPluginAmbilight({ - blur: '50px', - opacity: 1, - frequency: 10, - duration: 0.3, - }), - ], -}); \ No newline at end of file +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoSize: true, + plugins: [ + artplayerPluginAmbilight({ + blur: '50px', + opacity: 1, + frequency: 10, + duration: 0.3, + }), + ], +}) diff --git a/docs/assets/example/asr.js b/docs/assets/example/asr.js index 513d8a4c6..d7bc60b0b 100644 --- a/docs/assets/example/asr.js +++ b/docs/assets/example/asr.js @@ -1,58 +1,61 @@ -var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/steve-jobs.mp4', - autoSize: true, - fullscreen: true, - fullscreenWeb: true, - moreVideoAttr: { - // crossOrigin: 'anonymous', - }, - plugins: [ - artplayerPluginAsr({ - length: 2, - interval: 40, - sampleRate: 16000, - autoHideTimeout: 10000, - // Use your AI tool to convert pcm into subtitles - onAudioChunk: ({ pcm }) => startAsr(pcm), - }), - ] -}); +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/steve-jobs.mp4', + autoSize: true, + fullscreen: true, + fullscreenWeb: true, + moreVideoAttr: { + // crossOrigin: 'anonymous', + }, + plugins: [ + artplayerPluginAsr({ + length: 2, + interval: 40, + sampleRate: 16000, + autoHideTimeout: 10000, + // Use your AI tool to convert pcm into subtitles + onAudioChunk: ({ pcm }) => startAsr(pcm), + }), + ], +}) -var ws = null; -var loading = false; +let ws = null +let loading = false function stopAsr() { - try { - ws.send(JSON.stringify({ type: 'end' })); - ws.close(); - } catch {} - ws = null; - loading = false; + try { + ws.send(JSON.stringify({ type: 'end' })) + ws.close() + } + catch {} + ws = null + loading = false } async function startAsr(buffer) { - if (loading) return; - if (!ws) { - loading = true; - const api = 'https://api.aimu.app/asr/tencent?engine_model_type=16k_en'; - const { url } = await (await fetch(api)).json(); - ws = new WebSocket(url); - ws.binaryType = 'arraybuffer'; - ws.onmessage = (event) => { - const { code, result, message } = JSON.parse(event.data); - if (code === 0) { - art.plugins.artplayerPluginAsr.append(result?.voice_text_str); - } else { - console.error(code, message); - stopAsr(); - } - }; - loading = false; - } - if (ws?.readyState === WebSocket.OPEN) { - ws.send(buffer); + if (loading) + return + if (!ws) { + loading = true + const api = 'https://api.aimu.app/asr/tencent?engine_model_type=16k_en' + const { url } = await (await fetch(api)).json() + ws = new WebSocket(url) + ws.binaryType = 'arraybuffer' + ws.onmessage = (event) => { + const { code, result, message } = JSON.parse(event.data) + if (code === 0) { + art.plugins.artplayerPluginAsr.append(result?.voice_text_str) + } + else { + console.error(code, message) + stopAsr() + } } + loading = false + } + if (ws?.readyState === WebSocket.OPEN) { + ws.send(buffer) + } } -art.on('destroy', stopAsr); +art.on('destroy', stopAsr) diff --git a/docs/assets/example/auto.thumbnail.js b/docs/assets/example/auto.thumbnail.js index e04385fd5..bffe1c7e9 100644 --- a/docs/assets/example/auto.thumbnail.js +++ b/docs/assets/example/auto.thumbnail.js @@ -1,12 +1,12 @@ // npm i artplayer-plugin-auto-thumbnail // import artplayerPluginAutoThumbnail from 'artplayer-plugin-auto-thumbnail'; -var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', - plugins: [ - artplayerPluginAutoThumbnail({ - // - }), - ], -}); \ No newline at end of file +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginAutoThumbnail({ + // + }), + ], +}) diff --git a/docs/assets/example/canvas.js b/docs/assets/example/canvas.js index 2887ed712..dd32f6cf7 100644 --- a/docs/assets/example/canvas.js +++ b/docs/assets/example/canvas.js @@ -1,30 +1,30 @@ // npm i artplayer-proxy-canvas // import artplayerProxyCanvas from 'artplayer-proxy-canvas'; -var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', - poster: '/assets/sample/poster.jpg', - volume: 0.5, - autoplay: false, - autoSize: false, - screenshot: true, - setting: true, - loop: true, - flip: true, - pip: true, - playbackRate: true, - aspectRatio: true, - fullscreen: true, - fullscreenWeb: true, - miniProgressBar: true, - autoPlayback: true, - autoOrientation: true, - thumbnails: { - url: '/assets/sample/thumbnails.png', - number: 60, - column: 10, - scale: 0.85, - }, - proxy: artplayerProxyCanvas(), -}); \ No newline at end of file +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + poster: '/assets/sample/poster.jpg', + volume: 0.5, + autoplay: false, + autoSize: false, + screenshot: true, + setting: true, + loop: true, + flip: true, + pip: true, + playbackRate: true, + aspectRatio: true, + fullscreen: true, + fullscreenWeb: true, + miniProgressBar: true, + autoPlayback: true, + autoOrientation: true, + thumbnails: { + url: '/assets/sample/thumbnails.png', + number: 60, + column: 10, + scale: 0.85, + }, + proxy: artplayerProxyCanvas(), +}) diff --git a/docs/assets/example/chapter.js b/docs/assets/example/chapter.js index 47971ed73..4dba39272 100644 --- a/docs/assets/example/chapter.js +++ b/docs/assets/example/chapter.js @@ -1,28 +1,28 @@ // npm i artplayer-plugin-chapter // import artplayerPluginChapter from 'artplayer-plugin-chapter'; -var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', - autoSize: true, - fullscreen: true, - fullscreenWeb: true, - miniProgressBar: true, - autoOrientation: true, - thumbnails: { - url: '/assets/sample/thumbnails.png', - number: 60, - column: 10, - }, - plugins: [ - artplayerPluginChapter({ - chapters: [ - { start: 0, end: 18, title: 'One more chance' }, - { start: 18, end: 36, title: '谁でもいいはずなのに' }, - { start: 36, end: 54, title: '夏の想い出がまわる' }, - { start: 54, end: 72, title: 'こんなとこにあるはずもないのに' }, - { start: 72, end: Infinity, title: '终わり' }, - ] - }), - ], -}); \ No newline at end of file +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoSize: true, + fullscreen: true, + fullscreenWeb: true, + miniProgressBar: true, + autoOrientation: true, + thumbnails: { + url: '/assets/sample/thumbnails.png', + number: 60, + column: 10, + }, + plugins: [ + artplayerPluginChapter({ + chapters: [ + { start: 0, end: 18, title: 'One more chance' }, + { start: 18, end: 36, title: '谁でもいいはずなのに' }, + { start: 36, end: 54, title: '夏の想い出がまわる' }, + { start: 54, end: 72, title: 'こんなとこにあるはずもないのに' }, + { start: 72, end: Infinity, title: '终わり' }, + ], + }), + ], +}) diff --git a/docs/assets/example/chromecast.js b/docs/assets/example/chromecast.js index a855a5658..416a9cf2c 100644 --- a/docs/assets/example/chromecast.js +++ b/docs/assets/example/chromecast.js @@ -1,15 +1,15 @@ // npm i artplayer-plugin-chromecast // import artplayerPluginChromecast from 'artplayer-plugin-chromecast'; -var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', - fullscreen: true, - fullscreenWeb: true, - plugins: [ - artplayerPluginChromecast({ - // sdk: '', // The URL of the Cast SDK - // mimeType: '', // The MIME type of the media - }), - ], -}); \ No newline at end of file +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fullscreen: true, + fullscreenWeb: true, + plugins: [ + artplayerPluginChromecast({ + // sdk: '', // The URL of the Cast SDK + // mimeType: '', // The MIME type of the media + }), + ], +}) diff --git a/docs/assets/example/danmuku.js b/docs/assets/example/danmuku.js index 821384986..934816cb1 100644 --- a/docs/assets/example/danmuku.js +++ b/docs/assets/example/danmuku.js @@ -2,53 +2,53 @@ // import artplayerPluginDanmuku from 'artplayer-plugin-danmuku'; // 使用文档 https://artplayer.org/document/plugin/danmuku.html -var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', - autoSize: true, - fullscreen: true, - fullscreenWeb: true, - autoOrientation: true, - plugins: [ - artplayerPluginDanmuku({ - danmuku: '/assets/sample/danmuku.xml', +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoSize: true, + fullscreen: true, + fullscreenWeb: true, + autoOrientation: true, + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', - // 以下为非必填 - speed: 5, // 弹幕持续时间,范围在[1 ~ 10] - margin: [10, '25%'], // 弹幕上下边距,支持像素数字和百分比 - opacity: 1, // 弹幕透明度,范围在[0 ~ 1] - color: '#FFFFFF', // 默认弹幕颜色,可以被单独弹幕项覆盖 - mode: 0, // 默认弹幕模式: 0: 滚动,1: 顶部,2: 底部 - modes: [0, 1, 2], // 弹幕可见的模式 - fontSize: 25, // 弹幕字体大小,支持像素数字和百分比 - antiOverlap: true, // 弹幕是否防重叠 - synchronousPlayback: false, // 是否同步播放速度 - mount: undefined, // 弹幕发射器挂载点, 默认为播放器控制栏中部 - heatmap: true, // 是否开启热力图 - width: 512, // 当播放器宽度小于此值时,弹幕发射器置于播放器底部 - points: [], // 热力图数据 - filter: (danmu) => danmu.text.length <= 100, // 弹幕载入前的过滤器 - beforeVisible: () => true, // 弹幕显示前的过滤器,返回 true 则可以发送 - visible: true, // 弹幕层是否可见 - emitter: true, // 是否开启弹幕发射器 - maxLength: 200, // 弹幕输入框最大长度, 范围在[1 ~ 1000] - lockTime: 5, // 输入框锁定时间,范围在[1 ~ 60] - theme: 'dark', // 弹幕主题,支持 dark 和 light,只在自定义挂载时生效 - OPACITY: {}, // 不透明度配置项 - FONT_SIZE: {}, // 弹幕字号配置项 - MARGIN: {}, // 显示区域配置项 - SPEED: {}, // 弹幕速度配置项 - COLOR: [], // 颜色列表配置项 + // 以下为非必填 + speed: 5, // 弹幕持续时间,范围在[1 ~ 10] + margin: [10, '25%'], // 弹幕上下边距,支持像素数字和百分比 + opacity: 1, // 弹幕透明度,范围在[0 ~ 1] + color: '#FFFFFF', // 默认弹幕颜色,可以被单独弹幕项覆盖 + mode: 0, // 默认弹幕模式: 0: 滚动,1: 顶部,2: 底部 + modes: [0, 1, 2], // 弹幕可见的模式 + fontSize: 25, // 弹幕字体大小,支持像素数字和百分比 + antiOverlap: true, // 弹幕是否防重叠 + synchronousPlayback: false, // 是否同步播放速度 + mount: undefined, // 弹幕发射器挂载点, 默认为播放器控制栏中部 + heatmap: true, // 是否开启热力图 + width: 512, // 当播放器宽度小于此值时,弹幕发射器置于播放器底部 + points: [], // 热力图数据 + filter: danmu => danmu.text.length <= 100, // 弹幕载入前的过滤器 + beforeVisible: () => true, // 弹幕显示前的过滤器,返回 true 则可以发送 + visible: true, // 弹幕层是否可见 + emitter: true, // 是否开启弹幕发射器 + maxLength: 200, // 弹幕输入框最大长度, 范围在[1 ~ 1000] + lockTime: 5, // 输入框锁定时间,范围在[1 ~ 60] + theme: 'dark', // 弹幕主题,支持 dark 和 light,只在自定义挂载时生效 + OPACITY: {}, // 不透明度配置项 + FONT_SIZE: {}, // 弹幕字号配置项 + MARGIN: {}, // 显示区域配置项 + SPEED: {}, // 弹幕速度配置项 + COLOR: [], // 颜色列表配置项 - // 手动发送弹幕前的过滤器,返回 true 则可以发送,可以做存库处理 - beforeEmit(danmu) { - return new Promise((resolve) => { - console.log(danmu); - setTimeout(() => { - resolve(true); - }, 1000); - }); - }, - }), - ], -}); \ No newline at end of file + // 手动发送弹幕前的过滤器,返回 true 则可以发送,可以做存库处理 + beforeEmit(danmu) { + return new Promise((resolve) => { + console.log(danmu) + setTimeout(() => { + resolve(true) + }, 1000) + }) + }, + }), + ], +}) diff --git a/docs/assets/example/danmuku.mask.js b/docs/assets/example/danmuku.mask.js index 20114a080..224ad5036 100644 --- a/docs/assets/example/danmuku.mask.js +++ b/docs/assets/example/danmuku.mask.js @@ -4,19 +4,19 @@ // npm i @mediapipe/selfie_segmentation // 把 node_modules/@mediapipe/selfie_segmentation 目录复制到你的项目下 -var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', - autoSize: true, - fullscreen: true, - fullscreenWeb: true, - autoOrientation: true, - plugins: [ - artplayerPluginDanmuku({ - danmuku: '/assets/sample/danmuku.xml' - }), - artplayerPluginDanmukuMask({ - solutionPath: '/assets/@mediapipe/selfie_segmentation', - }), - ], -}); \ No newline at end of file +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoSize: true, + fullscreen: true, + fullscreenWeb: true, + autoOrientation: true, + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + artplayerPluginDanmukuMask({ + solutionPath: '/assets/@mediapipe/selfie_segmentation', + }), + ], +}) diff --git a/docs/assets/example/dash.control.js b/docs/assets/example/dash.control.js index f339a89c7..6b32a02c2 100644 --- a/docs/assets/example/dash.control.js +++ b/docs/assets/example/dash.control.js @@ -4,47 +4,49 @@ // import dashjs from 'dashjs'; // import artplayerPluginDashControl from 'artplayer-plugin-dash-control'; -var art = new Artplayer({ - container: '.artplayer-app', - url: 'https://media.axprod.net/TestVectors/v7-Clear/Manifest_1080p.mpd', - setting: true, - plugins: [ - artplayerPluginDashControl({ - quality: { - // Show qualitys in control - control: true, - // Show qualitys in setting - setting: true, - // Get the quality name from level - getName: (level) => level.height + 'P', - // I18n - title: 'Quality', - auto: 'Auto', - }, - audio: { - // Show audios in control - control: true, - // Show audios in setting - setting: true, - // Get the audio name from track - getName: (track) => track.lang.toUpperCase(), - // I18n - title: 'Audio', - auto: 'Auto', - } - }), - ], - customType: { - mpd: function playMpd(video, url, art) { - if (dashjs.supportsMediaSource()) { - if (art.dash) art.dash.destroy(); - const dash = dashjs.MediaPlayer().create(); - dash.initialize(video, url, art.option.autoplay); - art.dash = dash; - art.on('destroy', () => dash.destroy()); - } else { - art.notice.show = 'Unsupported playback format: mpd'; - } - } +const art = new Artplayer({ + container: '.artplayer-app', + url: 'https://media.axprod.net/TestVectors/v7-Clear/Manifest_1080p.mpd', + setting: true, + plugins: [ + artplayerPluginDashControl({ + quality: { + // Show qualitys in control + control: true, + // Show qualitys in setting + setting: true, + // Get the quality name from level + getName: level => `${level.height}P`, + // I18n + title: 'Quality', + auto: 'Auto', + }, + audio: { + // Show audios in control + control: true, + // Show audios in setting + setting: true, + // Get the audio name from track + getName: track => track.lang.toUpperCase(), + // I18n + title: 'Audio', + auto: 'Auto', + }, + }), + ], + customType: { + mpd: function playMpd(video, url, art) { + if (dashjs.supportsMediaSource()) { + if (art.dash) + art.dash.destroy() + const dash = dashjs.MediaPlayer().create() + dash.initialize(video, url, art.option.autoplay) + art.dash = dash + art.on('destroy', () => dash.destroy()) + } + else { + art.notice.show = 'Unsupported playback format: mpd' + } }, -}); + }, +}) diff --git a/docs/assets/example/dash.js b/docs/assets/example/dash.js index b55a9ebd2..e007a9b22 100644 --- a/docs/assets/example/dash.js +++ b/docs/assets/example/dash.js @@ -2,26 +2,28 @@ // import dashjs from 'dashjs'; function playMpd(video, url, art) { - if (dashjs.supportsMediaSource()) { - if (art.dash) art.dash.destroy(); - const dash = dashjs.MediaPlayer().create(); - dash.initialize(video, url, art.option.autoplay); - art.dash = dash; - art.on('destroy', () => dash.destroy()); - } else { - art.notice.show = 'Unsupported playback format: mpd'; - } + if (dashjs.supportsMediaSource()) { + if (art.dash) + art.dash.destroy() + const dash = dashjs.MediaPlayer().create() + dash.initialize(video, url, art.option.autoplay) + art.dash = dash + art.on('destroy', () => dash.destroy()) + } + else { + art.notice.show = 'Unsupported playback format: mpd' + } } -var art = new Artplayer({ - container: '.artplayer-app', - url: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd', - type: 'mpd', - customType: { - mpd: playMpd - }, -}); +const art = new Artplayer({ + container: '.artplayer-app', + url: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd', + type: 'mpd', + customType: { + mpd: playMpd, + }, +}) art.on('ready', () => { - console.info(art.dash); -}); \ No newline at end of file + console.info(art.dash) +}) diff --git a/docs/assets/example/document.pip.js b/docs/assets/example/document.pip.js index 437403d2f..a9d7ac177 100644 --- a/docs/assets/example/document.pip.js +++ b/docs/assets/example/document.pip.js @@ -1,7 +1,7 @@ // npm i artplayer-plugin-document-pip // import artplayerPluginDocumentPip from 'artplayer-plugin-document-pip'; -let art = new Artplayer({ +const art = new Artplayer({ container: '.artplayer-app', url: '/assets/sample/video.mp4', plugins: [ diff --git a/docs/assets/example/flv.js b/docs/assets/example/flv.js index 93ed114ff..e88b076dd 100644 --- a/docs/assets/example/flv.js +++ b/docs/assets/example/flv.js @@ -2,27 +2,29 @@ // import flvjs from 'flv.js'; function playFlv(video, url, art) { - if (flvjs.isSupported()) { - if (art.flv) art.flv.destroy(); - const flv = flvjs.createPlayer({ type: 'flv', url }); - flv.attachMediaElement(video); - flv.load(); - art.flv = flv; - art.on('destroy', () => flv.destroy()); - } else { - art.notice.show = 'Unsupported playback format: flv'; - } + if (flvjs.isSupported()) { + if (art.flv) + art.flv.destroy() + const flv = flvjs.createPlayer({ type: 'flv', url }) + flv.attachMediaElement(video) + flv.load() + art.flv = flv + art.on('destroy', () => flv.destroy()) + } + else { + art.notice.show = 'Unsupported playback format: flv' + } } -var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.flv', - type: 'flv', - customType: { - flv: playFlv, - }, -}); +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.flv', + type: 'flv', + customType: { + flv: playFlv, + }, +}) art.on('ready', () => { - console.info(art.flv); -}); \ No newline at end of file + console.info(art.flv) +}) diff --git a/docs/assets/example/hls.control.js b/docs/assets/example/hls.control.js index bd00744d1..f20742174 100644 --- a/docs/assets/example/hls.control.js +++ b/docs/assets/example/hls.control.js @@ -4,50 +4,53 @@ // import Hls from 'hls.js'; // import artplayerPluginHlsControl from 'artplayer-plugin-hls-control'; -var art = new Artplayer({ - container: '.artplayer-app', - url: 'https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/index.m3u8', - setting: true, - plugins: [ - artplayerPluginHlsControl({ - quality: { - // Show qualitys in control - control: true, - // Show qualitys in setting - setting: true, - // Get the quality name from level - getName: (level) => level.height + 'P', - // I18n - title: 'Quality', - auto: 'Auto', - }, - audio: { - // Show audios in control - control: true, - // Show audios in setting - setting: true, - // Get the audio name from track - getName: (track) => track.name, - // I18n - title: 'Audio', - auto: 'Auto', - } - }), - ], - customType: { - m3u8: function playM3u8(video, url, art) { - if (Hls.isSupported()) { - if (art.hls) art.hls.destroy(); - const hls = new Hls(); - hls.loadSource(url); - hls.attachMedia(video); - art.hls = hls; - art.on('destroy', () => hls.destroy()); - } else if (video.canPlayType('application/vnd.apple.mpegurl')) { - video.src = url; - } else { - art.notice.show = 'Unsupported playback format: m3u8'; - } - } +const art = new Artplayer({ + container: '.artplayer-app', + url: 'https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/index.m3u8', + setting: true, + plugins: [ + artplayerPluginHlsControl({ + quality: { + // Show qualitys in control + control: true, + // Show qualitys in setting + setting: true, + // Get the quality name from level + getName: level => `${level.height}P`, + // I18n + title: 'Quality', + auto: 'Auto', + }, + audio: { + // Show audios in control + control: true, + // Show audios in setting + setting: true, + // Get the audio name from track + getName: track => track.name, + // I18n + title: 'Audio', + auto: 'Auto', + }, + }), + ], + customType: { + m3u8: function playM3u8(video, url, art) { + if (Hls.isSupported()) { + if (art.hls) + art.hls.destroy() + const hls = new Hls() + hls.loadSource(url) + hls.attachMedia(video) + art.hls = hls + art.on('destroy', () => hls.destroy()) + } + else if (video.canPlayType('application/vnd.apple.mpegurl')) { + video.src = url + } + else { + art.notice.show = 'Unsupported playback format: m3u8' + } }, -}); + }, +}) diff --git a/docs/assets/example/hls.js b/docs/assets/example/hls.js index 930e21f38..31bf14d30 100644 --- a/docs/assets/example/hls.js +++ b/docs/assets/example/hls.js @@ -2,29 +2,32 @@ // import Hls from 'hls.js'; function playM3u8(video, url, art) { - if (Hls.isSupported()) { - if (art.hls) art.hls.destroy(); - const hls = new Hls(); - hls.loadSource(url); - hls.attachMedia(video); - art.hls = hls; - art.on('destroy', () => hls.destroy()); - } else if (video.canPlayType('application/vnd.apple.mpegurl')) { - video.src = url; - } else { - art.notice.show = 'Unsupported playback format: m3u8'; - } + if (Hls.isSupported()) { + if (art.hls) + art.hls.destroy() + const hls = new Hls() + hls.loadSource(url) + hls.attachMedia(video) + art.hls = hls + art.on('destroy', () => hls.destroy()) + } + else if (video.canPlayType('application/vnd.apple.mpegurl')) { + video.src = url + } + else { + art.notice.show = 'Unsupported playback format: m3u8' + } } -var art = new Artplayer({ - container: '.artplayer-app', - url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', - type: 'm3u8', - customType: { - m3u8: playM3u8, - }, -}); +const art = new Artplayer({ + container: '.artplayer-app', + url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', + type: 'm3u8', + customType: { + m3u8: playM3u8, + }, +}) art.on('ready', () => { - console.info(art.hls); -}); \ No newline at end of file + console.info(art.hls) +}) diff --git a/docs/assets/example/iframe.js b/docs/assets/example/iframe.js index f91bf9e78..165369436 100644 --- a/docs/assets/example/iframe.js +++ b/docs/assets/example/iframe.js @@ -1,46 +1,47 @@ // npm i artplayer-plugin-iframe // import ArtplayerPluginIframe from 'artplayer-plugin-iframe'; -const $iframe = document.createElement('iframe'); -$iframe.allowFullscreen = true; -$iframe.width = '100%'; -$iframe.height = '100%'; +const $iframe = document.createElement('iframe') +$iframe.allowFullscreen = true +$iframe.width = '100%' +$iframe.height = '100%' -const $container = document.querySelector('.artplayer-app'); -$container.innerHTML = ''; -$container.appendChild($iframe); +const $container = document.querySelector('.artplayer-app') +$container.innerHTML = '' +$container.appendChild($iframe) const iframe = new ArtplayerPluginIframe({ - iframe: $iframe, - url: '/iframe.html', -}); + iframe: $iframe, + url: '/iframe.html', +}) iframe.message(({ type, data }) => { - switch (type) { - case 'fullscreenWeb': - if (data) { - $iframe.classList.add('fullscreenWeb'); - } else { - $iframe.classList.remove('fullscreenWeb'); - } - break; - default: - break; - } -}); + switch (type) { + case 'fullscreenWeb': + if (data) { + $iframe.classList.add('fullscreenWeb') + } + else { + $iframe.classList.remove('fullscreenWeb') + } + break + default: + break + } +}) iframe.commit(() => { - var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', - fullscreen: true, - fullscreenWeb: true, - }); + const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fullscreen: true, + fullscreenWeb: true, + }) - art.on('fullscreenWeb', (state) => { - ArtplayerPluginIframe.postMessage({ - type: 'fullscreenWeb', - data: state, - }); - }); -}); + art.on('fullscreenWeb', (state) => { + ArtplayerPluginIframe.postMessage({ + type: 'fullscreenWeb', + data: state, + }) + }) +}) diff --git a/docs/assets/example/index.js b/docs/assets/example/index.js index 953e035b1..b359a1983 100644 --- a/docs/assets/example/index.js +++ b/docs/assets/example/index.js @@ -1,190 +1,190 @@ var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', - poster: '/assets/sample/poster.jpg', - volume: 0.5, - isLive: false, - muted: false, - autoplay: false, - pip: true, - autoSize: true, - autoMini: true, - screenshot: true, - setting: true, - loop: true, - flip: true, - playbackRate: true, - aspectRatio: true, - fullscreen: true, - fullscreenWeb: true, - subtitleOffset: true, - miniProgressBar: true, - mutex: true, - backdrop: true, - playsInline: true, - autoPlayback: true, - airplay: true, - theme: '#23ade5', - lang: navigator.language.toLowerCase(), - moreVideoAttr: { - crossOrigin: 'anonymous', + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + poster: '/assets/sample/poster.jpg', + volume: 0.5, + isLive: false, + muted: false, + autoplay: false, + pip: true, + autoSize: true, + autoMini: true, + screenshot: true, + setting: true, + loop: true, + flip: true, + playbackRate: true, + aspectRatio: true, + fullscreen: true, + fullscreenWeb: true, + subtitleOffset: true, + miniProgressBar: true, + mutex: true, + backdrop: true, + playsInline: true, + autoPlayback: true, + airplay: true, + theme: '#23ade5', + lang: navigator.language.toLowerCase(), + moreVideoAttr: { + crossOrigin: 'anonymous', + }, + settings: [ + { + width: 200, + html: 'Subtitle', + tooltip: 'Bilingual', + icon: '', + selector: [ + { + html: 'Display', + tooltip: 'Show', + switch: true, + onSwitch(item) { + item.tooltip = item.switch ? 'Hide' : 'Show' + art.subtitle.show = !item.switch + return !item.switch + }, + }, + { + default: true, + html: 'Bilingual', + url: '/assets/sample/subtitle.srt', + }, + { + html: 'Chinese', + url: '/assets/sample/subtitle.cn.srt', + }, + { + html: 'Japanese', + url: '/assets/sample/subtitle.jp.srt', + }, + ], + onSelect(item) { + art.subtitle.switch(item.url, { + name: item.html, + }) + return item.html + }, }, - settings: [ - { - width: 200, - html: 'Subtitle', - tooltip: 'Bilingual', - icon: '', - selector: [ - { - html: 'Display', - tooltip: 'Show', - switch: true, - onSwitch: function (item) { - item.tooltip = item.switch ? 'Hide' : 'Show'; - art.subtitle.show = !item.switch; - return !item.switch; - }, - }, - { - default: true, - html: 'Bilingual', - url: '/assets/sample/subtitle.srt', - }, - { - html: 'Chinese', - url: '/assets/sample/subtitle.cn.srt', - }, - { - html: 'Japanese', - url: '/assets/sample/subtitle.jp.srt', - }, - ], - onSelect: function (item) { - art.subtitle.switch(item.url, { - name: item.html, - }); - return item.html; - }, - }, - { - html: 'Switcher', - icon: '', - tooltip: 'OFF', - switch: false, - onSwitch: function (item) { - item.tooltip = item.switch ? 'OFF' : 'ON'; - console.info('You clicked on the custom switch', item.switch); - return !item.switch; - }, - }, - { - html: 'Slider', - icon: '', - tooltip: '5x', - range: [5, 1, 10, 0.1], - onRange: function (item) { - return item.range[0] + 'x'; - }, - }, - { - html: 'Button', - icon: '', - tooltip: 'tooltip', - onClick() { - return 'Button clicked'; - } - }, - ], - contextmenu: [ - { - html: 'Custom menu', - click: function (contextmenu) { - console.info('You clicked on the custom menu'); - contextmenu.show = false; - }, - }, - ], - layers: [ - { - html: '', - click: function () { - window.open('https://aimu.app'); - console.info('You clicked on the custom layer'); - }, - style: { - position: 'absolute', - top: '20px', - right: '20px', - opacity: '.9', - }, - }, - ], - quality: [ - { - default: true, - html: 'SD 480P', - url: '/assets/sample/video.mp4?q=480', - }, - { - html: 'HD 720P', - url: '/assets/sample/video.mp4?q=720', - }, - ], - thumbnails: { - url: '/assets/sample/thumbnails.png', - number: 60, - column: 10, - scale: 0.85, + { + html: 'Switcher', + icon: '', + tooltip: 'OFF', + switch: false, + onSwitch(item) { + item.tooltip = item.switch ? 'OFF' : 'ON' + console.info('You clicked on the custom switch', item.switch) + return !item.switch + }, }, - subtitle: { - url: '/assets/sample/subtitle.srt', - type: 'srt', - style: { - color: '#fe9200', - fontSize: '20px', - }, - encoding: 'utf-8', + { + html: 'Slider', + icon: '', + tooltip: '5x', + range: [5, 1, 10, 0.1], + onRange(item) { + return `${item.range[0]}x` + }, }, - highlight: [ - { - time: 15, - text: 'One more chance', - }, - { - time: 30, - text: '谁でもいいはずなのに', - }, - { - time: 45, - text: '夏の想い出がまわる', - }, - { - time: 60, - text: 'こんなとこにあるはずもないのに', - }, - { - time: 75, - text: '终わり', - }, - ], - controls: [ - { - position: 'right', - html: 'Control', - index: 1, - tooltip: 'Control Tooltip', - style: { - marginRight: '20px', - }, - click: function () { - console.info('You clicked on the custom control'); - }, - }, - ], - icons: { - loading: '', - state: '', - indicator: '', + { + html: 'Button', + icon: '', + tooltip: 'tooltip', + onClick() { + return 'Button clicked' + }, }, -}); + ], + contextmenu: [ + { + html: 'Custom menu', + click(contextmenu) { + console.info('You clicked on the custom menu') + contextmenu.show = false + }, + }, + ], + layers: [ + { + html: '', + click() { + window.open('https://aimu.app') + console.info('You clicked on the custom layer') + }, + style: { + position: 'absolute', + top: '20px', + right: '20px', + opacity: '.9', + }, + }, + ], + quality: [ + { + default: true, + html: 'SD 480P', + url: '/assets/sample/video.mp4?q=480', + }, + { + html: 'HD 720P', + url: '/assets/sample/video.mp4?q=720', + }, + ], + thumbnails: { + url: '/assets/sample/thumbnails.png', + number: 60, + column: 10, + scale: 0.85, + }, + subtitle: { + url: '/assets/sample/subtitle.srt', + type: 'srt', + style: { + color: '#fe9200', + fontSize: '20px', + }, + encoding: 'utf-8', + }, + highlight: [ + { + time: 15, + text: 'One more chance', + }, + { + time: 30, + text: '谁でもいいはずなのに', + }, + { + time: 45, + text: '夏の想い出がまわる', + }, + { + time: 60, + text: 'こんなとこにあるはずもないのに', + }, + { + time: 75, + text: '终わり', + }, + ], + controls: [ + { + position: 'right', + html: 'Control', + index: 1, + tooltip: 'Control Tooltip', + style: { + marginRight: '20px', + }, + click() { + console.info('You clicked on the custom control') + }, + }, + ], + icons: { + loading: '', + state: '', + indicator: '', + }, +}) diff --git a/docs/assets/example/libass.js b/docs/assets/example/libass.js index 67e15141a..77aa1f981 100644 --- a/docs/assets/example/libass.js +++ b/docs/assets/example/libass.js @@ -1,44 +1,44 @@ // npm i artplayer-plugin-libass // import artplayerPluginLibass from 'artplayer-plugin-libass'; -var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', - fullscreenWeb: true, - subtitle: { - url: '/assets/sample/style-test.ass', - }, - plugins: [ - artplayerPluginLibass({ - // debug: true, - workerUrl: 'https://unpkg.com/libass-wasm@4.1.0/dist/js/subtitles-octopus-worker.js', - // wasmUrl: 'https://unpkg.com/libass-wasm@4.1.0/dist/js/subtitles-octopus-worker.wasm', - fallbackFont: '/assets/misc/SourceHanSansCN-Bold.woff2' - }), - ], -}); +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fullscreenWeb: true, + subtitle: { + url: '/assets/sample/style-test.ass', + }, + plugins: [ + artplayerPluginLibass({ + // debug: true, + workerUrl: 'https://unpkg.com/libass-wasm@4.1.0/dist/js/subtitles-octopus-worker.js', + // wasmUrl: 'https://unpkg.com/libass-wasm@4.1.0/dist/js/subtitles-octopus-worker.wasm', + fallbackFont: '/assets/misc/SourceHanSansCN-Bold.woff2', + }), + ], +}) // init art.on('artplayerPluginLibass:init', (adapter) => { - console.info('artplayerPluginLibass:init', adapter); -}); + console.info('artplayerPluginLibass:init', adapter) +}) // subtitle switch art.on('artplayerPluginLibass:switch', (url) => { - console.info('artplayerPluginLibass:switch', url); + console.info('artplayerPluginLibass:switch', url) }) // subtitle visible art.on('artplayerPluginLibass:visible', (visible) => { - console.info('artplayerPluginLibass:visible', visible); + console.info('artplayerPluginLibass:visible', visible) }) // subtitle timeOffset art.on('artplayerPluginLibass:timeOffset', (timeOffset) => { - console.info('artplayerPluginLibass:timeOffset', timeOffset); + console.info('artplayerPluginLibass:timeOffset', timeOffset) }) // destroy art.on('artplayerPluginLibass:destroy', () => { - console.info('artplayerPluginLibass:destroy'); + console.info('artplayerPluginLibass:destroy') }) diff --git a/docs/assets/example/mobile.js b/docs/assets/example/mobile.js index 0225e7732..3fda3a802 100644 --- a/docs/assets/example/mobile.js +++ b/docs/assets/example/mobile.js @@ -1,98 +1,98 @@ var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', - poster: '/assets/sample/poster.jpg', - autoSize: true, - loop: true, - mutex: true, - setting: true, - flip: true, - lock: true, - fastForward: true, - playbackRate: true, - aspectRatio: true, - theme: '#ff0057', - fullscreen: true, - fullscreenWeb: true, - miniProgressBar: true, - autoOrientation: true, - airplay: true, - moreVideoAttr: { - 'x5-video-player-type': 'h5', - 'x5-video-player-fullscreen': false, - 'x5-video-orientation': 'portraint', - preload: "metadata" + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + poster: '/assets/sample/poster.jpg', + autoSize: true, + loop: true, + mutex: true, + setting: true, + flip: true, + lock: true, + fastForward: true, + playbackRate: true, + aspectRatio: true, + theme: '#ff0057', + fullscreen: true, + fullscreenWeb: true, + miniProgressBar: true, + autoOrientation: true, + airplay: true, + moreVideoAttr: { + 'x5-video-player-type': 'h5', + 'x5-video-player-fullscreen': false, + 'x5-video-orientation': 'portraint', + 'preload': 'metadata', + }, + thumbnails: { + url: '/assets/sample/thumbnails.png', + number: 60, + column: 10, + scale: 0.6, + }, + subtitle: { + name: '中日双语', + url: '/assets/sample/subtitle.srt', + style: { + color: '#48aff0', + fontSize: '16px', }, - thumbnails: { - url: '/assets/sample/thumbnails.png', - number: 60, - column: 10, - scale: 0.6 + }, + layers: [ + { + html: ``, + click() { + art.notice.show = '你点击了自定义层' + }, + style: { + position: 'absolute', + top: '10px', + right: '10px', + opacity: '.9', + }, }, - subtitle: { - name: '中日双语', - url: '/assets/sample/subtitle.srt', - style: { - color: '#48aff0', - fontSize: '16px', - }, - }, - layers: [ + ], + icons: { + loading: '', + state: '', + indicator: '', + }, + settings: [ + { + width: 200, + html: '切换字幕', + tooltip: '双语', + icon: '', + selector: [ { - html: ``, - click: function () { - art.notice.show = "你点击了自定义层"; - }, - style: { - position: 'absolute', - top: '10px', - right: '10px', - opacity: '.9', - }, + html: '开关', + switch: true, + tooltip: '显示', + onSwitch(item) { + item.tooltip = item.switch ? '隐藏' : '显示' + art.subtitle.show = !item.switch + return !item.switch + }, }, - ], - icons: { - loading: '', - state: '', - indicator: '', - }, - settings: [ { - width: 200, - html: '切换字幕', - tooltip: '双语', - icon: '', - selector: [ - { - html: '开关', - switch: true, - tooltip: '显示', - onSwitch: function (item) { - item.tooltip = item.switch ? '隐藏' : '显示'; - art.subtitle.show = !item.switch; - return !item.switch; - }, - }, - { - default: true, - html: '双语', - url: '/assets/sample/subtitle.srt' - }, - { - html: '中文', - url: '/assets/sample/subtitle.cn.srt' - }, - { - html: '日文', - url: '/assets/sample/subtitle.jp.srt' - }, - ], - onSelect: function (item) { - art.subtitle.switch(item.url, { - name: item.html - }); - return item.html; - }, + default: true, + html: '双语', + url: '/assets/sample/subtitle.srt', }, - ], -}); \ No newline at end of file + { + html: '中文', + url: '/assets/sample/subtitle.cn.srt', + }, + { + html: '日文', + url: '/assets/sample/subtitle.jp.srt', + }, + ], + onSelect(item) { + art.subtitle.switch(item.url, { + name: item.html, + }) + return item.html + }, + }, + ], +}) diff --git a/docs/assets/example/mpegts.js b/docs/assets/example/mpegts.js index 8f2b71f23..b35b17a07 100644 --- a/docs/assets/example/mpegts.js +++ b/docs/assets/example/mpegts.js @@ -2,33 +2,35 @@ // import mpegts from 'mpegts'; function playFlv(video, url, art) { - if (mpegts.isSupported()) { - if (art.flv) art.flv.destroy(); + if (mpegts.isSupported()) { + if (art.flv) + art.flv.destroy() - const flv = mpegts.createPlayer({ - type: 'flv', - url: url - }); - flv.attachMediaElement(video); - flv.load(); - flv.play(); + const flv = mpegts.createPlayer({ + type: 'flv', + url, + }) + flv.attachMediaElement(video) + flv.load() + flv.play() - art.flv = flv; - art.on('destroy', () => flv.destroy()); - } else { - art.notice.show = 'Unsupported playback format: flv'; - } + art.flv = flv + art.on('destroy', () => flv.destroy()) + } + else { + art.notice.show = 'Unsupported playback format: flv' + } } -var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.flv', - type: 'flv', - customType: { - flv: playFlv, - }, -}); +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.flv', + type: 'flv', + customType: { + flv: playFlv, + }, +}) art.on('ready', () => { - console.info(art.flv); -}); \ No newline at end of file + console.info(art.flv) +}) diff --git a/docs/assets/example/multiple.subtitles.js b/docs/assets/example/multiple.subtitles.js index d3b1a53a2..82af8ec98 100644 --- a/docs/assets/example/multiple.subtitles.js +++ b/docs/assets/example/multiple.subtitles.js @@ -2,88 +2,89 @@ // import artplayerPluginMultipleSubtitles from 'artplayer-plugin-multiple-subtitles'; var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', - setting: true, - plugins: [ - artplayerPluginMultipleSubtitles({ - subtitles: [ - { - name: 'chinese', - url: '/assets/sample/subtitle.cn.srt', - }, - { - name: 'japanese', - url: '/assets/sample/subtitle.jp.srt', - } - ], - }) - ], - settings: [ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + plugins: [ + artplayerPluginMultipleSubtitles({ + subtitles: [ { - width: 200, - html: 'Subtitle', - tooltip: 'Double', - icon: '', - selector: [ - { - html: 'Display', - tooltip: 'Show', - switch: true, - onSwitch: function (item) { - item.tooltip = item.switch ? 'Hide' : 'Show'; - // 显示/隐藏字幕 - // Show/hide subtitles - art.subtitle.show = !item.switch; - return !item.switch; - }, - }, - { - html: 'Reverse', - tooltip: 'Off', - switch: false, - onSwitch: function (item) { - item.tooltip = item.switch ? 'Off' : 'On'; - // 修改字幕顺序 - // Change the order of subtitles - if (item.switch) { - art.plugins.multipleSubtitles.tracks(['chinese', 'japanese']); - } else { - art.plugins.multipleSubtitles.tracks(['japanese', 'chinese']); - - } - return !item.switch; - }, - }, - { - default: true, - html: 'Double', - name: 'double', - }, - { - html: 'Chinese', - name: 'chinese', - }, - { - html: 'Japanese', - name: 'japanese', - }, - ], - onSelect: function (item) { - if (item.name === 'double') { - // 重置字幕 - // Reset subtitles - art.plugins.multipleSubtitles.reset(); - } else { - // 显示单个字幕 - // Show single subtitle - art.plugins.multipleSubtitles.tracks([item.name]); - } - return item.html; - }, + name: 'chinese', + url: '/assets/sample/subtitle.cn.srt', }, - ], -}); + { + name: 'japanese', + url: '/assets/sample/subtitle.jp.srt', + }, + ], + }), + ], + settings: [ + { + width: 200, + html: 'Subtitle', + tooltip: 'Double', + icon: '', + selector: [ + { + html: 'Display', + tooltip: 'Show', + switch: true, + onSwitch(item) { + item.tooltip = item.switch ? 'Hide' : 'Show' + // 显示/隐藏字幕 + // Show/hide subtitles + art.subtitle.show = !item.switch + return !item.switch + }, + }, + { + html: 'Reverse', + tooltip: 'Off', + switch: false, + onSwitch(item) { + item.tooltip = item.switch ? 'Off' : 'On' + // 修改字幕顺序 + // Change the order of subtitles + if (item.switch) { + art.plugins.multipleSubtitles.tracks(['chinese', 'japanese']) + } + else { + art.plugins.multipleSubtitles.tracks(['japanese', 'chinese']) + } + return !item.switch + }, + }, + { + default: true, + html: 'Double', + name: 'double', + }, + { + html: 'Chinese', + name: 'chinese', + }, + { + html: 'Japanese', + name: 'japanese', + }, + ], + onSelect(item) { + if (item.name === 'double') { + // 重置字幕 + // Reset subtitles + art.plugins.multipleSubtitles.reset() + } + else { + // 显示单个字幕 + // Show single subtitle + art.plugins.multipleSubtitles.tracks([item.name]) + } + return item.html + }, + }, + ], +}) // 自定义你自己的样式,请勿复制以下代码 // Customize your own style, please do not copy the following code @@ -98,14 +99,15 @@ const style = ` color: yellow; font-size: 12px; } -`; +` -const $style = document.getElementById('artplayer-subtitle-style'); +const $style = document.getElementById('artplayer-subtitle-style') if ($style) { - $style.textContent = style; -} else { - const $style = document.createElement('style'); - $style.id = 'artplayer-subtitle-style'; - $style.textContent = style; - document.head.appendChild($style); -} \ No newline at end of file + $style.textContent = style +} +else { + const $style = document.createElement('style') + $style.id = 'artplayer-subtitle-style' + $style.textContent = style + document.head.appendChild($style) +} diff --git a/docs/assets/example/setting.test.js b/docs/assets/example/setting.test.js index 1dab9085f..8ffcba30f 100644 --- a/docs/assets/example/setting.test.js +++ b/docs/assets/example/setting.test.js @@ -1,138 +1,140 @@ var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', - setting: true, - flip: true, - playbackRate: true, - aspectRatio: true, - subtitleOffset: true, - settings: [ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + flip: true, + playbackRate: true, + aspectRatio: true, + subtitleOffset: true, + settings: [ + { + width: 200, + html: 'Subtitle', + name: 'subtitle', + tooltip: 'Bilingual', + icon: '', + selector: [ { - width: 200, - html: 'Subtitle', - name: 'subtitle', - tooltip: 'Bilingual', - icon: '', - selector: [ - { - html: 'Display', - tooltip: 'Show', - switch: true, - onSwitch: function (item) { - item.tooltip = item.switch ? 'Hide' : 'Show'; - art.subtitle.show = !item.switch; - return !item.switch; - }, - }, - { - default: true, - html: 'Bilingual', - url: '/assets/sample/subtitle.srt', - }, - { - html: 'Chinese', - url: '/assets/sample/subtitle.cn.srt', - }, - { - html: 'Japanese', - url: '/assets/sample/subtitle.jp.srt', - }, - ], - onSelect: function (item) { - art.subtitle.switch(item.url, { - name: item.html, - }); - return item.html; - }, - mounted: function (...args) { - console.info(args); - }, + html: 'Display', + tooltip: 'Show', + switch: true, + onSwitch(item) { + item.tooltip = item.switch ? 'Hide' : 'Show' + art.subtitle.show = !item.switch + return !item.switch + }, }, { - html: 'Switcher', - icon: '', - tooltip: 'OFF', - switch: false, - onSwitch: function (item) { - item.tooltip = item.switch ? 'OFF' : 'ON'; - console.info('You clicked on the custom switch', item.switch); - return !item.switch; - }, - mounted: function (...args) { - console.info(args); - }, + default: true, + html: 'Bilingual', + url: '/assets/sample/subtitle.srt', }, { - html: 'Slider', - icon: '', - tooltip: '5x', - range: [5, 1, 10, 0.1], - onRange: function (item) { - return item.range[0] + 'x'; - }, - mounted: function (...args) { - console.info(args); - }, + html: 'Chinese', + url: '/assets/sample/subtitle.cn.srt', }, - ], + { + html: 'Japanese', + url: '/assets/sample/subtitle.jp.srt', + }, + ], + onSelect(item) { + art.subtitle.switch(item.url, { + name: item.html, + }) + return item.html + }, + mounted(...args) { + console.info(args) + }, + }, + { + html: 'Switcher', + icon: '', + tooltip: 'OFF', + switch: false, + onSwitch(item) { + item.tooltip = item.switch ? 'OFF' : 'ON' + console.info('You clicked on the custom switch', item.switch) + return !item.switch + }, + mounted(...args) { + console.info(args) + }, + }, + { + html: 'Slider', + icon: '', + tooltip: '5x', + range: [5, 1, 10, 0.1], + onRange(item) { + return `${item.range[0]}x` + }, + mounted(...args) { + console.info(args) + }, + }, + ], }, async () => { - const { sleep } = Artplayer.utils; - art.setting.show = true; - console.log(art.setting.builtin); - console.log(art.setting.find('aspect-ratio')); - console.log(art.setting.find('aspect-ratio2')); - await sleep(1000); - art.setting.resize(); - await sleep(1000); - art.setting.inactivate(art.setting.find('subtitle')); - art.setting.remove('aspect-ratio'); - try { - art.setting.remove('aspect-ratio2'); - } catch (error) { - console.log(error.message); - } - await sleep(1000); - art.setting.update({ - name: 'subtitle-offset', - html: 'new offset', - range: [5, -11, 11, 1], - }); - await sleep(1000); - art.setting.find('subtitle-offset').range = [0, -0, 10, 1]; - await sleep(1000); - art.setting.update({ - name: 'subtitle-offset2', - html: 'new offset 2', - range: [5, -11, 11, 1], - onChange(item) { - return item.range[0] + 's'; - }, - }); - await sleep(1000); - art.setting.update({ - name: 'flip', - html: 'new flip', - tooltip: 'OFF', - switch: false, - }); - await sleep(1000); - art.setting.find('flip').switch = true; - await sleep(1000); - art.setting.update({ - name: 'flip2', - html: 'new flip2', - tooltip: 'OFF', - switch: true, - }); - await sleep(1000); - try { - art.setting.add({ - name: 'flip2', - html: 'new flip2', - tooltip: 'OFF', - switch: true, - }); - } catch (error) { - console.log(error.message); - } -}); + const { sleep } = Artplayer.utils + art.setting.show = true + console.log(art.setting.builtin) + console.log(art.setting.find('aspect-ratio')) + console.log(art.setting.find('aspect-ratio2')) + await sleep(1000) + art.setting.resize() + await sleep(1000) + art.setting.inactivate(art.setting.find('subtitle')) + art.setting.remove('aspect-ratio') + try { + art.setting.remove('aspect-ratio2') + } + catch (error) { + console.log(error.message) + } + await sleep(1000) + art.setting.update({ + name: 'subtitle-offset', + html: 'new offset', + range: [5, -11, 11, 1], + }) + await sleep(1000) + art.setting.find('subtitle-offset').range = [0, -0, 10, 1] + await sleep(1000) + art.setting.update({ + name: 'subtitle-offset2', + html: 'new offset 2', + range: [5, -11, 11, 1], + onChange(item) { + return `${item.range[0]}s` + }, + }) + await sleep(1000) + art.setting.update({ + name: 'flip', + html: 'new flip', + tooltip: 'OFF', + switch: false, + }) + await sleep(1000) + art.setting.find('flip').switch = true + await sleep(1000) + art.setting.update({ + name: 'flip2', + html: 'new flip2', + tooltip: 'OFF', + switch: true, + }) + await sleep(1000) + try { + art.setting.add({ + name: 'flip2', + html: 'new flip2', + tooltip: 'OFF', + switch: true, + }) + } + catch (error) { + console.log(error.message) + } +}) diff --git a/docs/assets/example/thumbnail.js b/docs/assets/example/thumbnail.js index 2cb3b342c..ac28de015 100644 --- a/docs/assets/example/thumbnail.js +++ b/docs/assets/example/thumbnail.js @@ -1,14 +1,14 @@ // npm i artplayer-plugin-thumbnail // import artplayerPluginThumbnail from 'artplayer-plugin-thumbnail'; -var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', - plugins: [ - artplayerPluginThumbnail({ - width: 160, - number: 100, - scale: 1, - }), - ], -}); \ No newline at end of file +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginThumbnail({ + width: 160, + number: 100, + scale: 1, + }), + ], +}) diff --git a/docs/assets/example/vast.js b/docs/assets/example/vast.js index ab9e75099..b6457f5f9 100644 --- a/docs/assets/example/vast.js +++ b/docs/assets/example/vast.js @@ -1,4 +1,4 @@ -// Depends on: +// Depends on: // https://glomex.github.io/vast-ima-player/ // https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side @@ -9,16 +9,16 @@ // import artplayerPluginVast from 'artplayer-plugin-vast'; var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', - fullscreen: true, - fullscreenWeb: true, - plugins: [ - artplayerPluginVast(({ playUrl, imaPlayer, ima }) => { - // Play the ad when the video is played - art.once('play', () => { - playUrl('https://artplayer.org/assets/vast/linear-ad.xml'); - }); - }), - ], -}); \ No newline at end of file + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fullscreen: true, + fullscreenWeb: true, + plugins: [ + artplayerPluginVast(({ playUrl, imaPlayer, ima }) => { + // Play the ad when the video is played + art.once('play', () => { + playUrl('https://artplayer.org/assets/vast/linear-ad.xml') + }) + }), + ], +}) diff --git a/docs/assets/example/vtt.thumbnail.js b/docs/assets/example/vtt.thumbnail.js index b656729f9..451f60774 100644 --- a/docs/assets/example/vtt.thumbnail.js +++ b/docs/assets/example/vtt.thumbnail.js @@ -1,12 +1,12 @@ // npm i artplayer-plugin-vtt-thumbnail // import artplayerPluginVttThumbnail from 'artplayer-plugin-vtt-thumbnail'; -var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/bbb-video.mp4', - plugins: [ - artplayerPluginVttThumbnail({ - vtt: '/assets/sample/bbb-thumbnails.vtt', - }) - ] -}); \ No newline at end of file +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/bbb-video.mp4', + plugins: [ + artplayerPluginVttThumbnail({ + vtt: '/assets/sample/bbb-thumbnails.vtt', + }), + ], +}) diff --git a/docs/assets/example/webtorrent.js b/docs/assets/example/webtorrent.js index cd8ea3622..a25a6f762 100644 --- a/docs/assets/example/webtorrent.js +++ b/docs/assets/example/webtorrent.js @@ -2,35 +2,37 @@ // import WebTorrent from 'webtorrent'; async function playTorrent(video, url, art) { - if (WebTorrent.WEBRTC_SUPPORT) { - if (art.torrent) art.torrent.destroy(); - art.torrent = new WebTorrent(); + if (WebTorrent.WEBRTC_SUPPORT) { + if (art.torrent) + art.torrent.destroy() + art.torrent = new WebTorrent() - await navigator.serviceWorker.register('/webtorrent.sw.min.js'); - art.torrent.loadWorker(navigator.serviceWorker.controller); + await navigator.serviceWorker.register('/webtorrent.sw.min.js') + art.torrent.loadWorker(navigator.serviceWorker.controller) - art.torrent.add(url, (torrent) => { - const file = torrent.files.find((file) => { - return file.name.endsWith('.mp4'); - }); - file.streamTo(video); - }); + art.torrent.add(url, (torrent) => { + const file = torrent.files.find((file) => { + return file.name.endsWith('.mp4') + }) + file.streamTo(video) + }) - art.on('destroy', () => art.torrent.destroy()); - } else { - art.notice.show = 'Unsupported playback format: torrent'; - } + art.on('destroy', () => art.torrent.destroy()) + } + else { + art.notice.show = 'Unsupported playback format: torrent' + } } -var art = new Artplayer({ - container: '.artplayer-app', - url: 'magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent', - type: 'torrent', - customType: { - torrent: playTorrent, - }, -}); +const art = new Artplayer({ + container: '.artplayer-app', + url: 'magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent', + type: 'torrent', + customType: { + torrent: playTorrent, + }, +}) art.on('ready', () => { - console.info(art.torrent); -}); + console.info(art.torrent) +}) diff --git a/docs/assets/ts/artplayer-plugin-ads.d.ts b/docs/assets/ts/artplayer-plugin-ads.d.ts index dd3cde90e..618fdd6c1 100644 --- a/docs/assets/ts/artplayer-plugin-ads.d.ts +++ b/docs/assets/ts/artplayer-plugin-ads.d.ts @@ -48,5 +48,5 @@ declare const artplayerPluginAds: (option: Option) => (art: Artplayer) => Ads export default artplayerPluginAds -export = artplayerPluginAds; -export as namespace artplayerPluginAds; \ No newline at end of file +export = artplayerPluginAds +export as namespace artplayerPluginAds; diff --git a/docs/assets/ts/artplayer-plugin-ambilight.d.ts b/docs/assets/ts/artplayer-plugin-ambilight.d.ts index 786b884f4..50abda15a 100644 --- a/docs/assets/ts/artplayer-plugin-ambilight.d.ts +++ b/docs/assets/ts/artplayer-plugin-ambilight.d.ts @@ -16,5 +16,5 @@ declare const artplayerPluginAmbilight: (option: Option) => (art: Artplayer) => export default artplayerPluginAmbilight -export = artplayerPluginAmbilight; -export as namespace artplayerPluginAmbilight; \ No newline at end of file +export = artplayerPluginAmbilight +export as namespace artplayerPluginAmbilight; diff --git a/docs/assets/ts/artplayer-plugin-asr.d.ts b/docs/assets/ts/artplayer-plugin-asr.d.ts index a134e7348..769e0b30f 100644 --- a/docs/assets/ts/artplayer-plugin-asr.d.ts +++ b/docs/assets/ts/artplayer-plugin-asr.d.ts @@ -22,5 +22,5 @@ declare function artplayerPluginAsr(option?: AsrPluginOption): (art: Artplayer) export default artplayerPluginAsr -export = artplayerPluginAsr; -export as namespace artplayerPluginAsr; \ No newline at end of file +export = artplayerPluginAsr +export as namespace artplayerPluginAsr; diff --git a/docs/assets/ts/artplayer-plugin-auto-thumbnail.d.ts b/docs/assets/ts/artplayer-plugin-auto-thumbnail.d.ts index dff0302ce..e6f616aa0 100644 --- a/docs/assets/ts/artplayer-plugin-auto-thumbnail.d.ts +++ b/docs/assets/ts/artplayer-plugin-auto-thumbnail.d.ts @@ -13,5 +13,5 @@ declare const artplayerPluginAutoThumbnail: (option: Option) => (art: Artplayer) export default artplayerPluginAutoThumbnail -export = artplayerPluginAutoThumbnail; -export as namespace artplayerPluginAutoThumbnail; \ No newline at end of file +export = artplayerPluginAutoThumbnail +export as namespace artplayerPluginAutoThumbnail; diff --git a/docs/assets/ts/artplayer-plugin-chapter.d.ts b/docs/assets/ts/artplayer-plugin-chapter.d.ts index 0c16baa40..5d6f7b378 100644 --- a/docs/assets/ts/artplayer-plugin-chapter.d.ts +++ b/docs/assets/ts/artplayer-plugin-chapter.d.ts @@ -17,5 +17,5 @@ declare const artplayerPluginChapter: (option: Option) => (art: Artplayer) => Re export default artplayerPluginChapter -export = artplayerPluginChapter; -export as namespace artplayerPluginChapter; \ No newline at end of file +export = artplayerPluginChapter +export as namespace artplayerPluginChapter; diff --git a/docs/assets/ts/artplayer-plugin-chromecast.d.ts b/docs/assets/ts/artplayer-plugin-chromecast.d.ts index 4d3aadd12..5069b042d 100644 --- a/docs/assets/ts/artplayer-plugin-chromecast.d.ts +++ b/docs/assets/ts/artplayer-plugin-chromecast.d.ts @@ -13,5 +13,5 @@ declare const artplayerPluginChromecast: (option: Option) => (art: Artplayer) => export default artplayerPluginChromecast -export = artplayerPluginChromecast; -export as namespace artplayerPluginChromecast; \ No newline at end of file +export = artplayerPluginChromecast +export as namespace artplayerPluginChromecast; diff --git a/docs/assets/ts/artplayer-plugin-danmuku-mask.d.ts b/docs/assets/ts/artplayer-plugin-danmuku-mask.d.ts index b2c996b79..68515f8f8 100644 --- a/docs/assets/ts/artplayer-plugin-danmuku-mask.d.ts +++ b/docs/assets/ts/artplayer-plugin-danmuku-mask.d.ts @@ -21,5 +21,5 @@ declare const artplayerPluginDanmukuMask: (option?: Option) => (art: Artplayer) export default artplayerPluginDanmukuMask -export = artplayerPluginDanmukuMask; -export as namespace artplayerPluginDanmukuMask; \ No newline at end of file +export = artplayerPluginDanmukuMask +export as namespace artplayerPluginDanmukuMask; diff --git a/docs/assets/ts/artplayer-plugin-danmuku.d.ts b/docs/assets/ts/artplayer-plugin-danmuku.d.ts index 6f6a5ec04..554ce0fe2 100644 --- a/docs/assets/ts/artplayer-plugin-danmuku.d.ts +++ b/docs/assets/ts/artplayer-plugin-danmuku.d.ts @@ -255,5 +255,5 @@ declare const artplayerPluginDanmuku: (option: Option) => (art: Artplayer) => Re export default artplayerPluginDanmuku -export = artplayerPluginDanmuku; -export as namespace artplayerPluginDanmuku; \ No newline at end of file +export = artplayerPluginDanmuku +export as namespace artplayerPluginDanmuku; diff --git a/docs/assets/ts/artplayer-plugin-dash-control.d.ts b/docs/assets/ts/artplayer-plugin-dash-control.d.ts index 35057bb07..39a52d129 100644 --- a/docs/assets/ts/artplayer-plugin-dash-control.d.ts +++ b/docs/assets/ts/artplayer-plugin-dash-control.d.ts @@ -13,5 +13,5 @@ declare const artplayerPluginDashControl: (option: { quality?: Config, audio?: C export default artplayerPluginDashControl -export = artplayerPluginDashControl; -export as namespace artplayerPluginDashControl; \ No newline at end of file +export = artplayerPluginDashControl +export as namespace artplayerPluginDashControl; diff --git a/docs/assets/ts/artplayer-plugin-document-pip.d.ts b/docs/assets/ts/artplayer-plugin-document-pip.d.ts index dee9d2c41..df061d9f4 100644 --- a/docs/assets/ts/artplayer-plugin-document-pip.d.ts +++ b/docs/assets/ts/artplayer-plugin-document-pip.d.ts @@ -18,5 +18,5 @@ declare const artplayerPluginDocumentPip: (option: Option) => (art: Artplayer) = export default artplayerPluginDocumentPip -export = artplayerPluginDocumentPip; -export as namespace artplayerPluginDocumentPip; \ No newline at end of file +export = artplayerPluginDocumentPip +export as namespace artplayerPluginDocumentPip; diff --git a/docs/assets/ts/artplayer-plugin-hls-control.d.ts b/docs/assets/ts/artplayer-plugin-hls-control.d.ts index 01c881339..f047513e8 100644 --- a/docs/assets/ts/artplayer-plugin-hls-control.d.ts +++ b/docs/assets/ts/artplayer-plugin-hls-control.d.ts @@ -13,5 +13,5 @@ declare const artplayerPluginHlsControl: (option: { quality?: Config, audio?: Co export default artplayerPluginHlsControl -export = artplayerPluginHlsControl; -export as namespace artplayerPluginHlsControl; \ No newline at end of file +export = artplayerPluginHlsControl +export as namespace artplayerPluginHlsControl; diff --git a/docs/assets/ts/artplayer-plugin-iframe.d.ts b/docs/assets/ts/artplayer-plugin-iframe.d.ts index f5ebf54f5..88cfd5bad 100644 --- a/docs/assets/ts/artplayer-plugin-iframe.d.ts +++ b/docs/assets/ts/artplayer-plugin-iframe.d.ts @@ -28,5 +28,5 @@ declare class ArtplayerPluginIframe { export default ArtplayerPluginIframe -export = artplayerPluginIframe; -export as namespace artplayerPluginIframe; \ No newline at end of file +export = artplayerPluginIframe +export as namespace artplayerPluginIframe; diff --git a/docs/assets/ts/artplayer-plugin-libass.d.ts b/docs/assets/ts/artplayer-plugin-libass.d.ts index ae373d1b6..ff0632692 100644 --- a/docs/assets/ts/artplayer-plugin-libass.d.ts +++ b/docs/assets/ts/artplayer-plugin-libass.d.ts @@ -11,5 +11,5 @@ declare const artplayerPluginAss: (options: Options) => (art: Artplayer) => { export default artplayerPluginAss -export = artplayerPluginLibass; -export as namespace artplayerPluginLibass; \ No newline at end of file +export = artplayerPluginLibass +export as namespace artplayerPluginLibass; diff --git a/docs/assets/ts/artplayer-plugin-multiple-subtitles.d.ts b/docs/assets/ts/artplayer-plugin-multiple-subtitles.d.ts index 4d6161dff..a6f710bc1 100644 --- a/docs/assets/ts/artplayer-plugin-multiple-subtitles.d.ts +++ b/docs/assets/ts/artplayer-plugin-multiple-subtitles.d.ts @@ -12,5 +12,5 @@ declare const artplayerPluginMultipleSubtitles: (option: { export default artplayerPluginMultipleSubtitles -export = artplayerPluginMultipleSubtitles; -export as namespace artplayerPluginMultipleSubtitles; \ No newline at end of file +export = artplayerPluginMultipleSubtitles +export as namespace artplayerPluginMultipleSubtitles; diff --git a/docs/assets/ts/artplayer-plugin-vast.d.ts b/docs/assets/ts/artplayer-plugin-vast.d.ts index b34e73c10..41d815667 100644 --- a/docs/assets/ts/artplayer-plugin-vast.d.ts +++ b/docs/assets/ts/artplayer-plugin-vast.d.ts @@ -29,5 +29,5 @@ declare function artplayerPluginVast( export default artplayerPluginVast -export = artplayerPluginVast; -export as namespace artplayerPluginVast; \ No newline at end of file +export = artplayerPluginVast +export as namespace artplayerPluginVast; diff --git a/docs/assets/ts/artplayer-plugin-vtt-thumbnail.d.ts b/docs/assets/ts/artplayer-plugin-vtt-thumbnail.d.ts index aaed1e528..4e8307e77 100644 --- a/docs/assets/ts/artplayer-plugin-vtt-thumbnail.d.ts +++ b/docs/assets/ts/artplayer-plugin-vtt-thumbnail.d.ts @@ -6,5 +6,5 @@ declare const artplayerPluginVttThumbnail: (option: { vtt?: string, style?: Part export default artplayerPluginVttThumbnail -export = artplayerPluginVttThumbnail; -export as namespace artplayerPluginVttThumbnail; \ No newline at end of file +export = artplayerPluginVttThumbnail +export as namespace artplayerPluginVttThumbnail; diff --git a/docs/assets/ts/artplayer.d.ts b/docs/assets/ts/artplayer.d.ts index a181ef13e..43827733c 100644 --- a/docs/assets/ts/artplayer.d.ts +++ b/docs/assets/ts/artplayer.d.ts @@ -124,8 +124,6 @@ export interface Subtitle { onVttLoad?: (vtt: string) => string } - - type Props = { html: string icon: string @@ -239,10 +237,6 @@ export interface Quality { url: string } - - - - export type AspectRatio = 'default' | '4:3' | '16:9' | (`${number}:${number}` & Record) export type PlaybackRate = 0.5 | 0.75 | 1.0 | 1.25 | 1.5 | 1.75 | 2.0 | (number & Record) export type Flip = 'normal' | 'horizontal' | 'vertical' | (string & Record) @@ -354,15 +348,6 @@ export declare class Player { autoHeight(): void } - - - - - - - - - export type CustomType = | 'flv' | 'm3u8' @@ -771,9 +756,6 @@ declare module 'artplayer/i18n/*' { export default lang } - - - export type Bar = 'loaded' | 'played' | 'hover' export interface Events { @@ -992,8 +974,6 @@ export interface Config { ] } - - export interface Selector { /** * Whether the default is selected @@ -1120,18 +1100,6 @@ export interface ComponentOption { onSelect?: (this: Artplayer, selector: Selector, element: HTMLElement, event: Event) => void } - - - - - - - - - - - - export type { Config, Events, @@ -1313,5 +1281,5 @@ export default class Artplayer extends Player { } & Record } -export = Artplayer; -export as namespace Artplayer; \ No newline at end of file +export = Artplayer +export as namespace Artplayer; diff --git a/docs/index.html b/docs/index.html index c1020b91d..b9cc8d88b 100644 --- a/docs/index.html +++ b/docs/index.html @@ -135,6 +135,7 @@ Document Github + 🤖LLMs Q群: 320881312

diff --git a/docs/llms.txt b/docs/llms.txt new file mode 100644 index 000000000..2cd37da0d --- /dev/null +++ b/docs/llms.txt @@ -0,0 +1,8265 @@ + + +===== Documentation Summary ===== + +ArtPlayer Documentation + +The ArtPlayer class is the main entry point for creating and controlling video players. It is designed to be flexible and easy to use with a variety of configuration options. + +To create a new ArtPlayer instance, use the following constructor: + +new ArtPlayer(option); + +The option parameter is an object that defines the player's settings and behavior. Here are the available configuration options: + +container +Type: String|HTMLElement +Description: The container element where the player will be mounted. This can be either a CSS selector string or a direct reference to an HTMLElement. + +url +Type: String +Description: The video source URL. This is the path to the video file that will be played. + +type +Type: String +Description: The MIME type of the video. Common values include 'video/mp4', 'video/webm', and 'video/ogg'. If not specified, the player may attempt to detect the type automatically. + +volume +Type: Number +Default: 1 +Description: The initial volume level, ranging from 0 (muted) to 1 (maximum). + +isLive +Type: Boolean +Default: false +Description: Set to true if the video is a live stream. This may affect UI elements like the progress bar. + +muted +Type: Boolean +Default: false +Description: If true, the video will start muted. + +autoplay +Type: Boolean +Default: false +Description: If true, the video will start playing automatically once loaded. Note that many browsers require user interaction before autoplay is allowed. + +autoSize +Type: Boolean +Default: false +Description: If true, the player will automatically adjust its size to fit the container. + +autoMini +Type: Boolean +Default: false +Description: If true, the player will automatically enter mini mode when the page is scrolled. + +loop +Type: Boolean +Default: false +Description: If true, the video will loop from the beginning when it ends. + +flip +Type: String +Default: 'normal' +Description: Controls video flipping. Acceptable values are 'normal', 'horizontal', 'vertical', and 'horizontal,vertical'. + +playbackRate +Type: Number +Default: 1 +Description: The playback speed multiplier. For example, 1 is normal speed, 2 is double speed, and 0.5 is half speed. + +aspectRatio +Type: String +Default: '16:9' +Description: The aspect ratio of the video player. Common formats include '16:9', '4:3', and '1:1'. + +screenshot +Type: Boolean +Default: false +Description: If true, enables the screenshot feature, allowing users to capture frames from the video. + +setting +Type: Boolean +Default: true +Description: If true, shows the settings button in the control bar. + +hotkey +Type: Boolean +Default: true +Description: If true, enables keyboard shortcuts for common actions like play/pause and volume control. + +pip +Type: Boolean +Default: true +Description: If true, enables the Picture-in-Picture mode feature. + +fullscreen +Type: Boolean +Default: true +Description: If true, enables the fullscreen mode feature. + +fullscreenWeb +Type: Boolean +Default: false +Description: If true, uses the browser's native fullscreen API instead of the custom fullscreen mode. + +subtitleOffset +Type: Boolean +Default: false +Description: If true, allows users to adjust the timing offset for subtitles. + +miniProgressBar +Type: Boolean +Default: false +Description: If true, displays a small progress bar in mini mode. + +mute +Type: Boolean +Default: true +Description: If true, shows the mute button in the control bar. + +theme +Type: String +Default: '#ffad00' +Description: The primary color theme for the player's UI elements. + +lang +Type: String +Default: 'en' +Description: The language for the player's UI text. Supported languages include 'en' for English and 'zh-cn' for Simplified Chinese. + +moreVideoAttr +Type: Object +Default: {} +Description: Additional attributes to set on the underlying video element. For example, { crossOrigin: 'anonymous' }. + +controls +Type: Array +Default: See below +Description: An array of control items to display in the control bar. The default set includes: +[ + { + name: 'play', + position: 'left', + }, + { + name: 'time', + position: 'left', + }, + { + name: 'progress', + position: 'left', + }, + { + name: 'volume', + position: 'left', + }, + { + name: 'setting', + position: 'right', + }, + { + name: 'fullscreen', + position: 'right', + }, +] + +layers +Type: Array +Default: [] +Description: An array of layer items to display over the video. Each layer is an object with properties like name, style, and position. + +contextmenu +Type: Array +Default: [] +Description: An array of items to display in the right-click context menu. Each item is an object with properties like name and callback. + +quality +Type: Array +Default: [] +Description: An array of quality options for the user to select. Each option is an object with properties like name, url, and default. + +highlight +Type: Array +Default: [] +Description: An array of highlight markers to show on the progress bar. Each marker is an object with properties like time and text. + +settings +Type: Array +Default: See below +Description: An array of setting items to display in the settings menu. The default set includes: +[ + 'loop', + 'speed', + 'flip', +] + +plugins +Type: Array +Default: [] +Description: An array of plugin instances to extend the player's functionality. + +icons +Type: Object +Default: {} +Description: An object mapping icon names to SVG strings or HTML elements. Used to customize the player's icons. + +Here is a basic example of creating an ArtPlayer instance: + +const art = new ArtPlayer({ + container: '.artplayer-app', + url: 'path/to/video.mp4', + volume: 0.5, + autoplay: true, +}); + +This example creates a player in the element with the class 'artplayer-app', sets the video source, starts with half volume, and attempts to autoplay. + +For more advanced configurations, you can include additional options: + +const art = new ArtPlayer({ + container: document.getElementById('player'), + url: 'https://example.com/video.webm', + type: 'video/webm', + theme: '#ff0000', + playbackRate: 1.5, + controls: [ + { + name: 'play', + position: 'left', + }, + { + name: 'progress', + position: 'left', + }, + { + name: 'volume', + position: 'right', + }, + ], + settings: [ + 'loop', + 'speed', + ], +}); + +This example uses a direct HTMLElement reference, specifies the video type, changes the theme color, sets a faster playback rate, customizes the control bar, and modifies the settings menu. + +Remember to call the destroy method when you are done with the player to clean up resources: + +art.destroy(); + +This will remove the player from the DOM and free associated memory. + +Advanced Properties + +The Advanced Properties here refer to the secondary properties attached to the instance, which are less commonly used. + +option + +The player's options. + +Example code to access the player's options: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +console.info(art.option); + +Note: If you directly modify this option object, the player will not respond immediately. + +template + +Manages all DOM elements of the player. + +Example code to access player templates: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +console.info(art.template); +console.info(art.template.$video); + +Note: To easily distinguish between DOM elements and regular objects, all DOM elements within the player are prefixed with $. + +This is the definition of all DOM elements: artplayer/types/template.d.ts + +events + +Manages all DOM events for the player. It essentially proxies addEventListener and removeEventListener. When using the following methods to handle events, the event will be automatically destroyed when the player is destroyed. + +- The proxy method is used to proxy DOM events. +- The hover method is used to proxy custom hover events. + +Example code for event handling: + +var container = document.querySelector('.artplayer-app'); + +var art = new Artplayer({ + container: container, + url: '/assets/sample/video.mp4', +}); + +art.events.proxy(container, 'click', event => { + console.info('click', event); +}); + +art.events.hover(container, (event) => { + console.info('mouseenter', event); +}, (event) => { + console.info('mouseleave', event); +}); + +Note: If you need DOM events that only exist for the duration of the player's lifecycle, it is highly recommended to use these functions to avoid memory leaks. + +storage + +Manages the player's local storage. + +- The name property is used to set the cache key. +- The set method is used to set the cache. +- The get method is used to get the cache. +- The del method is used to delete the cache. +- The clear method is used to clear the cache. + +Example code for storage operations: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.storage.set('test', { foo: 'bar' }); +const test = art.storage.get('test'); +console.info(test); +art.storage.del('test'); +art.storage.clear(); + +Note: By default, all player instances share the same localStorage, and the default key is artplayer_settings. + +If you want different players to use different localStorage, you can modify art.storage.name. + +Example code for custom storage key: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.storage.name = 'your-storage-key'; +art.storage.set('test', { foo: 'bar' }); + +icons + +Manages all svg icons for the player. + +Example code to access icons: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +console.info(art.icons.loading); + +This is the definition of all icons: artplayer/types/icons.d.ts + +i18n + +Manages the player's i18n. + +- The get method is used to get the i18n value. +- The update method is used to update the i18n object. + +Example code for internationalization: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +console.info(art.i18n.get('Play')); + +art.i18n.update({ + 'zh-cn': { + Play: 'Your Play' + } +}); + +Note: Using art.i18n.update can only update the i18n after instantiation. If you want to update i18n before instantiation, please use the basic option i18n to update. + +notice + +Manages the player's notifications. It only has a show property for displaying notifications. + +Example code for showing notices: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.notice.show = 'Video Ready To Play'; +}) + +Note: If you want to hide the notice immediately: art.notice.show = ''; + +layers + +Manages the player's layers. + +- The add method is used to dynamically add a layer. +- The remove method is used to dynamically remove a layer. +- The update method is used to dynamically update a layer. +- The show property is used to set whether all layers are visible. +- The toggle method is used to toggle the visibility of all layers. + +Example code for layer management: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.layers.add({ + html: 'Some Text', + }); + + setTimeout(() => { + art.layers.show = false; + }, 1000); +}); + +For Component Configuration, please refer to: /component/layers.html + +controls + +Manages the player's controls. + +- The add method dynamically adds controls. +- The remove method dynamically removes controls. +- The update method dynamically updates controls. +- The show property sets whether to display all controls. +- The toggle method toggles the visibility of all controls. + +Example code for controls management: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.controls.add({ + html: 'Some Text', + position: 'left', + }); + + setTimeout(() => { + art.controls.show = false; + }, 1000); +}); + +For Component Configuration, please refer to: /component/controls.html + +contextmenu + +Manages the player's context menu. + +- The add method dynamically adds menu items. +- The remove method dynamically removes menu items. +- The update method dynamically updates menu items. +- The show property sets whether to display all menu items. +- The toggle method toggles the visibility of all menu items. + +Example code for context menu management: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.contextmenu.add({ + html: 'Some Text', + }); + + art.contextmenu.show = true; + setTimeout(() => { + art.contextmenu.show = false; + }, 1000); +}); + +For Component Configuration, please refer to: /component/contextmenu.html + +subtitle + +Manages the player's subtitle functionality. + +- The url property sets and returns the current subtitle URL. +- The style method sets the current subtitle's style. +- The switch method sets the current subtitle URL and options. +- textTrack gets the current text track. +- activeCues gets the list of currently active cues. +- cues gets the complete list of cues. + +Example code for subtitle management: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.subtitle.url = '/assets/sample/subtitle.srt' + art.subtitle.style({ + color: 'red', + }); +}); + +loading + +Manages the player's loading layer. + +- The show property sets whether to display the loading layer. +- The toggle property toggles the visibility of the loading layer. + +Example code for loading management: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.loading.show = true; + setTimeout(() => { + art.loading.show = false; + }, 1000); +}); + +hotkey + +Manages the player's hotkey functionality. + +- The add method adds hotkeys. +- The remove method removes hotkeys. + +Example code for hotkey management: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +function hotkeyEvent(event) { + console.info('click', event); +} + +art.on('ready', () => { + art.hotkey.add(32, hotkeyEvent); + setTimeout(() => { + art.hotkey.remove(32, hotkeyEvent); + }, 5000); +}); + +Note: These hotkeys only take effect when the player has focus (e.g., after clicking on the player). + +mask + +Manages the player's mask layer. + +- The show property sets whether to display the mask layer. +- The toggle property toggles the visibility of the mask layer. + +Example code for mask management: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.mask.show = false; + setTimeout(() => { + art.mask.show = true; + }, 1000); +}); + +setting + +Manages the player's settings panel. + +- The add method dynamically adds settings items. +- The remove method dynamically removes settings items. +- The update method dynamically updates settings items. +- The show property sets whether to display all settings items. +- The toggle method toggles the visibility of all settings items. + +Example code for settings management: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + flip: true, + playbackRate: true, + aspectRatio: true, + subtitleOffset: true, +}); + +art.on('ready', () => { + art.setting.show = true; + setTimeout(() => { + art.setting.show = false; + }, 1000); +}); + +For Settings Panel, please refer to: /component/setting.html + +plugins + +Manages the player's plugin functionality, with only one method add for dynamically adding plugins. + +Example code for plugin management: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +function myPlugin(art) { + console.info(art); + return { + name: 'myPlugin', + something: 'something', + doSomething: function () { + console.info('doSomething'); + }, + }; +} + +art.on('ready', () => { + art.plugins.add(myPlugin); +}); + +Static Properties + +Static properties refer to first-level properties mounted on the constructor that are rarely used. + +instances + +Returns an array of all player instances. This property can be useful when you need to manage multiple player instances simultaneously. + +Example code showing how to access player instances: + +console.info([...Artplayer.instances]); + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +console.info([...Artplayer.instances]); + +version + +Returns the version information of the player. + +Example code to check the player version: + +console.info(Artplayer.version); + +env + +Returns the environment variables of the player. + +Example code to access environment variables: + +console.info(Artplayer.env); + +build + +Returns the build timestamp of the player. + +Example code to check the build timestamp: + +console.info(Artplayer.build); + +config + +Returns the default configuration for videos. + +Example code to view default configuration: + +console.info(Artplayer.config); + +utils + +Returns the collection of utility functions for the player. + +Example code to access utility functions: + +console.info(Artplayer.utils); + +For all utility functions, please refer to: artplayer/types/utils.d.ts + +scheme + +Returns the validation schema for player options. + +Example code to access the validation schema: + +console.info(Artplayer.scheme); + +Emitter + +Returns the constructor for the event emitter. + +Example code to access the event emitter constructor: + +console.info(Artplayer.Emitter); + +validator + +Returns the validation function for options. + +Example code to access the validation function: + +console.info(Artplayer.validator); + +kindOf + +Returns the type detection utility function. + +Example code to access the type detection utility: + +console.info(Artplayer.kindOf); + +html + +Returns the HTML string required by the player. + +Example code to access the HTML string: + +console.info(Artplayer.html); + +option + +Returns the default options for the player. + +Example code to access default options: + +console.info(Artplayer.option); + +ArtPlayer Instance Events + +Player events are divided into two types: native events from the video (prefixed with 'video:') and custom events. + +To listen for events, use the on method: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('video:canplay', () => { + console.info('video:canplay'); +}); + +To listen for an event only once, use the once method: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.once('video:canplay', () => { + console.info('video:canplay'); +}); + +To manually trigger an event, use the emit method: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.emit('focus'); + +To remove an event listener, use the off method: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +const onReady = () => { + console.info('ready'); + art.off('ready', onReady); +} + +art.on('ready', onReady); + +For all available events, please refer to: artplayer/types/events.d.ts at https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/events.d.ts + +ready event - triggered when the player is ready for the first time: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + console.info('ready'); +}); + +restart event - triggered when the player switches URL and is ready to play: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.url = '/assets/sample/video.mp4' +}); + +art.on('restart', (url) => { + console.info('restart', url); +}); + +pause event - triggered when the player is paused: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('pause', () => { + console.info('pause'); +}); + +play event - triggered when the player starts playing: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('play', () => { + console.info('play'); +}); + +hotkey event - triggered when a player hotkey is pressed: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('hotkey', (event) => { + console.info('hotkey', event); +}); + +destroy event - triggered when the player is destroyed: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.destroy(); +}); + +art.on('destroy', () => { + console.info('destroy'); +}); + +focus event - triggered when the player gains focus: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('focus', (event) => { + console.info('focus', event); +}); + +blur event - triggered when the player loses focus: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('blur', (event) => { + console.info('blur', event); +}); + +dblclick event - triggered when the player is double-clicked: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('dblclick', (event) => { + console.info('dblclick', event); +}); + +click event - triggered when the player is clicked: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('click', (event) => { + console.info('click', event); +}); + +error event - triggered when an error occurs while the player is loading a video: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/404.mp4', +}); + +art.on('error', (error, reconnectTime) => { + console.info(error, reconnectTime); +}); + +hover event - triggered when the mouse pointer enters or leaves the player: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('hover', (state, event) => { + console.info('hover', state, event); +}); + +mousemove event - triggered when the mouse pointer moves over the player: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('mousemove', (event) => { + console.info('mousemove', event); +}); + +resize event - triggered when the player's dimensions change: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('resize', () => { + console.info('resize'); +}); + +view event - triggered when the player enters or leaves the viewport: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('view', (state) => { + console.info('view', state); +}); + +lock event - triggered when the lock state changes on mobile devices: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + lock: true, +}); + +art.on('lock', (state) => { + console.info('lock', state); +}); + +aspectRatio event - triggered when the player's aspect ratio changes: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + aspectRatio: true, + setting: true, +}); + +art.on('aspectRatio', (aspectRatio) => { + console.info('aspectRatio', aspectRatio); +}); + +autoHeight event - triggered when the player automatically sets its height: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.autoHeight(); +}); + +art.on('autoHeight', (height) => { + console.info('autoHeight', height); +}); + +autoSize event - triggered when the player automatically adjusts its size: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoSize: true, +}); + +art.on('autoSize', () => { + console.info('autoSize'); +}); + +flip event - triggered when the player's video is flipped: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + flip: true, + setting: true, +}); + +art.on('flip', (flip) => { + console.info('flip', flip); +}); + +fullscreen event - triggered when the player enters or exits fullscreen mode: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fullscreen: true, +}); + +art.on('fullscreen', (state) => { + console.info('fullscreen', state); +}); + +fullscreenError event - triggered when an error occurs during fullscreen mode transition: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.fullscreen = true; +}); + +art.on('fullscreenError', (event) => { + console.info('fullscreenError', event); +}); + +fullscreenWeb event - triggered when the player enters or exits web page fullscreen mode: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fullscreenWeb: true, +}); + +art.on('fullscreenWeb', (state) => { + console.info('fullscreenWeb', state); +}); + +mini event - triggered when the player enters or exits mini mode: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.mini = true; +}); + +art.on('mini', (state) => { + console.info('mini', state); +}); + +pip event - triggered when the player enters or exits picture-in-picture mode: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + pip: true, +}); + +art.on('pip', (state) => { + console.info('pip', state); +}); + +screenshot event - triggered when the player captures a screenshot: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + screenshot: true, +}); + +art.on('screenshot', (dataUri) => { + console.info('screenshot', dataUri); +}); + +seek event - triggered when the player performs a time jump: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('seek', (currentTime) => { + console.info('seek', currentTime); +}); + +subtitleOffset event - triggered when subtitle offset changes: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + subtitleOffset: true, + subtitle: { + url: '/assets/sample/subtitle.srt', + }, + setting: true, +}); + +art.on('subtitleOffset', (offset) => { + console.info('subtitleOffset', offset); +}); + +subtitleBeforeUpdate event - triggered before subtitles are updated: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + subtitle: { + url: '/assets/sample/subtitle.srt', + }, +}); + +art.on('subtitleBeforeUpdate', (cues) => { + console.info('subtitleBeforeUpdate', cues); +}); + +subtitleAfterUpdate event - triggered after subtitles are updated: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + subtitle: { + url: '/assets/sample/subtitle.srt', + }, +}); + +art.on('subtitleAfterUpdate', (cues) => { + console.info('subtitleAfterUpdate', cues); +}); + +subtitleLoad event - triggered when subtitles are loaded: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + subtitle: { + url: '/assets/sample/subtitle.srt', + }, +}); + +art.on('subtitleLoad', (option, cues) => { + console.info('subtitleLoad', cues, option); +}); + +info event - triggered when the info panel is shown or hidden: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('info', (state) => { + console.log(state); +}); + +layer event - triggered when custom layers are shown or hidden: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('layer', (state) => { + console.log(state); +}); + +loading event - triggered when the loading indicator is shown or hidden: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('loading', (state) => { + console.log(state); +}); + +mask event - triggered when the mask layer is shown or hidden: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('mask', (state) => { + console.log(state); +}); + +subtitle event - triggered when the subtitle layer is shown or hidden: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('subtitle', (state) => { + console.log(state); +}); + +contextmenu event - triggered when the context menu is shown or hidden: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('contextmenu', (state) => { + console.log(state); +}); + +control event - triggered when the controls are shown or hidden: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('control', (state) => { + console.log(state); +}); + +setting event - triggered when the settings panel is shown or hidden: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, +}); + +art.on('setting', (state) => { + console.log(state); +}); + +muted event - triggered when the muted state changes: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('muted', (state) => { + console.log(state); +}); + +keydown event - listens for the keydown event from document: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('keydown', (event) => { + console.log(event.code); +}); + +Native video events (prefixed with 'video:'): + +video:canplay - The browser can start playing the media, but estimates that there isn't enough data to play through to the end without having to stop for further buffering. + +video:canplaythrough - The browser estimates it can play the media through to the end without having to stop for buffering. + +video:complete - OfflineAudioContext rendering is complete. + +video:durationchange - Triggered when the value of the duration property changes. + +video:emptied - The media has become empty; for example, this event is sent when the media has already been loaded (or partially loaded), and the load() method is called to reload it. + +video:ended - Playback has stopped because the media has reached its end point. + +video:error - An error occurred while fetching the media data, or the resource type is not a supported media format. + +video:loadeddata - The first frame of the media has finished loading. + +video:loadedmetadata - Metadata has been loaded. + +video:pause - Playback has been paused. + +video:play - Playback has begun. + +video:playing - Playback is ready to start after having been paused or delayed due to lack of data. + +video:progress - Fired periodically as the browser loads the resource. + +video:ratechange - The playback rate has changed. + +video:seeked - A seek operation has completed. + +video:seeking - A seek operation has begun. + +video:stalled - The user agent is trying to fetch media data, but data is unexpectedly not forthcoming. + +video:suspend - Media data loading has been suspended. + +video:timeupdate - The time indicated by the currentTime attribute has been updated. + +video:volumechange - The volume has changed. + +video:waiting - Playback has stopped because of a temporary lack of data. + +To help an AI model learn ArtPlayer effectively, the following documentation has been reorganized into a clean, plain text format. All code blocks, examples, and configuration options are preserved as-is, with minimal explanations added for clarity where helpful. + +Global Configuration Options + +ArtPlayer supports various global configuration options that can be set when initializing the player. These options control the player's behavior, appearance, and functionality. + +Example of basic player initialization with common options: + +var art = new ArtPlayer({ + container: '.artplayer-app', + url: 'path/to/video.mp4', + volume: 0.5, + isLive: false, + muted: false, + autoplay: false, + pip: true, + autoSize: true, + autoMini: true, + screenshot: true, + setting: true, + loop: false, + flip: true, + playbackRate: true, + aspectRatio: true, + fullscreen: true, + fullscreenWeb: true, + subtitleOffset: true, + miniProgressBar: true, + mutex: true, + backdrop: true, + playsInline: true, + autoPlayback: true, + airplay: true, + theme: '#ffad00', + lang: 'en', + moreVideoAttr: { + crossOrigin: 'anonymous', + }, + contextmenu: [ + { + html: 'Copy video url', + click: function (contextmenu) { + var url = art.option.url; + // Copy logic here + }, + }, + ], + controls: [ + { + position: 'right', + html: 'Control', + click: function () { + // Custom control action + }, + }, + ], + settings: [ + { + width: 200, + html: 'Setting', + tooltip: 'Setting Tooltip', + selector: [ + { + html: 'Setting Item', + tooltip: 'Setting Item Tooltip', + value: 'item1', + }, + ], + onSelect: function (item) { + // Handle setting selection + }, + }, + ], + layers: [ + { + html: 'Layer', + style: { + position: 'absolute', + top: '20px', + left: '20px', + }, + click: function () { + // Layer click action + }, + }, + ], +}); + +This example includes many common options. Each option is explained below for better understanding. + +Common Configuration Options Explained + +container: Specifies the DOM element where the player will be mounted. Can be a selector string or an HTMLElement. + +url: The source URL of the video to be played. + +volume: Initial volume level, ranging from 0 to 1. + +isLive: Boolean indicating if the video is a live stream. + +muted: Boolean to start the video with audio muted. + +autoplay: Boolean to attempt automatic playback (subject to browser policies). + +pip: Boolean to enable or disable picture-in-picture functionality. + +autoSize: Boolean to automatically adjust player size based on video dimensions. + +autoMini: Boolean to automatically minimize the player when scrolling out of view. + +screenshot: Boolean to enable screenshot capability. + +setting: Boolean to show or hide the settings menu. + +loop: Boolean to loop the video playback. + +flip: Boolean to enable video flipping controls. + +playbackRate: Boolean to show playback speed controls. + +aspectRatio: Boolean to enable aspect ratio adjustments. + +fullscreen: Boolean to enable fullscreen mode. + +fullscreenWeb: Boolean to enable web fullscreen mode. + +subtitleOffset: Boolean to allow subtitle timing adjustments. + +miniProgressBar: Boolean to show a mini progress bar in minimized mode. + +mutex: Boolean to automatically pause other players when this one plays. + +backdrop: Boolean to show a backdrop behind the player. + +playsInline: Boolean for inline playback on mobile devices. + +autoPlayback: Boolean to remember playback position and resume. + +airplay: Boolean to enable AirPlay support. + +theme: Sets the player's theme color using a CSS color value. + +lang: Sets the player's language (e.g., 'en', 'zh-cn'). + +moreVideoAttr: An object to set additional attributes on the video element, such as crossOrigin. + +contextmenu: An array to define custom right-click context menu items. + +controls: An array to add custom control buttons to the player interface. + +settings: An array to add custom items to the settings menu. + +layers: An array to add custom layers over the video, useful for overlays or custom UI elements. + +Each of these options can be customized to fit specific use cases, and the provided examples show how they can be structured in the configuration object. + +Global Properties + +These global properties refer to the top-level properties mounted on the constructor. All property names are in uppercase. These are subject to change in the future and are generally not used. + +DEBUG + +Whether to enable debug mode, which can print all built-in video events. Default is off. + +Artplayer.DEBUG = true; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +STYLE + +Returns the player's style text. + +console.log(Artplayer.STYLE); + +CONTEXTMENU + +Whether to enable the context menu. Default is on. + +Artplayer.CONTEXTMENU = false; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +NOTICE_TIME + +The display duration for notification messages, in milliseconds. Default is 2000. + +Artplayer.NOTICE_TIME = 5000; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +SETTING_WIDTH + +The default width of the settings panel, in pixels. Default is 250. + +Artplayer.SETTING_WIDTH = 300; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + loop: true, + flip: true, + playbackRate: true, + aspectRatio: true, +}); + +SETTING_ITEM_WIDTH + +The default width of settings items in the settings panel, in pixels. Default is 200. + +Artplayer.SETTING_ITEM_WIDTH = 300; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + loop: true, + flip: true, + playbackRate: true, + aspectRatio: true, +}); + +SETTING_ITEM_HEIGHT + +The default height of settings items in the settings panel, in pixels. Default is 35. + +Artplayer.SETTING_ITEM_HEIGHT = 40; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + loop: true, + flip: true, + playbackRate: true, + aspectRatio: true, +}); + +RESIZE_TIME + +The throttle time for resize events, in milliseconds. Default is 200. + +Artplayer.RESIZE_TIME = 500; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('resize', () => { + console.log('resize'); +}); + +SCROLL_TIME + +The throttle time for scroll events, in milliseconds. Default is 200. + +Artplayer.SCROLL_TIME = 500; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('scroll', () => { + console.log('scroll'); +}); + +SCROLL_GAP + +The boundary tolerance distance for view events, in pixels. Default is 50. + +Artplayer.SCROLL_GAP = 100; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('scroll', () => { + console.log('scroll'); +}); + +AUTO_PLAYBACK_MAX + +The maximum number of records for the auto-playback feature. Default is 10. + +Artplayer.AUTO_PLAYBACK_MAX = 20; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoPlayback: true, +}); + +AUTO_PLAYBACK_MIN + +The minimum record duration for the auto-playback feature, in seconds. Default is 5. + +Artplayer.AUTO_PLAYBACK_MIN = 10; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoPlayback: true, +}); + +AUTO_PLAYBACK_TIMEOUT + +The hide delay duration for the auto-playback feature, in milliseconds. Default is 3000. + +Artplayer.AUTO_PLAYBACK_TIMEOUT = 5000; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoPlayback: true, +}); + +RECONNECT_TIME_MAX + +The maximum number of automatic reconnection attempts when a connection error occurs. Default is 5. + +Artplayer.RECONNECT_TIME_MAX = 10; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/404.mp4', +}); + +RECONNECT_SLEEP_TIME + +The delay time for automatic reconnection when a connection error occurs, in milliseconds. Default is 1000. + +Artplayer.RECONNECT_SLEEP_TIME = 3000; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/404.mp4', +}); + +CONTROL_HIDE_TIME + +The delay time for auto-hiding the bottom control bar, in milliseconds. Default is 3000. + +Artplayer.CONTROL_HIDE_TIME = 5000; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +DBCLICK_TIME + +The delay time for double-click events, in milliseconds. Default is 300. + +Artplayer.DBCLICK_TIME = 500; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('dblclick', () => { + console.log('dblclick'); +}); + +DBCLICK_FULLSCREEN + +On desktop, whether to toggle fullscreen on double-click. Default is true. + +Artplayer.DBCLICK_FULLSCREEN = false; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +MOBILE_DBCLICK_PLAY + +On mobile, whether to toggle play/pause on double-click. Default is true. + +Artplayer.MOBILE_DBCLICK_PLAY = false; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +MOBILE_CLICK_PLAY + +On mobile, whether to toggle play/pause on single click. Default is false. + +Artplayer.MOBILE_CLICK_PLAY = true; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +AUTO_ORIENTATION_TIME + +On mobile, the delay time for automatic screen rotation, in milliseconds. Default is 200. + +Artplayer.AUTO_ORIENTATION_TIME = 500; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoOrientation: true, +}); + +INFO_LOOP_TIME + +The refresh interval for the information panel, in milliseconds. Default is 1000. + +Artplayer.INFO_LOOP_TIME = 2000; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.info.show = true; + +FAST_FORWARD_VALUE + +On mobile, the speed multiplier for fast-forward during long press. Default is 3. + +Artplayer.FAST_FORWARD_VALUE = 5; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fastForward: true, +}); + +FAST_FORWARD_TIME + +On mobile, the delay time for fast-forward during long press, in milliseconds. Default is 1000. + +Artplayer.FAST_FORWARD_TIME = 2000; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fastForward: true, +}); + +TOUCH_MOVE_RATIO + +On mobile, the speed multiplier for progress seeking during left/right swipe. Default is 0.5. + +Artplayer.TOUCH_MOVE_RATIO = 1; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +VOLUME_STEP + +The volume adjustment step for keyboard shortcuts. Default is 0.1. + +Artplayer.VOLUME_STEP = 0.2; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +SEEK_STEP + +The seek adjustment step for keyboard shortcuts, in seconds. Default is 5. + +Artplayer.SEEK_STEP = 10; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +PLAYBACK_RATE + +The built-in playback rate options list. Default is [0.5, 0.75, 1, 1.25, 1.5, 2]. + +Artplayer.PLAYBACK_RATE = [0.5, 1, 2, 3, 4, 5]; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + playbackRate: true, +}); + +art.contextmenu.show = true; +art.setting.show = true; + +ASPECT_RATIO + +The built-in video aspect ratio options list. Default is ['default', '4:3', '16:9']. + +Artplayer.ASPECT_RATIO = ['default', '1:1', '2:1', '4:3', '6:5']; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + aspectRatio: true, +}); + +art.contextmenu.show = true; +art.setting.show = true; + +FLIP + +List of built-in video flip options, defaults to ['normal', 'horizontal', 'vertical']. + +Artplayer.FLIP = ['normal', 'horizontal']; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + flip: true, +}); + +art.contextmenu.show = true; +art.setting.show = true; + +FULLSCREEN_WEB_IN_BODY + +Whether to mount the player under the body element during web fullscreen mode, defaults to true. + +Artplayer.FULLSCREEN_WEB_IN_BODY = false; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fullscreenWeb: true, +}); + +LOG_VERSION + +Sets whether to print the player version, defaults to true. + +Artplayer.LOG_VERSION = false; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +USE_RAF + +Sets whether to use requestAnimationFrame, defaults to false. Currently mainly used for smooth progress bar effects. + +Artplayer.USE_RAF = true; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + miniProgressBar: true, +}); + +Writing Plugins + +Once you are familiar with the player's properties, methods, and events, writing plugins becomes very straightforward. + +You can load plugin functions during instantiation: + +```js +function myPlugin(art) { + console.info(art); + return { + name: 'myPlugin', + something: 'something', + doSomething: function () { + console.info('doSomething'); + }, + }; +} + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [myPlugin], +}); + +art.on('ready', () => { + console.info(art.plugins.myPlugin); +}); +``` + +You can also load plugin functions after instantiation: + +```js +function myPlugin(art) { + console.info(art); + return { + name: 'myPlugin', + something: 'something', + doSomething: function () { + console.info('doSomething'); + }, + }; +} + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.plugins.add(myPlugin); + +art.on('ready', () => { + console.info(art.plugins.myPlugin); +}); +``` + +For example, here is a plugin that displays an image ad when the video is paused: + +```js +function adsPlugin(option) { + return (art) => { + art.layers.add({ + name: 'ads', + html: ``, + style: { + display: 'none', + position: 'absolute', + top: '20px', + right: '20px', + }, + }); + + function show() { + art.layers.ads.style.display = 'block'; + } + + function hide() { + art.layers.ads.style.display = 'none'; + } + + art.controls.add({ + name: 'hide-ads', + position: 'right', + html: 'Hide Ads', + tooltip: 'Hide Ads', + click: hide, + style: { + marginRight: '20px' + } + }); + + art.controls.add({ + name: 'show-ads', + position: 'right', + html: 'Show Ads', + tooltip: 'Show Ads', + click: show, + }); + + art.on('play', hide); + art.on('pause', show); + + return { + name: 'adsPlugin', + show, + hide + }; + } +} + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + adsPlugin({ + url: '/assets/sample/layer.png' + }) + ], +}); +``` + +Instance Properties + +Instance properties refer to the top-level properties mounted on the ArtPlayer instance that are commonly used. + +play +Type: Function +Play the video. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + muted: true, +}); + +art.on('ready', () => { + art.play(); +}); + +pause +Type: Function +Pause the video. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + muted: true, +}); + +art.on('ready', () => { + art.play(); + + setTimeout(() => { + art.pause(); + }, 3000); +}); + +toggle +Type: Function +Toggle between playing and pausing the video. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + muted: true, +}); + +art.on('ready', () => { + art.toggle(); + + setTimeout(() => { + art.toggle(); + }, 3000); +}); + +destroy +Type: Function +Parameter: Boolean +Destroy the player. Accepts a parameter indicating whether to also remove the player's html after destruction. Defaults to true. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.destroy(); +}); + +seek +Type: Setter +Parameter: Number +Seek to a specific time in the video, in seconds. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.seek = 5; +}); + +forward +Type: Setter +Parameter: Number +Fast forward the video by a specified number of seconds. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.forward = 5; +}); + +backward +Type: Setter +Parameter: Number +Rewind the video by a specified number of seconds. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.seek = 5; + + setTimeout(() => { + art.backward = 2; + }, 3000); +}); + +volume +Type: Setter/Getter +Parameter: Number +Set or get the video volume. Range: [0, 1]. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + console.info(art.volume); + art.volume = 0.5; + console.info(art.volume); +}); + +url +Type: Setter/Getter +Parameter: String +Set or get the video URL. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.url = '/assets/sample/video.mp4?t=0'; +}); + +switch +Type: Setter +Parameter: String +Set the video URL. Similar to art.url when setting, but performs some optimization operations. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.seek = 10; + setTimeout(() => { + art.switch = '/assets/sample/video.mp4?t=0'; + }, 3000); +}); + +switchUrl +Type: Function +Parameter: String +Set the video URL. Similar to art.url when setting, but performs some optimization operations. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.seek = 10; + setTimeout(() => { + art.switchUrl('/assets/sample/video.mp4?t=0'); + }, 3000); +}); + +Note: art.switch and art.switchUrl have the same functionality, but art.switchUrl returns a Promise. It resolves when the new URL is playable and rejects when the new URL fails to load. + +switchQuality +Type: Function +Parameter: String +Sets the video quality URL. Similar to art.switchUrl, but preserves the previous playback progress. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.seek = 10; + setTimeout(() => { + art.switchQuality('/assets/sample/video.mp4?t=0'); + }, 3000); +}); + +muted +Type: Setter/Getter +Parameter: Boolean +Sets and gets whether the video is muted. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + console.info(art.muted); + art.muted = true; + console.info(art.muted); +}); + +currentTime +Type: Setter/Getter +Parameter: Number +Sets and gets the current playback time of the video. Setting the time is similar to seek, but it does not trigger additional events. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + console.info(art.currentTime); + art.currentTime = 5; + console.info(art.currentTime); +}); + +duration +Type: Getter +Gets the duration of the video. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + console.info(art.duration); +}); + +Note: Some videos may not have a duration, such as live streams or videos that haven't finished decoding. In these cases, the obtained duration will be 0. + +screenshot +Type: Function +Downloads a screenshot of the current video frame. An optional parameter specifies the screenshot filename. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.screenshot('your-name'); +}); + +getDataURL +Type: Function +Gets the base64 URL of the screenshot for the current video frame. Returns a Promise. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', async () => { + const url = await art.getDataURL(); + console.info(url) +}); + +getBlobUrl +Type: Function +Gets the blob URL of the screenshot for the current video frame. Returns a Promise. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', async () => { + const url = await art.getBlobUrl(); + console.info(url); +}); + +fullscreen +Type: Setter/Getter +Parameter: Boolean +Sets and gets the fullscreen state of the player window. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + controls: [ + { + position: 'right', + html: 'Fullscreen Switch', + click: function () { + art.fullscreen = !art.fullscreen; + }, + }, + ], +}); + +Note: Due to browser security mechanisms, the page must have prior interaction (e.g., the user has clicked on the page) before triggering window fullscreen. + +fullscreenWeb +Type: Setter/Getter +Parameter: Boolean +Sets and gets the web fullscreen state of the player. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fullscreenWeb: true, +}); + +art.on('ready', () => { + art.fullscreenWeb = true; + + setTimeout(() => { + art.fullscreenWeb = false; + }, 3000); +}); + +pip +Type: Setter/Getter +Parameter: Boolean +Sets and gets the Picture-in-Picture mode of the player. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + controls: [ + { + position: 'right', + html: 'PIP', + click: function () { + art.pip = !art.pip; + }, + }, + ], +}); + +Note: Due to browser security mechanisms, the page must have prior user interaction (e.g., a user click) before Picture-in-Picture can be triggered. + +poster +Type: Setter/Getter +Parameter: String +Sets and gets the video poster. The poster is only visible before video playback starts. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + poster: '/assets/sample/poster.jpg', +}); + +art.on('ready', () => { + console.info(art.poster); + art.poster = '/assets/sample/poster.jpg?t=0'; + console.info(art.poster); +}); + +mini +Type: Setter/Getter +Parameter: Boolean +Sets and gets the player's mini mode. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.mini = true; +}); + +playing +Type: Getter +Parameter: Boolean +Gets whether the video is currently playing. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + muted: true, +}); + +art.on('ready', () => { + console.info(art.playing); +}); + +autoSize +Type: Function +Sets whether the video should automatically adjust its size. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.autoSize(); +}); + +rect +Type: Getter +Gets the player's dimensions and coordinate information. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + console.info(JSON.stringify(art.rect)); +}); + +Note: The dimension and coordinate information is obtained via getBoundingClientRect. + +flip +Type: Setter/Getter +Parameter: String +Sets and gets the player flip mode. Supports normal, horizontal, vertical. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + console.info(art.flip); + art.flip = 'horizontal'; + console.info(art.flip); +}); + +playbackRate +Type: Setter/Getter +Parameter: Number +Sets and gets the player's playback speed. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + console.info(art.playbackRate); + art.playbackRate = 2; + console.info(art.playbackRate); +}); + +aspectRatio +Type: Setter/Getter +Parameter: String +Sets and gets the player's aspect ratio. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + console.info(art.aspectRatio); + art.aspectRatio = '16:9'; + console.info(art.aspectRatio); +}); + +autoHeight +Type: Function +When the container only has a defined width, this property can automatically calculate and set the video's height. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.autoHeight(); +}); + +art.on('resize', () => { + art.autoHeight(); +}); + +Note: This property is useful when your container has a defined width but an unknown height, as it automatically calculates the video's height. However, you need to determine the appropriate timing to set this property. + +attr +Type: Function +Parameter: String +Dynamically gets and sets attributes of the video element. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + console.info(art.attr('playsInline')); + art.attr('playsInline', true); + console.info(art.attr('playsInline')); +}); + +type +Type: Setter/Getter +Parameter: String +Dynamically gets and sets the video type. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + console.info(art.type); + art.type = 'm3u8'; + console.info(art.type); +}); + +theme +Type: Setter/Getter +Parameter: String +Dynamically gets and sets the player's theme color. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + console.info(art.theme); + art.theme = '#000'; + console.info(art.theme); +}); + +airplay +Type: Function +Initiates AirPlay. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + controls: [ + { + position: 'right', + html: 'AirPlay', + click: function () { + art.airplay(); + }, + }, + ], +}); + +loaded +Type: Getter +The proportion of the video that has been buffered, ranging from [0, 1]. Often used with the video:timeupdate event. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('video:timeupdate', () => { + console.info(art.loaded); +}); + +played +Type: Getter +The proportion of the video that has been played, ranging from [0, 1]. Often used with the video:timeupdate event. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('video:timeupdate', () => { + console.info(art.played); +}); + +proxy +Type: Function +A proxy function for DOM events, essentially proxying addEventListener and removeEventListener. When using proxy to handle events, the event will be automatically removed when the player is destroyed. + +Example usage: +var container = document.querySelector('.artplayer-app'); + +var art = new Artplayer({ + container: container, + url: '/assets/sample/video.mp4', +}); + +art.proxy(container, 'click', event => { + console.info(event); +}); + +Note: If you need certain DOM events to exist only for the duration of the player's lifecycle, it is strongly recommended to use this function to avoid memory leaks. + +query +Type: Function +A DOM query function, similar to document.querySelector, but the search is scoped to within the current player, preventing errors from duplicate class names. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +console.info(art.query('.art-video')); + +video +Type: Element +Quickly returns the player's video element. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +console.info(art.video); + +cssVar +Type: Function +Dynamically gets or sets CSS variables. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + console.log(art.cssVar('--art-theme')); + art.cssVar('--art-theme', 'green'); + console.log(art.cssVar('--art-theme')); +}); + +quality +Type: Setter +Parameter: Array +Dynamically sets the list of available quality levels. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + quality: [ + { + default: true, + html: 'SD 480P', + url: '/assets/sample/video.mp4', + }, + { + html: 'HD 720P', + url: '/assets/sample/video.mp4', + }, + ], +}); + +art.on('ready', () => { + setTimeout(() => { + art.quality = [ + { + default: true, + html: '1080P', + url: '/assets/sample/video.mp4', + }, + { + html: '4K', + url: '/assets/sample/video.mp4', + }, + ]; + }, 3000); +}) + +thumbnails +Type: Setter/Getter +Parameter: Object +Dynamically sets the thumbnails. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.on('ready', () => { + art.thumbnails = { + url: '/assets/sample/thumbnails.png', + number: 60, + column: 10, + }; +}); + +subtitleOffset +Type: Setter/Getter +Parameter: Number +Dynamically set subtitle offset. + +Example usage: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + subtitle: { + url: '/assets/sample/subtitle.srt', + }, +}); + +art.on('ready', () => { + art.subtitleOffset = 1; +}); + +Context Menu + +Configuration + +Property: disable +Type: Boolean +Description: Whether to disable the component + +Property: name +Type: String +Description: Unique component name for CSS class + +Property: index +Type: Number +Description: Component index for display priority + +Property: html +Type: String, Element +Description: Component DOM element + +Property: style +Type: Object +Description: Component style object + +Property: click +Type: Function +Description: Component click event + +Property: mounted +Type: Function +Description: Triggered after component mount + +Property: tooltip +Type: String +Description: Component tooltip text + +Creation + +You can create context menu items during ArtPlayer initialization by including them in the contextmenu array. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + contextmenu: [ + { + name: 'your-menu', + html: 'Your Menu', + click: function (...args) { + console.info(args); + art.contextmenu.show = false; + }, + }, + ], +}); + +art.contextmenu.show = true; + +// Get the Element of contextmenu by name +console.info(art.contextmenu['your-menu']); + +Addition + +You can add context menu items after ArtPlayer initialization using the add method. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.contextmenu.add({ + name: 'your-menu', + html: 'Your Menu', + click: function (...args) { + console.info(args); + art.contextmenu.show = false; + }, +}); + +art.contextmenu.show = true; + +// Get the Element of contextmenu by name +console.info(art.contextmenu['your-menu']); + +Removal + +You can remove context menu items by name using the remove method. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + contextmenu: [ + { + name: 'your-menu', + html: 'Your Menu', + click: function (...args) { + console.info(args); + art.contextmenu.show = false; + }, + }, + ], +}); + +art.contextmenu.show = true; + +art.on('ready', () => { + setTimeout(() => { + // Delete the contextmenu by name + art.contextmenu.remove('your-menu') + }, 3000); +}); + +Update + +You can update existing context menu items by name using the update method. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + contextmenu: [ + { + name: 'your-menu', + html: 'Your Menu', + click: function (...args) { + console.info(args); + art.contextmenu.show = false; + }, + }, + ], +}); + +art.contextmenu.show = true; + +art.on('ready', () => { + setTimeout(() => { + // Update the contextmenu by name + art.contextmenu.update({ + name: 'your-menu', + html: 'Your New Menu', + }) + }, 3000); +}); + +Controllers + +Configuration + +Property: disable +Type: Boolean +Description: Whether to disable the component + +Property: name +Type: String +Description: Unique component name for CSS class identification + +Property: index +Type: Number +Description: Component index for display priority + +Property: html +Type: String, Element +Description: Component DOM element + +Property: style +Type: Object +Description: Component style object + +Property: click +Type: Function +Description: Component click event + +Property: mounted +Type: Function +Description: Triggered after component mounting + +Property: tooltip +Type: String +Description: Component tooltip text + +Property: position +Type: String +Description: left and right control controller placement + +Property: selector +Type: Array +Description: Array of selector list objects + +Property: onSelect +Type: Function +Description: Function triggered when selector item is clicked + +Creation + +Here is an example of creating controllers during ArtPlayer initialization: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + controls: [ + { + name: 'your-button', + index: 10, + position: 'left', + html: 'Your Button', + tooltip: 'Your Button', + style: { + color: 'red', + }, + click: function (...args) { + console.info('click', args); + }, + mounted: function (...args) { + console.info('mounted', args); + }, + }, + { + name: 'subtitle', + position: 'right', + html: 'Subtitle', + selector: [ + { + default: true, + html: 'subtitle 01', + }, + { + html: 'subtitle 02', + }, + ], + onSelect: function (item, $dom) { + console.info(item, $dom); + return 'Your ' + item.html; + }, + }, + ], +}); + +// Get the Element of control by name +console.info(art.controls['your-button']); +console.info(art.controls['subtitle']); + +Addition + +You can add controllers to an existing ArtPlayer instance using the add method: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.controls.add({ + name: 'button1', + index: 10, + position: 'left', + html: 'Your Button', + tooltip: 'Your Button', + style: { + color: 'red', + }, + click: function (...args) { + console.info('click', args); + }, + mounted: function (...args) { + console.info('mounted', args); + }, +}); + +// Get the Element of control by name +console.info(art.controls['button1']); + +Removal + +Controllers can be removed by name using the remove method. This example removes a controller after a 3-second delay: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + controls: [ + { + name: 'button1', + index: 10, + position: 'right', + html: 'Your Button', + tooltip: 'Your Button', + style: { + color: 'red', + }, + } + ] +}); + +art.on('ready', () => { + setTimeout(() => { + // Delete the control by name + art.controls.remove('button1'); + }, 3000); +}); + +Update + +Existing controllers can be updated with new properties. This example updates a controller after a 3-second delay: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + controls: [ + { + name: 'button1', + index: 10, + position: 'right', + html: 'Subtitle', + selector: [ + { + default: true, + html: 'subtitle 01', + }, + { + html: 'subtitle 02', + }, + ], + } + ] +}); + +art.on('ready', () => { + setTimeout(() => { + // Update the control by name + art.controls.update({ + name: 'button1', + index: 10, + position: 'right', + html: 'New Subtitle', + selector: [ + { + default: true, + html: 'new subtitle 01', + }, + { + html: 'new subtitle 02', + }, + ], + }); + }, 3000); +}); + +Business Layer + +Configuration + +The following table describes the configuration properties available for layers in ArtPlayer. + +Property: disable +Type: Boolean +Description: Whether to disable the component + +Property: name +Type: String +Description: Unique component name for class marking + +Property: index +Type: Number +Description: Component index for display priority + +Property: html +Type: String, Element +Description: Component DOM element + +Property: style +Type: Object +Description: Component style object + +Property: click +Type: Function +Description: Component click event + +Property: mounted +Type: Function +Description: Triggered after component mounting + +Property: tooltip +Type: String +Description: Component tooltip text + +Creation + +You can create layers during ArtPlayer initialization by including them in the layers array. Here is an example: + +var img = '/assets/sample/layer.png'; +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + layers: [ + { + name: 'potser', + html: ``, + tooltip: 'Potser Tip', + style: { + position: 'absolute', + top: '50px', + right: '50px', + }, + click: function (...args) { + console.info('click', args); + }, + mounted: function (...args) { + console.info('mounted', args); + }, + }, + ], +}); + +// Get the Element of layer by name +console.info(art.layers['potser']); + +Addition + +You can add layers to an existing ArtPlayer instance using the layers.add method. Here is an example: + +var img = '/assets/sample/layer.png'; +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +art.layers.add({ + name: 'potser', + html: ``, + tooltip: 'Potser Tip', + style: { + position: 'absolute', + top: '50px', + right: '50px', + }, + click: function (...args) { + console.info('click', args); + }, + mounted: function (...args) { + console.info('mounted', args); + }, +}); + +// Get the Element of layer by name +console.info(art.layers['potser']); + +Removal + +You can remove layers by name using the layers.remove method. This example shows removing a layer after a delay: + +var img = '/assets/sample/layer.png'; +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + layers: [ + { + name: 'potser', + html: ``, + style: { + position: 'absolute', + top: '50px', + right: '50px', + }, + }, + ], +}); + +art.on('ready', () => { + setTimeout(() => { + // Delete the layer by name + art.layers.remove('potser'); + }, 3000); +}); + +Update + +You can update existing layers by name using the layers.update method. This example shows updating a layer's properties after a delay: + +var img = '/assets/sample/layer.png'; +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + layers: [ + { + name: 'potser', + html: ``, + style: { + position: 'absolute', + top: '50px', + right: '50px', + }, + }, + ], +}); + +art.on('ready', () => { + setTimeout(() => { + // Update the layer by name + art.layers.update({ + name: 'potser', + html: ``, + style: { + position: 'absolute', + top: '50px', + left: '50px', + }, + }); + }, 3000); +}); + +Settings Panel + +Built-in Settings + +To use the settings panel, first enable it by setting 'setting: true'. The panel includes four built-in items: flip, playbackRate, aspectRatio, and subtitleOffset. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + flip: true, + playbackRate: true, + aspectRatio: true, + subtitleOffset: true, +}); + +Creating a Button + +Properties for button creation: +html: String or Element - The DOM element +icon: String or Element - The icon element +onClick: Function - Click event handler +width: Number - List width +tooltip: String - Tooltip text + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + settings: [ + { + html: 'Button', + icon: '', + tooltip: 'tooltip', + onClick(item, $dom, event) { + console.info(item, $dom, event); + return 'new tooltip' + } + }, + ], +}); + +Creating a Selector List + +Properties for selector list: +html: String or Element - The DOM element +icon: String or Element - The icon element +selector: Array - List of options +onSelect: Function - Selection event handler +width: Number - List width +tooltip: String - Tooltip text + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + settings: [ + { + html: 'Subtitle', + width: 250, + tooltip: 'Subtitle 01', + selector: [ + { + default: true, + html: 'Subtitle 01', + url: '/assets/sample/subtitle.srt?id=1', + }, + { + html: 'Subtitle 02', + url: '/assets/sample/subtitle.srt?id=2', + }, + ], + onSelect: function (item, $dom, event) { + console.info(item, $dom, event); + art.subtitle.url = item.url; + return item.html; + }, + }, + { + html: 'Quality', + width: 150, + tooltip: '1080P', + selector: [ + { + default: true, + html: '1080P', + url: '/assets/sample/video.mp4?id=1080', + }, + { + html: '720P', + url: '/assets/sample/video.mp4?id=720', + }, + { + html: '360P', + url: '/assets/sample/video.mp4?id=360', + }, + ], + onSelect: function (item, $dom, event) { + console.info(item, $dom, event); + art.switchQuality(item.url, item.html); + return item.html; + }, + }, + ], +}); + +Creating a Nested List + +This example shows how to create multi-level nested settings. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + settings: [ + { + html: 'Multi-level', + selector: [ + { + html: 'Setting 01', + width: 150, + selector: [ + { + html: 'Setting 01 - 01', + }, + { + html: 'Setting 01 - 02', + }, + ], + onSelect: function (item, $dom, event) { + console.info(item, $dom, event); + return item.html; + }, + }, + { + html: 'Setting 02', + width: 150, + selector: [ + { + html: 'Setting 02 - 01', + }, + { + html: 'Setting 02 - 02', + }, + ], + onSelect: function (item, $dom, event) { + console.info(item, $dom, event); + return item.html; + }, + }, + ], + }, + ], +}); + +Creating a Toggle Button + +Properties for toggle button: +html: String or Element - DOM element of the item +icon: String or Element - Icon of the item +switch: Boolean - Default state of the button +onSwitch: Function - Button toggle event handler +tooltip: String - Tooltip text + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + settings: [ + { + html: 'PIP Mode', + tooltip: 'Close', + icon: '', + switch: false, + onSwitch: function (item, $dom, event) { + console.info(item, $dom, event); + const nextState = !item.switch; + art.pip = nextState; + item.tooltip = nextState ? 'Open' : 'Close'; + return nextState; + }, + }, + ], +}); + +Creating a Range Slider + +Properties for range slider: +html: String or Element - DOM element of the item +icon: String or Element - Icon of the item +range: Array - Default state array [value, min, max, step] +onRange: Function - Event triggered on completion +onChange: Function - Event triggered on change +tooltip: String - Tooltip text + +The range array format: [value, min, max, step] +For example: [5, 1, 10, 1] means value=5, min=1, max=10, step=1 + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + settings: [ + { + html: 'Slider', + tooltip: '5x', + icon: '', + range: [5, 1, 10, 1], + onChange: function (item, $dom, event) { + console.info(item, $dom, event); + return item.range[0] + 'x'; + }, + }, + ], +}); + +Adding Settings Dynamically + +You can add settings after initialization using the add method. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, +}); + +art.setting.show = true; + +art.setting.add({ + html: 'Slider', + tooltip: '5x', + icon: '', + range: [5, 1, 10, 1], +}); + +Removing Settings + +Settings can be removed by their name using the remove method. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + flip: true, + settings: [ + { + name: 'slider', + html: 'Slider', + tooltip: '5x', + icon: '', + range: [5, 1, 10, 1], + }, + ], +}); + +art.setting.show = true; + +art.on('ready', () => { + setTimeout(() => { + // Delete the setting by name + art.setting.remove('slider'); + }, 3000); +}); + +Updating Settings + +Existing settings can be updated using the update method with the setting name. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + settings: [ + { + name: 'slider', + html: 'Slider', + tooltip: '5x', + icon: '', + range: [5, 1, 10, 1], + }, + ], +}); + +art.setting.show = true; + +art.on('ready', () => { + setTimeout(() => { + // Update the setting by name + art.setting.update({ + name: 'slider', + html: 'PIP Mode', + tooltip: 'Close', + icon: '', + switch: false, + }); + }, 3000); +}); + +To help an AI model learn ArtPlayer, here is the reorganized documentation in a clean, plain text format. + +ArtPlayer Documentation + +Introduction + +ArtPlayer is a modern, feature-rich HTML5 video player with a highly customizable UI. It supports a wide range of video formats and offers extensive configuration options for developers. + +Installation + +You can install ArtPlayer via npm or include it directly from a CDN. + +Using npm: +npm install artplayer + +Using CDN: + + + +Basic Usage + +Here is a simple example to get started with ArtPlayer. Create a container element in your HTML and initialize the player with JavaScript. + +HTML: +
+ +JavaScript: +var art = new Artplayer({ + container: '#art-player', + url: 'path/to/video.mp4', +}); + +Configuration Options + +ArtPlayer offers various configuration options to customize the player's behavior and appearance. Below are some commonly used options. + +Option: url +Description: Specifies the video source URL. +Example: +url: 'https://example.com/sample-video.mp4' + +Option: volume +Description: Sets the initial volume level, from 0 to 1. +Example: +volume: 0.8 + +Option: autoplay +Description: Enables or disables autoplay. +Example: +autoplay: true + +Option: pip +Description: Enables or disables picture-in-picture mode. +Example: +pip: true + +Option: screenshot +Description: Enables or disables screenshot functionality. +Example: +screenshot: true + +Option: theme +Description: Sets the player's theme color. +Example: +theme: '#ffad00' + +Option: hotkey +Description: Enables or disables keyboard shortcuts. +Example: +hotkey: true + +Option: fullscreen +Description: Enables or disables fullscreen mode. +Example: +fullscreen: true + +Option: subtitle +Description: Configures subtitle settings. +Example: +subtitle: { + url: 'path/to/subtitle.vtt', + style: { + color: '#fff', + }, +} + +Option: moreVideoAttr +Description: Sets additional video attributes. +Example: +moreVideoAttr: { + crossOrigin: 'anonymous', +} + +Events + +ArtPlayer provides events to handle various player interactions. You can use these to execute code in response to player actions. + +Example: Ready event +Description: Triggered when the player is ready. +Code: +art.on('ready', () => { + console.log('Player is ready'); +}); + +Example: Play event +Description: Triggered when the video starts playing. +Code: +art.on('play', () => { + console.log('Video is playing'); +}); + +Example: Pause event +Description: Triggered when the video is paused. +Code: +art.on('pause', () => { + console.log('Video is paused'); +}); + +Example: Destroy event +Description: Triggered when the player is destroyed. +Code: +art.on('destroy', () => { + console.log('Player is destroyed'); +}); + +Methods + +ArtPlayer includes methods to control the player programmatically. Here are some essential methods. + +Method: play +Description: Starts video playback. +Example: +art.play(); + +Method: pause +Description: Pauses the video. +Example: +art.pause(); + +Method: destroy +Description: Destroys the player instance and cleans up resources. +Example: +art.destroy(); + +Components + +You can customize the player by adding or modifying components. Below is an example of adding a custom control. + +Example: Adding a custom control +Description: Adds a button to toggle playback speed. +Code: +art.controls.add({ + name: 'speed', + position: 'right', + html: 'Speed', + click: function () { + const speeds = [1, 1.5, 2]; + const current = art.playbackRate; + const index = speeds.indexOf(current); + const next = speeds[(index + 1) % speeds.length]; + art.playbackRate = next; + }, +}); + +This documentation covers the basics of ArtPlayer. For more advanced features and detailed API references, please refer to the official documentation. + +Installation + +You can install ArtPlayer using various package managers or include it directly via script tag. + +Using npm: +npm install artplayer + +Using yarn: +yarn add artplayer + +Using pnpm: +pnpm add artplayer + +Using script tag: + + +CDN + +You can also use CDN links for quick setup. + +From jsdelivr.net: +https://cdn.jsdelivr.net/npm/artplayer/dist/artplayer.js + +From unpkg.com: +https://unpkg.com/artplayer/dist/artplayer.js + +Usage + +Here is a basic HTML example to get started with ArtPlayer. + + + + ArtPlayer Demo + + + + +
+ + + + + +Note: The player's dimensions depend on the size of its container, so your container must have defined dimensions. + +For more usage examples, visit: +/packages/artplayer-template + +Vue.js + +Here is how to integrate ArtPlayer with Vue.js. + +Artplayer.vue component: + + + + + +app.vue usage: + + + + + +Important note: Artplayer is not reactive. Directly modifying option in Vue.js will not update the player. + +React.js + +Here is how to integrate ArtPlayer with React.js. + +Artplayer.jsx component: + +import Artplayer from 'artplayer' +import { useEffect, useRef } from 'react' + +export default function Player({ option, getInstance, ...rest }) { + const $container = useRef() + + useEffect(() => { + const art = new Artplayer({ + ...option, + container: $container.current, + }) + + if (typeof getInstance === 'function') { + getInstance(art) + } + + return () => art.destroy(false) + }, []) + + return
+} + +app.jsx usage: + +import Artplayer from './Artplayer.jsx' + +function App() { + return ( +
+ console.log(art)} + /> +
+ ) +} + +export default App + +Important note: Artplayer is not reactive. Directly modifying option in React.js will not update the player. + +TypeScript + +ArtPlayer includes TypeScript definitions that are automatically imported. + +Vue.js with TypeScript: + + + +React.js with TypeScript: + +import Artplayer from 'artplayer'; +const art = useRef(null); +art.current = new Artplayer(); + +Using the Option type: + +import Artplayer, { type Option } from 'artplayer'; + +const option: Option = { + container: '.artplayer-app', + url: './assets/sample/video.mp4', +}; + +option.volume = 0.5; + +const art = new Artplayer(option); + +For all TypeScript definitions, visit: +packages/artplayer/types + +JavaScript + +If you lose TypeScript type hints in JavaScript files, you can manually import types using JSDoc comments. + +For variables: + +/** + * @type {import("artplayer")} + */ +let art = null; + +For parameters: + +/** + * @param {import("artplayer")} art + */ +function getInstance(art) { + // +} + +For properties: + +export default { + data() { + return { + /** + * @type {import("artplayer")} + */ + art: null, + } + } +} + +For options: + +/** + * @type {import("artplayer/types/option").Option} + */ + +const option = { + container: '.artplayer-app', + url: './assets/sample/video.mp4', +}; + +option.volume = 0.5; + +const art8 = new Artplayer(option); + +Legacy Browsers + +The standard build supports the latest Chrome version. For legacy browser support, use the legacy version. + +Import legacy version: +import Artplayer from 'artplayer/legacy' + +CDN links for legacy version: + +From jsdelivr.net: +https://cdn.jsdelivr.net/npm/artplayer/dist/artplayer.legacy.js + +From unpkg.com: +https://unpkg.com/artplayer/dist/artplayer.legacy.js + +If you need to support even older browsers, modify the build configuration and build it yourself. + +Build Configuration: scripts/build.js +Reference Documentation: browserslist + +ECMAScript Module + +Starting from version 5.2.6, ArtPlayer provides ESM versions. + +ESM Demo: +https://artplayer.org/esm.html + +Example using ESM with import maps: + + + + + + + ArtPlayer ESM with Import Map + + + + + +
+ + + + + +Custom userAgent + +To adjust player UI by changing userAgent, set the global variable before importing ArtPlayer. + + + + ArtPlayer Demo + + + + +
+ + + + + + +Note: You need to modify it before importing the ArtPlayer dependency for it to take effect. + +Here is the reorganized documentation for ArtPlayer in a clean, plain text format. + +ArtPlayer Danmuku Documentation + +The danmuku feature in ArtPlayer allows you to display comments or messages over the video player. Below are the configuration options and examples for setting up danmuku. + +Danmuku Configuration Options + +You can configure danmuku by passing an object with the following properties: + +- comments: An array of comment objects to be displayed. Each object should have: + - text: The comment text string. + - time: The time in seconds when the comment should appear. + - color: Optional text color for the comment. + - border: Optional, set to true to display a border around the comment. + - mode: Optional, can be 'scroll' for scrolling comments or 'top'/'bottom' for static positions. + +- speed: The scrolling speed for comments in pixels per second. Default is 5. + +- opacity: The opacity of the comments, from 0 (transparent) to 1 (opaque). Default is 1. + +- area: The percentage of the screen height that danmuku can occupy. Default is 0.25. + +- maximum: The maximum number of comments displayed at once. Default is 50. + +- margin: An array [top, right] specifying margins in pixels from the edges. Default is [10, 10]. + +- theme: The color theme for comments; can be 'light' or 'dark'. Default is 'dark'. + +Example Configuration + +Here is an example of how to set up danmuku in ArtPlayer: + +var art = new ArtPlayer({ + container: '.artplayer-app', + url: 'path/to/video.mp4', + danmuku: { + comments: [ + { + text: 'Hello, world!', + time: 5, + color: '#ff0000', + mode: 'scroll', + }, + { + text: 'This is a top comment', + time: 10, + mode: 'top', + border: true, + }, + ], + speed: 8, + opacity: 0.8, + area: 0.3, + maximum: 100, + margin: [20, 100], + theme: 'light', + }, +}); + +Code Example for Adding Comments Dynamically + +You can add comments dynamically after initialization using the following method: + +art.danmuku.emit({ + text: 'New comment added!', + time: 15, + color: '#00ff00', + mode: 'scroll', +}); + +This will add a new comment that appears at 15 seconds in the video. + +Notes on Usage + +- Ensure the danmuku feature is enabled in your ArtPlayer instance. +- Comments with the same time may overlap; adjust the maximum and area settings to manage density. +- The theme setting affects default colors if not specified in individual comments. + +For more details, refer to the official ArtPlayer documentation. + +Danmaku Library Documentation + +Demo +View the full demo at https://artplayer.org/?libs=./uncompiled/artplayer-plugin-danmuku/index.js&example=danmuku + +Installation +You can install using various package managers: + +npm install artplayer-plugin-danmuku + +yarn add artplayer-plugin-danmuku + +pnpm add artplayer-plugin-danmuku + +Or include via script tag: + + +CDN +Available through these CDN providers: + +https://cdn.jsdelivr.net/npm/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js + +https://unpkg.com/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js + +Danmaku Structure +Each danmaku is represented as an object, and multiple danmaku objects form a danmaku library. Only the text field is required to send a danmaku, while other parameters are optional. + +{ + text: '', // Danmaku text + time: 10, // Danmaku timestamp, defaults to current player time + mode: 0, // Danmaku mode: 0: scroll (default), 1: top, 2: bottom + color: '#FFFFFF', // Danmaku color, defaults to white + border: false, // Whether the danmaku has a border, defaults to false + style: {}, // Custom danmaku styles, defaults to an empty object +} + +All Options +Only danmuku is a required parameter; all others are optional. + +{ + danmuku: [], // Danmaku data + speed: 5, // Danmaku duration, range [1 ~ 10] + margin: [10, '25%'], // Danmaku top and bottom margins, supports pixel values and percentages + opacity: 1, // Danmaku opacity, range [0 ~ 1] + color: '#FFFFFF', // Default danmaku color, can be overridden by individual danmaku items + mode: 0, // Default danmaku mode: 0: scroll, 1: top, 2: bottom + modes: [0, 1, 2], // Visible danmaku modes + fontSize: 25, // Danmaku font size, supports pixel values and percentages + antiOverlap: true, // Whether to prevent danmaku overlap + synchronousPlayback: false, // Whether to synchronize playback speed + mount: undefined, // Danmaku emitter mount point, defaults to the middle of the player control bar + heatmap: false, // Whether to enable the heatmap + width: 512, // When the player width is less than this value, the danmaku emitter is placed at the bottom of the player + points: [], // Heatmap data + filter: () => true, // Filter before danmaku loading, only supports boolean return values + beforeEmit: () => true, // Filter before danmaku emission, supports Promise return + beforeVisible: () => true, // Filter before danmaku display, supports Promise return + visible: true, // Whether the danmaku layer is visible + emitter: true, // Whether to enable the danmaku emitter + maxLength: 200, // Maximum input length for the danmaku input box, range [1 ~ 1000] + lockTime: 5, // Input box lock time, range [1 ~ 60] + theme: 'dark', // Danmaku theme, supports 'dark' and 'light', only effective when custom mounted + OPACITY: {}, // Opacity configuration + FONT_SIZE: {}, // Font size configuration + MARGIN: {}, // Display area configuration + SPEED: {}, // Danmaku speed configuration + COLOR: [], // Color list configuration +} + +Lifecycle +For user-input danmaku: beforeEmit -> filter -> beforeVisible -> artplayerPluginDanmuku:visible +For server-side danmaku: filter -> beforeVisible -> artplayerPluginDanmuku:visible + +Example showing lifecycle hooks and event handling: + +// Save to database +function saveDanmu(danmu) { + return new Promise(resolve => { + setTimeout(() => { + resolve(true); + }, 1000); + }) +} + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + + // This function is triggered when the user enters danmaku text in the input box and clicks the send button + // You can perform validation on the danmaku or save it to the database + // The danmaku is added to the queue only when true is returned + async beforeEmit(danmu) { + const isDirty = (/fuck/i).test(danmu.text); + if (isDirty) return false; + const state = await saveDanmu(danmu); + return state; + }, + + // This is a filter for all danmaku, including those from the server and user input + // You can perform validation on the danmaku + // The danmaku is added to the queue only when true is returned + filter(danmu) { + return danmu.text.length <= 200; + }, + + // This function is triggered when the danmaku is about to be displayed + // You can perform validation on the danmaku + // The danmaku is sent to the player only when true is returned + async beforeVisible(danmu) { + return true; + }, + }), + ], +}); + +// The danmaku has appeared in the player, and you can access its DOM element +art.on('artplayerPluginDanmuku:visible', danmu => { + danmu.$ref.innerHTML = 'ଘ(੭ˊᵕˋ)੭: ' + danmu.$ref.innerHTML; +}) + +Using Danmaku Array +Example of using an array of danmaku objects: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: [ + { + text: 'Using array', + time: 1 + }, + ], + }), + ], +}); + +Using Danmaku XML +The danmaku XML file follows the same format as Bilibili's danmaku system: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + ], +}); + +Using Asynchronous Returns +Example of using asynchronous danmaku data loading: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: function () { + return new Promise((resovle) => { + return resovle([ + { + text: 'Using Promise for asynchronous return', + time: 1 + }, + ]); + }); + }, + }), + ], +}); + +hide/show Methods +Use the hide and show methods to hide or display danmaku: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + ], + controls: [ + { + position: 'right', + html: 'Hide Danmaku', + click: function () { + art.plugins.artplayerPluginDanmuku.hide(); + }, + }, + { + position: 'right', + html: 'Show Danmaku', + click: function () { + art.plugins.artplayerPluginDanmuku.show(); + }, + }, + ], +}); + +isHide Property +Use the isHide property to determine if danmaku is currently hidden or displayed: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + ], + controls: [ + { + position: 'right', + html: 'Hide Danmaku', + click: function (_, event) { + if (art.plugins.artplayerPluginDanmuku.isHide) { + art.plugins.artplayerPluginDanmuku.show(); + event.target.innerText = 'Hide Danmaku'; + } else { + art.plugins.artplayerPluginDanmuku.hide(); + event.target.innerText = 'Show Danmaku'; + } + }, + }, + ], +}); + +emit Method +Use the emit method to send a real-time danmaku: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + ], + controls: [ + { + position: 'right', + html: 'Send Danmaku', + click: function () { + var text = prompt('Please enter danmaku text', 'Danmaku test text'); + if (!text || !text.trim()) return; + var color = '#' + Math.floor(Math.random() * 0xffffff).toString(16); + art.plugins.artplayerPluginDanmuku.emit({ + text: text, + color: color, + border: true, + }); + }, + }, + ], +}); + +config Method +Use the config method to dynamically change danmaku settings: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + ], + controls: [ + { + position: 'right', + html: 'Danmaku Size:', + style: { + display: 'flex', + alignItems: 'center', + }, + mounted: function ($setting) { + const $range = $setting.querySelector('input[type=range]'); + $range.addEventListener('change', () => { + art.plugins.artplayerPluginDanmuku.config({ + fontSize: Number($range.value), + }); + }); + }, + }, + ], +}); + +load Method +The load method can be used to reload the current danmaku library, switch to a new danmaku library, or append a new danmaku library. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + emitter: false, + }), + ], + controls: [ + { + position: 'right', + html: 'Reload', + click: function () { + // Reload the current danmaku library + art.plugins.artplayerPluginDanmuku.load(); + }, + }, + { + position: 'right', + html: 'Switch', + click: function () { + // Switch to a new danmaku library + art.plugins.artplayerPluginDanmuku.config({ + danmuku: '/assets/sample/danmuku-v2.xml', + }); + art.plugins.artplayerPluginDanmuku.load(); + }, + }, + { + position: 'right', + html: 'Append', + click: function () { + // Append a new danmaku library (parameter type is the same as option.danmuku) + const target = '/assets/sample/danmuku.xml' + art.plugins.artplayerPluginDanmuku.load(target); + }, + }, + ], +}); + +reset Method +Used to clear the currently displayed danmaku. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + ], +}); + +art.on('resize', () => { + art.plugins.artplayerPluginDanmuku.reset(); +}); + +mount Method +When initializing the danmaku plugin, you can specify the mount position for the danmaku emitter. By default, it is mounted in the center of the control bar. You can also mount it outside the player. + +When the player enters fullscreen mode, the emitter will automatically return to the center of the control bar. If the mounted location has a light background, it is recommended to set theme to light to ensure visibility. + +var $danmu = document.querySelector('.artplayer-app'); + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fullscreenWeb: true, + plugins: [ + artplayerPluginDanmuku({ + mount: $danmu, + theme: 'dark', + danmuku: '/assets/sample/danmuku.xml', + }), + ], +}); + +// Can also be mounted manually +// art.plugins.artplayerPluginDanmuku.mount($danmu); + +option Property +Used to get the current danmaku configuration. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + ], +}); + +art.on('ready', () => { + console.info(art.plugins.artplayerPluginDanmuku.option); +}); + +Events +Available events for monitoring danmaku activities: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + ], +}); + +art.on('artplayerPluginDanmuku:visible', (danmu) => { + console.info('Danmaku visible', danmu); +}); + +art.on('artplayerPluginDanmuku:loaded', (danmus) => { + console.info('Danmaku loaded', danmus.length); +}); + +art.on('artplayerPluginDanmuku:error', (error) => { + console.info('Load error', error); +}); + +art.on('artplayerPluginDanmuku:config', (option) => { + console.info('Configuration changed', option); +}); + +art.on('artplayerPluginDanmuku:stop', () => { + console.info('Danmaku stopped'); +}); + +art.on('artplayerPluginDanmuku:start', () => { + console.info('Danmaku started'); +}); + +art.on('artplayerPluginDanmuku:hide', () => { + console.info('Danmaku hidden'); +}); + +art.on('artplayerPluginDanmuku:show', () => { + console.info('Danmaku shown'); +}); + +art.on('artplayerPluginDanmuku:reset', () => { + console.info('Danmaku reset'); +}); + +art.on('artplayerPluginDanmuku:destroy', () => { + console.info('Danmaku destroyed'); +}); + +Language Settings + +Important note: Due to the increasing number of bundled multi-language packs, starting from version 5.1.0, the artplayer.js core code only includes Simplified Chinese and English by default. Other languages are no longer bundled and must be manually imported as needed. + +Additional note: When a language cannot be matched, English will be displayed by default. For i18n syntax reference, see: artplayer/types/i18n.d.ts + +Default Languages + +The default languages are: en, zh-cn, no manual import required + +Example configuration using default languages: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + lang: 'zh-cn', // or 'en' +}); + +Importing Languages + +Language files before bundling are located at: artplayer/src/i18n/*.js +Language files after bundling are located at: artplayer/dist/i18n/*.js +Contributions to add your language are welcome. + +Import method using ES modules: +import id from 'artplayer/i18n/id'; +import zhTw from 'artplayer/i18n/zh-tw'; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + i18n: { + id: id, + 'zh-tw': zhTw, + }, + lang: 'zh-tw', +}); + +Script tag method for browser usage: + + + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + i18n: { + id: window['artplayer-i18n-id'], + 'zh-tw': window['artplayer-i18n-zh-tw'], + }, + lang: 'zh-tw', +}); + +Adding a New Language + +You can add a custom language by defining it directly in the configuration: +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + lang: 'your-lang', + i18n: { + 'your-lang': { + Play: 'Your Play' + }, + }, +}); + +Modifying Languages + +You can modify existing languages by extending or overriding their definitions: +import zhTw from 'artplayer/i18n/zh-tw'; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + i18n: { + // Change the default language + 'zh-cn': { + Play: 'Your Play' + }, + // Change the imported language + 'zh-tw': { + ...zhTw, + Play: 'Your Play' + }, + }, +}); + +Basic Options + +container +Type: String, Element +Default: #artplayer +The DOM container where the player is mounted. + +You may need to initialize the size of the container element, for example: + +.artplayer-app { + width: 400px; + height: 300px; +} + +Or use aspect-ratio: + +.artplayer-app { + aspect-ratio: 16/9; +} + +Note: Among all options, only container is required. + +url +Type: String +Default: '' +The video source URL. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +Sometimes the url is not known immediately; in such cases, you can set the url asynchronously. + +var art = new Artplayer({ + container: '.artplayer-app', +}); + +setTimeout(() => { + art.url = '/assets/sample/video.mp4'; +}, 1000); + +Note: By default, three video file formats are supported: .mp4, .ogg, .webm. To play other formats like .m3u8 or .flv, please refer to the Third-party Libraries section. + +id +Type: String +Default: '' +The unique identifier for the player, currently only used for playback resumption (autoplayback). + +var art = new Artplayer({ + id: 'your-url-id', + container: '.artplayer-app', + url: '/assets/sample/video.mp4', +}); + +onReady +Type: Function +Default: undefined +The constructor accepts a function as the second argument, which is triggered when the player is successfully initialized and the video is ready to play, similar to the ready event. + +var art = new Artplayer( + { + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + muted: true, + }, + function onReady(art) { + this.play() + }, +); + +Equivalent to: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + muted: true, +}); + +art.on('ready', () => { + art.play(); +}); + +Note: Inside the callback function, this refers to the player instance. However, if an arrow function is used for the callback, this will not point to the player instance. + +poster +Type: String +Default: '' +The video poster image, which only appears when the player is initialized and not yet playing. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + poster: '/assets/sample/poster.jpg', +}); + +theme +Type: String +Default: #f00 +The player's theme color, currently used for the progress bar and highlighted elements. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + theme: '#ffad00', +}); + +volume +Type: Number +Default: 0.7 +The default volume of the player. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + volume: 0.5, +}); + +Note: The player caches the last volume level; upon next initialization (e.g., page refresh), the player will read this cached value. + +isLive +Type: Boolean +Default: false +Enable live streaming mode, which hides the progress bar and playback time. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + isLive: true, +}); + +muted +Type: Boolean +Default: false +Whether to default to muted. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + muted: true, +}); + +autoplay +Type: Boolean +Default: false +Whether to autoplay. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoplay: true, + muted: true, +}); + +Note: If you want the video to autoplay when entering the page by default, muted must be set to true. For more information, please read Autoplay Policy Changes. + +autoSize +Type: Boolean +Default: false +By default, the player's dimensions fill the entire container, which often results in black bars. This option automatically adjusts the player size to hide black bars, similar to object-fit: cover; in CSS. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoSize: true, +}); + +autoMini +Type: Boolean +Default: false +Automatically switches to mini player mode when the player scrolls outside the browser viewport. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoMini: true, +}); + +loop +Type: Boolean +Default: false +Whether to enable video looping. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + loop: true, +}); + +flip +Type: Boolean +Default: false +Whether to display the video flip functionality. Currently appears in the Settings Panel and Context Menu. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + flip: true, + setting: true, +}); + +playbackRate +Type: Boolean +Default: false +Whether to display the playback rate functionality. Appears in the Settings Panel and Context Menu. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + playbackRate: true, + setting: true, +}); + +aspectRatio +Type: Boolean +Default: false +Whether to display the aspect ratio functionality. Appears in the Settings Panel and Context Menu. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + aspectRatio: true, + setting: true, +}); + +screenshot +Type: Boolean +Default: false +Whether to display the Screenshot button in the bottom control bar. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + screenshot: true, +}); + +Note: Due to browser security mechanisms, screenshot capture may fail if the video source is cross-origin with the website. + +setting +Type: Boolean +Default: false +Whether to display the Settings Panel toggle button in the bottom control bar. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, +}); + +hotkey +Type: Boolean +Default: true +Whether to enable hotkeys. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + hotkey: true, +}); + +Hotkey Description +↑ Increase volume +↓ Decrease volume +← Seek backward +→ Seek forward +space Toggle play/pause + +Note: These hotkeys only take effect after the player gains focus (e.g., by clicking on the player). + +pip +Type: Boolean +Default: false +Whether to display the Picture-in-Picture toggle button in the bottom control bar. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + pip: true, +}); + +mutex +Type: Boolean +Default: true +When multiple players exist on the page, whether only one player is allowed to play at a time. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + mutex: true, +}); + +fullscreen +Type: Boolean +Default: false +Whether to display the Fullscreen button in the bottom control bar. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fullscreen: true, +}); + +fullscreenWeb +Type: Boolean +Default: false +Whether to display the Webpage Fullscreen button in the bottom control bar. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fullscreenWeb: true, +}); + +subtitleOffset +Type: Boolean +Default: false +Subtitle time offset, ranging from [-5s, 5s], appears in the Settings Panel. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + subtitleOffset: true, + subtitle: { + url: '/assets/sample/subtitle.srt', + }, + setting: true, +}); + +miniProgressBar +Type: Boolean +Default: false +Mini progress bar, appears only when the player loses focus and is playing. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + miniProgressBar: true, +}); + +useSSR +Type: Boolean +Default: false +Whether to use SSR mount mode. Useful if you want to pre-render the player's required HTML before the player is mounted. You can access the player's required HTML via Artplayer.html. + +var $container = document.querySelector('.artplayer-app'); +$container.innerHTML = Artplayer.html; + +var art = new Artplayer({ + container: $container, + url: '/assets/sample/video.mp4', + useSSR: true, +}); + +playsInline +Type: Boolean +Default: true +Whether to use playsInline mode on mobile devices. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + playsInline: true, +}); + +layers +Type: Array +Default: [] +Initialize custom layers. + +var img = '/assets/sample/layer.png'; +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + layers: [ + { + name: 'potser', + html: ``, + style: { + position: 'absolute', + top: '20px', + right: '20px', + opacity: '.9', + }, + click: function (...args) { + console.info('click', args); + art.layers.show = false; + }, + mounted: function (...args) { + console.info('mounted', args); + }, + }, + ], +}); + +For Component Configuration, please refer to: /component/layers.html + +settings +Type: Array +Default: [] +Initialize custom Settings Panel. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + settings: [ + { + html: 'setting01', + selector: [ + { + html: 'setting01-01', + }, + { + html: 'setting01-02', + }, + ], + onSelect: function (...args) { + console.info(args); + }, + }, + { + html: 'setting02', + selector: [ + { + html: 'setting02-01', + }, + { + html: 'setting02-02', + }, + ], + onSelect: function (...args) { + console.info(args); + }, + }, + ], +}); + +For Settings Panel, please refer to: /component/setting.html + +contextmenu +Type: Array +Default: [] +Initialize custom Context Menu. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + contextmenu: [ + { + html: 'your-menu', + click: function (...args) { + console.info('click', args); + art.contextmenu.show = false; + }, + }, + ], +}); + +For Component Configuration, please refer to: /component/contextmenu.html + +controls +Type: Array +Default: [] +Initialize custom bottom control bar. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + controls: [ + { + position: 'left', + html: 'your-control', + tooltip: 'Your Control', + style: { + color: 'green', + }, + click: function (...args) { + console.info('click', args); + }, + }, + ], +}); + +For component configuration, please refer to: /component/controls.html + +quality +Type: Array +Default: [] +Whether to display the quality selection list in the bottom control bar. + +Property Type Description +default Boolean Default quality +html String Quality name +url String Quality URL + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + quality: [ + { + default: true, + html: 'SD 480P', + url: '/assets/sample/video.mp4', + }, + { + html: 'HD 720P', + url: '/assets/sample/video.mp4', + }, + ], +}); + +highlight +Type: Array +Default: [] +Display highlight information on the progress bar. + +Property Type Description +time Number Highlight time (in seconds) +text String Highlight text + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + highlight: [ + { + time: 60, + text: 'One more chance', + }, + { + time: 120, + text: '谁でもいいはずなのに', + }, + { + time: 180, + text: '夏の想い出がまわる', + }, + { + time: 240, + text: 'こんなとこにあるはずもないのに', + }, + { + time: 300, + text: '--终わり--', + }, + ], +}); + +plugins +Type: Array +Default: [] +Initialize custom plugins. + +function myPlugin(art) { + console.info(art); + return { + name: 'myPlugin', + something: 'something', + doSomething: function () { + console.info('doSomething'); + }, + }; +} + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [myPlugin], +}); + +thumbnails +Type: Object +Default: {} +Set thumbnails on the progress bar. + +Property Type Description +url String Thumbnail URL +number Number Number of thumbnails +column Number Number of columns +width Number Thumbnail width +height Number Thumbnail height +scale Number Thumbnail scale + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + thumbnails: { + url: '/assets/sample/thumbnails.png', + number: 60, + column: 10, + }, +}); + +Generate thumbnails online: artplayer-tool-thumbnail + +subtitle +Type: Object +Default: {} +Set video subtitles, supported subtitle formats: vtt, srt, ass. + +Property Type Description +name String Subtitle name +url String Subtitle URL +type String Subtitle type, options: vtt, srt, ass +style Object Subtitle style +encoding String Subtitle encoding, defaults to utf-8 +escape Boolean Whether to escape html tags, defaults to true +onVttLoad Function Function used to modify vtt text + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + subtitle: { + url: '/assets/sample/subtitle.srt', + type: 'srt', + encoding: 'utf-8', + escape: true, + style: { + color: '#03A9F4', + 'font-size': '30px', + }, + }, +}); + +moreVideoAttr +Type: Object +Default: {'controls': false,'preload': 'metadata'} +More video attributes, these attributes will be directly written into the video element. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + moreVideoAttr: { + 'webkit-playsinline': true, + playsInline: true, + }, +}); + +icons +Type: Object +Default: {} +Used to replace default icons, supports Html string and HTMLElement. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + icons: { + loading: '', + state: '', + }, +}); + +All Icon Definitions: artplayer/types/icons.d.ts + +type +Type: String +Default: '' +Used to specify the video format, needs to be used together with customType. The default video format is the suffix of the video URL (e.g., .m3u8, .mkv, .ts). However, sometimes the video URL does not have the correct suffix, so it needs to be explicitly specified. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.m3u8', + type: 'm3u8', +}); + +Suffix Recognition: The player can only parse suffixes like this: /assets/sample/video.m3u8. But cannot parse suffixes like this: /assets/sample/video?type=m3u8. Therefore, if you use customType, it is best to also specify type. + +customType +Type: Object +Default: {} +Matches via the video's type and delegates video decoding to a third-party program for processing. The processing function can receive three parameters: video (Video DOM element), url (Video URL), art (Current instance). + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.m3u8', + customType: { + m3u8: function (video, url, art) { + // + }, + }, +}); + +lang +Type: String +Default: navigator.language.toLowerCase() +Default display language, currently supports: en, zh-cn. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + lang: 'en', +}); + +More Language Settings: /start/i18n.html + +i18n +Type: Object +Default: {} +Custom i18n configuration, this configuration will be deeply merged with the built-in i18n. + +Add your language: + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + lang: 'your-lang', + i18n: { + 'your-lang': { + Play: 'Your + + + +===== Type Definitions Overview ===== + +artplayer-plugin-ads.d.ts + +This plugin provides advertising functionality for ArtPlayer, allowing insertion of video, image, or HTML ads with configurable timing and playback options. + +interface Option { + /** + * 广告源文本,支持视频链接、图片链接、HTML文本 + */ + source: string + + /** + * 知名广告的类型:'video' | 'image' | 'html' + */ + type: 'video' | 'image' | 'html' + + /** + * 广告必看的时长,单位为秒 + */ + playDuration?: number + + /** + * 广告总的时长,单位为秒 + */ + totalDuration?: number + + /** + * 视频广告是否默认静音 + */ + muted?: boolean +} + +interface Ads { + name: 'artplayerPluginAds' + + /** + * 跳过广告 + */ + skip: () => void + + /** + * 暂停广告 + */ + pause: () => void + + /** + * 播放广告 + */ + play: () => void +} + +declare const artplayerPluginAds: (option: Option) => (art: Artplayer) => Ads + +export default artplayerPluginAds + +export = artplayerPluginAds +export as namespace artplayerPluginAds; + +artplayer-plugin-ambilight.d.ts + +This plugin creates ambient lighting effects around the video player that match the video content. + +interface Option { + blur?: string + opacity?: number + frequency?: number + zIndex?: number + duration?: number +} + +interface Result { + name: 'artplayerPluginAmbilight' + start: () => void + stop: () => void +} + +declare const artplayerPluginAmbilight: (option: Option) => (art: Artplayer) => Result + +export default artplayerPluginAmbilight + +export = artplayerPluginAmbilight +export as namespace artplayerPluginAmbilight; + +artplayer-plugin-asr.d.ts + +This plugin provides automatic speech recognition (ASR) functionality for generating subtitles from audio. + +interface AudioChunk { + pcm: ArrayBuffer + wav: ArrayBuffer +} + +interface AsrPluginOption { + length?: number + interval?: number + sampleRate?: number + autoHideTimeout?: number + onAudioChunk?: (chunk: AudioChunk) => void | Promise +} + +interface AsrPluginInstance { + name: 'artplayerPluginAsr' + stop: () => void + hide: () => void + append: (subtitle: string) => void +} + +declare function artplayerPluginAsr(option?: AsrPluginOption): (art: Artplayer) => AsrPluginInstance + +export default artplayerPluginAsr + +export = artplayerPluginAsr +export as namespace artplayerPluginAsr; + +artplayer-plugin-auto-thumbnail.d.ts + +This plugin automatically generates thumbnails for video scrubbing and preview. + +interface Option { + url?: string + width?: number + number?: number + scale?: number +} + +interface Result { + name: 'artplayerPluginAutoThumbnail' +} + +declare const artplayerPluginAutoThumbnail: (option: Option) => (art: Artplayer) => Result + +export default artplayerPluginAutoThumbnail + +export = artplayerPluginAutoThumbnail +export as namespace artplayerPluginAutoThumbnail; + +artplayer-plugin-chapter.d.ts + +This plugin adds chapter navigation functionality to the video player. + +type Chapters = { + start: number + end: number + title: string +}[] + +interface Option { + chapters?: Chapters +} + +interface Result { + name: 'artplayerPluginChapter' + update: (option: Option) => void +} + +declare const artplayerPluginChapter: (option: Option) => (art: Artplayer) => Result + +export default artplayerPluginChapter + +export = artplayerPluginChapter +export as namespace artplayerPluginChapter; + +artplayer-plugin-chromecast.d.ts + +This plugin enables Google Chromecast functionality for casting video to external devices. + +interface Option { + url?: string + sdk?: string + icon?: string + mimeType?: string +} + +interface Chromecast { + name: 'artplayerPluginChromecast' +} + +declare const artplayerPluginChromecast: (option: Option) => (art: Artplayer) => Chromecast + +export default artplayerPluginChromecast + +export = artplayerPluginChromecast +export as namespace artplayerPluginChromecast; + +artplayer-plugin-danmuku-mask.d.ts + +This plugin provides masking functionality for danmaku (bullet comments) to avoid obscuring important video content. + +interface Option { + solutionPath?: string + modelSelection?: number + smoothSegmentation?: boolean + minDetectionConfidence?: number + minTrackingConfidence?: number + selfieMode?: boolean + drawContour?: boolean + foregroundThreshold?: number + opacity?: number + maskBlurAmount?: number +} + +interface Result { + name: 'artplayerPluginDanmukuMask' + start: () => Promise + stop: () => void +} + +declare const artplayerPluginDanmukuMask: (option?: Option) => (art: Artplayer) => Result + +export default artplayerPluginDanmukuMask + +export = artplayerPluginDanmukuMask +export as namespace artplayerPluginDanmukuMask; + +artplayer-plugin-danmuku.d.ts + +This plugin provides comprehensive danmaku (bullet comment) functionality with extensive customization options for display, behavior, and styling. + +export type Mode = 0 | 1 | 2 +export type Danmuku + = | Danmu[] + | string // URL + | (() => Promise) + | Promise + +export interface Slider { + min?: number + max?: number + steps?: { + name?: string + value?: number | string + show?: boolean + }[] +} + +export interface Danmu { + /** + * 弹幕文本 + */ + text: string + + /** + * 弹幕发送模式: 0: 滚动,1: 顶部,2: 底部 + */ + mode?: Mode + + /** + * 弹幕颜色 + */ + color?: string + + /** + * 弹幕出现的时间,单位为秒 + */ + time?: number + + /** + * 弹幕是否有描边, 默认为 false + */ + border?: boolean + + /** + * 弹幕自定义样式 + */ + style?: Partial +} + +export interface Option { + /** + * 弹幕数据: 函数,数组,Promise,URL + */ + danmuku: Danmuku + + /** + * 弹幕持续时间,范围在[1 ~ 10] + */ + speed?: number + + /** + * 弹幕上下边距,支持像素数字和百分比 + */ + margin?: [number | `${number}%`, number | `${number}%`] + + /** + * 弹幕透明度,范围在[0 ~ 1] + */ + opacity?: number + + /** + * 默认弹幕颜色,可以被单独弹幕项覆盖 + */ + color?: string + + /** + * 弹幕模式: 0: 滚动,1: 顶部,2: 底部 + */ + mode?: Mode + + /** + * 弹幕可见的模式 + */ + modes?: Mode[] + + /** + * 弹幕字体大小,支持像素数字和百分比 + */ + fontSize?: number | `${number}%` + + /** + * 弹幕是否防重叠 + */ + antiOverlap?: boolean + + /** + * 是否同步播放速度 + */ + synchronousPlayback?: boolean + + /** + * 弹幕发射器挂载点, 默认为播放器控制栏中部 + */ + mount?: HTMLDivElement | string + + /** + * 是否开启弹幕热度图 + */ + heatmap?: + | boolean + | { + xMin?: number + xMax?: number + yMin?: number + yMax?: number + scale?: number + opacity?: number + minHeight?: number + sampling?: number + smoothing?: number + flattening?: number + } + + /** + * 当播放器宽度小于此值时,弹幕发射器置于播放器底部 + */ + width?: number + + /** + * 热力图数据 + */ + points?: { time: number, value: number }[] + + /** + * 弹幕载入前的过滤器,只支持返回布尔值 + */ + filter?: (danmu: Danmu) => boolean + + /** + * 弹幕发送前的过滤器,支持返回 Promise + */ + beforeEmit?: (danmu: Danmu) => boolean | Promise + + /** + * 弹幕显示前的过滤器,支持返回 Promise + */ + beforeVisible?: (danmu: Danmu) => boolean | Promise + + /** + * 弹幕是否可见 + */ + visible?: boolean + + /** + * 是否开启弹幕发射器 + */ + emitter?: boolean + + /** + * 弹幕输入框最大长度, 范围在[1 ~ 1000] + */ + maxLength?: number + + /** + * 输入框锁定时间,范围在[1 ~ 60] + */ + lockTime?: number + + /** + * 弹幕主题,只在自定义挂载时生效 + */ + theme?: 'light' | 'dark' + + /** + * 不透明度配置项 + */ + OPACITY?: Slider + + /** + * 弹幕速度配置项 + */ + SPEED?: Slider + + /** + * 显示区域配置项 + */ + MARGIN?: Slider + + /** + * 弹幕字号配置项 + */ + FONT_SIZE?: Slider + + /** + * 颜色列表配置项 + */ + COLOR?: string[] +} + +export interface Result { + name: 'artplayerPluginDanmuku' + + /** + * 发送一条实时弹幕 + */ + emit: (danmu: Danmu) => Result + + /** + * 重载弹幕源,或者切换新弹幕 + */ + load: (danmuku?: Danmuku) => Promise + + /** + * 实时改变弹幕配置 + */ + config: (option: Option) => Result + + /** + * 隐藏弹幕层 + */ + hide: () => Result + + /** + * 显示弹幕层 + */ + show: () => Result + + /** + * 挂载弹幕输入框 + */ + mount: (el?: HTMLDivElement | string) => void + + /** + * 重置弹幕 + */ + reset: () => Result + + /** + * 弹幕配置 + */ + option: Option + + /** + * 是否隐藏弹幕层 + */ + isHide: boolean + + /** + * 是否弹幕层停止状态 + */ + isStop: boolean +} + +declare const artplayerPluginDanmuku: (option: Option) => (art: Artplayer) => Result + +export default artplayerPluginDanmuku + +export = artplayerPluginDanmuku +export as namespace artplayerPluginDanmuku; + +artplayer-plugin-dash-control.d.ts + +This plugin appears to be incomplete in the provided file. It would typically provide controls for DASH (Dynamic Adaptive Streaming over HTTP) video playback. + +ARTPLAYER PLUGIN TYPE DEFINITIONS ANALYSIS + +artplayer-plugin-dash-control.d.ts + +This plugin provides DASH quality and audio track controls for ArtPlayer. + +interface Config { + control?: boolean + setting?: boolean + title?: string + auto?: string + getName?: (level: object) => string +} + +The Config interface defines options for quality/audio controls: +- control: Whether to show the control element +- setting: Whether to include in settings menu +- title: Display title for the control +- auto: Auto selection option text +- getName: Function to format level names + +declare const artplayerPluginDashControl: (option: { quality?: Config, audio?: Config }) => (art: Artplayer) => { + name: 'artplayerPluginDashControl' + update: () => void +} + +The main plugin function accepts quality and audio configuration objects and returns an ArtPlayer plugin with update capability. + +export default artplayerPluginDashControl +export = artplayerPluginDashControl +export as namespace artplayerPluginDashControl; + +artplayer-plugin-document-pip.d.ts + +This plugin enables Document Picture-in-Picture (PiP) functionality. + +interface Option { + width?: number + height?: number + placeholder?: string + fallbackToVideoPiP?: boolean +} + +Option interface for PiP configuration: +- width/height: PiP window dimensions +- placeholder: Text when PiP unavailable +- fallbackToVideoPiP: Use video PiP if document PiP unsupported + +interface Result { + name: 'artplayerPluginDocumentPip' + isSupported: boolean + isActive: boolean + open: () => void + close: () => void + toggle: () => void +} + +Result interface provides PiP state and control methods. + +declare const artplayerPluginDocumentPip: (option: Option) => (art: Artplayer) => Result + +export default artplayerPluginDocumentPip +export = artplayerPluginDocumentPip +export as namespace artplayerPluginDocumentPip; + +artplayer-plugin-hls-control.d.ts + +This plugin provides HLS quality and audio track controls (similar to DASH plugin). + +interface Config { + control?: boolean + setting?: boolean + title?: string + auto?: string + getName?: (level: object) => string +} + +Same Config interface as DASH plugin for consistency. + +declare const artplayerPluginHlsControl: (option: { quality?: Config, audio?: Config }) => (art: Artplayer) => { + name: 'artplayerPluginHlsControl' + update: () => void +} + +export default artplayerPluginHlsControl +export = artplayerPluginHlsControl +export as namespace artplayerPluginHlsControl; + +artplayer-plugin-iframe.d.ts + +This plugin enables iframe communication and control capabilities. + +interface Message { + type: string + data: any + id?: number +} + +Message interface for cross-iframe communication. + +declare class ArtplayerPluginIframe { + constructor(option: { iframe: HTMLIFrameElement, url: string }) + + static iframe: boolean + static postMessage(message: Message): void + static onMessage(event: MessageEvent & { data: Message }): void + static inject(): void + + readonly promises: Record any, reject: (...args: any[]) => any }> + readonly injected: boolean + readonly destroyed: boolean + readonly $iframe: HTMLIFrameElement + readonly url: string + readonly messageCallback: (...args: any[]) => any + + onMessage(event: MessageEvent & { data: Message }): void + postMessage(message: Message): Promise + commit any>(callback: T): Promise> + message(callback: (...args: any[]) => any): void + destroy(): void +} + +The class provides comprehensive iframe messaging with promise-based communication, injection capabilities, and lifecycle management. + +export default ArtplayerPluginIframe +export = artplayerPluginIframe +export as namespace artplayerPluginIframe; + +artplayer-plugin-libass.d.ts + +This plugin integrates libass for advanced subtitle rendering. + +declare const artplayerPluginAss: (options: Options) => (art: Artplayer) => { + name: 'artplayerPluginLibass' + libass: SubtitlesOctopus + visible: boolean + init: () => void + switch: (url: string) => void + show: () => void + hide: () => void + destroy: () => void +} + +Provides libass integration with subtitle switching, visibility control, and full lifecycle management. + +export default artplayerPluginAss +export = artplayerPluginLibass +export as namespace artplayerPluginLibass; + +artplayer-plugin-multiple-subtitles.d.ts + +This plugin enables multiple subtitle track support. + +declare const artplayerPluginMultipleSubtitles: (option: { + subtitles: { + url?: string + name?: string + type?: 'vtt' | 'srt' | 'ass' + encoding?: string + onParser?: (...args: object[]) => object + }[] +}) => (art: Artplayer) => { + name: 'multipleSubtitles' +} + +Supports multiple subtitle formats (VTT, SRT, ASS) with custom encoding and parser functions. + +export default artplayerPluginMultipleSubtitles +export = artplayerPluginMultipleSubtitles +export as namespace artplayerPluginMultipleSubtitles; + +artplayer-plugin-vast.d.ts + +This plugin provides VAST (Video Ad Serving Template) advertising integration. + +declare global { + interface Window { + artplayerPluginVast?: typeof artplayerPluginVast + } +} + +Extends Window interface for global plugin availability. + +type PlayUrlFn = (url: string) => void +type PlayResFn = (res: string) => void + +Function types for URL and resource playback. + +interface VastPluginContext { + art: Artplayer + ima: any + imaPlayer: Player + playUrl: PlayUrlFn + playRes: PlayResFn + container: HTMLDivElement | null +} + +Context object providing access to IMA SDK, player instances, and DOM elements. + +export type ArtplayerPluginVastOption = (params: VastPluginContext) => void | Promise + +Plugin option type that receives VAST context. + +export interface ArtplayerPluginVastInstance { + name: 'artplayerPluginVast' + destroy?: () => void +} + +Plugin instance interface with optional cleanup. + +declare function artplayerPluginVast( + option: ArtplayerPluginVastOption, +): (art: Artplayer) => ArtplayerPluginVastInstance + +export default artplayerPluginVast +export = artplayerPluginVast +export as namespace artplayerPluginVast; + +artplayer-plugin-vtt-thumbnail.d.ts + +This plugin enables VTT-based video thumbnails. + +declare const artplayerPluginVttThumbnail: (option: { vtt?: string, style?: Partial }) => ( + art: Artplayer, +) => { + name: 'artplayerPluginVttThumbnail' +} + +Accepts VTT file URL and custom CSS styling for thumbnail presentation. + +export default artplayerPluginVttThumbnail +export = artplayerPluginVttThumbnail +export as namespace artplayerPluginVttThumbnail; + +All plugins follow consistent ArtPlayer plugin pattern: they export functions that accept configuration options and return plugin factories that receive ArtPlayer instances and return plugin instances with standardized interfaces. + +ARTPLAYER TYPESCRIPT DECLARATION ANALYSIS + +UTILS INTERFACE +The Utils interface provides utility functions and properties for DOM manipulation, browser detection, and common operations. + +export interface Utils { + // Browser detection properties + userAgent: string + isMobile: boolean + isSafari: boolean + isIOS: boolean + isIOS13: boolean + + // DOM manipulation methods + query: (selector: string, parent?: HTMLElement) => HTMLElement + queryAll: (selector: string, parent?: HTMLElement) => HTMLElement[] + addClass: (target: HTMLElement, className: string) => void + removeClass: (target: HTMLElement, className: string) => void + hasClass: (target: HTMLElement, className: string) => boolean + append: (target: HTMLElement, child: HTMLElement) => HTMLElement + remove: (target: HTMLElement) => void + replaceElement: (newChild: HTMLElement, oldChild: HTMLElement) => HTMLElement + siblings: (target: HTMLElement) => HTMLElement[] + inverseClass: (target: HTMLElement, className: string) => void + createElement: (tag: K) => HTMLElementTagNameMap[K] + setStyle: ( + element: HTMLElement, + key: T, + value: CSSStyleDeclaration[T], + ) => HTMLElement + setStyles: (element: HTMLElement, styles: Partial) => HTMLElement + getStyle: ( + element: HTMLElement, + key: K, + numberType?: boolean, + ) => boolean extends true ? number : string + setStyleText: (element: HTMLElement, text: string) => void + getRect: (el: HTMLElement) => { top: number, left: number, width: number, height: number } + tooltip: (target: HTMLElement, msg: string, pos?: string) => void + isInViewport: (target: HTMLElement, offset?: number) => boolean + includeFromEvent: (event: Event, target: HTMLElement) => boolean + + // Subtitle format conversion methods + srtToVtt: (srtText: string) => string + vttToBlob: (vttText: string) => string + assToVtt: (assText: string) => string + + // File and network utilities + getExt: (url: string) => string + download: (url: string, name: string) => void + loadImg: (url: string, scale?: number) => Promise + + // Object and error utilities + errorHandle: (condition: T, msg: string) => T extends true ? T : never + def: (obj: object, name: string, value: unknown) => void + has: (obj: object, name: PropertyKey) => boolean + get: (obj: object, name: PropertyKey) => PropertyDescriptor | undefined + mergeDeep: (...args: T) => T[number] + + // Async and timing utilities + sleep: (ms: number) => Promise + debounce: any>(func: F, wait: number, context?: object) => (...args: Parameters) => ReturnType + throttle: any>(func: F, wait: number) => (...args: Parameters) => ReturnType + + // Math and string utilities + clamp: (num: number, a: number, b: number) => number + secondToTime: (second: number) => string + escape: (str: string) => string + capitalize: (str: string) => string + + // UI utilities + getIcon: (key: string, html: string | HTMLElement) => HTMLElement + supportsFlex: () => boolean +} + +TEMPLATE INTERFACE +Contains references to all major DOM elements used in the player structure. + +export interface Template { + readonly $container: HTMLDivElement + readonly $player: HTMLDivElement + readonly $video: HTMLVideoElement + readonly $track: HTMLTrackElement + readonly $poster: HTMLDivElement + readonly $subtitle: HTMLDivElement + readonly $danmuku: HTMLDivElement + readonly $bottom: HTMLDivElement + readonly $progress: HTMLDivElement + readonly $controls: HTMLDivElement + readonly $controlsLeft: HTMLDivElement + readonly $controlsRight: HTMLDivElement + readonly $layer: HTMLDivElement + readonly $loading: HTMLDivElement + readonly $notice: HTMLDivElement + readonly $noticeInner: HTMLDivElement + readonly $mask: HTMLDivElement + readonly $state: HTMLDivElement + readonly $setting: HTMLDivElement + readonly $info: HTMLDivElement + readonly $infoPanel: HTMLDivElement + readonly $infoClose: HTMLDivElement + readonly $contextmenu: HTMLDivElement + readonly $mini: HTMLDivElement +} + +SUBTITLE INTERFACE +Defines subtitle configuration options including URL, styling, and encoding. + +export interface Subtitle { + /** + * The subtitle url + */ + url?: string + + /** + * The subtitle name + */ + name?: string + + /** + * The subtitle type + */ + type?: 'vtt' | 'srt' | 'ass' | (string & Record) + + /** + * The subtitle style object + */ + style?: Partial + + /** + * The subtitle encoding, default utf-8 + */ + encoding?: string + + /** + * Whether use escape, default true + */ + escape?: boolean + + /** + * Change the vtt text + */ + onVttLoad?: (vtt: string) => string +} + +SETTING AND SETTING OPTION TYPES +Settings provide customizable UI controls for player configuration. + +type Props = { + html: string + icon: string + tooltip: string + $item: HTMLDivElement + $icon: HTMLDivElement + $html: HTMLDivElement + $tooltip: HTMLDivElement + $switch: HTMLDivElement + $range: HTMLInputElement + $parent: Setting + $parents: Setting[] + $option: Setting[] + $events: Array<(...args: unknown[]) => unknown> + $formatted: boolean +} & Omit + +export type SettingOption = Props + +export interface Setting { + /** + * Html string or html element of setting name + */ + html: string | HTMLElement + + /** + * Html string or html element of setting icon + */ + icon?: string | HTMLElement + + /** + * The width of setting + */ + width?: number + + /** + * The tooltip of setting + */ + tooltip?: string | HTMLElement + + /** + * Whether the default is selected + */ + default?: boolean + + /** + * Custom selector list + */ + selector?: Setting[] + + /** + * When the setting was mounted + */ + mounted?: (this: Artplayer, panel: HTMLDivElement, item: Setting) => void + + /** + * When selector item click + */ + onSelect?: (this: Artplayer, item: SettingOption, element: HTMLDivElement, event: Event) => void + + /** + * Custom switch item + */ + switch?: boolean + + /** + * When switch item click + */ + onSwitch?: (this: Artplayer, item: SettingOption, element: HTMLDivElement, event: Event) => void + + /** + * Custom range item + */ + range?: [value?: number, min?: number, max?: number, step?: number] + + /** + * When range item change + */ + onRange?: (this: Artplayer, item: SettingOption, element: HTMLDivElement, event: Event) => void + + /** + * When range item change in real time + */ + onChange?: (this: Artplayer, item: SettingOption, element: HTMLDivElement, event: Event) => void + + /** + * When range item change in real time + */ + onClick?: (this: Artplayer, item: SettingOption, element: HTMLDivElement, event: Event) => void + + /** + * Allow custom properties + */ + [key: string]: any +} + +QUALITY INTERFACE +Defines video quality options for adaptive streaming. + +export interface Quality { + /** + * Whether the default is selected + */ + default?: boolean + + /** + * Html string of quality + */ + html: string | HTMLElement + + /** + * Video quality url + */ + url: string +} + +PLAYER STATE AND CONTROL TYPES +Type definitions for player states, aspect ratios, playback rates, and flip states. + +export type AspectRatio = 'default' | '4:3' | '16:9' | (`${number}:${number}` & Record) +export type PlaybackRate = 0.5 | 0.75 | 1.0 | 1.25 | 1.5 | 1.75 | 2.0 | (number & Record) +export type Flip = 'normal' | 'horizontal' | 'vertical' | (string & Record) +export type State = 'standard' | 'mini' | 'pip' | 'fullscreen' | 'fullscreenWeb' + +PLAYER CLASS +The main Player class providing video playback controls and state management. + +export declare class Player { + // Aspect ratio control + get aspectRatio(): AspectRatio + set aspectRatio(ratio: AspectRatio) + + // Player state management + get state(): State + set state(state: State) + + // Video type handling + get type(): CustomType + set type(name: CustomType) + + // Playback rate control + get playbackRate(): PlaybackRate + set playbackRate(rate: PlaybackRate) + + // Time and progress management + get currentTime(): number + set currentTime(time: number) + get duration(): number + get played(): number + get playing(): boolean + + // Video transformation + get flip(): Flip + set flip(state: Flip) + + // Fullscreen controls + get fullscreen(): boolean + set fullscreen(state: boolean) + get fullscreenWeb(): boolean + set fullscreenWeb(state: boolean) + + // Buffer and load status + get loaded(): number + get loadedTime(): number + + // Mini player mode + get mini(): boolean + set mini(state: boolean) + + // Picture-in-picture mode + get pip(): boolean + set pip(state: boolean) + + // Poster image + get poster(): string + set poster(url: string) + + // DOM rect and positioning + get rect(): DOMRect + get bottom(): number + get height(): number + get left(): number + get right(): number + get top(): number + get width(): number + get x(): number + get y(): number + + // Seeking functionality + set seek(time: number) + get seek(): number + + // Forward/backward navigation + set forward(time: number) + get forward(): number + set backward(time: number) + get backward(): number + + // Video source + get url(): string + set url(url: string) + + // Audio controls + get volume(): number + set volume(percentage: number) + get muted(): boolean + set muted(state: boolean) + + // UI customization + get title(): string + set title(title: string) + get theme(): string + set theme(theme: string) + + // Subtitle controls + get subtitleOffset(): number + set subtitleOffset(time: number) + + // URL switching + get switch(): string + set switch(url: string) + + // Quality management + get quality(): Quality[] + set quality(quality: Quality[]) + + // Thumbnail support + get thumbnails(): Thumbnails + set thumbnails(thumbnails: Thumbnails) + + // Playback controls + pause(): void + play(): Promise + toggle(): void + + // Attribute and CSS management + attr(key: string, value?: unknown): unknown + cssVar(key: T, value?: CssVar[T]): CssVar[T] + + // URL and quality switching + switchUrl(url: string): Promise + switchQuality(url: string): Promise + + // Media capture + getDataURL(): Promise + getBlobUrl(): Promise + screenshot(name?: string): Promise + + // Additional features + airplay(): void + autoSize(): void + autoHeight(): void +} + +CUSTOM VIDEO TYPE SUPPORT +Supported video formats and streaming protocols. + +export type CustomType + = | 'flv' + | 'm3u8' + | 'hls' + | 'ts' + | 'mpd' + | 'torrent' + | (string & Record) + +THUMBNAILS INTERFACE +Configuration for video thumbnail previews. + +export interface Thumbnails { + /** + * The thumbnail image url + */ + url: string + + /** + * The thumbnail item number + */ + number?: number + + /** + * The thumbnail column size + */ + column?: number + + /** + * The thumbnail width + */ + width?: number + + /** + * The thumbnail height + */ + height?: number + + /** + * The thumbnail scale + */ + scale?: number +} + +OPTION INTERFACE +Main configuration object for initializing ArtPlayer instances. + +export interface Option { + /** + * The player id + */ + id?: string + + /** + * The container mounted by the player + */ + container: string | HTMLDivElement + + /** + * Video url + */ + url: string + + /** + * Video poster image url + */ + poster?: string + + /** + * Video url type + */ + type?: CustomType + + /** + * Player color theme + */ + theme?: string + + /** + * Player language + */ + lang?: keyof I18n + + /** + * Player default volume + */ + volume?: number + + /** + * Whether live broadcast mode + */ + isLive?: boolean + + /** + * Whether video muted + */ + muted?: boolean + + /** + * Whether video auto play + */ + autoplay?: boolean + + /** + * Whether player auto resize + */ + autoSize?: boolean + + /** + * Whether player auto run mini mode + */ + autoMini?: boolean + + /** + * Whether video auto loop + */ + loop?: boolean + + /** + * Whether show video flip button + */ + flip?: boolean + + /** + * Whether show video playback rate button + */ + playbackRate?: boolean + + /** + * Whether show video aspect ratio button + */ + aspectRatio?: boolean + + /** + * Whether show video screenshot button + */ + screenshot?: boolean + + /** + * Whether show video setting button + */ + setting?: boolean + + /** + * Whether to enable player hotkey + */ + hotkey?: boolean + + /** + * Whether show video pip button + */ + pip?: boolean + + /** + * Do you want to run only one player at a time + */ + mutex?: boolean + + /** + * Whether use backdrop in UI + */ + backdrop?: boolean + + /** + * Whether show video window fullscreen button + */ + fullscreen?: boolean + + /** + * Whether show video web fullscreen button + */ + fullscreenWeb?: boolean + + /** + * Whether to enable player subtitle offset + */ + subtitleOffset?: boolean + + /** + * Whether to enable player mini progress bar + */ + miniProgressBar?: boolean + + /** + * Whether use SSR function + */ + useSSR?: boolean + + /** + * Whether use playsInline in mobile + */ + playsInline?: boolean + + /** + * Whether use lock in mobile + */ + lock?: boolean + + /** + * Whether use gesture in mobile + */ + gesture?: boolean + + /** + * Whether use fast forward in mobile + */ + fastForward?: boolean + + /** + * Whether use auto playback + */ + autoPlayback?: boolean + + /** + * Whether use auto orientation in mobile + */ + autoOrientation?: boolean + + /** + * Whether use airplay + */ + airplay?: boolean + + /** + * Custom video proxy + */ + proxy?: (this: Artplayer, art: Artplayer) => HTMLCanvasElement | HTMLVideoElement + + /** + * Custom plugin list + */ + plugins?: ((this: Artplayer, art: Artplayer) => unknown)[] + + /** + * Custom layer list + */ + layers?: ComponentOption[] + + /** + * Custom contextmenu list + */ + contextmenu?: ComponentOption[] + + /** + * Custom control list + */ + controls?: ComponentOption[] + + /** + * Custom setting list + */ + settings?: Setting[] + + /** + * Custom video quality list + */ + quality?: Quality[] + + /** + * Custom highlight list + */ + highlight?: { + /** + * The highlight time + */ + time: number + + /** + * The highlight text + */ + text: string + }[] + + /** + * Custom thumbnail + */ + thumbnails?: Thumbnails + + /** + * Custom subtitle option + */ + subtitle?: Subtitle + + /** + * Other video attribute + */ + moreVideoAttr?: Partial unknown + ? never + : K]: HTMLVideoElement[K] + }>> + + /** + * Custom i18n + */ + i18n?: I18n + + /** + * Custom default icons + */ + icons?: { + [key in keyof Icons]?: HTMLElement | string + } + + /** + + + +===== Examples Summary ===== + +ads.js example: +This example demonstrates how to integrate advertising functionality using the artplayer-plugin-ads plugin. It shows HTML and video ads with configurable duration, skip options, and internationalization support. + +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoSize: true, + fullscreen: true, + fullscreenWeb: true, + plugins: [ + artplayerPluginAds({ + // html广告,假如是视频广告则忽略该值 + html: '', + + // 视频广告的地址 + video: '/assets/sample/test1.mp4', + + // 广告跳转网址,为空则不跳转 + url: 'http://artplayer.org', + + // 必须观看的时长,期间不能被跳过,单位为秒 + // 当该值大于或等于totalDuration时,不能提前关闭广告 + // 当该值等于或小于0时,则随时都可以关闭广告 + playDuration: 5, + + // 广告总时长,单位为秒 + totalDuration: 10, + + // 多语言支持 + i18n: { + close: '关闭广告', + countdown: '%s秒', + detail: '查看详情', + canBeClosed: '%s秒后可关闭广告', + }, + }), + ], +}) + +// 广告被点击 +art.on('artplayerPluginAds:click', (ads) => { + console.info('广告被点击', ads) +}) + +// 广告被跳过 +art.on('artplayerPluginAds:skip', (ads) => { + console.info('广告被跳过', ads) +}) + +ambilight.js example: +This example shows how to create ambient lighting effects around the video player using the artplayer-plugin-ambilight plugin with customizable blur, opacity, frequency, and duration settings. + +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoSize: true, + plugins: [ + artplayerPluginAmbilight({ + blur: '50px', + opacity: 1, + frequency: 10, + duration: 0.3, + }), + ], +}) + +asr.js example: +This example demonstrates automatic speech recognition integration using WebSocket connections to convert audio chunks to subtitles in real-time during video playback. + +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/steve-jobs.mp4', + autoSize: true, + fullscreen: true, + fullscreenWeb: true, + moreVideoAttr: { + // crossOrigin: 'anonymous', + }, + plugins: [ + artplayerPluginAsr({ + length: 2, + interval: 40, + sampleRate: 16000, + autoHideTimeout: 10000, + // Use your AI tool to convert pcm into subtitles + onAudioChunk: ({ pcm }) => startAsr(pcm), + }), + ], +}) + +let ws = null +let loading = false + +function stopAsr() { + try { + ws.send(JSON.stringify({ type: 'end' })) + ws.close() + } + catch {} + ws = null + loading = false +} + +async function startAsr(buffer) { + if (loading) + return + if (!ws) { + loading = true + const api = 'https://api.aimu.app/asr/tencent?engine_model_type=16k_en' + const { url } = await (await fetch(api)).json() + ws = new WebSocket(url) + ws.binaryType = 'arraybuffer' + ws.onmessage = (event) => { + const { code, result, message } = JSON.parse(event.data) + if (code === 0) { + art.plugins.artplayerPluginAsr.append(result?.voice_text_str) + } + else { + console.error(code, message) + stopAsr() + } + } + loading = false + } + if (ws?.readyState === WebSocket.OPEN) { + ws.send(buffer) + } +} + +art.on('destroy', stopAsr) + +auto.thumbnail.js example: +This example shows automatic thumbnail generation for video scrubbing using the artplayer-plugin-auto-thumbnail plugin with default configuration. + +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginAutoThumbnail({ + // + }), + ], +}) + +canvas.js example: +This example demonstrates advanced video manipulation using canvas proxy for features like screenshots, thumbnails, and various playback controls with artplayer-proxy-canvas integration. + +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + poster: '/assets/sample/poster.jpg', + volume: 0.5, + autoplay: false, + autoSize: false, + screenshot: true, + setting: true, + loop: true, + flip: true, + pip: true, + playbackRate: true, + aspectRatio: true, + fullscreen: true, + fullscreenWeb: true, + miniProgressBar: true, + autoPlayback: true, + autoOrientation: true, + thumbnails: { + url: '/assets/sample/thumbnails.png', + number: 60, + column: 10, + scale: 0.85, + }, + proxy: artplayerProxyCanvas(), +}) + +chapter.js example: +This example shows video chapter segmentation with defined time ranges and titles using the artplayer-plugin-chapter plugin for enhanced navigation. + +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoSize: true, + fullscreen: true, + fullscreenWeb: true, + miniProgressBar: true, + autoOrientation: true, + thumbnails: { + url: '/assets/sample/thumbnails.png', + number: 60, + column: 10, + }, + plugins: [ + artplayerPluginChapter({ + chapters: [ + { start: 0, end: 18, title: 'One more chance' }, + { start: 18, end: 36, title: '谁でもいいはずなのに' }, + { start: 36, end: 54, title: '夏の想い出がまわる' }, + { start: 54, end: 72, title: 'こんなとこにあるはずもないのに' }, + { start: 72, end: Infinity, title: '终わり' }, + ], + }), + ], +}) + +chromecast.js example: +This example demonstrates Google Chromecast integration for casting video content to external devices using the artplayer-plugin-chromecast plugin. + +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fullscreen: true, + fullscreenWeb: true, + plugins: [ + artplayerPluginChromecast({ + // sdk: '', // The URL of the Cast SDK + // mimeType: '', // The MIME type of the media + }), + ], +}) + +danmuku.js example: +This example shows comprehensive danmaku (bullet chat) functionality with extensive configuration options for appearance, behavior, and filtering using artplayer-plugin-danmuku. + +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoSize: true, + fullscreen: true, + fullscreenWeb: true, + autoOrientation: true, + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + + // 以下为非必填 + speed: 5, // 弹幕持续时间,范围在[1 ~ 10] + margin: [10, '25%'], // 弹幕上下边距,支持像素数字和百分比 + opacity: 1, // 弹幕透明度,范围在[0 ~ 1] + color: '#FFFFFF', // 默认弹幕颜色,可以被单独弹幕项覆盖 + mode: 0, // 默认弹幕模式: 0: 滚动,1: 顶部,2: 底部 + modes: [0, 1, 2], // 弹幕可见的模式 + fontSize: 25, // 弹幕字体大小,支持像素数字和百分比 + antiOverlap: true, // 弹幕是否防重叠 + synchronousPlayback: false, // 是否同步播放速度 + mount: undefined, // 弹幕发射器挂载点, 默认为播放器控制栏中部 + heatmap: true, // 是否开启热力图 + width: 512, // 当播放器宽度小于此值时,弹幕发射器置于播放器底部 + points: [], // 热力图数据 + filter: danmu => danmu.text.length <= 100, // 弹幕载入前的过滤器 + beforeVisible: () => true, // 弹幕显示前的过滤器,返回 true 则可以发送 + visible: true, // 弹幕层是否可见 + emitter: true, // 是否开启弹幕发射器 + maxLength: 200, // 弹幕输入框最大长度, 范围在[1 ~ 1000] + lockTime: 5, // 输入框锁定时间,范围在[1 ~ 60] + theme: 'dark', // 弹幕主题,支持 dark 和 light,只在自定义挂载时生效 + OPACITY: {}, // 不透明度配置项 + FONT_SIZE: {}, // 弹幕字号配置项 + MARGIN: {}, // 显示区域配置项 + SPEED: {}, // 弹幕速度配置项 + COLOR: [], // 颜色列表配置项 + + // 手动发送弹幕前的过滤器,返回 true 则可以发送,可以做存库处理 + beforeEmit(danmu) { + return new Promise((resolve) => { + console.log(danmu) + setTimeout(() => { + resolve(true) + }, 1000) + }) + }, + }), + ], +}) + +danmuku.mask.js example: +This example combines danmaku functionality with selfie segmentation masking using artplayer-plugin-danmuku-mask to create background-aware bullet chat displays. + +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + autoSize: true, + fullscreen: true, + fullscreenWeb: true, + autoOrientation: true, + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + artplayerPluginDanmukuMask({ + solutionPath: '/assets/@mediapipe/selfie_segmentation', + }), + ], +}) + +dash.control.js example: +This example demonstrates DASH (Dynamic Adaptive Streaming over HTTP) video streaming integration using dashjs library with artplayer-plugin-dash-control for adaptive bitrate streaming. + +// npm i dashjs +// npm i artplayer-plugin-dash-control + +// import dashjs from 'dashjs'; +// import artplayerPluginDashControl from 'artplayer-plugin-dash-control'; + +Example 1: ArtPlayer with DASH.js and custom quality/audio controls +This example shows how to integrate ArtPlayer with DASH.js for MPEG-DASH streaming and uses the artplayerPluginDashControl plugin to add quality selection and audio track controls. + +const art = new Artplayer({ + container: '.artplayer-app', + url: 'https://media.axprod.net/TestVectors/v7-Clear/Manifest_1080p.mpd', + setting: true, + plugins: [ + artplayerPluginDashControl({ + quality: { + // Show qualitys in control + control: true, + // Show qualitys in setting + setting: true, + // Get the quality name from level + getName: level => `${level.height}P`, + // I18n + title: 'Quality', + auto: 'Auto', + }, + audio: { + // Show audios in control + control: true, + // Show audios in setting + setting: true, + // Get the audio name from track + getName: track => track.lang.toUpperCase(), + // I18n + title: 'Audio', + auto: 'Auto', + }, + }), + ], + customType: { + mpd: function playMpd(video, url, art) { + if (dashjs.supportsMediaSource()) { + if (art.dash) + art.dash.destroy() + const dash = dashjs.MediaPlayer().create() + dash.initialize(video, url, art.option.autoplay) + art.dash = dash + art.on('destroy', () => dash.destroy()) + } + else { + art.notice.show = 'Unsupported playback format: mpd' + } + }, + }, +}) + +===== dash.js ===== + +Example 2: Basic DASH.js integration +This example demonstrates a simpler DASH.js implementation with ArtPlayer, showing how to handle MPEG-DASH streams with custom type handlers and accessing the DASH player instance. + +// npm i dashjs +// import dashjs from 'dashjs'; + +function playMpd(video, url, art) { + if (dashjs.supportsMediaSource()) { + if (art.dash) + art.dash.destroy() + const dash = dashjs.MediaPlayer().create() + dash.initialize(video, url, art.option.autoplay) + art.dash = dash + art.on('destroy', () => dash.destroy()) + } + else { + art.notice.show = 'Unsupported playback format: mpd' + } +} + +const art = new Artplayer({ + container: '.artplayer-app', + url: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd', + type: 'mpd', + customType: { + mpd: playMpd, + }, +}) + +art.on('ready', () => { + console.info(art.dash) +}) + +===== document.pip.js ===== + +Example 3: Document Picture-in-Picture plugin +This example shows how to use the artplayerPluginDocumentPip plugin to enable Document Picture-in-Picture mode with custom dimensions and fallback options. + +// npm i artplayer-plugin-document-pip +// import artplayerPluginDocumentPip from 'artplayer-plugin-document-pip'; + +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDocumentPip({ + width: 480, + height: 270, + fallbackToVideoPiP: true, + placeholder: `Playing in Document Picture-in-Picture`, + }), + ], +}) + +art.on('document-pip', (state) => { + console.log('Document Picture-in-Picture', state) +}) + +===== flv.js ===== + +Example 4: FLV.js integration +This example demonstrates how to integrate FLV.js with ArtPlayer for FLV video playback, including proper cleanup on player destruction. + +// npm i flv.js +// import flvjs from 'flv.js'; + +function playFlv(video, url, art) { + if (flvjs.isSupported()) { + if (art.flv) + art.flv.destroy() + const flv = flvjs.createPlayer({ type: 'flv', url }) + flv.attachMediaElement(video) + flv.load() + art.flv = flv + art.on('destroy', () => flv.destroy()) + } + else { + art.notice.show = 'Unsupported playback format: flv' + } +} + +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.flv', + type: 'flv', + customType: { + flv: playFlv, + }, +}) + +art.on('ready', () => { + console.info(art.flv) +}) + +===== hls.control.js ===== + +Example 5: HLS.js with quality and audio controls +This example shows HLS.js integration with the artplayerPluginHlsControl plugin, providing quality selection and audio track controls for HLS streams. + +// npm i hls.js +// npm i artplayer-plugin-hls-control + +// import Hls from 'hls.js'; +// import artplayerPluginHlsControl from 'artplayer-plugin-hls-control'; + +const art = new Artplayer({ + container: '.artplayer-app', + url: 'https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/index.m3u8', + setting: true, + plugins: [ + artplayerPluginHlsControl({ + quality: { + // Show qualitys in control + control: true, + // Show qualitys in setting + setting: true, + // Get the quality name from level + getName: level => `${level.height}P`, + // I18n + title: 'Quality', + auto: 'Auto', + }, + audio: { + // Show audios in control + control: true, + // Show audios in setting + setting: true, + // Get the audio name from track + getName: track => track.name, + // I18n + title: 'Audio', + auto: 'Auto', + }, + }), + ], + customType: { + m3u8: function playM3u8(video, url, art) { + if (Hls.isSupported()) { + if (art.hls) + art.hls.destroy() + const hls = new Hls() + hls.loadSource(url) + hls.attachMedia(video) + art.hls = hls + art.on('destroy', () => hls.destroy()) + } + else if (video.canPlayType('application/vnd.apple.mpegurl')) { + video.src = url + } + else { + art.notice.show = 'Unsupported playback format: m3u8' + } + }, + }, +}) + +===== hls.js ===== + +Example 6: Basic HLS.js integration +This example shows a simpler HLS.js implementation with ArtPlayer, handling both native HLS support and HLS.js fallback for broader browser compatibility. + +// npm i hls.js +// import Hls from 'hls.js'; + +function playM3u8(video, url, art) { + if (Hls.isSupported()) { + if (art.hls) + art.hls.destroy() + const hls = new Hls() + hls.loadSource(url) + hls.attachMedia(video) + art.hls = hls + art.on('destroy', () => hls.destroy()) + } + else if (video.canPlayType('application/vnd.apple.mpegurl')) { + video.src = url + } + else { + art.notice.show = 'Unsupported playback format: m3u8' + } +} + +const art = new Artplayer({ + container: '.artplayer-app', + url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', + type: 'm3u8', + customType: { + m3u8: playM3u8, + }, +}) + +art.on('ready', () => { + console.info(art.hls) +}) + +===== iframe.js ===== + +Example 7: Iframe integration plugin +This example demonstrates how to use the ArtplayerPluginIframe to embed ArtPlayer within an iframe and handle cross-frame communication for fullscreen functionality. + +// npm i artplayer-plugin-iframe +// import ArtplayerPluginIframe from 'artplayer-plugin-iframe'; + +const $iframe = document.createElement('iframe') +$iframe.allowFullscreen = true +$iframe.width = '100%' +$iframe.height = '100%' + +const $container = document.querySelector('.artplayer-app') +$container.innerHTML = '' +$container.appendChild($iframe) + +const iframe = new ArtplayerPluginIframe({ + iframe: $iframe, + url: '/iframe.html', +}) + +iframe.message(({ type, data }) => { + switch (type) { + case 'fullscreenWeb': + if (data) { + $iframe.classList.add('fullscreenWeb') + } + else { + $iframe.classList.remove('fullscreenWeb') + } + break + default: + break + } +}) + +iframe.commit(() => { + const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fullscreen: true, + fullscreenWeb: true, + }) + + art.on('fullscreenWeb', (state) => { + ArtplayerPluginIframe.postMessage({ + type: 'fullscreenWeb', + data: state, + }) + }) +}) + +===== index.js ===== + +Example 8: Basic ArtPlayer setup +This appears to be a placeholder for a basic ArtPlayer configuration file, typically used as the main entry point for simple video player implementations. + +Example 1: Basic ArtPlayer Configuration +This example shows a comprehensive ArtPlayer setup with multiple features enabled, including custom settings, context menu, layers, quality options, thumbnails, subtitles, highlights, and custom controls. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + poster: '/assets/sample/poster.jpg', + volume: 0.5, + isLive: false, + muted: false, + autoplay: false, + pip: true, + autoSize: true, + autoMini: true, + screenshot: true, + setting: true, + loop: true, + flip: true, + playbackRate: true, + aspectRatio: true, + fullscreen: true, + fullscreenWeb: true, + subtitleOffset: true, + miniProgressBar: true, + mutex: true, + backdrop: true, + playsInline: true, + autoPlayback: true, + airplay: true, + theme: '#23ade5', + lang: navigator.language.toLowerCase(), + moreVideoAttr: { + crossOrigin: 'anonymous', + }, + settings: [ + { + width: 200, + html: 'Subtitle', + tooltip: 'Bilingual', + icon: '', + selector: [ + { + html: 'Display', + tooltip: 'Show', + switch: true, + onSwitch(item) { + item.tooltip = item.switch ? 'Hide' : 'Show' + art.subtitle.show = !item.switch + return !item.switch + }, + }, + { + default: true, + html: 'Bilingual', + url: '/assets/sample/subtitle.srt', + }, + { + html: 'Chinese', + url: '/assets/sample/subtitle.cn.srt', + }, + { + html: 'Japanese', + url: '/assets/sample/subtitle.jp.srt', + }, + ], + onSelect(item) { + art.subtitle.switch(item.url, { + name: item.html, + }) + return item.html + }, + }, + { + html: 'Switcher', + icon: '', + tooltip: 'OFF', + switch: false, + onSwitch(item) { + item.tooltip = item.switch ? 'OFF' : 'ON' + console.info('You clicked on the custom switch', item.switch) + return !item.switch + }, + }, + { + html: 'Slider', + icon: '', + tooltip: '5x', + range: [5, 1, 10, 0.1], + onRange(item) { + return `${item.range[0]}x` + }, + }, + { + html: 'Button', + icon: '', + tooltip: 'tooltip', + onClick() { + return 'Button clicked' + }, + }, + ], + contextmenu: [ + { + html: 'Custom menu', + click(contextmenu) { + console.info('You clicked on the custom menu') + contextmenu.show = false + }, + }, + ], + layers: [ + { + html: '', + click() { + window.open('https://aimu.app') + console.info('You clicked on the custom layer') + }, + style: { + position: 'absolute', + top: '20px', + right: '20px', + opacity: '.9', + }, + }, + ], + quality: [ + { + default: true, + html: 'SD 480P', + url: '/assets/sample/video.mp4?q=480', + }, + { + html: 'HD 720P', + url: '/assets/sample/video.mp4?q=720', + }, + ], + thumbnails: { + url: '/assets/sample/thumbnails.png', + number: 60, + column: 10, + scale: 0.85, + }, + subtitle: { + url: '/assets/sample/subtitle.srt', + type: 'srt', + style: { + color: '#fe9200', + fontSize: '20px', + }, + encoding: 'utf-8', + }, + highlight: [ + { + time: 15, + text: 'One more chance', + }, + { + time: 30, + text: '谁でもいいはずなのに', + }, + { + time: 45, + text: '夏の想い出がまわる', + }, + { + time: 60, + text: 'こんなとこにあるはずもないのに', + }, + { + time: 75, + text: '终わり', + }, + ], + controls: [ + { + position: 'right', + html: 'Control', + index: 1, + tooltip: 'Control Tooltip', + style: { + marginRight: '20px', + }, + click() { + console.info('You clicked on the custom control') + }, + }, + ], + icons: { + loading: '', + state: '', + indicator: '', + }, +}) + +===== libass.js ===== + +Example 2: Libass Plugin Integration +This example demonstrates how to integrate the libass plugin for advanced ASS subtitle support, including event handling for subtitle operations. + +// npm i artplayer-plugin-libass +// import artplayerPluginLibass from 'artplayer-plugin-libass'; + +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fullscreenWeb: true, + subtitle: { + url: '/assets/sample/style-test.ass', + }, + plugins: [ + artplayerPluginLibass({ + // debug: true, + workerUrl: 'https://unpkg.com/libass-wasm@4.1.0/dist/js/subtitles-octopus-worker.js', + // wasmUrl: 'https://unpkg.com/libass-wasm@4.1.0/dist/js/subtitles-octopus-worker.wasm', + fallbackFont: '/assets/misc/SourceHanSansCN-Bold.woff2', + }), + ], +}) + +// init +art.on('artplayerPluginLibass:init', (adapter) => { + console.info('artplayerPluginLibass:init', adapter) +}) + +// subtitle switch +art.on('artplayerPluginLibass:switch', (url) => { + console.info('artplayerPluginLibass:switch', url) +}) + +// subtitle visible +art.on('artplayerPluginLibass:visible', (visible) => { + console.info('artplayerPluginLibass:visible', visible) +}) + +// subtitle timeOffset +art.on('artplayerPluginLibass:timeOffset', (timeOffset) => { + console.info('artplayerPluginLibass:timeOffset', timeOffset) +}) + +// destroy +art.on('artplayerPluginLibass:destroy', () => { + console.info('artplayerPluginLibass:destroy') +}) + +===== mobile.js ===== + +Example 3: Mobile-Optimized Configuration +This example shows a mobile-optimized ArtPlayer setup with mobile-specific attributes, auto-orientation, and Chinese language interface. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + poster: '/assets/sample/poster.jpg', + autoSize: true, + loop: true, + mutex: true, + setting: true, + flip: true, + lock: true, + fastForward: true, + playbackRate: true, + aspectRatio: true, + theme: '#ff0057', + fullscreen: true, + fullscreenWeb: true, + miniProgressBar: true, + autoOrientation: true, + airplay: true, + moreVideoAttr: { + 'x5-video-player-type': 'h5', + 'x5-video-player-fullscreen': false, + 'x5-video-orientation': 'portraint', + 'preload': 'metadata', + }, + thumbnails: { + url: '/assets/sample/thumbnails.png', + number: 60, + column: 10, + scale: 0.6, + }, + subtitle: { + name: '中日双语', + url: '/assets/sample/subtitle.srt', + style: { + color: '#48aff0', + fontSize: '16px', + }, + }, + layers: [ + { + html: ``, + click() { + art.notice.show = '你点击了自定义层' + }, + style: { + position: 'absolute', + top: '10px', + right: '10px', + opacity: '.9', + }, + }, + ], + icons: { + loading: '', + state: '', + indicator: '', + }, + settings: [ + { + width: 200, + html: '切换字幕', + tooltip: '双语', + icon: '', + selector: [ + { + html: '开关', + switch: true, + tooltip: '显示', + onSwitch(item) { + item.tooltip = item.switch ? '隐藏' : '显示' + art.subtitle.show = !item.switch + return !item.switch + }, + }, + { + default: true, + html: '双语', + url: '/assets/sample/subtitle.srt', + }, + { + html: '中文', + url: '/assets/sample/subtitle.cn.srt', + }, + { + html: '日文', + url: '/assets/sample/subtitle.jp.srt', + }, + ], + onSelect(item) { + art.subtitle.switch(item.url, { + name: item.html, + }) + return item.html + }, + }, + ], +}) + +===== mpegts.js ===== + +Example 4: FLV Playback with mpegts.js +This example demonstrates how to integrate mpegts.js for FLV video playback support with proper cleanup and media element attachment. + +// npm i mpegts +// import mpegts from 'mpegts'; + +function playFlv(video, url, art) { + if (mpegts.isSupported()) { + if (art.flv) + art.flv.destroy() + + const flv = mpegts.createPlayer({ + type: 'flv', + url, + }) + flv.attachMediaElement(video) + flv.load() + flv.play() + +Example: flv.js +This example demonstrates how to play FLV format videos using a custom type handler with ArtPlayer. It shows integration with an external FLV library and proper cleanup on player destruction. + +function playFlv(video, url, art) { + if (flvjs.isSupported()) { + const flv = flvjs.createPlayer({ type: 'flv', url }) + flv.attachMediaElement(video) + flv.load() + art.flv = flv + art.on('destroy', () => flv.destroy()) + } + else { + art.notice.show = 'Unsupported playback format: flv' + } +} + +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.flv', + type: 'flv', + customType: { + flv: playFlv, + }, +}) + +art.on('ready', () => { + console.info(art.flv) +}) + +===== multiple.subtitles.js ===== + +Example: Multiple Subtitles Plugin +This example shows how to use the multiple subtitles plugin with ArtPlayer, featuring bilingual subtitles, custom styling, and interactive settings for subtitle management. + +// npm i artplayer-plugin-multiple-subtitles +// import artplayerPluginMultipleSubtitles from 'artplayer-plugin-multiple-subtitles'; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + plugins: [ + artplayerPluginMultipleSubtitles({ + subtitles: [ + { + name: 'chinese', + url: '/assets/sample/subtitle.cn.srt', + }, + { + name: 'japanese', + url: '/assets/sample/subtitle.jp.srt', + }, + ], + }), + ], + settings: [ + { + width: 200, + html: 'Subtitle', + tooltip: 'Double', + icon: '', + selector: [ + { + html: 'Display', + tooltip: 'Show', + switch: true, + onSwitch(item) { + item.tooltip = item.switch ? 'Hide' : 'Show' + // 显示/隐藏字幕 + // Show/hide subtitles + art.subtitle.show = !item.switch + return !item.switch + }, + }, + { + html: 'Reverse', + tooltip: 'Off', + switch: false, + onSwitch(item) { + item.tooltip = item.switch ? 'Off' : 'On' + // 修改字幕顺序 + // Change the order of subtitles + if (item.switch) { + art.plugins.multipleSubtitles.tracks(['chinese', 'japanese']) + } + else { + art.plugins.multipleSubtitles.tracks(['japanese', 'chinese']) + } + return !item.switch + }, + }, + { + default: true, + html: 'Double', + name: 'double', + }, + { + html: 'Chinese', + name: 'chinese', + }, + { + html: 'Japanese', + name: 'japanese', + }, + ], + onSelect(item) { + if (item.name === 'double') { + // 重置字幕 + // Reset subtitles + art.plugins.multipleSubtitles.reset() + } + else { + // 显示单个字幕 + // Show single subtitle + art.plugins.multipleSubtitles.tracks([item.name]) + } + return item.html + }, + }, + ], +}) + +// 自定义你自己的样式,请勿复制以下代码 +// Customize your own style, please do not copy the following code + +const style = ` +.art-subtitle-chinese { + color: red; + font-size: 18px; +} + +.art-subtitle-japanese { + color: yellow; + font-size: 12px; +} +` + +const $style = document.getElementById('artplayer-subtitle-style') +if ($style) { + $style.textContent = style +} +else { + const $style = document.createElement('style') + $style.id = 'artplayer-subtitle-style' + $style.textContent = style + document.head.appendChild($style) +} + +===== setting.test.js ===== + +Example: Settings API Testing +This example demonstrates comprehensive testing of ArtPlayer's settings API, including custom settings, dynamic updates, and error handling for various setting types. + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + setting: true, + flip: true, + playbackRate: true, + aspectRatio: true, + subtitleOffset: true, + settings: [ + { + width: 200, + html: 'Subtitle', + name: 'subtitle', + tooltip: 'Bilingual', + icon: '', + selector: [ + { + html: 'Display', + tooltip: 'Show', + switch: true, + onSwitch(item) { + item.tooltip = item.switch ? 'Hide' : 'Show' + art.subtitle.show = !item.switch + return !item.switch + }, + }, + { + default: true, + html: 'Bilingual', + url: '/assets/sample/subtitle.srt', + }, + { + html: 'Chinese', + url: '/assets/sample/subtitle.cn.srt', + }, + { + html: 'Japanese', + url: '/assets/sample/subtitle.jp.srt', + }, + ], + onSelect(item) { + art.subtitle.switch(item.url, { + name: item.html, + }) + return item.html + }, + mounted(...args) { + console.info(args) + }, + }, + { + html: 'Switcher', + icon: '', + tooltip: 'OFF', + switch: false, + onSwitch(item) { + item.tooltip = item.switch ? 'OFF' : 'ON' + console.info('You clicked on the custom switch', item.switch) + return !item.switch + }, + mounted(...args) { + console.info(args) + }, + }, + { + html: 'Slider', + icon: '', + tooltip: '5x', + range: [5, 1, 10, 0.1], + onRange(item) { + return `${item.range[0]}x` + }, + mounted(...args) { + console.info(args) + }, + }, + ], +}, async () => { + const { sleep } = Artplayer.utils + art.setting.show = true + console.log(art.setting.builtin) + console.log(art.setting.find('aspect-ratio')) + console.log(art.setting.find('aspect-ratio2')) + await sleep(1000) + art.setting.resize() + await sleep(1000) + art.setting.inactivate(art.setting.find('subtitle')) + art.setting.remove('aspect-ratio') + try { + art.setting.remove('aspect-ratio2') + } + catch (error) { + console.log(error.message) + } + await sleep(1000) + art.setting.update({ + name: 'subtitle-offset', + html: 'new offset', + range: [5, -11, 11, 1], + }) + await sleep(1000) + art.setting.find('subtitle-offset').range = [0, -0, 10, 1] + await sleep(1000) + art.setting.update({ + name: 'subtitle-offset2', + html: 'new offset 2', + range: [5, -11, 11, 1], + onChange(item) { + return `${item.range[0]}s` + }, + }) + await sleep(1000) + art.setting.update({ + name: 'flip', + html: 'new flip', + tooltip: 'OFF', + switch: false, + }) + await sleep(1000) + art.setting.find('flip').switch = true + await sleep(1000) + art.setting.update({ + name: 'flip2', + html: 'new flip2', + tooltip: 'OFF', + switch: true, + }) + await sleep(1000) + try { + art.setting.add({ + name: 'flip2', + html: 'new flip2', + tooltip: 'OFF', + switch: true, + }) + } + catch (error) { + console.log(error.message) + } +}) + +===== thumbnail.js ===== + +Example: Thumbnail Plugin +This example demonstrates the thumbnail preview plugin for ArtPlayer, showing hover-based video thumbnails with customizable size and quantity. + +// npm i artplayer-plugin-thumbnail +// import artplayerPluginThumbnail from 'artplayer-plugin-thumbnail'; + +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginThumbnail({ + width: 160, + number: 100, + scale: 1, + }), + ], +}) + +===== vast.js ===== + +Example: VAST Advertising Plugin +This example shows integration with VAST advertising using Google IMA SDK, demonstrating ad playback triggered by video play events. + +// Depends on: +// https://glomex.github.io/vast-ima-player/ +// https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side + +// Google's IMA SDK are blocked by your Ad blocker. +// Please Turn Off Your Ad Blocker. + +// npm i artplayer-plugin-vast +// import artplayerPluginVast from 'artplayer-plugin-vast'; + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fullscreen: true, + fullscreenWeb: true, + plugins: [ + artplayerPluginVast(({ playUrl, imaPlayer, ima }) => { + // Play the ad when the video is played + art.once('play', () => { + playUrl('https://artplayer.org/assets/vast/linear-ad.xml') + }) + }), + ], +}) + +===== vtt.thumbnail.js ===== + +Example: VTT Thumbnail Plugin +This example demonstrates using WebVTT files for thumbnail generation, showing how to display thumbnails from a VTT file during video playback. + +// npm i artplayer-plugin-vtt-thumbnail +// import artplayerPluginVttThumbnail from 'artplayer-plugin-vtt-thumbnail'; + +const art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/bbb-video.mp4', + plugins: [ + artplayerPluginVttThumbnail({ + vtt: '/assets/sample/bbb-thumbnails.vtt', + }), + ], +}) + +===== webtorrent.js ===== + +Example: WebTorrent Integration +This example shows the setup for WebTorrent integration with ArtPlayer, enabling torrent-based video streaming (implementation details would follow). + +// npm i webtorrent +// import WebTorrent from 'webtorrent'; + +Full example code: + +async function playTorrent(video, url, art) { + if (WebTorrent.WEBRTC_SUPPORT) { + if (art.torrent) + art.torrent.destroy() + art.torrent = new WebTorrent() + + await navigator.serviceWorker.register('/webtorrent.sw.min.js') + art.torrent.loadWorker(navigator.serviceWorker.controller) + + art.torrent.add(url, (torrent) => { + const file = torrent.files.find((file) => { + return file.name.endsWith('.mp4') + }) + file.streamTo(video) + }) + + art.on('destroy', () => art.torrent.destroy()) + } + else { + art.notice.show = 'Unsupported playback format: torrent' + } +} + +const art = new Artplayer({ + container: '.artplayer-app', + url: 'magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent', + type: 'torrent', + customType: { + torrent: playTorrent, + }, +}) + +art.on('ready', () => { + console.info(art.torrent) +}) + +This example demonstrates WebTorrent integration with ArtPlayer, showing how to stream torrent content directly in the video player. The playTorrent function checks for WebRTC support, initializes WebTorrent, registers a service worker, and streams MP4 files from torrents to the video element. The ArtPlayer configuration uses a magnet URI as the video source and defines a custom 'torrent' type that calls the playTorrent function. The ready event listener logs the torrent instance when the player is initialized. Key APIs used include WebTorrent for torrent streaming, ArtPlayer's customType for custom video handlers, and the player's event system for lifecycle management. \ No newline at end of file diff --git a/docs/uncompiled/artplayer/index.js b/docs/uncompiled/artplayer/index.js index 7bf25be13..018cd12d3 100644 --- a/docs/uncompiled/artplayer/index.js +++ b/docs/uncompiled/artplayer/index.js @@ -428,7 +428,7 @@ if (_utils.isBrowser) { } },{"bundle-text:./style/index.less":"bFGMG","option-validator":"9hUEm","../package.json":"3r4tp","./config":"1dlGA","./contextmenu":"dx7Zz","./control":"cNVpy","./events":"dQzlM","./hotkey":"drylQ","./i18n":"3Jf1s","./icons":"avFu3","./info":"gvEup","./layer":"7Oas9","./loading":"glZdQ","./mask":"iGxU6","./notice":"jZEIz","./player":"iA4EC","./plugins":"6P64r","./scheme":"8YPbR","./setting":"7ATN8","./storage":"grHII","./subtitle":"hxr0l","./template":"5JwMQ","./utils":"gGxPm","./utils/emitter":"2qvfs","@parcel/transformer-js/src/esmodule-helpers.js":"8oCsH"}],"bFGMG":[function(require,module,exports,__globalThis) { -module.exports = ".art-video-player {\n --art-theme: red;\n --art-font-color: #fff;\n --art-background-color: #000;\n --art-text-shadow-color: #00000080;\n --art-transition-duration: .2s;\n --art-padding: 10px;\n --art-border-radius: 3px;\n --art-progress-height: 6px;\n --art-progress-color: #ffffff40;\n --art-hover-color: #ffffff40;\n --art-loaded-color: #ffffff40;\n --art-state-size: 80px;\n --art-state-opacity: .8;\n --art-bottom-height: 100px;\n --art-bottom-offset: 20px;\n --art-bottom-gap: 5px;\n --art-highlight-width: 8px;\n --art-highlight-color: #ffffff80;\n --art-control-height: 46px;\n --art-control-opacity: .75;\n --art-control-icon-size: 36px;\n --art-control-icon-scale: 1.1;\n --art-volume-height: 120px;\n --art-volume-handle-size: 14px;\n --art-lock-size: 36px;\n --art-indicator-scale: 0;\n --art-indicator-size: 16px;\n --art-fullscreen-web-index: 9999;\n --art-settings-icon-size: 24px;\n --art-settings-max-height: 300px;\n --art-selector-max-height: 300px;\n --art-contextmenus-min-width: 250px;\n --art-subtitle-font-size: 20px;\n --art-subtitle-gap: 5px;\n --art-subtitle-bottom: 15px;\n --art-subtitle-border: #000;\n --art-widget-background: #000000d9;\n --art-tip-background: #000000b3;\n --art-scrollbar-size: 4px;\n --art-scrollbar-background: #ffffff40;\n --art-scrollbar-background-hover: #ffffff80;\n --art-mini-progress-height: 2px;\n}\n\n.art-bg-cover {\n background-position: center;\n background-repeat: no-repeat;\n background-size: cover;\n}\n\n.art-bottom-gradient {\n background-image: linear-gradient(to top, #000, #0006, #0000);\n background-position: bottom;\n background-repeat: repeat-x;\n}\n\n.art-backdrop-filter {\n backdrop-filter: saturate(180%) blur(20px);\n background-color: #000000bf !important;\n}\n\n.art-truncate {\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.art-video-player {\n zoom: 1;\n text-align: left;\n user-select: none;\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n color: var(--art-font-color);\n background-color: var(--art-background-color);\n text-shadow: 0 0 2px var(--art-text-shadow-color);\n -webkit-tap-highlight-color: #0000;\n -ms-touch-action: manipulation;\n touch-action: manipulation;\n -ms-high-contrast-adjust: none;\n direction: ltr;\n outline: 0;\n margin: 0 auto;\n padding: 0;\n font-family: PingFang SC, Helvetica Neue, Microsoft YaHei, Roboto, Arial, sans-serif;\n font-size: 14px;\n line-height: 1.3;\n position: relative;\n}\n\n.art-video-player *, .art-video-player :before, .art-video-player :after {\n box-sizing: border-box;\n}\n\n.art-video-player ::-webkit-scrollbar {\n width: var(--art-scrollbar-size);\n height: var(--art-scrollbar-size);\n}\n\n.art-video-player ::-webkit-scrollbar-thumb {\n background-color: var(--art-scrollbar-background);\n}\n\n.art-video-player ::-webkit-scrollbar-thumb:hover {\n background-color: var(--art-scrollbar-background-hover);\n}\n\n.art-video-player img {\n vertical-align: top;\n max-width: 100%;\n}\n\n.art-video-player svg {\n fill: var(--art-font-color);\n}\n\n.art-video-player a {\n color: var(--art-font-color);\n text-decoration: none;\n}\n\n.art-icon {\n justify-content: center;\n align-items: center;\n line-height: 1;\n display: flex;\n}\n\n.art-video-player.art-backdrop .art-contextmenus, .art-video-player.art-backdrop .art-info, .art-video-player.art-backdrop .art-settings, .art-video-player.art-backdrop .art-layer-auto-playback, .art-video-player.art-backdrop .art-selector-list, .art-video-player.art-backdrop .art-volume-inner {\n backdrop-filter: saturate(180%) blur(20px);\n background-color: #000000bf !important;\n}\n\n.art-video {\n z-index: 10;\n cursor: pointer;\n width: 100%;\n height: 100%;\n position: absolute;\n inset: 0;\n}\n\n.art-poster {\n z-index: 11;\n pointer-events: none;\n background-position: center;\n background-repeat: no-repeat;\n background-size: cover;\n width: 100%;\n height: 100%;\n position: absolute;\n inset: 0;\n}\n\n.art-video-player .art-subtitle {\n z-index: 20;\n text-align: center;\n pointer-events: none;\n justify-content: center;\n align-items: center;\n gap: var(--art-subtitle-gap);\n width: 100%;\n bottom: var(--art-subtitle-bottom);\n font-size: var(--art-subtitle-font-size);\n transition: bottom var(--art-transition-duration) ease;\n text-shadow: var(--art-subtitle-border) 1px 0 1px, var(--art-subtitle-border) 0 1px 1px, var(--art-subtitle-border) -1px 0 1px, var(--art-subtitle-border) 0 -1px 1px, var(--art-subtitle-border) 1px 1px 1px, var(--art-subtitle-border) -1px -1px 1px, var(--art-subtitle-border) 1px -1px 1px, var(--art-subtitle-border) -1px 1px 1px;\n flex-direction: column;\n padding: 0 5%;\n display: none;\n position: absolute;\n}\n\n.art-video-player.art-subtitle-show .art-subtitle {\n display: flex;\n}\n\n.art-video-player.art-control-show .art-subtitle {\n bottom: calc(var(--art-control-height) + var(--art-subtitle-bottom));\n}\n\n.art-danmuku {\n z-index: 30;\n pointer-events: none;\n width: 100%;\n height: 100%;\n position: absolute;\n inset: 0;\n overflow: hidden;\n}\n\n.art-video-player .art-layers {\n z-index: 40;\n pointer-events: none;\n width: 100%;\n height: 100%;\n display: none;\n position: absolute;\n inset: 0;\n}\n\n.art-video-player .art-layers .art-layer {\n pointer-events: auto;\n}\n\n.art-video-player.art-layer-show .art-layers {\n display: flex;\n}\n\n.art-video-player .art-mask {\n z-index: 50;\n pointer-events: none;\n justify-content: center;\n align-items: center;\n width: 100%;\n height: 100%;\n display: flex;\n position: absolute;\n inset: 0;\n}\n\n.art-video-player .art-mask .art-state {\n opacity: 0;\n width: var(--art-state-size);\n height: var(--art-state-size);\n transition: all var(--art-transition-duration) ease;\n justify-content: center;\n align-items: center;\n display: flex;\n transform: scale(2);\n}\n\n.art-video-player.art-mask-show .art-state {\n cursor: pointer;\n pointer-events: auto;\n opacity: var(--art-state-opacity);\n transform: scale(1);\n}\n\n.art-video-player.art-loading-show .art-state {\n display: none;\n}\n\n.art-video-player .art-loading {\n z-index: 70;\n pointer-events: none;\n justify-content: center;\n align-items: center;\n width: 100%;\n height: 100%;\n display: none;\n position: absolute;\n inset: 0;\n}\n\n.art-video-player.art-loading-show .art-loading {\n display: flex;\n}\n\n.art-video-player.art-loading-show .art-mask {\n display: none;\n}\n\n.art-video-player .art-bottom {\n z-index: 60;\n opacity: 0;\n pointer-events: none;\n width: 100%;\n height: 100%;\n padding: 0 var(--art-padding);\n transition: all var(--art-transition-duration) ease;\n background-size: 100% var(--art-bottom-height);\n background-image: linear-gradient(to top, #000, #0006, #0000);\n background-position: bottom;\n background-repeat: repeat-x;\n flex-direction: column;\n justify-content: flex-end;\n display: flex;\n position: absolute;\n inset: 0;\n overflow: hidden;\n}\n\n.art-video-player .art-bottom .art-controls, .art-video-player .art-bottom .art-progress {\n transform: translateY(var(--art-bottom-offset));\n transition: transform var(--art-transition-duration) ease;\n}\n\n.art-video-player.art-control-show .art-bottom, .art-video-player.art-hover .art-bottom {\n opacity: 1;\n}\n\n.art-video-player.art-control-show .art-bottom .art-controls, .art-video-player.art-hover .art-bottom .art-controls, .art-video-player.art-control-show .art-bottom .art-progress, .art-video-player.art-hover .art-bottom .art-progress {\n transform: translateY(0);\n}\n\n.art-bottom .art-progress {\n z-index: 0;\n pointer-events: auto;\n padding-bottom: var(--art-bottom-gap);\n position: relative;\n}\n\n.art-bottom .art-progress .art-control-progress {\n cursor: pointer;\n height: var(--art-progress-height);\n justify-content: center;\n align-items: center;\n display: flex;\n position: relative;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner {\n width: 100%;\n height: 50%;\n transition: height var(--art-transition-duration) ease;\n background-color: var(--art-progress-color);\n align-items: center;\n display: flex;\n position: relative;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-hover {\n z-index: 0;\n background-color: var(--art-hover-color);\n width: 0%;\n height: 100%;\n position: absolute;\n inset: 0;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-loaded {\n z-index: 10;\n background-color: var(--art-loaded-color);\n width: 0%;\n height: 100%;\n position: absolute;\n inset: 0;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-played {\n z-index: 20;\n background-color: var(--art-theme);\n width: 0%;\n height: 100%;\n position: absolute;\n inset: 0;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-highlight {\n z-index: 30;\n pointer-events: none;\n width: 100%;\n height: 100%;\n position: absolute;\n inset: 0;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-highlight span {\n z-index: 0;\n pointer-events: auto;\n width: 100%;\n height: 100%;\n transform: translateX(calc(var(--art-highlight-width) / -2));\n background-color: var(--art-highlight-color);\n position: absolute;\n inset: 0 auto 0 0;\n width: var(--art-highlight-width) !important;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-indicator {\n z-index: 40;\n width: var(--art-indicator-size);\n height: var(--art-indicator-size);\n transform: scale(var(--art-indicator-scale));\n margin-left: calc(var(--art-indicator-size) / -2);\n transition: transform var(--art-transition-duration) ease;\n border-radius: 50%;\n justify-content: center;\n align-items: center;\n display: flex;\n position: absolute;\n left: 0;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-indicator .art-icon {\n pointer-events: none;\n width: 100%;\n height: 100%;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-indicator:hover {\n transform: scale(1.2) !important;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-indicator:active {\n transform: scale(1) !important;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-tip {\n z-index: 50;\n border-radius: var(--art-border-radius);\n white-space: nowrap;\n background-color: var(--art-tip-background);\n padding: 3px 5px;\n font-size: 12px;\n line-height: 1;\n display: none;\n position: absolute;\n top: -25px;\n left: 0;\n}\n\n.art-bottom .art-progress .art-control-progress:hover .art-control-progress-inner {\n height: 100%;\n}\n\n.art-bottom .art-progress .art-control-thumbnails {\n bottom: calc(var(--art-bottom-gap) + 10px);\n border-radius: var(--art-border-radius);\n pointer-events: none;\n background-color: var(--art-widget-background);\n display: none;\n position: absolute;\n left: 0;\n box-shadow: 0 1px 3px #0003, 0 1px 2px -1px #0003;\n}\n\n.art-bottom:hover .art-progress .art-control-progress .art-control-progress-inner .art-progress-indicator {\n transform: scale(1);\n}\n\n.art-controls {\n z-index: 10;\n pointer-events: auto;\n height: var(--art-control-height);\n justify-content: space-between;\n align-items: center;\n display: flex;\n position: relative;\n}\n\n.art-controls .art-controls-left, .art-controls .art-controls-right {\n height: 100%;\n display: flex;\n}\n\n.art-controls .art-controls-center {\n flex: 1;\n justify-content: center;\n align-items: center;\n height: 100%;\n padding: 0 10px;\n display: none;\n}\n\n.art-controls .art-controls-right {\n justify-content: flex-end;\n}\n\n.art-controls .art-control {\n cursor: pointer;\n white-space: nowrap;\n opacity: var(--art-control-opacity);\n min-height: var(--art-control-height);\n min-width: var(--art-control-height);\n transition: opacity var(--art-transition-duration) ease;\n flex-shrink: 0;\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.art-controls .art-control .art-icon {\n height: var(--art-control-icon-size);\n width: var(--art-control-icon-size);\n transform: scale(var(--art-control-icon-scale));\n transition: transform var(--art-transition-duration) ease;\n}\n\n.art-controls .art-control .art-icon:active {\n transform: scale(calc(var(--art-control-icon-scale) * .8));\n}\n\n.art-controls .art-control:hover {\n opacity: 1;\n}\n\n.art-control-volume {\n position: relative;\n}\n\n.art-control-volume .art-volume-panel {\n text-align: center;\n cursor: default;\n opacity: 0;\n pointer-events: none;\n left: 0;\n right: 0;\n bottom: var(--art-control-height);\n width: var(--art-control-height);\n height: var(--art-volume-height);\n transition: all var(--art-transition-duration) ease;\n justify-content: center;\n align-items: center;\n padding: 0 5px;\n font-size: 12px;\n display: flex;\n position: absolute;\n transform: translateY(10px);\n}\n\n.art-control-volume .art-volume-panel .art-volume-inner {\n border-radius: var(--art-border-radius);\n background-color: var(--art-widget-background);\n flex-direction: column;\n align-items: center;\n gap: 10px;\n width: 100%;\n height: 100%;\n padding: 10px 0 12px;\n display: flex;\n}\n\n.art-control-volume .art-volume-panel .art-volume-inner .art-volume-slider {\n cursor: pointer;\n flex: 1;\n justify-content: center;\n width: 100%;\n display: flex;\n position: relative;\n}\n\n.art-control-volume .art-volume-panel .art-volume-inner .art-volume-slider .art-volume-handle {\n border-radius: var(--art-border-radius);\n background-color: #ffffff40;\n justify-content: center;\n width: 2px;\n display: flex;\n position: relative;\n overflow: hidden;\n}\n\n.art-control-volume .art-volume-panel .art-volume-inner .art-volume-slider .art-volume-handle .art-volume-loaded {\n z-index: 0;\n background-color: var(--art-theme);\n width: 100%;\n height: 100%;\n position: absolute;\n inset: 0;\n}\n\n.art-control-volume .art-volume-panel .art-volume-inner .art-volume-slider .art-volume-indicator {\n width: var(--art-volume-handle-size);\n height: var(--art-volume-handle-size);\n margin-top: calc(var(--art-volume-handle-size) / -2);\n background-color: var(--art-theme);\n transition: transform var(--art-transition-duration) ease;\n border-radius: 100%;\n flex-shrink: 0;\n position: absolute;\n transform: scale(1);\n}\n\n.art-control-volume .art-volume-panel .art-volume-inner .art-volume-slider:active .art-volume-indicator {\n transform: scale(.9);\n}\n\n.art-control-volume:hover .art-volume-panel {\n opacity: 1;\n pointer-events: auto;\n transform: translateY(0);\n}\n\n.art-video-player .art-notice {\n z-index: 80;\n width: 100%;\n height: auto;\n padding: var(--art-padding);\n pointer-events: none;\n display: none;\n position: absolute;\n inset: 0 0 auto;\n}\n\n.art-video-player .art-notice .art-notice-inner {\n border-radius: var(--art-border-radius);\n background-color: var(--art-tip-background);\n padding: 5px;\n line-height: 1;\n display: inline-flex;\n}\n\n.art-video-player.art-notice-show .art-notice {\n display: flex;\n}\n\n.art-video-player .art-contextmenus {\n z-index: 120;\n border-radius: var(--art-border-radius);\n background-color: var(--art-widget-background);\n min-width: var(--art-contextmenus-min-width);\n flex-direction: column;\n padding: 5px 0;\n font-size: 12px;\n display: none;\n position: absolute;\n}\n\n.art-video-player .art-contextmenus .art-contextmenu {\n cursor: pointer;\n border-bottom: 1px solid #ffffff1a;\n padding: 10px 15px;\n display: flex;\n}\n\n.art-video-player .art-contextmenus .art-contextmenu span {\n padding: 0 8px;\n}\n\n.art-video-player .art-contextmenus .art-contextmenu span:hover, .art-video-player .art-contextmenus .art-contextmenu span.art-current {\n color: var(--art-theme);\n}\n\n.art-video-player .art-contextmenus .art-contextmenu:hover {\n background-color: #ffffff1a;\n}\n\n.art-video-player .art-contextmenus .art-contextmenu:last-child {\n border-bottom: none;\n}\n\n.art-video-player.art-contextmenu-show .art-contextmenus {\n display: flex;\n}\n\n.art-video-player .art-settings {\n z-index: 90;\n border-radius: var(--art-border-radius);\n max-height: var(--art-settings-max-height);\n left: auto;\n right: var(--art-padding);\n bottom: var(--art-control-height);\n transition: all var(--art-transition-duration) ease;\n background-color: var(--art-widget-background);\n flex-direction: column;\n display: none;\n position: absolute;\n overflow: hidden auto;\n}\n\n.art-video-player .art-settings .art-setting-panel {\n flex-direction: column;\n display: none;\n}\n\n.art-video-player .art-settings .art-setting-panel.art-current {\n display: flex;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item {\n cursor: pointer;\n transition: background-color var(--art-transition-duration) ease;\n justify-content: space-between;\n align-items: center;\n padding: 0 5px;\n display: flex;\n overflow: hidden;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item:hover {\n background-color: #ffffff1a;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item.art-current {\n color: var(--art-theme);\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item .art-icon-check {\n visibility: hidden;\n height: 15px;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item.art-current .art-icon-check {\n visibility: visible;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-left {\n flex-shrink: 0;\n justify-content: center;\n align-items: center;\n gap: 5px;\n display: flex;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-left .art-setting-item-left-icon {\n height: var(--art-settings-icon-size);\n width: var(--art-settings-icon-size);\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-right {\n justify-content: center;\n align-items: center;\n gap: 5px;\n font-size: 12px;\n display: flex;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-right .art-setting-item-right-tooltip {\n white-space: nowrap;\n color: #ffffff80;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-right .art-setting-item-right-icon {\n justify-content: center;\n align-items: center;\n min-width: 32px;\n height: 24px;\n display: flex;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-right .art-setting-range {\n appearance: none;\n background-color: #fff3;\n outline: none;\n width: 80px;\n height: 3px;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item-back {\n border-bottom: 1px solid #ffffff1a;\n}\n\n.art-video-player.art-setting-show .art-settings {\n display: flex;\n}\n\n.art-video-player .art-info {\n left: var(--art-padding);\n top: var(--art-padding);\n z-index: 100;\n border-radius: var(--art-border-radius);\n background-color: var(--art-widget-background);\n padding: 10px;\n font-size: 12px;\n display: none;\n position: absolute;\n}\n\n.art-video-player .art-info .art-info-panel {\n flex-direction: column;\n gap: 5px;\n display: flex;\n}\n\n.art-video-player .art-info .art-info-panel .art-info-item {\n align-items: center;\n gap: 5px;\n display: flex;\n}\n\n.art-video-player .art-info .art-info-panel .art-info-item .art-info-title {\n text-align: right;\n width: 100px;\n}\n\n.art-video-player .art-info .art-info-panel .art-info-item .art-info-content {\n text-overflow: ellipsis;\n white-space: nowrap;\n user-select: all;\n width: 250px;\n overflow: hidden;\n}\n\n.art-video-player .art-info .art-info-close {\n cursor: pointer;\n position: absolute;\n top: 5px;\n right: 5px;\n}\n\n.art-video-player.art-info-show .art-info {\n display: flex;\n}\n\n.art-hide-cursor * {\n cursor: none !important;\n}\n\n.art-video-player[data-aspect-ratio] {\n overflow: hidden;\n}\n\n.art-video-player[data-aspect-ratio] .art-video {\n object-fit: fill;\n box-sizing: content-box;\n}\n\n.art-fullscreen {\n --art-progress-height: 8px;\n --art-indicator-size: 20px;\n --art-control-height: 60px;\n --art-control-icon-scale: 1.3;\n}\n\n.art-fullscreen-web {\n --art-progress-height: 8px;\n --art-indicator-size: 20px;\n --art-control-height: 60px;\n --art-control-icon-scale: 1.3;\n z-index: var(--art-fullscreen-web-index);\n width: 100%;\n height: 100%;\n position: fixed;\n inset: 0;\n}\n\n.art-mini-popup {\n z-index: 9999;\n border-radius: var(--art-border-radius);\n cursor: move;\n user-select: none;\n background: #000;\n width: 320px;\n height: 180px;\n transition: opacity .2s;\n position: fixed;\n overflow: hidden;\n box-shadow: 0 0 5px #00000080;\n}\n\n.art-mini-popup svg {\n fill: #fff;\n}\n\n.art-mini-popup .art-video {\n pointer-events: none;\n}\n\n.art-mini-popup .art-mini-close {\n z-index: 20;\n cursor: pointer;\n opacity: 0;\n transition: opacity .2s;\n position: absolute;\n top: 10px;\n right: 10px;\n}\n\n.art-mini-popup .art-mini-state {\n z-index: 30;\n pointer-events: none;\n opacity: 0;\n background-color: #00000040;\n justify-content: center;\n align-items: center;\n width: 100%;\n height: 100%;\n transition: opacity .2s;\n display: flex;\n position: absolute;\n inset: 0;\n}\n\n.art-mini-popup .art-mini-state .art-icon {\n opacity: .75;\n cursor: pointer;\n pointer-events: auto;\n transition: transform .2s;\n transform: scale(3);\n}\n\n.art-mini-popup .art-mini-state .art-icon:active {\n transform: scale(2.5);\n}\n\n.art-mini-popup.art-mini-dragging {\n opacity: .9;\n}\n\n.art-mini-popup:hover .art-mini-close, .art-mini-popup:hover .art-mini-state {\n opacity: 1;\n}\n\n.art-video-player[data-flip=\"horizontal\"] .art-video {\n transform: scaleX(-1);\n}\n\n.art-video-player[data-flip=\"vertical\"] .art-video {\n transform: scaleY(-1);\n}\n\n.art-video-player .art-layer-lock {\n height: var(--art-lock-size);\n width: var(--art-lock-size);\n top: 50%;\n left: var(--art-padding);\n background-color: var(--art-tip-background);\n border-radius: 50%;\n justify-content: center;\n align-items: center;\n display: none;\n position: absolute;\n transform: translateY(-50%);\n}\n\n.art-video-player .art-layer-auto-playback {\n border-radius: var(--art-border-radius);\n left: var(--art-padding);\n bottom: calc(var(--art-control-height) + var(--art-bottom-gap) + 10px);\n background-color: var(--art-widget-background);\n align-items: center;\n gap: 10px;\n padding: 10px;\n line-height: 1;\n display: none;\n position: absolute;\n}\n\n.art-video-player .art-layer-auto-playback .art-auto-playback-close {\n cursor: pointer;\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.art-video-player .art-layer-auto-playback .art-auto-playback-close svg {\n width: 15px;\n height: 15px;\n fill: var(--art-theme);\n}\n\n.art-video-player .art-layer-auto-playback .art-auto-playback-jump {\n color: var(--art-theme);\n cursor: pointer;\n}\n\n.art-video-player.art-lock .art-subtitle {\n bottom: var(--art-subtitle-bottom) !important;\n}\n\n.art-video-player.art-mini-progress-bar .art-bottom, .art-video-player.art-lock .art-bottom {\n opacity: 1;\n background-image: none;\n padding: 0;\n}\n\n.art-video-player.art-mini-progress-bar .art-bottom .art-controls, .art-video-player.art-lock .art-bottom .art-controls, .art-video-player.art-mini-progress-bar .art-bottom .art-progress, .art-video-player.art-lock .art-bottom .art-progress {\n transform: translateY(calc(var(--art-control-height) + var(--art-bottom-gap) + var(--art-progress-height) / 4));\n}\n\n.art-video-player.art-mini-progress-bar .art-bottom .art-progress-indicator, .art-video-player.art-lock .art-bottom .art-progress-indicator {\n display: none !important;\n}\n\n.art-video-player.art-control-show .art-layer-lock {\n display: flex;\n}\n\n.art-control-selector {\n justify-content: center;\n display: flex;\n position: relative;\n}\n\n.art-control-selector .art-selector-list {\n text-align: center;\n border-radius: var(--art-border-radius);\n opacity: 0;\n pointer-events: none;\n bottom: var(--art-control-height);\n max-height: var(--art-selector-max-height);\n background-color: var(--art-widget-background);\n transition: all var(--art-transition-duration) ease;\n flex-direction: column;\n align-items: center;\n display: flex;\n position: absolute;\n overflow: hidden auto;\n transform: translateY(10px);\n}\n\n.art-control-selector .art-selector-list .art-selector-item {\n flex-shrink: 0;\n justify-content: center;\n align-items: center;\n width: 100%;\n padding: 10px 15px;\n line-height: 1;\n display: flex;\n}\n\n.art-control-selector .art-selector-list .art-selector-item:hover {\n background-color: #ffffff1a;\n}\n\n.art-control-selector .art-selector-list .art-selector-item:hover, .art-control-selector .art-selector-list .art-selector-item.art-current {\n color: var(--art-theme);\n}\n\n.art-control-selector:hover .art-selector-list {\n opacity: 1;\n pointer-events: auto;\n transform: translateY(0);\n}\n\n[class*=\"hint--\"] {\n font-style: normal;\n display: inline-block;\n position: relative;\n}\n\n[class*=\"hint--\"]:before, [class*=\"hint--\"]:after {\n visibility: hidden;\n opacity: 0;\n z-index: 1000000;\n pointer-events: none;\n transition: all .3s;\n position: absolute;\n transform: translate3d(0, 0, 0);\n}\n\n[class*=\"hint--\"]:hover:before, [class*=\"hint--\"]:hover:after {\n visibility: visible;\n opacity: 1;\n transition-delay: .1s;\n}\n\n[class*=\"hint--\"]:before {\n content: \"\";\n z-index: 1000001;\n background: none;\n border: 6px solid #0000;\n position: absolute;\n}\n\n[class*=\"hint--\"]:after {\n color: #fff;\n white-space: nowrap;\n background: #000;\n padding: 8px 10px;\n font-family: Helvetica Neue, Helvetica, Arial, sans-serif;\n font-size: 12px;\n line-height: 12px;\n}\n\n[class*=\"hint--\"][aria-label]:after {\n content: attr(aria-label);\n}\n\n[class*=\"hint--\"][data-hint]:after {\n content: attr(data-hint);\n}\n\n[aria-label=\"\"]:before, [aria-label=\"\"]:after, [data-hint=\"\"]:before, [data-hint=\"\"]:after {\n display: none !important;\n}\n\n.hint--top-left:before, .hint--top-right:before, .hint--top:before {\n border-top-color: #000;\n}\n\n.hint--bottom-left:before, .hint--bottom-right:before, .hint--bottom:before {\n border-bottom-color: #000;\n}\n\n.hint--left:before {\n border-left-color: #000;\n}\n\n.hint--right:before {\n border-right-color: #000;\n}\n\n.hint--top:before {\n margin-bottom: -11px;\n}\n\n.hint--top:before, .hint--top:after {\n bottom: 100%;\n left: 50%;\n}\n\n.hint--top:before {\n left: calc(50% - 6px);\n}\n\n.hint--top:after {\n transform: translateX(-50%);\n}\n\n.hint--top:hover:before {\n transform: translateY(-8px);\n}\n\n.hint--top:hover:after {\n transform: translateX(-50%)translateY(-8px);\n}\n\n.hint--bottom:before {\n margin-top: -11px;\n}\n\n.hint--bottom:before, .hint--bottom:after {\n top: 100%;\n left: 50%;\n}\n\n.hint--bottom:before {\n left: calc(50% - 6px);\n}\n\n.hint--bottom:after {\n transform: translateX(-50%);\n}\n\n.hint--bottom:hover:before {\n transform: translateY(8px);\n}\n\n.hint--bottom:hover:after {\n transform: translateX(-50%)translateY(8px);\n}\n\n.hint--right:before {\n margin-bottom: -6px;\n margin-left: -11px;\n}\n\n.hint--right:after {\n margin-bottom: -14px;\n}\n\n.hint--right:before, .hint--right:after {\n bottom: 50%;\n left: 100%;\n}\n\n.hint--right:hover:before, .hint--right:hover:after {\n transform: translateX(8px);\n}\n\n.hint--left:before {\n margin-bottom: -6px;\n margin-right: -11px;\n}\n\n.hint--left:after {\n margin-bottom: -14px;\n}\n\n.hint--left:before, .hint--left:after {\n bottom: 50%;\n right: 100%;\n}\n\n.hint--left:hover:before, .hint--left:hover:after {\n transform: translateX(-8px);\n}\n\n.hint--top-left:before {\n margin-bottom: -11px;\n}\n\n.hint--top-left:before, .hint--top-left:after {\n bottom: 100%;\n left: 50%;\n}\n\n.hint--top-left:before {\n left: calc(50% - 6px);\n}\n\n.hint--top-left:after {\n margin-left: 12px;\n transform: translateX(-100%);\n}\n\n.hint--top-left:hover:before {\n transform: translateY(-8px);\n}\n\n.hint--top-left:hover:after {\n transform: translateX(-100%)translateY(-8px);\n}\n\n.hint--top-right:before {\n margin-bottom: -11px;\n}\n\n.hint--top-right:before, .hint--top-right:after {\n bottom: 100%;\n left: 50%;\n}\n\n.hint--top-right:before {\n left: calc(50% - 6px);\n}\n\n.hint--top-right:after {\n margin-left: -12px;\n transform: translateX(0);\n}\n\n.hint--top-right:hover:before, .hint--top-right:hover:after {\n transform: translateY(-8px);\n}\n\n.hint--bottom-left:before {\n margin-top: -11px;\n}\n\n.hint--bottom-left:before, .hint--bottom-left:after {\n top: 100%;\n left: 50%;\n}\n\n.hint--bottom-left:before {\n left: calc(50% - 6px);\n}\n\n.hint--bottom-left:after {\n margin-left: 12px;\n transform: translateX(-100%);\n}\n\n.hint--bottom-left:hover:before {\n transform: translateY(8px);\n}\n\n.hint--bottom-left:hover:after {\n transform: translateX(-100%)translateY(8px);\n}\n\n.hint--bottom-right:before {\n margin-top: -11px;\n}\n\n.hint--bottom-right:before, .hint--bottom-right:after {\n top: 100%;\n left: 50%;\n}\n\n.hint--bottom-right:before {\n left: calc(50% - 6px);\n}\n\n.hint--bottom-right:after {\n margin-left: -12px;\n transform: translateX(0);\n}\n\n.hint--bottom-right:hover:before, .hint--bottom-right:hover:after {\n transform: translateY(8px);\n}\n\n.hint--small:after, .hint--medium:after, .hint--large:after {\n white-space: normal;\n word-wrap: break-word;\n line-height: 1.4em;\n}\n\n.hint--small:after {\n width: 80px;\n}\n\n.hint--medium:after {\n width: 150px;\n}\n\n.hint--large:after {\n width: 300px;\n}\n\n[class*=\"hint--\"]:after {\n text-shadow: 0 -1px #000;\n box-shadow: 4px 4px 8px #0000004d;\n}\n\n.hint--error:after {\n text-shadow: 0 -1px #592726;\n background-color: #b34e4d;\n}\n\n.hint--error.hint--top-left:before, .hint--error.hint--top-right:before, .hint--error.hint--top:before {\n border-top-color: #b34e4d;\n}\n\n.hint--error.hint--bottom-left:before, .hint--error.hint--bottom-right:before, .hint--error.hint--bottom:before {\n border-bottom-color: #b34e4d;\n}\n\n.hint--error.hint--left:before {\n border-left-color: #b34e4d;\n}\n\n.hint--error.hint--right:before {\n border-right-color: #b34e4d;\n}\n\n.hint--warning:after {\n text-shadow: 0 -1px #6c5328;\n background-color: #c09854;\n}\n\n.hint--warning.hint--top-left:before, .hint--warning.hint--top-right:before, .hint--warning.hint--top:before {\n border-top-color: #c09854;\n}\n\n.hint--warning.hint--bottom-left:before, .hint--warning.hint--bottom-right:before, .hint--warning.hint--bottom:before {\n border-bottom-color: #c09854;\n}\n\n.hint--warning.hint--left:before {\n border-left-color: #c09854;\n}\n\n.hint--warning.hint--right:before {\n border-right-color: #c09854;\n}\n\n.hint--info:after {\n text-shadow: 0 -1px #1a3c4d;\n background-color: #3986ac;\n}\n\n.hint--info.hint--top-left:before, .hint--info.hint--top-right:before, .hint--info.hint--top:before {\n border-top-color: #3986ac;\n}\n\n.hint--info.hint--bottom-left:before, .hint--info.hint--bottom-right:before, .hint--info.hint--bottom:before {\n border-bottom-color: #3986ac;\n}\n\n.hint--info.hint--left:before {\n border-left-color: #3986ac;\n}\n\n.hint--info.hint--right:before {\n border-right-color: #3986ac;\n}\n\n.hint--success:after {\n text-shadow: 0 -1px #1a321a;\n background-color: #458746;\n}\n\n.hint--success.hint--top-left:before, .hint--success.hint--top-right:before, .hint--success.hint--top:before {\n border-top-color: #458746;\n}\n\n.hint--success.hint--bottom-left:before, .hint--success.hint--bottom-right:before, .hint--success.hint--bottom:before {\n border-bottom-color: #458746;\n}\n\n.hint--success.hint--left:before {\n border-left-color: #458746;\n}\n\n.hint--success.hint--right:before {\n border-right-color: #458746;\n}\n\n.hint--always:after, .hint--always:before {\n opacity: 1;\n visibility: visible;\n}\n\n.hint--always.hint--top:before {\n transform: translateY(-8px);\n}\n\n.hint--always.hint--top:after {\n transform: translateX(-50%)translateY(-8px);\n}\n\n.hint--always.hint--top-left:before {\n transform: translateY(-8px);\n}\n\n.hint--always.hint--top-left:after {\n transform: translateX(-100%)translateY(-8px);\n}\n\n.hint--always.hint--top-right:before, .hint--always.hint--top-right:after {\n transform: translateY(-8px);\n}\n\n.hint--always.hint--bottom:before {\n transform: translateY(8px);\n}\n\n.hint--always.hint--bottom:after {\n transform: translateX(-50%)translateY(8px);\n}\n\n.hint--always.hint--bottom-left:before {\n transform: translateY(8px);\n}\n\n.hint--always.hint--bottom-left:after {\n transform: translateX(-100%)translateY(8px);\n}\n\n.hint--always.hint--bottom-right:before, .hint--always.hint--bottom-right:after {\n transform: translateY(8px);\n}\n\n.hint--always.hint--left:before, .hint--always.hint--left:after {\n transform: translateX(-8px);\n}\n\n.hint--always.hint--right:before, .hint--always.hint--right:after {\n transform: translateX(8px);\n}\n\n.hint--rounded:after {\n border-radius: 4px;\n}\n\n.hint--no-animate:before, .hint--no-animate:after {\n transition-duration: 0s;\n}\n\n.hint--bounce:before, .hint--bounce:after {\n -webkit-transition: opacity .3s, visibility .3s, -webkit-transform .3s cubic-bezier(.71, 1.7, .77, 1.24);\n -moz-transition: opacity .3s, visibility .3s, -moz-transform .3s cubic-bezier(.71, 1.7, .77, 1.24);\n transition: opacity .3s, visibility .3s, transform .3s cubic-bezier(.71, 1.7, .77, 1.24);\n}\n\n.hint--no-shadow:before, .hint--no-shadow:after {\n text-shadow: initial;\n box-shadow: initial;\n}\n\n.hint--no-arrow:before {\n display: none;\n}\n\n.art-video-player.art-mobile {\n --art-bottom-gap: 10px;\n --art-control-height: 38px;\n --art-control-icon-scale: 1;\n --art-state-size: 60px;\n --art-settings-max-height: 180px;\n --art-selector-max-height: 180px;\n --art-indicator-scale: 1;\n --art-control-opacity: 1;\n}\n\n.art-video-player.art-mobile .art-controls-left {\n margin-left: calc(var(--art-padding) / -1);\n}\n\n.art-video-player.art-mobile .art-controls-right {\n margin-right: calc(var(--art-padding) / -1);\n}\n"; +module.exports = ".art-video-player {\n --art-theme: red;\n --art-font-color: #fff;\n --art-background-color: #000;\n --art-text-shadow-color: #00000080;\n --art-transition-duration: .2s;\n --art-padding: 10px;\n --art-border-radius: 3px;\n --art-progress-height: 6px;\n --art-progress-color: #ffffff40;\n --art-progress-top-gap: 8px;\n --art-hover-color: #ffffff40;\n --art-loaded-color: #ffffff40;\n --art-state-size: 80px;\n --art-state-opacity: .8;\n --art-bottom-height: 100px;\n --art-bottom-offset: 20px;\n --art-bottom-gap: 5px;\n --art-highlight-width: 8px;\n --art-highlight-color: #ffffff80;\n --art-control-height: 46px;\n --art-control-opacity: .75;\n --art-control-icon-size: 36px;\n --art-control-icon-scale: 1.1;\n --art-volume-height: 120px;\n --art-volume-handle-size: 14px;\n --art-lock-size: 36px;\n --art-indicator-scale: 0;\n --art-indicator-size: 16px;\n --art-fullscreen-web-index: 9999;\n --art-settings-icon-size: 24px;\n --art-settings-max-height: 300px;\n --art-selector-max-height: 300px;\n --art-contextmenus-min-width: 250px;\n --art-subtitle-font-size: 20px;\n --art-subtitle-gap: 5px;\n --art-subtitle-bottom: 15px;\n --art-subtitle-border: #000;\n --art-widget-background: #000000d9;\n --art-tip-background: #000000b3;\n --art-scrollbar-size: 4px;\n --art-scrollbar-background: #ffffff40;\n --art-scrollbar-background-hover: #ffffff80;\n --art-mini-progress-height: 2px;\n}\n\n.art-bg-cover {\n background-position: center;\n background-repeat: no-repeat;\n background-size: cover;\n}\n\n.art-bottom-gradient {\n background-image: linear-gradient(to top, #000, #0006, #0000);\n background-position: bottom;\n background-repeat: repeat-x;\n}\n\n.art-backdrop-filter {\n backdrop-filter: saturate(180%) blur(20px);\n background-color: #000000bf !important;\n}\n\n.art-truncate {\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.art-video-player {\n zoom: 1;\n text-align: left;\n user-select: none;\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n color: var(--art-font-color);\n background-color: var(--art-background-color);\n text-shadow: 0 0 2px var(--art-text-shadow-color);\n -webkit-tap-highlight-color: #0000;\n -ms-touch-action: manipulation;\n touch-action: manipulation;\n -ms-high-contrast-adjust: none;\n direction: ltr;\n outline: 0;\n margin: 0 auto;\n padding: 0;\n font-family: PingFang SC, Helvetica Neue, Microsoft YaHei, Roboto, Arial, sans-serif;\n font-size: 14px;\n line-height: 1.3;\n position: relative;\n}\n\n.art-video-player *, .art-video-player :before, .art-video-player :after {\n box-sizing: border-box;\n}\n\n.art-video-player ::-webkit-scrollbar {\n width: var(--art-scrollbar-size);\n height: var(--art-scrollbar-size);\n}\n\n.art-video-player ::-webkit-scrollbar-thumb {\n background-color: var(--art-scrollbar-background);\n}\n\n.art-video-player ::-webkit-scrollbar-thumb:hover {\n background-color: var(--art-scrollbar-background-hover);\n}\n\n.art-video-player img {\n vertical-align: top;\n max-width: 100%;\n}\n\n.art-video-player svg {\n fill: var(--art-font-color);\n}\n\n.art-video-player a {\n color: var(--art-font-color);\n text-decoration: none;\n}\n\n.art-icon {\n justify-content: center;\n align-items: center;\n line-height: 1;\n display: flex;\n}\n\n.art-video-player.art-backdrop .art-contextmenus, .art-video-player.art-backdrop .art-info, .art-video-player.art-backdrop .art-settings, .art-video-player.art-backdrop .art-layer-auto-playback, .art-video-player.art-backdrop .art-selector-list, .art-video-player.art-backdrop .art-volume-inner {\n backdrop-filter: saturate(180%) blur(20px);\n background-color: #000000bf !important;\n}\n\n.art-video {\n z-index: 10;\n cursor: pointer;\n width: 100%;\n height: 100%;\n position: absolute;\n inset: 0;\n}\n\n.art-poster {\n z-index: 11;\n pointer-events: none;\n background-position: center;\n background-repeat: no-repeat;\n background-size: cover;\n width: 100%;\n height: 100%;\n position: absolute;\n inset: 0;\n}\n\n.art-video-player .art-subtitle {\n z-index: 20;\n text-align: center;\n pointer-events: none;\n justify-content: center;\n align-items: center;\n gap: var(--art-subtitle-gap);\n width: 100%;\n bottom: var(--art-subtitle-bottom);\n font-size: var(--art-subtitle-font-size);\n transition: bottom var(--art-transition-duration) ease;\n text-shadow: var(--art-subtitle-border) 1px 0 1px, var(--art-subtitle-border) 0 1px 1px, var(--art-subtitle-border) -1px 0 1px, var(--art-subtitle-border) 0 -1px 1px, var(--art-subtitle-border) 1px 1px 1px, var(--art-subtitle-border) -1px -1px 1px, var(--art-subtitle-border) 1px -1px 1px, var(--art-subtitle-border) -1px 1px 1px;\n flex-direction: column;\n padding: 0 5%;\n display: none;\n position: absolute;\n}\n\n.art-video-player.art-subtitle-show .art-subtitle {\n display: flex;\n}\n\n.art-video-player.art-control-show .art-subtitle {\n bottom: calc(var(--art-control-height) + var(--art-subtitle-bottom));\n}\n\n.art-danmuku {\n z-index: 30;\n pointer-events: none;\n width: 100%;\n height: 100%;\n position: absolute;\n inset: 0;\n overflow: hidden;\n}\n\n.art-video-player .art-layers {\n z-index: 40;\n pointer-events: none;\n width: 100%;\n height: 100%;\n display: none;\n position: absolute;\n inset: 0;\n}\n\n.art-video-player .art-layers .art-layer {\n pointer-events: auto;\n}\n\n.art-video-player.art-layer-show .art-layers {\n display: flex;\n}\n\n.art-video-player .art-mask {\n z-index: 50;\n pointer-events: none;\n justify-content: center;\n align-items: center;\n width: 100%;\n height: 100%;\n display: flex;\n position: absolute;\n inset: 0;\n}\n\n.art-video-player .art-mask .art-state {\n opacity: 0;\n width: var(--art-state-size);\n height: var(--art-state-size);\n transition: all var(--art-transition-duration) ease;\n justify-content: center;\n align-items: center;\n display: flex;\n transform: scale(2);\n}\n\n.art-video-player.art-mask-show .art-state {\n cursor: pointer;\n pointer-events: auto;\n opacity: var(--art-state-opacity);\n transform: scale(1);\n}\n\n.art-video-player.art-loading-show .art-state {\n display: none;\n}\n\n.art-video-player .art-loading {\n z-index: 70;\n pointer-events: none;\n justify-content: center;\n align-items: center;\n width: 100%;\n height: 100%;\n display: none;\n position: absolute;\n inset: 0;\n}\n\n.art-video-player.art-loading-show .art-loading {\n display: flex;\n}\n\n.art-video-player.art-loading-show .art-mask {\n display: none;\n}\n\n.art-video-player .art-bottom {\n z-index: 60;\n opacity: 0;\n pointer-events: none;\n width: 100%;\n height: 100%;\n padding: 0 var(--art-padding);\n transition: all var(--art-transition-duration) ease;\n background-size: 100% var(--art-bottom-height);\n background-image: linear-gradient(to top, #000, #0006, #0000);\n background-position: bottom;\n background-repeat: repeat-x;\n flex-direction: column;\n justify-content: flex-end;\n display: flex;\n position: absolute;\n inset: 0;\n overflow: hidden;\n}\n\n.art-video-player .art-bottom .art-controls, .art-video-player .art-bottom .art-progress {\n transform: translateY(var(--art-bottom-offset));\n transition: transform var(--art-transition-duration) ease;\n}\n\n.art-video-player.art-control-show .art-bottom, .art-video-player.art-hover .art-bottom {\n opacity: 1;\n}\n\n.art-video-player.art-control-show .art-bottom .art-controls, .art-video-player.art-hover .art-bottom .art-controls, .art-video-player.art-control-show .art-bottom .art-progress, .art-video-player.art-hover .art-bottom .art-progress {\n transform: translateY(0);\n}\n\n.art-bottom .art-progress {\n z-index: 0;\n pointer-events: auto;\n padding-top: var(--art-progress-top-gap);\n padding-bottom: var(--art-bottom-gap);\n position: relative;\n}\n\n.art-bottom .art-progress .art-control-progress {\n cursor: pointer;\n height: var(--art-progress-height);\n justify-content: center;\n align-items: center;\n display: flex;\n position: relative;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner {\n width: 100%;\n height: 50%;\n transition: height var(--art-transition-duration) ease;\n background-color: var(--art-progress-color);\n align-items: center;\n display: flex;\n position: relative;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-hover {\n z-index: 0;\n background-color: var(--art-hover-color);\n width: 0%;\n height: 100%;\n position: absolute;\n inset: 0;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-loaded {\n z-index: 10;\n background-color: var(--art-loaded-color);\n width: 0%;\n height: 100%;\n position: absolute;\n inset: 0;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-played {\n z-index: 20;\n background-color: var(--art-theme);\n width: 0%;\n height: 100%;\n position: absolute;\n inset: 0;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-highlight {\n z-index: 30;\n pointer-events: none;\n width: 100%;\n height: 100%;\n position: absolute;\n inset: 0;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-highlight span {\n z-index: 0;\n pointer-events: auto;\n width: 100%;\n height: 100%;\n transform: translateX(calc(var(--art-highlight-width) / -2));\n background-color: var(--art-highlight-color);\n position: absolute;\n inset: 0 auto 0 0;\n width: var(--art-highlight-width) !important;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-indicator {\n z-index: 40;\n width: var(--art-indicator-size);\n height: var(--art-indicator-size);\n transform: scale(var(--art-indicator-scale));\n margin-left: calc(var(--art-indicator-size) / -2);\n transition: transform var(--art-transition-duration) ease;\n border-radius: 50%;\n justify-content: center;\n align-items: center;\n display: flex;\n position: absolute;\n left: 0;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-indicator .art-icon {\n pointer-events: none;\n width: 100%;\n height: 100%;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-indicator:hover {\n transform: scale(1.2) !important;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-indicator:active {\n transform: scale(1) !important;\n}\n\n.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-tip {\n z-index: 50;\n border-radius: var(--art-border-radius);\n white-space: nowrap;\n background-color: var(--art-tip-background);\n padding: 3px 5px;\n font-size: 12px;\n line-height: 1;\n display: none;\n position: absolute;\n top: -25px;\n left: 0;\n}\n\n.art-bottom .art-progress .art-control-progress:hover .art-control-progress-inner {\n height: 100%;\n}\n\n.art-bottom .art-progress .art-control-thumbnails {\n bottom: calc(var(--art-bottom-gap) + 10px);\n border-radius: var(--art-border-radius);\n pointer-events: none;\n background-color: var(--art-widget-background);\n display: none;\n position: absolute;\n left: 0;\n box-shadow: 0 1px 3px #0003, 0 1px 2px -1px #0003;\n}\n\n.art-bottom:hover .art-progress .art-control-progress .art-control-progress-inner .art-progress-indicator {\n transform: scale(1);\n}\n\n.art-controls {\n z-index: 10;\n pointer-events: auto;\n height: var(--art-control-height);\n justify-content: space-between;\n align-items: center;\n display: flex;\n position: relative;\n}\n\n.art-controls .art-controls-left, .art-controls .art-controls-right {\n height: 100%;\n display: flex;\n}\n\n.art-controls .art-controls-center {\n flex: 1;\n justify-content: center;\n align-items: center;\n height: 100%;\n padding: 0 10px;\n display: none;\n}\n\n.art-controls .art-controls-right {\n justify-content: flex-end;\n}\n\n.art-controls .art-control {\n cursor: pointer;\n white-space: nowrap;\n opacity: var(--art-control-opacity);\n min-height: var(--art-control-height);\n min-width: var(--art-control-height);\n transition: opacity var(--art-transition-duration) ease;\n flex-shrink: 0;\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.art-controls .art-control .art-icon {\n height: var(--art-control-icon-size);\n width: var(--art-control-icon-size);\n transform: scale(var(--art-control-icon-scale));\n transition: transform var(--art-transition-duration) ease;\n}\n\n.art-controls .art-control .art-icon:active {\n transform: scale(calc(var(--art-control-icon-scale) * .8));\n}\n\n.art-controls .art-control:hover {\n opacity: 1;\n}\n\n.art-control-volume {\n position: relative;\n}\n\n.art-control-volume .art-volume-panel {\n text-align: center;\n cursor: default;\n opacity: 0;\n pointer-events: none;\n left: 0;\n right: 0;\n bottom: var(--art-control-height);\n width: var(--art-control-height);\n height: var(--art-volume-height);\n transition: all var(--art-transition-duration) ease;\n justify-content: center;\n align-items: center;\n padding: 0 5px;\n font-size: 12px;\n display: flex;\n position: absolute;\n transform: translateY(10px);\n}\n\n.art-control-volume .art-volume-panel .art-volume-inner {\n border-radius: var(--art-border-radius);\n background-color: var(--art-widget-background);\n flex-direction: column;\n align-items: center;\n gap: 10px;\n width: 100%;\n height: 100%;\n padding: 10px 0 12px;\n display: flex;\n}\n\n.art-control-volume .art-volume-panel .art-volume-inner .art-volume-slider {\n cursor: pointer;\n flex: 1;\n justify-content: center;\n width: 100%;\n display: flex;\n position: relative;\n}\n\n.art-control-volume .art-volume-panel .art-volume-inner .art-volume-slider .art-volume-handle {\n border-radius: var(--art-border-radius);\n background-color: #ffffff40;\n justify-content: center;\n width: 2px;\n display: flex;\n position: relative;\n overflow: hidden;\n}\n\n.art-control-volume .art-volume-panel .art-volume-inner .art-volume-slider .art-volume-handle .art-volume-loaded {\n z-index: 0;\n background-color: var(--art-theme);\n width: 100%;\n height: 100%;\n position: absolute;\n inset: 0;\n}\n\n.art-control-volume .art-volume-panel .art-volume-inner .art-volume-slider .art-volume-indicator {\n width: var(--art-volume-handle-size);\n height: var(--art-volume-handle-size);\n margin-top: calc(var(--art-volume-handle-size) / -2);\n background-color: var(--art-theme);\n transition: transform var(--art-transition-duration) ease;\n border-radius: 100%;\n flex-shrink: 0;\n position: absolute;\n transform: scale(1);\n}\n\n.art-control-volume .art-volume-panel .art-volume-inner .art-volume-slider:active .art-volume-indicator {\n transform: scale(.9);\n}\n\n.art-control-volume:hover .art-volume-panel {\n opacity: 1;\n pointer-events: auto;\n transform: translateY(0);\n}\n\n.art-video-player .art-notice {\n z-index: 80;\n width: 100%;\n height: auto;\n padding: var(--art-padding);\n pointer-events: none;\n display: none;\n position: absolute;\n inset: 0 0 auto;\n}\n\n.art-video-player .art-notice .art-notice-inner {\n border-radius: var(--art-border-radius);\n background-color: var(--art-tip-background);\n padding: 5px;\n line-height: 1;\n display: inline-flex;\n}\n\n.art-video-player.art-notice-show .art-notice {\n display: flex;\n}\n\n.art-video-player .art-contextmenus {\n z-index: 120;\n border-radius: var(--art-border-radius);\n background-color: var(--art-widget-background);\n min-width: var(--art-contextmenus-min-width);\n flex-direction: column;\n padding: 5px 0;\n font-size: 12px;\n display: none;\n position: absolute;\n}\n\n.art-video-player .art-contextmenus .art-contextmenu {\n cursor: pointer;\n border-bottom: 1px solid #ffffff1a;\n padding: 10px 15px;\n display: flex;\n}\n\n.art-video-player .art-contextmenus .art-contextmenu span {\n padding: 0 8px;\n}\n\n.art-video-player .art-contextmenus .art-contextmenu span:hover, .art-video-player .art-contextmenus .art-contextmenu span.art-current {\n color: var(--art-theme);\n}\n\n.art-video-player .art-contextmenus .art-contextmenu:hover {\n background-color: #ffffff1a;\n}\n\n.art-video-player .art-contextmenus .art-contextmenu:last-child {\n border-bottom: none;\n}\n\n.art-video-player.art-contextmenu-show .art-contextmenus {\n display: flex;\n}\n\n.art-video-player .art-settings {\n z-index: 90;\n border-radius: var(--art-border-radius);\n max-height: var(--art-settings-max-height);\n left: auto;\n right: var(--art-padding);\n bottom: var(--art-control-height);\n transition: all var(--art-transition-duration) ease;\n background-color: var(--art-widget-background);\n flex-direction: column;\n display: none;\n position: absolute;\n overflow: hidden auto;\n}\n\n.art-video-player .art-settings .art-setting-panel {\n flex-direction: column;\n display: none;\n}\n\n.art-video-player .art-settings .art-setting-panel.art-current {\n display: flex;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item {\n cursor: pointer;\n transition: background-color var(--art-transition-duration) ease;\n justify-content: space-between;\n align-items: center;\n padding: 0 5px;\n display: flex;\n overflow: hidden;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item:hover {\n background-color: #ffffff1a;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item.art-current {\n color: var(--art-theme);\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item .art-icon-check {\n visibility: hidden;\n height: 15px;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item.art-current .art-icon-check {\n visibility: visible;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-left {\n flex-shrink: 0;\n justify-content: center;\n align-items: center;\n gap: 5px;\n display: flex;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-left .art-setting-item-left-icon {\n height: var(--art-settings-icon-size);\n width: var(--art-settings-icon-size);\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-right {\n justify-content: center;\n align-items: center;\n gap: 5px;\n font-size: 12px;\n display: flex;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-right .art-setting-item-right-tooltip {\n white-space: nowrap;\n color: #ffffff80;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-right .art-setting-item-right-icon {\n justify-content: center;\n align-items: center;\n min-width: 32px;\n height: 24px;\n display: flex;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-right .art-setting-range {\n appearance: none;\n background-color: #fff3;\n outline: none;\n width: 80px;\n height: 3px;\n}\n\n.art-video-player .art-settings .art-setting-panel .art-setting-item-back {\n border-bottom: 1px solid #ffffff1a;\n}\n\n.art-video-player.art-setting-show .art-settings {\n display: flex;\n}\n\n.art-video-player .art-info {\n left: var(--art-padding);\n top: var(--art-padding);\n z-index: 100;\n border-radius: var(--art-border-radius);\n background-color: var(--art-widget-background);\n padding: 10px;\n font-size: 12px;\n display: none;\n position: absolute;\n}\n\n.art-video-player .art-info .art-info-panel {\n flex-direction: column;\n gap: 5px;\n display: flex;\n}\n\n.art-video-player .art-info .art-info-panel .art-info-item {\n align-items: center;\n gap: 5px;\n display: flex;\n}\n\n.art-video-player .art-info .art-info-panel .art-info-item .art-info-title {\n text-align: right;\n width: 100px;\n}\n\n.art-video-player .art-info .art-info-panel .art-info-item .art-info-content {\n text-overflow: ellipsis;\n white-space: nowrap;\n user-select: all;\n width: 250px;\n overflow: hidden;\n}\n\n.art-video-player .art-info .art-info-close {\n cursor: pointer;\n position: absolute;\n top: 5px;\n right: 5px;\n}\n\n.art-video-player.art-info-show .art-info {\n display: flex;\n}\n\n.art-hide-cursor * {\n cursor: none !important;\n}\n\n.art-video-player[data-aspect-ratio] {\n overflow: hidden;\n}\n\n.art-video-player[data-aspect-ratio] .art-video {\n object-fit: fill;\n box-sizing: content-box;\n}\n\n.art-fullscreen {\n --art-progress-height: 8px;\n --art-indicator-size: 20px;\n --art-control-height: 60px;\n --art-control-icon-scale: 1.3;\n}\n\n.art-fullscreen-web {\n --art-progress-height: 8px;\n --art-indicator-size: 20px;\n --art-control-height: 60px;\n --art-control-icon-scale: 1.3;\n z-index: var(--art-fullscreen-web-index);\n width: 100%;\n height: 100%;\n position: fixed;\n inset: 0;\n}\n\n.art-mini-popup {\n z-index: 9999;\n border-radius: var(--art-border-radius);\n cursor: move;\n user-select: none;\n background: #000;\n width: 320px;\n height: 180px;\n transition: opacity .2s;\n position: fixed;\n overflow: hidden;\n box-shadow: 0 0 5px #00000080;\n}\n\n.art-mini-popup svg {\n fill: #fff;\n}\n\n.art-mini-popup .art-video {\n pointer-events: none;\n}\n\n.art-mini-popup .art-mini-close {\n z-index: 20;\n cursor: pointer;\n opacity: 0;\n transition: opacity .2s;\n position: absolute;\n top: 10px;\n right: 10px;\n}\n\n.art-mini-popup .art-mini-state {\n z-index: 30;\n pointer-events: none;\n opacity: 0;\n background-color: #00000040;\n justify-content: center;\n align-items: center;\n width: 100%;\n height: 100%;\n transition: opacity .2s;\n display: flex;\n position: absolute;\n inset: 0;\n}\n\n.art-mini-popup .art-mini-state .art-icon {\n opacity: .75;\n cursor: pointer;\n pointer-events: auto;\n transition: transform .2s;\n transform: scale(3);\n}\n\n.art-mini-popup .art-mini-state .art-icon:active {\n transform: scale(2.5);\n}\n\n.art-mini-popup.art-mini-dragging {\n opacity: .9;\n}\n\n.art-mini-popup:hover .art-mini-close, .art-mini-popup:hover .art-mini-state {\n opacity: 1;\n}\n\n.art-video-player[data-flip=\"horizontal\"] .art-video {\n transform: scaleX(-1);\n}\n\n.art-video-player[data-flip=\"vertical\"] .art-video {\n transform: scaleY(-1);\n}\n\n.art-video-player .art-layer-lock {\n height: var(--art-lock-size);\n width: var(--art-lock-size);\n top: 50%;\n left: var(--art-padding);\n background-color: var(--art-tip-background);\n border-radius: 50%;\n justify-content: center;\n align-items: center;\n display: none;\n position: absolute;\n transform: translateY(-50%);\n}\n\n.art-video-player .art-layer-auto-playback {\n border-radius: var(--art-border-radius);\n left: var(--art-padding);\n bottom: calc(var(--art-control-height) + var(--art-bottom-gap) + 10px);\n background-color: var(--art-widget-background);\n align-items: center;\n gap: 10px;\n padding: 10px;\n line-height: 1;\n display: none;\n position: absolute;\n}\n\n.art-video-player .art-layer-auto-playback .art-auto-playback-close {\n cursor: pointer;\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.art-video-player .art-layer-auto-playback .art-auto-playback-close svg {\n width: 15px;\n height: 15px;\n fill: var(--art-theme);\n}\n\n.art-video-player .art-layer-auto-playback .art-auto-playback-jump {\n color: var(--art-theme);\n cursor: pointer;\n}\n\n.art-video-player.art-lock .art-subtitle {\n bottom: var(--art-subtitle-bottom) !important;\n}\n\n.art-video-player.art-mini-progress-bar .art-bottom, .art-video-player.art-lock .art-bottom {\n opacity: 1;\n background-image: none;\n padding: 0;\n}\n\n.art-video-player.art-mini-progress-bar .art-bottom .art-controls, .art-video-player.art-lock .art-bottom .art-controls, .art-video-player.art-mini-progress-bar .art-bottom .art-progress, .art-video-player.art-lock .art-bottom .art-progress {\n transform: translateY(calc(var(--art-control-height) + var(--art-bottom-gap) + var(--art-progress-height) / 4));\n}\n\n.art-video-player.art-mini-progress-bar .art-bottom .art-progress-indicator, .art-video-player.art-lock .art-bottom .art-progress-indicator {\n display: none !important;\n}\n\n.art-video-player.art-control-show .art-layer-lock {\n display: flex;\n}\n\n.art-control-selector {\n justify-content: center;\n display: flex;\n position: relative;\n}\n\n.art-control-selector .art-selector-list {\n text-align: center;\n border-radius: var(--art-border-radius);\n opacity: 0;\n pointer-events: none;\n bottom: var(--art-control-height);\n max-height: var(--art-selector-max-height);\n background-color: var(--art-widget-background);\n transition: all var(--art-transition-duration) ease;\n flex-direction: column;\n align-items: center;\n display: flex;\n position: absolute;\n overflow: hidden auto;\n transform: translateY(10px);\n}\n\n.art-control-selector .art-selector-list .art-selector-item {\n flex-shrink: 0;\n justify-content: center;\n align-items: center;\n width: 100%;\n padding: 10px 15px;\n line-height: 1;\n display: flex;\n}\n\n.art-control-selector .art-selector-list .art-selector-item:hover {\n background-color: #ffffff1a;\n}\n\n.art-control-selector .art-selector-list .art-selector-item:hover, .art-control-selector .art-selector-list .art-selector-item.art-current {\n color: var(--art-theme);\n}\n\n.art-control-selector:hover .art-selector-list {\n opacity: 1;\n pointer-events: auto;\n transform: translateY(0);\n}\n\n[class*=\"hint--\"] {\n font-style: normal;\n display: inline-block;\n position: relative;\n}\n\n[class*=\"hint--\"]:before, [class*=\"hint--\"]:after {\n visibility: hidden;\n opacity: 0;\n z-index: 1000000;\n pointer-events: none;\n transition: all .3s;\n position: absolute;\n transform: translate3d(0, 0, 0);\n}\n\n[class*=\"hint--\"]:hover:before, [class*=\"hint--\"]:hover:after {\n visibility: visible;\n opacity: 1;\n transition-delay: .1s;\n}\n\n[class*=\"hint--\"]:before {\n content: \"\";\n z-index: 1000001;\n background: none;\n border: 6px solid #0000;\n position: absolute;\n}\n\n[class*=\"hint--\"]:after {\n color: #fff;\n white-space: nowrap;\n background: #000;\n padding: 8px 10px;\n font-family: Helvetica Neue, Helvetica, Arial, sans-serif;\n font-size: 12px;\n line-height: 12px;\n}\n\n[class*=\"hint--\"][aria-label]:after {\n content: attr(aria-label);\n}\n\n[class*=\"hint--\"][data-hint]:after {\n content: attr(data-hint);\n}\n\n[aria-label=\"\"]:before, [aria-label=\"\"]:after, [data-hint=\"\"]:before, [data-hint=\"\"]:after {\n display: none !important;\n}\n\n.hint--top-left:before, .hint--top-right:before, .hint--top:before {\n border-top-color: #000;\n}\n\n.hint--bottom-left:before, .hint--bottom-right:before, .hint--bottom:before {\n border-bottom-color: #000;\n}\n\n.hint--left:before {\n border-left-color: #000;\n}\n\n.hint--right:before {\n border-right-color: #000;\n}\n\n.hint--top:before {\n margin-bottom: -11px;\n}\n\n.hint--top:before, .hint--top:after {\n bottom: 100%;\n left: 50%;\n}\n\n.hint--top:before {\n left: calc(50% - 6px);\n}\n\n.hint--top:after {\n transform: translateX(-50%);\n}\n\n.hint--top:hover:before {\n transform: translateY(-8px);\n}\n\n.hint--top:hover:after {\n transform: translateX(-50%)translateY(-8px);\n}\n\n.hint--bottom:before {\n margin-top: -11px;\n}\n\n.hint--bottom:before, .hint--bottom:after {\n top: 100%;\n left: 50%;\n}\n\n.hint--bottom:before {\n left: calc(50% - 6px);\n}\n\n.hint--bottom:after {\n transform: translateX(-50%);\n}\n\n.hint--bottom:hover:before {\n transform: translateY(8px);\n}\n\n.hint--bottom:hover:after {\n transform: translateX(-50%)translateY(8px);\n}\n\n.hint--right:before {\n margin-bottom: -6px;\n margin-left: -11px;\n}\n\n.hint--right:after {\n margin-bottom: -14px;\n}\n\n.hint--right:before, .hint--right:after {\n bottom: 50%;\n left: 100%;\n}\n\n.hint--right:hover:before, .hint--right:hover:after {\n transform: translateX(8px);\n}\n\n.hint--left:before {\n margin-bottom: -6px;\n margin-right: -11px;\n}\n\n.hint--left:after {\n margin-bottom: -14px;\n}\n\n.hint--left:before, .hint--left:after {\n bottom: 50%;\n right: 100%;\n}\n\n.hint--left:hover:before, .hint--left:hover:after {\n transform: translateX(-8px);\n}\n\n.hint--top-left:before {\n margin-bottom: -11px;\n}\n\n.hint--top-left:before, .hint--top-left:after {\n bottom: 100%;\n left: 50%;\n}\n\n.hint--top-left:before {\n left: calc(50% - 6px);\n}\n\n.hint--top-left:after {\n margin-left: 12px;\n transform: translateX(-100%);\n}\n\n.hint--top-left:hover:before {\n transform: translateY(-8px);\n}\n\n.hint--top-left:hover:after {\n transform: translateX(-100%)translateY(-8px);\n}\n\n.hint--top-right:before {\n margin-bottom: -11px;\n}\n\n.hint--top-right:before, .hint--top-right:after {\n bottom: 100%;\n left: 50%;\n}\n\n.hint--top-right:before {\n left: calc(50% - 6px);\n}\n\n.hint--top-right:after {\n margin-left: -12px;\n transform: translateX(0);\n}\n\n.hint--top-right:hover:before, .hint--top-right:hover:after {\n transform: translateY(-8px);\n}\n\n.hint--bottom-left:before {\n margin-top: -11px;\n}\n\n.hint--bottom-left:before, .hint--bottom-left:after {\n top: 100%;\n left: 50%;\n}\n\n.hint--bottom-left:before {\n left: calc(50% - 6px);\n}\n\n.hint--bottom-left:after {\n margin-left: 12px;\n transform: translateX(-100%);\n}\n\n.hint--bottom-left:hover:before {\n transform: translateY(8px);\n}\n\n.hint--bottom-left:hover:after {\n transform: translateX(-100%)translateY(8px);\n}\n\n.hint--bottom-right:before {\n margin-top: -11px;\n}\n\n.hint--bottom-right:before, .hint--bottom-right:after {\n top: 100%;\n left: 50%;\n}\n\n.hint--bottom-right:before {\n left: calc(50% - 6px);\n}\n\n.hint--bottom-right:after {\n margin-left: -12px;\n transform: translateX(0);\n}\n\n.hint--bottom-right:hover:before, .hint--bottom-right:hover:after {\n transform: translateY(8px);\n}\n\n.hint--small:after, .hint--medium:after, .hint--large:after {\n white-space: normal;\n word-wrap: break-word;\n line-height: 1.4em;\n}\n\n.hint--small:after {\n width: 80px;\n}\n\n.hint--medium:after {\n width: 150px;\n}\n\n.hint--large:after {\n width: 300px;\n}\n\n[class*=\"hint--\"]:after {\n text-shadow: 0 -1px #000;\n box-shadow: 4px 4px 8px #0000004d;\n}\n\n.hint--error:after {\n text-shadow: 0 -1px #592726;\n background-color: #b34e4d;\n}\n\n.hint--error.hint--top-left:before, .hint--error.hint--top-right:before, .hint--error.hint--top:before {\n border-top-color: #b34e4d;\n}\n\n.hint--error.hint--bottom-left:before, .hint--error.hint--bottom-right:before, .hint--error.hint--bottom:before {\n border-bottom-color: #b34e4d;\n}\n\n.hint--error.hint--left:before {\n border-left-color: #b34e4d;\n}\n\n.hint--error.hint--right:before {\n border-right-color: #b34e4d;\n}\n\n.hint--warning:after {\n text-shadow: 0 -1px #6c5328;\n background-color: #c09854;\n}\n\n.hint--warning.hint--top-left:before, .hint--warning.hint--top-right:before, .hint--warning.hint--top:before {\n border-top-color: #c09854;\n}\n\n.hint--warning.hint--bottom-left:before, .hint--warning.hint--bottom-right:before, .hint--warning.hint--bottom:before {\n border-bottom-color: #c09854;\n}\n\n.hint--warning.hint--left:before {\n border-left-color: #c09854;\n}\n\n.hint--warning.hint--right:before {\n border-right-color: #c09854;\n}\n\n.hint--info:after {\n text-shadow: 0 -1px #1a3c4d;\n background-color: #3986ac;\n}\n\n.hint--info.hint--top-left:before, .hint--info.hint--top-right:before, .hint--info.hint--top:before {\n border-top-color: #3986ac;\n}\n\n.hint--info.hint--bottom-left:before, .hint--info.hint--bottom-right:before, .hint--info.hint--bottom:before {\n border-bottom-color: #3986ac;\n}\n\n.hint--info.hint--left:before {\n border-left-color: #3986ac;\n}\n\n.hint--info.hint--right:before {\n border-right-color: #3986ac;\n}\n\n.hint--success:after {\n text-shadow: 0 -1px #1a321a;\n background-color: #458746;\n}\n\n.hint--success.hint--top-left:before, .hint--success.hint--top-right:before, .hint--success.hint--top:before {\n border-top-color: #458746;\n}\n\n.hint--success.hint--bottom-left:before, .hint--success.hint--bottom-right:before, .hint--success.hint--bottom:before {\n border-bottom-color: #458746;\n}\n\n.hint--success.hint--left:before {\n border-left-color: #458746;\n}\n\n.hint--success.hint--right:before {\n border-right-color: #458746;\n}\n\n.hint--always:after, .hint--always:before {\n opacity: 1;\n visibility: visible;\n}\n\n.hint--always.hint--top:before {\n transform: translateY(-8px);\n}\n\n.hint--always.hint--top:after {\n transform: translateX(-50%)translateY(-8px);\n}\n\n.hint--always.hint--top-left:before {\n transform: translateY(-8px);\n}\n\n.hint--always.hint--top-left:after {\n transform: translateX(-100%)translateY(-8px);\n}\n\n.hint--always.hint--top-right:before, .hint--always.hint--top-right:after {\n transform: translateY(-8px);\n}\n\n.hint--always.hint--bottom:before {\n transform: translateY(8px);\n}\n\n.hint--always.hint--bottom:after {\n transform: translateX(-50%)translateY(8px);\n}\n\n.hint--always.hint--bottom-left:before {\n transform: translateY(8px);\n}\n\n.hint--always.hint--bottom-left:after {\n transform: translateX(-100%)translateY(8px);\n}\n\n.hint--always.hint--bottom-right:before, .hint--always.hint--bottom-right:after {\n transform: translateY(8px);\n}\n\n.hint--always.hint--left:before, .hint--always.hint--left:after {\n transform: translateX(-8px);\n}\n\n.hint--always.hint--right:before, .hint--always.hint--right:after {\n transform: translateX(8px);\n}\n\n.hint--rounded:after {\n border-radius: 4px;\n}\n\n.hint--no-animate:before, .hint--no-animate:after {\n transition-duration: 0s;\n}\n\n.hint--bounce:before, .hint--bounce:after {\n -webkit-transition: opacity .3s, visibility .3s, -webkit-transform .3s cubic-bezier(.71, 1.7, .77, 1.24);\n -moz-transition: opacity .3s, visibility .3s, -moz-transform .3s cubic-bezier(.71, 1.7, .77, 1.24);\n transition: opacity .3s, visibility .3s, transform .3s cubic-bezier(.71, 1.7, .77, 1.24);\n}\n\n.hint--no-shadow:before, .hint--no-shadow:after {\n text-shadow: initial;\n box-shadow: initial;\n}\n\n.hint--no-arrow:before {\n display: none;\n}\n\n.art-video-player.art-mobile {\n --art-bottom-gap: 10px;\n --art-control-height: 38px;\n --art-control-icon-scale: 1;\n --art-state-size: 60px;\n --art-settings-max-height: 180px;\n --art-selector-max-height: 180px;\n --art-indicator-scale: 1;\n --art-control-opacity: 1;\n}\n\n.art-video-player.art-mobile .art-controls-left {\n margin-left: calc(var(--art-padding) / -1);\n}\n\n.art-video-player.art-mobile .art-controls-right {\n margin-right: calc(var(--art-padding) / -1);\n}\n"; },{}],"9hUEm":[function(require,module,exports,__globalThis) { !function(r, t) { diff --git a/package.json b/package.json index ba172b423..5dfdd1dbb 100644 --- a/package.json +++ b/package.json @@ -28,13 +28,14 @@ "bootstrap": "npx lerna link", "postinstall": "npx lerna run prepare", "build:ts": "node ./scripts/build-ts.js", + "build:llm": "node ./scripts/build-llm.js", "build:docs": "node ./scripts/build-docs.js", "build:i18n": "node ./scripts/build-i18n.js", "trans:docs": "node ./scripts/trans-docs.js", "create:plugin": "node ./scripts/plugin/create.js", - "lint": "npx eslint packages/*/{src,types,package.json} --fix", "dev": "npx cross-env NODE_ENV=development node ./scripts/dev.js", "build": "npx cross-env NODE_ENV=production node ./scripts/build.js", + "lint": "npx eslint packages/*/{src,types,package.json} scripts/*.js test/* docs/assets/ts/* --fix", "build:all": "npm run build all && npm run build:i18n && npm run build:ts && npm run build:docs" }, "browserslist": "last 1 Chrome version", @@ -57,4 +58,4 @@ "servor": "^4.0.2", "svgo": "^4.0.0" } -} +} \ No newline at end of file diff --git a/packages/artplayer-vitepress/docs/.vitepress/cache/deps/@theme_index.js b/packages/artplayer-vitepress/docs/.vitepress/cache/deps/@theme_index.js index aea9608fb..ec6a94270 100644 --- a/packages/artplayer-vitepress/docs/.vitepress/cache/deps/@theme_index.js +++ b/packages/artplayer-vitepress/docs/.vitepress/cache/deps/@theme_index.js @@ -9,38 +9,38 @@ import { } from "./chunk-PYKB7VCN.js"; // ../../node_modules/vitepress/dist/client/theme-default/index.js -import "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/fonts.css"; +import "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/fonts.css"; // ../../node_modules/vitepress/dist/client/theme-default/without-fonts.js -import "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/vars.css"; -import "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/base.css"; -import "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/icons.css"; -import "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/utils.css"; -import "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/components/custom-block.css"; -import "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code.css"; -import "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code-group.css"; -import "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/components/vp-doc.css"; -import "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/components/vp-sponsor.css"; -import VPBadge from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue"; -import Layout from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/Layout.vue"; -import { default as default2 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue"; -import { default as default3 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPButton.vue"; -import { default as default4 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPDocAsideSponsors.vue"; -import { default as default5 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPFeatures.vue"; -import { default as default6 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPHomeContent.vue"; -import { default as default7 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPHomeFeatures.vue"; -import { default as default8 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPHomeHero.vue"; -import { default as default9 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPHomeSponsors.vue"; -import { default as default10 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPImage.vue"; -import { default as default11 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPLink.vue"; -import { default as default12 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPNavBarSearch.vue"; -import { default as default13 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPSocialLink.vue"; -import { default as default14 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPSocialLinks.vue"; -import { default as default15 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPSponsors.vue"; -import { default as default16 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPTeamMembers.vue"; -import { default as default17 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPTeamPage.vue"; -import { default as default18 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageSection.vue"; -import { default as default19 } from "/Users/zhaohaiwei/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageTitle.vue"; +import "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/vars.css"; +import "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/base.css"; +import "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/icons.css"; +import "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/utils.css"; +import "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/components/custom-block.css"; +import "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code.css"; +import "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code-group.css"; +import "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/components/vp-doc.css"; +import "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/styles/components/vp-sponsor.css"; +import VPBadge from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue"; +import Layout from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/Layout.vue"; +import { default as default2 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue"; +import { default as default3 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPButton.vue"; +import { default as default4 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPDocAsideSponsors.vue"; +import { default as default5 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPFeatures.vue"; +import { default as default6 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPHomeContent.vue"; +import { default as default7 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPHomeFeatures.vue"; +import { default as default8 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPHomeHero.vue"; +import { default as default9 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPHomeSponsors.vue"; +import { default as default10 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPImage.vue"; +import { default as default11 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPLink.vue"; +import { default as default12 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPNavBarSearch.vue"; +import { default as default13 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPSocialLink.vue"; +import { default as default14 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPSocialLinks.vue"; +import { default as default15 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPSponsors.vue"; +import { default as default16 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPTeamMembers.vue"; +import { default as default17 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPTeamPage.vue"; +import { default as default18 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageSection.vue"; +import { default as default19 } from "C:/Users/laozh/Desktop/github/ArtPlayer/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageTitle.vue"; // ../../node_modules/vitepress/dist/client/theme-default/composables/local-nav.js import { onContentUpdated } from "vitepress"; diff --git a/packages/artplayer-vitepress/docs/.vitepress/cache/deps/_metadata.json b/packages/artplayer-vitepress/docs/.vitepress/cache/deps/_metadata.json index 4feb5a0c0..3f084b69e 100644 --- a/packages/artplayer-vitepress/docs/.vitepress/cache/deps/_metadata.json +++ b/packages/artplayer-vitepress/docs/.vitepress/cache/deps/_metadata.json @@ -1,31 +1,31 @@ { - "hash": "f505e736", - "configHash": "b7d2330b", - "lockfileHash": "b7b1f3f6", - "browserHash": "45c5fb1b", + "hash": "b82527ba", + "configHash": "ab6bce56", + "lockfileHash": "13bd46c6", + "browserHash": "7fe2a1dd", "optimized": { "vue": { "src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js", "file": "vue.js", - "fileHash": "69d6db4c", + "fileHash": "9dd397e2", "needsInterop": false }, "vitepress > @vue/devtools-api": { "src": "../../../../../../node_modules/@vue/devtools-api/dist/index.js", "file": "vitepress___@vue_devtools-api.js", - "fileHash": "8a53d1df", + "fileHash": "04d89fe7", "needsInterop": false }, "vitepress > @vueuse/core": { "src": "../../../../../../node_modules/@vueuse/core/index.mjs", "file": "vitepress___@vueuse_core.js", - "fileHash": "ca279ebd", + "fileHash": "a580052a", "needsInterop": false }, "@theme/index": { "src": "../../../../../../node_modules/vitepress/dist/client/theme-default/index.js", "file": "@theme_index.js", - "fileHash": "c611f6d4", + "fileHash": "553740c4", "needsInterop": false } }, diff --git a/packages/artplayer-vitepress/docs/en/advanced/built-in.md b/packages/artplayer-vitepress/docs/en/advanced/built-in.md index 25baf7cfa..8a657deab 100644 --- a/packages/artplayer-vitepress/docs/en/advanced/built-in.md +++ b/packages/artplayer-vitepress/docs/en/advanced/built-in.md @@ -1,10 +1,10 @@ # Advanced Properties -The `advanced properties` here refer to the `secondary attributes` mounted on the `instance`, which are less commonly used +The `Advanced Properties` here refer to the `secondary properties` attached to the `instance`, which are less commonly used. ## `option` -Options for the player +The player's options.
▶ Run Code
@@ -17,7 +17,7 @@ var art = new Artplayer({ console.info(art.option); ``` -:::warning Reminder +:::warning Note If you directly modify this `option` object, the player will not respond immediately. @@ -25,7 +25,7 @@ If you directly modify this `option` object, the player will not respond immedia ## `template` -Manages all of the `DOM` elements of the player +Manages all `DOM` elements of the player.
▶ Run Code
@@ -38,20 +38,21 @@ var art = new Artplayer({ console.info(art.template); console.info(art.template.$video); ``` -:::warning Warning -To easily distinguish between `DOM` elements and plain objects, all `DOM` elements in the player are named starting with a `$` +:::warning Note -Here is the definition of all `DOM` elements: [artplayer/types/template.d.ts](https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/template.d.ts) +To easily distinguish between `DOM` elements and regular objects, all `DOM` elements within the player are prefixed with `$`. + +This is the definition of all `DOM` elements: [artplayer/types/template.d.ts](https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/template.d.ts) ::: ## `events` -Manages all `DOM` events in the player, which is essentially a proxy for `addEventListener` and `removeEventListener`. When using the following methods to handle events, the event will also be automatically destroyed when the player is destroyed. +Manages all `DOM` events for the player. It essentially proxies `addEventListener` and `removeEventListener`. When using the following methods to handle events, the event will be automatically destroyed when the player is destroyed. -- The `proxy` method is used to proxy `DOM` events -- The `hover` method is used to proxy custom `hover` events +- The `proxy` method is used to proxy `DOM` events. +- The `hover` method is used to proxy custom `hover` events.
▶ Run Code
@@ -64,7 +65,7 @@ var art = new Artplayer({ }); art.events.proxy(container, 'click', event => { - console.info('click', event); + console.info('click', event); }); art.events.hover(container, (event) => { @@ -73,21 +74,22 @@ art.events.hover(container, (event) => { console.info('mouseleave', event); }); ``` -:::warning Warning -If you need some `DOM` events to only exist during the lifecycle of the player, it is strongly recommended to use these functions to avoid causing memory leaks +:::warning Note + +If you need `DOM` events that only exist for the duration of the player's lifecycle, it is highly recommended to use these functions to avoid memory leaks. ::: ## `storage` -Manages the local storage of the player +Manages the player's local storage. -- The `name` attribute is used to set the cache `key` -- The `set` method is used to set the cache -- The `get` method is used to get the cache -- The `del` method is used to delete the cache -- The `clear` method is used to clear the cache +- The `name` property is used to set the cache `key`. +- The `set` method is used to set the cache. +- The `get` method is used to get the cache. +- The `del` method is used to delete the cache. +- The `clear` method is used to clear the cache.
▶ Run Code
@@ -104,11 +106,11 @@ art.storage.del('test'); art.storage.clear(); ``` -:::warning Warning +:::warning Note -By default, all player instances share the same `localStorage`, and the default `key` is `artplayer_settings` +By default, all player instances share the same `localStorage`, and the default `key` is `artplayer_settings`. -If you want different players to use different `localStorage`, you can modify `art.storage.name` accordingly +If you want different players to use different `localStorage`, you can modify `art.storage.name`. ::: @@ -126,7 +128,7 @@ art.storage.set('test', { foo: 'bar' }); ## `icons` -Manage all the `svg` icons of the player +Manages all `svg` icons for the player.
▶ Run Code
@@ -147,10 +149,10 @@ console.info(art.icons.loading); ## `i18n` -Manage the player's `i18n` +Manages the player's `i18n`. -- The `get` method is used to retrieve the value of `i18n` -- The `update` method is used to update the `i18n` object +- The `get` method is used to get the `i18n` value. +- The `update` method is used to update the `i18n` object.
▶ Run Code
@@ -171,14 +173,13 @@ art.i18n.update({ :::warning -Using `art.i18n.update` can only update `i18n` after instantiation. If you want to update `i18n` before instantiation, please use the `i18n` from the basic options for the update. +Using `art.i18n.update` can only update the `i18n` after instantiation. If you want to update `i18n` before instantiation, please use the basic option `i18n` to update. ::: - ## `notice` -Manage the player's notices, there's only one `show` property used to display notices. +Manages the player's notifications. It only has a `show` property for displaying notifications.
▶ Run Code
@@ -195,19 +196,19 @@ art.on('ready', () => { :::warning -To immediately hide the display of `notice`: `art.notice.show = '';` +If you want to hide the `notice` immediately: `art.notice.show = '';` ::: ## `layers` -Manage the layers of the player +Manages the player's layers. -- The `add` method is used for dynamically adding layers -- The `remove` method is used for dynamically removing layers -- The `update` method is used for dynamically updating layers -- The `show` property is used to set whether to display all layers or not -- The `toggle` method is used to toggle the display of all layers +- The `add` method is used to dynamically add a layer. +- The `remove` method is used to dynamically remove a layer. +- The `update` method is used to dynamically update a layer. +- The `show` property is used to set whether all layers are visible. +- The `toggle` method is used to toggle the visibility of all layers.
▶ Run Code
@@ -226,8 +227,8 @@ art.on('ready', () => { art.layers.show = false; }, 1000); }); -``` -:::warning `Component configuration` Please refer to the following address: + +:::warning For `Component Configuration`, please refer to: [/component/layers.html](/component/layers.html) @@ -235,13 +236,13 @@ art.on('ready', () => { ## `controls` -Manage the player's controllers +Manages the player's controls -- `add` method is used to dynamically add controllers -- `remove` method is used to dynamically remove controllers -- `update` method is used to dynamically update controllers -- `show` property is used to set whether to display all controllers -- `toggle` method is used to toggle the display of all controllers +- The `add` method dynamically adds controls +- The `remove` method dynamically removes controls +- The `update` method dynamically updates controls +- The `show` property sets whether to display all controls +- The `toggle` method toggles the visibility of all controls
▶ Run Code
@@ -263,7 +264,7 @@ art.on('ready', () => { }); ``` -:::warning `Component Configuration` Please refer to the following address: +:::warning For `Component Configuration`, please refer to: [/component/controls.html](/component/controls.html) @@ -271,13 +272,13 @@ art.on('ready', () => { ## `contextmenu` -Manage the right-click context menu of the player +Manages the player's context menu -- The `add` method is used to dynamically add menu items -- The `remove` method is used to dynamically remove menu items -- The `update` method is used to dynamically update menu items -- The `show` attribute is used to set whether to show all menu items -- The `toggle` method is used to switch the visibility of all menu items +- The `add` method dynamically adds menu items +- The `remove` method dynamically removes menu items +- The `update` method dynamically updates menu items +- The `show` property sets whether to display all menu items +- The `toggle` method toggles the visibility of all menu items
▶ Run Code
@@ -293,13 +294,13 @@ art.on('ready', () => { }); art.contextmenu.show = true; - setTimeout(() => { - art.contextmenu.show = false; - }, 1000); + setTimeout(() => { + art.contextmenu.show = false; + }, 1000); }); ``` -:::warning `Component Configuration` Please refer to the following address: +:::warning For `Component Configuration`, please refer to: [/component/contextmenu.html](/component/contextmenu.html) @@ -307,14 +308,14 @@ art.on('ready', () => { ## `subtitle` -Manage the subtitle features of the player +Manages the player's subtitle functionality -- `url` property sets and returns the current subtitle address -- `style` method sets the style of the current subtitle -- `switch` method sets the current subtitle address and options -- `textTrack` Get the current subtitle track -- `activeCues` Get the currently active subtitle list -- `cues` Get the subtitle list +- The `url` property sets and returns the current subtitle URL +- The `style` method sets the current subtitle's style +- The `switch` method sets the current subtitle URL and options +- `textTrack` gets the current text track +- `activeCues` gets the list of currently active cues +- `cues` gets the complete list of cues
▶ Run Code
@@ -334,10 +335,10 @@ art.on('ready', () => { ## `loading` -Manage the loading layer of the player +Manages the player's loading layer -- `show` property is used to set whether to display the loading layer -- The `toggle` attribute is used to toggle the display of the loading layer +- The `show` property sets whether to display the loading layer +- The `toggle` property toggles the visibility of the loading layer
▶ Run Code
@@ -357,10 +358,10 @@ art.on('ready', () => { ## `hotkey` -Manage the hotkey functionality of the player +Manages the player's hotkey functionality -- The `add` method is used to add a hotkey -- The `remove` method is used to remove a hotkey +- The `add` method adds hotkeys +- The `remove` method removes hotkeys
▶ Run Code
@@ -381,18 +382,19 @@ art.on('ready', () => { }, 5000); }); ``` -:::warning Warning -Shortcut keys will only work after the player has gained focus (e.g., after clicking on the player). +:::warning Note + +These hotkeys only take effect when the player has focus (e.g., after clicking on the player) ::: ## `mask` -Manage the player's mask layer +Manages the player's mask layer -- `show` property is used to set whether to display the mask layer -- `toggle` property is used to toggle the display of the mask layer +- The `show` property sets whether to display the mask layer +- The `toggle` property toggles the visibility of the mask layer
▶ Run Code
@@ -404,22 +406,21 @@ var art = new Artplayer({ art.on('ready', () => { art.mask.show = false; - setTimeout(() => { - art.mask.show = true; - }, 1000); + setTimeout(() => { + art.mask.show = true; + }, 1000); }); ``` ## `setting` -Manage the player's settings panel - -- `add` method is used to dynamically add a setting item -- `remove` method is used to dynamically remove a setting item -- `update` method is used to dynamically update a setting item -- `show` property is used to set whether to display all the setting items -- `toggle` method is used to toggle whether to display all the setting items +Manages the player's settings panel +- The `add` method dynamically adds settings items +- The `remove` method dynamically removes settings items +- The `update` method dynamically updates settings items +- The `show` property sets whether to display all settings items +- The `toggle` method toggles the visibility of all settings items
▶ Run Code
@@ -442,7 +443,7 @@ art.on('ready', () => { }); ``` -:::warning `Settings Panel` Please refer to the following link +:::warning For `Settings Panel`, please refer to: [/component/setting.html](/component/setting.html) @@ -450,7 +451,7 @@ art.on('ready', () => { ## `plugins` -Manage the player's plugin features, with only one method `add` to dynamically add plugins +Manages the player's plugin functionality, with only one method `add` for dynamically adding plugins
▶ Run Code
@@ -473,5 +474,4 @@ function myPlugin(art) { art.on('ready', () => { art.plugins.add(myPlugin); -}); -``` \ No newline at end of file +}); \ No newline at end of file diff --git a/packages/artplayer-vitepress/docs/en/advanced/class.md b/packages/artplayer-vitepress/docs/en/advanced/class.md index 3f2b86d51..8aa3078d7 100644 --- a/packages/artplayer-vitepress/docs/en/advanced/class.md +++ b/packages/artplayer-vitepress/docs/en/advanced/class.md @@ -1,10 +1,10 @@ # Static Properties -Here, `Static Properties` refer to the `first-level properties` mounted on the `constructor`, which are very rarely used. +Static properties here refer to first-level properties mounted on the constructor, which are rarely used. ## `instances` -Returns an array of all player instances. You can use this property when you want to manage multiple players at the same time. +Returns an array of all player instances. This property can be useful when you need to manage multiple player instances simultaneously.
▶ Run Code
@@ -41,7 +41,7 @@ console.info(Artplayer.env); ## `build` -Returns the build time of the player +Returns the build timestamp of the player.
▶ Run Code
@@ -51,7 +51,7 @@ console.info(Artplayer.build); ## `config` -Returns the default configuration of the video +Returns the default configuration for videos.
▶ Run Code
@@ -61,7 +61,7 @@ console.info(Artplayer.config); ## `utils` -Returns a collection of utility functions for the player +Returns the collection of utility functions for the player.
▶ Run Code
@@ -69,7 +69,7 @@ Returns a collection of utility functions for the player console.info(Artplayer.utils); ``` -:::warning For the full list of utility functions, please refer to the following address: +:::warning For all utility functions, please refer to: [artplayer/types/utils.d.ts](https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/utils.d.ts) @@ -77,7 +77,7 @@ console.info(Artplayer.utils); ## `scheme` -Returns the validation scheme for the player options +Returns the validation schema for player options.
▶ Run Code
@@ -87,7 +87,7 @@ console.info(Artplayer.scheme); ## `Emitter` -Returns the constructor for the event dispatcher +Returns the constructor for the event emitter.
▶ Run Code
@@ -97,7 +97,7 @@ console.info(Artplayer.Emitter); ## `validator` -Returns the validation function for options +Returns the validation function for options.
▶ Run Code
@@ -107,7 +107,7 @@ console.info(Artplayer.validator); ## `kindOf` -Returns the function tool for type checking +Returns the type detection utility function.
▶ Run Code
@@ -117,7 +117,7 @@ console.info(Artplayer.kindOf); ## `html` -Returns the `html` string required for the player +Returns the HTML string required by the player.
▶ Run Code
@@ -127,10 +127,9 @@ console.info(Artplayer.html); ## `option` -Returns the player's default options +Returns the default options for the player.
▶ Run Code
```js -console.info(Artplayer.option); -``` \ No newline at end of file +console.info(Artplayer.option); \ No newline at end of file diff --git a/packages/artplayer-vitepress/docs/en/advanced/event.md b/packages/artplayer-vitepress/docs/en/advanced/event.md index 44e6265e6..60381f79b 100644 --- a/packages/artplayer-vitepress/docs/en/advanced/event.md +++ b/packages/artplayer-vitepress/docs/en/advanced/event.md @@ -1,8 +1,8 @@ -# Example Events +# Instance Events -Player events are divided into two types, one is the video's `native events` (prefix `video:`), the other is `custom events` +Player events are divided into two types: `native events` from the video (prefixed with `video:`) and `custom events`. -Listening to events: +Listen for events:
▶ Run Code
@@ -17,7 +17,7 @@ art.on('video:canplay', () => { }); ``` -Listen to an event only once: +Listen for an event only once:
▶ Run Code
@@ -63,14 +63,15 @@ const onReady = () => { art.on('ready', onReady); ``` -:::warning For a full list of events, please refer to the following address: +:::warning For all events, please refer to the following address: [artplayer/types/events.d.ts](https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/events.d.ts) ::: + ## `ready` -Triggered when the player is able to play for the first time +Triggered when the player is ready for the first time
▶ Run Code
@@ -87,7 +88,7 @@ art.on('ready', () => { ## `restart` -Triggered when the player switches the address and is able to play +Triggered when the player switches URL and is ready to play
▶ Run Code
@@ -105,6 +106,7 @@ art.on('restart', (url) => { console.info('restart', url); }); ``` + ## `pause` Triggered when the player is paused @@ -141,7 +143,7 @@ art.on('play', () => { ## `hotkey` -Triggered when a hotkey on the player is pressed +Triggered when a player hotkey is pressed
▶ Run Code
@@ -227,6 +229,7 @@ art.on('dblclick', (event) => { console.info('dblclick', event); }); ``` + ## `click` Triggered when the player is clicked @@ -242,11 +245,10 @@ var art = new Artplayer({ art.on('click', (event) => { console.info('click', event); }); -``` ## `error` -Triggered when an error occurs while the player is loading the video +Triggered when an error occurs while the player is loading a video
▶ Run Code
@@ -260,9 +262,10 @@ art.on('error', (error, reconnectTime) => { console.info(error, reconnectTime); }); ``` + ## `hover` -Triggered when the player is hovered or unhovered by the mouse +Triggered when the mouse pointer enters or leaves the player
▶ Run Code
@@ -279,7 +282,7 @@ art.on('hover', (state, event) => { ## `mousemove` -Triggered when the mouse moves over the player +Triggered when the mouse pointer moves over the player
▶ Run Code
@@ -293,9 +296,10 @@ art.on('mousemove', (event) => { console.info('mousemove', event); }); ``` + ## `resize` -Triggered when the player size changes +Triggered when the player's dimensions change
▶ Run Code
@@ -312,7 +316,7 @@ art.on('resize', () => { ## `view` -Triggered when the player appears in the viewport +Triggered when the player enters or leaves the viewport
▶ Run Code
@@ -329,7 +333,7 @@ art.on('view', (state) => { ## `lock` -On mobile, triggered when the locked state changes +Triggered when the lock state changes on mobile devices
▶ Run Code
@@ -347,7 +351,7 @@ art.on('lock', (state) => { ## `aspectRatio` -Triggered when the aspect ratio of the player changes +Triggered when the player's aspect ratio changes
▶ Run Code
@@ -366,7 +370,7 @@ art.on('aspectRatio', (aspectRatio) => { ## `autoHeight` -Triggered when the player automatically sets the height +Triggered when the player automatically sets its height
▶ Run Code
@@ -387,7 +391,7 @@ art.on('autoHeight', (height) => { ## `autoSize` -Triggered when the player automatically sets the size +Triggered when the player automatically adjusts its size
▶ Run Code
@@ -402,9 +406,10 @@ art.on('autoSize', () => { console.info('autoSize'); }); ``` + ## `flip` -Triggered when the player flips +Triggered when the player's video is flipped
▶ Run Code
@@ -423,7 +428,7 @@ art.on('flip', (flip) => { ## `fullscreen` -Triggered when the player goes into full screen +Triggered when the player enters or exits fullscreen mode
▶ Run Code
@@ -441,7 +446,7 @@ art.on('fullscreen', (state) => { ## `fullscreenError` -Triggered when the player goes into full screen error +Triggered when an error occurs during fullscreen mode transition
▶ Run Code
@@ -462,7 +467,7 @@ art.on('fullscreenError', (event) => { ## `fullscreenWeb` -Triggered when the player enters web fullscreen +Triggered when the player enters or exits web page fullscreen mode
▶ Run Code
@@ -480,7 +485,7 @@ art.on('fullscreenWeb', (state) => { ## `mini` -Triggered when the player enters mini mode +Triggered when the player enters or exits mini mode
▶ Run Code
@@ -498,9 +503,10 @@ art.on('mini', (state) => { console.info('mini', state); }); ``` + ## `pip` -Triggered when the player enters Picture-in-Picture mode +Triggered when the player enters or exits picture-in-picture mode
▶ Run Code
@@ -518,7 +524,7 @@ art.on('pip', (state) => { ## `screenshot` -Triggered when the player takes a screenshot +Triggered when the player captures a screenshot
▶ Run Code
@@ -533,9 +539,10 @@ art.on('screenshot', (dataUri) => { console.info('screenshot', dataUri); }); ``` + ## `seek` -Triggered when the player jumps in time +Triggered when the player performs a time jump
▶ Run Code
@@ -552,7 +559,7 @@ art.on('seek', (currentTime) => { ## `subtitleOffset` -Triggered when the subtitle offset occurs in the player +Triggered when subtitle offset changes
▶ Run Code
@@ -594,7 +601,7 @@ art.on('subtitleBeforeUpdate', (cues) => { ## `subtitleAfterUpdate` -Triggered when the subtitles are updated +Triggered after subtitles are updated
▶ Run Code
@@ -614,7 +621,7 @@ art.on('subtitleAfterUpdate', (cues) => { ## `subtitleLoad` -Triggered when the subtitle loads +Triggered when subtitles are loaded
▶ Run Code
@@ -634,7 +641,7 @@ art.on('subtitleLoad', (option, cues) => { ## `info` -Triggered when the information panel is shown or hidden +Triggered when the info panel is shown or hidden
▶ Run Code
@@ -648,9 +655,10 @@ art.on('info', (state) => { console.log(state); }); ``` + ## `layer` -Triggered when a custom layer is shown or hidden +Triggered when custom layers are shown or hidden
▶ Run Code
@@ -667,7 +675,7 @@ art.on('layer', (state) => { ## `loading` -Triggered when a loader is shown or hidden +Triggered when the loading indicator is shown or hidden
▶ Run Code
@@ -684,7 +692,7 @@ art.on('loading', (state) => { ## `mask` -Triggered when a mask layer is shown or hidden +Triggered when the mask layer is shown or hidden
▶ Run Code
@@ -714,11 +722,10 @@ var art = new Artplayer({ art.on('subtitle', (state) => { console.log(state); }); -``` ## `contextmenu` -Triggered when the right-click menu is shown or hidden +Triggered when the context menu is shown or hidden
▶ Run Code
@@ -735,7 +742,7 @@ art.on('contextmenu', (state) => { ## `control` -Triggered when the controller is shown or hidden +Triggered when the controls are shown or hidden
▶ Run Code
@@ -787,7 +794,7 @@ art.on('muted', (state) => { ## `keydown` -Listen to the `keydown` event from `document` +Listens for the `keydown` event from `document`
▶ Run Code
@@ -804,11 +811,11 @@ art.on('keydown', (event) => { ## `video:canplay` -The browser can play the media file, but estimates there is not enough data to play through to the end without having to stop for further buffering +The browser can start playing the media, but estimates that there isn't enough data to play through to the end without having to stop for further buffering ## `video:canplaythrough` -The browser estimates it can play the media through to the end without stopping for content buffering +The browser estimates it can play the media through to the end without having to stop for buffering ## `video:complete` @@ -820,15 +827,15 @@ Triggered when the value of the duration property changes ## `video:emptied` -The media content becomes empty; for example, when this media has been completely loaded (or partially loaded), this event is sent and the load() method is called to reload it +The media has become empty; for example, this event is sent when the media has already been loaded (or partially loaded), and the load() method is called to reload it ## `video:ended` -The video has stopped because the media reached the end point +Playback has stopped because the media has reached its end point ## `video:error` -An error occurred while fetching media data, or the resource type is not a supported media format +An error occurred while fetching the media data, or the resource type is not a supported media format ## `video:loadeddata` @@ -844,15 +851,15 @@ Playback has been paused ## `video:play` -Playback has started +Playback has begun ## `video:playing` -Playback is ready to start following a pause or delay due to lack of data +Playback is ready to start after having been paused or delayed due to lack of data ## `video:progress` -Periodically triggered while the browser is loading resources +Fired periodically as the browser loads the resource ## `video:ratechange` @@ -860,15 +867,15 @@ The playback rate has changed ## `video:seeked` -A seek (frame skipping) operation has completed +A seek operation has completed ## `video:seeking` -A seek (frame skipping) operation has started +A seek operation has begun ## `video:stalled` -The user agent is trying to fetch media data, but the data unexpectedly has not appeared +The user agent is trying to fetch media data, but data is unexpectedly not forthcoming ## `video:suspend` @@ -876,11 +883,12 @@ Media data loading has been suspended ## `video:timeupdate` -The time specified by the currentTime attribute has changed +The time indicated by the currentTime attribute has been updated + ## `video:volumechange` -Volume changed +The volume has changed ## `video:waiting` -Playback has stopped due to temporarily missing data \ No newline at end of file +Playback has stopped because of a temporary lack of data \ No newline at end of file diff --git a/packages/artplayer-vitepress/docs/en/advanced/global.md b/packages/artplayer-vitepress/docs/en/advanced/global.md index 2f494d4bf..1cfbfaa38 100644 --- a/packages/artplayer-vitepress/docs/en/advanced/global.md +++ b/packages/artplayer-vitepress/docs/en/advanced/global.md @@ -1,10 +1,10 @@ -# Global Attributes +# Global Properties -Here, `Global Attributes` refer to the `first-level properties` mounted on the `constructor`. Property names are all in uppercase, subject to change in the future and basically not needed. +These `global properties` refer to the top-level properties mounted on the `constructor`. All property names are in uppercase. These are subject to change in the future and are generally not used. ## DEBUG -Whether to start `debug` mode, which can print out all built-in events of the video by default is turned off. +Whether to enable `debug` mode, which can print all built-in video events. Default is off.
▶ Run Code
@@ -19,7 +19,7 @@ var art = new Artplayer({ ## STYLE -Returns the player style text +Returns the player's style text.
▶ Run Code
@@ -29,7 +29,7 @@ console.log(Artplayer.STYLE); ## CONTEXTMENU -Whether to enable the right-click context menu, enabled by default. +Whether to enable the context menu. Default is on.
▶ Run Code
@@ -41,9 +41,10 @@ var art = new Artplayer({ url: '/assets/sample/video.mp4', }); ``` + ## NOTICE_TIME -The display duration of the notification message, in milliseconds, defaults to `2000` +The display duration for notification messages, in milliseconds. Default is `2000`.
▶ Run Code
@@ -58,7 +59,7 @@ var art = new Artplayer({ ## SETTING_WIDTH -The default width of the settings panel, in pixels, defaults to `250` +The default width of the settings panel, in pixels. Default is `250`.
▶ Run Code
@@ -75,9 +76,10 @@ var art = new Artplayer({ aspectRatio: true, }); ``` + ## SETTING_ITEM_WIDTH -Set the default width of the settings items in the panel, in pixels, the default is `200`. +The default width of settings items in the settings panel, in pixels. Default is `200`.
▶ Run Code
@@ -97,7 +99,7 @@ var art = new Artplayer({ ## SETTING_ITEM_HEIGHT -Set the default height of the settings items in the panel, in pixels, the default is `35`. +The default height of settings items in the settings panel, in pixels. Default is `35`.
▶ Run Code
@@ -117,7 +119,7 @@ var art = new Artplayer({ ## RESIZE_TIME -Throttle time for the `resize` event, in milliseconds, defaults to `200` +The throttle time for `resize` events, in milliseconds. Default is `200`.
▶ Run Code
@@ -136,7 +138,7 @@ art.on('resize', () => { ## SCROLL_TIME -Throttle time for the `scroll` event, in milliseconds, defaults to `200` +The throttle time for `scroll` events, in milliseconds. Default is `200`.
▶ Run Code
@@ -152,9 +154,10 @@ art.on('scroll', () => { console.log('scroll'); }); ``` + ## SCROLL_GAP -The boundary tolerance distance for the `view` event, in pixels, default is `50` +The boundary tolerance distance for `view` events, in pixels. Default is `50`.
▶ Run Code
@@ -173,7 +176,7 @@ art.on('scroll', () => { ## AUTO_PLAYBACK_MAX -The maximum number of records for the automatic playback feature, default is `10` +The maximum number of records for the auto-playback feature. Default is `10`.
▶ Run Code
@@ -189,7 +192,7 @@ var art = new Artplayer({ ## AUTO_PLAYBACK_MIN -The minimum duration for the auto playback feature, in seconds, with a default of `5`. +The minimum record duration for the auto-playback feature, in seconds. Default is `5`.
▶ Run Code
@@ -205,7 +208,7 @@ var art = new Artplayer({ ## AUTO_PLAYBACK_TIMEOUT -The delay duration for hiding the auto playback feature, in milliseconds, with a default of `3000`. +The hide delay duration for the auto-playback feature, in milliseconds. Default is `3000`.
▶ Run Code
@@ -221,7 +224,7 @@ var art = new Artplayer({ ## RECONNECT_TIME_MAX -The maximum number of automatic reconnection attempts when a connection error occurs, default is `5` +The maximum number of automatic reconnection attempts when a connection error occurs. Default is `5`.
▶ Run Code
@@ -232,11 +235,10 @@ var art = new Artplayer({ container: '.artplayer-app', url: '/assets/sample/404.mp4', }); -``` ## RECONNECT_SLEEP_TIME -The delay time for the automatic reconnection attempt when a connection error occurs, in milliseconds, default is `1000` +The delay time for automatic reconnection when a connection error occurs, in milliseconds. Default is `1000`
▶ Run Code
@@ -251,9 +253,7 @@ var art = new Artplayer({ ## CONTROL_HIDE_TIME -... - -Auto-hide delay time for the bottom control bar, measured in milliseconds, default is `3000` +The delay time for auto-hiding the bottom control bar, in milliseconds. Default is `3000`
▶ Run Code
@@ -268,7 +268,7 @@ var art = new Artplayer({ ## DBCLICK_TIME -Double-click event delay time, measured in milliseconds, default is `300` +The delay time for double-click events, in milliseconds. Default is `300`
▶ Run Code
@@ -287,7 +287,7 @@ art.on('dblclick', () => { ## DBCLICK_FULLSCREEN -On desktop, whether to switch to fullscreen on double click, default is `true` +On desktop, whether to toggle fullscreen on double-click. Default is `true`
▶ Run Code
@@ -302,7 +302,7 @@ var art = new Artplayer({ ## MOBILE_DBCLICK_PLAY -On mobile, whether to toggle play/pause on double click, default is `true` +On mobile, whether to toggle play/pause on double-click. Default is `true`
▶ Run Code
@@ -317,19 +317,7 @@ var art = new Artplayer({ ## MOBILE_CLICK_PLAY -On mobile, whether to play/pause on single click, default is `false` - -
▶ Run Code
- -```js -Artplayer.MOBILE_CLICK_PLAY = true; - -var art = new Artplayer({ - container: '.artplayer-app', - url: '/assets/sample/video.mp4', -}); -``` -On mobile devices, whether a tap toggles play/pause, defaults to `false` +On mobile, whether to toggle play/pause on single click. Default is `false`
▶ Run Code
@@ -344,7 +332,7 @@ var art = new Artplayer({ ## AUTO_ORIENTATION_TIME -On mobile devices, the delay time for auto-rotation, in milliseconds, defaults to `200` +On mobile, the delay time for automatic screen rotation, in milliseconds. Default is `200`
▶ Run Code
@@ -359,7 +347,8 @@ var art = new Artplayer({ ``` ## INFO_LOOP_TIME -Info panel refresh time, unit in milliseconds, default is `1000` + +The refresh interval for the information panel, in milliseconds. Default is `1000`
▶ Run Code
@@ -376,7 +365,7 @@ art.info.show = true; ## FAST_FORWARD_VALUE -On mobile, the multiplier rate of the speed when long-pressing for fast-forward, default is `3` +On mobile, the speed multiplier for fast-forward during long press. Default is `3`
▶ Run Code
@@ -392,8 +381,7 @@ var art = new Artplayer({ ## FAST_FORWARD_TIME -The time, in milliseconds, to fast-forward when double-tapped, default is `10` -On mobile, the delay time of the long-press acceleration, in milliseconds, default is `1000` +On mobile, the delay time for fast-forward during long press, in milliseconds. Default is `1000`
▶ Run Code
@@ -409,7 +397,7 @@ var art = new Artplayer({ ## TOUCH_MOVE_RATIO -On mobile, the ratio of the speed of sliding left and right to seek, default is `0.5` +On mobile, the speed multiplier for progress seeking during left/right swipe. Default is `0.5`
▶ Run Code
@@ -424,7 +412,7 @@ var art = new Artplayer({ ## VOLUME_STEP -The step ratio of adjusting volume with shortcuts, default is `0.1` +The volume adjustment step for keyboard shortcuts. Default is `0.1`
▶ Run Code
@@ -439,7 +427,7 @@ var art = new Artplayer({ ## SEEK_STEP -The increment by which the playback progress is adjusted via keyboard shortcuts, in seconds, the default is `5` +The seek adjustment step for keyboard shortcuts, in seconds. Default is `5`
▶ Run Code
@@ -454,7 +442,7 @@ var art = new Artplayer({ ## PLAYBACK_RATE -The list of built-in playback speeds, by default `[0.5, 0.75, 1, 1.25, 1.5, 2]` +The built-in playback rate options list. Default is `[0.5, 0.75, 1, 1.25, 1.5, 2]`
▶ Run Code
@@ -474,7 +462,7 @@ art.setting.show = true; ## ASPECT_RATIO -Built-in list of video aspect ratios, default is `['default', '4:3', '16:9']` +The built-in video aspect ratio options list. Default is `['default', '4:3', '16:9']`
▶ Run Code
@@ -490,10 +478,10 @@ var art = new Artplayer({ art.contextmenu.show = true; art.setting.show = true; -``` + ## FLIP -Built-in list of video flips, defaults to `['normal', 'horizontal', 'vertical']` +List of built-in video flip options, defaults to `['normal', 'horizontal', 'vertical']`
▶ Run Code
@@ -513,7 +501,7 @@ art.setting.show = true; ## FULLSCREEN_WEB_IN_BODY -When in web fullscreen, whether to mount the player under the `body` element, defaults to `true` +Whether to mount the player under the `body` element during web fullscreen mode, defaults to `true`
▶ Run Code
@@ -526,9 +514,10 @@ var art = new Artplayer({ fullscreenWeb: true, }); ``` + ## LOG_VERSION -Setting whether to print the player version, the default is `true` +Sets whether to print the player version, defaults to `true`
▶ Run Code
@@ -543,7 +532,7 @@ var art = new Artplayer({ ## USE_RAF -Setting whether to use `requestAnimationFrame`, the default is `false`, currently mainly used for smooth progress bar effects +Sets whether to use `requestAnimationFrame`, defaults to `false`. Currently mainly used for smooth progress bar effects
▶ Run Code
@@ -554,5 +543,4 @@ var art = new Artplayer({ container: '.artplayer-app', url: '/assets/sample/video.mp4', miniProgressBar: true, -}); -``` \ No newline at end of file +}); \ No newline at end of file diff --git a/packages/artplayer-vitepress/docs/en/advanced/plugin.md b/packages/artplayer-vitepress/docs/en/advanced/plugin.md index 815cc4b06..be8149cf0 100644 --- a/packages/artplayer-vitepress/docs/en/advanced/plugin.md +++ b/packages/artplayer-vitepress/docs/en/advanced/plugin.md @@ -1,8 +1,8 @@ # Writing Plugins -Once you know the player's `properties`, `methods`, and `events`, writing plugins is very simple. +Once you're familiar with the player's `properties`, `methods`, and `events`, writing plugins becomes very straightforward. -You can load plugin functions during instantiation. +You can load plugin functions during instantiation:
▶ Run Code
@@ -28,7 +28,8 @@ art.on('ready', () => { console.info(art.plugins.myPlugin); }); ``` -You can also load plugins after instantiation: + +You can also load plugin functions after instantiation:
▶ Run Code
@@ -56,7 +57,10 @@ art.on('ready', () => { }); ``` -For example, I want to write a plugin that displays an image advertisement after the video is paused. +For example, let's say I want to write a plugin that displays an image ad when the video is paused: + +
▶ Run Code
+ ```js function adsPlugin(option) { return (art) => { @@ -117,5 +121,4 @@ var art = new Artplayer({ url: '/assets/sample/layer.png' }) ], -}); -``` \ No newline at end of file +}); \ No newline at end of file diff --git a/packages/artplayer-vitepress/docs/en/advanced/property.md b/packages/artplayer-vitepress/docs/en/advanced/property.md index de86c0219..ff0e99bc4 100644 --- a/packages/artplayer-vitepress/docs/en/advanced/property.md +++ b/packages/artplayer-vitepress/docs/en/advanced/property.md @@ -1,17 +1,16 @@ # Instance Properties -Here, `instance properties` refer to the `primary properties` mounted on the `instance`, which are quite commonly used. +Here, `instance properties` refer to the `top-level properties` mounted on the `instance`, which are commonly used. ## `play` - Type: `Function` -Play video +Play the video.
▶ Run Code
```js{8} - var art = new Artplayer({ container: '.artplayer-app', url: '/assets/sample/video.mp4', @@ -27,7 +26,7 @@ art.on('ready', () => { - Type: `Function` -Pause video +Pause the video.
▶ Run Code
@@ -46,11 +45,12 @@ art.on('ready', () => { }, 3000); }); ``` + ## `toggle` - Type: `Function` -Toggle the play and pause state of the video +Toggle between playing and pausing the video.
▶ Run Code
@@ -75,7 +75,7 @@ art.on('ready', () => { - Type: `Function` - Parameter: `Boolean` -Destroy the player. Accepts a parameter indicating whether to remove the player's `html` after destruction, which defaults to `true` +Destroy the player. Accepts a parameter indicating whether to also remove the player's `html` after destruction. Defaults to `true`.
▶ Run Code
@@ -89,12 +89,13 @@ art.on('ready', () => { art.destroy(); }); ``` + ## `seek` - Type: `Setter` - Parameter: `Number` -Jump to a specific time in the video, in seconds. +Seek to a specific time in the video, in seconds.
▶ Run Code
@@ -114,7 +115,7 @@ art.on('ready', () => { - Type: `Setter` - Parameter: `Number` -Fast forward the video time, in seconds. +Fast forward the video by a specified number of seconds.
▶ Run Code
@@ -128,12 +129,13 @@ art.on('ready', () => { art.forward = 5; }); ``` + ## `backward` - Type: `Setter` - Parameter: `Number` -Video rewind time in seconds +Rewind the video by a specified number of seconds.
▶ Run Code
@@ -157,7 +159,7 @@ art.on('ready', () => { - Type: `Setter/Getter` - Parameter: `Number` -Sets and gets the video volume, which ranges from `[0, 1]` +Set or get the video volume. Range: `[0, 1]`.
▶ Run Code
@@ -173,12 +175,13 @@ art.on('ready', () => { console.info(art.volume); }); ``` + ## `url` -- Type: `Setter/Getter` -- Parameter: `String` +- Type: `Setter/Getter` +- Parameter: `String` -Set and retrieve the video address +Set or get the video URL.
▶ Run Code
@@ -195,10 +198,10 @@ art.on('ready', () => { ## `switch` -- Type: `Setter` -- Parameter: `String` +- Type: `Setter` +- Parameter: `String` -Set the video address, which is similar to `art.url`, but performs some optimization operations +Set the video URL. Similar to `art.url` when setting, but performs some optimization operations.
▶ Run Code
@@ -215,12 +218,13 @@ art.on('ready', () => { }, 3000); }); ``` + ## `switchUrl` -- Type: `Function` -- Parameter: `String` +- Type: `Function` +- Parameter: `String` -Set the video address, similar to setting `art.url`, but some optimization operations will be executed. +Set the video URL. Similar to `art.url` when setting, but performs some optimization operations.
▶ Run Code
@@ -238,17 +242,18 @@ art.on('ready', () => { }); ``` -:::warning Warning +:::warning Note -`art.switch` and `art.switchUrl` have the same functionality. However, the `art.switchUrl` method will return a `Promise`. When it resolves, it indicates that the new address can be played; when it rejects, it indicates there was an error loading the new address. +`art.switch` and `art.switchUrl` have the same functionality, but `art.switchUrl` returns a `Promise`. It `resolves` when the new URL is playable and `rejects` when the new URL fails to load. ::: + ## `switchQuality` -- Type: `Function` -- Parameter: `String` +- Type: `Function` +- Parameter: `String` -Set video quality address, similar to `art.switchUrl`, but it will carry over the previous playback progress. +Sets the video quality URL. Similar to `art.switchUrl`, but preserves the previous playback progress.
▶ Run Code
@@ -268,10 +273,10 @@ art.on('ready', () => { ## `muted` -- Type: `Setter/Getter` -- Parameter: `Boolean` +- Type: `Setter/Getter` +- Parameter: `Boolean` -Set and get whether the video is muted. +Sets and gets whether the video is muted.
▶ Run Code
@@ -293,7 +298,7 @@ art.on('ready', () => { - Type: `Setter/Getter` - Parameter: `Number` -Set and get the current time of the video. Setting the time is similar to `seek`, but it does not trigger additional events. +Sets and gets the current playback time of the video. Setting the time is similar to `seek`, but it does not trigger additional events.
▶ Run Code
@@ -309,11 +314,12 @@ art.on('ready', () => { console.info(art.currentTime); }); ``` + ## `duration` - Type: `Getter` -Get the video duration +Gets the duration of the video.
▶ Run Code
@@ -330,7 +336,7 @@ art.on('ready', () => { :::warning Note -Some videos do not have a duration, such as videos that are being live streamed or videos that have not been fully decoded, in which case the obtained duration will be `0` +Some videos may not have a duration, such as live streams or videos that haven't finished decoding. In these cases, the obtained duration will be `0`. ::: @@ -338,7 +344,7 @@ Some videos do not have a duration, such as videos that are being live streamed - Type: `Function` -Download a screenshot of the current video frame, optional parameter is the screenshot name +Downloads a screenshot of the current video frame. An optional parameter specifies the screenshot filename.
▶ Run Code
@@ -355,9 +361,9 @@ art.on('ready', () => { ## `getDataURL` -- Type: `Function` +- Type: `Function` -Gets the `base64` address of the screenshot of the current video frame, which returns a `Promise`. +Gets the `base64` URL of the screenshot for the current video frame. Returns a `Promise`.
▶ Run Code
@@ -375,9 +381,9 @@ art.on('ready', async () => { ## `getBlobUrl` -- Type: `Function` +- Type: `Function` -Gets the `blob` address of the screenshot of the current video frame, which returns a `Promise`. +Gets the `blob` URL of the screenshot for the current video frame. Returns a `Promise`.
▶ Run Code
@@ -392,12 +398,13 @@ art.on('ready', async () => { console.info(url); }); ``` + ## `fullscreen` - Type: `Setter/Getter` - Parameter: `Boolean` -Set and get the player window fullscreen +Sets and gets the fullscreen state of the player window.
▶ Run Code
@@ -419,15 +426,16 @@ var art = new Artplayer({ :::warning Note -Due to browser security mechanisms, before triggering the window to fullscreen, the page must have had an interaction (e.g. user clicked on the page). +Due to browser security mechanisms, the page must have prior interaction (e.g., the user has clicked on the page) before triggering window fullscreen. ::: + ## `fullscreenWeb` -- Type: `Setter/Getter` -- Parameter: `Boolean` +- Type: `Setter/Getter` +- Parameter: `Boolean` -Set and get the player web page full screen +Sets and gets the web fullscreen state of the player.
▶ Run Code
@@ -449,10 +457,10 @@ art.on('ready', () => { ## `pip` -- Type: `Setter/Getter` -- Parameter: `Boolean` +- Type: `Setter/Getter` +- Parameter: `Boolean` -Set and get the player picture-in-picture mode +Sets and gets the Picture-in-Picture mode of the player.
▶ Run Code
@@ -470,11 +478,10 @@ var art = new Artplayer({ }, ], }); -``` -:::warning Warning +:::warning Note -Due to browser security mechanisms, user interaction (e.g., a user click on the page) must occur before triggering picture-in-picture. +Due to browser security mechanisms, the page must have prior user interaction (e.g., a user click) before Picture-in-Picture can be triggered. ::: @@ -483,7 +490,7 @@ Due to browser security mechanisms, user interaction (e.g., a user click on the - Type: `Setter/Getter` - Parameter: `String` -Set and get the video poster, the poster effect is visible only before the video starts playing. +Sets and gets the video poster. The poster is only visible before video playback starts.
▶ Run Code
@@ -500,12 +507,13 @@ art.on('ready', () => { console.info(art.poster); }); ``` + ## `mini` -- Type: `Setter/Getter` -- Parameter: `Boolean` +- Type: `Setter/Getter` +- Parameter: `Boolean` -Set and get the player's mini mode +Sets and gets the player's mini mode.
▶ Run Code
@@ -522,10 +530,10 @@ art.on('ready', () => { ## `playing` -- Type: `Getter` -- Parameter: `Boolean` +- Type: `Getter` +- Parameter: `Boolean` -Get whether the video is currently playing +Gets whether the video is currently playing.
▶ Run Code
@@ -540,11 +548,12 @@ art.on('ready', () => { console.info(art.playing); }); ``` + ## `autoSize` - Type: `Function` -Sets whether the video should auto adjust its size +Sets whether the video should automatically adjust its size.
▶ Run Code
@@ -563,7 +572,7 @@ art.on('ready', () => { - Type: `Getter` -Gets the size and position information of the player +Gets the player's dimensions and coordinate information.
▶ Run Code
@@ -577,9 +586,10 @@ art.on('ready', () => { console.info(JSON.stringify(art.rect)); }); ``` -:::warning Warning -The size and coordinates are obtained via `getBoundingClientRect` +:::warning Note + +The dimension and coordinate information is obtained via `getBoundingClientRect`. ::: @@ -588,7 +598,7 @@ The size and coordinates are obtained via `getBoundingClientRect` - Type: `Setter/Getter` - Parameter: `String` -Set and get the player flip, supports `normal`, `horizontal`, `vertical` +Sets and gets the player flip mode. Supports `normal`, `horizontal`, `vertical`.
▶ Run Code
@@ -609,7 +619,8 @@ art.on('ready', () => { - Type: `Setter/Getter` - Parameter: `Number` -Set and get the playback speed of the player + +Sets and gets the player's playback speed.
▶ Run Code
@@ -631,7 +642,7 @@ art.on('ready', () => { - Type: `Setter/Getter` - Parameter: `String` -Set and get the aspect ratio of the player +Sets and gets the player's aspect ratio.
▶ Run Code
@@ -647,11 +658,12 @@ art.on('ready', () => { console.info(art.aspectRatio); }); ``` + ## `autoHeight` - Type: `Function` -When the container has only width, this attribute can automatically calculate and set the height of the video +When the container only has a defined width, this property can automatically calculate and set the video's height.
▶ Run Code
@@ -670,9 +682,9 @@ art.on('resize', () => { }); ``` -:::warning Warning +:::warning Note -When your container has only width but you do not know the exact height, this property is very useful as it can automatically calculate the video's height, but you need to make sure when to set this property. +This property is useful when your container has a defined width but an unknown height, as it automatically calculates the video's height. However, you need to determine the appropriate timing to set this property. ::: @@ -681,7 +693,7 @@ When your container has only width but you do not know the exact height, this pr - Type: `Function` - Parameter: `String` -Dynamically get and set the attributes of the video element +Dynamically gets and sets attributes of the video element.
▶ Run Code
@@ -700,10 +712,10 @@ art.on('ready', () => { ## `type` -- Type: `Setter/Getter` -- Parameter: `String` +- Type: `Setter/Getter` +- Parameter: `String` -Dynamically get and set the video type +Dynamically gets and sets the video type.
▶ Run Code
@@ -718,13 +730,13 @@ art.on('ready', () => { art.type = 'm3u8'; console.info(art.type); }); -``` + ## `theme` - Type: `Setter/Getter` - Parameter: `String` -Dynamically get and set the player's theme color +Dynamically gets and sets the player's theme color.
▶ Run Code
@@ -745,7 +757,7 @@ art.on('ready', () => { - Type: `Function` -Activate airplay +Initiates AirPlay.
▶ Run Code
@@ -764,11 +776,12 @@ var art = new Artplayer({ ], }); ``` + ## `loaded` -- Type: `Getter` +- Type: `Getter` -The proportion of the video that is cached, ranging from `[0, 1]`, commonly used with the `video:timeupdate` event. +The proportion of the video that has been buffered, ranging from `[0, 1]`. Often used with the `video:timeupdate` event.
▶ Run Code
@@ -785,9 +798,9 @@ art.on('video:timeupdate', () => { ## `played` -- Type: `Getter` +- Type: `Getter` -The proportion of the video that has been played, ranging from `[0, 1]`, commonly used with the `video:timeupdate` event. +The proportion of the video that has been played, ranging from `[0, 1]`. Often used with the `video:timeupdate` event.
▶ Run Code
@@ -801,11 +814,12 @@ art.on('video:timeupdate', () => { console.info(art.played); }); ``` + ## `proxy` -- Type: `Function` +- Type: `Function` -A proxy function for `DOM` events, which essentially proxies `addEventListener` and `removeEventListener`. When using `proxy` to handle events, the event will automatically be destroyed when the player is destroyed. +A proxy function for DOM events, essentially proxying `addEventListener` and `removeEventListener`. When using `proxy` to handle events, the event will be automatically removed when the player is destroyed.
▶ Run Code
@@ -822,9 +836,9 @@ art.proxy(container, 'click', event => { }); ``` -:::warning Tip +:::warning Note -If you need some `DOM` events to exist only during the lifespan of the player, it is highly recommended to use this function to avoid causing memory leaks. +If you need certain DOM events to exist only for the duration of the player's lifecycle, it is strongly recommended to use this function to avoid memory leaks. ::: @@ -832,7 +846,7 @@ If you need some `DOM` events to exist only during the lifespan of the player, i - Type: `Function` -DOM query function, similar to `document.querySelector`, but the object being queried is limited within the current player, which can prevent errors due to having the same class name. +A DOM query function, similar to `document.querySelector`, but the search is scoped to within the current player, preventing errors from duplicate class names.
▶ Run Code
@@ -849,7 +863,7 @@ console.info(art.query('.art-video')); - Type: `Element` -A shortcut to return the `video` element of the player. +Quickly returns the player's `video` element.
▶ Run Code
@@ -866,7 +880,7 @@ console.info(art.video); - Type: `Function` -Dynamically getting or setting `css` variables +Dynamically gets or sets CSS variables.
▶ Run Code
@@ -888,7 +902,7 @@ art.on('ready', () => { - Type: `Setter` - Parameter: `Array` -Dynamically setting the list of qualities +Dynamically sets the list of available quality levels.
▶ Run Code
@@ -931,7 +945,7 @@ art.on('ready', () => { - Type: `Setter/Getter` - Parameter: `Object` -Dynamically set thumbnails +Dynamically sets the thumbnails.
▶ Run Code
@@ -970,5 +984,4 @@ var art = new Artplayer({ art.on('ready', () => { art.subtitleOffset = 1; -}); -``` \ No newline at end of file +}); \ No newline at end of file diff --git a/packages/artplayer-vitepress/docs/en/component/contextmenu.md b/packages/artplayer-vitepress/docs/en/component/contextmenu.md index 2ec517c80..2832b2a5d 100644 --- a/packages/artplayer-vitepress/docs/en/component/contextmenu.md +++ b/packages/artplayer-vitepress/docs/en/component/contextmenu.md @@ -2,16 +2,16 @@ ## Configuration -| Property | Type | Description | -| --------- | ------------------- | ----------------------------| -| `disable` | `Boolean` | Whether to disable the component | -| `name` | `String` | Unique name of the component, used for marking the class name | -| `index` | `Number` | Component index, used for display priority | -| `html` | `String`, `Element` | The DOM element of the component | -| `style` | `Object` | Style object for the component | -| `click` | `Function` | Click event for the component | -| `mounted` | `Function` | Triggered after the component is mounted | -| `tooltip` | `String` | Tooltip text for the component | +| Property | Type | Description | +| --------- | ------------------- | ------------------------------------ | +| `disable` | `Boolean` | Whether to disable the component | +| `name` | `String` | Unique component name for CSS class | +| `index` | `Number` | Component index for display priority | +| `html` | `String`, `Element` | Component DOM element | +| `style` | `Object` | Component style object | +| `click` | `Function` | Component click event | +| `mounted` | `Function` | Triggered after component mount | +| `tooltip` | `String` | Component tooltip text | ## Creation @@ -38,7 +38,8 @@ art.contextmenu.show = true; // Get the Element of contextmenu by name console.info(art.contextmenu['your-menu']); ``` -## Add + +## Addition
▶ Run Code
@@ -63,7 +64,7 @@ art.contextmenu.show = true; console.info(art.contextmenu['your-menu']); ``` -## Delete +## Removal
▶ Run Code
@@ -87,13 +88,13 @@ art.contextmenu.show = true; art.on('ready', () => { setTimeout(() => { - // Delete the context menu by name + // Delete the contextmenu by name art.contextmenu.remove('your-menu') }, 3000); }); ``` -## Updates +## Update
▶ Run Code
@@ -123,5 +124,4 @@ art.on('ready', () => { html: 'Your New Menu', }) }, 3000); -}); -``` \ No newline at end of file +}); \ No newline at end of file diff --git a/packages/artplayer-vitepress/docs/en/component/controls.md b/packages/artplayer-vitepress/docs/en/component/controls.md index 8cd5824ef..fc15bb7ca 100644 --- a/packages/artplayer-vitepress/docs/en/component/controls.md +++ b/packages/artplayer-vitepress/docs/en/component/controls.md @@ -1,20 +1,20 @@ -# Controller +# Controllers ## Configuration -| Property | Type | Description | -| ---------- | ------------------- | ------------------------------------------ | -| `disable` | `Boolean` | Whether to disable the component | -| `name` | `String` | The unique name of the component for class identification | -| `index` | `Number` | The index of the component, used for display priority | -| `html` | `String`, `Element` | The component's DOM element | -| `style` | `Object` | The style object for the component | -| `click` | `Function` | Component click event | -| `mounted` | `Function` | Triggered after the component is mounted | -| `tooltip` | `String` | Tooltip text for the component | -| `position` | `String` | `left` and `right` control the position of the controller | -| `selector` | `Array` | An array of objects for the selection list | -| `onSelect` | `Function` | Function triggered when an item from the selection list is clicked | +| Property | Type | Description | +| ----------- | ------------------- | ------------------------------------------------ | +| `disable` | `Boolean` | Whether to disable the component | +| `name` | `String` | Unique component name for CSS class identification | +| `index` | `Number` | Component index for display priority | +| `html` | `String`, `Element` | Component DOM element | +| `style` | `Object` | Component style object | +| `click` | `Function` | Component click event | +| `mounted` | `Function` | Triggered after component mounting | +| `tooltip` | `String` | Component tooltip text | +| `position` | `String` | `left` and `right` control controller placement | +| `selector` | `Array` | Array of selector list objects | +| `onSelect` | `Function` | Function triggered when selector item is clicked | ## Creation @@ -66,7 +66,8 @@ var art = new Artplayer({ console.info(art.controls['your-button']); console.info(art.controls['subtitle']); ``` -## Adding + +## Addition
▶ Run Code
@@ -96,7 +97,8 @@ art.controls.add({ // Get the Element of control by name console.info(art.controls['button1']); ``` -## Delete + +## Removal
▶ Run Code
@@ -125,6 +127,7 @@ art.on('ready', () => { }, 3000); }); ``` + ## Update
▶ Run Code
@@ -171,5 +174,4 @@ art.on('ready', () => { ], }); }, 3000); -}); -``` \ No newline at end of file +}); \ No newline at end of file diff --git a/packages/artplayer-vitepress/docs/en/component/layers.md b/packages/artplayer-vitepress/docs/en/component/layers.md index b312fd93d..7f939332f 100644 --- a/packages/artplayer-vitepress/docs/en/component/layers.md +++ b/packages/artplayer-vitepress/docs/en/component/layers.md @@ -1,17 +1,17 @@ -# Layer +# Business Layer ## Configuration -| Property | Type | Description | -| --------- | --------------------- | ---------------------------- | -| `disable` | `Boolean` | Whether to disable the component | -| `name` | `String` | Unique name of the component, used for marking class name | -| `index` | `Number` | Component index, used for display priority | -| `html` | `String`, `Element` | The DOM element of the component | -| `style` | `Object` | Style object for the component | -| `click` | `Function` | Click event for the component | -| `mounted` | `Function` | Triggered after the component is mounted | -| `tooltip` | `String` | Tooltip text for the component | +| Property | Type | Description | +| --------- | ------------------- | ------------------------------------ | +| `disable` | `Boolean` | Whether to disable the component | +| `name` | `String` | Unique component name for class marking | +| `index` | `Number` | Component index for display priority | +| `html` | `String`, `Element` | Component DOM element | +| `style` | `Object` | Component style object | +| `click` | `Function` | Component click event | +| `mounted` | `Function` | Triggered after component mounting | +| `tooltip` | `String` | Component tooltip text | ## Creation @@ -24,9 +24,9 @@ var art = new Artplayer({ url: '/assets/sample/video.mp4', layers: [ { - name: 'poster', + name: 'potser', html: ``, - tooltip: 'Poster Tip', + tooltip: 'Potser Tip', style: { position: 'absolute', top: '50px', @@ -43,9 +43,10 @@ var art = new Artplayer({ }); // Get the Element of layer by name -console.info(art.layers['poster']); +console.info(art.layers['potser']); ``` -## Add + +## Addition
▶ Run Code
@@ -57,9 +58,9 @@ var art = new Artplayer({ }); art.layers.add({ - name: 'poster', + name: 'potser', html: ``, - tooltip: 'Poster Tip', + tooltip: 'Potser Tip', style: { position: 'absolute', top: '50px', @@ -74,9 +75,10 @@ art.layers.add({ }); // Get the Element of layer by name -console.info(art.layers['poster']); +console.info(art.layers['potser']); ``` -## Delete + +## Removal
▶ Run Code
@@ -87,7 +89,7 @@ var art = new Artplayer({ url: '/assets/sample/video.mp4', layers: [ { - name: 'poster', + name: 'potser', html: ``, style: { position: 'absolute', @@ -101,10 +103,11 @@ var art = new Artplayer({ art.on('ready', () => { setTimeout(() => { // Delete the layer by name - art.layers.remove('poster'); + art.layers.remove('potser'); }, 3000); }); ``` + ## Update
▶ Run Code
@@ -116,7 +119,7 @@ var art = new Artplayer({ url: '/assets/sample/video.mp4', layers: [ { - name: 'poster', + name: 'potser', html: ``, style: { position: 'absolute', @@ -131,7 +134,7 @@ art.on('ready', () => { setTimeout(() => { // Update the layer by name art.layers.update({ - name: 'poster', + name: 'potser', html: ``, style: { position: 'absolute', @@ -140,5 +143,4 @@ art.on('ready', () => { }, }); }, 3000); -}); -``` \ No newline at end of file +}); \ No newline at end of file diff --git a/packages/artplayer-vitepress/docs/en/component/setting.md b/packages/artplayer-vitepress/docs/en/component/setting.md index 45aed67c0..383d66418 100644 --- a/packages/artplayer-vitepress/docs/en/component/setting.md +++ b/packages/artplayer-vitepress/docs/en/component/setting.md @@ -2,7 +2,7 @@ ## Built-in -First, open the settings panel, and then it comes with four built-in items: `flip`, `playbackRate`, `aspectRatio`, `subtitleOffset` +First, you need to open the settings panel. It comes with four built-in items: `flip`, `playbackRate`, `aspectRatio`, `subtitleOffset`
▶ Run Code
@@ -10,7 +10,7 @@ First, open the settings panel, and then it comes with four built-in items: `fli var art = new Artplayer({ container: '.artplayer-app', url: '/assets/sample/video.mp4', - setting: true, + setting: true, flip: true, playbackRate: true, aspectRatio: true, @@ -20,13 +20,13 @@ var art = new Artplayer({ ## Create - Button -| Property | Type | Description | -| ---------- | ------------------- | ------------- | -| `html` | `String`, `Element` | Item DOM | -| `icon` | `String`, `Element` | Item Icon | -| `onClick` | `Function` | Click event | -| `width` | `Number` | List Width | -| `tooltip` | `String` | Tooltip text | +| Property | Type | Description | +| ---------- | ------------------- | -------------------- | +| `html` | `String`, `Element` | Element DOM | +| `icon` | `String`, `Element` | Element icon | +| `onClick` | `Function` | Element click event | +| `width` | `Number` | List width | +| `tooltip` | `String` | Tooltip text |
▶ Run Code
@@ -49,16 +49,16 @@ var art = new Artplayer({ }); ``` -## Create - Selection List +## Create - Selector List -| Property | Type | Description | -| ---------- | ------------------- | ------------- | -| `html` | `String`, `Element` | Item DOM | -| `icon` | `String`, `Element` | Item Icon | -| `selector` | `Array` | Item selector | -| `onSelect` | `Function` | Click event | -| `width` | `Number` | List Width | -| `tooltip` | `String` | Tooltip text | +| Property | Type | Description | +| ---------- | ------------------- | -------------------- | +| `html` | `String`, `Element` | Element DOM | +| `icon` | `String`, `Element` | Element icon | +| `selector` | `Array` | Element list | +| `onSelect` | `Function` | Element click event | +| `width` | `Number` | List width | +| `tooltip` | `String` | Tooltip text |
▶ Run Code
@@ -118,7 +118,7 @@ var art = new Artplayer({ }); ``` -## Creating - Nested Lists +## Create - Nested List
▶ Run Code
@@ -167,16 +167,16 @@ var art = new Artplayer({ }, ], }); -``` + ## Create - Toggle Button -| Property | Type | Description | -| ---------- | ------------------- | ---------------------- | -| `html` | `String`, `Element` | Element's DOM element | -| `icon` | `String`, `Element` | Element's icon | -| `switch` | `Boolean` | Button's default state | -| `onSwitch` | `Function` | Button toggle event | -| `tooltip` | `String` | Tooltip text | +| Property | Type | Description | +| ---------- | ------------------- | ------------------------- | +| `html` | `String`, `Element` | DOM element of the item | +| `icon` | `String`, `Element` | Icon of the item | +| `switch` | `Boolean` | Default state of the button | +| `onSwitch` | `Function` | Button toggle event | +| `tooltip` | `String` | Tooltip text |
▶ Run Code
@@ -202,16 +202,17 @@ var art = new Artplayer({ ], }); ``` + ## Create - Range Slider -| Attribute | Type | Description | -| ---------- | ------------------- | ------------------------------- | -| `html` | `String`, `Element` | The element's DOM element | -| `icon` | `String`, `Element` | The element's icon | -| `range` | `Array` | Default state array | -| `onRange` | `Function` | Event triggered upon completion | -| `onChange` | `Function` | Event triggered on change | -| `tooltip` | `String` | Tooltip text | +| Property | Type | Description | +| ---------- | ------------------- | ------------------------- | +| `html` | `String`, `Element` | DOM element of the item | +| `icon` | `String`, `Element` | Icon of the item | +| `range` | `Array` | Default state array | +| `onRange` | `Function` | Event triggered on completion | +| `onChange` | `Function` | Event triggered on change | +| `tooltip` | `String` | Tooltip text | ```js const range = [5, 1, 10, 1]; @@ -220,6 +221,7 @@ const min = range[1]; const max = range[2]; const step = range[3]; ``` +
▶ Run Code
```js @@ -241,6 +243,7 @@ var art = new Artplayer({ ], }); ``` + ## Add
▶ Run Code
@@ -262,7 +265,7 @@ art.setting.add({ }); ``` -## Delete +## Remove
▶ Run Code
@@ -292,7 +295,8 @@ art.on('ready', () => { }, 3000); }); ``` -## Updates + +## Update
▶ Run Code
@@ -325,5 +329,4 @@ art.on('ready', () => { switch: false, }); }, 3000); -}); -``` \ No newline at end of file +}); \ No newline at end of file diff --git a/packages/artplayer-vitepress/docs/en/index.md b/packages/artplayer-vitepress/docs/en/index.md index c301a9874..72419497e 100644 --- a/packages/artplayer-vitepress/docs/en/index.md +++ b/packages/artplayer-vitepress/docs/en/index.md @@ -1,4 +1,4 @@ -# Installation and Usage +# Installation & Usage ## Installation @@ -22,7 +22,7 @@ pnpm add artplayer ::: -## `CDN` +## CDN ::: code-group @@ -66,19 +66,20 @@ https://unpkg.com/artplayer/dist/artplayer.js ``` ::: -::: warning Warning -The player's size depends on the size of the container `container`, so your container `container` must have a size. +::: warning Note + +The player's dimensions depend on the size of its `container`, so your container must have defined dimensions ::: -::: tip You can find more usage examples at the link below +::: tip See more usage examples at the following link [/packages/artplayer-template](https://github.com/zhw2590582/ArtPlayer/tree/master/packages/artplayer-template) ::: -## `Vue.js` +## Vue.js ::: code-group @@ -144,14 +145,13 @@ function getInstance(art) { ::: +::: warning Artplayer is not reactive: -::: warning Artplayer Not Responsive: - -Modifying `option` directly in `Vue.js` will not change the player +Directly modifying `option` in `Vue.js` will not update the player ::: -## `React.js` +## React.js ::: code-group @@ -205,15 +205,15 @@ export default App ::: -::: warning Non-responsive Artplayer +::: warning Artplayer is not reactive: -In `React.js`, directly modifying the `option` will not change the player +Directly modifying `option` in `React.js` will not update the player ::: ## TypeScript -Importing `Artplayer` will automatically import `artplayer.d.ts` +The `artplayer.d.ts` file is automatically imported when importing `Artplayer` ### Vue.js @@ -250,16 +250,17 @@ option.volume = 0.5; const art = new Artplayer(option); ``` -::: tip Complete TypeScript Definitions +::: tip All TypeScript Definitions [packages/artplayer/types](https://github.com/zhw2590582/ArtPlayer/tree/master/packages/artplayer/types) ::: + ## JavaScript -Sometimes your `js` file may lose the `TypeScript` type hints, in this case, you can manually import the types +Sometimes your `js` files may lose `TypeScript` type hints. In such cases, you can manually import the types. -Variables: +Variable: ```js{1-3} /** @@ -268,7 +269,7 @@ Variables: let art = null; ``` -Parameters: +Parameter: ```js{1-3} /** @@ -279,7 +280,7 @@ function getInstance(art) { } ``` -Properties: +Property: ```js{4-6} export default { @@ -294,7 +295,7 @@ export default { } ``` -Options: +Option: ```js{1-3} /** @@ -310,11 +311,12 @@ option.volume = 0.5; const art8 = new Artplayer(option); ``` -## Ancient Browsers -The production build of `artplayer.js` is only compatible with the latest major version of `Chrome`: `last 1 Chrome version` +## Legacy Browsers -For ancient browsers, you can use the `artplayer.legacy.js` file, which is compatible up to: `IE 11` +The production build `artplayer.js` only supports the latest major version of `Chrome`: `last 1 Chrome version`. + +For legacy browsers, you can use the `artplayer.legacy.js` file, which supports compatibility down to: `IE 11`. ```js import Artplayer from 'artplayer/legacy' @@ -332,11 +334,11 @@ https://unpkg.com/artplayer/dist/artplayer.legacy.js ::: -::: tip If you need to support even older browsers, please modify the following configuration and then build it yourself: +::: tip If you need to support even older browsers, modify the following configuration and build it yourself: -Build configuration: [scripts/build.js](https://github.com/zhw2590582/ArtPlayer/blob/master/scripts/build.js#L29) +Build Configuration: [scripts/build.js](https://github.com/zhw2590582/ArtPlayer/blob/master/scripts/build.js#L29) -Refer to documentation: [browserslist](https://github.com/browserslist/browserslist#full-list) +Reference Documentation: [browserslist](https://github.com/browserslist/browserslist#full-list) ::: @@ -348,7 +350,7 @@ Refer to documentation: [browserslist](https://github.com/browserslist/browsersl ::: -Starting from `5.2.6`, `artplayer` and all plugins will also provide an `ESM` version of `mjs`, such as: +Starting from `5.2.6`, `artplayer` and all plugins also provide an `ESM` version `mjs`, such as: - `artplayer/dist/artplayer.mjs` - `artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.mjs` @@ -371,7 +373,7 @@ Starting from `5.2.6`, `artplayer` and all plugins will also provide an `ESM` ve @@ -394,7 +396,7 @@ Starting from `5.2.6`, `artplayer` and all plugins will also provide an `ESM` ve ## Custom userAgent -Currently, the determination of mobile devices is not accurate. Sometimes, you may want to adjust the player UI by changing the `userAgent`. Therefore, starting in version `5.2.4`, we added a new `globalThis.CUSTOM_USER_AGENT` global variable. +Currently, the detection for whether a device is mobile is not entirely accurate. Sometimes you may want to adjust the player's UI by changing the `userAgent`. Therefore, starting from `5.2.4`, a `globalThis.CUSTOM_USER_AGENT` global variable has been added. ```html @@ -422,8 +424,8 @@ Currently, the determination of mobile devices is not accurate. Sometimes, you m ``` -::: warning Warning +::: warning Note -You need to modify it before import the `Artplayer` for it to take effect +You need to modify it before importing the `Artplayer` dependency for it to take effect. -::: +::: \ No newline at end of file diff --git a/packages/artplayer-vitepress/docs/en/plugin/danmuku.md b/packages/artplayer-vitepress/docs/en/plugin/danmuku.md new file mode 100644 index 000000000..e8a265342 --- /dev/null +++ b/packages/artplayer-vitepress/docs/en/plugin/danmuku.md @@ -0,0 +1,555 @@ +# Danmaku Library + +## Demo + +👉 [View Full Demo](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-danmuku/index.js&example=danmuku) + +## Installation + +::: code-group + +```bash [npm] +npm install artplayer-plugin-danmuku +``` + +```bash [yarn] +yarn add artplayer-plugin-danmuku +``` + +```bash [pnpm] +pnpm add artplayer-plugin-danmuku +``` + +```html [script] + +``` + +::: + +## CDN + +::: code-group + +```bash [jsdelivr.net] +https://cdn.jsdelivr.net/npm/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js +``` + +```bash [unpkg.com] +https://unpkg.com/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js +``` + +::: + +## Danmaku Structure + +Each danmaku is an object, and an array of multiple danmaku objects forms the danmaku library. Typically, only `text` is required to send a danmaku, while the rest are optional parameters. + +```js +{ + text: '', // Danmaku text + time: 10, // Danmaku timestamp, defaults to current player time + mode: 0, // Danmaku mode: 0: scroll (default), 1: top, 2: bottom + color: '#FFFFFF', // Danmaku color, defaults to white + border: false, // Whether the danmaku has a border, defaults to false + style: {}, // Custom danmaku styles, defaults to an empty object +} +``` + +## All Options + +Only `danmuku` is a required parameter; all others are optional. + +```js +{ + danmuku: [], // Danmaku data + speed: 5, // Danmaku duration, range [1 ~ 10] + margin: [10, '25%'], // Danmaku top and bottom margins, supports pixel values and percentages + opacity: 1, // Danmaku opacity, range [0 ~ 1] + color: '#FFFFFF', // Default danmaku color, can be overridden by individual danmaku items + mode: 0, // Default danmaku mode: 0: scroll, 1: top, 2: bottom + modes: [0, 1, 2], // Visible danmaku modes + fontSize: 25, // Danmaku font size, supports pixel values and percentages + antiOverlap: true, // Whether to prevent danmaku overlap + synchronousPlayback: false, // Whether to synchronize playback speed + mount: undefined, // Danmaku emitter mount point, defaults to the middle of the player control bar + heatmap: false, // Whether to enable the heatmap + width: 512, // When the player width is less than this value, the danmaku emitter is placed at the bottom of the player + points: [], // Heatmap data + filter: () => true, // Filter before danmaku loading, only supports boolean return values + beforeEmit: () => true, // Filter before danmaku emission, supports Promise return + beforeVisible: () => true, // Filter before danmaku display, supports Promise return + visible: true, // Whether the danmaku layer is visible + emitter: true, // Whether to enable the danmaku emitter + maxLength: 200, // Maximum input length for the danmaku input box, range [1 ~ 1000] + lockTime: 5, // Input box lock time, range [1 ~ 60] + theme: 'dark', // Danmaku theme, supports 'dark' and 'light', only effective when custom mounted + OPACITY: {}, // Opacity configuration + FONT_SIZE: {}, // Font size configuration + MARGIN: {}, // Display area configuration + SPEED: {}, // Danmaku speed configuration + COLOR: [], // Color list configuration +} +``` + +## Lifecycle + +For user-input danmaku: + +`beforeEmit -> filter -> beforeVisible -> artplayerPluginDanmuku:visible` + +For server-side danmaku: + +`filter -> beforeVisible -> artplayerPluginDanmuku:visible` + +
+ ▶ Run Code +
+ +```js +// Save to database +function saveDanmu(danmu) { + return new Promise(resolve => { + setTimeout(() => { + resolve(true); + }, 1000); + }) +} + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + + // This function is triggered when the user enters danmaku text in the input box and clicks the send button + // You can perform validation on the danmaku or save it to the database + // The danmaku is added to the queue only when true is returned + async beforeEmit(danmu) { + const isDirty = (/fuck/i).test(danmu.text); + if (isDirty) return false; + const state = await saveDanmu(danmu); + return state; + }, + + // This is a filter for all danmaku, including those from the server and user input + // You can perform validation on the danmaku + // The danmaku is added to the queue only when true is returned + filter(danmu) { + return danmu.text.length <= 200; + }, + + // This function is triggered when the danmaku is about to be displayed + // You can perform validation on the danmaku + // The danmaku is sent to the player only when true is returned + async beforeVisible(danmu) { + return true; + }, + }), + ], +}); + +// The danmaku has appeared in the player, and you can access its DOM element +art.on('artplayerPluginDanmuku:visible', danmu => { + danmu.$ref.innerHTML = 'ଘ(੭ˊᵕˋ)੭: ' + danmu.$ref.innerHTML; +}) +``` + +## Using Danmaku Array + +
+ ▶ Run Code +
+ +```js +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: [ + { + text: 'Using array', + time: 1 + }, + ], + }), + ], +}); + +## Using Danmaku XML + +The danmaku XML file follows the same format as Bilibili's danmaku system + +
+ ▶ Run Code +
+ +```js +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + ], +}); +``` + +## Using Asynchronous Returns + +
+ ▶ Run Code +
+ +```js +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: function () { + return new Promise((resovle) => { + return resovle([ + { + text: 'Using Promise for asynchronous return', + time: 1 + }, + ]); + }); + }, + }), + ], +}); +``` + +## `hide/show` + +Use the `hide` and `show` methods to hide or display danmaku + +
+ ▶ Run Code +
+ +```js +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + ], + controls: [ + { + position: 'right', + html: 'Hide Danmaku', + click: function () { + art.plugins.artplayerPluginDanmuku.hide(); + }, + }, + { + position: 'right', + html: 'Show Danmaku', + click: function () { + art.plugins.artplayerPluginDanmuku.show(); + }, + }, + ], +}); +``` + +## `isHide` + +Use the `isHide` property to determine if danmaku is currently hidden or displayed + +
+ ▶ Run Code +
+ +```js +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + ], + controls: [ + { + position: 'right', + html: 'Hide Danmaku', + click: function (_, event) { + if (art.plugins.artplayerPluginDanmuku.isHide) { + art.plugins.artplayerPluginDanmuku.show(); + event.target.innerText = 'Hide Danmaku'; + } else { + art.plugins.artplayerPluginDanmuku.hide(); + event.target.innerText = 'Show Danmaku'; + } + }, + }, + ], +}); +``` + +## `emit` + +Use the `emit` method to send a real-time danmaku + +
+ ▶ Run Code +
+ +```js +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + ], + controls: [ + { + position: 'right', + html: 'Send Danmaku', + click: function () { + var text = prompt('Please enter danmaku text', 'Danmaku test text'); + if (!text || !text.trim()) return; + var color = '#' + Math.floor(Math.random() * 0xffffff).toString(16); + art.plugins.artplayerPluginDanmuku.emit({ + text: text, + color: color, + border: true, + }); + }, + }, + ], +}); +``` + +## `config` + +Use the `config` method to dynamically change danmaku settings + +
+ ▶ Run Code +
+ +```js +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + ], + controls: [ + { + position: 'right', + html: 'Danmaku Size:', + style: { + display: 'flex', + alignItems: 'center', + }, + mounted: function ($setting) { + const $range = $setting.querySelector('input[type=range]'); + $range.addEventListener('change', () => { + art.plugins.artplayerPluginDanmuku.config({ + fontSize: Number($range.value), + }); + }); + }, + }, + ], +}); + +## `load` + +The `load` method can be used to reload the current danmaku library, switch to a new danmaku library, or append a new danmaku library. + +
+ ▶ Run Code +
+ +```js +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + emitter: false, + }), + ], + controls: [ + { + position: 'right', + html: 'Reload', + click: function () { + // Reload the current danmaku library + art.plugins.artplayerPluginDanmuku.load(); + }, + }, + { + position: 'right', + html: 'Switch', + click: function () { + // Switch to a new danmaku library + art.plugins.artplayerPluginDanmuku.config({ + danmuku: '/assets/sample/danmuku-v2.xml', + }); + art.plugins.artplayerPluginDanmuku.load(); + }, + }, + { + position: 'right', + html: 'Append', + click: function () { + // Append a new danmaku library (parameter type is the same as option.danmuku) + const target = '/assets/sample/danmuku.xml' + art.plugins.artplayerPluginDanmuku.load(target); + }, + }, + ], +}); +``` + +## `reset` + +Used to clear the currently displayed danmaku. + +
+ ▶ Run Code +
+ +```js +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + ], +}); + +art.on('resize', () => { + art.plugins.artplayerPluginDanmuku.reset(); +}); +``` + +## `mount` + +When initializing the danmaku plugin, you can specify the mount position for the danmaku emitter. By default, it is mounted in the center of the control bar. You can also mount it outside the player. + +When the player enters fullscreen mode, the emitter will automatically return to the center of the control bar. If the mounted location has a light background, it is recommended to set `theme` to `light` to ensure visibility. + +
+ ▶ Run Code +
+ +```js +var $danmu = document.querySelector('.artplayer-app'); + +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + fullscreenWeb: true, + plugins: [ + artplayerPluginDanmuku({ + mount: $danmu, + theme: 'dark', + danmuku: '/assets/sample/danmuku.xml', + }), + ], +}); + +// Can also be mounted manually +// art.plugins.artplayerPluginDanmuku.mount($danmu); +``` + +## `option` + +Used to get the current danmaku configuration. + +
+ ▶ Run Code +
+ +```js +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + ], +}); + +art.on('ready', () => { + console.info(art.plugins.artplayerPluginDanmuku.option); +}); +``` + +## Events + +
+ ▶ Run Code +
+ +```js +var art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginDanmuku({ + danmuku: '/assets/sample/danmuku.xml', + }), + ], +}); + +art.on('artplayerPluginDanmuku:visible', (danmu) => { + console.info('Danmaku visible', danmu); +}); + +art.on('artplayerPluginDanmuku:loaded', (danmus) => { + console.info('Danmaku loaded', danmus.length); +}); + +art.on('artplayerPluginDanmuku:error', (error) => { + console.info('Load error', error); +}); + +art.on('artplayerPluginDanmuku:config', (option) => { + console.info('Configuration changed', option); +}); + +art.on('artplayerPluginDanmuku:stop', () => { + console.info('Danmaku stopped'); +}); + +art.on('artplayerPluginDanmuku:start', () => { + console.info('Danmaku started'); +}); + +art.on('artplayerPluginDanmuku:hide', () => { + console.info('Danmaku hidden'); +}); + +art.on('artplayerPluginDanmuku:show', () => { + console.info('Danmaku shown'); +}); + +art.on('artplayerPluginDanmuku:reset', () => { + console.info('Danmaku reset'); +}); + +art.on('artplayerPluginDanmuku:destroy', () => { + console.info('Danmaku destroyed'); +}); \ No newline at end of file diff --git a/packages/artplayer-vitepress/docs/en/start/i18n.md b/packages/artplayer-vitepress/docs/en/start/i18n.md index 2fff2ca00..4aff6bdac 100644 --- a/packages/artplayer-vitepress/docs/en/start/i18n.md +++ b/packages/artplayer-vitepress/docs/en/start/i18n.md @@ -2,19 +2,19 @@ ::: danger -Given the increasing number of bundled multilingual packs, starting from version `5.1.0`, the core code of `artplayer.js` will no longer include other languages besides `Simplified Chinese` and `English`. You need to import the required languages on your own. +Due to the increasing number of bundled multi-language packs, starting from version `5.1.0`, the `artplayer.js` core code only includes `Simplified Chinese` and `English` by default. Other languages are no longer bundled and must be manually imported as needed. ::: :::warning -When a language cannot be matched, English will be displayed by default. For i18n usage, refer to: [artplayer/types/i18n.d.ts](https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/i18n.d.ts) +When a language cannot be matched, English will be displayed by default. For i18n syntax reference, see: [artplayer/types/i18n.d.ts](https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/i18n.d.ts) ::: ## Default Languages -The default languages are: `en`, `zh-cn`, which do not require manual import. +The default languages are: `en`, `zh-cn`, no manual import required ```js{4} var art = new Artplayer({ @@ -26,9 +26,9 @@ var art = new Artplayer({ ## Importing Languages -Language files before packaging are located in: `artplayer/src/i18n/*.js`. +Language files before bundling are located at: `artplayer/src/i18n/*.js`. Contributions to add your language are welcome. -Packaged language files are located at: `artplayer/dist/i18n/*.js`. +Language files after bundling are located at: `artplayer/dist/i18n/*.js` ::: code-group @@ -63,7 +63,8 @@ var art = new Artplayer({ ``` ::: -## Add Language + +## Adding a New Language ```js{4-9} var art = new Artplayer({ @@ -78,7 +79,7 @@ var art = new Artplayer({ }); ``` -## Modify Language +## Modifying Languages ```js import zhTw from 'artplayer/i18n/zh-tw'; @@ -97,5 +98,4 @@ var art = new Artplayer({ Play: 'Your Play' }, }, -}); -``` \ No newline at end of file +}); \ No newline at end of file diff --git a/packages/artplayer-vitepress/docs/en/start/option.md b/packages/artplayer-vitepress/docs/en/start/option.md index 948ece9cd..88aa7f0d2 100644 --- a/packages/artplayer-vitepress/docs/en/start/option.md +++ b/packages/artplayer-vitepress/docs/en/start/option.md @@ -2,22 +2,22 @@ ## `container` -- Type: `String, Element` -- Default: `#artplayer` +- Type: `String, Element` +- Default: `#artplayer` -The `DOM` container where the player mounts +The `DOM` container where the player is mounted.
▶ Run Code
```js{2} var art = new Artplayer({ - container: '.artplayer-app', + container: '.artplayer-app', // container: document.querySelector('.artplayer-app'), url: '/assets/sample/video.mp4', }); ``` -You may need to initialize the size of the container element, like: +You may need to initialize the size of the container element, for example: ```css{2-3} .artplayer-app { @@ -34,17 +34,18 @@ Or use `aspect-ratio`: } ``` -:::warning Tip +:::warning Note -Among all options, only `container` is mandatory. +Among all options, only `container` is required. ::: + ## `url` -- Type: `String` -- Default: `''` +- Type: `String` +- Default: `''` -The video source address +The video source URL.
▶ Run Code
@@ -55,7 +56,7 @@ var art = new Artplayer({ }); ``` -Sometimes the `url` is not known so quickly, in which case you can set the `url` asynchronously. +Sometimes the `url` is not known immediately; in such cases, you can set the `url` asynchronously.
▶ Run Code
@@ -71,17 +72,18 @@ setTimeout(() => { :::warning Note -The default support is for three video file formats: `.mp4`, `.ogg`, `.webm`. +By default, three video file formats are supported: `.mp4`, `.ogg`, `.webm`. -If you need to play other formats such as `.m3u8` or `.flv`, please refer to the `Third-party Libraries` on the left side. +To play other formats like `.m3u8` or `.flv`, please refer to the `Third-party Libraries` section on the left. ::: + ## `id` -- Type: `String` -- Default: `''` +- Type: `String` +- Default: `''` -The unique identifier of the player, currently used only for remembering playback with `autoplayback` +The unique identifier for the player, currently only used for playback resumption (`autoplayback`).
▶ Run Code
@@ -95,10 +97,10 @@ var art = new Artplayer({ ## `onReady` -- Type: `Function` -- Default: `undefined` +- Type: `Function` +- Default: `undefined` -The constructor accepts a function as the second argument, which is triggered when the player is successfully initialized and the video is ready to play, similar to the `ready` event +The constructor accepts a function as the second argument, which is triggered when the player is successfully initialized and the video is ready to play, similar to the `ready` event.
▶ Run Code
@@ -114,6 +116,7 @@ var art = new Artplayer( }, ); ``` + Equivalent to: ```js{7-9} @@ -128,9 +131,9 @@ art.on('ready', () => { }); ``` -:::warning Warning +:::warning Note -The `this` inside the callback function refers to the player instance, but if an arrow function is used for the callback, `this` will not point to the player instance. +Inside the callback function, `this` refers to the player instance. However, if an arrow function is used for the callback, `this` will not point to the player instance. ::: @@ -139,7 +142,7 @@ The `this` inside the callback function refers to the player instance, but if an - Type: `String` - Default: `''` -The poster of the video, which only appears when the player is initialized and before it starts playing. +The video poster image, which only appears when the player is initialized and not yet playing.
▶ Run Code
@@ -150,12 +153,13 @@ var art = new Artplayer({ poster: '/assets/sample/poster.jpg', }); ``` + ## `theme` -- Type: `String` -- Default: `#f00` +- Type: `String` +- Default: `#f00` -Player theme color, currently used for `progress bar` and `highlight elements` +The player's theme color, currently used for the `progress bar` and `highlighted elements`.
▶ Run Code
@@ -169,10 +173,10 @@ var art = new Artplayer({ ## `volume` -- Type: `Number` -- Default: `0.7` +- Type: `Number` +- Default: `0.7` -The default volume of the player +The default volume of the player.
▶ Run Code
@@ -184,17 +188,18 @@ var art = new Artplayer({ }); ``` -:::warning Warning +:::warning Note -The player will cache the size of the last volume. When the player is initialized next time (such as refreshing the page), it will read this cached value. +The player caches the last volume level; upon next initialization (e.g., page refresh), the player will read this cached value. ::: + ## `isLive` - Type: `Boolean` - Default: `false` -Use live mode, which will hide the progress bar and playback time +Enable live streaming mode, which hides the progress bar and playback time.
▶ Run Code
@@ -211,7 +216,7 @@ var art = new Artplayer({ - Type: `Boolean` - Default: `false` -Whether to mute by default +Whether to default to muted.
▶ Run Code
@@ -228,10 +233,7 @@ var art = new Artplayer({ - Type: `Boolean` - Default: `false` -Whether to play automatically -- Default: `false` - -Autoplay +Whether to autoplay.
▶ Run Code
@@ -244,18 +246,18 @@ var art = new Artplayer({ }); ``` -:::warning Warning +:::warning Note -If you want the video to autoplay by default upon entering the page, `muted` must be set to `true`. For more information, please read [Autoplay Policy Changes](https://developers.google.com/web/updates/2017/09/autoplay-policy-changes) +If you want the video to autoplay when entering the page by default, `muted` must be set to `true`. For more information, please read [Autoplay Policy Changes](https://developers.google.com/web/updates/2017/09/autoplay-policy-changes). ::: ## `autoSize` -- Type: `Boolean` -- Default: `false` +- Type: `Boolean` +- Default: `false` -The size of the player will by default fill the entire `container`, which often results in black bars. This value can automatically adjust the player size to hide the black bars, similar to `css`'s `object-fit: cover;` +By default, the player's dimensions fill the entire `container`, which often results in black bars. This option automatically adjusts the player size to hide black bars, similar to `object-fit: cover;` in CSS.
▶ Run Code
@@ -272,7 +274,7 @@ var art = new Artplayer({ - Type: `Boolean` - Default: `false` -Automatically enter `mini mode` when the player scrolls out of the browser viewport +Automatically switches to `mini player` mode when the player scrolls outside the browser viewport.
▶ Run Code
@@ -289,7 +291,7 @@ var art = new Artplayer({ - Type: `Boolean` - Default: `false` -Whether to loop the video +Whether to enable video looping.
▶ Run Code
@@ -303,10 +305,10 @@ var art = new Artplayer({ ## `flip` -- Type: `Boolean` -- Default: `false` +- Type: `Boolean` +- Default: `false` -Whether to show the video flip feature, currently only available in `Settings Panel` and `Context Menu` +Whether to display the video flip functionality. Currently appears in the `Settings Panel` and `Context Menu`.
▶ Run Code
@@ -321,10 +323,10 @@ var art = new Artplayer({ ## `playbackRate` -- Type: `Boolean` -- Default: `false` +- Type: `Boolean` +- Default: `false` -Whether to show the playback speed feature of the video, it will appear in `Settings Panel` and `Context Menu` +Whether to display the playback rate functionality. Appears in the `Settings Panel` and `Context Menu`.
▶ Run Code
@@ -342,7 +344,7 @@ var art = new Artplayer({ - Type: `Boolean` - Default: `false` -Displays the video aspect ratio feature, it will appear in the `Settings Panel` and `Context Menu` +Whether to display the aspect ratio functionality. Appears in the `Settings Panel` and `Context Menu`.
▶ Run Code
@@ -360,8 +362,7 @@ var art = new Artplayer({ - Type: `Boolean` - Default: `false` -Whether to show the screenshot button, it will appear in the `toolbar` and `context menu` -Display `Screenshot` function in the bottom control bar +Whether to display the `Screenshot` button in the bottom control bar.
▶ Run Code
@@ -373,18 +374,18 @@ var art = new Artplayer({ }); ``` -:::warning Warning +:::warning Note -Due to browser security mechanisms, if the video source address and the website are cross-origin, screenshot failure may occur +Due to browser security mechanisms, screenshot capture may fail if the video source is cross-origin with the website. ::: ## `setting` -- Type: `Boolean` -- Default: `false` +- Type: `Boolean` +- Default: `false` -Display the toggle button for the `Settings Panel` in the bottom control bar +Whether to display the `Settings Panel` toggle button in the bottom control bar.
▶ Run Code
@@ -395,12 +396,13 @@ var art = new Artplayer({ setting: true, }); ``` + ## `hotkey` -- Type: `Boolean` -- Default: `true` +- Type: `Boolean` +- Default: `true` -Whether to use hotkeys +Whether to enable hotkeys.
▶ Run Code
@@ -412,25 +414,26 @@ var art = new Artplayer({ }); ``` -| Hotkey | Description | -| ------- | ------------------ | -| `↑` | Increase volume | -| `↓` | Decrease volume | -| `←` | Fast forward video | -| `→` | Rewind video | -| `space` | Toggle play/pause | +| Hotkey | Description | +| ------- | ------------------------- | +| `↑` | Increase volume | +| `↓` | Decrease volume | +| `←` | Seek backward | +| `→` | Seek forward | +| `space` | Toggle play/pause | -:::warning Tip +:::warning Note -These hotkeys will only work after the player gains focus (such as after clicking on the player). +These hotkeys only take effect after the player gains focus (e.g., by clicking on the player). ::: ## `pip` -- Type: `Boolean` -- Default: `false` -Show the `Picture in Picture` switch button on the bottom control bar +- Type: `Boolean` +- Default: `false` + +Whether to display the `Picture-in-Picture` toggle button in the bottom control bar.
▶ Run Code
@@ -447,7 +450,7 @@ var art = new Artplayer({ - Type: `Boolean` - Default: `true` -If there are multiple players on the page, should only one player be allowed to play at a time +When multiple players exist on the page, whether only one player is allowed to play at a time.
▶ Run Code
@@ -463,7 +466,8 @@ var art = new Artplayer({ - Type: `Boolean` - Default: `false` -Display the `Window Fullscreen` button on the control bar at the bottom + +Whether to display the `Fullscreen` button in the bottom control bar.
▶ Run Code
@@ -480,7 +484,7 @@ var art = new Artplayer({ - Type: `Boolean` - Default: `false` -Display the `Web Fullscreen` button on the control bar at the bottom +Whether to display the `Webpage Fullscreen` button in the bottom control bar.
▶ Run Code
@@ -490,15 +494,13 @@ var art = new Artplayer({ url: '/assets/sample/video.mp4', fullscreenWeb: true, }); -``` ## `subtitleOffset` - Type: `Boolean` - - Default: `false` -Subtitle time offset, the range is `[-5s, 5s]`, appearing in the `Settings panel` +Subtitle time offset, ranging from `[-5s, 5s]`, appears in the `Settings Panel`
▶ Run Code
@@ -519,7 +521,7 @@ var art = new Artplayer({ - Type: `Boolean` - Default: `false` -Mini progress bar, only appears when the player loses focus and is playing +Mini progress bar, appears only when the player loses focus and is playing
▶ Run Code
@@ -536,9 +538,9 @@ var art = new Artplayer({ - Type: `Boolean` - Default: `false` -Whether to use `SSR` (Server-Side Rendering) mount mode, useful if you want to pre-render the player's required `HTML` before the player mounts +Whether to use `SSR` mount mode. Useful if you want to pre-render the player's required `HTML` before the player is mounted. -You can access the player's required `HTML` through `Artplayer.html` +You can access the player's required `HTML` via `Artplayer.html`
▶ Run Code
@@ -575,7 +577,7 @@ var art = new Artplayer({ - Type: `Array` - Default: `[]` -Initializes custom `layers` +Initialize custom `layers`
▶ Run Code
@@ -586,7 +588,7 @@ var art = new Artplayer({ url: '/assets/sample/video.mp4', layers: [ { - name: 'poster', + name: 'potser', html: ``, style: { position: 'absolute', @@ -605,7 +607,8 @@ var art = new Artplayer({ ], }); ``` -:::warning `Component Configuration` Please refer to the following address: + +:::warning For `Component Configuration`, please refer to: [/component/layers.html](/component/layers.html) @@ -616,7 +619,7 @@ var art = new Artplayer({ - Type: `Array` - Default: `[]` -Initializes custom `settings panel` +Initialize custom `Settings Panel`
▶ Run Code
@@ -657,7 +660,8 @@ var art = new Artplayer({ ], }); ``` -:::warning `Settings Panel` Please refer to the following address + +:::warning For `Settings Panel`, please refer to: [/component/setting.html](/component/setting.html) @@ -668,7 +672,7 @@ var art = new Artplayer({ - Type: `Array` - Default: `[]` -Initialize custom `context menu` +Initialize custom `Context Menu`
▶ Run Code
@@ -687,7 +691,8 @@ var art = new Artplayer({ ], }); ``` -:::warning `Component Configuration` Please refer to the following address: + +:::warning For `Component Configuration`, please refer to: [/component/contextmenu.html](/component/contextmenu.html)- Type: @@ -698,7 +703,7 @@ var art = new Artplayer({ - Type: `Array` - Default: `[]` -Initializes custom bottom `control bar` +Initialize custom bottom `control bar`
▶ Run Code
@@ -721,7 +726,8 @@ var art = new Artplayer({ ], }); ``` -:::warning `Component Configuration` Refer to the following address: + +:::warning For `component configuration`, please refer to: [/component/controls.html](/component/controls.html) @@ -732,13 +738,13 @@ var art = new Artplayer({ - Type: `Array` - Default: `[]` -Whether to show the `quality selection` list in the bottom control bar +Whether to display the `quality selection` list in the bottom control bar -| Property | Type | Description | -| --------- | --------- | --------------- | -| `default` | `Boolean` | Default quality | -| `html` | `String` | Quality name | -| `url` | `String` | Quality URL | +| Property | Type | Description | +| --------- | --------- | ---------------- | +| `default` | `Boolean` | Default quality | +| `html` | `String` | Quality name | +| `url` | `String` | Quality URL |
▶ Run Code
@@ -759,17 +765,18 @@ var art = new Artplayer({ ], }); ``` + ## `highlight` -- Type: `Array` -- Default: `[]` +- Type: `Array` +- Default: `[]` -Show `highlight information` on the progress bar +Display `highlight information` on the progress bar -| Property | Type | Description | -| -------- | -------- | --------------------------- | -| `time` | `Number` | Highlight time (in seconds) | -| `text` | `String` | Highlight text | +| Property | Type | Description | +| -------- | -------- | ------------------------------- | +| `time` | `Number` | Highlight time (in seconds) | +| `text` | `String` | Highlight text |
▶ Run Code
@@ -801,6 +808,7 @@ var art = new Artplayer({ ], }); ``` + ## `plugins` - Type: `Array` @@ -840,7 +848,7 @@ Set `thumbnails` on the progress bar | -------- | -------- | --------------------- | | `url` | `String` | Thumbnail URL | | `number` | `Number` | Number of thumbnails | -| `column` | `Number` | Columns of thumbnails | +| `column` | `Number` | Number of columns | | `width` | `Number` | Thumbnail width | | `height` | `Number` | Thumbnail height | | `scale` | `Number` | Thumbnail scale | @@ -858,7 +866,8 @@ var art = new Artplayer({ }, }); ``` -:::warning Online thumbnail generation + +:::warning Generate thumbnails online [artplayer-tool-thumbnail](https://artplayer.org/?libs=./uncompiled/artplayer-tool-thumbnail/index.js&example=thumbnail) @@ -869,17 +878,18 @@ var art = new Artplayer({ - Type: `Object` - Default: `{}` -Set the video subtitles, supporting subtitle formats: `vtt`, `srt`, `ass` +Set video subtitles, supported subtitle formats: `vtt`, `srt`, `ass` -| Property | Type | Description | -| ----------- | ---------- | --------------------------------------------- | -| `name` | `String` | Subtitle name | -| `url` | `String` | Subtitle URL | -| `type` | `String` | Subtitle type, options: `vtt`, `srt`, `ass` | -| `style` | `Object` | Subtitle style | -| `encoding` | `String` | Subtitle encoding, default `utf-8` | -| `escape` | `Boolean` | Whether to escape `html` tags, default `true` | -| `onVttLoad` | `Function` | Function to modify `vtt` text | +| Property | Type | Description | +| ---------- | -------- | ------------------------------------------ | +| `name` | `String` | Subtitle name | +| `url` | `String` | Subtitle URL | +| `type` | `String` | Subtitle type, options: `vtt`, `srt`, `ass` | + +| `style` | `Object` | Subtitle style | +| `encoding` | `String` | Subtitle encoding, defaults to `utf-8` | +| `escape` | `Boolean` | Whether to escape `html` tags, defaults to `true` | +| `onVttLoad` | `Function` | Function used to modify `vtt` text |
▶ Run Code
@@ -899,12 +909,13 @@ var art = new Artplayer({ }, }); ``` + ## `moreVideoAttr` - Type: `Object` -- Default: `{'controls': false, 'preload': 'metadata'}` +- Default: `{'controls': false,'preload': 'metadata'}` -Additional video attributes, these attributes will be directly written into the video element +More video attributes, these attributes will be directly written into the video element
▶ Run Code
@@ -924,7 +935,7 @@ var art = new Artplayer({ - Type: `Object` - Default: `{}` -Used to replace default icons, supports `Html` strings and `HTMLElement` +Used to replace default icons, supports `Html` string and `HTMLElement`
▶ Run Code
@@ -933,12 +944,13 @@ var art = new Artplayer({ container: '.artplayer-app', url: '/assets/sample/video.mp4', icons: { - loading: '', + loading: '', state: '', }, }); ``` -:::warning Definition of all icons + +:::warning All Icon Definitions [artplayer/types/icons.d.ts](https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/icons.d.ts) @@ -949,7 +961,7 @@ var art = new Artplayer({ - Type: `String` - Default: `''` -Used to specify the format of the video, which is used in conjunction with `customType`. By default, the video format is the same as the file extension of the video URL (such as `.m3u8`, `.mkv`, `.ts`). However, sometimes the video URL does not have the correct file extension, so it needs to be specifically stated. +Used to specify the video format, needs to be used together with `customType`. The default video format is the suffix of the video URL (e.g., `.m3u8`, `.mkv`, `.ts`). However, sometimes the video URL does not have the correct suffix, so it needs to be explicitly specified.
▶ Run Code
@@ -961,25 +973,26 @@ var art = new Artplayer({ }); ``` -:::warning Recognition of file extensions +:::warning Suffix Recognition -The player can only parse this type of file extension: `/assets/sample/video.m3u8` +The player can only parse suffixes like this: `/assets/sample/video.m3u8` -But it cannot parse this type of file extension: `/assets/sample/video?type=m3u8` +But cannot parse suffixes like this: `/assets/sample/video?type=m3u8` -Therefore, if you use `customType`, it is best to specify `type` as well +Therefore, if you use `customType`, it is best to also specify `type`. ::: + ## `customType` - Type: `Object` - Default: `{}` -Matches the video's `type` to delegate the video decoding to a third-party program for processing. The processing function can receive three parameters: +Matches via the video's `type` and delegates video decoding to a third-party program for processing. The processing function can receive three parameters: -- `video`: Video `DOM` element -- `url`: Video address -- `art`: Current instance +- `video` : Video `DOM` element +- `url` : Video URL +- `art` : Current instance
▶ Run Code
@@ -1001,6 +1014,7 @@ var art = new Artplayer({ - Default: `navigator.language.toLowerCase()` Default display language, currently supports: `en`, `zh-cn` +
▶ Run Code
```js{4} @@ -1011,7 +1025,7 @@ var art = new Artplayer({ }); ``` -:::warning More language settings +:::warning More Language Settings [/start/i18n.html](/start/i18n.html) @@ -1022,7 +1036,7 @@ var art = new Artplayer({ - Type: `Object` - Default: `{}` -Custom `i18n` configuration, which will be deeply merged with the built-in `i18n` +Custom `i18n` configuration, this configuration will be deeply merged with the built-in `i18n` Add your language: @@ -1040,7 +1054,8 @@ var art = new Artplayer({ }, }); ``` -Modifying the existing language + +Modify existing languages:
▶ Run Code
@@ -1059,7 +1074,7 @@ var art = new Artplayer({ }); ``` -:::warning More language settings +:::warning More Language Settings [/start/i18n.html](/start/i18n.html) @@ -1070,7 +1085,7 @@ var art = new Artplayer({ - Type: `Boolean` - Default: `false` -Whether to display a `lock button` on mobile to hide the bottom `control bar` +Whether to display a `lock button` on mobile devices to hide the bottom `control bar`
▶ Run Code
@@ -1085,9 +1100,10 @@ var art = new Artplayer({ ## `gesture` - Type: `Boolean` + - Default: `true` -Whether to enable gesture events on video elements on mobile devices +Whether to enable gesture events on video elements for mobile devices
▶ Run Code
@@ -1101,10 +1117,10 @@ var art = new Artplayer({ ## `fastForward` -- Type: `Boolean` -- Default: `false` +- Type: `Boolean` +- Default: `false` -Whether to add fast forward function by long pressing on the video on mobile devices +Whether to add a long-press video fast-forward feature for mobile devices
▶ Run Code
@@ -1118,10 +1134,10 @@ var art = new Artplayer({ ## `autoPlayback` -- Type: `Boolean` -- Default: `false` +- Type: `Boolean` +- Default: `false` -Whether to use the automatic playback feature +Whether to use the automatic `playback feature`
▶ Run Code
@@ -1133,20 +1149,21 @@ var art = new Artplayer({ autoPlayback: true, }); ``` -:::warning Warning -Because the player by default uses `url` as the `key` to cache the playback progress +:::warning Note -But if the `url` of the same video is different, then you need to use `id` to identify the unique `key` of the video +The player uses the `url` as the default `key` to cache playback progress. + +However, if the same video has different `url`s, you need to use the `id` to identify the unique `key` for the video. ::: ## `autoOrientation` -- Type: `Boolean` -- Default: `false` +- Type: `Boolean` +- Default: `false` -Whether to rotate the player on mobile web pages in full screen according to the video size and the viewport dimensions +Whether to rotate the player during fullscreen mode on mobile devices, based on the video dimensions and viewport size
▶ Run Code
@@ -1160,10 +1177,10 @@ var art = new Artplayer({ ## `airplay` -- Type: `Boolean` -- Default: `false` +- Type: `Boolean` +- Default: `false` -Whether to display the `airplay` button, currently only some browsers support this feature +Whether to display the `airplay` button (currently only supported by some browsers)
▶ Run Code
@@ -1180,7 +1197,7 @@ var art = new Artplayer({ - Type: `Object` - Default: `{}` -Used to change built-in CSS variables +Used to modify the built-in CSS variables
▶ Run Code
@@ -1196,7 +1213,7 @@ var art = new Artplayer({ :::warning Reference for cssVar syntax -[artplayer/types/cssVar.d.ts](https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/cssVar.d.ts) +[artplayer/types/cssVar.d.d.ts](https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/cssVar.d.ts) ::: @@ -1214,5 +1231,4 @@ var art = new Artplayer({ container: '.artplayer-app', url: '/assets/sample/video.mp4', proxy: () => document.createElement('video') -}); -``` \ No newline at end of file +}); \ No newline at end of file diff --git a/scripts/build-llm.js b/scripts/build-llm.js new file mode 100644 index 000000000..0e388dba0 --- /dev/null +++ b/scripts/build-llm.js @@ -0,0 +1,158 @@ +import fs from 'node:fs' +import path from 'node:path' +import process from 'node:process' +import { fileURLToPath } from 'node:url' +import dotenv from 'dotenv' +import { glob } from 'glob' + +dotenv.config() + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const rootDir = path.resolve(__dirname, '../packages/artplayer-vitepress') +const dostDir = path.resolve(__dirname, '../docs') +const outputFile = path.resolve(__dirname, '../docs/llms.txt') +const API_URL = 'https://api.deepseek.com/v1/chat/completions' +const API_KEY = process.env.DEEPL_API_KEY +const MAX_CONCURRENT_REQUESTS = 3 // 每类并发请求数 + +if (!API_KEY) { + console.error('❌ Missing DEEPL_API_KEY in .env file') + process.exit(1) +} + +async function getFiles(pattern) { + const files = await glob(pattern.replace(/\\/g, '/'), { nodir: true }) + return files.sort() +} + +function readFiles(files) { + let content = '' + for (const f of files) { + console.log(`📄 Reading: ${f}`) + content += `\n\n===== ${path.basename(f)} =====\n\n` + content += fs.readFileSync(f, 'utf8') + } + return content +} + +function splitText(text, maxLen = 5000) { + const paragraphs = text.split(/\n{2,}/) + const chunks = [] + let buffer = '' + for (const p of paragraphs) { + if ((`${buffer}\n\n${p}`).length > maxLen) { + chunks.push(buffer) + buffer = '' + } + buffer += `\n\n${p}` + } + if (buffer.trim()) + chunks.push(buffer) + return chunks +} + +async function askDeepSeek(prompt, text, tag, id) { + console.log(`🧠 [${tag}] Sending chunk ${id}`) + const res = await fetch(API_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${API_KEY}`, + }, + body: JSON.stringify({ + model: 'deepseek-chat', + messages: [ + { role: 'system', content: 'You are a professional technical writer.' }, + { role: 'user', content: `${prompt}\n\n${text}` }, + ], + temperature: 0.3, + }), + }) + + if (!res.ok) { + const err = await res.text() + console.error(`[${tag}] API Error:`, err) + throw new Error(`DeepSeek API error: ${res.status}`) + } + + const data = await res.json() + return data.choices?.[0]?.message?.content?.trim() || '' +} + +async function runConcurrent(tasks, limit = MAX_CONCURRENT_REQUESTS) { + const results = [] + const executing = new Set() + for (const task of tasks) { + const p = task().finally(() => executing.delete(p)) + results.push(p) + executing.add(p) + if (executing.size >= limit) { + await Promise.race(executing) + } + } + return Promise.all(results) +} + +async function summarizeSection(title, files, type) { + if (files.length === 0) + return '' + const text = readFiles(files) + const chunks = splitText(text, 8000) + + let prompt = '' + if (type === 'docs') { + prompt = ` +You are preparing documentation content for an AI model to learn ArtPlayer. +Reorganize the following documentation into a clean, readable plain text format. +Keep all code blocks, examples, and configuration options fully intact. +Add minimal explanations or clarifications in natural English before or after each block if helpful. +Do not use Markdown syntax (no #, **, or lists). Preserve indentation for code.` + } + else if (type === 'ts') { + prompt = ` +You are analyzing TypeScript declaration files that define the APIs of ArtPlayer. +Your goal is to produce a developer-oriented plain text output that preserves the actual type definitions, +interfaces, classes, and comments exactly as they are, while adding concise explanations +above or below them when necessary to clarify their purpose or usage. +Keep all code in the output, formatted as plain text (no Markdown).` + } + else if (type === 'js') { + prompt = ` +You are analyzing example JavaScript files demonstrating how to use ArtPlayer and its plugins. +Produce a plain text output that includes the full example code and short inline explanations +describing what each example shows, which APIs it uses, and what feature it demonstrates. +Keep all code exactly as-is, formatted as plain text, without Markdown syntax.` + } + + const tasks = chunks.map((chunk, i) => () => askDeepSeek(prompt, chunk, type, i + 1)) + const results = await runConcurrent(tasks, MAX_CONCURRENT_REQUESTS) + const merged = results.join('\n\n') + return `\n\n===== ${title} =====\n\n${merged}` +} + +async function main() { + console.log('🚀 Building llms.txt using DeepSeek...') + + const [mdFiles, tsFiles, jsFiles] = await Promise.all([ + getFiles(`${rootDir}/docs/en/**/*.md`), + getFiles(`${dostDir}/assets/ts/*.d.ts`), + getFiles(`${dostDir}/assets/example/*.js`), + ]) + + // 并行执行三个大类任务 + const [docs, ts, js] = await Promise.all([ + summarizeSection('Documentation Summary', mdFiles, 'docs'), + summarizeSection('Type Definitions Overview', tsFiles, 'ts'), + summarizeSection('Examples Summary', jsFiles, 'js'), + ]) + + const finalText = [docs, ts, js].filter(Boolean).join('\n\n') + fs.mkdirSync(path.dirname(outputFile), { recursive: true }) + fs.writeFileSync(outputFile, finalText, 'utf8') + console.log(`✅ LLMs text built successfully at ${outputFile}`) +} + +main().catch((err) => { + console.error('❌ Build failed:', err) + process.exit(1) +}) diff --git a/scripts/plugin/template/package.json b/scripts/plugin/template/package.json index 3ffc1d564..c471db281 100644 --- a/scripts/plugin/template/package.json +++ b/scripts/plugin/template/package.json @@ -27,7 +27,7 @@ "types": "./types/artplayer-plugin-{{name}}.d.ts", "import": "./dist/artplayer-plugin-{{name}}.legacy.js", "require": "./dist/artplayer-plugin-{{name}}.legacy.js" - + } }, "main": "dist/artplayer-plugin-{{name}}.js", diff --git a/scripts/trans-docs.js b/scripts/trans-docs.js index d3f79c1a7..40c13566c 100644 --- a/scripts/trans-docs.js +++ b/scripts/trans-docs.js @@ -1,175 +1,148 @@ import fs from 'node:fs' import path from 'node:path' import process from 'node:process' -import cpy from 'cpy' +import { fileURLToPath } from 'node:url' import dotenv from 'dotenv' import { glob } from 'glob' -import { removeDir } from './utils.js' dotenv.config() -// 配置常量 -const MAX_RETRIES = 3 -const RETRY_DELAY = 1000 // 1秒 -const MAX_CHARS_PER_REQUEST = 128000 -const BASE_PATH = 'packages/artplayer-vitepress/docs' -const EN_PATH = 'packages/artplayer-vitepress/docs/en' -const COPY_DIRS = ['advanced', 'component', 'plugin', 'start'] +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const rootDir = path.resolve(__dirname, '../packages/artplayer-vitepress') +const srcDirs = ['docs/advanced', 'docs/component', 'docs/start'] +const indexFile = 'docs/index.md' +const outputRoot = path.join(rootDir, 'docs/en') +const API_URL = 'https://api.deepseek.com/v1/chat/completions' +const API_KEY = process.env.DEEPL_API_KEY -function splitMarkdown(text, maxChars) { - const parts = [] - let currentPart = '' - let inCodeBlock = false - let inSpecialBlock = false - - const lines = text.split('\n') - for (let i = 0; i < lines.length; i++) { - const line = lines[i] - - // 检测代码块 - if (line.trim().startsWith('```')) { - inCodeBlock = !inCodeBlock - } - - // 检测特殊块 - if (line.trim().startsWith(':::')) { - inSpecialBlock = !inSpecialBlock - } - - // 添加当前行到当前部分 - currentPart += `${line}\n` - - // 检测是否达到字符限制 - if (!inCodeBlock && !inSpecialBlock && currentPart.length > maxChars && i < lines.length - 1) { - parts.push(currentPart) - currentPart = '' - } - } - - // 添加最后一部分 - if (currentPart !== '') { - parts.push(currentPart) - } - - return parts +if (!API_KEY) { + console.error('❌ Missing DEEPL_API_KEY in .env file') + process.exit(1) } -async function fetchWithRetry(url, options, retries = MAX_RETRIES) { - try { - const response = await fetch(url, { - ...options, +function splitMarkdown(text, maxLen = 4000) { + const lines = text.split('\n') + const chunks = [] + let buffer = '' + let insideCodeBlock = false + + for (const line of lines) { + if (line.trim().startsWith('```')) + insideCodeBlock = !insideCodeBlock + if ((`${buffer}\n${line}`).length > maxLen && !insideCodeBlock) { + chunks.push(buffer) + buffer = '' + } + buffer += `\n${line}` + } + + if (buffer.trim()) + chunks.push(buffer) + return chunks +} + +function cleanTranslation(text) { + return text + .replace(/^```(markdown|md)?/gi, '') + .replace(/```$/g, '') + .replace(/^(Here('|’)s|Below is|Translation|The English version|Here is)[::]?\s*/gi, '') + .replace(/(Translation completed\.?|End of translation\.?)$/gi, '') + .trim() +} + +async function translateText(text) { + const chunks = splitMarkdown(text, 4000) + let result = '' + + for (let i = 0; i < chunks.length; i++) { + console.log(`🔹 Translating chunk ${i + 1}/${chunks.length}`) + const prompt = ` +You are a professional English technical translator for documentation. +Translate the following Markdown content from Simplified Chinese to fluent English. +Keep all Markdown structure, code blocks, and formatting intact. +Return ONLY the translated Markdown content — do NOT add any explanations, prefixes, or summaries. + +Text to translate: +${chunks[i]} + `.trim() + + const res = await fetch(API_URL, { + method: 'POST', headers: { 'Content-Type': 'application/json', - ...options.headers, + 'Authorization': `Bearer ${API_KEY}`, }, - }) - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) - } - - return await response.json() - } - catch (error) { - if (retries > 0) { - console.log(`Retrying... (${MAX_RETRIES - retries + 1}/${MAX_RETRIES})`) - await new Promise(resolve => setTimeout(resolve, RETRY_DELAY)) - return fetchWithRetry(url, options, retries - 1) - } - throw error - } -} - -async function translateContent(content, targetLanguage) { - try { - const TRANSLATE_URL = 'https://api.deepseek.com/chat/completions' - const response = await fetchWithRetry(TRANSLATE_URL, { - headers: { - Authorization: `Bearer ${process.env.DEEPL_API_KEY}`, - }, - method: 'POST', body: JSON.stringify({ - stream: false, model: 'deepseek-chat', - messages: [ - { - role: 'user', - content: `The following text is written in VitePress's extended Markdown syntax, Please translate it into ${targetLanguage}, and keep the Markdown format and don't add your explanation:\n\n${content}`, - }, - ], + messages: [{ role: 'user', content: prompt }], + temperature: 0.2, }), }) - return response.choices[0].message.content - } - catch (error) { - console.error(`Translation failed: ${error.message}`) - return content // 返回原文作为回退 - } -} - -class ProgressTracker { - constructor(totalParts) { - this.totalParts = totalParts - this.translatedPartsCount = 0 - } - - update() { - this.translatedPartsCount++ - const progressPercentage = ((this.translatedPartsCount / this.totalParts) * 100).toFixed(2) - console.log(`Overall progress: ${progressPercentage}% (${this.translatedPartsCount}/${this.totalParts})`) - } -} - -async function translateMarkdownFiles(filePaths, targetLanguage, maxCharsPerRequest) { - // 先计算总共有多少部分需要翻译 - let totalParts = 0 - for (const filePath of filePaths) { - const content = fs.readFileSync(filePath, 'utf-8') - totalParts += splitMarkdown(content, maxCharsPerRequest).length - } - - console.log(`Starting translation of ${filePaths.length} files, total ${totalParts} parts.`) - const progressTracker = new ProgressTracker(totalParts) - - for (const filePath of filePaths) { - console.log(`Translating: ${path.relative(EN_PATH, filePath)}`) - - const content = fs.readFileSync(filePath, 'utf-8') - const parts = splitMarkdown(content, maxCharsPerRequest) - const translatedParts = [] - - for (const part of parts) { - const translatedPart = await translateContent(part, targetLanguage) - translatedParts.push(translatedPart) - progressTracker.update() + if (!res.ok) { + const error = await res.text() + console.error('API Error:', error) + throw new Error(`DeepSeek API error: ${res.status}`) } - const translatedContent = translatedParts.join('\n') - fs.writeFileSync(filePath, translatedContent) + const data = await res.json() + const raw = data.choices?.[0]?.message?.content?.trim() || '' + const translated = cleanTranslation(raw) + result += `\n\n${translated}` } + + return result.trim() } -async function setupDirectoryStructure() { - // 清理并创建目录结构 - removeDir(EN_PATH) - await cpy(path.resolve(BASE_PATH, 'index.md'), path.resolve(EN_PATH)) - - // 并行复制所有目录 - await Promise.all( - COPY_DIRS.map(dir => cpy(path.resolve(BASE_PATH, dir), path.resolve(EN_PATH, dir), { flat: true })), - ) +async function processFile(inputPath, outputPath) { + const content = fs.readFileSync(inputPath, 'utf8') + console.log(`🌍 Translating: ${inputPath}`) + const translated = await translateText(content) + fs.mkdirSync(path.dirname(outputPath), { recursive: true }) + fs.writeFileSync(outputPath, translated, 'utf8') + console.log(`✅ Saved: ${outputPath}`) } -(async () => { - try { - await setupDirectoryStructure() - const mdsPaths = glob.sync(path.resolve(EN_PATH, '**/*.md')) - await translateMarkdownFiles(mdsPaths, 'English', MAX_CHARS_PER_REQUEST) - console.log('Translation completed successfully!') +async function runWithConcurrency(tasks, limit = 5) { + const results = [] + const executing = new Set() + for (const task of tasks) { + const p = task().finally(() => executing.delete(p)) + results.push(p) + executing.add(p) + if (executing.size >= limit) { + await Promise.race(executing) + } } - catch (error) { - console.error('Translation failed:', error) - process.exit(1) + return Promise.all(results) +} + +async function main() { + console.log('🚀 Starting translation...') + fs.rmSync(outputRoot, { recursive: true, force: true }) + fs.mkdirSync(outputRoot, { recursive: true }) + + const indexPath = path.join(rootDir, indexFile) + const indexOut = path.join(outputRoot, 'index.md') + await processFile(indexPath, indexOut) + + const tasks = [] + for (const dir of srcDirs) { + const fullDir = path.join(rootDir, dir) + const files = await glob(`${fullDir}/**/*.md`) + for (const file of files) { + const relative = path.relative(rootDir, file) + const outputFile = path.join(outputRoot, relative.replace(/^docs[\\/]/, '')) + tasks.push(() => processFile(file, outputFile)) + } } -})() + + console.log(`🧠 Total files: ${tasks.length}, concurrency: 5`) + await runWithConcurrency(tasks, 5) + console.log('🎉 Translation complete!') +} + +main().catch((err) => { + console.error('❌ Translation failed:', err) + process.exit(1) +}) diff --git a/test/esm.test.js b/test/esm.test.js index df304e49a..778283de2 100644 --- a/test/esm.test.js +++ b/test/esm.test.js @@ -1,5 +1,5 @@ import Artplayer from 'artplayer' import artplayerPluginDanmuku from 'artplayer-plugin-danmuku' -console.log(Artplayer.version) -console.log(typeof artplayerPluginDanmuku) +console.warn(Artplayer.version) +console.warn(typeof artplayerPluginDanmuku) diff --git a/test/i18n.test.js b/test/i18n.test.js index 87c58752f..3d70c9f6f 100644 --- a/test/i18n.test.js +++ b/test/i18n.test.js @@ -1,3 +1,3 @@ -import id from 'artplayer/i18n/id'; +import id from 'artplayer/i18n/id' -console.log(id) +console.warn(id) diff --git a/test/ssr.test.js b/test/ssr.test.js index aaf5f155c..da1bcc6cb 100644 --- a/test/ssr.test.js +++ b/test/ssr.test.js @@ -1,3 +1,3 @@ import Artplayer from 'artplayer' -console.log(Artplayer.html) \ No newline at end of file +console.warn(Artplayer.html) diff --git a/test/types.test.js b/test/types.test.js deleted file mode 100644 index e52d469b9..000000000 --- a/test/types.test.js +++ /dev/null @@ -1,77 +0,0 @@ -// https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html - -// ======================== ES6 Module ========================== - -import Artplayer from '../packages/artplayer'; - -const art1 = new Artplayer({ - container: '.artplayer-app', - url: './assets/sample/video.mp4', -}); - -// ======================== CommonJS Module ========================== - -const Artplayer = require('../packages/artplayer'); - -const art2 = new Artplayer({ - container: '.artplayer-app', - url: './assets/sample/video.mp4', -}); - -// ======================== Triple-Slash Directives ========================== - -/// - -/** - * @type {Artplayer} - An Artplayer instance. - */ -const art3 = {}; - -/** - * @param {Artplayer} art4 - An Artplayer instance. - */ -function getInstance(art4) { - // -} - -// ======================== @typedef ========================== - -/** - * @typedef { import("../packages/artplayer") } Artplayer - */ - -/** - * @type {Artplayer} - An Artplayer instance. - */ -const art5 = null; - -/** - * @param {Artplayer} art6 - An Artplayer instance. - */ -function getInstance(art6) { - // -} - -// ======================== @param ========================== - -/** - * @param { import("../packages/artplayer") } art7 - An Artplayer instance. - */ -function getInstance(art7) { - // -} - -// ======================== @option ========================== - -/** - * @type {import("../packages/artplayer/types/option").Option} - */ - -const option = { - container: '.artplayer-app', - url: './assets/sample/video.mp4', -}; - -option.volume = 0.5; - -const art8 = new Artplayer(option);