Files
ArtPlayer/packages/artplayer-plugin-libass/src/adapter.js
T

223 lines
6.5 KiB
JavaScript

import SubtitlesOctopus from "libass-wasm"
const defaultAssSubtitle = `[Script Info]\nScriptType: v4.00+`
export default class LibassAdapter {
constructor(art, option) {
const { constructor, template } = art
this.art = art
this.$video = template.$video
this.$webvtt = template.$subtitle
this.utils = constructor.utils
this.libass = null
art.once("ready", this.init.bind(this, option))
}
async init(option) {
this.#checkWebAssemblySupport()
await this.#createLibass(option)
this.#addEventListeners()
this.art.emit("artplayerPluginLibass:init", this)
// set initial subtitle
const initialSubtitle = this.art?.option?.subtitle?.url
if (initialSubtitle && this.utils.getExt(initialSubtitle) === "ass") {
this.switch(initialSubtitle)
}
}
switch(url) {
this.art.emit("artplayerPluginLibass:switch", url)
if (url && this.utils.getExt(url) === "ass") {
this.currentType = "ass"
this.libass.freeTrack()
this.libass.setTrackByUrl(this.#toAbsoluteUrl(url))
this.visible = this.art.subtitle.show
} else {
this.currentType = "webvtt"
this.hide()
this.libass.freeTrack()
}
}
setVisibility(visible) {
this.visible = visible
}
setOffset(offset) {
this.timeOffset = offset
}
get active() {
return this.currentType === 'ass'
}
get visible() {
if (!this.libass)
return false
return this.libass.canvasParent.style.display !== "none"
}
set visible(visible) {
this.art.emit("artplayerPluginLibass:visible", visible)
this.#setVttVisible(!this.active)
if (this.libass.canvasParent) {
this.libass.canvasParent.style.display = visible ? "block" : "none"
if (visible) this.libass.resize()
}
}
get timeOffset() {
return this.libass.timeOffset
}
set timeOffset(offset) {
this.art.emit("artplayerPluginLibass:timeOffset", offset)
this.libass.timeOffset = offset
}
show() {
this.visible = true
}
hide() {
this.visible = false
}
destroy() {
this.art.emit("artplayerPluginLibass:destroy")
this.#removeEventListeners()
this.libass.dispose()
URL.revokeObjectURL(this.workerScriptUrl)
this.libass = null
}
async #createLibass(option = {}) {
if (!option.fallbackFont) {
return this.utils.errorHandle(option.fallbackFont, 'artplayerPluginLibass: fallbackFont is required')
}
if (!option.workerUrl) {
option.workerUrl = 'https://cdnjs.cloudflare.com/ajax/libs/libass-wasm/4.1.0/js/subtitles-octopus-worker.js'
}
if (option.availableFonts) {
option.availableFonts = Object
.entries(option.availableFonts)
.reduce((acc, [key, value]) => {
acc[key] = this.#toAbsoluteUrl(value)
return acc
}, {})
}
this.libass = new SubtitlesOctopus({
subContent: defaultAssSubtitle,
video: this.$video,
...option,
workerUrl: await this.#loadWorker(option),
fallbackFont: this.#toAbsoluteUrl(option.fallbackFont),
fonts: option.fonts?.map((font) => this.#toAbsoluteUrl(font)),
})
this.libass.canvasParent.className = "artplayer-plugin-libass"
this.libass.canvasParent.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
user-select: none;
pointer-events: none;
z-index: 20;`
}
#addEventListeners() {
this.switchHandler = this.switch.bind(this)
this.visibleHandler = this.setVisibility.bind(this)
this.offsetHandler = this.setOffset.bind(this)
this.art.on("subtitle", this.visibleHandler)
this.art.on('subtitleLoad', this.switchHandler)
this.art.on("subtitleOffset", this.offsetHandler)
this.art.once("destroy", this.destroy.bind(this))
}
#removeEventListeners() {
this.art.off("subtitle", this.visibleHandler)
this.art.off('subtitleLoad', this.switchHandler)
this.art.off("subtitleOffset", this.offsetHandler)
}
#setVttVisible(visible) {
this.$webvtt.style.visibility = visible ? 'visible' : 'hidden'
}
#toAbsoluteUrl(url) {
if (this.#isAbsoluteUrl(url))
return url
// handle absolute URL when the `Worker` of `BLOB` type loading network resources
return new URL(url, document.baseURI).toString()
}
#isAbsoluteUrl(url) {
return /^https?:\/\//.test(url)
}
#loadWorker({ workerUrl, wasmUrl }) {
return new Promise((resolve) => {
fetch(workerUrl)
.then(res => res.text())
.then(text => {
let workerScriptContent = text
workerScriptContent = workerScriptContent.replace(
/wasmBinaryFile\s*=\s*"(subtitles-octopus-worker\.wasm)"/g,
(_match, wasm) => {
if (!wasmUrl) {
wasmUrl = new URL(wasm, workerUrl).toString()
} else {
wasmUrl = this.#toAbsoluteUrl(wasmUrl)
}
return `wasmBinaryFile = "${wasmUrl}"`
}
)
const workerBlob = new Blob([workerScriptContent], { type: "text/javascript" })
resolve(URL.createObjectURL(workerBlob))
})
})
}
#checkWebAssemblySupport() {
let supportsWebAssembly = false
try {
if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") {
const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00))
if (module instanceof WebAssembly.Module)
supportsWebAssembly = (new WebAssembly.Instance(module) instanceof WebAssembly.Instance)
}
} catch (e) {
//
}
this.utils.errorHandle(supportsWebAssembly, "Browser does not support WebAssembly")
}
}