SPA playlist transitions for generic (video) type

- Add _ytpLoadSource(hlsUrl, mp4Url) to video-player component:
  destroys old HLS instance, creates a new one with the new source,
  then plays — browser retains autoplay permission since the <video>
  element never leaves the page
- Add _ytpNavOverride hook: playlist overlay can replace navigateNext/
  navigatePrev without modifying the component internals
- Add _plOnVideoEnd hook to 'ended' handler so the playlist overlay
  can control autoplay/loop behavior independently
- Expose window._ytpHls for HLS instance lifecycle management
- Add hls_url + has_hls to /videos/{video}/player-data JSON endpoint
- Replace generic.blade.php playlist controls with full SPA system
  identical in structure to music type: plTransitionTo, plSwapContent,
  plAdj, plRender, plHighlight — no page refresh on track change
- Sidebar shows all playlist tracks; current track highlighted in red

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
ghassanandClaude Sonnet 4.6 committed 2026-05-16 13:16:25 +03:00
1 parent da02425aeb
commit 77e7b950be
3 files changed
+200 -41

No files matched your search

@@ -806,18 +806,38 @@ function getShuffledPrevUrl() {
return vids[order[posPrev]]?.showUrl ?? PREV_URL;
}
window._ytpHls = null;
function initSource() {
if (HLS_URL && window.Hls && Hls.isSupported()) {
const hls = new Hls({ startLevel: -1 });
hls.loadSource(HLS_URL);
hls.attachMedia(video);
window._ytpHls = new Hls({ startLevel: -1 });
window._ytpHls.loadSource(HLS_URL);
window._ytpHls.attachMedia(video);
} else if (HLS_URL && video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = HLS_URL; // Safari native HLS
video.src = HLS_URL;
} else {
video.src = MP4_URL;
}
}
// Reinitialize source after SPA transition — called by playlist overlay scripts
window._ytpLoadSource = function(hlsUrl, mp4Url) {
if (window._ytpHls) { window._ytpHls.destroy(); window._ytpHls = null; }
if (hlsUrl && window.Hls && Hls.isSupported()) {
window._ytpHls = new Hls({ startLevel: -1 });
window._ytpHls.loadSource(hlsUrl);
window._ytpHls.attachMedia(video);
} else if (hlsUrl && video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = hlsUrl;
video.load();
} else {
video.src = mp4Url;
video.load();
}
video.muted = false;
video.play().catch(function(){});
};
// Load HLS.js from CDN then init
function loadHlsJs(cb) {
if (window.Hls) { cb(); return; }
@@ -1169,10 +1189,12 @@ video.addEventListener('playing', () => spinner.classList.remove('active'));
video.addEventListener('canplay', () => spinner.classList.remove('active'));
function navigateNext() {
const url = shuffleOn ? getShuffledNextUrl() : NEXT_URL;
if (window._ytpNavOverride?.next) { window._ytpNavOverride.next(url); return; }
if (url) window.location.href = url;
}
function navigatePrev() {
const url = shuffleOn ? getShuffledPrevUrl() : PREV_URL;
if (window._ytpNavOverride?.prev) { window._ytpNavOverride.prev(url); return; }
if (url) window.location.href = url;
}
window._ytpNav = { next: navigateNext, prev: navigatePrev };
@@ -1182,7 +1204,9 @@ video.addEventListener('ended', () => {
largePlay.classList.add('visible');
clearTimeout(hideTimer);
releaseWakeLock();
if (autoplayOn || isLooping) {
if (window._plOnVideoEnd) {
window._plOnVideoEnd();
} else if (autoplayOn || isLooping) {
navigateNext();
}
});