Files

116 lines
3.8 KiB
TypeScript

import type Artplayer from 'artplayer'
import type { AsyncResult, Option } from '../types/artplayer-plugin-document-pip'
import type { WindowApi } from './window-session'
import { createControl } from './control'
import { createProjection } from './projection'
import { releaseAll } from './resources'
import { installStyle } from './styles'
import { createWindowSession } from './window-session'
function artplayerPluginDocumentPip(userOptions: Option = {}) {
const options = { width: 480, height: 270, fallbackToVideoPiP: true, placeholder: 'Playing in Document Picture-in-Picture', ...userOptions }
return (art: Artplayer) => {
const browser = window as Window & { documentPictureInPicture?: WindowApi }
const isSupported = 'documentPictureInPicture' in browser && typeof browser.documentPictureInPicture?.requestWindow === 'function'
let host: (Artplayer & { resize?: () => void }) | null = art
let player: HTMLElement | null = art.template.$player
let closed = art.isDestroy
const utils = (art.constructor as typeof Artplayer).utils
const alive = () => !closed && !!host && !host.isDestroy
const session = createWindowSession({
alive,
request: () => browser.documentPictureInPicture!.requestWindow({ width: options.width, height: options.height }),
project: document => createProjection(player!, document, options.placeholder),
activate(active) {
if (!host || !player)
return
if (active)
utils.addClass(player, 'artplayer-document-pip')
else
utils.removeClass(player, 'artplayer-document-pip')
if (!alive())
return
host.events.bindGlobalEvents?.()
if (alive())
host.emit('document-pip', active)
},
resize: () => {
if (alive())
host!.emit('resize')
},
nativeResize: () => {
if (alive())
host!.resize?.()
},
report(action, error) {
if (!alive())
return
host!.notice.show = `Document Picture-in-Picture ${action} failed`
console.warn(`[artplayer-plugin-document-pip] ${action} failed:`, error)
},
})
async function open() {
if (!alive())
return
if (!isSupported && options.fallbackToVideoPiP) {
host!.pip = true
console.warn('[artplayer-plugin-document-pip] Document Picture-in-Picture is not supported, falling back to Video Picture-in-Picture')
return
}
return session.open()
}
function toggle() {
if (!alive())
return
if (session.active || session.opening)
void session.close()
else
void open()
}
let control: ReturnType<typeof createControl> | null = createControl(art, toggle, alive)
function destroy() {
if (closed)
return
closed = true
const current = host
const previousControl = control
control = null
const errors = releaseAll([session.destroy, () => previousControl?.destroy(), () => current?.off('destroy', destroy)])
host = null
player = null
if (errors.length)
console.warn('[artplayer-plugin-document-pip] cleanup failed:', errors[0])
}
const result: AsyncResult = {
name: 'artplayerPluginDocumentPip' as const,
get isSupported() { return isSupported },
get isActive() { return session.active },
open,
close: session.close,
toggle,
}
if (closed) {
host = null
player = null
return result
}
try {
art.on('destroy', destroy)
if (alive())
control?.mount()
else
art.off('destroy', destroy)
}
catch (error) {
destroy()
throw error
}
return result
}
}
export default Object.assign(artplayerPluginDocumentPip, { default: artplayerPluginDocumentPip })
if (typeof document !== 'undefined')
installStyle(document)