Compare commits

...

12 Commits

Author SHA1 Message Date
CodeDevMLH
9a6997f1da Update manifest.json for release v1.6.1.30 [skip ci] 2026-02-14 15:32:46 +00:00
CodeDevMLH
31d315ed8f Bump version to 1.6.1.30
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 54s
2026-02-14 16:31:55 +01:00
CodeDevMLH
2b1301ea0b Refactor video playback management in SlideshowManager for improved performance and auto-unpause functionality 2026-02-14 16:31:38 +01:00
CodeDevMLH
ee8c0b8888 Update manifest.json for release v1.6.1.29 [skip ci] 2026-02-14 15:04:22 +00:00
CodeDevMLH
64ef4915b8 Bump version to 1.6.1.29
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 53s
2026-02-14 16:03:30 +01:00
CodeDevMLH
1f655ed7b6 Enhance SponsorBlock data fetching with caching and improve slide transition logic 2026-02-14 16:03:14 +01:00
CodeDevMLH
0682967591 Update manifest.json for release v1.6.1.28 [skip ci] 2026-02-14 14:38:41 +00:00
CodeDevMLH
7938728f8e Bump version to 1.6.1.28 in project file and manifest
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 53s
2026-02-14 15:37:50 +01:00
CodeDevMLH
a0773c66eb Refactor video playback logic to manage mute state more effectively and improve autoplay handling 2026-02-14 15:37:44 +01:00
CodeDevMLH
10f2a38add Update manifest.json for release v1.6.1.27 [skip ci] 2026-02-14 14:22:59 +00:00
CodeDevMLH
9bfa3ba5ea Bump version to 1.6.1.26
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-02-14 15:22:09 +01:00
CodeDevMLH
5c00c07b8a Refactor video playback logic and enhance slide management 2026-02-14 15:21:32 +01:00
3 changed files with 95 additions and 31 deletions

View File

@@ -12,7 +12,7 @@
<!-- <TreatWarningsAsErrors>false</TreatWarningsAsErrors> --> <!-- <TreatWarningsAsErrors>false</TreatWarningsAsErrors> -->
<Title>Jellyfin Media Bar Enhanced Plugin</Title> <Title>Jellyfin Media Bar Enhanced Plugin</Title>
<Authors>CodeDevMLH</Authors> <Authors>CodeDevMLH</Authors>
<Version>1.6.1.25</Version> <Version>1.6.1.30</Version>
<RepositoryUrl>https://github.com/CodeDevMLH/jellyfin-plugin-media-bar-enhanced</RepositoryUrl> <RepositoryUrl>https://github.com/CodeDevMLH/jellyfin-plugin-media-bar-enhanced</RepositoryUrl>
</PropertyGroup> </PropertyGroup>

View File

@@ -1258,9 +1258,20 @@ const ApiUtils = {
*/ */
async fetchSponsorBlockData(videoId) { async fetchSponsorBlockData(videoId) {
if (!CONFIG.useSponsorBlock) return { intro: null, outro: null }; if (!CONFIG.useSponsorBlock) return { intro: null, outro: null };
// Return cached result if available
if (!this._sponsorBlockCache) this._sponsorBlockCache = {};
if (this._sponsorBlockCache[videoId]) {
return this._sponsorBlockCache[videoId];
}
try { try {
const response = await fetch(`https://sponsor.ajay.app/api/skipSegments?videoID=${videoId}&categories=["intro","outro"]`); const response = await fetch(`https://sponsor.ajay.app/api/skipSegments?videoID=${videoId}&categories=["intro","outro"]`);
if (!response.ok) return { intro: null, outro: null }; if (!response.ok) {
const result = { intro: null, outro: null };
this._sponsorBlockCache[videoId] = result;
return result;
}
const segments = await response.json(); const segments = await response.json();
let intro = null; let intro = null;
@@ -1274,7 +1285,9 @@ const ApiUtils = {
} }
}); });
return { intro, outro }; const result = { intro, outro };
this._sponsorBlockCache[videoId] = result;
return result;
} catch (error) { } catch (error) {
console.warn('Error fetching SponsorBlock data:', error); console.warn('Error fetching SponsorBlock data:', error);
return { intro: null, outro: null }; return { intro: null, outro: null };
@@ -1709,13 +1722,6 @@ const SlideCreator = {
event.target._endTime = playerVars.end || undefined; event.target._endTime = playerVars.end || undefined;
event.target._videoId = videoId; event.target._videoId = videoId;
if (STATE.slideshow.isMuted) {
event.target.mute();
} else {
event.target.unMute();
event.target.setVolume(40);
}
if (typeof event.target.setPlaybackQuality === 'function') { if (typeof event.target.setPlaybackQuality === 'function') {
event.target.setPlaybackQuality(quality); event.target.setPlaybackQuality(quality);
} }
@@ -1725,6 +1731,14 @@ const SlideCreator = {
const isVideoPlayerOpen = document.querySelector('.videoPlayerContainer') || document.querySelector('.youtubePlayerContainer'); const isVideoPlayerOpen = document.querySelector('.videoPlayerContainer') || document.querySelector('.youtubePlayerContainer');
if (slide && slide.classList.contains('active') && !document.hidden && (!isVideoPlayerOpen || isVideoPlayerOpen.classList.contains('hide'))) { if (slide && slide.classList.contains('active') && !document.hidden && (!isVideoPlayerOpen || isVideoPlayerOpen.classList.contains('hide'))) {
// Set mute state only for active slide
if (STATE.slideshow.isMuted) {
event.target.mute();
} else {
event.target.unMute();
event.target.setVolume(40);
}
event.target.playVideo(); event.target.playVideo();
// Check if it actually started playing after a short delay (handling autoplay blocks) // Check if it actually started playing after a short delay (handling autoplay blocks)
@@ -1754,14 +1768,19 @@ const SlideCreator = {
if (CONFIG.waitForTrailerToEnd && STATE.slideshow.slideInterval) { if (CONFIG.waitForTrailerToEnd && STATE.slideshow.slideInterval) {
STATE.slideshow.slideInterval.stop(); STATE.slideshow.slideInterval.stop();
} }
} else {
event.target.mute();
} }
}, },
'onStateChange': (event) => { 'onStateChange': (event) => {
if (event.data === YT.PlayerState.ENDED) { if (event.data === YT.PlayerState.ENDED) {
if (CONFIG.waitForTrailerToEnd) { const slide = document.querySelector(`.slide[data-item-id="${itemId}"]`);
SlideshowManager.nextSlide(); if (slide && slide.classList.contains('active')) {
} else { if (CONFIG.waitForTrailerToEnd) {
event.target.playVideo(); // Loop if not waiting for end if trailer is shorter than slide duration SlideshowManager.nextSlide();
} else {
event.target.playVideo(); // Loop if trailer is shorter than slide duration
}
} }
} }
}, },
@@ -1804,15 +1823,23 @@ const SlideCreator = {
STATE.slideshow.videoPlayers[itemId] = backdrop; STATE.slideshow.videoPlayers[itemId] = backdrop;
backdrop.addEventListener('play', () => { backdrop.addEventListener('play', () => {
// backdrop.addEventListener('play', (event) => {
// const slide = document.querySelector(`.slide[data-item-id="${itemId}"]`);
// if (!slide || !slide.classList.contains('active')) {
// console.log(`Local video ${itemId} started playing but is not active, pausing.`);
// event.target.pause();
// event.target.currentTime = 0;
// return;
// }
if (CONFIG.waitForTrailerToEnd && STATE.slideshow.slideInterval) { if (CONFIG.waitForTrailerToEnd && STATE.slideshow.slideInterval) {
STATE.slideshow.slideInterval.stop(); STATE.slideshow.slideInterval.stop();
} }
}); });
backdrop.addEventListener('ended', () => { backdrop.addEventListener('ended', () => {
if (CONFIG.waitForTrailerToEnd) {
SlideshowManager.nextSlide(); SlideshowManager.nextSlide();
}
}); });
backdrop.addEventListener('error', () => { backdrop.addEventListener('error', () => {
@@ -2271,13 +2298,26 @@ const SlideshowManager = {
if (previousVisibleSlide) { if (previousVisibleSlide) {
previousVisibleSlide.classList.remove("active"); previousVisibleSlide.classList.remove("active");
// previousVisibleSlide.setAttribute("inert", "");
// previousVisibleSlide.setAttribute("tabindex", "-1");
} }
currentSlide.classList.add("active"); currentSlide.classList.add("active");
// // Update Play/Pause Button State if it was paused
// if (STATE.slideshow.isPaused) {
// STATE.slideshow.isPaused = false;
// const pauseButton = document.querySelector('.pause-button');
// if (pauseButton) {
// pauseButton.innerHTML = '<i class="material-icons">pause</i>';
// const pauseLabel = LocalizationUtils.getLocalizedString('ButtonPause', 'Pause');
// pauseButton.setAttribute("aria-label", pauseLabel);
// pauseButton.setAttribute("title", pauseLabel);
// }
// }
// Manage Video Playback: Stop others, Play current // Manage Video Playback: Stop others, Play current
// 1. Pause all other YouTube players // 1. Stop all other YouTube players
if (STATE.slideshow.videoPlayers) { if (STATE.slideshow.videoPlayers) {
Object.keys(STATE.slideshow.videoPlayers).forEach(id => { Object.keys(STATE.slideshow.videoPlayers).forEach(id => {
if (id !== currentItemId) { if (id !== currentItemId) {
@@ -2299,6 +2339,18 @@ const SlideshowManager = {
// 3. Play and Reset current video // 3. Play and Reset current video
const videoBackdrop = currentSlide.querySelector('.video-backdrop'); const videoBackdrop = currentSlide.querySelector('.video-backdrop');
// Auto-unpause when a video slide becomes active
if (videoBackdrop && STATE.slideshow.isPaused) {
STATE.slideshow.isPaused = false;
const pauseButton = document.querySelector('.pause-button');
if (pauseButton) {
pauseButton.innerHTML = '<i class="material-icons">pause</i>';
const pauseLabel = LocalizationUtils.getLocalizedString('ButtonPause', 'Pause');
pauseButton.setAttribute("aria-label", pauseLabel);
pauseButton.setAttribute("title", pauseLabel);
}
}
// Update mute button visibility // Update mute button visibility
const muteButton = document.querySelector('.mute-button'); const muteButton = document.querySelector('.mute-button');
if (muteButton) { if (muteButton) {
@@ -2318,8 +2370,8 @@ const SlideshowManager = {
videoBackdrop.play().catch(e => { videoBackdrop.play().catch(e => {
// Check if it actually started playing after a short delay (handling autoplay blocks) // Check if it actually started playing after a short delay (handling autoplay blocks)
setTimeout(() => { setTimeout(() => {
if (videoBackdrop.paused) { if (videoBackdrop.paused && currentSlide.classList.contains('active')) {
console.warn(`Autoplay blocked for ${itemId}, attempting muted fallback`); console.warn(`Autoplay blocked for ${currentItemId}, attempting muted fallback`);
videoBackdrop.muted = true; videoBackdrop.muted = true;
videoBackdrop.play().catch(err => console.error("Muted fallback failed", err)); videoBackdrop.play().catch(err => console.error("Muted fallback failed", err));
} }
@@ -2344,6 +2396,7 @@ const SlideshowManager = {
// Check if playback successfully started, otherwise fallback to muted // Check if playback successfully started, otherwise fallback to muted
setTimeout(() => { setTimeout(() => {
if (!currentSlide.classList.contains('active')) return;
if (player.getPlayerState && if (player.getPlayerState &&
player.getPlayerState() !== YT.PlayerState.PLAYING && player.getPlayerState() !== YT.PlayerState.PLAYING &&
player.getPlayerState() !== YT.PlayerState.BUFFERING) { player.getPlayerState() !== YT.PlayerState.BUFFERING) {
@@ -2394,7 +2447,8 @@ const SlideshowManager = {
this.updateDots(); this.updateDots();
// Only restart interval if we are NOT waiting for a video to end // Only restart interval if we are NOT waiting for a video to end
const hasVideo = currentSlide.querySelector('.video-backdrop'); const hasVideo = currentSlide.querySelector('.video-backdrop') ||
(STATE.slideshow.videoPlayers && STATE.slideshow.videoPlayers[currentItemId]);
if (STATE.slideshow.slideInterval && !STATE.slideshow.isPaused) { if (STATE.slideshow.slideInterval && !STATE.slideshow.isPaused) {
if (CONFIG.waitForTrailerToEnd && hasVideo) { if (CONFIG.waitForTrailerToEnd && hasVideo) {
STATE.slideshow.slideInterval.stop(); STATE.slideshow.slideInterval.stop();
@@ -2454,20 +2508,24 @@ const SlideshowManager = {
const totalItems = STATE.slideshow.totalItems; const totalItems = STATE.slideshow.totalItems;
const preloadCount = Math.min(Math.max(CONFIG.preloadCount || 1, 1), 5); const preloadCount = Math.min(Math.max(CONFIG.preloadCount || 1, 1), 5);
const preloadedIds = new Set(); const preloadedIds = new Set();
// Preload next slides // Preload next slides
for (let i = 1; i <= preloadCount; i++) { for (let i = 1; i <= preloadCount; i++) {
const nextIndex = (currentIndex + i) % totalItems; const nextIndex = (currentIndex + i) % totalItems;
if (nextIndex === currentIndex) break; if (nextIndex === currentIndex) break;
const itemId = STATE.slideshow.itemIds[nextIndex]; const itemId = STATE.slideshow.itemIds[nextIndex];
if (!preloadedIds.has(itemId)) { if (!preloadedIds.has(itemId)) {
preloadedIds.add(itemId); preloadedIds.add(itemId);
SlideCreator.createSlideForItemId(itemId); SlideCreator.createSlideForItemId(itemId);
} }
} }
// Preload previous slides // Preload previous slides
for (let i = 1; i <= preloadCount; i++) { for (let i = 1; i <= preloadCount; i++) {
const prevIndex = (currentIndex - i + totalItems) % totalItems; const prevIndex = (currentIndex - i + totalItems) % totalItems;
if (prevIndex === currentIndex) break; if (prevIndex === currentIndex) break;
const prevItemId = STATE.slideshow.itemIds[prevIndex]; const prevItemId = STATE.slideshow.itemIds[prevIndex];
if (!preloadedIds.has(prevItemId)) { if (!preloadedIds.has(prevItemId)) {
preloadedIds.add(prevItemId); preloadedIds.add(prevItemId);
@@ -2724,18 +2782,24 @@ const SlideshowManager = {
const currentSlide = document.querySelector(`.slide[data-item-id="${currentItemId}"]`); const currentSlide = document.querySelector(`.slide[data-item-id="${currentItemId}"]`);
if (!currentSlide) return; if (!currentSlide) return;
// 1. Try YouTube Player // YouTube player: just resume, don't reload
const ytPlayer = STATE.slideshow.videoPlayers[currentItemId]; const ytPlayer = STATE.slideshow.videoPlayers?.[currentItemId];
if (ytPlayer && typeof ytPlayer.playVideo === 'function') { if (ytPlayer && typeof ytPlayer.playVideo === 'function') {
if (STATE.slideshow.isMuted) {
if (typeof ytPlayer.mute === 'function') ytPlayer.mute();
} else {
if (typeof ytPlayer.unMute === 'function') ytPlayer.unMute();
if (typeof ytPlayer.setVolume === 'function') ytPlayer.setVolume(40);
}
ytPlayer.playVideo(); ytPlayer.playVideo();
return;
} }
// 2. Try HTML5 Video // HTML5 video: just resume, don't reset currentTime
const html5Video = currentSlide.querySelector('video'); const html5Video = currentSlide.querySelector('video.video-backdrop');
if (html5Video) { if (html5Video) {
if (STATE.slideshow.isMuted) { html5Video.muted = STATE.slideshow.isMuted;
html5Video.muted = true; if (!STATE.slideshow.isMuted) html5Video.volume = 0.4;
}
html5Video.play().catch(e => console.warn("Error resuming HTML5 video:", e)); html5Video.play().catch(e => console.warn("Error resuming HTML5 video:", e));
} }
}, },

View File

@@ -9,12 +9,12 @@
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/raw/branch/main/logo.png", "imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/raw/branch/main/logo.png",
"versions": [ "versions": [
{ {
"version": "1.6.1.25", "version": "1.6.1.30",
"changelog": "- fix tv mode issue\n- refactor video playback management", "changelog": "- fix tv mode issue\n- refactor video playback management",
"targetAbi": "10.11.0.0", "targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.6.1.25/Jellyfin.Plugin.MediaBarEnhanced.zip", "sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.6.1.30/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "283886aa51ccd1d40f057b65617b7ea4", "checksum": "429d556a12eed8530e6c5d0cf28e55f1",
"timestamp": "2026-02-14T02:21:14Z" "timestamp": "2026-02-14T15:32:46Z"
}, },
{ {
"version": "1.6.0.2", "version": "1.6.0.2",