Add Up Next autoplay controls + SPA transitions to music type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d73f877d18
commit
5960c6e7b1
@ -732,12 +732,120 @@
|
||||
</a>
|
||||
@endif
|
||||
@else
|
||||
<h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;">Up Next</h3>
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:12px;">
|
||||
<h3 style="font-size:16px; font-weight:500; margin:0;">Up Next</h3>
|
||||
<button id="recAutoplayBtn" class="pl-ctrl-btn pl-ctrl-autoplay" title="Autoplay" onclick="recToggleAutoplay()" style="margin-left:auto;">
|
||||
<i class="bi bi-play-circle"></i>
|
||||
<span class="pl-autoplay-label">Autoplay</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var recTransiting = false;
|
||||
var recAutoplay = localStorage.getItem('ytpAutoplay_solo') !== '0';
|
||||
|
||||
function recRender() {
|
||||
var btn = document.getElementById('recAutoplayBtn');
|
||||
if (!btn) return;
|
||||
btn.classList.toggle('pl-ctrl-active', recAutoplay);
|
||||
btn.title = 'Autoplay: ' + (recAutoplay ? 'On' : 'Off');
|
||||
}
|
||||
|
||||
async function recTransitionTo(url, pushHist) {
|
||||
if (recTransiting) return;
|
||||
recTransiting = true;
|
||||
try {
|
||||
var dataUrl = url.split('?')[0] + '/player-data';
|
||||
var resp = await fetch(dataUrl, { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
|
||||
if (!resp.ok) { window.location.href = url; return; }
|
||||
var d = await resp.json();
|
||||
|
||||
var audio = document.getElementById('audioEl');
|
||||
if (audio) {
|
||||
audio.src = d.stream_url;
|
||||
audio.load();
|
||||
audio.play().catch(function(){});
|
||||
}
|
||||
|
||||
var coverImg = document.querySelector('.audio-cover-img');
|
||||
if (coverImg) coverImg.src = d.cover_url || '';
|
||||
|
||||
var titleEl = document.querySelector('.audio-title');
|
||||
if (titleEl) titleEl.textContent = d.title || '';
|
||||
|
||||
if (pushHist !== false) history.pushState({ url: url }, '', url);
|
||||
|
||||
recSwapContent(url);
|
||||
} catch(e) {
|
||||
console.warn('recTransitionTo', e);
|
||||
window.location.href = url;
|
||||
} finally {
|
||||
recTransiting = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function recSwapContent(url) {
|
||||
try {
|
||||
var resp = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
|
||||
if (!resp.ok) return;
|
||||
var html = await resp.text();
|
||||
var parser = new DOMParser();
|
||||
var doc = parser.parseFromString(html, 'text/html');
|
||||
|
||||
var newVdb = doc.getElementById('vdbWrap');
|
||||
var oldVdb = document.getElementById('vdbWrap');
|
||||
if (newVdb && oldVdb) oldVdb.innerHTML = newVdb.innerHTML;
|
||||
|
||||
var newCh = doc.querySelector('.channel-row');
|
||||
var oldCh = document.querySelector('.channel-row');
|
||||
if (newCh && oldCh) oldCh.outerHTML = newCh.outerHTML;
|
||||
|
||||
var newYtc = doc.getElementById('ytcSection');
|
||||
var oldYtc = document.getElementById('ytcSection');
|
||||
if (newYtc && oldYtc) {
|
||||
oldYtc.innerHTML = newYtc.innerHTML;
|
||||
var s = oldYtc.querySelector('script');
|
||||
if (s) { var ns = document.createElement('script'); ns.textContent = s.textContent; s.replaceWith(ns); }
|
||||
}
|
||||
|
||||
var newSidebar = doc.querySelector('.yt-sidebar-container');
|
||||
var oldSidebar = document.querySelector('.yt-sidebar-container');
|
||||
if (newSidebar && oldSidebar) {
|
||||
oldSidebar.innerHTML = newSidebar.innerHTML;
|
||||
oldSidebar.querySelectorAll('script').forEach(function(s){
|
||||
var ns = document.createElement('script'); ns.textContent = s.textContent; s.replaceWith(ns);
|
||||
});
|
||||
}
|
||||
} catch(e) { console.warn('recSwapContent', e); }
|
||||
}
|
||||
|
||||
window.recGoTo = function(url) { if (url) recTransitionTo(url); };
|
||||
window.recToggleAutoplay = function() {
|
||||
recAutoplay = !recAutoplay;
|
||||
localStorage.setItem('ytpAutoplay_solo', recAutoplay ? '1' : '0');
|
||||
recRender();
|
||||
};
|
||||
|
||||
window._plOnTrackEnd = function() {
|
||||
if (!recAutoplay) return;
|
||||
var first = document.querySelector('#recList .sidebar-video-card[data-rec-url]');
|
||||
if (first) recTransitionTo(first.dataset.recUrl);
|
||||
};
|
||||
|
||||
window.addEventListener('popstate', function(e) { if (e.state && e.state.url) recTransitionTo(e.state.url, false); });
|
||||
|
||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', recRender); else recRender();
|
||||
})();
|
||||
</script>
|
||||
|
||||
@if ($recommendedVideos && $recommendedVideos->count() > 0)
|
||||
<div class="recommended-videos-list">
|
||||
<div class="recommended-videos-list" id="recList">
|
||||
@foreach ($recommendedVideos as $recVideo)
|
||||
<div class="sidebar-video-card"
|
||||
onclick="window.location.href='{{ route('videos.show', $recVideo) }}'">
|
||||
data-rec-url="{{ route('videos.show', $recVideo) }}"
|
||||
onclick="recGoTo('{{ route('videos.show', $recVideo) }}')"
|
||||
style="cursor:pointer;">
|
||||
<div class="sidebar-thumb" style="position: relative;">
|
||||
@if ($recVideo->thumbnail)
|
||||
<img src="{{ route('media.thumbnail', $recVideo->thumbnail) }}"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user