diff --git a/css/player.css b/css/player.css index 442991c..e5c78ad 100644 --- a/css/player.css +++ b/css/player.css @@ -735,7 +735,7 @@ body { bottom: 300px; right: 60px; z-index: 200; - pointer-events: all; + pointer-events: none; opacity: 0; transform: translateY(20px); transition: opacity 0.3s ease, transform 0.3s ease; @@ -744,6 +744,7 @@ body { .skip-overlay.visible { opacity: 1; transform: translateY(0); + pointer-events: all; } .skip-button-container { diff --git a/js/player.js b/js/player.js index 11e80c7..5a68bf0 100644 --- a/js/player.js +++ b/js/player.js @@ -42,10 +42,10 @@ var PlayerController = (function () { let isDolbyVisionMedia = false; // Track if current media is Dolby Vision let playbackHealthCheckTimer = null; // Timer for checking playback health let forcePlayMode = null; // User override for playback mode ('direct' or 'transcode') - + const USE_PLAYBACK_MANAGER = true; let playbackManagerReady = false; - + // Load persisted play mode on init function loadForcePlayMode() { if (typeof storage !== "undefined" && itemId) { @@ -55,7 +55,7 @@ var PlayerController = (function () { } } } - + // Save play mode to persist across reloads function saveForcePlayMode(mode) { if (typeof storage !== "undefined" && itemId) { @@ -167,86 +167,88 @@ var PlayerController = (function () { ); return; } - + loadForcePlayMode(); cacheElements(); setupEventListeners(); - + if (USE_PLAYBACK_MANAGER) { initPlaybackManagerAdapter(); } - + // Initialize playback flow (adapter will be created once playback method is known) loadItemAndPlay(); } - + function initPlaybackManagerAdapter() { console.log('[Player] Initializing PlaybackManager adapter...'); - + if (typeof PlaybackManagerAdapter === 'undefined') { console.error('[Player] PlaybackManagerAdapter not found! Make sure playback-manager-adapter.js is loaded'); return; } - + // Define UI callbacks for PlaybackManager events var callbacks = { - onTimeUpdate: function(currentTicks, durationTicks) { + onTimeUpdate: function (currentTicks, durationTicks) { updateTimeDisplay(); checkMediaSegments(currentTicks); }, - - onPause: function() { + + onPause: function () { updatePlayPauseButton(); + showControls(); }, - - onUnpause: function() { + + onUnpause: function () { updatePlayPauseButton(); + showControls(); }, - - onPlaybackStart: function(state) { + + onPlaybackStart: function (state) { console.log('[Player] PlaybackManager playback started', state); showControls(); - + // Update our internal state from PlaybackManager if (state && state.NowPlayingItem) { itemData = state.NowPlayingItem; itemId = itemData.Id; } - + // PlaybackManager handles server reporting automatically // No need to call reportPlaybackStart() - + // Load tracks from PlaybackManager loadAudioTracksFromPlaybackManager(); loadSubtitleTracksFromPlaybackManager(); - + // Start UI updates if (!progressInterval) { startProgressReporting(); // Keep our interval for UI updates } }, - - onPlaybackStop: function(stopInfo) { + + onPlaybackStop: function (stopInfo) { console.log('[Player] PlaybackManager playback stopped', stopInfo); cleanup(); }, - - onMediaStreamsChange: function() { + + onMediaStreamsChange: function () { console.log('[Player] Media streams changed, reloading tracks'); loadAudioTracksFromPlaybackManager(); loadSubtitleTracksFromPlaybackManager(); }, - - onError: function(error) { + + onError: function (error) { console.error('[Player] PlaybackManager error:', error); showErrorDialog('Playback Error', error.message || 'An error occurred during playback'); } }; - + // Initialize adapter with callbacks playbackManagerReady = PlaybackManagerAdapter.init(callbacks); - + if (playbackManagerReady) { console.log('[Player] PlaybackManager adapter initialized successfully'); } else { @@ -337,7 +339,7 @@ var PlayerController = (function () { }; videoPlayer = elements.videoPlayer; - + // Set initial volume to maximum and ensure not muted if (videoPlayer) { videoPlayer.volume = 1.0; @@ -521,7 +523,7 @@ var PlayerController = (function () { hideLoading(); }); - playerAdapter.on("qualitychange", function (data) {}); + playerAdapter.on("qualitychange", function (data) { }); playerAdapter.on("audiotrackchange", function (data) { detectCurrentAudioTrack(); @@ -573,8 +575,14 @@ var PlayerController = (function () { // If seekbar is focused, toggle play/pause togglePlayPause(); } else { - // Otherwise toggle play/pause + // Otherwise toggle play/pause and ensure focus togglePlayPause(); + + // Ensure controls are visible and focus is on play button + showControls(); + if (elements.playPauseBtn) { + elements.playPauseBtn.focus(); + } } break; @@ -633,11 +641,8 @@ var PlayerController = (function () { document.activeElement && focusableButtons.includes(document.activeElement) ) { - // If on the bottom buttons (chaptersBtn or videoInfoBtn), move to seekbar - if ( - currentFocusIndex === focusableButtons.length - 1 || - currentFocusIndex === focusableButtons.length - 2 - ) { + // If on the bottom buttons (index >= 6), move to seekbar + if (currentFocusIndex >= 6) { if (elements.progressBar) { elements.progressBar.focus(); } @@ -653,16 +658,20 @@ var PlayerController = (function () { document.activeElement && focusableButtons.includes(document.activeElement) ) { - // If on any of the top buttons, move to seekbar - if (currentFocusIndex < focusableButtons.length - 2) { + // If on any of the top buttons (index < 6), move to seekbar + if (currentFocusIndex < 6) { if (elements.progressBar) { elements.progressBar.focus(); } } } else if (isSeekbarFocused) { - // Move from seekbar to first bottom button (chaptersBtn) - if (focusableButtons.length > 1) { - currentFocusIndex = focusableButtons.length - 2; + // Move from seekbar to first bottom button (chaptersBtn at index 6) + if (focusableButtons.length > 6) { + currentFocusIndex = 6; + focusableButtons[currentFocusIndex].focus(); + } else if (focusableButtons.length > 0) { + // Fallback if fewer buttons + currentFocusIndex = focusableButtons.length - 1; focusableButtons[currentFocusIndex].focus(); } } else if (focusableButtons.length > 0) { @@ -685,6 +694,14 @@ var PlayerController = (function () { (currentFocusIndex - 1 + focusableButtons.length) % focusableButtons.length; focusableButtons[currentFocusIndex].focus(); + } else { + // Fallback: Show controls, focus seekbar, and seek + evt.preventDefault(); + showControls(); + if (elements.progressBar) { + elements.progressBar.focus(); + seekBackward(); + } } break; @@ -701,6 +718,14 @@ var PlayerController = (function () { currentFocusIndex = (currentFocusIndex + 1) % focusableButtons.length; focusableButtons[currentFocusIndex].focus(); + } else { + // Fallback: Show controls, focus seekbar, and seek + evt.preventDefault(); + showControls(); + if (elements.progressBar) { + elements.progressBar.focus(); + seekForward(); + } } break; } @@ -824,7 +849,7 @@ var PlayerController = (function () { loadMediaSegments(); loadAdjacentEpisodes(); - + if (USE_PLAYBACK_MANAGER && playbackManagerReady) { startPlaybackViaPlaybackManager(); } else { @@ -833,23 +858,23 @@ var PlayerController = (function () { } ); } - + /** * This replaces the manual getPlaybackInfo() → startPlayback() flow */ function startPlaybackViaPlaybackManager() { console.log('[Player] Starting playback via PlaybackManager'); - + if (!itemData) { console.error('[Player] No item data available'); showErrorDialog('Playback Error', 'Item data not loaded'); return; } - + // Get start position from URL or resume point var startPositionSeconds = getStartPositionFromUrl(); var startPositionTicks = 0; - + if (startPositionSeconds !== null) { startPositionTicks = startPositionSeconds * 10000000; // Convert to ticks console.log('[Player] Starting at position:', startPositionSeconds, 'seconds'); @@ -857,43 +882,43 @@ var PlayerController = (function () { startPositionTicks = itemData.UserData.PlaybackPositionTicks; console.log('[Player] Resuming from position:', startPositionTicks / 10000000, 'seconds'); } - + // Build playback options for PlaybackManager var playOptions = { items: [itemData], startPositionTicks: startPositionTicks, fullscreen: true }; - + // Add track preferences if available (from details page or previous selection) var preferredAudioIndex = localStorage.getItem('preferredAudioTrack_' + itemId); var preferredSubtitleIndex = localStorage.getItem('preferredSubtitleTrack_' + itemId); - + if (preferredAudioIndex !== null) { playOptions.audioStreamIndex = parseInt(preferredAudioIndex, 10); console.log('[Player] Preferred audio track:', preferredAudioIndex); } - + if (preferredSubtitleIndex !== null) { playOptions.subtitleStreamIndex = parseInt(preferredSubtitleIndex, 10); console.log('[Player] Preferred subtitle track:', preferredSubtitleIndex); } - + // Get max bitrate setting (in bps) var currentMaxBitrate = storage.get("maxBitrate", false) || "120000000"; playOptions.maxBitrate = parseInt(currentMaxBitrate, 10); - console.log('[Player] PlaybackManager play options:', playOptions); - + console.log('[Player] PlaybackManager play options:', playOptions); + // Start playback via PlaybackManager PlaybackManagerAdapter.play(playOptions) - .then(function() { + .then(function () { console.log('[Player] PlaybackManager playback started successfully'); hideLoading(); - + // Generate session ID for our own tracking (PlaybackManager has its own) playSessionId = generateUUID(); }) - .catch(function(error) { + .catch(function (error) { console.error('[Player] PlaybackManager play failed:', error); hideLoading(); showErrorDialog( @@ -911,15 +936,15 @@ var PlayerController = (function () { if (!USE_PLAYBACK_MANAGER || !playbackManagerReady) { return; } - + try { var tracks = PlaybackManagerAdapter.audioTracks(); console.log('[Player] Loaded audio tracks from PlaybackManager:', tracks); - + // Update UI with tracks (implementation depends on your track selector UI) // For now, just log the available tracks if (tracks && tracks.length > 0) { - tracks.forEach(function(track, index) { + tracks.forEach(function (track, index) { console.log('[Player] Audio track ' + index + ':', { index: track.index, language: track.language, @@ -941,14 +966,14 @@ var PlayerController = (function () { if (!USE_PLAYBACK_MANAGER || !playbackManagerReady) { return; } - + try { var tracks = PlaybackManagerAdapter.subtitleTracks(); console.log('[Player] Loaded subtitle tracks from PlaybackManager:', tracks); - + // Update UI with tracks if (tracks && tracks.length > 0) { - tracks.forEach(function(track, index) { + tracks.forEach(function (track, index) { console.log('[Player] Subtitle track ' + index + ':', { index: track.index, language: track.language, @@ -980,7 +1005,7 @@ var PlayerController = (function () { var isLiveTV = itemData && itemData.Type === "TvChannel"; var deviceProfile = getDeviceProfile(); - + // Check MKV support with ES5 loop for Tizen 4 compatibility var mkvSupported = false; var mkvProfile = null; @@ -1002,7 +1027,7 @@ var PlayerController = (function () { DeviceProfile: deviceProfile, AutoOpenLiveStream: isLiveTV, }; - + if (typeof ServerLogger !== "undefined") { ServerLogger.logPlaybackInfo("Requesting playback info", { itemId: itemId, @@ -1011,7 +1036,7 @@ var PlayerController = (function () { serverAddress: auth.serverAddress }); } - + ajax.request(playbackUrl, { method: "POST", headers: { @@ -1032,13 +1057,13 @@ var PlayerController = (function () { var mediaSource = playbackInfo.MediaSources[0]; var videoStream = mediaSource.MediaStreams ? mediaSource.MediaStreams.find(function (s) { - return s.Type === "Video"; - }) + return s.Type === "Video"; + }) : null; var audioStream = mediaSource.MediaStreams ? mediaSource.MediaStreams.find(function (s) { - return s.Type === "Audio"; - }) + return s.Type === "Audio"; + }) : null; if (typeof ServerLogger !== "undefined") { @@ -1055,7 +1080,7 @@ var PlayerController = (function () { directStreamUrl: mediaSource.DirectStreamUrl || "none" }); } - + // Additional debug logging for codec troubleshooting console.log("[Player] Server playback decision:", { container: mediaSource.Container, @@ -1086,9 +1111,9 @@ var PlayerController = (function () { playbackInfo.MediaSources.length > 0 ) { var mediaSourceToPlay = playbackInfo.MediaSources[0]; - + // Tizen AVPlay supports EAC3 in MKV containers - + startPlayback(mediaSourceToPlay).catch(onError); } else { showErrorDialog( @@ -1100,7 +1125,7 @@ var PlayerController = (function () { }, error: function (err) { console.error("[Player] PlaybackInfo request failed:", err); - + var title = "Playback Error"; var message = "Failed to get playback information from the server."; var details = ""; @@ -1181,13 +1206,13 @@ var PlayerController = (function () { // Populate audio/subtitle streams early so preferences can be applied audioStreams = mediaSource.MediaStreams ? mediaSource.MediaStreams.filter(function (s) { - return s.Type === "Audio"; - }) + return s.Type === "Audio"; + }) : []; subtitleStreams = mediaSource.MediaStreams ? mediaSource.MediaStreams.filter(function (s) { - return s.Type === "Subtitle"; - }) + return s.Type === "Subtitle"; + }) : []; currentAudioIndex = -1; @@ -1222,13 +1247,13 @@ var PlayerController = (function () { var videoStream = mediaSource.MediaStreams ? mediaSource.MediaStreams.find(function (s) { - return s.Type === "Video"; - }) + return s.Type === "Video"; + }) : null; var audioStream = mediaSource.MediaStreams ? mediaSource.MediaStreams.find(function (s) { - return s.Type === "Audio"; - }) + return s.Type === "Audio"; + }) : null; var isDolbyVision = @@ -1246,11 +1271,11 @@ var PlayerController = (function () { videoStream.BitDepth === 10 || (videoStream.Profile && videoStream.Profile.toLowerCase().indexOf("main 10") !== -1) ); - var isHDR = videoStream && - (videoStream.VideoRangeType === "HDR10" || - videoStream.VideoRangeType === "HDR10Plus" || - videoStream.VideoRangeType === "HLG" || - videoStream.VideoRangeType === "DOVIWithHDR10"); + var isHDR = videoStream && + (videoStream.VideoRangeType === "HDR10" || + videoStream.VideoRangeType === "HDR10Plus" || + videoStream.VideoRangeType === "HLG" || + videoStream.VideoRangeType === "DOVIWithHDR10"); // Log video stream details for debugging playback issues console.log("[Player] Video stream analysis:", { @@ -1284,7 +1309,7 @@ var PlayerController = (function () { // Priority: DirectPlay > DirectStream > Transcode (unless user forces specific mode) var shouldUseDirectPlay = false; var shouldUseDirectStream = false; - + if (forcePlayMode === "direct") { // User explicitly wants direct play - try it if server says we can shouldUseDirectPlay = canDirectPlay; @@ -1315,7 +1340,7 @@ var PlayerController = (function () { console.log("[Player] Neither direct play nor direct stream available - will transcode"); } } - + var playbackMethod = shouldUseDirectPlay ? "DirectPlay" : (shouldUseDirectStream ? "DirectStream" : (canTranscode ? "Transcode" : "None")); console.log("[Player] Playback decision:", { method: playbackMethod, @@ -1444,16 +1469,16 @@ var PlayerController = (function () { // Prepare the correct adapter based on playback method var creationOptions = {}; - + // Containers supported by HTML5 video element on Tizen // Tizen's HTML5