mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-23 22:01:20 -08:00
refactor(ads): [PKG-ADS-03] split TypeScript lifecycle and preserve legacy behavior
This commit is contained in:
1 parent
069ad4aae1
commit
39e2d4cdfb
37 files changed
+3083
-788
No files matched your search
@@ -0,0 +1,55 @@
|
||||
import type { Cleanup } from './types'
|
||||
|
||||
export function createCountdown(total: () => number, render: (seconds: number) => void, complete: Cleanup) {
|
||||
let elapsed = 0
|
||||
let ready = false
|
||||
let running = false
|
||||
let closed = false
|
||||
let timer: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
function schedule(): void {
|
||||
if (!ready || !running || closed || timer !== undefined)
|
||||
return
|
||||
timer = setTimeout(() => {
|
||||
timer = undefined
|
||||
if (closed || !running)
|
||||
return
|
||||
elapsed += 1
|
||||
render(elapsed)
|
||||
if (closed)
|
||||
return
|
||||
if (elapsed >= total())
|
||||
complete()
|
||||
else
|
||||
schedule()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
function pause(): void {
|
||||
running = false
|
||||
if (timer !== undefined)
|
||||
clearTimeout(timer)
|
||||
timer = undefined
|
||||
}
|
||||
|
||||
return {
|
||||
start() {
|
||||
if (ready || closed)
|
||||
return
|
||||
ready = true
|
||||
running = true
|
||||
schedule()
|
||||
},
|
||||
play() {
|
||||
if (!ready || closed)
|
||||
return
|
||||
running = true
|
||||
schedule()
|
||||
},
|
||||
pause,
|
||||
stop() {
|
||||
closed = true
|
||||
pause()
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
import style from './style.less?inline'
|
||||
|
||||
function checkVersion(art) {
|
||||
const {
|
||||
version,
|
||||
utils: { errorHandle },
|
||||
} = art.constructor
|
||||
const arr = version.split('.').map(Number)
|
||||
const major = arr[0]
|
||||
const minor = arr[1] / 100
|
||||
errorHandle(
|
||||
major + minor >= 5,
|
||||
`Artplayer.js@${version} is not compatible the artplayerPluginAds@${artplayerPluginAds.version}. Please update it to version Artplayer.js@5.x.x`,
|
||||
)
|
||||
}
|
||||
|
||||
export default function artplayerPluginAds(option) {
|
||||
return (art) => {
|
||||
checkVersion(art)
|
||||
|
||||
const {
|
||||
template: { $player },
|
||||
icons: { volume, volumeClose, fullscreenOn, fullscreenOff, loading },
|
||||
constructor: {
|
||||
validator,
|
||||
utils: { query, append, setStyle },
|
||||
},
|
||||
} = art
|
||||
|
||||
option = validator(
|
||||
{
|
||||
html: '',
|
||||
video: '',
|
||||
url: '',
|
||||
playDuration: 5,
|
||||
totalDuration: 10,
|
||||
muted: false,
|
||||
i18n: {
|
||||
close: '关闭广告',
|
||||
countdown: '%s秒',
|
||||
detail: '查看详情',
|
||||
canBeClosed: '%s秒后可关闭广告',
|
||||
},
|
||||
...option,
|
||||
},
|
||||
{
|
||||
html: '?string',
|
||||
video: '?string',
|
||||
url: '?string',
|
||||
playDuration: 'number',
|
||||
totalDuration: 'number',
|
||||
muted: '?boolean',
|
||||
i18n: {
|
||||
close: 'string',
|
||||
countdown: 'string',
|
||||
detail: 'string',
|
||||
canBeClosed: 'string',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
let $ads = null
|
||||
let $timer = null
|
||||
let $close = null
|
||||
let $countdown = null
|
||||
let $control = null
|
||||
let $loading = null
|
||||
|
||||
let time = 0
|
||||
let timer = null
|
||||
let isEnd = false
|
||||
let isInit = false
|
||||
let isCanClose = false
|
||||
|
||||
function getI18n(val, str) {
|
||||
return str.replace('%s', val)
|
||||
}
|
||||
|
||||
function skip() {
|
||||
isEnd = true
|
||||
art.play()
|
||||
if (option.video)
|
||||
$ads.pause()
|
||||
setStyle(art.template.$ads, 'display', 'none')
|
||||
art.emit('artplayerPluginAds:skip', option)
|
||||
}
|
||||
|
||||
function play() {
|
||||
if (isEnd)
|
||||
return
|
||||
|
||||
timer = setTimeout(() => {
|
||||
time += 1
|
||||
|
||||
const playDuration = option.playDuration - time
|
||||
if (playDuration >= 1) {
|
||||
$close.innerHTML = getI18n(playDuration, option.i18n.canBeClosed)
|
||||
}
|
||||
else {
|
||||
$close.innerHTML = option.i18n.close
|
||||
if (!isCanClose) {
|
||||
isCanClose = true
|
||||
}
|
||||
}
|
||||
|
||||
$countdown.innerHTML = getI18n(option.totalDuration - time, option.i18n.countdown)
|
||||
|
||||
if (time >= option.totalDuration) {
|
||||
skip()
|
||||
}
|
||||
else {
|
||||
play()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
function pause() {
|
||||
if (isEnd)
|
||||
return
|
||||
clearTimeout(timer)
|
||||
}
|
||||
|
||||
function show() {
|
||||
art.template.$ads = append($player, '<div class="artplayer-plugin-ads"></div>')
|
||||
|
||||
$ads = append(
|
||||
art.template.$ads,
|
||||
option.video
|
||||
? `<video class="artplayer-plugin-ads-video" src="${option.video}" loop playsInline></video>`
|
||||
: `<div class="artplayer-plugin-ads-html">${option.html}</div>`,
|
||||
)
|
||||
|
||||
$loading = append(art.template.$ads, '<div class="artplayer-plugin-ads-loading"></div>')
|
||||
append($loading, loading)
|
||||
|
||||
$timer = append(
|
||||
art.template.$ads,
|
||||
`<div class="artplayer-plugin-ads-timer">
|
||||
<div class="artplayer-plugin-ads-close">${
|
||||
option.playDuration <= 0
|
||||
? option.i18n.close
|
||||
: getI18n(option.playDuration, option.i18n.canBeClosed)
|
||||
}</div>
|
||||
<div class="artplayer-plugin-ads-countdown">${getI18n(
|
||||
option.totalDuration,
|
||||
option.i18n.countdown,
|
||||
)}</div>
|
||||
</div>`,
|
||||
)
|
||||
|
||||
$close = query('.artplayer-plugin-ads-close', $timer)
|
||||
$countdown = query('.artplayer-plugin-ads-countdown', $timer)
|
||||
|
||||
if (option.playDuration >= option.totalDuration) {
|
||||
setStyle($close, 'display', 'none')
|
||||
}
|
||||
|
||||
art.proxy($close, 'click', () => {
|
||||
if (isCanClose) {
|
||||
skip()
|
||||
}
|
||||
})
|
||||
|
||||
$control = append(
|
||||
art.template.$ads,
|
||||
`<div class="artplayer-plugin-ads-control">
|
||||
<div class="artplayer-plugin-ads-detail">${option.i18n.detail}</div>
|
||||
<div class="artplayer-plugin-ads-muted"></div>
|
||||
<div class="artplayer-plugin-ads-fullscreen"></div>
|
||||
</div>`,
|
||||
)
|
||||
|
||||
const $detail = query('.artplayer-plugin-ads-detail', $control)
|
||||
const $muted = query('.artplayer-plugin-ads-muted', $control)
|
||||
const $fullscreen = query('.artplayer-plugin-ads-fullscreen', $control)
|
||||
|
||||
if (option.url) {
|
||||
art.proxy($detail, 'click', () => {
|
||||
window.open(option.url)
|
||||
art.emit('artplayerPluginAds:click', option)
|
||||
})
|
||||
}
|
||||
else {
|
||||
setStyle($detail, 'display', 'none')
|
||||
}
|
||||
|
||||
if (option.video) {
|
||||
const $volume = append($muted, volume)
|
||||
const $volumeClose = append($muted, volumeClose)
|
||||
setStyle($volumeClose, 'display', 'none')
|
||||
|
||||
// If the ad was set to muted initially, match the icon
|
||||
if (option.muted) {
|
||||
$ads.muted = true
|
||||
setStyle($volume, 'display', 'none')
|
||||
setStyle($volumeClose, 'display', 'inline-flex')
|
||||
}
|
||||
|
||||
art.proxy($muted, 'click', () => {
|
||||
$ads.muted = !$ads.muted
|
||||
if ($ads.muted) {
|
||||
setStyle($volume, 'display', 'none')
|
||||
setStyle($volumeClose, 'display', 'inline-flex')
|
||||
}
|
||||
else {
|
||||
setStyle($volume, 'display', 'inline-flex')
|
||||
setStyle($volumeClose, 'display', 'none')
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
setStyle($muted, 'display', 'none')
|
||||
}
|
||||
|
||||
const $fullscreenOn = append($fullscreen, fullscreenOn)
|
||||
const $fullscreenOff = append($fullscreen, fullscreenOff)
|
||||
setStyle($fullscreenOff, 'display', 'none')
|
||||
|
||||
art.proxy($fullscreen, 'click', () => {
|
||||
art.fullscreen = !art.fullscreen
|
||||
if (art.fullscreen) {
|
||||
setStyle($fullscreenOn, 'display', 'inline-flex')
|
||||
setStyle($fullscreenOff, 'display', 'none')
|
||||
}
|
||||
else {
|
||||
setStyle($fullscreenOn, 'display', 'none')
|
||||
setStyle($fullscreenOff, 'display', 'inline-flex')
|
||||
}
|
||||
})
|
||||
|
||||
art.proxy($ads, 'click', () => {
|
||||
if (option.url)
|
||||
window.open(option.url)
|
||||
art.emit('artplayerPluginAds:click', option)
|
||||
})
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (isInit)
|
||||
return
|
||||
isInit = true
|
||||
|
||||
show()
|
||||
art.pause()
|
||||
|
||||
if (option.video) {
|
||||
art.proxy($ads, 'error', skip)
|
||||
art.proxy($ads, 'loadedmetadata', () => {
|
||||
play()
|
||||
$ads.play()
|
||||
setStyle($timer, 'display', 'flex')
|
||||
setStyle($control, 'display', 'flex')
|
||||
setStyle($loading, 'display', 'none')
|
||||
})
|
||||
}
|
||||
else {
|
||||
play()
|
||||
setStyle($timer, 'display', 'flex')
|
||||
setStyle($control, 'display', 'flex')
|
||||
setStyle($loading, 'display', 'none')
|
||||
}
|
||||
|
||||
art.on('document:visibilitychange', () => {
|
||||
if (document.hidden) {
|
||||
pause()
|
||||
}
|
||||
else {
|
||||
play()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
art.on('ready', () => {
|
||||
art.once('play', init)
|
||||
art.once('video:playing', init)
|
||||
})
|
||||
|
||||
return {
|
||||
name: 'artplayerPluginAds',
|
||||
skip,
|
||||
pause,
|
||||
play,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
if (!document.getElementById('artplayer-plugin-ads')) {
|
||||
const $style = document.createElement('style')
|
||||
$style.id = 'artplayer-plugin-ads'
|
||||
$style.textContent = style
|
||||
document.head.appendChild($style)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { Input, Result } from './types'
|
||||
import { normalizeOptions } from './options'
|
||||
import { createSession } from './session'
|
||||
import style from './style.less?inline'
|
||||
|
||||
export default function artplayerPluginAds(input?: Input) {
|
||||
return (art: Artplayer): Result => {
|
||||
// Legacy core declarations type instance.constructor as Function. Check the
|
||||
// actual capabilities before using its existing static validator/utilities.
|
||||
const constructor = art.constructor as typeof Artplayer
|
||||
if (typeof constructor.validator !== 'function'
|
||||
|| !['append', 'query', 'setStyle'].every(name => typeof Reflect.get(constructor.utils || {}, name) === 'function')) { throw new Error('Artplayer Ads requires the core validator and DOM utilities') }
|
||||
const option = normalizeOptions(input, constructor.validator)
|
||||
const { volume, volumeClose, fullscreenOn, fullscreenOff, loading } = art.icons
|
||||
return createSession(art, option, { volume, volumeClose, fullscreenOn, fullscreenOff, loading }, constructor.utils)
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined' && !document.getElementById('artplayer-plugin-ads')) {
|
||||
const element = document.createElement('style')
|
||||
element.id = 'artplayer-plugin-ads'
|
||||
element.textContent = style
|
||||
document.head.appendChild(element)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { Input, Options } from './types'
|
||||
|
||||
export function normalizeOptions(input: Input | undefined, validate: typeof Artplayer.validator): Options {
|
||||
return validate({
|
||||
html: '',
|
||||
video: '',
|
||||
url: '',
|
||||
playDuration: 5,
|
||||
totalDuration: 10,
|
||||
muted: false,
|
||||
i18n: {
|
||||
close: '关闭广告',
|
||||
countdown: '%s秒',
|
||||
detail: '查看详情',
|
||||
canBeClosed: '%s秒后可关闭广告',
|
||||
},
|
||||
...input,
|
||||
}, {
|
||||
html: '?string',
|
||||
video: '?string',
|
||||
url: '?string',
|
||||
playDuration: 'number',
|
||||
totalDuration: 'number',
|
||||
muted: '?boolean',
|
||||
i18n: { close: 'string', countdown: 'string', detail: 'string', canBeClosed: 'string' },
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { Cleanup, Host } from './types'
|
||||
|
||||
export function report(error: unknown): void {
|
||||
console.warn('Artplayer Ads:', error)
|
||||
}
|
||||
|
||||
export function createResources(host: Pick<Host, 'on' | 'off'>) {
|
||||
const cleaners: Cleanup[] = []
|
||||
let closed = false
|
||||
|
||||
function install(add: Cleanup, remove: Cleanup): void {
|
||||
if (closed)
|
||||
return
|
||||
cleaners.push(remove)
|
||||
try {
|
||||
add()
|
||||
}
|
||||
finally {
|
||||
// A custom host/target can destroy the owner while registration is executing.
|
||||
if (closed)
|
||||
remove()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
on(name: string, callback: Cleanup) {
|
||||
const listener = () => {
|
||||
if (!closed)
|
||||
callback()
|
||||
}
|
||||
install(() => {
|
||||
host.on(name, listener)
|
||||
}, () => {
|
||||
host.off(name, listener)
|
||||
})
|
||||
},
|
||||
dom(target: EventTarget, name: string, callback: Cleanup) {
|
||||
const listener = () => {
|
||||
if (!closed)
|
||||
callback()
|
||||
}
|
||||
install(() => target.addEventListener(name, listener), () => target.removeEventListener(name, listener))
|
||||
},
|
||||
dispose() {
|
||||
if (closed)
|
||||
return
|
||||
closed = true
|
||||
for (const clean of cleaners.splice(0).reverse()) {
|
||||
try {
|
||||
clean()
|
||||
}
|
||||
catch (error) { report(error) }
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export type Resources = ReturnType<typeof createResources>
|
||||
@@ -0,0 +1,182 @@
|
||||
import type { Host, Icons, Options, Result, Utilities } from './types'
|
||||
import type { View } from './view'
|
||||
import { createCountdown } from './countdown'
|
||||
import { createResources, report } from './resources'
|
||||
import { createView } from './view'
|
||||
|
||||
export function createSession(art: Host, option: Options, icons: Icons, utils: Utilities): Result {
|
||||
const events = createResources(art)
|
||||
const lifetime = createResources(art)
|
||||
const template = art.template as Host['template'] & { $ads?: HTMLElement }
|
||||
let state: 'waiting' | 'active' | 'ended' | 'destroyed' = 'waiting'
|
||||
let initialized = false
|
||||
let armed = false
|
||||
let mediaReady = false
|
||||
let root: HTMLElement | undefined
|
||||
let view: View | undefined
|
||||
const clock = createCountdown(() => option.totalDuration, time => view?.render(time), skip)
|
||||
const active = () => state === 'active' && !art.isDestroy
|
||||
const destroyed = () => state === 'destroyed' || art.isDestroy
|
||||
|
||||
function pauseVideo(video: HTMLVideoElement | null | undefined): void {
|
||||
try {
|
||||
video?.pause()
|
||||
}
|
||||
catch (error) { report(error) }
|
||||
}
|
||||
|
||||
function requestPlay(start: () => unknown, rejected: (error: unknown) => void, late?: () => void): void {
|
||||
try {
|
||||
Promise.resolve(start()).then(() => {
|
||||
if (!active())
|
||||
late?.()
|
||||
}, rejected).catch(report)
|
||||
}
|
||||
catch (error) { rejected(error) }
|
||||
}
|
||||
|
||||
function skip(): void {
|
||||
if (state === 'ended' || state === 'destroyed' || art.isDestroy)
|
||||
return
|
||||
state = 'ended'
|
||||
clock.stop()
|
||||
events.dispose()
|
||||
if (initialized) {
|
||||
requestPlay(() => art.play(), (error) => {
|
||||
if (state !== 'destroyed')
|
||||
report(error)
|
||||
})
|
||||
if (destroyed())
|
||||
return
|
||||
pauseVideo(view?.video)
|
||||
if (destroyed())
|
||||
return
|
||||
view?.hide()
|
||||
}
|
||||
if (!destroyed())
|
||||
art.emit('artplayerPluginAds:skip', option)
|
||||
}
|
||||
|
||||
function disposeView(): void {
|
||||
const video = view?.video
|
||||
pauseVideo(video)
|
||||
if (video) {
|
||||
try {
|
||||
video.removeAttribute('src')
|
||||
video.load()
|
||||
}
|
||||
catch (error) { report(error) }
|
||||
}
|
||||
try {
|
||||
root?.remove()
|
||||
}
|
||||
catch (error) { report(error) }
|
||||
finally {
|
||||
if (template.$ads === root)
|
||||
delete template.$ads
|
||||
}
|
||||
}
|
||||
|
||||
function destroy(): void {
|
||||
if (state === 'destroyed')
|
||||
return
|
||||
state = 'destroyed'
|
||||
clock.stop()
|
||||
events.dispose()
|
||||
lifetime.dispose()
|
||||
disposeView()
|
||||
}
|
||||
|
||||
function init(): void {
|
||||
if (state !== 'waiting' || art.isDestroy)
|
||||
return
|
||||
state = 'active'
|
||||
initialized = true
|
||||
try {
|
||||
view = createView(template.$player, icons, option, utils, (node) => {
|
||||
root = node
|
||||
template.$ads = node
|
||||
})
|
||||
if (!active()) {
|
||||
disposeView()
|
||||
return
|
||||
}
|
||||
art.pause()
|
||||
if (!active())
|
||||
return
|
||||
const current = view
|
||||
const document = root!.ownerDocument
|
||||
current.fullscreen(art.fullscreen)
|
||||
current.bind(events, {
|
||||
skip,
|
||||
click() {
|
||||
if (option.url)
|
||||
(document.defaultView || window).open(option.url)
|
||||
if (active())
|
||||
art.emit('artplayerPluginAds:click', option)
|
||||
},
|
||||
fullscreen() {
|
||||
art.fullscreen = !art.fullscreen
|
||||
if (active())
|
||||
current.fullscreen(art.fullscreen)
|
||||
},
|
||||
})
|
||||
events.on('fullscreen', () => current.fullscreen(art.fullscreen))
|
||||
const visibility = () => document.hidden ? clock.pause() : clock.play()
|
||||
events.dom(document, 'visibilitychange', visibility)
|
||||
// Support the newer core event bridge as well; the clock deduplicates resume.
|
||||
events.on('document:visibilitychange', visibility)
|
||||
const ready = () => {
|
||||
if (!active() || mediaReady)
|
||||
return
|
||||
mediaReady = true
|
||||
clock.start()
|
||||
if (document.hidden)
|
||||
clock.pause()
|
||||
if (current.video) {
|
||||
const video = current.video
|
||||
requestPlay(() => video.play(), (error) => {
|
||||
if (active()) {
|
||||
report(error)
|
||||
skip()
|
||||
}
|
||||
}, () => pauseVideo(video))
|
||||
}
|
||||
if (active())
|
||||
current.ready()
|
||||
}
|
||||
if (current.video) {
|
||||
events.dom(current.video, 'error', skip)
|
||||
events.dom(current.video, 'loadedmetadata', ready)
|
||||
// Observe readiness/failure before initiating a potentially cached request.
|
||||
current.video.src = option.video!
|
||||
}
|
||||
else {
|
||||
ready()
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
destroy()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
lifetime.on('destroy', destroy)
|
||||
events.on('ready', () => {
|
||||
if (armed || state !== 'waiting')
|
||||
return
|
||||
armed = true
|
||||
events.on('play', init)
|
||||
events.on('video:playing', init)
|
||||
})
|
||||
if (art.isDestroy)
|
||||
destroy()
|
||||
}
|
||||
catch (error) {
|
||||
destroy()
|
||||
throw error
|
||||
}
|
||||
|
||||
return { name: 'artplayerPluginAds', skip, pause: clock.pause, play: clock.play }
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
|
||||
export interface Translations {
|
||||
close: string
|
||||
countdown: string
|
||||
detail: string
|
||||
canBeClosed: string
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
html?: string
|
||||
video?: string
|
||||
url?: string
|
||||
playDuration: number
|
||||
totalDuration: number
|
||||
muted?: boolean
|
||||
i18n: Translations
|
||||
}
|
||||
|
||||
export type Input = Partial<Options>
|
||||
export type Host = Pick<Artplayer, 'on' | 'off' | 'emit' | 'play' | 'pause' | 'template' | 'icons' | 'fullscreen' | 'isDestroy'>
|
||||
export type Utilities = Pick<typeof Artplayer.utils, 'append' | 'query' | 'setStyle'>
|
||||
export type Icons = Pick<Artplayer['icons'], 'volume' | 'volumeClose' | 'fullscreenOn' | 'fullscreenOff' | 'loading'>
|
||||
export type Cleanup = () => void
|
||||
|
||||
export interface Result {
|
||||
name: 'artplayerPluginAds'
|
||||
skip: Cleanup
|
||||
pause: Cleanup
|
||||
play: Cleanup
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import type { Resources } from './resources'
|
||||
import type { Cleanup, Icons, Options, Utilities } from './types'
|
||||
|
||||
const translate = (value: number, text: string) => text.replace('%s', String(value))
|
||||
|
||||
export function createView(parent: HTMLElement, icons: Icons, option: Options, utils: Utilities, own: (root: HTMLElement) => void) {
|
||||
const { append, query, setStyle } = utils
|
||||
// The private markup below fixes the element kind; public append/query retain their broad types.
|
||||
const root = append(parent, '<div class="artplayer-plugin-ads"></div>') as HTMLElement
|
||||
own(root)
|
||||
const content = append(root, option.video
|
||||
? '<video class="artplayer-plugin-ads-video" loop playsInline></video>'
|
||||
: `<div class="artplayer-plugin-ads-html">${option.html}</div>`) as HTMLElement
|
||||
const video = option.video ? content as HTMLVideoElement : null
|
||||
const loading = append(root, '<div class="artplayer-plugin-ads-loading"></div>') as HTMLElement
|
||||
append(loading, icons.loading)
|
||||
const timer = append(root, `<div class="artplayer-plugin-ads-timer">
|
||||
<div class="artplayer-plugin-ads-close"></div>
|
||||
<div class="artplayer-plugin-ads-countdown"></div>
|
||||
</div>`) as HTMLElement
|
||||
const close = query('.artplayer-plugin-ads-close', timer) as HTMLElement
|
||||
const countdown = query('.artplayer-plugin-ads-countdown', timer) as HTMLElement
|
||||
const control = append(root, `<div class="artplayer-plugin-ads-control">
|
||||
<div class="artplayer-plugin-ads-detail">${option.i18n.detail}</div>
|
||||
<div class="artplayer-plugin-ads-muted"></div>
|
||||
<div class="artplayer-plugin-ads-fullscreen"></div>
|
||||
</div>`) as HTMLElement
|
||||
const detail = query('.artplayer-plugin-ads-detail', control) as HTMLElement
|
||||
const muted = query('.artplayer-plugin-ads-muted', control) as HTMLElement
|
||||
const fullscreen = query('.artplayer-plugin-ads-fullscreen', control) as HTMLElement
|
||||
let canClose = option.playDuration <= 0
|
||||
|
||||
if (option.playDuration >= option.totalDuration)
|
||||
setStyle(close, 'display', 'none')
|
||||
if (!option.url)
|
||||
setStyle(detail, 'display', 'none')
|
||||
if (video) {
|
||||
append(muted, icons.volume)
|
||||
append(muted, icons.volumeClose)
|
||||
video.muted = Boolean(option.muted)
|
||||
syncMuted()
|
||||
}
|
||||
else {
|
||||
setStyle(muted, 'display', 'none')
|
||||
}
|
||||
append(fullscreen, icons.fullscreenOn)
|
||||
append(fullscreen, icons.fullscreenOff)
|
||||
|
||||
function syncMuted(): void {
|
||||
setStyle(icons.volume, 'display', video?.muted ? 'none' : 'inline-flex')
|
||||
setStyle(icons.volumeClose, 'display', video?.muted ? 'inline-flex' : 'none')
|
||||
}
|
||||
|
||||
function render(elapsed: number): void {
|
||||
const remaining = option.playDuration - elapsed
|
||||
canClose = elapsed === 0 ? option.playDuration <= 0 : remaining < 1 || Number.isNaN(remaining)
|
||||
close.innerHTML = canClose ? option.i18n.close : translate(remaining, option.i18n.canBeClosed)
|
||||
countdown.innerHTML = translate(option.totalDuration - elapsed, option.i18n.countdown)
|
||||
}
|
||||
render(0)
|
||||
|
||||
return {
|
||||
root,
|
||||
video,
|
||||
render,
|
||||
ready() {
|
||||
setStyle(timer, 'display', 'flex')
|
||||
setStyle(control, 'display', 'flex')
|
||||
setStyle(loading, 'display', 'none')
|
||||
},
|
||||
hide() { setStyle(root, 'display', 'none') },
|
||||
fullscreen(active: boolean) {
|
||||
setStyle(icons.fullscreenOn, 'display', active ? 'none' : 'inline-flex')
|
||||
setStyle(icons.fullscreenOff, 'display', active ? 'inline-flex' : 'none')
|
||||
},
|
||||
bind(events: Resources, callbacks: { skip: Cleanup, click: Cleanup, fullscreen: Cleanup }) {
|
||||
events.dom(close, 'click', () => {
|
||||
if (canClose)
|
||||
callbacks.skip()
|
||||
})
|
||||
events.dom(content, 'click', callbacks.click)
|
||||
if (option.url)
|
||||
events.dom(detail, 'click', callbacks.click)
|
||||
if (video) {
|
||||
events.dom(muted, 'click', () => {
|
||||
video.muted = !video.muted
|
||||
syncMuted()
|
||||
})
|
||||
}
|
||||
events.dom(fullscreen, 'click', callbacks.fullscreen)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export type View = ReturnType<typeof createView>
|
||||
Reference in new issue
Block a user