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.
This commit is contained in:
Harvey Zack committed 2025-08-16 15:55:21 +08:00
1 parent bd071b2d63
commit c23520a4a6
12 files changed
+853 -279

No files matched your search

+18
View File
@@ -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)
})
+283 -279
View File
@@ -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();
}
});
})();
})
})()
+16
View File
@@ -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;
+2
View File
@@ -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)
+3
View File
@@ -111,6 +111,9 @@
<a
href="/?libs=./uncompiled/artplayer-plugin-asr/index.js&example=asr">artplayer-plugin-asr</a>
</li>
<li>
<a href="/?libs=./uncompiled/artplayer-plugin-pip/index.js&example=pip">artplayer-plugin-pip</a>
</li>
</ul>
<ul>
<li class="title">Proxys</li>
@@ -0,0 +1,321 @@
// modules are defined as an array
// [ module function, map of requires ]
//
// map of requires is short require name -> numeric require
//
// anything defined in a previous bundle is accessed via the
// orig method which is the require for previous bundles
(function (
modules,
entry,
mainEntry,
parcelRequireName,
externals,
distDir,
publicUrl,
devServer
) {
/* eslint-disable no-undef */
var globalObject =
typeof globalThis !== 'undefined'
? globalThis
: typeof self !== 'undefined'
? self
: typeof window !== 'undefined'
? window
: typeof global !== 'undefined'
? global
: {};
/* eslint-enable no-undef */
// Save the require from previous bundle to this closure if any
var previousRequire =
typeof globalObject[parcelRequireName] === 'function' &&
globalObject[parcelRequireName];
var importMap = previousRequire.i || {};
var cache = previousRequire.cache || {};
// Do not use `require` to prevent Webpack from trying to bundle this call
var nodeRequire =
typeof module !== 'undefined' &&
typeof module.require === 'function' &&
module.require.bind(module);
function newRequire(name, jumped) {
if (!cache[name]) {
if (!modules[name]) {
if (externals[name]) {
return externals[name];
}
// if we cannot find the module within our internal map or
// cache jump to the current global require ie. the last bundle
// that was added to the page.
var currentRequire =
typeof globalObject[parcelRequireName] === 'function' &&
globalObject[parcelRequireName];
if (!jumped && currentRequire) {
return currentRequire(name, true);
}
// If there are other bundles on this page the require from the
// previous one is saved to 'previousRequire'. Repeat this as
// many times as there are bundles until the module is found or
// we exhaust the require chain.
if (previousRequire) {
return previousRequire(name, true);
}
// Try the node require function if it exists.
if (nodeRequire && typeof name === 'string') {
return nodeRequire(name);
}
var err = new Error("Cannot find module '" + name + "'");
err.code = 'MODULE_NOT_FOUND';
throw err;
}
localRequire.resolve = resolve;
localRequire.cache = {};
var module = (cache[name] = new newRequire.Module(name));
modules[name][0].call(
module.exports,
localRequire,
module,
module.exports,
globalObject
);
}
return cache[name].exports;
function localRequire(x) {
var res = localRequire.resolve(x);
return res === false ? {} : newRequire(res);
}
function resolve(x) {
var id = modules[name][1][x];
return id != null ? id : x;
}
}
function Module(moduleName) {
this.id = moduleName;
this.bundle = newRequire;
this.require = nodeRequire;
this.exports = {};
}
newRequire.isParcelRequire = true;
newRequire.Module = Module;
newRequire.modules = modules;
newRequire.cache = cache;
newRequire.parent = previousRequire;
newRequire.distDir = distDir;
newRequire.publicUrl = publicUrl;
newRequire.devServer = devServer;
newRequire.i = importMap;
newRequire.register = function (id, exports) {
modules[id] = [
function (require, module) {
module.exports = exports;
},
{},
];
};
// Only insert newRequire.load when it is actually used.
// The code in this file is linted against ES5, so dynamic import is not allowed.
// INSERT_LOAD_HERE
Object.defineProperty(newRequire, 'root', {
get: function () {
return globalObject[parcelRequireName];
},
});
globalObject[parcelRequireName] = newRequire;
for (var i = 0; i < entry.length; i++) {
newRequire(entry[i]);
}
if (mainEntry) {
// Expose entry point to Node, AMD or browser globals
// Based on https://github.com/ForbesLindesay/umd/blob/master/template.js
var mainExports = newRequire(mainEntry);
// CommonJS
if (typeof exports === 'object' && typeof module !== 'undefined') {
module.exports = mainExports;
// RequireJS
} else if (typeof define === 'function' && define.amd) {
define(function () {
return mainExports;
});
}
}
})({"55Mmu":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>artplayerPluginPip);
function artplayerPluginPip(userOptions = {}) {
const options = {
width: 480,
height: 270,
placeholder: `Playing in Document Picture-in-Picture`,
...userOptions
};
return (art)=>{
if (!('documentPictureInPicture' in window)) {
art.notice.show = 'Document Picture-in-Picture is not supported';
return;
}
const { proxy, icons, i18n, template: { $container }, constructor: { utils: { append, tooltip } } } = art;
const state = {
win: null,
originalParent: null,
originalNext: null,
currentDoc: null,
placeholder: null
};
function copyStylesTo(targetDoc) {
const baseStyle = `
html, body { margin:0; padding:0; width:100%; height:100%; background:#000; overflow:hidden; }
#__art_dpip_root { position:absolute; inset:0; display:flex; }
#__art_dpip_root > * { width:100% !important; height:100% !important; }
`;
const $style = document.querySelector('#artplayer-style');
const clone = targetDoc.createElement('style');
clone.textContent = baseStyle + $style.textContent;
targetDoc.head.appendChild(clone);
const meta = targetDoc.createElement('meta');
meta.name = 'viewport';
meta.content = 'width=device-width,initial-scale=1';
targetDoc.head.appendChild(meta);
}
async function open() {
if (state.win) return;
try {
const pipWin = await window.documentPictureInPicture.requestWindow({
width: options.width,
height: options.height
});
state.win = pipWin;
state.currentDoc = pipWin.document;
state.placeholder = document.createElement('div');
state.placeholder.className = 'artplayer-pip-placeholder';
state.placeholder.style.cssText = `display:flex;justify-content:center;align-items:center;width:100%;height:100%;`;
state.placeholder.textContent = options.placeholder;
state.originalParent = $container.parentNode;
state.originalNext = $container.nextSibling;
state.originalParent.insertBefore(state.placeholder, state.originalNext);
const root = pipWin.document.createElement('div');
root.id = '__art_dpip_root';
pipWin.document.body.appendChild(root);
copyStylesTo(pipWin.document);
const adopted = pipWin.document.adoptNode($container);
root.appendChild(adopted);
state.currentDoc = pipWin.document;
const onResize = ()=>art.resize?.();
const onPageHide = ()=>close();
pipWin.addEventListener('resize', onResize);
pipWin.addEventListener('pagehide', onPageHide);
state.cleanup = ()=>{
pipWin.removeEventListener('resize', onResize);
pipWin.removeEventListener('pagehide', onPageHide);
};
art.emit('document-pip', true);
} catch (err) {
art.notice.show = 'Document Picture-in-Picture open failed';
console.warn('[artplayer-plugin-pip] open failed:', err);
}
}
function restoreToOriginalDocument() {
if (!state.placeholder) return;
const backDoc = state.placeholder.ownerDocument || document;
const adoptedBack = backDoc.adoptNode($container);
state.originalParent.insertBefore(adoptedBack, state.placeholder);
state.placeholder.remove();
state.placeholder = null;
state.currentDoc = backDoc;
}
function close() {
if (!state.win) return;
try {
state.cleanup?.();
restoreToOriginalDocument();
state.win.close();
state.win = null;
art.emit('document-pip', false);
} catch (err) {
art.notice.show = 'Document Picture-in-Picture close failed';
console.warn('[artplayer-plugin-pip] close failed:', err);
}
}
function toggle() {
if (state.win) close();
else open();
}
art.controls.add({
name: 'document-pip',
position: 'right',
index: 40,
tooltip: art.i18n.get('PIP Mode'),
mounted: ($control)=>{
append($control, icons.pip);
proxy($control, 'click', toggle);
art.on('document-pip', (value)=>{
tooltip($control, i18n.get(value ? 'Exit PIP Mode' : 'PIP Mode'));
});
}
});
art.on('destroy', close);
return {
name: 'artplayerPluginPip',
open,
close,
toggle
};
};
}
if (typeof window !== 'undefined') window.artplayerPluginPip = artplayerPluginPip;
},{"@parcel/transformer-js/src/esmodule-helpers.js":"8oCsH"}],"8oCsH":[function(require,module,exports,__globalThis) {
exports.interopDefault = function(a) {
return a && a.__esModule ? a : {
default: a
};
};
exports.defineInteropFlag = function(a) {
Object.defineProperty(a, '__esModule', {
value: true
});
};
exports.exportAll = function(source, dest) {
Object.keys(source).forEach(function(key) {
if (key === 'default' || key === '__esModule' || Object.prototype.hasOwnProperty.call(dest, key)) return;
Object.defineProperty(dest, key, {
enumerable: true,
get: function() {
return source[key];
}
});
});
return dest;
};
exports.export = function(dest, destName, get) {
Object.defineProperty(dest, destName, {
enumerable: true,
get: get
});
};
},{}]},["55Mmu"], "55Mmu", "parcelRequire4dc0", {})
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"mappings":"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","sources":["packages/artplayer-plugin-pip/src/index.js","node_modules/@parcel/transformer-js/src/esmodule-helpers.js"],"sourcesContent":["export default function artplayerPluginPip(userOptions = {}) {\n const options = {\n width: 480,\n height: 270,\n placeholder: `Playing in Document Picture-in-Picture`,\n ...userOptions,\n }\n\n return (art) => {\n if (!('documentPictureInPicture' in window)) {\n art.notice.show = 'Document Picture-in-Picture is not supported'\n return\n }\n\n const { proxy, icons, i18n, template: { $container }, constructor: { utils: { append, tooltip } } } = art\n\n const state = {\n win: null,\n originalParent: null,\n originalNext: null,\n currentDoc: null,\n placeholder: null,\n }\n\n function copyStylesTo(targetDoc) {\n const baseStyle = `\n html, body { margin:0; padding:0; width:100%; height:100%; background:#000; overflow:hidden; }\n #__art_dpip_root { position:absolute; inset:0; display:flex; }\n #__art_dpip_root > * { width:100% !important; height:100% !important; }\n `\n\n const $style = document.querySelector('#artplayer-style')\n const clone = targetDoc.createElement('style')\n clone.textContent = baseStyle + $style.textContent\n targetDoc.head.appendChild(clone)\n\n const meta = targetDoc.createElement('meta')\n meta.name = 'viewport'\n meta.content = 'width=device-width,initial-scale=1'\n targetDoc.head.appendChild(meta)\n }\n\n async function open() {\n if (state.win)\n return\n\n try {\n consLine truncated
+11
View File
@@ -0,0 +1,11 @@
# artplayer-plugin-pip
pip plugin for ArtPlayer
## Demo
[https://artplayer.org](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-pip/index.js&example=pip)
## License
MIT © Harvey Zack
@@ -0,0 +1,24 @@
{
"name": "artplayer-plugin-pip",
"version": "1.0.0",
"description": "pip plugin for ArtPlayer",
"author": "Harvey Zack <laozhaochaguan@gmail.com>",
"license": "MIT",
"homepage": "https://artplayer.org",
"repository": {
"type": "git",
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
},
"bugs": {
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
},
"keywords": [
"html5",
"video",
"player"
],
"main": "dist/artplayer-plugin-pip.js",
"types": "types/artplayer-plugin-pip.d.ts",
"legacy": "dist/artplayer-plugin-pip.legacy.js",
"browserslist": "last 1 Chrome version"
}
+156
View File
@@ -0,0 +1,156 @@
export default function artplayerPluginPip(userOptions = {}) {
const options = {
width: 480,
height: 270,
placeholder: `Playing in Document Picture-in-Picture`,
...userOptions,
}
return (art) => {
if (!('documentPictureInPicture' in window)) {
art.notice.show = 'Document Picture-in-Picture is not supported'
return
}
const { proxy, icons, i18n, template: { $container }, constructor: { utils: { append, tooltip } } } = art
const state = {
win: null,
originalParent: null,
originalNext: null,
currentDoc: null,
placeholder: null,
}
function copyStylesTo(targetDoc) {
const baseStyle = `
html, body { margin:0; padding:0; width:100%; height:100%; background:#000; overflow:hidden; }
#__art_dpip_root { position:absolute; inset:0; display:flex; }
#__art_dpip_root > * { width:100% !important; height:100% !important; }
`
const $style = document.querySelector('#artplayer-style')
const clone = targetDoc.createElement('style')
clone.textContent = baseStyle + $style.textContent
targetDoc.head.appendChild(clone)
const meta = targetDoc.createElement('meta')
meta.name = 'viewport'
meta.content = 'width=device-width,initial-scale=1'
targetDoc.head.appendChild(meta)
}
async function open() {
if (state.win)
return
try {
const pipWin = await window.documentPictureInPicture.requestWindow({
width: options.width,
height: options.height,
})
state.win = pipWin
state.currentDoc = pipWin.document
state.placeholder = document.createElement('div')
state.placeholder.className = 'artplayer-pip-placeholder'
state.placeholder.style.cssText = `display:flex;justify-content:center;align-items:center;width:100%;height:100%;`
state.placeholder.textContent = options.placeholder
state.originalParent = $container.parentNode
state.originalNext = $container.nextSibling
state.originalParent.insertBefore(state.placeholder, state.originalNext)
const root = pipWin.document.createElement('div')
root.id = '__art_dpip_root'
pipWin.document.body.appendChild(root)
copyStylesTo(pipWin.document)
const adopted = pipWin.document.adoptNode($container)
root.appendChild(adopted)
state.currentDoc = pipWin.document
const onResize = () => art.resize?.()
const onPageHide = () => close()
pipWin.addEventListener('resize', onResize)
pipWin.addEventListener('pagehide', onPageHide)
state.cleanup = () => {
pipWin.removeEventListener('resize', onResize)
pipWin.removeEventListener('pagehide', onPageHide)
}
art.emit('document-pip', true)
}
catch (err) {
art.notice.show = 'Document Picture-in-Picture open failed'
console.warn('[artplayer-plugin-pip] open failed:', err)
}
}
function restoreToOriginalDocument() {
if (!state.placeholder)
return
const backDoc = state.placeholder.ownerDocument || document
const adoptedBack = backDoc.adoptNode($container)
state.originalParent.insertBefore(adoptedBack, state.placeholder)
state.placeholder.remove()
state.placeholder = null
state.currentDoc = backDoc
}
function close() {
if (!state.win)
return
try {
state.cleanup?.()
restoreToOriginalDocument()
state.win.close()
state.win = null
art.emit('document-pip', false)
}
catch (err) {
art.notice.show = 'Document Picture-in-Picture close failed'
console.warn('[artplayer-plugin-pip] close failed:', err)
}
}
function toggle() {
if (state.win) {
close()
}
else {
open()
}
}
art.controls.add({
name: 'document-pip',
position: 'right',
index: 40,
tooltip: art.i18n.get('PIP Mode'),
mounted: ($control) => {
append($control, icons.pip)
proxy($control, 'click', toggle)
art.on('document-pip', (value) => {
tooltip($control, i18n.get(value ? 'Exit PIP Mode' : 'PIP Mode'))
})
},
})
art.on('destroy', close)
return {
name: 'artplayerPluginPip',
open,
close,
toggle,
}
}
}
if (typeof window !== 'undefined') {
window.artplayerPluginPip = artplayerPluginPip
}
@@ -0,0 +1,3 @@
.artplayer-plugin-pip {
//
}
@@ -0,0 +1,15 @@
import type Artplayer from 'artplayer'
interface Option {
width?: number
height?: number
placeholder?: string
}
interface Result {
name: 'artplayerPluginPip'
}
declare const artplayerPluginPip: (option: Option) => (art: Artplayer) => Result
export default artplayerPluginPip