mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-17 10:01:20 -08:00
223 lines
6.5 KiB
JavaScript
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")
|
|
}
|
|
}
|