Files
ArtPlayer/packages/artplayer-plugin-vast/src/session.ts
T

188 lines
5.4 KiB
TypeScript

import type { ImaSdk } from '@alugha/ima'
import type Artplayer from 'artplayer'
import type { Context, RequestConfig, Utilities } from './types'
import { createRequest, Player, PlayerOptions } from './sdk'
import { createContainer } from './view'
interface Owner {
container: HTMLDivElement
player: Player | null
listeners: [string, EventListener][]
playing: boolean
released: boolean
}
export function createSession(art: Artplayer, ima: ImaSdk, utils: Utilities, closed: () => boolean, workspaceMode: boolean) {
const { $video, $player } = art.template
const adsRenderingSettings = new ima.AdsRenderingSettings()
if (workspaceMode) {
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true
adsRenderingSettings.enablePreloading = true
}
const playerOptions = new PlayerOptions()
let current: Owner | null = null
let initializing = false
let releasing = 0
const context: Context = {
art,
playUrl: (url, config = {}) => play('adTagUrl', url, config),
playRes: (response, config = {}) => play('adsResponse', response, config),
init,
ima,
adsRenderingSettings,
playerOptions,
imaPlayer: null,
id: null,
$container: null,
get container() { return current?.container || null },
}
if (workspaceMode) {
Object.defineProperties(context, {
imaPlayer: { get: () => current?.player || null },
id: { get: () => current?.container.id || null },
$container: { get: () => current?.container || null },
})
}
const live = (owner: Owner) => current === owner && !owner.released && !closed()
function release(owner: Owner): void {
if (current === owner)
current = null
owner.released = true
owner.playing = false
const player = owner.player
owner.player = null
const listeners = owner.listeners.splice(0)
let failed = false
let failure: unknown
const attempt = (cleanup: () => void) => {
try {
cleanup()
}
catch (error) {
if (!failed) {
failed = true
failure = error
}
else {
console.error('VAST cleanup error:', error)
}
}
}
releasing++
try {
if (player) {
for (const [name, callback] of listeners)
attempt(() => player.removeEventListener(name, callback))
attempt(() => player.destroy())
}
attempt(() => {
if (owner.container.parentNode)
owner.container.parentNode.removeChild(owner.container)
})
}
finally {
releasing--
}
if (failed)
throw failure
}
function init(): Player | null {
if (closed() || releasing)
return null
if (initializing)
throw new Error('VAST initialization is already in progress')
if (current?.player)
return current.player
initializing = true
let owner: Owner | undefined
try {
owner = { container: createContainer(utils, workspaceMode), player: null, listeners: [], playing: false, released: false }
current = owner
if (!live(owner)) {
release(owner)
return null
}
$player.appendChild(owner.container)
if (!live(owner)) {
release(owner)
return null
}
owner.player = new Player(ima, $video, owner.container, adsRenderingSettings, playerOptions)
if (!live(owner)) {
release(owner)
return null
}
const allocated = owner
const events = workspaceMode
? [
['AdContentPauseRequested', true],
['AdContentResumeRequested', false],
['AdStarted', true],
['AdError', false],
] as const
: []
for (const [name, playing] of events) {
const callback: EventListener = (event) => {
if (!live(allocated))
return
if (name === 'AdError')
console.error('VAST Ad Error:', (event as CustomEvent<unknown>).detail)
allocated.playing = playing
allocated.container.style.display = playing ? 'block' : 'none'
}
owner.listeners.push([name, callback])
owner.player.addEventListener(name, callback)
if (!live(owner)) {
release(owner)
return null
}
}
if (!workspaceMode) {
// Published fields are writable data properties, not the workspace getters.
Object.defineProperties(context, {
imaPlayer: { value: owner.player },
id: { value: owner.container.id },
$container: { value: owner.container },
})
}
return owner.player
}
catch (error) {
if (owner) {
try {
release(owner)
}
catch (cleanupError) { console.error('VAST cleanup error:', cleanupError) }
}
throw error
}
finally {
initializing = false
}
}
function play(field: 'adTagUrl' | 'adsResponse', value: string, config: RequestConfig): void {
if (closed() || (workspaceMode && current?.playing))
return
const player = init()
const owner = current
if (!player || !owner)
return
const request = createRequest(ima, field, value, config)
// Request constructors and config accessors may synchronously destroy or replace the session.
if (live(owner) && owner.player === player)
player.playAds(request)
}
function destroy(): void {
if (current)
release(current)
}
return { context, destroy }
}