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;
|
cursor: pointer;
|
||||||
accent-color: #3498db; /* Optional: color for the seek bar thumb and progress */
|
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>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
function hideLoader() {
|
function hideLoader() {
|
||||||
@ -166,6 +177,7 @@
|
|||||||
let customControls = null;
|
let customControls = null;
|
||||||
let playPauseButton = null;
|
let playPauseButton = null;
|
||||||
let seekBar = null;
|
let seekBar = null;
|
||||||
|
let videoPlaying = false;
|
||||||
|
|
||||||
function exitVideoPlayback() {
|
function exitVideoPlayback() {
|
||||||
if (videoElement && videoElement.pause) {
|
if (videoElement && videoElement.pause) {
|
||||||
@ -178,14 +190,14 @@
|
|||||||
overlayContainer.hide(); // Hide instead of remove
|
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) {
|
if (videoElement) {
|
||||||
$(videoElement).off('.currentVideo'); // Remove all namespaced video events
|
$(videoElement).off('.currentVideo'); // Remove all namespaced video events
|
||||||
videoElement.src = ""; // Clear the source
|
videoElement.src = ""; // Clear the source
|
||||||
videoElement.load(); // Important to ensure the video unloads
|
videoElement.load(); // Important to ensure the video unloads
|
||||||
}
|
}
|
||||||
if (seekBar) {
|
if (seekBar) { // seekBar is a jQuery object
|
||||||
seekBar.val(0);
|
seekBar.val(0);
|
||||||
seekBar.css('background', ''); // Reset seek bar style
|
seekBar.css('background', ''); // Reset seek bar style
|
||||||
}
|
}
|
||||||
@ -203,6 +215,12 @@
|
|||||||
document.msExitFullscreen();
|
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() {
|
function showControlsAndResetTimer() {
|
||||||
@ -284,6 +302,10 @@
|
|||||||
currentVideo = data.currentVideo;
|
currentVideo = data.currentVideo;
|
||||||
$("#current-thumb").attr("src", currentVideo.thumbnail);
|
$("#current-thumb").attr("src", currentVideo.thumbnail);
|
||||||
hideLoader();
|
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
|
// Attach event listeners that can be set up once for static controls
|
||||||
@ -316,8 +338,13 @@
|
|||||||
} else if (containerEl.msRequestFullscreen) {
|
} else if (containerEl.msRequestFullscreen) {
|
||||||
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));
|
videoElement.play().catch(err => console.error("Error attempting to play video:", err));
|
||||||
|
videoPlaying = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -329,29 +356,135 @@
|
|||||||
setInterval(handleVideoTimeUpdate, 1000/30);
|
setInterval(handleVideoTimeUpdate, 1000/30);
|
||||||
|
|
||||||
// --- Setup Fullscreen Change Listener (session-specific) ---
|
// --- Setup Fullscreen Change Listener (session-specific) ---
|
||||||
$(document).off('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 mozfullscreenchange.videoPlayback MSFullscreenChange.videoPlayback', handleFullscreenChange);
|
$(document).on('fullscreenchange.videoPlayback webkitfullscreenchange.videoPlayback mozFullScreen.videoPlayback MSFullscreenChange.videoPlayback', handleFullscreenChange);
|
||||||
|
|
||||||
overlayContainer.on('mousemove.inactivityControls', showControlsAndResetTimer);
|
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>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="loader" id="loader"></div>
|
<div class="loader" id="loader"></div>
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
<img id="prev-thumb" class="video-thumbnail side-video" alt="Previous Video">
|
<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">
|
<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">
|
<img id="next-thumb" class="video-thumbnail side-video" alt="Next Video" tabindex="0">
|
||||||
<div class="play-button" id="play-button"></div>
|
<div class="play-button" id="play-button" tabindex="0" role="button" aria-label="Play video"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- STATIC VIDEO OVERLAY STRUCTURE -->
|
<!-- STATIC VIDEO OVERLAY STRUCTURE -->
|
||||||
<div id="video-overlay-container">
|
<div id="video-overlay-container">
|
||||||
<video class="video-player"></video> <!-- no src, no autoplay, no controls initially -->
|
<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">
|
<div id="custom-video-controls">
|
||||||
<button id="custom-play-pause-button">►</button> <!-- Default to Play icon -->
|
<button id="custom-play-pause-button" class="nofocus" tabindex="0">►</button> <!-- Default to Play icon -->
|
||||||
<input type="range" id="custom-seek-bar" value="0" step="any" />
|
<input type="range" id="custom-seek-bar" class="nofocus" value="0" step="any" tabindex="0" aria-label="Video seek bar" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user