Enhance accessibility and keyboard navigation for video controls and overlays
This commit is contained in:
parent
e0297afe0f
commit
3d603e1b68
@ -151,6 +151,17 @@
|
||||
cursor: pointer;
|
||||
accent-color: #3498db; /* Optional: color for the seek bar thumb and progress */
|
||||
}
|
||||
|
||||
/* Styles for TV remote focus indication */
|
||||
.video-thumbnail:focus,
|
||||
#back-button:focus {
|
||||
outline: 3px solid #3498db !important; /* A clear blue outline */
|
||||
box-shadow: 0 0 15px #3498db; /* A glow effect */
|
||||
}
|
||||
|
||||
.nofocus:focus {
|
||||
outline: none !important; /* Remove focus outline for elements with nofocus class */
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function hideLoader() {
|
||||
@ -166,6 +177,7 @@
|
||||
let customControls = null;
|
||||
let playPauseButton = null;
|
||||
let seekBar = null;
|
||||
let videoPlaying = false;
|
||||
|
||||
function exitVideoPlayback() {
|
||||
if (videoElement && videoElement.pause) {
|
||||
@ -178,14 +190,14 @@
|
||||
overlayContainer.hide(); // Hide instead of remove
|
||||
}
|
||||
|
||||
$(document).off('fullscreenchange.videoPlayback webkitfullscreenchange.videoPlayback mozfullscreenchange.videoPlayback MSFullscreenChange.videoPlayback', handleFullscreenChange);
|
||||
$(document).off('fullscreenchange.videoPlayback webkitfullscreenchange.videoPlayback mozFullScreen.videoPlayback MSFullscreenChange.videoPlayback', handleFullscreenChange);
|
||||
|
||||
if (videoElement) {
|
||||
$(videoElement).off('.currentVideo'); // Remove all namespaced video events
|
||||
videoElement.src = ""; // Clear the source
|
||||
videoElement.load(); // Important to ensure the video unloads
|
||||
}
|
||||
if (seekBar) {
|
||||
if (seekBar) { // seekBar is a jQuery object
|
||||
seekBar.val(0);
|
||||
seekBar.css('background', ''); // Reset seek bar style
|
||||
}
|
||||
@ -203,6 +215,12 @@
|
||||
document.msExitFullscreen();
|
||||
}
|
||||
}
|
||||
// FOCUS: Return focus to the main view's play button
|
||||
if ($(".video-container").is(":visible") && $('#play-button').length) {
|
||||
$('#play-button').focus();
|
||||
}
|
||||
|
||||
videoPlaying = false;
|
||||
}
|
||||
|
||||
function showControlsAndResetTimer() {
|
||||
@ -284,6 +302,10 @@
|
||||
currentVideo = data.currentVideo;
|
||||
$("#current-thumb").attr("src", currentVideo.thumbnail);
|
||||
hideLoader();
|
||||
// FOCUS: Set initial focus on the main screen
|
||||
if ($(".video-container").is(":visible") && $('#play-button').length) {
|
||||
$('#play-button').focus();
|
||||
}
|
||||
})
|
||||
|
||||
// Attach event listeners that can be set up once for static controls
|
||||
@ -316,8 +338,13 @@
|
||||
} else if (containerEl.msRequestFullscreen) {
|
||||
containerEl.msRequestFullscreen();
|
||||
}
|
||||
// FOCUS: Set focus in the video overlay
|
||||
if (playPauseButton && playPauseButton.length) {
|
||||
playPauseButton.focus();
|
||||
}
|
||||
|
||||
videoElement.play().catch(err => console.error("Error attempting to play video:", err));
|
||||
videoPlaying = true;
|
||||
}
|
||||
});
|
||||
|
||||
@ -329,29 +356,135 @@
|
||||
setInterval(handleVideoTimeUpdate, 1000/30);
|
||||
|
||||
// --- Setup Fullscreen Change Listener (session-specific) ---
|
||||
$(document).off('fullscreenchange.videoPlayback webkitfullscreenchange.videoPlayback mozfullscreenchange.videoPlayback MSFullscreenChange.videoPlayback', handleFullscreenChange);
|
||||
$(document).on('fullscreenchange.videoPlayback webkitfullscreenchange.videoPlayback mozfullscreenchange.videoPlayback MSFullscreenChange.videoPlayback', handleFullscreenChange);
|
||||
$(document).off('fullscreenchange.videoPlayback webkitfullscreenchange.videoPlayback mozFullScreen.videoPlayback MSFullscreenChange.videoPlayback', handleFullscreenChange);
|
||||
$(document).on('fullscreenchange.videoPlayback webkitfullscreenchange.videoPlayback mozFullScreen.videoPlayback MSFullscreenChange.videoPlayback', handleFullscreenChange);
|
||||
|
||||
overlayContainer.on('mousemove.inactivityControls', showControlsAndResetTimer);
|
||||
|
||||
// --- TV Remote/Keyboard Navigation ---
|
||||
const mainViewFocusable = [$('#prev-thumb'), $('#play-button'), $('#next-thumb')].filter(el => el && el.length > 0);
|
||||
const getOverlayViewFocusableElements = () => [backButton, playPauseButton, seekBar].filter(el => el && el.length > 0 && el.is(':visible'));
|
||||
|
||||
$(document).on('keydown', function(e) {
|
||||
let isOverlayVisible = overlayContainer.is(':visible');
|
||||
let activeFocusableSet;
|
||||
|
||||
if (isOverlayVisible) {
|
||||
activeFocusableSet = getOverlayViewFocusableElements();
|
||||
} else {
|
||||
activeFocusableSet = mainViewFocusable;
|
||||
}
|
||||
|
||||
if (!activeFocusableSet || activeFocusableSet.length === 0) return;
|
||||
|
||||
const focusedElement = $(document.activeElement);
|
||||
let currentIndex = -1;
|
||||
|
||||
for (let i = 0; i < activeFocusableSet.length; i++) {
|
||||
if (focusedElement.is(activeFocusableSet[i])) {
|
||||
currentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Global key handling for overlay
|
||||
if (isOverlayVisible) {
|
||||
if (e.key === 'Backspace' || e.keyCode === 8) { // Backspace
|
||||
e.preventDefault();
|
||||
if (backButton && backButton.length) backButton.click();
|
||||
return;
|
||||
}
|
||||
if (e.keyCode === 179) { // MediaPlayPause
|
||||
e.preventDefault();
|
||||
if (playPauseButton && playPauseButton.length) playPauseButton.click();
|
||||
return;
|
||||
}
|
||||
if (e.keyCode === 178) { // MediaStop
|
||||
e.preventDefault();
|
||||
exitVideoPlayback();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If focus is not on a managed element, and not body/html, usually let it be.
|
||||
// However, for certain keys like Enter, we might want a default action.
|
||||
if (currentIndex === -1 && !focusedElement.is('body') && !focusedElement.is('html')) {
|
||||
return;
|
||||
}
|
||||
|
||||
let keyHandled = false;
|
||||
switch (e.key || e.keyCode) {
|
||||
case 'ArrowLeft':
|
||||
case 37: // ArrowLeft
|
||||
if (videoPlaying) {
|
||||
videoElement.currentTime = Math.max(0, videoElement.currentTime - 5); // Seek back 5s
|
||||
handleVideoTimeUpdate();
|
||||
showControlsAndResetTimer();
|
||||
} else if (currentIndex !== -1) {
|
||||
let nextIndex = (currentIndex - 1 + activeFocusableSet.length) % activeFocusableSet.length;
|
||||
activeFocusableSet[nextIndex].focus();
|
||||
} else if (activeFocusableSet.length > 0) { // If nothing specific focused, focus last
|
||||
activeFocusableSet[activeFocusableSet.length - 1].focus();
|
||||
}
|
||||
keyHandled = true;
|
||||
break;
|
||||
|
||||
case 'ArrowRight':
|
||||
case 39: // ArrowRight
|
||||
if (videoPlaying) {
|
||||
videoElement.currentTime = Math.min(videoElement.duration, videoElement.currentTime + 5); // Seek forward 5s
|
||||
handleVideoTimeUpdate();
|
||||
showControlsAndResetTimer();
|
||||
} else if (currentIndex !== -1) {
|
||||
let nextIndex = (currentIndex + 1) % activeFocusableSet.length;
|
||||
activeFocusableSet[nextIndex].focus();
|
||||
} else if (activeFocusableSet.length > 0) { // If nothing specific focused, focus first
|
||||
activeFocusableSet[0].focus();
|
||||
}
|
||||
keyHandled = true;
|
||||
break;
|
||||
|
||||
case 'Enter':
|
||||
case 13: // Enter
|
||||
if (videoPlaying) {
|
||||
$('#play-button').click();
|
||||
} else if (currentIndex !== -1) {
|
||||
activeFocusableSet[currentIndex].click();
|
||||
}
|
||||
keyHandled = true;
|
||||
break;
|
||||
|
||||
case 179: // MediaPlayPause (if not handled by global overlay section)
|
||||
if (!isOverlayVisible && $('#play-button').length) {
|
||||
$('#play-button').click();
|
||||
keyHandled = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (keyHandled) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="loader" id="loader"></div>
|
||||
<div class="video-container">
|
||||
<img id="prev-thumb" class="video-thumbnail side-video" alt="Previous Video">
|
||||
<img id="current-thumb" class="video-thumbnail current-video" alt="Current Video">
|
||||
<img id="next-thumb" class="video-thumbnail side-video" alt="Next Video">
|
||||
<div class="play-button" id="play-button"></div>
|
||||
<img id="prev-thumb" class="video-thumbnail side-video" alt="Previous Video" tabindex="0">
|
||||
<img id="current-thumb" class="video-thumbnail current-video" alt="Current Video" tabindex="0">
|
||||
<img id="next-thumb" class="video-thumbnail side-video" alt="Next Video" tabindex="0">
|
||||
<div class="play-button" id="play-button" tabindex="0" role="button" aria-label="Play video"></div>
|
||||
</div>
|
||||
|
||||
<!-- STATIC VIDEO OVERLAY STRUCTURE -->
|
||||
<div id="video-overlay-container">
|
||||
<video class="video-player"></video> <!-- no src, no autoplay, no controls initially -->
|
||||
<button id="back-button">Back</button>
|
||||
<button id="back-button" tabindex="0">Back</button>
|
||||
<div id="custom-video-controls">
|
||||
<button id="custom-play-pause-button">►</button> <!-- Default to Play icon -->
|
||||
<input type="range" id="custom-seek-bar" value="0" step="any" />
|
||||
<button id="custom-play-pause-button" class="nofocus" tabindex="0">►</button> <!-- Default to Play icon -->
|
||||
<input type="range" id="custom-seek-bar" class="nofocus" value="0" step="any" tabindex="0" aria-label="Video seek bar" />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
Loading…
x
Reference in New Issue
Block a user