Enhance accessibility and keyboard navigation for video controls and overlays
This commit is contained in:
		@@ -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>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user