diff --git a/AGENTS.md b/AGENTS.md
index 352bf4088..e722bbe53 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -69,6 +69,9 @@ The repository contains:
- Preserve the existing visual and API style of sibling packages instead of inventing a new pattern.
- When touching demo examples, make sure the example still works in `http://localhost:8082`.
- Do not hand-edit `dist/`, `docs/compiled/`, or `docs/uncompiled/` unless a build step generated them.
+- Desktop editor sources live in `packages/artplayer-vitepress/browser/`; `docs/assets/js/common.js`,
+ `bootstrap.js`, `loader.js`, `mobile.js` and `packages/artplayer-vitepress/docs/public/main.js`
+ are generated with `yarn build:site-assets`. `editor-libraries.ts` is generated by `yarn build:ts`.
## Primary Commands
diff --git a/docs/assets/js/bootstrap.js b/docs/assets/js/bootstrap.js
new file mode 100644
index 000000000..b42d90fe3
--- /dev/null
+++ b/docs/assets/js/bootstrap.js
@@ -0,0 +1,42 @@
+// Generated by yarn build:site-assets. Do not edit.
+"use strict";
+(() => {
+ // packages/artplayer-vitepress/browser/editor-preferences.ts
+ function readPreference(key, storage = () => localStorage) {
+ try {
+ return storage().getItem(key) === "true";
+ } catch {
+ return false;
+ }
+ }
+
+ // packages/artplayer-vitepress/browser/bootstrap.ts
+ function loadScript(url) {
+ return new Promise((resolve, reject) => {
+ const script = document.createElement("script");
+ const clear = () => {
+ script.onload = null;
+ script.onerror = null;
+ };
+ script.onload = () => {
+ clear();
+ resolve();
+ };
+ script.onerror = () => {
+ clear();
+ script.remove();
+ reject(new Error(`Editor bootstrap failed: ${url}`));
+ };
+ script.src = url;
+ document.body.appendChild(script);
+ });
+ }
+ async function bootstrap() {
+ await loadScript(
+ readPreference("prod") ? "./compiled/artplayer.js" : "./uncompiled/artplayer/index.js"
+ );
+ await loadScript("./assets/js/vs/loader.js");
+ await loadScript("./assets/js/common.js");
+ }
+ void bootstrap().catch((error) => console.error(error));
+})();
diff --git a/docs/assets/js/common.js b/docs/assets/js/common.js
index d092642b4..94c52ff28 100644
--- a/docs/assets/js/common.js
+++ b/docs/assets/js/common.js
@@ -1,245 +1,416 @@
-/* global Artplayer, monaco */
-(function () {
- Artplayer.DEBUG = true
-
- if (Artplayer.utils.isMobile) {
- window.location.href = `./mobile.html${location.search}`
+// Generated by yarn build:site-assets. Do not edit.
+"use strict";
+(() => {
+ // packages/artplayer-vitepress/browser/editor-files.ts
+ function readEditorFile(file, signal, createReader = () => new FileReader()) {
+ return new Promise((resolve, reject) => {
+ const reader = createReader();
+ function clear() {
+ reader.onload = null;
+ reader.onerror = null;
+ reader.onabort = null;
+ signal.removeEventListener("abort", cancel);
+ }
+ function fail(error) {
+ clear();
+ reject(error);
+ }
+ function cancel() {
+ reader.abort();
+ fail(new Error(`File import cancelled: ${file.name}`));
+ }
+ reader.onload = () => {
+ if (typeof reader.result !== "string") {
+ fail(new Error(`File is not text: ${file.name}`));
+ return;
+ }
+ const value = reader.result;
+ clear();
+ resolve(value);
+ };
+ reader.onerror = () => fail(reader.error || new Error(`Reading file failed: ${file.name}`));
+ reader.onabort = () => fail(new Error(`File import cancelled: ${file.name}`));
+ signal.addEventListener("abort", cancel, { once: true });
+ try {
+ if (signal.aborted)
+ cancel();
+ else reader.readAsText(file);
+ } catch (error) {
+ fail(error);
+ }
+ });
}
-
- const $codeMirror = document.querySelector('.codeMirrorWrap')
- const $lib = document.querySelector('.libsInput')
- const $run = document.querySelector('.run')
- const $popups = document.querySelector('.popups')
- const $console = document.querySelector('.console')
- const $prod = document.querySelector('#prod')
- const $ts = document.querySelector('#ts')
- const $code = document.querySelector('#code')
- const $log = document.querySelector('#log')
- const $file = document.querySelector('#file')
- const $editor = document.querySelector('#editor')
-
- window.consoleLog($console)
-
- // Helper functions for localStorage
- const getStorageBoolean = key => localStorage.getItem(key) === 'true'
- const setStorageBoolean = (key, value) => localStorage.setItem(key, value ? 'true' : 'false')
-
- $prod.checked = getStorageBoolean('prod')
- $ts.checked = getStorageBoolean('ts')
- $code.checked = getStorageBoolean('code')
- $log.checked = getStorageBoolean('log')
-
- if ($code.checked) {
- $editor.style.display = 'none'
- }
-
- if ($log.checked) {
- $console.style.display = 'none'
- }
-
- let editor = null
- let initialization = 0
- require.config({ paths: { vs: './assets/js/vs' } })
- require(['vs/editor/editor.main'], async () => {
- monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
- noSemanticValidation: true,
- noSyntaxValidation: false,
- })
-
- monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
- target: monaco.languages.typescript.ScriptTarget.ES6,
- allowNonTsExtensions: true,
- })
-
- const libUris = [
- './assets/ts/artplayer-plugin-ads.d.ts',
- './assets/ts/artplayer-plugin-ambilight.d.ts',
- './assets/ts/artplayer-plugin-asr.d.ts',
- './assets/ts/artplayer-plugin-audio-track.d.ts',
- './assets/ts/artplayer-plugin-auto-thumbnail.d.ts',
- './assets/ts/artplayer-plugin-chapter.d.ts',
- './assets/ts/artplayer-plugin-chromecast.d.ts',
- './assets/ts/artplayer-plugin-danmuku-mask.d.ts',
- './assets/ts/artplayer-plugin-danmuku.d.ts',
- './assets/ts/artplayer-plugin-dash-control.d.ts',
- './assets/ts/artplayer-plugin-document-pip.d.ts',
- './assets/ts/artplayer-plugin-hls-control.d.ts',
- './assets/ts/artplayer-plugin-jassub.d.ts',
- './assets/ts/artplayer-plugin-multiple-subtitles.d.ts',
- './assets/ts/artplayer-plugin-vast.d.ts',
- './assets/ts/artplayer-plugin-vtt-thumbnail.d.ts',
- './assets/ts/artplayer-proxy-canvas.d.ts',
- './assets/ts/artplayer-proxy-mediabunny.d.ts',
- './assets/ts/artplayer-tool-iframe.d.ts',
- './assets/ts/artplayer-tool-thumbnail.d.ts',
- './assets/ts/artplayer.d.ts',
- './assets/ts/artplayer-i18n.d.ts',
- ]
-
- for (let index = 0; index < libUris.length; index++) {
- const libUri = libUris[index]
- const libSource = await (await fetch(libUri)).text()
- monaco.languages.typescript.javascriptDefaults.addExtraLib(libSource, libUri)
- monaco.editor.createModel(libSource, 'typescript', monaco.Uri.parse(libUri))
- }
-
- const disposable = monaco.editor.onDidCreateEditor(() => {
- disposable.dispose()
- setTimeout(initApp, 1000)
- })
-
- editor = monaco.editor.create($codeMirror, {
- theme: 'vs-dark',
- folding: true,
- automaticLayout: true,
- quickSuggestions: {
- other: true,
- comments: true,
- strings: true,
+ function createFileImports(document, label) {
+ const controller = new AbortController();
+ const elements = [];
+ let queue = Promise.resolve();
+ return {
+ add(files) {
+ const work = queue.then(async () => {
+ for (const file of files) {
+ const name = file.name.toLowerCase().trim();
+ if (!name.endsWith(".js") && !name.endsWith(".css"))
+ continue;
+ const text = await readEditorFile(file, controller.signal);
+ if (controller.signal.aborted)
+ return;
+ const element = document.createElement(
+ name.endsWith(".css") ? "style" : "script"
+ );
+ element.textContent = text;
+ document.body.appendChild(element);
+ elements.push(element);
+ label(name);
+ }
+ });
+ queue = work.catch(() => {
+ });
+ return work;
},
- model: monaco.editor.createModel(
+ dispose() {
+ controller.abort();
+ for (const element of elements) element.remove();
+ elements.length = 0;
+ }
+ };
+ }
+
+ // packages/artplayer-vitepress/browser/editor-libraries.ts
+ var libUris = [
+ "./assets/ts/artplayer-plugin-ads.d.ts",
+ "./assets/ts/artplayer-plugin-ambilight.d.ts",
+ "./assets/ts/artplayer-plugin-asr.d.ts",
+ "./assets/ts/artplayer-plugin-audio-track.d.ts",
+ "./assets/ts/artplayer-plugin-auto-thumbnail.d.ts",
+ "./assets/ts/artplayer-plugin-chapter.d.ts",
+ "./assets/ts/artplayer-plugin-chromecast.d.ts",
+ "./assets/ts/artplayer-plugin-danmuku-mask.d.ts",
+ "./assets/ts/artplayer-plugin-danmuku.d.ts",
+ "./assets/ts/artplayer-plugin-dash-control.d.ts",
+ "./assets/ts/artplayer-plugin-document-pip.d.ts",
+ "./assets/ts/artplayer-plugin-hls-control.d.ts",
+ "./assets/ts/artplayer-plugin-jassub.d.ts",
+ "./assets/ts/artplayer-plugin-multiple-subtitles.d.ts",
+ "./assets/ts/artplayer-plugin-vast.d.ts",
+ "./assets/ts/artplayer-plugin-vtt-thumbnail.d.ts",
+ "./assets/ts/artplayer-proxy-canvas.d.ts",
+ "./assets/ts/artplayer-proxy-mediabunny.d.ts",
+ "./assets/ts/artplayer-tool-iframe.d.ts",
+ "./assets/ts/artplayer-tool-thumbnail.d.ts",
+ "./assets/ts/artplayer.d.ts",
+ "./assets/ts/artplayer-i18n.d.ts"
+ ];
+
+ // packages/artplayer-vitepress/browser/editor-monaco.ts
+ async function createMonaco(host, container, typescript, signal) {
+ host.require.config({ paths: { vs: "./assets/js/vs" } });
+ await new Promise(
+ (resolve, reject) => host.require(["vs/editor/editor.main"], resolve, reject)
+ );
+ await new Promise(
+ (resolve, reject) => host.require(
[
- 'var art = new Artplayer({',
- '\tcontainer: \'.artplayer-app\',',
- '\turl: \'/assets/sample/video.mp4\',',
- '});',
- ].join('\n'),
- $ts.checked ? 'typescript' : 'javascript',
- ),
- })
- })
-
- async function loadLib(libs) {
- $lib.value = decodeURIComponent(libs || '')
- return window.ArtplayerDocsLoader.loadLibraries(libs)
- }
-
- async function runExample(name, current) {
- const text = await window.ArtplayerDocsLoader.exampleSource(name)
- if (current !== initialization)
- return
- editor.setValue(text)
- runCode()
- }
-
- function loadCode(code, example, current) {
- if (current !== initialization)
- return
- if (example)
- return runExample(example, current)
- if (code) {
- editor.setValue(decodeURIComponent(code).trim())
- runCode()
- }
- else {
- return runExample('index', current)
- }
- }
-
- function runCode() {
- window.dispatchEvent(new Event('artplayer:example:cleanup'))
- // Destroy all existing instances
- Artplayer.instances.slice().forEach(art => art.destroy(true))
-
- const value = editor.getValue()
- // eslint-disable-next-line no-eval -- Preserve the editor's classic-script Run scope for user-provided examples.
- eval(value)
- window.art = Artplayer.instances[0]
- }
-
- function initApp() {
- const current = ++initialization
- const urlParams = window.ArtplayerDocsLoader.parameters(window.location.href)
- const { code, libs, example } = urlParams
-
- loadLib(libs)
- .then(() => loadCode(code, example, current))
- .catch((err) => {
- console.error('Failed to initialize app:', err)
- })
- }
-
- function restart() {
- const libs = encodeURIComponent($lib.value)
- const code = encodeURIComponent(editor.getValue())
- const url = `${window.location.origin}${window.location.pathname}?libs=${libs}&code=${code}`
- history.pushState(null, null, url)
- initApp()
- }
-
- function readFile(file) {
- return new Promise((resolve) => {
- const reader = new FileReader()
- reader.onload = () => resolve(reader.result)
- reader.readAsText(file)
- })
- }
-
- $run.addEventListener('click', () => {
- restart()
- })
-
- $popups.addEventListener('click', function (event) {
- if (event.target === this) {
- this.style.display = 'none'
- }
- })
-
- $prod.addEventListener('change', () => {
- setStorageBoolean('prod', $prod.checked)
- window.location.reload()
- })
-
- $ts.addEventListener('change', () => {
- setStorageBoolean('ts', $ts.checked)
- window.location.reload()
- })
-
- $code.addEventListener('change', () => {
- setStorageBoolean('code', $code.checked)
- window.location.reload()
- })
-
- $log.addEventListener('change', () => {
- setStorageBoolean('log', $log.checked)
- window.location.reload()
- })
-
- $file.addEventListener('change', async () => {
- for (let index = 0; index < $file.files.length; index++) {
- const file = $file.files[index]
- const name = file.name.toLowerCase().trim()
- if (name.endsWith('.css')) {
- const text = await readFile(file)
- const $style = document.createElement('style')
- $style.textContent = text
- document.body.appendChild($style)
- $lib.value = `\n[${name}]`
- $lib.value = $lib.value.trim()
+ "vs/basic-languages/typescript/typescript",
+ "vs/basic-languages/javascript/javascript",
+ "vs/language/typescript/tsMode"
+ ],
+ resolve,
+ reject
+ )
+ );
+ if (signal.aborted)
+ throw new Error("Editor initialization cancelled");
+ const api = host.monaco;
+ const types = api.languages.typescript;
+ const resources = [];
+ const temporary = /* @__PURE__ */ new Set();
+ let editor;
+ let disposed = false;
+ const dispose = () => {
+ if (disposed)
+ return;
+ disposed = true;
+ signal.removeEventListener("abort", dispose);
+ editor?.dispose();
+ editor = void 0;
+ for (const model of temporary) model.dispose();
+ temporary.clear();
+ for (const resource of resources.splice(0).reverse()) resource.dispose();
+ };
+ signal.addEventListener("abort", dispose, { once: true });
+ try {
+ for (const defaults of [
+ types.javascriptDefaults,
+ types.typescriptDefaults
+ ]) {
+ defaults.setDiagnosticsOptions({
+ noSemanticValidation: defaults === types.javascriptDefaults,
+ noSyntaxValidation: false
+ });
+ defaults.setCompilerOptions({
+ target: types.ScriptTarget.ES2015,
+ module: types.ModuleKind.None,
+ allowNonTsExtensions: true,
+ noEmit: false
+ });
}
- if (name.endsWith('.js')) {
- const text = await readFile(file)
- const $script = document.createElement('script')
- $script.textContent = text
- document.body.appendChild($script)
- $lib.value = `\n[${name}]`
- $lib.value = $lib.value.trim()
+ for (const uri of libUris) {
+ const response = await host.fetch(uri, { signal });
+ if (!response.ok) {
+ throw new Error(
+ `Loading editor declaration failed: ${uri} (${response.status})`
+ );
+ }
+ const source = await response.text();
+ if (signal.aborted)
+ throw new Error("Editor initialization cancelled");
+ resources.push(
+ types.javascriptDefaults.addExtraLib(source, uri),
+ types.typescriptDefaults.addExtraLib(source, uri)
+ );
+ resources.push(
+ api.editor.createModel(source, "typescript", api.Uri.parse(uri))
+ );
}
+ const model = api.editor.createModel(
+ "var art = new Artplayer({\n container: '.artplayer-app',\n url: '/assets/sample/video.mp4',\n});",
+ typescript ? "typescript" : "javascript"
+ );
+ resources.push(model);
+ editor = api.editor.create(container, {
+ theme: "vs-dark",
+ folding: true,
+ automaticLayout: true,
+ quickSuggestions: { other: true, comments: true, strings: true },
+ model
+ });
+ return {
+ editor,
+ async compile(source) {
+ if (disposed)
+ throw new Error("Editor was disposed");
+ if (!typescript)
+ return source;
+ const model2 = api.editor.createModel(source, "typescript");
+ temporary.add(model2);
+ try {
+ const worker = await (await types.getTypeScriptWorker())(model2.uri);
+ const file = model2.uri.toString();
+ const syntax = await worker.getSyntacticDiagnostics(file);
+ if (syntax.length) {
+ throw new Error(
+ `TypeScript syntax error: ${syntax.map((item) => typeof item.messageText === "string" ? item.messageText : item.messageText.messageText).join("; ")}`
+ );
+ }
+ const output = await worker.getEmitOutput(file);
+ const script = output.outputFiles.find(
+ (file2) => file2.name.endsWith(".js")
+ );
+ if (output.emitSkipped || !script)
+ throw new Error("TypeScript worker did not emit JavaScript");
+ return script.text;
+ } finally {
+ temporary.delete(model2);
+ if (!model2.isDisposed())
+ model2.dispose();
+ }
+ },
+ dispose
+ };
+ } catch (error) {
+ dispose();
+ throw error;
}
- })
+ }
- window.addEventListener('error', (err) => {
- console.error(err)
- })
-
- window.addEventListener('unhandledrejection', (err) => {
- console.error(err)
- })
-
- document.addEventListener('keydown', (event) => {
- if ((event.ctrlKey || event.metaKey) && event.key === 's') {
- event.preventDefault()
- restart()
+ // packages/artplayer-vitepress/browser/editor-preferences.ts
+ function readPreference(key, storage = () => localStorage) {
+ try {
+ return storage().getItem(key) === "true";
+ } catch {
+ return false;
}
- })
-})()
+ }
+ function writePreference(key, value, storage = () => localStorage) {
+ try {
+ storage().setItem(key, value ? "true" : "false");
+ return true;
+ } catch {
+ return false;
+ }
+ }
+
+ // packages/artplayer-vitepress/browser/editor-session.ts
+ function createEditorSession(options) {
+ let generation = 0;
+ let disposed = false;
+ return {
+ async run({
+ libs = "",
+ code,
+ example
+ }) {
+ const current = ++generation;
+ const active = () => !disposed && current === generation;
+ if (!active())
+ return;
+ await options.loadLibraries(libs);
+ if (!active())
+ return;
+ const source = example ? await options.exampleSource(example) : code ? decodeURIComponent(code).trim() : await options.exampleSource("index");
+ if (!active())
+ return;
+ options.writeCode(source);
+ const executable = await options.compile(source);
+ if (active())
+ options.execute(executable);
+ },
+ dispose() {
+ disposed = true;
+ generation++;
+ }
+ };
+ }
+ function executeClassic(code) {
+ new Function(code)();
+ }
+
+ // packages/artplayer-vitepress/browser/editor.ts
+ async function installEditor(host) {
+ const document = host.document;
+ const element = (selector) => {
+ const node = document.querySelector(selector);
+ if (!node)
+ throw new Error(`Missing editor element: ${selector}`);
+ return node;
+ };
+ host.Artplayer.DEBUG = true;
+ if (host.Artplayer.utils.isMobile) {
+ host.location.href = `./mobile.html${host.location.search}`;
+ return;
+ }
+ const run = element(".run");
+ const libs = element(".libsInput");
+ const file = element("#file");
+ const popup = element(".popups");
+ const output = element(".console");
+ host.consoleLog(output);
+ const controller = new AbortController();
+ const cleanups = [];
+ const listen = (target, type, callback) => {
+ target.addEventListener(type, callback);
+ cleanups.push(() => target.removeEventListener(type, callback));
+ };
+ const report = (error) => {
+ if (!controller.signal.aborted)
+ host.console.error(error);
+ };
+ listen(host, "error", (event) => host.console.error(event));
+ listen(host, "unhandledrejection", (event) => host.console.error(event));
+ for (const key of ["prod", "ts", "code", "log"]) {
+ const checkbox = element(`#${key}`);
+ checkbox.checked = readPreference(key, () => host.localStorage);
+ listen(checkbox, "change", () => {
+ if (writePreference(key, checkbox.checked, () => host.localStorage)) {
+ host.location.reload();
+ } else {
+ checkbox.checked = readPreference(key, () => host.localStorage);
+ report(
+ new Error("Browser storage is unavailable; preference was not saved")
+ );
+ }
+ });
+ }
+ if (element("#code").checked)
+ element("#editor").style.display = "none";
+ if (element("#log").checked)
+ output.style.display = "none";
+ const imports = createFileImports(document, (name) => {
+ libs.value = `[${name}]`;
+ });
+ cleanups.push(() => imports.dispose());
+ cleanups.push(() => {
+ host.dispatchEvent(new Event("artplayer:example:cleanup"));
+ for (const art of host.Artplayer.instances.slice()) {
+ try {
+ art.destroy(true);
+ } catch (error) {
+ host.console.error(error);
+ }
+ }
+ host.art = void 0;
+ });
+ listen(file, "change", () => {
+ void imports.add(Array.from(file.files || [])).catch(report);
+ });
+ listen(popup, "click", (event) => {
+ if (event.target === popup)
+ popup.style.display = "none";
+ });
+ const onPageHide = (event) => {
+ if (event.persisted)
+ return;
+ controller.abort();
+ for (const cleanup of cleanups.reverse()) {
+ try {
+ cleanup();
+ } catch (error) {
+ host.console.error(error);
+ }
+ }
+ };
+ host.addEventListener("pagehide", onPageHide);
+ cleanups.push(() => host.removeEventListener("pagehide", onPageHide));
+ run.setAttribute("aria-disabled", "true");
+ try {
+ let restart2 = function() {
+ const code = encodeURIComponent(monaco.editor.getValue());
+ const dependencies = encodeURIComponent(libs.value);
+ host.history.pushState(
+ null,
+ "",
+ `${host.location.origin}${host.location.pathname}?libs=${dependencies}&code=${code}`
+ );
+ void session.run({ libs: dependencies, code }).catch(report);
+ };
+ var restart = restart2;
+ const monaco = await createMonaco(
+ host,
+ element(".codeMirrorWrap"),
+ element("#ts").checked,
+ controller.signal
+ );
+ const session = createEditorSession({
+ loadLibraries: (encoded) => {
+ libs.value = decodeURIComponent(encoded);
+ return host.ArtplayerDocsLoader.loadLibraries(encoded);
+ },
+ exampleSource: (name) => host.ArtplayerDocsLoader.exampleSource(name),
+ writeCode: (code) => monaco.editor.setValue(code),
+ compile: monaco.compile,
+ execute: (code) => {
+ host.dispatchEvent(new Event("artplayer:example:cleanup"));
+ for (const art of host.Artplayer.instances.slice()) art.destroy(true);
+ executeClassic(code);
+ host.art = host.Artplayer.instances[0];
+ }
+ });
+ cleanups.push(() => session.dispose(), monaco.dispose);
+ listen(run, "click", restart2);
+ listen(document, "keydown", (event) => {
+ const key = event;
+ if ((key.ctrlKey || key.metaKey) && key.key === "s") {
+ key.preventDefault();
+ restart2();
+ }
+ });
+ run.removeAttribute("aria-disabled");
+ await session.run(host.ArtplayerDocsLoader.parameters(host.location.href));
+ } catch (error) {
+ report(error);
+ }
+ }
+ void installEditor(window).catch(
+ (error) => console.error(error)
+ );
+})();
diff --git a/docs/index.html b/docs/index.html
index 84f291249..534cce34c 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -230,29 +230,7 @@
-
+