add video backend server, fix styling and more #54
@@ -117,7 +117,15 @@ window.onload = async function () {
|
|||||||
const isChecked = watchedMoviesInYear.some(watchedMovie => watchedMovie.imdb_id === movie.imdb_id);
|
const isChecked = watchedMoviesInYear.some(watchedMovie => watchedMovie.imdb_id === movie.imdb_id);
|
||||||
const watchedMovie = watchedMoviesInYear.find(wm => wm.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 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');
|
const movieDiv = document.createElement('div');
|
||||||
movieDiv.classList.add('col-md-4', 'mb-4', 'movie-card');
|
movieDiv.classList.add('col-md-4', 'mb-4', 'movie-card');
|
||||||
|
|||||||
+97
-16
@@ -11,9 +11,24 @@ function showAlert(message) {
|
|||||||
const el = document.getElementById('alert');
|
const el = document.getElementById('alert');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
el.textContent = message;
|
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');
|
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) {
|
function setSourceLabel(text) {
|
||||||
const el = document.getElementById('source-label');
|
const el = document.getElementById('source-label');
|
||||||
if (!el) return;
|
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) {
|
async function playVodLink(vodLink) {
|
||||||
const raw = normalizeVodLink(vodLink);
|
const raw = normalizeVodLink(vodLink);
|
||||||
if (!raw) {
|
if (!raw) {
|
||||||
@@ -149,11 +191,7 @@ async function playVodLink(vodLink) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// "Open original" button
|
// "Open original" button
|
||||||
const openOriginal = document.getElementById('open-original');
|
setOpenOriginal(raw);
|
||||||
if (openOriginal) {
|
|
||||||
openOriginal.href = raw;
|
|
||||||
openOriginal.classList.remove('d-none');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefer known embed providers
|
// Prefer known embed providers
|
||||||
const yt = toYoutubeEmbed(raw);
|
const yt = toYoutubeEmbed(raw);
|
||||||
@@ -226,16 +264,27 @@ async function playVodLink(vodLink) {
|
|||||||
showEmbedPlayer(raw);
|
showEmbedPlayer(raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cleanUrlValue(v) {
|
||||||
|
const s = typeof v === 'string' ? v.trim() : '';
|
||||||
|
return s ? s : null;
|
||||||
|
}
|
||||||
|
|
||||||
function resolveMovieSource(movie) {
|
function resolveMovieSource(movie) {
|
||||||
const mode = String(movie?.player_mode || 'auto').toLowerCase();
|
const mode = String(movie?.player_mode || 'auto').toLowerCase();
|
||||||
const legacy = movie?.vod_link;
|
const legacy = cleanUrlValue(movie?.vod_link);
|
||||||
const video = movie?.video_src;
|
const video = cleanUrlValue(movie?.video_src);
|
||||||
const embed = movie?.embed_src;
|
const embed = cleanUrlValue(movie?.embed_src);
|
||||||
|
|
||||||
if (mode === 'video') return video || legacy || embed || null;
|
// Legacy should never embed in the player (open in new tab only).
|
||||||
if (mode === 'embed') return embed || legacy || video || null;
|
// So we only pick legacy if there is no other option, and mark it.
|
||||||
// auto
|
let picked = null;
|
||||||
return video || embed || legacy || 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 () {
|
window.onload = async function () {
|
||||||
@@ -258,8 +307,20 @@ window.onload = async function () {
|
|||||||
window.location.href = '/change-password.html';
|
window.location.href = '/change-password.html';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nameEl = document.getElementById('player-user-name');
|
const nameEl = document.getElementById('user-name');
|
||||||
if (nameEl) nameEl.textContent = decoded.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 (_) {
|
} catch (_) {
|
||||||
localStorage.removeItem('auth_token');
|
localStorage.removeItem('auth_token');
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
@@ -273,7 +334,10 @@ window.onload = async function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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(() => ({}));
|
const movie = await res.json().catch(() => ({}));
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
showAlert(movie?.message || movie?.error || `Failed to load movie (${res.status}).`);
|
showAlert(movie?.message || movie?.error || `Failed to load movie (${res.status}).`);
|
||||||
@@ -281,8 +345,25 @@ window.onload = async function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setHeader(movie);
|
setHeader(movie);
|
||||||
const src = resolveMovieSource(movie);
|
const resolved = resolveMovieSource(movie);
|
||||||
await playVodLink(src);
|
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) {
|
} catch (err) {
|
||||||
showAlert(err.message || 'Network error.');
|
showAlert(err.message || 'Network error.');
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-6
@@ -7,6 +7,24 @@
|
|||||||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="./css/app.css" rel="stylesheet">
|
<link href="./css/app.css" rel="stylesheet">
|
||||||
<style>
|
<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 {
|
.player-wrap {
|
||||||
max-width: 1100px;
|
max-width: 1100px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -43,18 +61,32 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-light">
|
<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">
|
<div class="container">
|
||||||
<a class="navbar-brand" href="/movies.html">Pool Oscars</a>
|
<a class="navbar-brand" style="margin-left: -7px;" href="movies.html">
|
||||||
<div class="d-flex ms-auto align-items-center gap-2">
|
<img src="img/logo_w.png" style="max-width: 220px; height: 70px; object-fit: contain;">
|
||||||
<span id="player-user-name" class="navbar-text text-white-50"></span>
|
</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 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>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="container py-4 player-wrap">
|
<div class="container mt-5 pb-4 player-wrap">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<h2 id="movie-title" class="mb-1">Loading…</h2>
|
<h2 id="movie-title" class="mb-1">Loading…</h2>
|
||||||
<div class="meta-muted">
|
<div class="meta-muted">
|
||||||
@@ -83,6 +115,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<!-- HLS support (m3u8) -->
|
<!-- HLS support (m3u8) -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/hls.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/hls.min.js"></script>
|
||||||
<script src="./js/app_player.js"></script>
|
<script src="./js/app_player.js"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user