mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
120 lines
7.8 KiB
JavaScript
120 lines
7.8 KiB
JavaScript
/* global Artplayer, artplayerPluginChapter */
|
|
(() => {
|
|
const report = {
|
|
kind: 'dom', environment: { userAgent: navigator.userAgent, language: navigator.language },
|
|
scripts: [...document.scripts].map(script => new URL(script.src).pathname),
|
|
checks: [], errors: [], unhandled: [], interactions: [], observations: {},
|
|
}
|
|
const status = document.querySelector('#status')
|
|
const check = (id, passed) => {
|
|
if (report.checks.some(item => item.id === id)) return
|
|
report.checks.push({ id, passed: Boolean(passed) })
|
|
if (!passed) throw new Error(id)
|
|
status.textContent = report.checks.map(item => item.id).join(' | ')
|
|
}
|
|
addEventListener('error', event => report.errors.push(event.message))
|
|
addEventListener('unhandledrejection', event => report.unhandled.push(String(event.reason)))
|
|
function tree(element) {
|
|
return {
|
|
tag: element.tagName.toLowerCase(),
|
|
attributes: Object.fromEntries([...element.attributes].filter(attr => /^(class|role|aria-.+|tabindex|data-tooltip|type)$/.test(attr.name)).map(attr => [attr.name, attr.value])),
|
|
children: element.tagName.toLowerCase() === 'svg' ? [] : [...element.children].map(tree),
|
|
}
|
|
}
|
|
function layout() {
|
|
const player = art.template.$player
|
|
const rect = player.getBoundingClientRect()
|
|
return {
|
|
width: rect.width, height: rect.height, scrollWidth: player.scrollWidth,
|
|
controls: [...player.querySelectorAll('.art-control')].map(el => {
|
|
const r = el.getBoundingClientRect()
|
|
return { class: el.className, width: Math.round(r.width * 100) / 100, withinPlayer: r.left >= rect.left - 1 && r.right <= rect.right + 1, display: getComputedStyle(el).display }
|
|
}),
|
|
}
|
|
}
|
|
const art = new Artplayer({
|
|
container: '.player', url: '/assets/sample/video.mp4', muted: true, lang: 'en',
|
|
setting: true, playbackRate: true, aspectRatio: true, fullscreenWeb: true, hotkey: true,
|
|
controls: [{ name: 'compat', position: 'right', html: 'Probe control', tooltip: 'Probe control', click(component, event) {
|
|
check('DOM.control-click', this === art && component === art.controls && event.isTrusted)
|
|
} }],
|
|
layers: [{ name: 'compat', html: '<label class="user-hook">User layer <input aria-label="Typing probe" value="abc"></label>', style: { top: '12px', left: '12px' } }],
|
|
settings: [{ name: 'compat-setting', html: 'Baseline selector', selector: [{ html: 'Low', default: true }, { html: 'High' }], onSelect(item, element, event) {
|
|
check('DOM.setting-select', this === art && item.html === 'High' && element.classList.contains('art-current') && event.isTrusted)
|
|
report.observations.selected = { html: item.html, defaults: item.$parent.selector.map(value => value.default) }
|
|
return item.html
|
|
} }],
|
|
plugins: [artplayerPluginChapter({ chapters: [{ start: 0, end: 10, title: 'First' }, { start: 10, end: Infinity, title: 'Second' }] })],
|
|
})
|
|
let ready = false
|
|
art.once('ready', () => {
|
|
check('DOM.version-template', Artplayer.version === '5.4.0' && art.query('.art-video') === art.video)
|
|
art.cssVar('--art-theme', 'rgb(1, 2, 3)')
|
|
check('DOM.user-style', getComputedStyle(art.query('.user-hook')).color === 'rgb(12, 34, 56)' && art.cssVar('--art-theme').trim() === 'rgb(1, 2, 3)')
|
|
const chapters = [...art.query('.art-chapters').children].map(el => ({ class: el.className, start: Number(el.dataset.start), end: Number(el.dataset.end), title: el.dataset.title }))
|
|
check('DOM.chapter-markup', chapters.length === 2 && chapters[0].title === 'First' && chapters[1].title === 'Second')
|
|
report.snapshot = {
|
|
template: Artplayer.html, tree: tree(art.template.$player),
|
|
cssVariables: [...new Set(Artplayer.STYLE.match(/--art-[\w-]+/g))].sort(),
|
|
cssClasses: [...new Set(Artplayer.STYLE.match(/\.art-[\w-]+/g))].sort(),
|
|
chapters,
|
|
controlAttributes: [...art.template.$player.querySelectorAll('.art-control')].map(el => ({ class: el.className, tag: el.tagName, tabIndex: el.tabIndex, role: el.getAttribute('role'), ariaLabel: el.getAttribute('aria-label'), tooltip: el.getAttribute('data-tooltip'), title: el.getAttribute('title') })),
|
|
chapterStylePresent: Boolean(document.getElementById('artplayer-plugin-chapter')),
|
|
}
|
|
report.observations.wide = layout()
|
|
ready = true
|
|
})
|
|
let beforeKey
|
|
let hotkeys = 0
|
|
document.addEventListener('keydown', event => {
|
|
beforeKey = { time: art.currentTime, hotkeys, focused: art.isFocus, target: event.target.tagName, code: event.code, trusted: event.isTrusted }
|
|
}, true)
|
|
art.on('hotkey', () => hotkeys++)
|
|
document.addEventListener('keydown', event => {
|
|
if (!ready) return
|
|
const after = { ...beforeKey, afterTime: art.currentTime, afterHotkeys: hotkeys, activeTag: document.activeElement.tagName, activeLabel: document.activeElement.getAttribute('aria-label') }
|
|
report.interactions.push(after)
|
|
if (event.code === 'ArrowRight' && beforeKey.target !== 'INPUT') check('DOM.keyboard-seek', beforeKey.trusted && beforeKey.focused && hotkeys === beforeKey.hotkeys + 1 && Math.abs(art.currentTime - beforeKey.time - Artplayer.SEEK_STEP) < 0.05)
|
|
if (event.code === 'ArrowRight' && beforeKey.target === 'INPUT') check('DOM.input-ignores-hotkey', beforeKey.trusted && beforeKey.focused && hotkeys === beforeKey.hotkeys && art.currentTime === beforeKey.time)
|
|
})
|
|
document.addEventListener('focusin', event => report.interactions.push({ focus: event.target.tagName, label: event.target.getAttribute('aria-label'), id: event.target.id }))
|
|
art.on('fullscreenWeb', value => {
|
|
if (value) check('DOM.fullscreen-web-enter', art.template.$player.parentElement === document.body && art.template.$player.classList.contains('art-fullscreen-web'))
|
|
else check('DOM.fullscreen-web-exit', art.template.$player.parentElement === art.template.$container && !art.template.$player.classList.contains('art-fullscreen-web'))
|
|
})
|
|
document.querySelector('#narrow').onclick = () => {
|
|
document.querySelector('.player').style.cssText = 'width:320px;height:180px'
|
|
requestAnimationFrame(() => {
|
|
report.observations.narrow = layout()
|
|
check('DOM.narrow-container', report.observations.narrow.width === 320 && report.observations.narrow.height === 180)
|
|
})
|
|
}
|
|
document.querySelector('#finish').onclick = async () => {
|
|
try {
|
|
const previous = art.controls.compat
|
|
const updateResult = art.controls.update({ name: 'compat', html: 'Updated control' })
|
|
const replacement = art.controls.compat
|
|
check('DOM.control-update', updateResult === undefined && replacement instanceof HTMLElement && !previous.isConnected && art.template.$player.querySelectorAll('.art-control-compat').length === 1)
|
|
art.controls.remove('compat')
|
|
check('DOM.control-remove', !replacement.isConnected && !art.controls.compat)
|
|
art.setting.update({ name: 'compat-setting', html: 'Updated selector' })
|
|
check('DOM.setting-update', art.setting.find('compat-setting').html === 'Updated selector')
|
|
art.setting.remove('compat-setting')
|
|
check('DOM.setting-remove', !art.setting.find('compat-setting'))
|
|
art.layers.remove('compat')
|
|
check('DOM.layer-remove', !art.query('.user-hook'))
|
|
art.destroy()
|
|
check('DOM.destroy', document.querySelector('.player').children.length === 0 && Artplayer.instances.length === 0)
|
|
}
|
|
catch (error) {
|
|
report.errors.push(error.message)
|
|
if (Artplayer.instances.includes(art)) art.destroy()
|
|
}
|
|
report.checks.sort((a, b) => a.id.localeCompare(b.id))
|
|
document.querySelector('#result').textContent = JSON.stringify(report, null, 2)
|
|
const response = await fetch('/reports/dom', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(report) })
|
|
status.textContent = response.ok ? `Saved ${report.checks.length} checks; errors ${report.errors.length}` : `Save failed: ${response.status}`
|
|
document.querySelector('#finish').disabled = true
|
|
}
|
|
})()
|