From c23520a4a6145fbe11156539f1fc05d1d76ea5c9 Mon Sep 17 00:00:00 2001 From: Harvey Zack Date: Sat, 16 Aug 2025 15:55:21 +0800 Subject: [PATCH] feat: add Picture-in-Picture plugin for ArtPlayer - Introduced `artplayer-plugin-pip` to enable Picture-in-Picture functionality. - Updated `artplayer.d.ts` to export new types related to the plugin. - Added example usage of the plugin in `pip.js`. - Created TypeScript definitions for the plugin in `artplayer-plugin-pip.d.ts`. - Implemented the plugin logic in `index.js` with necessary styles and event handling. - Updated documentation to include the new plugin and its usage. - Added README and package.json for the new plugin. --- docs/assets/example/pip.js | 18 + docs/assets/js/common.js | 562 +++++++++--------- docs/assets/ts/artplayer-plugin-pip.d.ts | 16 + docs/assets/ts/artplayer.d.ts | 2 + docs/index.html | 3 + docs/uncompiled/artplayer-plugin-pip/index.js | 321 ++++++++++ .../artplayer-plugin-pip/index.js.map | 1 + packages/artplayer-plugin-pip/README.md | 11 + packages/artplayer-plugin-pip/package.json | 24 + packages/artplayer-plugin-pip/src/index.js | 156 +++++ packages/artplayer-plugin-pip/src/style.less | 3 + .../types/artplayer-plugin-pip.d.ts | 15 + 12 files changed, 853 insertions(+), 279 deletions(-) create mode 100644 docs/assets/example/pip.js create mode 100644 docs/assets/ts/artplayer-plugin-pip.d.ts create mode 100644 docs/uncompiled/artplayer-plugin-pip/index.js create mode 100644 docs/uncompiled/artplayer-plugin-pip/index.js.map create mode 100644 packages/artplayer-plugin-pip/README.md create mode 100644 packages/artplayer-plugin-pip/package.json create mode 100644 packages/artplayer-plugin-pip/src/index.js create mode 100644 packages/artplayer-plugin-pip/src/style.less create mode 100644 packages/artplayer-plugin-pip/types/artplayer-plugin-pip.d.ts diff --git a/docs/assets/example/pip.js b/docs/assets/example/pip.js new file mode 100644 index 000000000..56705b221 --- /dev/null +++ b/docs/assets/example/pip.js @@ -0,0 +1,18 @@ +// npm i artplayer-plugin-pip +// import artplayerPluginPip from 'artplayer-plugin-pip'; + +let art = new Artplayer({ + container: '.artplayer-app', + url: '/assets/sample/video.mp4', + plugins: [ + artplayerPluginPip({ + width: 480, + height: 270, + placeholder: `Playing in Document Picture-in-Picture`, + }), + ], +}) + +art.on('document-pip', (state) => { + console.log('Document Picture-in-Picture', state) +}) diff --git a/docs/assets/js/common.js b/docs/assets/js/common.js index f175583f3..fb4eeeb84 100644 --- a/docs/assets/js/common.js +++ b/docs/assets/js/common.js @@ -1,305 +1,309 @@ (function () { - Artplayer.DEBUG = true; + Artplayer.DEBUG = true - if (Artplayer.utils.isMobile) { - window.location.href = './mobile.html' + location.search; + if (Artplayer.utils.isMobile) { + window.location.href = `./mobile.html${location.search}` + } + + let $codeMirror = document.querySelector('.codeMirrorWrap') + let $lib = document.querySelector('.libsInput') + let $run = document.querySelector('.run') + let $popups = document.querySelector('.popups') + let $console = document.querySelector('.console') + let $prod = document.querySelector('#prod') + let $ts = document.querySelector('#ts') + let $code = document.querySelector('#code') + let $log = document.querySelector('#log') + let $file = document.querySelector('#file') + let $editor = document.querySelector('#editor') + + window.consoleLog($console) + + $prod.checked = localStorage.getItem('prod') === 'true' + $ts.checked = localStorage.getItem('ts') === 'true' + $code.checked = localStorage.getItem('code') === 'true' + $log.checked = localStorage.getItem('log') === 'true' + + if ($code.checked) { + $editor.style.display = 'none' + } + + if ($log.checked) { + $console.style.display = 'none' + } + + let editor = null + let loadedLibs = [] + 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, + }) + + let libUris = [ + './assets/ts/artplayer-plugin-ads.d.ts', + './assets/ts/artplayer-plugin-asr.d.ts', + './assets/ts/artplayer-plugin-ambilight.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-hls-control.d.ts', + './assets/ts/artplayer-plugin-iframe.d.ts', + './assets/ts/artplayer-plugin-libass.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-plugin-pip.d.ts', + './assets/ts/artplayer.d.ts', + ] + + for (let index = 0; index < libUris.length; index++) { + let libUri = libUris[index] + let libSource = await (await fetch(libUri)).text() + monaco.languages.typescript.javascriptDefaults.addExtraLib(libSource, libUri) + monaco.editor.createModel(libSource, 'typescript', monaco.Uri.parse(libUri)) } - var $codeMirror = document.querySelector('.codeMirrorWrap'); - var $lib = document.querySelector('.libsInput'); - var $run = document.querySelector('.run'); - var $popups = document.querySelector('.popups'); - var $console = document.querySelector('.console'); - var $prod = document.querySelector('#prod'); - var $ts = document.querySelector('#ts'); - var $code = document.querySelector('#code'); - var $log = document.querySelector('#log'); - var $file = document.querySelector('#file'); - var $editor = document.querySelector('#editor'); + var disposable = monaco.editor.onDidCreateEditor(() => { + disposable.dispose() + setTimeout(initApp, 1000) + }) - window['consoleLog']($console); + editor = monaco.editor.create($codeMirror, { + theme: 'vs-dark', + folding: true, + automaticLayout: true, + quickSuggestions: { + other: true, + comments: true, + strings: true, + }, + model: monaco.editor.createModel( + [ + 'var art = new Artplayer({', + '\tcontainer: \'.artplayer-app\',', + '\turl: \'/assets/sample/video.mp4\',', + '});', + ].join('\n'), + $ts.checked ? 'typescript' : 'javascript', + ), + }) + }) - $prod.checked = localStorage.getItem('prod') === 'true'; - $ts.checked = localStorage.getItem('ts') === 'true'; - $code.checked = localStorage.getItem('code') === 'true'; - $log.checked = localStorage.getItem('log') === 'true'; + function getURLParameters(url) { + return (url.match(/([^?=&]+)(=([^&]*))/g) || []).reduce((a, v) => { + return (a[v.slice(0, v.indexOf('='))] = v.slice(v.indexOf('=') + 1)), a + }, {}) + } - if ($code.checked) { - $editor.style.display = 'none'; + function getExt(url) { + if (url.includes('?')) { + return getExt(url.split('?')[0]) } - if ($log.checked) { - $console.style.display = 'none'; + if (url.includes('#')) { + return getExt(url.split('#')[0]) } - var editor = null; - var loadedLibs = []; - require.config({ paths: { vs: './assets/js/vs' } }); - require(['vs/editor/editor.main'], async function () { - monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({ - noSemanticValidation: true, - noSyntaxValidation: false, - }); + return url.trim().toLowerCase().split('.').pop() + } - monaco.languages.typescript.javascriptDefaults.setCompilerOptions({ - target: monaco.languages.typescript.ScriptTarget.ES6, - allowNonTsExtensions: true, - }); + function loadScript(url) { + return new Promise((resolve, reject) => { + let define2 = window.define + window.define = undefined + let script = document.createElement('script') + script.type = 'text/javascript' + script.src = url + script.onload = function () { + window.define = define2 + resolve(url) + } + script.onerror = function () { + reject(new Error(`Loading script failed:${url}`)) + } + document.querySelector('head').appendChild(script) + }) + } - var libUris = [ - './assets/ts/artplayer-plugin-ads.d.ts', - './assets/ts/artplayer-plugin-asr.d.ts', - './assets/ts/artplayer-plugin-ambilight.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-hls-control.d.ts', - './assets/ts/artplayer-plugin-iframe.d.ts', - './assets/ts/artplayer-plugin-libass.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.d.ts', - ]; - - for (let index = 0; index < libUris.length; index++) { - var libUri = libUris[index]; - var libSource = await (await fetch(libUri)).text(); - monaco.languages.typescript.javascriptDefaults.addExtraLib(libSource, libUri); - monaco.editor.createModel(libSource, 'typescript', monaco.Uri.parse(libUri)); + function loadStyle(url) { + return new Promise((resolve, reject) => { + let link = document.createElement('link') + link.rel = 'stylesheet' + link.href = url + link.onload = function () { + resolve(url) + } + link.onerror = function () { + reject(new Error(`Loading style failed:${url}`)) + } + document.querySelector('head').appendChild(link) + }) + } + + function loadLib(libs) { + let libPromise = [] + let libsDecode = decodeURIComponent(libs || '') + libsDecode + .split(/\r?\n/) + .filter((url) => { + return !loadedLibs.includes(url) + }) + .forEach((url) => { + let ext = getExt(url) + if (ext === 'js') { + libPromise.push(loadScript(url)) } - - var disposable = monaco.editor.onDidCreateEditor(function () { - disposable.dispose(); - setTimeout(initApp, 1000); - }); - - editor = monaco.editor.create($codeMirror, { - theme: 'vs-dark', - folding: true, - automaticLayout: true, - quickSuggestions: { - other: true, - comments: true, - strings: true, - }, - model: monaco.editor.createModel( - [ - 'var art = new Artplayer({', - "\tcontainer: '.artplayer-app',", - "\turl: '/assets/sample/video.mp4',", - '});', - ].join('\n'), - $ts.checked ? 'typescript' : 'javascript', - ), - }); - }); - - function getURLParameters(url) { - return (url.match(/([^?=&]+)(=([^&]*))/g) || []).reduce(function (a, v) { - return (a[v.slice(0, v.indexOf('='))] = v.slice(v.indexOf('=') + 1)), a; - }, {}); - } - - function getExt(url) { - if (url.includes('?')) { - return getExt(url.split('?')[0]); + else if (ext === 'css') { + libPromise.push(loadStyle(url)) } + }) + $lib.value = libsDecode + return Promise.all(libPromise) + } - if (url.includes('#')) { - return getExt(url.split('#')[0]); - } + function runExample(name) { + fetch(`./assets/example/${name}.js`) + .then((response) => { + return response.text() + }) + .then((text) => { + editor.setValue(text) + runCode() + }) + .catch((err) => { + console.error(err) + }) + } - return url.trim().toLowerCase().split('.').pop(); + function loadCode(code, example) { + if (example) { + runExample(example) } - - function loadScript(url) { - return new Promise(function (resolve, reject) { - var define2 = window.define; - window.define = undefined; - var script = document.createElement('script'); - script.type = 'text/javascript'; - script.src = url; - script.onload = function () { - window.define = define2; - resolve(url); - }; - script.onerror = function () { - reject(new Error('Loading script failed:' + url)); - }; - document.querySelector('head').appendChild(script); - }); + else if (code) { + editor.setValue(decodeURIComponent(code).trim()) + runCode() } - - function loadStyle(url) { - return new Promise(function (resolve, reject) { - var link = document.createElement('link'); - link.rel = 'stylesheet'; - link.href = url; - link.onload = function () { - resolve(url); - }; - link.onerror = function () { - reject(new Error('Loading style failed:' + url)); - }; - document.querySelector('head').appendChild(link); - }); + else { + runExample('index') } + } - function loadLib(libs) { - var libPromise = []; - var libsDecode = decodeURIComponent(libs || ''); - libsDecode - .split(/\r?\n/) - .filter(function (url) { - return !loadedLibs.includes(url); - }) - .forEach(function (url) { - var ext = getExt(url); - if (ext === 'js') { - libPromise.push(loadScript(url)); - } else if (ext === 'css') { - libPromise.push(loadStyle(url)); - } - }); - $lib.value = libsDecode; - return Promise.all(libPromise); + function runCode() { + [...Artplayer.instances].forEach((art) => { + art.destroy(true) + }) + const value = editor.getValue() + eval(value) + window.art = Artplayer.instances[0] + } + + function initApp() { + let _getURLParameters = getURLParameters(window.location.href) + let code = _getURLParameters.code + let libs = _getURLParameters.libs + example = _getURLParameters.example + + loadLib(libs) + .then((result) => { + loadedLibs = loadedLibs.concat(result) + loadCode(code, example) + }) + .catch((err) => { + console.error(err) + }) + } + + function restart() { + let libs = encodeURIComponent($lib.value) + let code = encodeURIComponent(editor.getValue()) + let 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' } + }) - function runExample(name) { - fetch('./assets/example/' + name + '.js') - .then(function (response) { - return response.text(); - }) - .then(function (text) { - editor.setValue(text); - runCode(); - }) - .catch(function (err) { - console.error(err); - }); + $prod.addEventListener('change', () => { + localStorage.setItem('prod', $prod.checked ? 'true' : 'false') + window.location.reload() + }) + + $ts.addEventListener('change', () => { + localStorage.setItem('ts', $ts.checked ? 'true' : 'false') + window.location.reload() + }) + + $code.addEventListener('change', () => { + localStorage.setItem('code', $code.checked ? 'true' : 'false') + window.location.reload() + }) + + $log.addEventListener('change', () => { + localStorage.setItem('log', $log.checked ? 'true' : 'false') + 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() + } + 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() + } } + }) - function loadCode(code, example) { - if (example) { - runExample(example); - } else if (code) { - editor.setValue(decodeURIComponent(code).trim()); - runCode(); - } else { - runExample('index'); - } + 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() } - - function runCode() { - [...Artplayer.instances].forEach(function (art) { - art.destroy(true); - }); - const value = editor.getValue(); - eval(value); - window.art = Artplayer.instances[0]; - } - - function initApp() { - var _getURLParameters = getURLParameters(window.location.href), - code = _getURLParameters.code, - libs = _getURLParameters.libs; - example = _getURLParameters.example; - - loadLib(libs) - .then(function (result) { - loadedLibs = loadedLibs.concat(result); - loadCode(code, example); - }) - .catch(function (err) { - console.error(err); - }); - } - - function restart() { - var libs = encodeURIComponent($lib.value); - var code = encodeURIComponent(editor.getValue()); - var 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', function () { - restart(); - }); - - $popups.addEventListener('click', function (event) { - if (event.target === this) { - this.style.display = 'none'; - } - }); - - $prod.addEventListener('change', function () { - localStorage.setItem('prod', $prod.checked ? 'true' : 'false'); - window.location.reload(); - }); - - $ts.addEventListener('change', function () { - localStorage.setItem('ts', $ts.checked ? 'true' : 'false'); - window.location.reload(); - }); - - $code.addEventListener('change', function () { - localStorage.setItem('code', $code.checked ? 'true' : 'false'); - window.location.reload(); - }); - - $log.addEventListener('change', function () { - localStorage.setItem('log', $log.checked ? 'true' : 'false'); - window.location.reload(); - }); - - $file.addEventListener('change', async function () { - 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(); - } - 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(); - } - } - }); - - window.addEventListener('error', function (err) { - console.error(err); - }); - - window.addEventListener('unhandledrejection', function (err) { - console.error(err); - }); - - document.addEventListener('keydown', function (event) { - if ((event.ctrlKey || event.metaKey) && event.key === 's') { - event.preventDefault(); - restart(); - } - }); -})(); \ No newline at end of file + }) +})() diff --git a/docs/assets/ts/artplayer-plugin-pip.d.ts b/docs/assets/ts/artplayer-plugin-pip.d.ts new file mode 100644 index 000000000..50bafc3cf --- /dev/null +++ b/docs/assets/ts/artplayer-plugin-pip.d.ts @@ -0,0 +1,16 @@ +interface Option { + width?: number + height?: number + placeholder?: string +} + +interface Result { + name: 'artplayerPluginPip' +} + +declare const artplayerPluginPip: (option: Option) => (art: Artplayer) => Result + +export default artplayerPluginPip + +export = artplayerPluginPip; +export as namespace artplayerPluginPip; \ No newline at end of file diff --git a/docs/assets/ts/artplayer.d.ts b/docs/assets/ts/artplayer.d.ts index 11588d07b..b4db11be2 100644 --- a/docs/assets/ts/artplayer.d.ts +++ b/docs/assets/ts/artplayer.d.ts @@ -1054,6 +1054,8 @@ export interface ComponentOption { +export type { Config, Events, I18n, Icons, Option, Player, Setting, SettingOption, Subtitle, Template, Utils } + export default class Artplayer extends Player { constructor(option: Option, readyCallback?: (this: Artplayer, art: Artplayer) => unknown) diff --git a/docs/index.html b/docs/index.html index c03f7c9c6..814df1206 100644 --- a/docs/index.html +++ b/docs/index.html @@ -111,6 +111,9 @@ artplayer-plugin-asr +
  • + artplayer-plugin-pip +