Fix progress bar seek pausing playback + add persistent mini-player

- video-player: add userSeeking flag so mid-seek pause events are
  suppressed; force-resume on 'seeked' if video was playing before seek;
  guard player click handler against progCont clicks; e.preventDefault()
  on touchend to stop synthetic click toggling play
- audio-player: apply identical seek fixes (same four changes)
- app layout: add floating mini-player that saves video state to
  sessionStorage when bottom nav is tapped while a video is playing,
  then restores playback on the next page via a floating overlay

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
ghassanandClaude Sonnet 4.6 committed 2026-05-14 01:45:20 +03:00
1 parent 615e7efd7c
commit d1441b213a
3 files changed
+264 -13

No files matched your search

+204
View File
@@ -1597,6 +1597,210 @@
@endif
@endauth
<!-- ── Persistent Mini-Player ─────────────────────────────────────────
Shown on non-video pages when the user navigated away while a
video was playing. State is stored in sessionStorage under the
key "ytpMiniState".
──────────────────────────────────────────────────────────────────── -->
<div id="ytpMini" style="display:none;" aria-label="Mini player">
<div id="ytpMiniVideo">
<video id="ytpMiniVid" playsinline></video>
</div>
<div id="ytpMiniBar">
<div id="ytpMiniInfo">
<span id="ytpMiniTitle"></span>
</div>
<div id="ytpMiniControls">
<button id="ytpMiniPlay" title="Play / Pause"><i class="bi bi-play-fill"></i></button>
<a id="ytpMiniExpand" title="Open video"><i class="bi bi-box-arrow-up-right"></i></a>
<button id="ytpMiniClose" title="Close"><i class="bi bi-x-lg"></i></button>
</div>
</div>
</div>
<style>
#ytpMini {
position: fixed;
bottom: calc(64px + env(safe-area-inset-bottom, 0px));
right: 12px;
width: 280px;
background: #1a1a1a;
border: 1px solid #333;
border-radius: 10px;
overflow: hidden;
z-index: 1999;
box-shadow: 0 4px 24px rgba(0,0,0,.6);
}
#ytpMiniVideo {
position: relative;
width: 100%;
aspect-ratio: 16/9;
background: #000;
}
#ytpMiniVid { width:100%; height:100%; object-fit:contain; display:block; }
#ytpMiniBar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 8px;
gap: 6px;
background: #1a1a1a;
}
#ytpMiniInfo {
flex: 1;
min-width: 0;
}
#ytpMiniTitle {
font-size: 12px;
color: #eee;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
}
#ytpMiniControls {
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
}
#ytpMiniControls button,
#ytpMiniControls a {
background: none;
border: none;
color: #ccc;
cursor: pointer;
padding: 4px 6px;
font-size: 16px;
border-radius: 4px;
text-decoration: none;
line-height: 1;
}
#ytpMiniControls button:hover,
#ytpMiniControls a:hover { color: #fff; background: rgba(255,255,255,.1); }
@media (max-width: 480px) {
#ytpMini { width: calc(100vw - 24px); right: 12px; }
}
</style>
<script>
(function () {
var STORAGE_KEY = 'ytpMiniState';
/* ── Save state when nav bar is tapped while video plays ── */
document.querySelectorAll('.yt-bottom-nav-item').forEach(function (link) {
link.addEventListener('click', function () {
var v = document.getElementById('videoPlayer');
if (!v || v.paused || !v.currentSrc) return;
var title = document.title.replace(/\s*\|.*$/, '').trim();
var videoPageUrl = window.location.href;
sessionStorage.setItem(STORAGE_KEY, JSON.stringify({
src: v.currentSrc,
time: v.currentTime,
title: title,
url: videoPageUrl,
muted: v.muted,
volume: v.volume
}));
});
});
/* ── On page load, restore mini-player if state exists ── */
document.addEventListener('DOMContentLoaded', function () {
/* If we're on the video page itself, clear the saved state */
var mainVideo = document.getElementById('videoPlayer');
if (mainVideo) {
sessionStorage.removeItem(STORAGE_KEY);
return;
}
var raw = sessionStorage.getItem(STORAGE_KEY);
if (!raw) return;
var state;
try { state = JSON.parse(raw); } catch(e) { return; }
if (!state || !state.src) return;
var wrap = document.getElementById('ytpMini');
var vid = document.getElementById('ytpMiniVid');
var titleEl = document.getElementById('ytpMiniTitle');
var playBtn = document.getElementById('ytpMiniPlay');
var expandBtn = document.getElementById('ytpMiniExpand');
var closeBtn = document.getElementById('ytpMiniClose');
if (!wrap || !vid) return;
titleEl.textContent = state.title || 'Video';
expandBtn.href = state.url || '#';
vid.volume = state.volume ?? 0.5;
vid.muted = state.muted ?? false;
/* Load HLS.js if needed, then attach source */
function startMini(Hls) {
if (Hls && Hls.isSupported() && /\.m3u8/.test(state.src)) {
var hls = new Hls({ startLevel: -1 });
hls.loadSource(state.src);
hls.attachMedia(vid);
hls.on(Hls.Events.MANIFEST_PARSED, function () {
vid.currentTime = state.time || 0;
vid.play().catch(function () {
vid.muted = true;
vid.play().catch(function () {});
});
});
} else {
vid.src = state.src;
vid.addEventListener('loadedmetadata', function () {
vid.currentTime = state.time || 0;
}, { once: true });
vid.play().catch(function () {
vid.muted = true;
vid.play().catch(function () {});
});
}
wrap.style.display = 'block';
}
if (window.Hls) {
startMini(window.Hls);
} else {
var s = document.createElement('script');
s.src = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/hls.min.js';
s.onload = function () { startMini(window.Hls); };
s.onerror = function () { startMini(null); };
document.head.appendChild(s);
}
/* Play / pause toggle */
vid.addEventListener('play', function () { playBtn.querySelector('i').className = 'bi bi-pause-fill'; });
vid.addEventListener('pause', function () { playBtn.querySelector('i').className = 'bi bi-play-fill'; });
playBtn.addEventListener('click', function () {
if (vid.paused) { vid.play().catch(function(){}); }
else { vid.pause(); }
});
/* Expand → navigate to video page */
expandBtn.addEventListener('click', function (e) {
e.preventDefault();
var t = vid.currentTime;
sessionStorage.setItem(STORAGE_KEY, JSON.stringify({
src: state.src, time: t, title: state.title,
url: state.url, muted: vid.muted, volume: vid.volume
}));
window.location.href = state.url;
});
/* Close */
closeBtn.addEventListener('click', function () {
vid.pause();
wrap.style.display = 'none';
sessionStorage.removeItem(STORAGE_KEY);
});
});
})();
</script>
</body>
</html>