diff --git a/js&css/web-accessible/functions.js b/js&css/web-accessible/functions.js index 2326e4457..e37a42008 100644 --- a/js&css/web-accessible/functions.js +++ b/js&css/web-accessible/functions.js @@ -345,6 +345,16 @@ ImprovedTube.videoPageUpdate = function () { ImprovedTube.playerCinemaModeButton(); ImprovedTube.playerHamburgerButton(); ImprovedTube.playerControls(); + + // Initialize large playlist handler for playlist videos + if (this.getParam(location.href, 'list')) { + ImprovedTube.playlistLargePlaylistHandler(); + } else { + // Cleanup when not on a playlist + if (typeof ImprovedTube.cleanupPlaylistHandlers === 'function') { + ImprovedTube.cleanupPlaylistHandlers(); + } + } } }; diff --git a/js&css/web-accessible/init.js b/js&css/web-accessible/init.js index e29c7b5b6..442e516b7 100644 --- a/js&css/web-accessible/init.js +++ b/js&css/web-accessible/init.js @@ -185,6 +185,7 @@ ImprovedTube.init = function () { ImprovedTube.playlistPopup(); ImprovedTube.playlistCopyVideoIdButton(); ImprovedTube.playlistCompleteInit(); + ImprovedTube.playlistLargePlaylistHandler(); } try { if (ImprovedTube.lastWatchedOverlay) ImprovedTube.lastWatchedOverlay(); } catch (e) { console.error('[LWO] page-data-updated error', e); } }); @@ -231,15 +232,15 @@ document.addEventListener('yt-navigate-finish', function () { if(node.getAttribute('name')) { //if(node.getAttribute('name') === 'title') {ImprovedTube.title = node.content;} //duplicate //if(node.getAttribute('name') === 'description') {ImprovedTube.description = node.content;} //duplicate - //if node.getAttribute('name') === 'themeColor') {ImprovedTube.themeColor = node.content;} //might help our darkmode/themes - //Do we need any of these here before the player starts? + //if(node.getAttribute('name') === 'themeColor') {ImprovedTube.themeColor = node.content;} //might help our darkmode/themes +//Do we need any of these here before the player starts? //if(node.getAttribute('name') === 'keywords') {ImprovedTube.keywords = node.content;} } else if (node.getAttribute('itemprop')) { //if(node.getAttribute('itemprop') === 'name') {ImprovedTube.title = node.content;} if(node.getAttribute('itemprop') === 'genre') {ImprovedTube.category = node.content;} //if(node.getAttribute('itemprop') === 'channelId') {ImprovedTube.channelId = node.content;} //if(node.getAttribute('itemprop') === 'videoId') {ImprovedTube.videoId = node.content;} - //The following infos will enable awesome, smart features. Some of which everyone should use. +//The following infos will enable awesome, smart features. Some of which everyone should use. //if(node.getAttribute('itemprop') === 'description') {ImprovedTube.description = node.content;} //if(node.getAttribute('itemprop') === 'duration') {ImprovedTube.duration = node.content;} //if(node.getAttribute('itemprop') === 'interactionCount'){ImprovedTube.views = node.content;} @@ -250,13 +251,20 @@ document.addEventListener('yt-navigate-finish', function () { // if(node.getAttribute('itemprop') === 'datePublished' ){ImprovedTube.datePublished = node.content;} //to use in the "how long ago"-feature, not to fail without API key? just like the "day-of-week"-feature above // if(node.getAttribute('itemprop') === 'uploadDate') {ImprovedTube.uploadDate = node.content;} - */ +*/ ImprovedTube.pageType(); ImprovedTube.YouTubeExperiments(); ImprovedTube.commentsSidebar(); ImprovedTube.categoryRefreshButton(); try { if (ImprovedTube.lastWatchedOverlay) ImprovedTube.lastWatchedOverlay(); } catch (e) { console.error('[LWO] nav-finish error', e); } + // Cleanup playlist handlers when navigating away from playlist pages + if (!location.search.match(ImprovedTube.regex.playlist_id)) { + if (typeof ImprovedTube.cleanupPlaylistHandlers === 'function') { + ImprovedTube.cleanupPlaylistHandlers(); + } + } + // Return YouTube Dislike - call on video pages and Shorts if (document.documentElement.dataset.pageType === 'video' || window.location.pathname.startsWith('/shorts/')) { try { diff --git a/js&css/web-accessible/www.youtube.com/playlist.js b/js&css/web-accessible/www.youtube.com/playlist.js index 5493a72db..87f16d1ba 100644 --- a/js&css/web-accessible/www.youtube.com/playlist.js +++ b/js&css/web-accessible/www.youtube.com/playlist.js @@ -4,15 +4,98 @@ /*------------------------------------------------------------------------------ 4.5.1 UP NEXT AUTOPLAY ------------------------------------------------------------------------------*/ -ImprovedTube.playlistUpNextAutoplay = function () { if (this.storage.playlist_up_next_autoplay === false) { - const playlistData = this.elements.ytd_watch?.playlistData; - if (this.getParam(location.href, 'list') && playlistData - && playlistData.currentIndex - && playlistData.totalVideos - && playlistData.localCurrentIndex) { - playlistData.currentIndex = playlistData.totalVideos; +ImprovedTube.playlistUpNextAutoplay = function () { + if (this.storage.playlist_up_next_autoplay === false) { + const playlistData = this.elements.ytd_watch?.playlistData; + if (this.getParam(location.href, 'list') && playlistData + && playlistData.currentIndex + && playlistData.totalVideos + && playlistData.localCurrentIndex) { + + // Fix for large playlists: ensure proper synchronization instead of forcing end + // YouTube loads playlists in chunks (typically 200 videos), so we need to + // keep currentIndex and localCurrentIndex in sync as new segments load + if (playlistData.currentIndex !== playlistData.localCurrentIndex) { + playlistData.currentIndex = playlistData.localCurrentIndex; + } + + // Monitor for playlist data updates to handle pagination + if (!this.playlistAutoplayObserver) { + this.playlistAutoplayObserver = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && + mutation.attributeName === 'data' && + this.elements.ytd_watch?.playlistData) { + + const updatedData = this.elements.ytd_watch.playlistData; + // Resync when YouTube loads new playlist segments + if (updatedData.currentIndex !== updatedData.localCurrentIndex) { + updatedData.currentIndex = updatedData.localCurrentIndex; + } + } + }); + }); + + // Observe the watch element for playlist data changes + if (this.elements.ytd_watch) { + this.playlistAutoplayObserver.observe(this.elements.ytd_watch, { + attributes: true, + attributeFilter: ['data'] + }); + } + } } + } else { + // Clean up observer when feature is enabled + if (this.playlistAutoplayObserver) { + this.playlistAutoplayObserver.disconnect(); + this.playlistAutoplayObserver = null; + } + } +}; + +// Enhanced playlist navigation handler for large playlists +ImprovedTube.playlistLargePlaylistHandler = function() { + if (!this.getParam(location.href, 'list')) return; + + const playlistData = this.elements.ytd_watch?.playlistData; + if (!playlistData) return; + + // Monitor video changes to handle large playlist navigation + const videoElement = this.elements.player?.querySelector('video'); + if (videoElement && !this.playlistVideoChangeListener) { + this.playlistVideoChangeListener = () => { + setTimeout(() => { + const currentData = this.elements.ytd_watch?.playlistData; + if (currentData && currentData.currentIndex !== currentData.localCurrentIndex) { + // Force synchronization when video changes + currentData.currentIndex = currentData.localCurrentIndex; + + // Update the player's playlist manager if available + const playlistManager = document.querySelector('yt-playlist-manager'); + if (playlistManager && playlistManager.autoplayData) { + playlistManager.autoplayData.currentIndex = currentData.localCurrentIndex; + } + } + }, 100); + }; + + videoElement.addEventListener('loadedmetadata', this.playlistVideoChangeListener); + videoElement.addEventListener('play', this.playlistVideoChangeListener); } + + // Cleanup function for when navigating away from playlist pages + this.cleanupPlaylistHandlers = function() { + if (this.playlistAutoplayObserver) { + this.playlistAutoplayObserver.disconnect(); + this.playlistAutoplayObserver = null; + } + if (this.playlistVideoChangeListener && videoElement) { + videoElement.removeEventListener('loadedmetadata', this.playlistVideoChangeListener); + videoElement.removeEventListener('play', this.playlistVideoChangeListener); + this.playlistVideoChangeListener = null; + } + }; }; /*------------------------------------------------------------------------------ 4.5.2 REVERSE diff --git a/test-large-playlist-fix.js b/test-large-playlist-fix.js new file mode 100644 index 000000000..b46c13e39 --- /dev/null +++ b/test-large-playlist-fix.js @@ -0,0 +1,96 @@ +// Test script to verify the large playlist autoplay fix +// This script can be run in the browser console on a YouTube playlist page + +function testLargePlaylistFix() { + console.log('๐Ÿงช Testing Large Playlist Autoplay Fix'); + + // Check if we're on a playlist page + const playlistId = new URLSearchParams(window.location.search).get('list'); + if (!playlistId) { + console.error('โŒ Not on a playlist page. Please navigate to a YouTube playlist first.'); + return false; + } + + console.log('โœ… Found playlist ID:', playlistId); + + // Check if ImprovedTube is loaded + if (typeof ImprovedTube === 'undefined') { + console.error('โŒ ImprovedTube not loaded. Please ensure the extension is active.'); + return false; + } + + console.log('โœ… ImprovedTube loaded'); + + // Check if our new functions exist + if (typeof ImprovedTube.playlistLargePlaylistHandler !== 'function') { + console.error('โŒ playlistLargePlaylistHandler function not found'); + return false; + } + + if (typeof ImprovedTube.cleanupPlaylistHandlers !== 'function') { + console.error('โŒ cleanupPlaylistHandlers function not found'); + return false; + } + + console.log('โœ… New playlist functions are available'); + + // Test playlist data access + const playlistData = ImprovedTube.elements.ytd_watch?.playlistData; + if (!playlistData) { + console.error('โŒ Playlist data not available. Try playing a video from the playlist first.'); + return false; + } + + console.log('โœ… Playlist data found:', { + currentIndex: playlistData.currentIndex, + localCurrentIndex: playlistData.localCurrentIndex, + totalVideos: playlistData.totalVideos + }); + + // Test the fix by calling our handler + try { + ImprovedTube.playlistLargePlaylistHandler(); + console.log('โœ… playlistLargePlaylistHandler executed successfully'); + } catch (error) { + console.error('โŒ Error in playlistLargePlaylistHandler:', error); + return false; + } + + // Check if observer is created + if (ImprovedTube.playlistAutoplayObserver) { + console.log('โœ… Playlist autoplay observer created'); + } else { + console.log('โ„น๏ธ Playlist autoplay observer not created (may be normal if playlist_up_next_autoplay is enabled)'); + } + + // Test synchronization logic + const testData = ImprovedTube.elements.ytd_watch?.playlistData; + if (testData && testData.currentIndex === testData.localCurrentIndex) { + console.log('โœ… Playlist indices are synchronized'); + } else { + console.log('โ„น๏ธ Playlist indices:', { + currentIndex: testData?.currentIndex, + localCurrentIndex: testData?.localCurrentIndex + }); + } + + console.log('๐ŸŽ‰ Large playlist autoplay fix test completed successfully!'); + console.log('๐Ÿ“ To test with a large playlist:'); + console.log(' 1. Find a playlist with 400+ videos'); + console.log(' 2. Start playing from video #200 or later'); + console.log(' 3. Let videos autoplay to verify the fix works'); + + return true; +} + +// Auto-run test if on a playlist page +if (new URLSearchParams(window.location.search).get('list')) { + setTimeout(testLargePlaylistFix, 2000); +} else { + console.log('โ„น๏ธ Navigate to a YouTube playlist to test the large playlist fix'); +} + +// Export for manual testing +if (typeof window !== 'undefined') { + window.testLargePlaylistFix = testLargePlaylistFix; +}