feat: frontend modernization with HTMX, Alpine.js and Plyr (Phase 3)
CI / Test (Python 3.11) (push) Has been cancelled
CI / Test (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Type Check (push) Has been cancelled
CI / Summary (push) Has been cancelled

- Integrated HTMX for server-driven UI updates and fragments
- Adopted Alpine.js for global reactive state and tab management
- Replaced legacy player with Plyr.io for premium streaming experience
- Implemented real-time download polling via HTMX
- Added server-sent Toast notification system
- Fixed navigation and authentication scoping issues
This commit is contained in:
root
2026-03-24 11:10:22 +00:00
parent 2b4cc617cb
commit 5c7116557d
17 changed files with 584 additions and 690 deletions
+23 -64
View File
@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ filename }} - Ohm Stream Player</title>
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
<style>
* {
margin: 0;
@@ -67,10 +68,8 @@
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
video {
width: 100%;
display: block;
max-height: 80vh;
.plyr {
border-radius: 15px;
}
.controls {
@@ -123,41 +122,13 @@
margin-top: 20px;
}
.loading {
text-align: center;
padding: 60px 20px;
color: #aaa;
}
.loading::after {
content: '...';
animation: dots 1.5s steps(4, end) infinite;
}
@keyframes dots {
0%, 20% { content: '.'; }
40% { content: '..'; }
60%, 100% { content: '...'; }
}
@media (max-width: 768px) {
.header h1 {
font-size: 1.2rem;
}
.video-info {
flex-direction: column;
align-items: flex-start;
}
.controls {
flex-direction: column;
}
.btn {
width: 100%;
justify-content: center;
}
.video-info { flex-direction: column; align-items: flex-start; }
.controls { flex-direction: column; }
.btn { width: 100%; justify-content: center; }
}
</style>
</head>
@@ -173,12 +144,8 @@
</div>
<div class="video-wrapper">
<video controls preload="metadata">
<video id="player" playsinline controls preload="metadata">
<source src="/stream/{{ filename }}" type="video/mp4">
<div class="error-message">
Votre navigateur ne supporte pas la lecture vidéo.<br>
<a href="/stream/{{ filename }}" style="color: #00d9ff;">Télécharger la vidéo</a>
</div>
</video>
</div>
@@ -188,32 +155,24 @@
</div>
</div>
<script src="https://cdn.plyr.io/3.7.8/plyr.polyfilled.js"></script>
<script>
// Video error handling
const video = document.querySelector('video');
video.addEventListener('error', (e) => {
console.error('Video error:', e);
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
errorDiv.innerHTML = `
Erreur lors du chargement de la vidéo.<br>
<a href="/video/{{ task_id }}" style="color: #00d9ff;">Réessayer</a>
const player = new Plyr('#player', {
captions: { active: true, update: true, language: 'auto' },
speed: { selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 2] }
});
// Error handling
player.on('error', (error) => {
console.error('Plyr error:', error);
const wrapper = document.querySelector('.video-wrapper');
wrapper.innerHTML = `
<div class="error-message">
Erreur lors de la lecture du flux vidéo.<br>
<a href="/video/{{ task_id }}" style="color: #00d9ff; text-decoration: underline;">Réessayer</a> ou
<a href="/stream/{{ filename }}" style="color: #00d9ff; text-decoration: underline;" download>Télécharger</a>
</div>
`;
video.parentNode.replaceChild(errorDiv, video);
});
// Video loaded successfully
video.addEventListener('loadedmetadata', () => {
console.log('Video duration:', video.duration);
});
// Log seeking events for debugging
video.addEventListener('seeking', () => {
console.log('Seeking to:', video.currentTime);
});
video.addEventListener('seeked', () => {
console.log('Seeked to:', video.currentTime);
});
</script>
</body>