-

-

+ {{-- Cover art / slideshow — both always in DOM so SPA transitions can switch between them --}}
+

1) style="display:none"@endif>
+
+

+
- @else
-

- @endif
{{-- Bars canvas overlay --}}
@@ -726,107 +723,119 @@ function stopBars() {
}
// ── Crossfade slideshow ───────────────────────────────────────
+// Variables hoisted outside the if-block so the SPA update hook can access them
const SLIDE_URLS = @json($slideUrls);
-if (SLIDE_URLS.length > 1) {
- const slideA = document.getElementById('slideA');
- const slideB = document.getElementById('slideB');
- let currentSlide = 0;
- let aIsTop = true; // slideA starts on top (opacity 1)
+const slideA = document.getElementById('slideA');
+const slideB = document.getElementById('slideB');
+let currentSlide = 0;
+let aIsTop = true;
+let slideshowTimer = null;
+const slideOrientations = new Array(Math.max(SLIDE_URLS.length, 1)).fill(false);
- // Preload all images and detect portrait orientation
- const slideOrientations = new Array(SLIDE_URLS.length).fill(false); // true = portrait
- SLIDE_URLS.forEach((url, idx) => {
- const img = new Image();
- img.onload = () => { slideOrientations[idx] = img.naturalHeight > img.naturalWidth; };
- img.src = url;
- });
-
- function applyOrientation(el, idx) {
- el.classList.toggle('portrait', !!slideOrientations[idx]);
- }
-
- function getSlideInterval() {
- return audio.duration ? (audio.duration / SLIDE_URLS.length) * 1000 : 8000;
- }
-
- function advanceSlide() {
- currentSlide = (currentSlide + 1) % SLIDE_URLS.length;
- const next = SLIDE_URLS[currentSlide];
-
- if (aIsTop) {
- slideB.src = next;
- applyOrientation(slideB, currentSlide);
- slideB.style.zIndex = '2';
- slideB.style.opacity = '1';
- slideA.style.opacity = '0';
- slideA.style.zIndex = '1';
- } else {
- slideA.src = next;
- applyOrientation(slideA, currentSlide);
- slideA.style.zIndex = '2';
- slideA.style.opacity = '1';
- slideB.style.opacity = '0';
- slideB.style.zIndex = '1';
- }
- aIsTop = !aIsTop;
- }
-
- let slideshowTimer = null;
-
- function startSlideshow() {
- if (slideshowTimer || SLIDE_URLS.length <= 1) return;
- slideshowTimer = setInterval(advanceSlide, getSlideInterval());
- }
- function stopSlideshow() {
- clearInterval(slideshowTimer);
- slideshowTimer = null;
- }
-
- audio.addEventListener('play', startSlideshow);
- audio.addEventListener('pause', stopSlideshow);
- audio.addEventListener('ended', stopSlideshow);
-
- // Seek: jump to the correct slide instantly (no transition flash)
- audio.addEventListener('seeked', () => {
- if (!audio.duration) return;
- stopSlideshow();
- const idx = Math.min(
- Math.floor((audio.currentTime / audio.duration) * SLIDE_URLS.length),
- SLIDE_URLS.length - 1
- );
- currentSlide = idx;
- // Reset: show correct slide without animation
- slideA.style.transition = 'none';
- slideB.style.transition = 'none';
- slideA.src = SLIDE_URLS[idx];
- applyOrientation(slideA, idx);
- slideA.style.opacity = '1'; slideA.style.zIndex = '2';
+function applyOrientation(el, idx) {
+ if (!el) return;
+ el.classList.toggle('portrait', !!slideOrientations[idx]);
+}
+function getSlideInterval() {
+ return audio.duration ? (audio.duration / SLIDE_URLS.length) * 1000 : 8000;
+}
+function advanceSlide() {
+ if (!slideA || !slideB || SLIDE_URLS.length <= 1) return;
+ currentSlide = (currentSlide + 1) % SLIDE_URLS.length;
+ const next = SLIDE_URLS[currentSlide];
+ if (aIsTop) {
+ slideB.src = next; applyOrientation(slideB, currentSlide);
+ slideB.style.zIndex = '2'; slideB.style.opacity = '1';
+ slideA.style.opacity = '0'; slideA.style.zIndex = '1';
+ } else {
+ slideA.src = next; applyOrientation(slideA, currentSlide);
+ slideA.style.zIndex = '2'; slideA.style.opacity = '1';
slideB.style.opacity = '0'; slideB.style.zIndex = '1';
- const nextIdx = (idx + 1) % SLIDE_URLS.length;
- slideB.src = SLIDE_URLS[nextIdx];
- applyOrientation(slideB, nextIdx);
- aIsTop = true;
- requestAnimationFrame(() => {
- slideA.style.transition = '';
- slideB.style.transition = '';
- });
- if (!audio.paused) startSlideshow();
- });
+ }
+ aIsTop = !aIsTop;
+}
+function startSlideshow() {
+ if (slideshowTimer || SLIDE_URLS.length <= 1) return;
+ slideshowTimer = setInterval(advanceSlide, getSlideInterval());
+}
+function stopSlideshow() {
+ clearInterval(slideshowTimer);
+ slideshowTimer = null;
+}
- // Extract colors + apply orientation for first slide
+// Always attach listeners; functions guard themselves with SLIDE_URLS.length check
+audio.addEventListener('play', startSlideshow);
+audio.addEventListener('pause', stopSlideshow);
+audio.addEventListener('ended', stopSlideshow);
+
+audio.addEventListener('seeked', () => {
+ if (SLIDE_URLS.length <= 1 || !slideA || !slideB || !audio.duration) return;
+ stopSlideshow();
+ const idx = Math.min(Math.floor((audio.currentTime / audio.duration) * SLIDE_URLS.length), SLIDE_URLS.length - 1);
+ currentSlide = idx;
+ slideA.style.transition = 'none'; slideB.style.transition = 'none';
+ slideA.src = SLIDE_URLS[idx]; applyOrientation(slideA, idx);
+ slideA.style.opacity = '1'; slideA.style.zIndex = '2';
+ slideB.style.opacity = '0'; slideB.style.zIndex = '1';
+ const nextIdx = (idx + 1) % SLIDE_URLS.length;
+ slideB.src = SLIDE_URLS[nextIdx]; applyOrientation(slideB, nextIdx);
+ aIsTop = true;
+ requestAnimationFrame(() => { slideA.style.transition = ''; slideB.style.transition = ''; });
+ if (!audio.paused) startSlideshow();
+});
+
+// Preload orientations
+SLIDE_URLS.forEach((url, idx) => {
+ const img = new Image();
+ img.onload = () => { slideOrientations[idx] = img.naturalHeight > img.naturalWidth; };
+ img.src = url;
+});
+
+if (SLIDE_URLS.length > 1 && slideA) {
function initSlideA() {
extractColors(slideA);
const portrait = slideA.naturalHeight > slideA.naturalWidth;
slideA.classList.toggle('portrait', portrait);
slideOrientations[0] = portrait;
}
- if (slideA.complete && slideA.naturalWidth) {
- initSlideA();
- } else {
- slideA.addEventListener('load', initSlideA, { once: true });
- }
+ if (slideA.complete && slideA.naturalWidth) initSlideA();
+ else slideA.addEventListener('load', initSlideA, { once: true });
}
+// ── SPA update hook — called by recTransitionTo / plTransitionTo ──────────
+window._audioPlayerUpdate = function(d) {
+ var newSlides = (d.slides && d.slides.length > 1) ? d.slides : [];
+ var coverEl = document.getElementById('audioCoverImg');
+ var slideshowEl = document.getElementById('slideshowWrap');
+
+ stopSlideshow();
+ SLIDE_URLS.length = 0;
+
+ if (newSlides.length > 1) {
+ newSlides.forEach(function(s) { SLIDE_URLS.push(s); });
+ if (coverEl) coverEl.style.display = 'none';
+ if (slideshowEl) slideshowEl.style.display = '';
+ currentSlide = 0; aIsTop = true;
+ if (slideA) { slideA.style.transition='none'; slideA.src=newSlides[0]; slideA.style.opacity='1'; slideA.style.zIndex='2'; }
+ if (slideB) { slideB.style.transition='none'; slideB.src=newSlides[1]||newSlides[0]; slideB.style.opacity='0'; slideB.style.zIndex='1'; }
+ requestAnimationFrame(function() { if(slideA) slideA.style.transition=''; if(slideB) slideB.style.transition=''; });
+ } else {
+ if (slideshowEl) slideshowEl.style.display = 'none';
+ if (coverEl) { coverEl.style.display = ''; coverEl.src = d.cover_url || ''; }
+ }
+
+ // Update page title
+ var titleEl = document.querySelector('.video-title span');
+ if (titleEl) titleEl.textContent = d.title || '';
+ document.title = (d.title || '') + ' | {{ config("app.name") }}';
+
+ // Reset progress bar
+ if (played) played.style.width = '0%';
+ if (scrubber) scrubber.parentElement.style.left = '0%';
+ if (timeCur) timeCur.textContent = '0:00';
+ if (timeDur && d.duration) timeDur.textContent = fmt(d.duration);
+};
+
// ── Init ─────────────────────────────────────────────────────
const savedVol = localStorage.getItem('ytpVolume');
const savedMuted = localStorage.getItem('ytpMuted');
diff --git a/resources/views/videos/types/music.blade.php b/resources/views/videos/types/music.blade.php
index 67b49bc..ee06206 100644
--- a/resources/views/videos/types/music.blade.php
+++ b/resources/views/videos/types/music.blade.php
@@ -559,24 +559,8 @@
var audio = document.getElementById('audioEl');
if (audio) { audio.src=d.stream_url; audio.load(); }
- // update cover / slideshow
- var ci = document.getElementById('audioCoverImg');
- if (ci) ci.src = d.cover_url;
- var sa = document.getElementById('slideA');
- if (sa) sa.src = d.cover_url;
-
- // reset progress
- var pl=document.getElementById('ytpPlayed'), sc=document.getElementById('ytpScrubber');
- var cu=document.getElementById('ytpCurrent'), dr=document.getElementById('ytpDuration');
- if(pl) pl.style.width='0%';
- if(sc) sc.style.left='0%';
- if(cu) cu.textContent='0:00';
- if(dr&&d.duration) dr.textContent=plFmt(d.duration);
-
- // update visible title
- var ts=document.querySelector('.video-title span');
- if(ts) ts.textContent=d.title;
- document.title=d.title+' | {{ config("app.name") }}';
+ // update cover / slideshow / title / progress via shared hook
+ if (window._audioPlayerUpdate) window._audioPlayerUpdate(d);
// update state
PL_CURRENT = d.id;
@@ -762,17 +746,11 @@
var d = await resp.json();
var audio = document.getElementById('audioEl');
- if (audio) {
- audio.src = d.stream_url;
- audio.load();
- audio.play().catch(function(){});
- }
+ if (audio) { audio.src = d.stream_url; audio.load(); }
- var coverImg = document.querySelector('.audio-cover-img');
- if (coverImg) coverImg.src = d.cover_url || '';
+ if (window._audioPlayerUpdate) window._audioPlayerUpdate(d);
- var titleEl = document.querySelector('.audio-title');
- if (titleEl) titleEl.textContent = d.title || '';
+ if (audio) audio.play().catch(function(){});
if (pushHist !== false) history.pushState({ url: url }, '', url);