mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-27 01:21:20 -08:00
refactor(core): CORE-12 type template resources and verify bundled notices
This commit is contained in:
1 parent
b8039ad1f1
commit
9eb7c945ca
101 files changed
+7363
-664
No files matched your search
@@ -1,3 +1,6 @@
|
||||
import type { Dictionary } from './publish'
|
||||
import { publishLanguage } from './publish'
|
||||
|
||||
const ar = {
|
||||
'Video Info': 'معلومات الفيديو',
|
||||
'Close': 'إغلاق',
|
||||
@@ -35,10 +38,8 @@ const ar = {
|
||||
'Jump Play': 'التشغيل القافز',
|
||||
'AirPlay': 'AirPlay',
|
||||
'AirPlay Not Available': 'AirPlay غير متاح',
|
||||
}
|
||||
} satisfies Dictionary
|
||||
|
||||
export default ar
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window['artplayer-i18n-ar'] = ar
|
||||
}
|
||||
publishLanguage('artplayer-i18n-ar', ar)
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { Dictionary } from './publish'
|
||||
import { publishLanguage } from './publish'
|
||||
|
||||
const cs = {
|
||||
'Video Info': 'Info o videu',
|
||||
'Close': 'Zavřít',
|
||||
@@ -35,10 +38,8 @@ const cs = {
|
||||
'Jump Play': 'Hra na skok',
|
||||
'AirPlay': 'AirPlay',
|
||||
'AirPlay Not Available': 'AirPlay není k dispozici',
|
||||
}
|
||||
} satisfies Dictionary
|
||||
|
||||
export default cs
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window['artplayer-i18n-cs'] = cs
|
||||
}
|
||||
publishLanguage('artplayer-i18n-cs', cs)
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { Dictionary } from './publish'
|
||||
import { publishLanguage } from './publish'
|
||||
|
||||
const es = {
|
||||
'Video Info': 'Información del video',
|
||||
'Close': 'Cerrar',
|
||||
@@ -35,10 +38,8 @@ const es = {
|
||||
'Jump Play': 'Saltar',
|
||||
'AirPlay': 'AirPlay',
|
||||
'AirPlay Not Available': 'AirPlay no disponible',
|
||||
}
|
||||
} satisfies Dictionary
|
||||
|
||||
export default es
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window['artplayer-i18n-es'] = es
|
||||
}
|
||||
publishLanguage('artplayer-i18n-es', es)
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { Dictionary } from './publish'
|
||||
import { publishLanguage } from './publish'
|
||||
|
||||
const fa = {
|
||||
'Video Info': 'اطلاعات ویدیو',
|
||||
'Close': 'بستن',
|
||||
@@ -35,10 +38,8 @@ const fa = {
|
||||
'Jump Play': 'جامپ پلی',
|
||||
'AirPlay': 'ایر پلی',
|
||||
'AirPlay Not Available': 'عدم پشتیبانی از ایرپلی',
|
||||
}
|
||||
} satisfies Dictionary
|
||||
|
||||
export default fa
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window['artplayer-i18n-fa'] = fa
|
||||
}
|
||||
publishLanguage('artplayer-i18n-fa', fa)
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { Dictionary } from './publish'
|
||||
import { publishLanguage } from './publish'
|
||||
|
||||
const fr = {
|
||||
'Video Info': 'Informations de la vidéo',
|
||||
'Close': 'Fermer',
|
||||
@@ -35,10 +38,8 @@ const fr = {
|
||||
'Jump Play': 'Continuer',
|
||||
'AirPlay': 'AirPlay',
|
||||
'AirPlay Not Available': 'AirPlay non disponible',
|
||||
}
|
||||
} satisfies Dictionary
|
||||
|
||||
export default fr
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window['artplayer-i18n-fr'] = fr
|
||||
}
|
||||
publishLanguage('artplayer-i18n-fr', fr)
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { Dictionary } from './publish'
|
||||
import { publishLanguage } from './publish'
|
||||
|
||||
const id = {
|
||||
'Video Info': 'Informasi Video',
|
||||
'Close': 'Tutup',
|
||||
@@ -35,10 +38,8 @@ const id = {
|
||||
'Jump Play': 'Lompat Putar',
|
||||
'AirPlay': 'AirPlay',
|
||||
'AirPlay Not Available': 'AirPlay Tidak Tersedia',
|
||||
}
|
||||
} satisfies Dictionary
|
||||
|
||||
export default id
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window['artplayer-i18n-id'] = id
|
||||
}
|
||||
publishLanguage('artplayer-i18n-id', id)
|
||||
@@ -1,30 +0,0 @@
|
||||
import { mergeDeep } from '../utils'
|
||||
import zhCn from './zh-cn'
|
||||
|
||||
export default class I18n {
|
||||
constructor(art) {
|
||||
this.art = art
|
||||
|
||||
this.languages = {
|
||||
'zh-cn': zhCn,
|
||||
}
|
||||
|
||||
this.language = {}
|
||||
|
||||
this.update(art.option.i18n)
|
||||
}
|
||||
|
||||
init() {
|
||||
const lang = this.art.option.lang.toLowerCase()
|
||||
this.language = this.languages[lang] || {}
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this.language[key] || key
|
||||
}
|
||||
|
||||
update(value) {
|
||||
this.languages = mergeDeep(this.languages, value)
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Dictionary, Languages } from './publish'
|
||||
import { has, mergeDeep } from '../utils/property'
|
||||
import zhCn from './zh-cn'
|
||||
|
||||
export interface I18nHost {
|
||||
option: { lang: string, i18n: Languages }
|
||||
}
|
||||
|
||||
export default class I18n {
|
||||
declare art: I18nHost
|
||||
declare languages: Languages
|
||||
declare language: Dictionary
|
||||
|
||||
constructor(art: I18nHost) {
|
||||
this.art = art
|
||||
this.languages = { 'zh-cn': zhCn }
|
||||
this.language = {}
|
||||
this.update(art.option.i18n)
|
||||
}
|
||||
|
||||
init(): void {
|
||||
const lang = this.art.option.lang.toLowerCase()
|
||||
this.language = (has(this.languages, lang) && this.languages[lang]) || {}
|
||||
}
|
||||
|
||||
get(key: string): string {
|
||||
return (has(this.language, key) && this.language[key]) || key
|
||||
}
|
||||
|
||||
update(value: Languages): void {
|
||||
this.languages = mergeDeep(this.languages, value)
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { Dictionary } from './publish'
|
||||
import { publishLanguage } from './publish'
|
||||
|
||||
const pl = {
|
||||
'Video Info': 'Informacje o wideo',
|
||||
'Close': 'Zamknij',
|
||||
@@ -35,10 +38,8 @@ const pl = {
|
||||
'Jump Play': 'Skocz do gry',
|
||||
'AirPlay': 'AirPlay',
|
||||
'AirPlay Not Available': 'AirPlay nie jest dostępny',
|
||||
}
|
||||
} satisfies Dictionary
|
||||
|
||||
export default pl
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window['artplayer-i18n-pl'] = pl
|
||||
}
|
||||
publishLanguage('artplayer-i18n-pl', pl)
|
||||
@@ -0,0 +1,12 @@
|
||||
export type Dictionary = Record<string, string | undefined>
|
||||
export type Languages = Record<string, Dictionary | undefined>
|
||||
|
||||
type LanguageGlobal = 'artplayer-i18n-ar' | 'artplayer-i18n-cs' | 'artplayer-i18n-es' | 'artplayer-i18n-fa' | 'artplayer-i18n-fr' | 'artplayer-i18n-id' | 'artplayer-i18n-pl' | 'artplayer-i18n-ru' | 'artplayer-i18n-tr' | 'artplayer-i18n-vi' | 'artplayer-i18n-zh-cn' | 'artplayer-i18n-zh-tw'
|
||||
|
||||
// Each standalone language keeps its historical browser alias, including ESM.
|
||||
export function publishLanguage(name: LanguageGlobal, value: Dictionary): void {
|
||||
if (typeof window !== 'undefined') {
|
||||
const globals = window as Window & Partial<Record<LanguageGlobal, Dictionary>>
|
||||
globals[name] = value
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { Dictionary } from './publish'
|
||||
import { publishLanguage } from './publish'
|
||||
|
||||
const ru = {
|
||||
'Video Info': 'Информация',
|
||||
'Close': 'Закрыть',
|
||||
@@ -35,10 +38,8 @@ const ru = {
|
||||
'Jump Play': 'Перейти',
|
||||
'AirPlay': 'AirPlay',
|
||||
'AirPlay Not Available': 'AirPlay недоступен',
|
||||
}
|
||||
} satisfies Dictionary
|
||||
|
||||
export default ru
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window['artplayer-i18n-ru'] = ru
|
||||
}
|
||||
publishLanguage('artplayer-i18n-ru', ru)
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { Dictionary } from './publish'
|
||||
import { publishLanguage } from './publish'
|
||||
|
||||
const tr = {
|
||||
'Video Info': 'Video Bilgisi',
|
||||
'Close': 'Kapat',
|
||||
@@ -35,10 +38,8 @@ const tr = {
|
||||
'Jump Play': 'Atla ve Oynat',
|
||||
'AirPlay': 'AirPlay',
|
||||
'AirPlay Not Available': 'AirPlay Kullanılamıyor',
|
||||
}
|
||||
} satisfies Dictionary
|
||||
|
||||
export default tr
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window['artplayer-i18n-tr'] = tr
|
||||
}
|
||||
publishLanguage('artplayer-i18n-tr', tr)
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { Dictionary } from './publish'
|
||||
import { publishLanguage } from './publish'
|
||||
|
||||
const vi = {
|
||||
'Video Info': 'Thông tin video',
|
||||
'Close': 'Đóng',
|
||||
@@ -35,10 +38,8 @@ const vi = {
|
||||
'Jump Play': 'Nhảy đến đoạn phát',
|
||||
'AirPlay': 'AirPlay',
|
||||
'AirPlay Not Available': 'AirPlay không khả dụng',
|
||||
}
|
||||
} satisfies Dictionary
|
||||
|
||||
export default vi
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window['artplayer-i18n-vi'] = vi
|
||||
}
|
||||
publishLanguage('artplayer-i18n-vi', vi)
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { Dictionary } from './publish'
|
||||
import { publishLanguage } from './publish'
|
||||
|
||||
const zhCn = {
|
||||
'Video Info': '统计信息',
|
||||
'Close': '关闭',
|
||||
@@ -35,10 +38,8 @@ const zhCn = {
|
||||
'Jump Play': '跳转播放',
|
||||
'AirPlay': '隔空播放',
|
||||
'AirPlay Not Available': '隔空播放不可用',
|
||||
}
|
||||
} satisfies Dictionary
|
||||
|
||||
export default zhCn
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window['artplayer-i18n-zh-cn'] = zhCn
|
||||
}
|
||||
publishLanguage('artplayer-i18n-zh-cn', zhCn)
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { Dictionary } from './publish'
|
||||
import { publishLanguage } from './publish'
|
||||
|
||||
const zhTw = {
|
||||
'Video Info': '統計訊息',
|
||||
'Close': '關閉',
|
||||
@@ -35,10 +38,8 @@ const zhTw = {
|
||||
'Jump Play': '跳轉播放',
|
||||
'AirPlay': '隔空播放',
|
||||
'AirPlay Not Available': '隔空播放不可用',
|
||||
}
|
||||
} satisfies Dictionary
|
||||
|
||||
export default zhTw
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window['artplayer-i18n-zh-tw'] = zhTw
|
||||
}
|
||||
publishLanguage('artplayer-i18n-zh-tw', zhTw)
|
||||
@@ -1,4 +1,3 @@
|
||||
import { def, getIcon } from '../utils'
|
||||
import airplay from './airplay.svg?raw'
|
||||
import arrowLeft from './arrow-left.svg?raw'
|
||||
import arrowRight from './arrow-right.svg?raw'
|
||||
@@ -27,43 +26,32 @@ import unlock from './unlock.svg?raw'
|
||||
import volumeClose from './volume-close.svg?raw'
|
||||
import volume from './volume.svg?raw'
|
||||
|
||||
export default class Icons {
|
||||
constructor(art) {
|
||||
const icons = {
|
||||
loading,
|
||||
state,
|
||||
play,
|
||||
pause,
|
||||
check,
|
||||
volume,
|
||||
volumeClose,
|
||||
screenshot,
|
||||
setting,
|
||||
pip,
|
||||
arrowLeft,
|
||||
arrowRight,
|
||||
playbackRate,
|
||||
aspectRatio,
|
||||
config,
|
||||
lock,
|
||||
flip,
|
||||
unlock,
|
||||
fullscreenOff,
|
||||
fullscreenOn,
|
||||
fullscreenWebOff,
|
||||
fullscreenWebOn,
|
||||
switchOn,
|
||||
switchOff,
|
||||
error,
|
||||
close,
|
||||
airplay,
|
||||
...art.option.icons,
|
||||
}
|
||||
|
||||
for (const key in icons) {
|
||||
def(this, key, {
|
||||
get: () => getIcon(key, icons[key]),
|
||||
})
|
||||
}
|
||||
}
|
||||
export default {
|
||||
loading,
|
||||
state,
|
||||
play,
|
||||
pause,
|
||||
check,
|
||||
volume,
|
||||
volumeClose,
|
||||
screenshot,
|
||||
setting,
|
||||
pip,
|
||||
arrowLeft,
|
||||
arrowRight,
|
||||
playbackRate,
|
||||
aspectRatio,
|
||||
config,
|
||||
lock,
|
||||
flip,
|
||||
unlock,
|
||||
fullscreenOff,
|
||||
fullscreenOn,
|
||||
fullscreenWebOff,
|
||||
fullscreenWebOn,
|
||||
switchOn,
|
||||
switchOff,
|
||||
error,
|
||||
close,
|
||||
airplay,
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { def, getIcon } from '../utils'
|
||||
import defaults from './defaults'
|
||||
|
||||
export default class Icons {
|
||||
readonly [key: string]: HTMLElement
|
||||
|
||||
constructor(art: { option: { icons: Record<string, string | HTMLElement> } }) {
|
||||
const icons: Record<string, string | HTMLElement | undefined> = { ...defaults, ...art.option.icons }
|
||||
for (const key in icons) {
|
||||
def(this, key, {
|
||||
get: (): HTMLElement => getIcon(key, icons[key]),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import Plugins from './plugins'
|
||||
import scheme from './scheme'
|
||||
import Setting from './setting'
|
||||
import Storage from './storage'
|
||||
import style from './style/index.less?inline'
|
||||
import style from './style'
|
||||
import Subtitle from './subtitle'
|
||||
import Template from './template'
|
||||
import * as utils from './utils'
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import css from './index.less?inline'
|
||||
|
||||
export default css
|
||||
@@ -0,0 +1,16 @@
|
||||
export function setStyleText(id: string, style: string): void {
|
||||
let $style = document.getElementById(id)
|
||||
if (!$style) {
|
||||
$style = document.createElement('style')
|
||||
$style.id = id
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.head.appendChild($style!)
|
||||
})
|
||||
}
|
||||
else {
|
||||
(document.head || document.documentElement).appendChild($style)
|
||||
}
|
||||
}
|
||||
$style.textContent = style
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
import { version } from '../package.json'
|
||||
import { ownContainer } from './lifecycle/instance'
|
||||
import captureTemplate from './lifecycle/template-rollback'
|
||||
import { addClass, errorHandle, isMobile, query, replaceElement, supportsFlex } from './utils'
|
||||
|
||||
export default class Template {
|
||||
constructor(art) {
|
||||
this.art = art
|
||||
const { option, constructor } = art
|
||||
|
||||
if (option.container instanceof Element) {
|
||||
this.$container = option.container
|
||||
}
|
||||
else {
|
||||
this.$container = query(option.container)
|
||||
errorHandle(this.$container, `No container element found by ${option.container}`)
|
||||
}
|
||||
|
||||
errorHandle(supportsFlex(), 'The current browser does not support flex layout')
|
||||
|
||||
const type = this.$container.tagName.toLowerCase()
|
||||
errorHandle(type === 'div', `Unsupported container element type, only support 'div' but got '${type}'`)
|
||||
|
||||
errorHandle(
|
||||
constructor.instances.every(ins => ins.template.$container !== this.$container),
|
||||
'Cannot mount multiple instances on the same dom element',
|
||||
)
|
||||
|
||||
this.query = this.query.bind(this)
|
||||
ownContainer(art, this.$container, captureTemplate(this.$container))
|
||||
this.$container.dataset.artId = art.id
|
||||
this.init()
|
||||
}
|
||||
|
||||
static get html() {
|
||||
return `
|
||||
<div class="art-video-player art-subtitle-show art-layer-show art-control-show art-mask-show">
|
||||
<video class="art-video">
|
||||
<track default kind="metadata" src=""></track>
|
||||
</video>
|
||||
<div class="art-poster"></div>
|
||||
<div class="art-subtitle"></div>
|
||||
<div class="art-danmuku"></div>
|
||||
<div class="art-layers"></div>
|
||||
<div class="art-mask">
|
||||
<div class="art-state"></div>
|
||||
</div>
|
||||
<div class="art-bottom">
|
||||
<div class="art-progress"></div>
|
||||
<div class="art-controls">
|
||||
<div class="art-controls-left"></div>
|
||||
<div class="art-controls-center"></div>
|
||||
<div class="art-controls-right"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="art-loading"></div>
|
||||
<div class="art-notice">
|
||||
<div class="art-notice-inner"></div>
|
||||
</div>
|
||||
<div class="art-settings"></div>
|
||||
<div class="art-info">
|
||||
<div class="art-info-panel">
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Player version:</div>
|
||||
<div class="art-info-content">${version}</div>
|
||||
</div>
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video url:</div>
|
||||
<div class="art-info-content" data-video="currentSrc"></div>
|
||||
</div>
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video volume:</div>
|
||||
<div class="art-info-content" data-video="volume"></div>
|
||||
</div>
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video time:</div>
|
||||
<div class="art-info-content" data-video="currentTime"></div>
|
||||
</div>
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video duration:</div>
|
||||
<div class="art-info-content" data-video="duration"></div>
|
||||
</div>
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video resolution:</div>
|
||||
<div class="art-info-content">
|
||||
<span data-video="videoWidth"></span> x <span data-video="videoHeight"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="art-info-close">[x]</div>
|
||||
</div>
|
||||
<div class="art-contextmenus"></div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
query(className) {
|
||||
return query(className, this.$container)
|
||||
}
|
||||
|
||||
init() {
|
||||
const { option } = this.art
|
||||
|
||||
if (!option.useSSR) {
|
||||
this.$container.innerHTML = Template.html
|
||||
}
|
||||
|
||||
this.$player = this.query('.art-video-player')
|
||||
this.$video = this.query('.art-video')
|
||||
this.$track = this.query('track')
|
||||
this.$poster = this.query('.art-poster')
|
||||
this.$subtitle = this.query('.art-subtitle')
|
||||
this.$danmuku = this.query('.art-danmuku')
|
||||
this.$bottom = this.query('.art-bottom')
|
||||
this.$progress = this.query('.art-progress')
|
||||
this.$controls = this.query('.art-controls')
|
||||
this.$controlsLeft = this.query('.art-controls-left')
|
||||
this.$controlsCenter = this.query('.art-controls-center')
|
||||
this.$controlsRight = this.query('.art-controls-right')
|
||||
this.$layer = this.query('.art-layers')
|
||||
this.$loading = this.query('.art-loading')
|
||||
this.$notice = this.query('.art-notice')
|
||||
this.$noticeInner = this.query('.art-notice-inner')
|
||||
this.$mask = this.query('.art-mask')
|
||||
this.$state = this.query('.art-state')
|
||||
this.$setting = this.query('.art-settings')
|
||||
this.$info = this.query('.art-info')
|
||||
this.$infoPanel = this.query('.art-info-panel')
|
||||
this.$infoClose = this.query('.art-info-close')
|
||||
this.$contextmenu = this.query('.art-contextmenus')
|
||||
|
||||
if (option.proxy) {
|
||||
const video = option.proxy.call(this.art, this.art)
|
||||
errorHandle(
|
||||
video instanceof HTMLVideoElement || video instanceof HTMLCanvasElement,
|
||||
`Function 'option.proxy' needs to return 'HTMLVideoElement' or 'HTMLCanvasElement'`,
|
||||
)
|
||||
replaceElement(video, this.$video)
|
||||
video.className = 'art-video'
|
||||
this.$video = video
|
||||
}
|
||||
|
||||
if (option.backdrop) {
|
||||
addClass(this.$player, 'art-backdrop')
|
||||
}
|
||||
|
||||
if (isMobile) {
|
||||
addClass(this.$player, 'art-mobile')
|
||||
}
|
||||
}
|
||||
|
||||
destroy(removeHtml) {
|
||||
if (removeHtml) {
|
||||
this.$container.innerHTML = ''
|
||||
}
|
||||
else {
|
||||
addClass(this.$player, 'art-destroy')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import type { TemplateHost, TemplateNodes } from './template/types'
|
||||
import { ownContainer } from './lifecycle/instance'
|
||||
import captureTemplate from './lifecycle/template-rollback'
|
||||
import html from './template/html'
|
||||
import bindNodes from './template/nodes'
|
||||
import { addClass, errorHandle, isMobile, query, replaceElement, supportsFlex } from './utils'
|
||||
|
||||
export default class Template<Host extends TemplateHost<Host>> implements TemplateNodes {
|
||||
declare art: Host
|
||||
declare $container: HTMLDivElement
|
||||
declare $player: HTMLDivElement | null
|
||||
declare $video: HTMLVideoElement | HTMLCanvasElement | null
|
||||
declare $track: HTMLTrackElement | null
|
||||
declare $poster: HTMLDivElement | null
|
||||
declare $subtitle: HTMLDivElement | null
|
||||
declare $danmuku: HTMLDivElement | null
|
||||
declare $bottom: HTMLDivElement | null
|
||||
declare $progress: HTMLDivElement | null
|
||||
declare $controls: HTMLDivElement | null
|
||||
declare $controlsLeft: HTMLDivElement | null
|
||||
declare $controlsCenter: HTMLDivElement | null
|
||||
declare $controlsRight: HTMLDivElement | null
|
||||
declare $layer: HTMLDivElement | null
|
||||
declare $loading: HTMLDivElement | null
|
||||
declare $notice: HTMLDivElement | null
|
||||
declare $noticeInner: HTMLDivElement | null
|
||||
declare $mask: HTMLDivElement | null
|
||||
declare $state: HTMLDivElement | null
|
||||
declare $setting: HTMLDivElement | null
|
||||
declare $info: HTMLDivElement | null
|
||||
declare $infoPanel: HTMLDivElement | null
|
||||
declare $infoClose: HTMLDivElement | null
|
||||
declare $contextmenu: HTMLDivElement | null
|
||||
|
||||
constructor(art: Host) {
|
||||
this.art = art
|
||||
const { option, constructor } = art
|
||||
|
||||
if (option.container instanceof Element) {
|
||||
this.$container = option.container as HTMLDivElement
|
||||
}
|
||||
else {
|
||||
this.$container = query(option.container) as HTMLDivElement
|
||||
errorHandle(this.$container, `No container element found by ${option.container}`)
|
||||
}
|
||||
|
||||
errorHandle(supportsFlex(), 'The current browser does not support flex layout')
|
||||
|
||||
const type = this.$container.tagName.toLowerCase()
|
||||
errorHandle(type === 'div', `Unsupported container element type, only support 'div' but got '${type}'`)
|
||||
|
||||
errorHandle(
|
||||
constructor.instances.every(ins => ins.template.$container !== this.$container),
|
||||
'Cannot mount multiple instances on the same dom element',
|
||||
)
|
||||
|
||||
this.query = this.query.bind(this)
|
||||
ownContainer(art, this.$container, captureTemplate(this.$container))
|
||||
this.$container.dataset.artId = String(art.id)
|
||||
this.init()
|
||||
}
|
||||
|
||||
static get html(): string {
|
||||
return html
|
||||
}
|
||||
|
||||
query<T extends Element = Element>(className: string): T | null {
|
||||
return query(className, this.$container) as T | null
|
||||
}
|
||||
|
||||
init(): void {
|
||||
const { option } = this.art
|
||||
|
||||
if (!option.useSSR) {
|
||||
this.$container.innerHTML = Template.html
|
||||
}
|
||||
|
||||
bindNodes(this)
|
||||
|
||||
if (option.proxy) {
|
||||
const video = option.proxy.call(this.art, this.art)
|
||||
assertProxy(video)
|
||||
replaceElement(video, this.$video)
|
||||
video.className = 'art-video'
|
||||
this.$video = video
|
||||
}
|
||||
|
||||
if (option.backdrop) {
|
||||
addClass(this.$player, 'art-backdrop')
|
||||
}
|
||||
|
||||
if (isMobile) {
|
||||
addClass(this.$player, 'art-mobile')
|
||||
}
|
||||
}
|
||||
|
||||
destroy(removeHtml: boolean): void {
|
||||
if (removeHtml) {
|
||||
this.$container.innerHTML = ''
|
||||
}
|
||||
else {
|
||||
addClass(this.$player, 'art-destroy')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertProxy(video: unknown): asserts video is HTMLVideoElement | HTMLCanvasElement {
|
||||
errorHandle(
|
||||
video instanceof HTMLVideoElement || video instanceof HTMLCanvasElement,
|
||||
'Function \'option.proxy\' needs to return \'HTMLVideoElement\' or \'HTMLCanvasElement\'',
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { version } from '../../package.json'
|
||||
|
||||
export default `
|
||||
<div class="art-video-player art-subtitle-show art-layer-show art-control-show art-mask-show">
|
||||
<video class="art-video">
|
||||
<track default kind="metadata" src=""></track>
|
||||
</video>
|
||||
<div class="art-poster"></div>
|
||||
<div class="art-subtitle"></div>
|
||||
<div class="art-danmuku"></div>
|
||||
<div class="art-layers"></div>
|
||||
<div class="art-mask">
|
||||
<div class="art-state"></div>
|
||||
</div>
|
||||
<div class="art-bottom">
|
||||
<div class="art-progress"></div>
|
||||
<div class="art-controls">
|
||||
<div class="art-controls-left"></div>
|
||||
<div class="art-controls-center"></div>
|
||||
<div class="art-controls-right"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="art-loading"></div>
|
||||
<div class="art-notice">
|
||||
<div class="art-notice-inner"></div>
|
||||
</div>
|
||||
<div class="art-settings"></div>
|
||||
<div class="art-info">
|
||||
<div class="art-info-panel">
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Player version:</div>
|
||||
<div class="art-info-content">${version}</div>
|
||||
</div>
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video url:</div>
|
||||
<div class="art-info-content" data-video="currentSrc"></div>
|
||||
</div>
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video volume:</div>
|
||||
<div class="art-info-content" data-video="volume"></div>
|
||||
</div>
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video time:</div>
|
||||
<div class="art-info-content" data-video="currentTime"></div>
|
||||
</div>
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video duration:</div>
|
||||
<div class="art-info-content" data-video="duration"></div>
|
||||
</div>
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video resolution:</div>
|
||||
<div class="art-info-content">
|
||||
<span data-video="videoWidth"></span> x <span data-video="videoHeight"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="art-info-close">[x]</div>
|
||||
</div>
|
||||
<div class="art-contextmenus"></div>
|
||||
</div>
|
||||
`
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { TemplateNodes } from './types'
|
||||
|
||||
export default function bindNodes(target: TemplateNodes & { query: <T extends Element = Element>(selector: string) => T | null }): void {
|
||||
target.$player = target.query<HTMLDivElement>('.art-video-player')
|
||||
target.$video = target.query<HTMLVideoElement>('.art-video')
|
||||
target.$track = target.query<HTMLTrackElement>('track')
|
||||
target.$poster = target.query<HTMLDivElement>('.art-poster')
|
||||
target.$subtitle = target.query<HTMLDivElement>('.art-subtitle')
|
||||
target.$danmuku = target.query<HTMLDivElement>('.art-danmuku')
|
||||
target.$bottom = target.query<HTMLDivElement>('.art-bottom')
|
||||
target.$progress = target.query<HTMLDivElement>('.art-progress')
|
||||
target.$controls = target.query<HTMLDivElement>('.art-controls')
|
||||
target.$controlsLeft = target.query<HTMLDivElement>('.art-controls-left')
|
||||
target.$controlsCenter = target.query<HTMLDivElement>('.art-controls-center')
|
||||
target.$controlsRight = target.query<HTMLDivElement>('.art-controls-right')
|
||||
target.$layer = target.query<HTMLDivElement>('.art-layers')
|
||||
target.$loading = target.query<HTMLDivElement>('.art-loading')
|
||||
target.$notice = target.query<HTMLDivElement>('.art-notice')
|
||||
target.$noticeInner = target.query<HTMLDivElement>('.art-notice-inner')
|
||||
target.$mask = target.query<HTMLDivElement>('.art-mask')
|
||||
target.$state = target.query<HTMLDivElement>('.art-state')
|
||||
target.$setting = target.query<HTMLDivElement>('.art-settings')
|
||||
target.$info = target.query<HTMLDivElement>('.art-info')
|
||||
target.$infoPanel = target.query<HTMLDivElement>('.art-info-panel')
|
||||
target.$infoClose = target.query<HTMLDivElement>('.art-info-close')
|
||||
target.$contextmenu = target.query<HTMLDivElement>('.art-contextmenus')
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
export interface TemplateHost<Host> {
|
||||
id: number
|
||||
option: {
|
||||
container: string | HTMLElement
|
||||
useSSR: boolean
|
||||
proxy?: (this: Host, art: Host) => unknown
|
||||
backdrop: boolean
|
||||
}
|
||||
constructor: { instances: readonly { template: { $container: Element } }[] }
|
||||
}
|
||||
|
||||
// Missing SSR nodes remain nullable; mounting does not synthesize replacements.
|
||||
export interface TemplateNodes {
|
||||
$player: HTMLDivElement | null
|
||||
$video: HTMLVideoElement | HTMLCanvasElement | null
|
||||
$track: HTMLTrackElement | null
|
||||
$poster: HTMLDivElement | null
|
||||
$subtitle: HTMLDivElement | null
|
||||
$danmuku: HTMLDivElement | null
|
||||
$bottom: HTMLDivElement | null
|
||||
$progress: HTMLDivElement | null
|
||||
$controls: HTMLDivElement | null
|
||||
$controlsLeft: HTMLDivElement | null
|
||||
$controlsCenter: HTMLDivElement | null
|
||||
$controlsRight: HTMLDivElement | null
|
||||
$layer: HTMLDivElement | null
|
||||
$loading: HTMLDivElement | null
|
||||
$notice: HTMLDivElement | null
|
||||
$noticeInner: HTMLDivElement | null
|
||||
$mask: HTMLDivElement | null
|
||||
$state: HTMLDivElement | null
|
||||
$setting: HTMLDivElement | null
|
||||
$info: HTMLDivElement | null
|
||||
$infoPanel: HTMLDivElement | null
|
||||
$infoClose: HTMLDivElement | null
|
||||
$contextmenu: HTMLDivElement | null
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import { isMobile } from './compatibility'
|
||||
|
||||
export { setStyleText } from '../style/inject'
|
||||
|
||||
/** @param {string} selector @param {ParentNode} parent */
|
||||
export function query(selector, parent = document) {
|
||||
return parent.querySelector(selector)
|
||||
}
|
||||
@@ -90,6 +93,7 @@ export function createElement(tag) {
|
||||
return document.createElement(tag)
|
||||
}
|
||||
|
||||
/** @param {string} key @param {string | HTMLElement} html */
|
||||
export function getIcon(key = '', html = '') {
|
||||
const icon = createElement('i')
|
||||
addClass(icon, 'art-icon')
|
||||
@@ -98,23 +102,6 @@ export function getIcon(key = '', html = '') {
|
||||
return icon
|
||||
}
|
||||
|
||||
export function setStyleText(id, style) {
|
||||
let $style = document.getElementById(id)
|
||||
if (!$style) {
|
||||
$style = document.createElement('style')
|
||||
$style.id = id
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.head.appendChild($style)
|
||||
})
|
||||
}
|
||||
else {
|
||||
(document.head || document.documentElement).appendChild($style)
|
||||
}
|
||||
}
|
||||
$style.textContent = style
|
||||
}
|
||||
|
||||
export function supportsFlex() {
|
||||
const div = document.createElement('div')
|
||||
div.style.display = 'flex'
|
||||
|
||||
Reference in new issue
Block a user