mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-20 04:01:20 -08:00
docs(vtt): [SITE-VTT-01] add bilingual guides and sprite pixel checks
This commit is contained in:
1 parent
d3349e83f2
commit
9ba3c82f99
71 files changed
+6207
-1683
No files matched your search
@@ -528,3 +528,7 @@ cleanup. Pixel readiness uses the existing polling deadline, not media time alon
|
||||
The first immediate-pixel WebKit failure remains in the refactor evidence.
|
||||
This is native desktop media playback, not physical audibility or full devices.
|
||||
`document-site.spec.js` also checks generated guide links and Run Code parameters.
|
||||
|
||||
## VTT Thumbnail documentation examples
|
||||
|
||||
yarn test:browser document-vtt.spec.js document-site.spec.js --workers=1 validates both generated guides and their Run Code destinations after yarn build:docs. The guide test requires the original demo verbatim, preserves the original demo video and substitutes local VTT/SVG resources, and inspects actual preview screenshot pixels with pngjs. It covers red/blue crops from real mouse hover, inclusive endpoint selection via public setBar with a MouseEvent, a gap, destroy and a late event on both cores. It loads local dist explicitly and records its hash; this does not replace the verified installed-artifact suite, physical touch tests or full playback acceptance.
|
||||
@@ -100,5 +100,16 @@ for (const language of ['zh', 'en']) {
|
||||
expect(audioTarget.searchParams.get('code')).toContain('artplayerPluginAudioTrack({')
|
||||
expect(failedAssets).toEqual([])
|
||||
await audioExample.close()
|
||||
await page.locator(`.VPSidebar a[href="${prefix}/plugin/vtt-thumbnail.html"]`).click()
|
||||
await expect(page.locator('h1')).toContainText(language === 'en' ? 'VTT Thumbnail' : 'VTT 缩略图')
|
||||
const vttPopup = context.waitForEvent('page')
|
||||
await page.locator('.run-code, [classname="run-code"]').first().click()
|
||||
const vttExample = await vttPopup
|
||||
await vttExample.waitForLoadState('domcontentloaded')
|
||||
const vttTarget = new URL(vttExample.url())
|
||||
expect(vttTarget.searchParams.get('libs')).toBe('./uncompiled/artplayer-plugin-vtt-thumbnail/index.js')
|
||||
expect(vttTarget.searchParams.get('code')).toContain('artplayerPluginVttThumbnail({')
|
||||
expect(failedAssets).toEqual([])
|
||||
await vttExample.close()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import { PNG } from 'pngjs'
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { extractExamples } from '../../scripts/docs-smoke/parser.ts'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const exampleSource = fs.readFileSync('docs/assets/example/vtt.thumbnail.js', 'utf8').trim()
|
||||
const plugin = fs.readFileSync('packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.js')
|
||||
const videoSHA256 = hash(fs.readFileSync('docs/assets/sample/bbb-video.mp4'))
|
||||
const documents = ['plugin/vtt-thumbnail.md', 'en/plugin/vtt-thumbnail.md']
|
||||
// Whole-second boundaries and a final gap are deliberate public-contract cases.
|
||||
const cues = 'WEBVTT\n\n00:00.000 --> 05:00.000\nvtt-doc-sprite.svg#xywh=0,0,80,45\n\n05:00.000 --> 08:20.000\nvtt-doc-sprite.svg#xywh=80,0,80,45\n'
|
||||
const sprite = '<svg xmlns="http://www.w3.org/2000/svg" width="160" height="45"><path fill="red" d="M0 0h80v45H0z"/><path fill="blue" d="M80 0h80v45H80z"/></svg>'
|
||||
for (const document of documents) {
|
||||
const examples = extractExamples(fs.readFileSync(`packages/artplayer-vitepress/docs/${document}`, 'utf8'), document)
|
||||
assert.equal(examples.length, 1)
|
||||
assert.equal(examples[0].code, exampleSource, 'Both VTT guides must execute the actual demo source')
|
||||
}
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
test(`documented VTT: visible sprite crops, boundaries, gaps and cleanup with ${core} core`, async ({ page }, testInfo) => {
|
||||
await page.route('**/assets/sample/bbb-thumbnails.vtt', route => route.fulfill({ contentType: 'text/vtt', body: cues }))
|
||||
const imageRequests = []
|
||||
await page.route('**/assets/sample/vtt-doc-sprite.svg', (route) => {
|
||||
imageRequests.push(route.request().url())
|
||||
return route.fulfill({ contentType: 'image/svg+xml', body: sprite })
|
||||
})
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
await page.evaluate(() => document.querySelector('.player').classList.add('artplayer-app'))
|
||||
await page.addScriptTag({ content: plugin.toString() })
|
||||
await page.addScriptTag({ content: `${exampleSource}\nwindow.art = art;` })
|
||||
const preview = page.locator('.art-control-vtt-thumbnail')
|
||||
const observed = []
|
||||
try {
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady && window.art.plugins.artplayerPluginVttThumbnail?.name)).toBe('artplayerPluginVttThumbnail')
|
||||
await expect.poll(() => page.evaluate(() => window.art.duration)).toBeCloseTo(596.458, 2)
|
||||
const progress = page.locator('.art-control-progress')
|
||||
const bounds = await progress.boundingBox()
|
||||
async function verifyPixels(name, color) {
|
||||
await expect(preview).toHaveCSS('display', 'flex')
|
||||
await expect(preview).toHaveCSS('width', '80px')
|
||||
await expect(preview).toHaveCSS('height', '45px')
|
||||
let screenshot
|
||||
let pixel
|
||||
await expect.poll(async () => {
|
||||
screenshot = await preview.screenshot()
|
||||
const png = PNG.sync.read(screenshot)
|
||||
const offset = (Math.floor(png.height / 2) * png.width + Math.floor(png.width / 2)) * 4
|
||||
pixel = [...png.data.subarray(offset, offset + 4)]
|
||||
return pixel
|
||||
}).toEqual(color)
|
||||
observed.push({ name, pixel })
|
||||
await testInfo.attach(name, { contentType: 'image/png', body: screenshot })
|
||||
}
|
||||
await page.mouse.move(bounds.x + bounds.width * 0.25, bounds.y + bounds.height / 2)
|
||||
await verifyPixels('first-red-crop', [255, 0, 0, 255])
|
||||
await page.mouse.move(bounds.x + bounds.width * 0.625, bounds.y + bounds.height / 2)
|
||||
await verifyPixels('second-blue-crop', [0, 0, 255, 255])
|
||||
expect(imageRequests.length).toBeGreaterThan(0)
|
||||
// Exact endpoints use the public event: mouse coordinates round to device pixels.
|
||||
await page.evaluate(() => window.art.emit('setBar', 'hover', 300 / window.art.duration, new MouseEvent('mousemove')))
|
||||
await verifyPixels('shared-boundary-first-crop', [255, 0, 0, 255])
|
||||
await page.mouse.move(bounds.x + bounds.width * 0.875, bounds.y + bounds.height / 2)
|
||||
await expect(preview).toHaveCSS('display', 'none')
|
||||
await page.evaluate(() => window.art.destroy(false))
|
||||
await expect(preview).toHaveCount(0)
|
||||
await page.evaluate(() => window.art.emit('setBar', 'hover', 0.25, new MouseEvent('mousemove')))
|
||||
await expect(preview).toHaveCount(0)
|
||||
}
|
||||
finally {
|
||||
await testInfo.attach('documented-vtt-evidence', { contentType: 'application/json', body: JSON.stringify({ documents, core, pluginSHA256: hash(plugin), sourceSHA256: hash(exampleSource), videoSHA256, cuesSHA256: hash(cues), spriteSHA256: hash(sprite), imageRequests, observed, scope: 'Original demo code and video with controlled local VTT/SVG; real mouse and screenshot pixels, exact endpoint via public setBar with MouseEvent; not physical touch or full video playback acceptance' }) })
|
||||
await page.evaluate(() => {
|
||||
if (!window.art.isDestroy)
|
||||
window.art.destroy()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user