tv完善弹幕以及各种焦点

This commit is contained in:
mtvpls
2026-05-30 01:01:37 +08:00
parent a880b65f35
commit e7558874bc
5 changed files with 375 additions and 48 deletions
+33
View File
@@ -30,8 +30,17 @@ function isVisible(element: HTMLElement) {
}
function getFocusableElements() {
const scope = document.querySelector<HTMLElement>('[data-tv-focus-scope="active"]');
if (scope) {
return Array.from(scope.querySelectorAll<HTMLElement>(focusableSelector))
.filter((element) => !element.closest('[data-tv-remote]'))
.filter((element) => !element.closest('[data-tv-no-focus="true"]'))
.filter(isVisible);
}
return Array.from(document.querySelectorAll<HTMLElement>(focusableSelector))
.filter((element) => !element.closest('[data-tv-remote]'))
.filter((element) => !element.closest('[data-tv-no-focus="true"]'))
.filter(isVisible);
}
@@ -232,6 +241,25 @@ export default function TVVirtualRemote() {
if (event.defaultPrevented) return;
if (event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
const active = document.activeElement;
if (active instanceof HTMLElement && active.closest('[data-tv-danmaku-settings]')) {
return;
}
if (
active instanceof HTMLInputElement &&
active.type === 'range' &&
active.closest('[data-tv-danmaku-settings]')
) {
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
return;
}
}
if (active instanceof HTMLInputElement && active.type === 'range' && active.closest('[data-tv-no-focus="true"]')) {
return;
}
event.preventDefault();
const direction = event.key.replace('Arrow', '').toLowerCase() as 'up' | 'down' | 'left' | 'right';
moveSpatialFocus(direction, lastFocusedRef.current);
@@ -239,6 +267,11 @@ export default function TVVirtualRemote() {
}
if (event.key === 'Enter') {
const playerRoot = document.querySelector<HTMLElement>('[data-tv-player-root]');
if (playerRoot?.dataset.tvControlsOpen === 'false') {
return;
}
const active = document.activeElement;
if (active instanceof HTMLElement && !active.closest('input, textarea, select') && !active.closest('[data-tv-remote]')) {
event.preventDefault();
+9 -5
View File
@@ -235,14 +235,18 @@ export default function TVNativeVideo({
attach();
let seekedInitialTime = false;
let triedInitialSeek = false;
const seekToInitialTime = () => {
if (live || seekedInitialTime || !startTime || startTime <= 1) return;
if (live || triedInitialSeek || !startTime || startTime <= 1) return;
if ((videoEl.currentTime || 0) > 1) {
triedInitialSeek = true;
return;
}
const duration = videoEl.duration || 0;
const safeTime = duration > 30 ? Math.min(startTime, Math.max(0, duration - 8)) : startTime;
try {
videoEl.currentTime = safeTime;
seekedInitialTime = true;
triedInitialSeek = true;
} catch {
// ignore unsupported seek state
}
@@ -289,7 +293,7 @@ export default function TVNativeVideo({
videoEl.addEventListener('waiting', onBuffering);
videoEl.addEventListener('stalled', onBuffering);
videoEl.addEventListener('seeking', onBuffering);
videoEl.addEventListener('seeked', onLoaded);
videoEl.addEventListener('seeked', clearBuffering);
videoEl.addEventListener('error', onError);
videoEl.addEventListener('timeupdate', onTimeUpdate);
@@ -305,7 +309,7 @@ export default function TVNativeVideo({
videoEl.removeEventListener('waiting', onBuffering);
videoEl.removeEventListener('stalled', onBuffering);
videoEl.removeEventListener('seeking', onBuffering);
videoEl.removeEventListener('seeked', onLoaded);
videoEl.removeEventListener('seeked', clearBuffering);
videoEl.removeEventListener('error', onError);
videoEl.removeEventListener('timeupdate', onTimeUpdate);
clearBuffering();