Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions js&css/web-accessible/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}
};

Expand Down
16 changes: 12 additions & 4 deletions js&css/web-accessible/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
});
Expand Down Expand Up @@ -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;}
Expand All @@ -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 {
Expand Down
97 changes: 90 additions & 7 deletions js&css/web-accessible/www.youtube.com/playlist.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
96 changes: 96 additions & 0 deletions test-large-playlist-fix.js
Original file line number Diff line number Diff line change
@@ -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;
}
Loading