refactor(multiple-subtitles): [PKG-MULTI-SUB-03] own requests and subtitle resources

This commit is contained in:
Harvey Zhao committed 2026-09-13 11:01:47 +08:00
1 parent 36cd151095
commit beaa93d6cc
25 files changed
+7908 -250

No files matched your search

@@ -0,0 +1,169 @@
import { hash } from '../../refactor/scripts/releases.mjs'
import { multipleSubtitlesCandidate } from '../helpers/multiple-subtitles.js'
import { expect, test } from './fixtures.js'
const implementation = await multipleSubtitlesCandidate()
const vtt = 'WEBVTT\n\n00:00.000 --> 00:08.000\nCached caption\n'
const srt = text => `1\n00:00:00,000 --> 00:00:08,000\n${text}\n`
async function prepare(page, core) {
await page.goto(`/test/player.html?core=${core}`)
await page.addScriptTag({ content: implementation.code })
await page.evaluate(() => {
window.createPlayer('/test/pattern.mp4')
window.multipleProbe = { initialized: [], revoked: [], signal: null, errors: [], requests: [] }
const fetch = window.fetch.bind(window)
window.multipleNativeFetch = fetch
window.fetch = (url, options) => {
if (typeof url === 'string' && url.startsWith('/test/multiple-'))
window.multipleProbe.requests.push(url)
if (url === '/test/multiple-held.vtt')
window.multipleProbe.signal = options?.signal
return fetch(url, options)
}
const revoke = URL.revokeObjectURL.bind(URL)
URL.revokeObjectURL = (url) => {
window.multipleProbe.revoked.push(url)
revoke(url)
}
const init = window.art.subtitle.init.bind(window.art.subtitle)
window.art.subtitle.init = (option) => {
window.multipleProbe.initialized.push({ url: option.url, destroyed: window.art.isDestroy })
const result = init(option)
Promise.resolve(result).catch(error => window.multipleProbe.errors.push({ name: error.name, message: error.message }))
return result
}
})
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
}
async function evidence(page, testInfo, core, observation) {
const probe = await page.evaluate(() => ({ ...window.multipleProbe, signal: window.multipleProbe.signal ? { aborted: window.multipleProbe.signal.aborted } : null }))
expect(probe.errors).toEqual([])
await testInfo.attach('multiple-subtitles-lifecycle', { body: JSON.stringify({ core, implementation: { name: implementation.name, sha256: hash(implementation.code) }, observation, probe }), contentType: 'application/json' })
}
for (const core of ['published', 'candidate']) {
test(`Multiple subtitles candidate ${core} core: SRT display and selectors survive the resource refactor`, async ({ page }, testInfo) => {
await page.route('**/test/multiple-en.srt', route => route.fulfill({ body: srt('English'), contentType: 'text/plain' }))
await page.route('**/test/multiple-jp.srt', route => route.fulfill({ body: srt('日本語'), contentType: 'text/plain' }))
await prepare(page, core)
await page.evaluate(async () => {
await window.art.plugins.add(window.artplayerPluginMultipleSubtitles({ subtitles: [{ name: 'en', url: '/test/multiple-en.srt' }, { name: 'jp', url: '/test/multiple-jp.srt' }] }))
})
await expect.poll(() => page.evaluate(() => window.art.subtitle.cues.length)).toBe(2)
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.1)
await page.locator('#pause').click()
await expect(page.locator('.art-subtitle-en')).toHaveText('English')
await expect(page.locator('.art-subtitle-jp')).toHaveText('日本語')
await page.evaluate(() => window.art.plugins.multipleSubtitles.tracks(['jp']))
await expect(page.locator('.art-subtitle-en')).toHaveCount(0)
await expect(page.locator('.art-subtitle-jp')).toHaveText('日本語')
await page.evaluate(() => window.art.plugins.multipleSubtitles.tracks([]))
await expect(page.locator('.art-subtitle-jp')).toHaveCount(0)
await page.evaluate(() => window.art.plugins.multipleSubtitles.reset())
await expect(page.locator('.art-subtitle-en')).toHaveText('English')
await expect(page.locator('.art-subtitle-jp')).toHaveText('日本語')
const released = await page.evaluate(() => {
window.art.destroy(false)
return window.multipleProbe.initialized.every(item => window.multipleProbe.revoked.includes(item.url))
})
expect(released).toBe(true)
await evidence(page, testInfo, core, { nativeSrtDisplay: true, released })
})
test(`Multiple subtitles candidate ${core} core: destroy settles a pending native request before it responds`, async ({ page }, testInfo) => {
let requested
let release
const started = new Promise((resolve) => {
requested = resolve
})
const held = new Promise((resolve) => {
release = resolve
})
await page.route('**/test/multiple-held.vtt', async (route) => {
requested()
await held
await route.fulfill({ body: vtt, contentType: 'text/vtt' })
})
try {
await prepare(page, core)
await page.evaluate(() => {
window.multiplePending = window.artplayerPluginMultipleSubtitles({ subtitles: [{ url: '/test/multiple-held.vtt', name: 'held' }] })(window.art)
})
await started
const state = await page.evaluate(async () => {
window.art.destroy(false)
const result = await window.multiplePending
return { name: result.name, voidReset: result.reset() === undefined, initialized: window.multipleProbe.initialized.length, aborted: window.multipleProbe.signal.aborted }
})
expect(state).toEqual({ name: 'multipleSubtitles', voidReset: true, initialized: 0, aborted: true })
release()
await evidence(page, testInfo, core, state)
}
finally { release() }
})
test(`Multiple subtitles candidate ${core} core: destroy revokes the Blob and retained reset stays inert`, async ({ page }, testInfo) => {
await page.route('**/test/multiple-ready.vtt', route => route.fulfill({ body: vtt, contentType: 'text/vtt' }))
await prepare(page, core)
await page.evaluate(async () => {
window.multipleResult = await window.artplayerPluginMultipleSubtitles({ subtitles: [{ url: '/test/multiple-ready.vtt', name: 'ready' }] })(window.art)
})
await expect.poll(() => page.evaluate(() => window.art.subtitle.cues.length)).toBe(1)
const state = await page.evaluate(async () => {
const url = window.multipleProbe.initialized[0].url
window.art.destroy(false)
const voidReset = window.multipleResult.reset() === undefined
let inaccessible = false
try {
await window.multipleNativeFetch(url)
}
catch (error) {
inaccessible = error.name === 'TypeError'
}
return { voidReset, inaccessible, initialized: window.multipleProbe.initialized.length, revoked: window.multipleProbe.revoked.includes(url) }
})
expect(state).toEqual({ voidReset: true, inaccessible: true, initialized: 1, revoked: true })
await evidence(page, testInfo, core, state)
})
test(`Multiple subtitles candidate ${core} core: native HTTP error rejects without installing subtitles`, async ({ page }, testInfo) => {
await page.route('**/test/multiple-error.vtt', route => route.fulfill({ status: 404, body: vtt, contentType: 'text/vtt' }))
await prepare(page, core)
const state = await page.evaluate(async () => {
let message
try {
await window.artplayerPluginMultipleSubtitles({ subtitles: [{ url: '/test/multiple-error.vtt', name: 'error' }] })(window.art)
}
catch (error) {
message = error.message
}
window.art.destroy(false)
return { message, initialized: window.multipleProbe.initialized.length }
})
expect(state).toEqual({ message: 'Failed to fetch multiple subtitles: HTTP 404', initialized: 0 })
await evidence(page, testInfo, core, state)
})
test(`Multiple subtitles candidate ${core} core: video source switching retains cached subtitle selection`, async ({ page }, testInfo) => {
await page.route('**/test/multiple-restart.vtt', route => route.fulfill({ body: vtt, contentType: 'text/vtt' }))
await prepare(page, core)
await page.evaluate(async () => {
await window.art.plugins.add(window.artplayerPluginMultipleSubtitles({ subtitles: [{ url: '/test/multiple-restart.vtt', name: 'restart' }] }))
})
await expect.poll(() => page.evaluate(() => window.art.subtitle.cues.length)).toBe(1)
await page.evaluate(() => window.art.switchUrl('/test/pattern.mp4?multiple-restart=1'))
await page.evaluate(() => window.art.plugins.multipleSubtitles.reset())
await expect.poll(() => page.evaluate(() => window.art.subtitle.cues.length)).toBe(1)
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.1)
await page.locator('#pause').click()
await expect(page.locator('.art-subtitle-restart')).toHaveText('Cached caption')
const requests = await page.evaluate(() => window.multipleProbe.requests)
expect(requests).toEqual(['/test/multiple-restart.vtt'])
await page.evaluate(() => window.art.destroy(false))
await evidence(page, testInfo, core, { requests, nativeSourceSwitch: true })
})
}
+5 -3
View File
@@ -28,7 +28,7 @@ export async function multipleSubtitlesHistorical() {
return implementations
}
export function multipleSubtitlesEnvironment(implementation, { script = false, responses = {}, fetchResponse, coreUtils, onInit, onCreate } = {}) {
export function multipleSubtitlesEnvironment(implementation, { script = false, responses = {}, fetchResponse, coreUtils, onInit, onCreate, abortController = true } = {}) {
const requests = []
const requestOptions = []
const blobs = new Map()
@@ -38,6 +38,7 @@ export function multipleSubtitlesEnvironment(implementation, { script = false, r
const initStates = []
const converted = []
const listeners = new Map()
const warnings = []
const module = { exports: {} }
const utils = {
getExt: url => url.split('.').pop(),
@@ -80,7 +81,8 @@ export function multipleSubtitlesEnvironment(implementation, { script = false, r
const context = {
Blob,
TextDecoder,
console: { log() {}, warn() {}, error() {} },
AbortController: abortController ? AbortController : undefined,
console: { log() {}, warn(...args) { warnings.push(args) }, error() {} },
URL: {
createObjectURL(blob) {
onCreate?.(blob)
@@ -115,5 +117,5 @@ export function multipleSubtitlesEnvironment(implementation, { script = false, r
for (const callback of [...(listeners.get(name) || [])])
callback(...args)
}
return { factory, exported, art, utils, requests, requestOptions, blobs, liveBlobs, revoked, initialized, initStates, converted, listeners, emit, responseFor, latestText }
return { factory, exported, art, utils, requests, requestOptions, blobs, liveBlobs, revoked, initialized, initStates, converted, listeners, warnings, emit, responseFor, latestText }
}
+319
View File
@@ -0,0 +1,319 @@
import assert from 'node:assert/strict'
// eslint-disable-next-line test/no-import-node-test -- Lifecycle regressions use the repository Node runner.
import test from 'node:test'
import { multipleSubtitlesCandidate, multipleSubtitlesEnvironment, subtitleVtt } from './helpers/multiple-subtitles.js'
const candidate = await multipleSubtitlesCandidate()
const create = options => multipleSubtitlesEnvironment(candidate, options)
const options = { subtitles: [{ url: 'a.vtt', name: 'a' }] }
function deferred() {
let resolve
let reject
const promise = new Promise((yes, no) => {
resolve = yes
reject = no
})
return { promise, resolve, reject }
}
async function flush() {
for (let index = 0; index < 20; index++)
await Promise.resolve()
}
function empty(env) {
assert.equal(env.liveBlobs.size, 0)
assert.equal(env.listeners.get('destroy')?.size || 0, 0)
}
for (const abortController of [true, false]) {
for (const phase of ['fetch', 'body']) {
test(`Multiple subtitles candidate: destroy cancels ${phase}, AbortController=${abortController}`, async () => {
const held = deferred()
const started = deferred()
const env = create({ abortController, fetchResponse: (_url, _options, responseFor) => {
if (phase === 'fetch') {
started.resolve()
return held.promise
}
return { ...responseFor('a.vtt'), arrayBuffer() {
started.resolve()
return held.promise
} }
} })
const pending = env.factory(options)(env.art)
await started.promise
env.emit('destroy')
const result = await pending
assert.equal(result.name, 'multipleSubtitles')
assert.equal(result.reset(), undefined)
assert.equal(result.tracks(['missing']), undefined)
assert.equal(env.requestOptions[0]?.signal.aborted, abortController ? true : undefined)
assert.equal(env.initialized.length, 0)
empty(env)
held.reject(new Error('late rejected operation'))
await flush()
assert.equal(env.initialized.length, 0)
assert.equal(env.warnings.length, 0)
})
}
}
test('Multiple subtitles candidate: already destroyed hosts do not request or subscribe', async () => {
const env = create()
env.emit('destroy')
const result = await env.factory(options)(env.art)
result.reset()
assert.equal(env.requests.length, 0)
assert.equal(env.initialized.length, 0)
empty(env)
})
test('Multiple subtitles candidate: failed request preserves error identity and cancels a sibling body', async () => {
const failed = deferred()
const body = deferred()
const started = deferred()
const error = new Error('network failed')
const env = create({ fetchResponse: url => url === 'a.vtt'
? failed.promise
: { ok: true, arrayBuffer() {
started.resolve()
return body.promise
} } })
const pending = env.factory({ subtitles: [...options.subtitles, { url: 'b.vtt', name: 'b' }] })(env.art)
await started.promise
failed.reject(error)
await assert.rejects(pending, caught => caught === error)
assert.equal(env.requestOptions[1].signal.aborted, true)
body.resolve(new TextEncoder().encode(subtitleVtt('late')).buffer)
await flush()
assert.equal(env.initialized.length, 0)
empty(env)
})
test('Multiple subtitles candidate: HTTP failure rejects without reading or installing its body', async () => {
let read = false
const env = create({ fetchResponse: () => ({ ok: false, status: 404, arrayBuffer() {
read = true
} }) })
await assert.rejects(env.factory(options)(env.art), { message: 'Failed to fetch multiple subtitles: HTTP 404' })
assert.equal(read, false)
assert.equal(env.requestOptions[0].signal.aborted, true)
assert.equal(env.initialized.length, 0)
empty(env)
})
test('Multiple subtitles candidate: body failure and bad encoding release registration resources', async () => {
const error = new Error('body failed')
const env = create({ fetchResponse: () => ({ ok: true, arrayBuffer: () => Promise.reject(error) }) })
await assert.rejects(env.factory(options)(env.art), caught => caught === error)
empty(env)
const invalid = create()
await assert.rejects(invalid.factory({ subtitles: [{ ...options.subtitles[0], encoding: 'invalid-label' }] })(invalid.art), { name: 'RangeError' })
empty(invalid)
})
test('Multiple subtitles candidate: destroy releases the last URL and makes retained methods inert', async () => {
const env = create()
const result = await env.factory(options)(env.art)
env.emit('destroy')
env.emit('destroy')
assert.equal(result.reset(), undefined)
assert.equal(result.tracks(), undefined)
assert.deepEqual(env.revoked, ['blob:subtitle-1'])
assert.equal(env.initialized.length, 1)
empty(env)
})
test('Multiple subtitles candidate: allocation failure preserves the old URL and reset recovers', async () => {
let calls = 0
const error = new Error('allocation failed')
const env = create({ onCreate() {
if (++calls === 2)
throw error
} })
const result = await env.factory(options)(env.art)
assert.throws(() => result.reset(), caught => caught === error)
assert.equal(env.liveBlobs.has('blob:subtitle-1'), true)
assert.equal(env.revoked.length, 0)
result.reset()
assert.deepEqual([...env.liveBlobs.keys()], ['blob:subtitle-2'])
env.emit('destroy')
empty(env)
})
test('Multiple subtitles candidate: synchronous initial host failure rolls back escape and frees the new URL', async () => {
const error = new Error('init failed')
const env = create({ onInit() {
throw error
} })
await assert.rejects(env.factory(options)(env.art), caught => caught === error)
assert.equal(env.art.option.subtitle.escape, true)
empty(env)
})
test('Multiple subtitles candidate: a failed synchronous replacement retains the previous resource', async () => {
let calls = 0
const error = new Error('replacement failed')
const env = create({ onInit() {
if (++calls === 2)
throw error
} })
const result = await env.factory(options)(env.art)
assert.throws(() => result.reset(), caught => caught === error)
assert.deepEqual([...env.liveBlobs.keys()], ['blob:subtitle-1'])
result.reset()
assert.deepEqual([...env.liveBlobs.keys()], ['blob:subtitle-3'])
env.emit('destroy')
empty(env)
})
test('Multiple subtitles candidate: host media loading does not delay registration or change void selection', async () => {
const held = deferred()
const env = create({ onInit: () => held.promise })
const result = await env.factory(options)(env.art)
assert.equal(result.name, 'multipleSubtitles')
assert.equal(result.reset(), undefined)
assert.equal(env.initialized.length, 2)
held.resolve()
await flush()
assert.deepEqual([...env.liveBlobs.keys()], ['blob:subtitle-2'])
env.emit('destroy')
empty(env)
})
test('Multiple subtitles candidate: asynchronous rejection is observed and only frees its own URL', async () => {
const operations = [deferred(), deferred()]
let calls = 0
const env = create({ onInit: () => operations[calls++].promise })
const result = await env.factory(options)(env.art)
result.reset()
operations[0].reject(new Error('stale failure'))
await flush()
assert.deepEqual([...env.liveBlobs.keys()], ['blob:subtitle-2'])
operations[1].reject(new Error('current failure'))
await flush()
assert.equal(env.liveBlobs.size, 0)
assert.equal(env.warnings.length, 2)
env.emit('destroy')
empty(env)
})
test('Multiple subtitles candidate: destroy in host init does not install resources again', async () => {
const env = create({ onInit() {
env.emit('destroy')
} })
const result = await env.factory(options)(env.art)
result.reset()
assert.equal(env.initialized.length, 1)
assert.deepEqual(env.revoked, ['blob:subtitle-1'])
empty(env)
})
test('Multiple subtitles candidate: reentrant selection keeps only the newest resource', async () => {
let result
let reenter = false
const env = create({ onInit() {
if (reenter) {
reenter = false
result.tracks([])
}
} })
result = await env.factory(options)(env.art)
reenter = true
result.reset()
assert.deepEqual([...env.liveBlobs.keys()], ['blob:subtitle-3'])
assert.equal(await env.latestText(), 'WEBVTT\n\n')
env.emit('destroy')
empty(env)
})
test('Multiple subtitles candidate: unknown names throw before allocation and do not corrupt later resets', async () => {
const env = create()
const result = await env.factory(options)(env.art)
assert.throws(() => result.tracks(['missing']), { name: 'TypeError' })
assert.deepEqual([...env.liveBlobs.keys()], ['blob:subtitle-1'])
result.reset()
assert.equal((env.utils.unescape(await env.latestText()).match(/<div/g) || []).length, 1)
env.emit('destroy')
empty(env)
})
test('Multiple subtitles candidate: destroy during allocation releases the returned URL immediately', async () => {
const env = create({ onCreate() {
env.emit('destroy')
} })
await env.factory(options)(env.art)
assert.equal(env.initialized.length, 0)
empty(env)
})
test('Multiple subtitles candidate: throwing live configuration preserves the old resource', async () => {
const env = create()
const result = await env.factory(options)(env.art)
const old = env.art.option.subtitle
const error = new Error('configuration getter failed')
Object.defineProperty(env.art.option, 'subtitle', { configurable: true, get() {
throw error
} })
assert.throws(() => result.reset(), caught => caught === error)
assert.deepEqual([...env.liveBlobs.keys()], ['blob:subtitle-1'])
Object.defineProperty(env.art.option, 'subtitle', { value: old })
result.reset()
env.emit('destroy')
empty(env)
})
test('Multiple subtitles candidate: destroy from live configuration stops the following init', async () => {
const env = create()
const result = await env.factory(options)(env.art)
Object.defineProperty(env.art.option.subtitle, 'style', { enumerable: true, get() {
env.emit('destroy')
return {}
} })
result.reset()
assert.equal(env.initialized.length, 1)
empty(env)
})
test('Multiple subtitles candidate: reentrant successful selection survives the outer host error', async () => {
let result
let reenter = false
const error = new Error('outer init failed')
const env = create({ onInit() {
if (reenter) {
reenter = false
result.tracks([])
throw error
}
} })
result = await env.factory(options)(env.art)
reenter = true
assert.throws(() => result.reset(), caught => caught === error)
assert.deepEqual([...env.liveBlobs.keys()], ['blob:subtitle-3'])
env.emit('destroy')
empty(env)
})
test('Multiple subtitles candidate: late host rejection after destroy is handled without another write', async () => {
const host = deferred()
const env = create({ onInit: () => host.promise })
await env.factory(options)(env.art)
env.emit('destroy')
host.reject(new Error('late host failure'))
await flush()
assert.equal(env.warnings.length, 0)
assert.equal(env.initialized.length, 1)
assert.deepEqual(env.revoked, ['blob:subtitle-1'])
empty(env)
})
test('Multiple subtitles candidate: destroy from a URL getter stops the request before dispatch', async () => {
const env = create()
const entry = { name: 'a', get url() {
env.emit('destroy')
return 'a.vtt'
} }
await env.factory({ subtitles: [entry] })(env.art)
assert.equal(env.requests.length, 0)
assert.equal(env.initialized.length, 0)
empty(env)
})
+76
View File
@@ -0,0 +1,76 @@
import assert from 'node:assert/strict'
import { Buffer } from 'node:buffer'
// eslint-disable-next-line test/no-import-node-test -- Merge and decoder regressions use the repository Node runner.
import test from 'node:test'
import { loadModules } from './helpers/load.js'
import { multipleSubtitlesCandidate, multipleSubtitlesEnvironment, multipleSubtitlesHistorical, subtitleVtt } from './helpers/multiple-subtitles.js'
const { parseTracks, serializeTracks, ...coreUtils } = await loadModules({
parseTracks: { file: 'packages/artplayer-plugin-multiple-subtitles/src/merge', name: 'parseTracks' },
serializeTracks: { file: 'packages/artplayer-plugin-multiple-subtitles/src/merge', name: 'serializeTracks' },
getExt: { file: 'packages/artplayer/src/utils/file', name: 'getExt' },
srtToVtt: { file: 'packages/artplayer/src/utils/subtitle', name: 'srtToVtt' },
assToVtt: { file: 'packages/artplayer/src/utils/subtitle', name: 'assToVtt' },
})
const candidate = await multipleSubtitlesCandidate()
const historical = (await multipleSubtitlesHistorical()).find(item => item.name === 'published-1.2.0-main')
function freeze(value) {
if (value && typeof value === 'object') {
Object.freeze(value)
for (const child of Object.values(value)) freeze(child)
}
return value
}
test('Multiple subtitles merge: repeated selection serializes frozen trees without accumulating wrappers', () => {
const trees = freeze(parseTracks([subtitleVtt('A'), subtitleVtt('B')], [{ name: 'a' }, { name: 'b' }]))
const original = JSON.stringify(trees)
const first = serializeTracks(trees)
serializeTracks([trees[1], trees[0], trees[1]])
assert.equal(serializeTracks(trees), first)
assert.equal(JSON.stringify(trees), original)
})
test('Multiple subtitles merge: actual released serializer output is preserved for text, markup and timing cases', async () => {
const samples = [
subtitleVtt('A & B < C > D'),
subtitleVtt('<b>bold</b> tail'),
subtitleVtt('<c.red>color</c> <i>italic</i>'),
subtitleVtt('<v Speaker>voice</v>'),
subtitleVtt('<ruby>text<rt>note</rt></ruby>'),
subtitleVtt('line one\nline two'),
'WEBVTT\n\n00:02.000 --> 00:05.000\nLater\n\n00:00.000 --> 00:03.000\nEarlier\n',
subtitleVtt('best effort').replace('WEBVTT', 'BAD HEADER'),
'WEBVTT\n\n',
]
for (const body of samples) {
const environments = [historical, candidate].map(implementation => multipleSubtitlesEnvironment(implementation, { responses: { 'a.vtt': body, 'b.vtt': subtitleVtt('second') } }))
const results = await Promise.all(environments.map(env => env.factory({ subtitles: [{ url: 'a.vtt', name: 'a' }, { url: 'b.vtt', name: 'b' }] })(env.art)))
for (const selection of [null, ['b', 'a', 'b'], [], ['a']]) {
if (selection)
results.forEach(result => result.tracks(selection))
assert.equal(await environments[1].latestText(), await environments[0].latestText(), body)
}
results.forEach(result => result.reset())
assert.equal(await environments[1].latestText(), await environments[0].latestText(), body)
environments[1].emit('destroy')
}
})
test('Multiple subtitles request: real core SRT/ASS conversion and encoded text retain released output', async () => {
const responses = {
'a.SRT?track=1': '1\n00:00:00,000 --> 00:00:02,000\nSRT text\n',
'b.ass': 'Dialogue: 0,0:00:00.00,0:00:02.00,Default,,0,0,0,,ASS text',
'c.vtt': Buffer.from(subtitleVtt('café'), 'latin1'),
'd.vtt': Buffer.from(subtitleVtt('日本語'), 'utf16le'),
}
const options = { subtitles: [{ url: 'a.SRT?track=1', name: 'srt' }, { url: 'b.ass', name: 'ass' }, { url: 'c.vtt', name: 'latin', encoding: 'windows-1252' }, { url: 'd.vtt', name: 'utf16', encoding: 'utf-16le' }] }
const old = multipleSubtitlesEnvironment(historical, { responses, coreUtils })
const current = multipleSubtitlesEnvironment(candidate, { responses, coreUtils })
await old.factory(options)(old.art)
await current.factory(options)(current.art)
const text = await current.latestText()
assert.equal(text, await old.latestText())
for (const value of ['SRT text', 'ASS text', 'café', '日本語']) assert(text.includes(value))
current.emit('destroy')
})
+2 -2
View File
@@ -64,11 +64,11 @@ for (const implementation of implementations) {
assert.equal(env.initialized[0].onVttLoad, env.utils.unescape)
assert.equal(env.initialized[0].style, env.art.option.subtitle.style)
assert.equal(env.initialized[0].type, 'vtt')
assert.deepEqual(env.revoked, [''])
assert.deepEqual(env.revoked, implementation.version === 'candidate' ? [] : [''])
env.art.option.subtitle.encoding = 'utf-16le'
result.reset()
assert.equal(env.initialized[1].encoding, 'utf-16le')
assert.deepEqual(env.revoked, ['', 'blob:subtitle-1'])
assert.deepEqual(env.revoked, implementation.version === 'candidate' ? ['blob:subtitle-1'] : ['', 'blob:subtitle-1'])
assert.equal(env.blobs.get('blob:subtitle-2').type, 'text/vtt')
})