Refactor movie player and navigation logic

Co-authored-by: mathieumoran1 <[email protected]>
This commit is contained in:
Cursor Agent
2025-12-21 00:50:45 +00:00
co-authored by mathieumoran1
parent 7a7d94968b
commit b72f1bde82
3 changed files with 146 additions and 24 deletions
+9 -1
View File
@@ -117,7 +117,15 @@ window.onload = async function () {
const isChecked = watchedMoviesInYear.some(watchedMovie => watchedMovie.imdb_id === movie.imdb_id);
const watchedMovie = watchedMoviesInYear.find(wm => wm.imdb_id === movie.imdb_id);
const watchedDate = watchedMovie ? new Date(watchedMovie.watchedDate).toLocaleString() : '';
const playerUrl = movie && movie._id ? `/player.html?id=${encodeURIComponent(movie._id)}` : (movie.vod_link || '#');
const hasVideoSrc = !!(movie && typeof movie.video_src === 'string' && movie.video_src.trim());
const hasEmbedSrc = !!(movie && typeof movie.embed_src === 'string' && movie.embed_src.trim());
const hasNewPlayerSource = hasVideoSrc || hasEmbedSrc;
const hasLegacy = !!(movie && typeof movie.vod_link === 'string' && movie.vod_link.trim());
// If the movie only has legacy `vod_link`, skip the player entirely and open the original URL in a new tab.
const playerUrl = (movie && movie._id && hasNewPlayerSource)
? `/player.html?id=${encodeURIComponent(movie._id)}`
: (hasLegacy ? movie.vod_link : (movie && movie._id ? `/player.html?id=${encodeURIComponent(movie._id)}` : '#'));
const movieDiv = document.createElement('div');
movieDiv.classList.add('col-md-4', 'mb-4', 'movie-card');
+97 -16
View File
@@ -11,9 +11,24 @@ function showAlert(message) {
const el = document.getElementById('alert');
if (!el) return;
el.textContent = message;
// Ensure error styling (danger) when using the simple API.
el.classList.remove('alert-primary', 'alert-secondary', 'alert-success', 'alert-danger', 'alert-warning', 'alert-info', 'alert-light', 'alert-dark');
el.classList.add('alert-danger');
el.classList.remove('d-none');
}
function showAlertVariant(message, variant) {
const el = document.getElementById('alert');
if (!el) return;
el.textContent = message;
el.classList.remove('d-none');
// Reset any prior bootstrap alert-* variants
el.classList.remove('alert-primary', 'alert-secondary', 'alert-success', 'alert-danger', 'alert-warning', 'alert-info', 'alert-light', 'alert-dark');
const v = String(variant || 'danger').trim().toLowerCase();
el.classList.add(`alert-${v || 'danger'}`);
}
function setSourceLabel(text) {
const el = document.getElementById('source-label');
if (!el) return;
@@ -140,6 +155,33 @@ function showEmbedPlayer(embedUrl) {
}
}
function hideAllPlayers() {
const videoEl = document.getElementById('video');
const iframeEl = document.getElementById('embed');
if (videoEl) {
try { videoEl.pause(); } catch (_) {}
videoEl.classList.add('d-none');
try { videoEl.removeAttribute('src'); videoEl.load(); } catch (_) {}
}
if (iframeEl) {
iframeEl.classList.add('d-none');
iframeEl.removeAttribute('src');
}
}
function setOpenOriginal(raw) {
const openOriginal = document.getElementById('open-original');
if (!openOriginal) return;
if (raw) {
openOriginal.href = raw;
openOriginal.classList.remove('d-none');
} else {
openOriginal.removeAttribute('href');
openOriginal.classList.add('d-none');
}
}
async function playVodLink(vodLink) {
const raw = normalizeVodLink(vodLink);
if (!raw) {
@@ -149,11 +191,7 @@ async function playVodLink(vodLink) {
}
// "Open original" button
const openOriginal = document.getElementById('open-original');
if (openOriginal) {
openOriginal.href = raw;
openOriginal.classList.remove('d-none');
}
setOpenOriginal(raw);
// Prefer known embed providers
const yt = toYoutubeEmbed(raw);
@@ -226,16 +264,27 @@ async function playVodLink(vodLink) {
showEmbedPlayer(raw);
}
function cleanUrlValue(v) {
const s = typeof v === 'string' ? v.trim() : '';
return s ? s : null;
}
function resolveMovieSource(movie) {
const mode = String(movie?.player_mode || 'auto').toLowerCase();
const legacy = movie?.vod_link;
const video = movie?.video_src;
const embed = movie?.embed_src;
const legacy = cleanUrlValue(movie?.vod_link);
const video = cleanUrlValue(movie?.video_src);
const embed = cleanUrlValue(movie?.embed_src);
if (mode === 'video') return video || legacy || embed || null;
if (mode === 'embed') return embed || legacy || video || null;
// auto
return video || embed || legacy || null;
// Legacy should never embed in the player (open in new tab only).
// So we only pick legacy if there is no other option, and mark it.
let picked = null;
if (mode === 'video') picked = video || null;
else if (mode === 'embed') picked = embed || null;
else picked = video || embed || null; // auto
if (picked) return { src: picked, isLegacy: false };
if (legacy) return { src: legacy, isLegacy: true };
return { src: null, isLegacy: false };
}
window.onload = async function () {
@@ -258,8 +307,20 @@ window.onload = async function () {
window.location.href = '/change-password.html';
return;
}
const nameEl = document.getElementById('player-user-name');
const nameEl = document.getElementById('user-name');
if (nameEl) nameEl.textContent = decoded.name || '';
if (decoded.admin) {
const adminLink = document.getElementById('admin-control-link');
if (adminLink) adminLink.classList.remove('d-none');
}
const logoffBtn = document.getElementById('log-off');
if (logoffBtn) {
logoffBtn.addEventListener('click', function () {
localStorage.removeItem('auth_token');
window.location.href = '/';
});
}
} catch (_) {
localStorage.removeItem('auth_token');
window.location.href = '/';
@@ -273,7 +334,10 @@ window.onload = async function () {
}
try {
const res = await fetch(`/api/movies/${encodeURIComponent(id)}`, { method: 'GET' });
const res = await fetch(`/api/movies/${encodeURIComponent(id)}`, {
method: 'GET',
headers: token ? { 'Authorization': `Bearer ${token}` } : undefined,
});
const movie = await res.json().catch(() => ({}));
if (!res.ok) {
showAlert(movie?.message || movie?.error || `Failed to load movie (${res.status}).`);
@@ -281,8 +345,25 @@ window.onload = async function () {
}
setHeader(movie);
const src = resolveMovieSource(movie);
await playVodLink(src);
const resolved = resolveMovieSource(movie);
if (!resolved?.src) {
setOpenOriginal(null);
setSourceLabel('—');
hideAllPlayers();
showAlert('No playable source configured for this movie.');
return;
}
if (resolved.isLegacy) {
// Legacy source: never embed it in the player.
setOpenOriginal(resolved.src);
setSourceLabel('Legacy link (opens in new tab)');
hideAllPlayers();
showAlertVariant('Legacy source: use “Open original” to watch in a new tab.', 'warning');
return;
}
await playVodLink(resolved.src);
} catch (err) {
showAlert(err.message || 'Network error.');
}
+39 -6
View File
@@ -7,6 +7,24 @@
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="./css/app.css" rel="stylesheet">
<style>
.navbar-custom {
background-color: black;
color: white;
}
.navbar-custom .navbar-brand,
.navbar-custom .nav-link {
color: white;
}
.navbar-custom .nav-link:hover {
color: #ddd;
}
.navbar-toggler {
border-color: white;
}
.navbar-toggler-icon {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='white' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E");
}
.player-wrap {
max-width: 1100px;
margin: 0 auto;
@@ -43,18 +61,32 @@
</style>
</head>
<body class="bg-light">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<!-- Black banner with buttons and user info -->
<nav class="navbar navbar-expand-lg navbar-custom">
<div class="container">
<a class="navbar-brand" href="/movies.html">Pool Oscars</a>
<div class="d-flex ms-auto align-items-center gap-2">
<span id="player-user-name" class="navbar-text text-white-50"></span>
<a class="navbar-brand" style="margin-left: -7px;" href="movies.html">
<img src="img/logo_w.png" style="max-width: 220px; height: 70px; object-fit: contain;">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<div class="navbar-nav">
<a href="movies.html" class="nav-link btn btn-outline-light me-2">Films</a>
<a href="checklist.html" class="nav-link btn btn-outline-light me-2">Checklist</a>
<a href="user-stat.html" class="nav-link btn btn-outline-light me-2">Statistiques</a>
<a id="admin-control-link" href="control.html" class="nav-link btn btn-warning me-2 d-none">Admin</a>
</div>
<div class="d-flex ms-auto mt-2 mt-lg-0 align-items-center gap-2">
<a id="open-original" class="btn btn-outline-light btn-sm d-none" target="_blank" rel="noopener noreferrer">Open original</a>
<a class="btn btn-outline-light btn-sm" href="/movies.html">Back to movies</a>
<span id="user-name" class="navbar-text me-1"></span>
<button id="log-off" class="btn btn-danger btn-sm">Déconnexion</button>
</div>
</div>
</div>
</nav>
<div class="container py-4 player-wrap">
<div class="container mt-5 pb-4 player-wrap">
<div class="mb-3">
<h2 id="movie-title" class="mb-1">Loading…</h2>
<div class="meta-muted">
@@ -83,6 +115,7 @@
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<!-- HLS support (m3u8) -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/hls.min.js"></script>
<script src="./js/app_player.js"></script>