diff --git a/public/assets/js/player-state.js b/public/assets/js/player-state.js new file mode 100644 index 0000000..ad2e62f --- /dev/null +++ b/public/assets/js/player-state.js @@ -0,0 +1,170 @@ +// Player state manager for persistent music playback +class PlayerState { + constructor() { + this.currentSong = null; + this.isPlaying = false; + this.currentTime = 0; + this.duration = 0; + this.loadFromStorage(); + this.initializePlayer(); + } + + // load saved state from localStorage + loadFromStorage() { + try { + const saved = localStorage.getItem('playerState'); + if (saved) { + const state = JSON.parse(saved); + this.currentSong = state.currentSong; + this.currentTime = state.currentTime || 0; + } + } catch (e) { + console.error('failed to load player state:', e); + } + } + + // save current state to localStorage + saveToStorage() { + try { + localStorage.setItem('playerState', JSON.stringify({ + currentSong: this.currentSong, + currentTime: this.currentTime, + isPlaying: this.isPlaying + })); + } catch (e) { + console.error('failed to save player state:', e); + } + } + + // initialize player on page load + initializePlayer() { + document.addEventListener('DOMContentLoaded', () => { + const audio = document.getElementById('audio'); + if (!audio) return; + + // restore previous song if exists + if (this.currentSong) { + this.updateAudioElement(); + this.showPlayer(); + } + + // save current time periodically + audio.addEventListener('timeupdate', () => { + this.currentTime = audio.currentTime; + // debounce storage writes + if (!this._saveTimeout) { + this._saveTimeout = setTimeout(() => { + this.saveToStorage(); + this._saveTimeout = null; + }, 2000); + } + }); + + // update duration + audio.addEventListener('loadedmetadata', () => { + this.duration = audio.duration; + }); + + // track play/pause state + audio.addEventListener('play', () => { + this.isPlaying = true; + this.saveToStorage(); + }); + + audio.addEventListener('pause', () => { + this.isPlaying = false; + this.saveToStorage(); + }); + }); + } + + // load a new song into player + loadSong(musicId, title, artist) { + this.currentSong = { + id: musicId, + title: title, + artist: artist, + streamUrl: `/music/stream?id=${musicId}` + }; + this.currentTime = 0; + this.saveToStorage(); + this.updateAudioElement(); + this.showPlayer(); + this.autoPlay(); + } + + // update the audio element with current song + updateAudioElement() { + const audio = document.getElementById('audio'); + if (!audio || !this.currentSong) return; + + // only change source if its different + if (audio.src !== window.location.origin + this.currentSong.streamUrl) { + audio.src = this.currentSong.streamUrl; + } + + audio.currentTime = this.currentTime; + + // update UI elements + const titleEl = document.getElementById('currentSongTitle'); + const artistEl = document.getElementById('currentArtist'); + + if (titleEl) titleEl.textContent = this.currentSong.title; + if (artistEl) artistEl.textContent = this.currentSong.artist; + } + + // show the persistent player + showPlayer() { + const player = document.getElementById('persistent-player'); + if (player) { + player.style.display = 'block'; + } + } + + // hide the persistent player + hidePlayer() { + const player = document.getElementById('persistent-player'); + if (player) { + player.style.display = 'none'; + } + } + + // auto-play after loading + autoPlay() { + const audio = document.getElementById('audio'); + if (!audio) return; + + // small delay to ensure audio is ready + setTimeout(() => { + audio.play().catch(err => { + console.log('autoplay prevented:', err); + // browser blocked autoplay, user needs to click play + }); + }, 100); + } + + // toggle play/pause + togglePlay() { + const audio = document.getElementById('audio'); + if (!audio || !this.currentSong) return; + + if (this.isPlaying) { + audio.pause(); + } else { + audio.play(); + } + } + + // get current song info + getCurrentSong() { + return this.currentSong; + } +} + +// create global instance +window.playerState = new PlayerState(); + +// helper function for loading songs from onclick attributes +window.loadSong = (id, title, artist) => { + window.playerState.loadSong(id, title, artist); +}; diff --git a/public/assets/player.js b/public/assets/player.js index ce89990..3ab3d45 100644 --- a/public/assets/player.js +++ b/public/assets/player.js @@ -36,7 +36,6 @@ window.jumpTo = function (seconds) { document.addEventListener("DOMContentLoaded", () => { const audio = document.getElementById("audio"); const playBtn = document.getElementById("playBtn"); - const icon = playBtn.querySelector("i"); const bar = document.getElementById("progressBar"); const barCont = document.getElementById("progressContainer"); const timestampInput = document.getElementById("timestampInput"); @@ -44,6 +43,9 @@ document.addEventListener("DOMContentLoaded", () => { const popupUser = document.getElementById("popupUser"); const popupContent = document.getElementById("popupContent"); + if (!audio || !playBtn || !bar || !barCont) return; // skip if essential elements missing + + const icon = playBtn.querySelector("i"); let isPlaying = false; let markersCreated = false; @@ -93,7 +95,7 @@ document.addEventListener("DOMContentLoaded", () => { if (timestampInput) timestampInput.value = Math.floor(audio.currentTime); const currentSec = audio.currentTime; - if (typeof commentsData !== "undefined") { + if (typeof commentsData !== "undefined" && popup && popupUser && popupContent) { const activeComment = commentsData.find( (c) => Math.abs(c.timestamp - currentSec) < 0.5, ); @@ -113,7 +115,11 @@ document.addEventListener("DOMContentLoaded", () => { audio.currentTime = pct * audio.duration; }); - init3D(); + // only init 3D if canvas container exists (fullscreen page only) + const canvasContainer = document.getElementById("canvas-container"); + if (canvasContainer) { + init3D(); + } }); let scene, camera, renderer, geometry, mesh, context, analyser, dataArray; diff --git a/public/index.php b/public/index.php index c6bbc9d..e079a4e 100644 --- a/public/index.php +++ b/public/index.php @@ -25,8 +25,12 @@ (new MusicController())->show(); break; - case '/music/stream': - (new MusicController())->stream(); + case '/music/stream': + (new MusicController())->stream(); + break; + + case '/music/details': + (new MusicController())->details(); break; // Auth diff --git a/src/Controllers/Controller.php b/src/Controllers/Controller.php index 43adaf7..79556ce 100644 --- a/src/Controllers/Controller.php +++ b/src/Controllers/Controller.php @@ -2,16 +2,41 @@ namespace App\Controllers; class Controller { - protected function render($view, $data = []) { - extract($data); // Converts array keys to variables ($music, $comments, etc.) - - // Check if the view file exists + protected function render($view, $data = [], $layout = 'layouts/base') { + // extract data for use in views + extract($data); + + // check if htmx request + $isHtmxRequest = isset($_SERVER['HTTP_HX_REQUEST']) && $_SERVER['HTTP_HX_REQUEST'] === 'true'; + + // get view content $viewFile = __DIR__ . "/../Views/$view.php"; - if (file_exists($viewFile)) { - require $viewFile; - } else { + if (!file_exists($viewFile)) { die("View '$view' not found!"); } + + // capture view output + ob_start(); + require $viewFile; + $content = ob_get_clean(); + + // if htmx request, return content only + if ($isHtmxRequest) { + echo $content; + return; + } + + // otherwise wrap in layout + if ($layout !== false) { + $layoutFile = __DIR__ . "/../Views/$layout.php"; + if (file_exists($layoutFile)) { + require $layoutFile; + } else { + echo $content; // fallback if layout missing + } + } else { + echo $content; + } } protected function redirect($url) { diff --git a/src/Controllers/MusicController.php b/src/Controllers/MusicController.php index 0286ee5..cecfc95 100644 --- a/src/Controllers/MusicController.php +++ b/src/Controllers/MusicController.php @@ -141,13 +141,63 @@ public function show() { $comments = $commentModel->getAllForMusic($musicId); $openDrawer = (isset($_GET['drawer']) && $_GET['drawer'] === 'open') ? 'open' : ''; - // Render View + // Render fullscreen visualization (no layout) $this->render('music/show', [ 'music' => $music, 'comments' => $comments, 'avgRating' => $avgRating, 'openDrawer' => $openDrawer, 'isUserLoggedIn' => $userId ? true : false + ], false); + } + + public function details() { + // music details page with comments and info + if (!isset($_GET['id'])) die("ID manquant"); + $musicId = (int)$_GET['id']; + + // session check for user + if (session_status() === PHP_SESSION_NONE) session_start(); + $userId = $_SESSION['user_id'] ?? null; + + $pdo = Database::getConnection(); + $musicModel = new Music($pdo); + $commentModel = new Comment($pdo); + + // handle POST actions (comments & ratings) + if ($_SERVER['REQUEST_METHOD'] === 'POST' && $userId) { + // comments + if (isset($_POST['comment'])) { + $content = trim($_POST['comment']); + $timestamp = (int)($_POST['timestamp'] ?? 0); + if (!empty($content)) { + $commentModel->create($userId, $musicId, $content, $timestamp); + $this->redirect("/music/details?id=$musicId"); + } + } + // ratings + if (isset($_POST['rating'])) { + $val = (int)$_POST['rating']; + if ($val >= 1 && $val <= 5) { + $musicModel->addRating($userId, $musicId, $val); + $this->redirect("/music/details?id=$musicId"); + } + } + } + + // fetch data + $music = $musicModel->findById($musicId); + if (!$music) die("Musique introuvable"); + + $avgRating = $musicModel->getAvgRating($musicId); + $comments = $commentModel->getAllForMusic($musicId); + + // render details page with layout + $this->render('music/details', [ + 'music' => $music, + 'comments' => $comments, + 'avgRating' => $avgRating, + 'isUserLoggedIn' => $userId ? true : false ]); } } diff --git a/src/Views/admin/index.php b/src/Views/admin/index.php index 5f18b06..2389084 100644 --- a/src/Views/admin/index.php +++ b/src/Views/admin/index.php @@ -1,196 +1,172 @@ - - -
- -
-
- Gérez les utilisateurs et le contenu de la plateforme.
-Gérez les utilisateurs et le contenu de la plateforme.
- -| Utilisateur | -Rôle | -Actions | -|||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
-
-
- = strtoupper(substr($u['username'], 0, 1)); ?>
-
- = htmlspecialchars($u['username']); ?>
+
+
+
+ Vous
+
+
|
+
-
- + Pas encore de compte ? Inscrivez-vous +
+
-
- - = isset($_SESSION['user_role']) ? ucfirst($_SESSION['user_role']) : 'Membre'; ?> -
- - - -+ = isset($_SESSION['user_role']) ? ucfirst($_SESSION['user_role']) : 'Membre'; ?> +
+ + +Vous n'avez pas encore posté de musique.
+
-
-