mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-25 08:01:20 -08:00
feat(subtitle): add artplayer-plugin-libass
This commit is contained in:
1 parent
cf8cea8283
commit
08ec5716f9
17 files changed
+2450
No files matched your search
@@ -0,0 +1,222 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import LibassAdapter from './adapter'
|
||||
|
||||
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 artplayerPluginLibass@${artplayerPluginLibass.version}. Please update it to version Artplayer.js@5.x.x`,
|
||||
)
|
||||
}
|
||||
|
||||
export default function artplayerPluginLibass(option) {
|
||||
return (art) => {
|
||||
checkVersion(art)
|
||||
|
||||
let adapter = new LibassAdapter(art, option)
|
||||
|
||||
return {
|
||||
name: 'artplayerPluginLibass',
|
||||
libass: adapter.libass,
|
||||
visible: adapter.visible,
|
||||
init: adapter.init.bind(adapter),
|
||||
switch: adapter.switch.bind(adapter),
|
||||
show: adapter.show.bind(adapter),
|
||||
hide: adapter.hide.bind(adapter),
|
||||
destroy: adapter.destroy.bind(adapter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
artplayerPluginLibass.env = process.env.NODE_ENV
|
||||
artplayerPluginLibass.version = process.env.APP_VER
|
||||
artplayerPluginLibass.build = process.env.BUILD_DATE
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window['artplayerPluginLibass'] = artplayerPluginLibass
|
||||
}
|
||||
Reference in new issue
Block a user