© MoBBee.fr
Cet article à été Lu : 721 fois sur le site MoBBee.fr
Recherche par Mots clés : : LECTEUR PODCAST

Lundi 18 Mai 2026

PODCAST

Les 20 Titres à découvrir sur MoBBee


En rotation sur MoBBee.fr * La web Radio *


MoBBee.fr – Lecteur Podcast :root { --bg-main: #05070b; --bg-panel: #10131b; --bg-glass: rgba(16, 19, 27, 0.75); --accent: #ff6b3b; --accent-soft: #ffb38f; --accent-alt: #3bd5ff; --text-main: #f5f7ff; --text-muted: #9aa0b5; --border-soft: rgba(255, 255, 255, 0.06); --shadow-strong: 0 18px 40px rgba(0, 0, 0, 0.65); --shadow-soft: 0 10px 25px rgba(0, 0, 0, 0.45); --radius-lg: 18px; --radius-md: 12px; --radius-sm: 8px; --transition-fast: 0.18s ease-out; --transition-med: 0.28s ease-out; --transition-slow: 0.45s ease-out; } * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: radial-gradient(circle at top, #151a26 0, #05070b 55%, #020308 100%); color: var(--text-main); padding: 24px; } .player-shell { max-width: 980px; margin: 0 auto; background: linear-gradient(145deg, #0b0f18 0%, #05070b 40%, #121624 100%); border-radius: 26px; padding: 22px 22px 18px; box-shadow: var(--shadow-strong); border: 1px solid rgba(255, 255, 255, 0.04); position: relative; overflow: hidden; } .player-shell::before { content: ""; position: absolute; inset: -40%; background: radial-gradient(circle at 0% 0%, rgba(255, 107, 59, 0.12) 0, transparent 55%), radial-gradient(circle at 100% 0%, rgba(59, 213, 255, 0.12) 0, transparent 55%); opacity: 0.9; filter: blur(40px); pointer-events: none; } .player-shell-inner { position: relative; display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.8fr); gap: 18px; z-index: 1; } @media (max-width: 860px) { .player-shell-inner { grid-template-columns: minmax(0, 1fr); } } .player-main { background: radial-gradient(circle at top left, #181d2b 0, #0b0f18 55%, #05070b 100%); border-radius: var(--radius-lg); padding: 18px 18px 16px; box-shadow: var(--shadow-soft); border: 1px solid var(--border-soft); display: flex; flex-direction: column; gap: 14px; position: relative; overflow: hidden; } .player-main-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; } .player-title-block { display: flex; flex-direction: column; gap: 4px; } .player-title { font-size: 16px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--accent-soft); } .player-subtitle { font-size: 13px; color: var(--text-muted); } .player-options { display: flex; align-items: center; gap: 8px; } .toggle-chip { border-radius: 999px; padding: 4px 10px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; border: 1px solid rgba(255, 255, 255, 0.12); color: var(--text-muted); background: rgba(5, 7, 11, 0.7); display: inline-flex; align-items: center; gap: 6px; cursor: pointer; transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast), transform var(--transition-fast); } .toggle-chip span.dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(255, 255, 255, 0.18); box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.12); } .toggle-chip.active { background: radial-gradient(circle at top, rgba(255, 107, 59, 0.35), rgba(5, 7, 11, 0.9)); border-color: rgba(255, 107, 59, 0.7); color: var(--accent-soft); transform: translateY(-1px); } .toggle-chip.active span.dot { background: var(--accent); box-shadow: 0 0 0 1px rgba(255, 107, 59, 0.7); } .player-visual-row { display: grid; grid-template-columns: 120px minmax(0, 1fr); gap: 14px; align-items: center; } @media (max-width: 480px) { .player-visual-row { grid-template-columns: 96px minmax(0, 1fr); } } .cover-wrapper { position: relative; width: 120px; height: 120px; border-radius: 24px; background: radial-gradient(circle at 30% 0%, #ff6b3b 0, #05070b 55%, #121624 100%); box-shadow: 0 18px 40px rgba(0, 0, 0, 0.7); overflow: hidden; transform-origin: center; animation: cover-rotate 26s linear infinite; } @keyframes cover-rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .cover-inner { position: absolute; inset: 10px; border-radius: 20px; background: radial-gradient(circle at 0% 0%, rgba(255, 255, 255, 0.12) 0, transparent 55%), radial-gradient(circle at 100% 100%, rgba(59, 213, 255, 0.18) 0, transparent 55%); backdrop-filter: blur(6px); border: 1px solid rgba(255, 255, 255, 0.12); display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 6px; } .cover-logo { width: 46px; height: 46px; border-radius: 50%; background: radial-gradient(circle at 30% 0%, #ff6b3b 0, #ffb38f 40%, #05070b 100%); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.6); display: flex; align-items: center; justify-content: center; color: #05070b; font-weight: 800; font-size: 20px; letter-spacing: 0.06em; text-transform: uppercase; } .cover-text { font-size: 11px; text-transform: uppercase; letter-spacing: 0.16em; color: var(--text-muted); } .cover-ring { position: absolute; inset: 0; border-radius: inherit; border: 1px dashed rgba(255, 255, 255, 0.18); pointer-events: none; } .spectrum-wrapper { position: relative; border-radius: var(--radius-md); background: radial-gradient(circle at top, rgba(59, 213, 255, 0.16), rgba(5, 7, 11, 0.9)); border: 1px solid rgba(59, 213, 255, 0.35); padding: 10px 12px 12px; box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6); overflow: hidden; } .spectrum-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.12em; color: var(--text-muted); } .spectrum-header span.label { color: var(--accent-alt); } .spectrum-header span.mode { opacity: 0.8; } #spectrumCanvas { width: 100%; height: 70px; display: block; border-radius: 10px; background: radial-gradient(circle at bottom, #05070b 0, #020308 60%, #000000 100%); } .track-info-block { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; } .track-title { font-size: 14px; font-weight: 500; color: var(--text-main); line-height: 1.4; } .track-meta { font-size: 11px; color: var(--text-muted); display: flex; align-items: center; gap: 10px; } .track-meta span.dot { width: 4px; height: 4px; border-radius: 50%; background: rgba(255, 255, 255, 0.18); } .timeline-row { display: flex; align-items: center; gap: 8px; margin-top: 4px; } .time-label { font-size: 11px; color: var(--text-muted); min-width: 40px; text-align: center; } .timeline { flex: 1; position: relative; height: 6px; border-radius: 999px; background: rgba(255, 255, 255, 0.06); overflow: hidden; cursor: pointer; } .timeline-buffer { position: absolute; inset: 0; border-radius: inherit; background: linear-gradient(90deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.02)); transform-origin: left; transform: scaleX(0); pointer-events: none; } .timeline-progress { position: absolute; inset: 0; border-radius: inherit; background: linear-gradient(90deg, var(--accent), var(--accent-alt)); transform-origin: left; transform: scaleX(0); box-shadow: 0 0 12px rgba(255, 107, 59, 0.7); } .controls-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 10px; } .controls-main { display: flex; align-items: center; gap: 10px; } .btn-circle { width: 40px; height: 40px; border-radius: 999px; border: 1px solid rgba(255, 255, 255, 0.16); background: radial-gradient(circle at top, rgba(255, 107, 59, 0.35), rgba(5, 7, 11, 0.9)); color: var(--text-main); display: inline-flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 12px 26px rgba(0, 0, 0, 0.7); transition: transform var(--transition-fast), box-shadow var(--transition-fast), background var(--transition-fast), border-color var(--transition-fast), opacity var(--transition-fast); } .btn-circle.secondary { background: radial-gradient(circle at top, rgba(59, 213, 255, 0.35), rgba(5, 7, 11, 0.9)); border-color: rgba(59, 213, 255, 0.6); } .btn-circle.icon-only { width: 34px; height: 34px; box-shadow: var(--shadow-soft); background: radial-gradient(circle at top, rgba(255, 255, 255, 0.12), rgba(5, 7, 11, 0.9)); border-color: rgba(255, 255, 255, 0.18); } .btn-circle:hover { transform: translateY(-1px); box-shadow: 0 16px 32px rgba(0, 0, 0, 0.8); opacity: 0.95; } .btn-circle:active { transform: translateY(1px) scale(0.97); box-shadow: 0 8px 18px rgba(0, 0, 0, 0.7); opacity: 0.9; } .btn-circle svg { width: 18px; height: 18px; } .controls-extra { display: flex; align-items: center; gap: 8px; } .option-toggle { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; padding: 4px 8px; border-radius: 999px; border: 1px solid rgba(255, 255, 255, 0.12); color: var(--text-muted); background: rgba(5, 7, 11, 0.7); cursor: pointer; display: inline-flex; align-items: center; gap: 6px; transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast), transform var(--transition-fast); } .option-toggle span.indicator { width: 8px; height: 8px; border-radius: 50%; background: rgba(255, 255, 255, 0.18); } .option-toggle.active { background: radial-gradient(circle at top, rgba(59, 213, 255, 0.35), rgba(5, 7, 11, 0.9)); border-color: rgba(59, 213, 255, 0.7); color: var(--accent-alt); transform: translateY(-1px); } .option-toggle.active span.indicator { background: var(--accent-alt); } .playlist-panel { background: radial-gradient(circle at top, #181d2b 0, #0b0f18 55%, #05070b 100%); border-radius: var(--radius-lg); padding: 16px 16px 14px; box-shadow: var(--shadow-soft); border: 1px solid var(--border-soft); display: flex; flex-direction: column; gap: 10px; position: relative; overflow: hidden; } .playlist-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; } .playlist-title { font-size: 14px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.12em; color: var(--text-muted); } .playlist-count { font-size: 11px; color: var(--accent-soft); text-transform: uppercase; letter-spacing: 0.08em; } .playlist-search { position: relative; flex: 1; } .playlist-search input { width: 100%; border-radius: 999px; border: 1px solid rgba(255, 255, 255, 0.16); background: rgba(5, 7, 11, 0.85); color: var(--text-main); font-size: 12px; padding: 6px 26px 6px 10px; outline: none; transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background var(--transition-fast); } .playlist-search input::placeholder { color: var(--text-muted); } .playlist-search input:focus { border-color: rgba(59, 213, 255, 0.7); box-shadow: 0 0 0 1px rgba(59, 213, 255, 0.4); background: rgba(5, 7, 11, 0.95); } .playlist-search-icon { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); width: 14px; height: 14px; opacity: 0.7; pointer-events: none; } .playlist-list { margin-top: 6px; max-height: 320px; overflow-y: auto; padding-right: 4px; } .playlist-list::-webkit-scrollbar { width: 6px; } .playlist-list::-webkit-scrollbar-track { background: rgba(5, 7, 11, 0.6); border-radius: 999px; } .playlist-list::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.18); border-radius: 999px; } .playlist-item { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: var(--radius-md); margin-bottom: 4px; cursor: pointer; position: relative; overflow: hidden; background: radial-gradient(circle at left, rgba(255, 255, 255, 0.04), rgba(5, 7, 11, 0.9)); border: 1px solid rgba(255, 255, 255, 0.04); transition: background var(--transition-med), transform var(--transition-med), box-shadow var(--transition-med), border-color var(--transition-med); } .playlist-item::before { content: ""; position: absolute; inset: 0; background: linear-gradient(120deg, rgba(255, 107, 59, 0.18), rgba(59, 213, 255, 0.18)); opacity: 0; transform: translateX(-20%); transition: opacity var(--transition-med), transform var(--transition-med); pointer-events: none; } .playlist-item:hover { transform: translateY(-1px); box-shadow: 0 10px 22px rgba(0, 0, 0, 0.6); border-color: rgba(255, 255, 255, 0.12); } .playlist-item:hover::before { opacity: 0.18; transform: translateX(0); } .playlist-item.active { border-color: rgba(255, 107, 59, 0.7); box-shadow: 0 12px 26px rgba(0, 0, 0, 0.7); background: radial-gradient(circle at left, rgba(255, 107, 59, 0.35), rgba(5, 7, 11, 0.9)); } .playlist-item.active::before { opacity: 0.28; transform: translateX(0); } .playlist-index { width: 22px; height: 22px; border-radius: 999px; border: 1px solid rgba(255, 255, 255, 0.18); display: flex; align-items: center; justify-content: center; font-size: 11px; color: var(--text-muted); background: rgba(5, 7, 11, 0.8); flex-shrink: 0; } .playlist-item.active .playlist-index { border-color: rgba(255, 107, 59, 0.7); color: var(--accent-soft); background: rgba(5, 7, 11, 0.95); } .playlist-text { flex: 1; display: flex; flex-direction: column; gap: 2px; } .playlist-title-text { font-size: 13px; color: var(--text-main); line-height: 1.4; } .playlist-meta-text { font-size: 11px; color: var(--text-muted); } .playlist-duration { font-size: 11px; color: var(--text-muted); flex-shrink: 0; } .mini-player { position: fixed; right: 18px; bottom: 18px; width: 260px; max-width: calc(100% - 36px); border-radius: 22px; background: var(--bg-glass); backdrop-filter: blur(16px) saturate(140%); box-shadow: 0 18px 40px rgba(0, 0, 0, 0.75); border: 1px solid rgba(255, 255, 255, 0.16); overflow: hidden; transform-origin: bottom right; animation: mini-slide-in var(--transition-slow) ease-out; z-index: 9999; } @keyframes mini-slide-in { from { transform: translate(40px, 40px) scale(0.85); opacity: 0; } to { transform: translate(0, 0) scale(1); opacity: 1; } } .mini-player.hidden { pointer-events: none; opacity: 0; transform: translate(40px, 40px) scale(0.85); transition: opacity var(--transition-med), transform var(--transition-med); } .mini-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 10px 6px; background: linear-gradient(120deg, rgba(255, 107, 59, 0.18), rgba(59, 213, 255, 0.18)); } .mini-title { font-size: 11px; text-transform: uppercase; letter-spacing: 0.12em; color: var(--text-main); } .mini-controls-top { display: flex; align-items: center; gap: 6px; } .mini-btn { width: 22px; height: 22px; border-radius: 999px; border: 1px solid rgba(255, 255, 255, 0.18); background: rgba(5, 7, 11, 0.85); display: inline-flex; align-items: center; justify-content: center; cursor: pointer; transition: background var(--transition-fast), transform var(--transition-fast), opacity var(--transition-fast); } .mini-btn:hover { background: rgba(5, 7, 11, 0.95); transform: translateY(-1px); opacity: 0.95; } .mini-btn svg { width: 12px; height: 12px; } .mini-body { padding: 8px 10px 10px; display: flex; align-items: center; gap: 8px; } .mini-cover { width: 40px; height: 40px; border-radius: 14px; background: radial-gradient(circle at 30% 0%, #ff6b3b 0, #05070b 55%, #121624 100%); box-shadow: 0 10px 22px rgba(0, 0, 0, 0.7); flex-shrink: 0; position: relative; overflow: hidden; } .mini-cover::after { content: ""; position: absolute; inset: 6px; border-radius: 10px; border: 1px solid rgba(255, 255, 255, 0.18); background: radial-gradient(circle at 0% 0%, rgba(255, 255, 255, 0.12), transparent 55%); } .mini-text { flex: 1; display: flex; flex-direction: column; gap: 2px; } .mini-track-title { font-size: 11px; color: var(--text-main); line-height: 1.4; } .mini-track-meta { font-size: 10px; color: var(--text-muted); } .mini-controls-bottom { display: flex; align-items: center; gap: 6px; margin-top: 4px; } .mini-btn-main { width: 26px; height: 26px; border-radius: 999px; border: 1px solid rgba(255, 255, 255, 0.18); background: radial-gradient(circle at top, rgba(255, 107, 59, 0.35), rgba(5, 7, 11, 0.9)); display: inline-flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 10px 22px rgba(0, 0, 0, 0.7); transition: transform var(--transition-fast), box-shadow var(--transition-fast), opacity var(--transition-fast); } .mini-btn-main:hover { transform: translateY(-1px); box-shadow: 0 14px 28px rgba(0, 0, 0, 0.8); opacity: 0.95; } .mini-btn-main svg { width: 14px; height: 14px; } .mini-progress { flex: 1; height: 4px; border-radius: 999px; background: rgba(255, 255, 255, 0.08); overflow: hidden; position: relative; } .mini-progress-bar { position: absolute; inset: 0; border-radius: inherit; background: linear-gradient(90deg, var(--accent), var(--accent-alt)); transform-origin: left; transform: scaleX(0); } .badge { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; padding: 4px 8px; border-radius: 999px; border: 1px solid rgba(255, 255, 255, 0.12); color: var(--text-muted); background: rgba(5, 7, 11, 0.7); } .badge span.dot { width: 6px; height: 6px; border-radius: 50%; background: rgba(255, 255, 255, 0.18); } .badge.accent { border-color: rgba(255, 107, 59, 0.7); color: var(--accent-soft); background: radial-gradient(circle at top, rgba(255, 107, 59, 0.35), rgba(5, 7, 11, 0.9)); } .badge.accent span.dot { background: var(--accent); } .feed-info-bar { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; font-size: 11px; color: var(--text-muted); } .feed-info-bar span.url { color: var(--accent-alt); } .feed-info-bar span.status-ok { color: var(--accent-soft); } .feed-info-bar span.status-error { color: #ff4b6b; } @media (max-width: 640px) { .feed-info-bar { flex-direction: column; align-items: flex-start; } }
Flux podcast : https://www.mobbee.fr/xml/podcast.xml
Chargement de la playlist…
MoBBee Podcast Player
Lecteur HTML5 + Web Audio, playlist RSS dynamique
Spectre ON/OFF Mini‑lecteur
Podcast • MoBBee.fr
Spectre audio Web Audio API
Aucune piste en cours
0 titres
00:00
--:--
AutoNext Shuffle Repeat: none
Playlist RSS
0 titres
Flux XML → playlist automatique
Mini‑lecteur MoBBee
Aucune piste
Playlist RSS MoBBee.fr
const FEED_URL = "https://www.mobbee.fr/xml/podcast.xml"; const state = { tracks: [], currentIndex: -1, isPlaying: false, autoNext: true, shuffle: false, repeatMode: "none", audioCtx: null, analyser: null, gainNode: null, spectrumEnabled: true, spectrumAnimationId: null, lastProgressTime: 0, stalledTimeoutId: null, autoNextFallbackTimerId: null, shuffleHistory: [], miniVisible: true }; const audioEl = document.getElementById("mainAudio"); const feedStatusEl = document.getElementById("feedStatus"); const playlistListEl = document.getElementById("playlistList"); const playlistCountLabelEl = document.getElementById("playlistCountLabel"); const currentTrackTitleEl = document.getElementById("currentTrackTitle"); const currentTrackIndexEl = document.getElementById("currentTrackIndex"); const currentTrackCountEl = document.getElementById("currentTrackCount"); const currentTimeLabelEl = document.getElementById("currentTimeLabel"); const durationLabelEl = document.getElementById("durationLabel"); const timelineEl = document.getElementById("timeline"); const timelineBufferEl = document.getElementById("timelineBuffer"); const timelineProgressEl = document.getElementById("timelineProgress"); const btnPlayPauseEl = document.getElementById("btnPlayPause"); const btnNextEl = document.getElementById("btnNext"); const btnPrevEl = document.getElementById("btnPrev"); const optAutoNextEl = document.getElementById("optAutoNext"); const optShuffleEl = document.getElementById("optShuffle"); const optRepeatEl = document.getElementById("optRepeat"); const toggleSpectrumEl = document.getElementById("toggleSpectrum"); const toggleMiniEl = document.getElementById("toggleMini"); const spectrumCanvas = document.getElementById("spectrumCanvas"); const spectrumModeLabelEl = document.getElementById("spectrumModeLabel"); const miniPlayerEl = document.getElementById("miniPlayer"); const miniTrackTitleEl = document.getElementById("miniTrackTitle"); const miniTrackMetaEl = document.getElementById("miniTrackMeta"); const miniProgressBarEl = document.getElementById("miniProgressBar"); const miniPlayPauseEl = document.getElementById("miniPlayPause"); const miniNextEl = document.getElementById("miniNext"); const miniToggleVisibilityEl = document.getElementById("miniToggleVisibility"); const miniCloseEl = document.getElementById("miniClose"); const playlistSearchInputEl = document.getElementById("playlistSearchInput"); const spectrumCtx = spectrumCanvas.getContext("2d"); function formatTime(seconds) { if (!isFinite(seconds) || seconds < 0) return "--:--"; const m = Math.floor(seconds / 60); const s = Math.floor(seconds % 60); return String(m).padStart(2, "0") + ":" + String(s).padStart(2, "0"); } function safeLog(...args) { // console.log(...args); } function ensureHttps(url) { if (!url) return ""; try { const u = new URL(url, FEED_URL); if (u.protocol === "http:") { u.protocol = "https:"; } if (u.hostname.toLowerCase().includes("podtrac.com")) { u.protocol = "https:"; } return u.toString(); } catch (e) { return url.replace(/^http:/i, "https:"); } } function initAudioContextIfNeeded() { if (state.audioCtx) return; try { const AudioContextClass = window.AudioContext || window.webkitAudioContext; state.audioCtx = new AudioContextClass(); state.gainNode = state.audioCtx.createGain(); state.analyser = state.audioCtx.createAnalyser(); state.analyser.fftSize = 256; const sourceNode = state.audioCtx.createMediaElementSource(audioEl); sourceNode.connect(state.gainNode); state.gainNode.connect(state.analyser); state.analyser.connect(state.audioCtx.destination); spectrumModeLabelEl.textContent = "Web Audio API (actif)"; } catch (e) { spectrumModeLabelEl.textContent = "Web Audio API indisponible"; state.spectrumEnabled = false; } } async function resumeAudioContextOnGesture() { if (!state.audioCtx) return; if (state.audioCtx.state === "suspended") { try { await state.audioCtx.resume(); } catch (e) { safeLog("AudioContext resume error", e); } } } function startSpectrum() { if (!state.spectrumEnabled || !state.analyser) return; cancelSpectrum(); const bufferLength = state.analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); function draw() { state.spectrumAnimationId = requestAnimationFrame(draw); state.analyser.getByteFrequencyData(dataArray); const width = spectrumCanvas.width; const height = spectrumCanvas.height; spectrumCtx.clearRect(0, 0, width, height); const barWidth = (width / bufferLength) * 1.6; let x = 0; for (let i = 0; i < bufferLength; i++) { const v = dataArray[i] / 255; const barHeight = v * height * 0.9; const gradient = spectrumCtx.createLinearGradient(0, height - barHeight, 0, height); gradient.addColorStop(0, "rgba(255,107,59,0.9)"); gradient.addColorStop(0.5, "rgba(255,179,143,0.7)"); gradient.addColorStop(1, "rgba(59,213,255,0.9)"); spectrumCtx.fillStyle = gradient; spectrumCtx.fillRect(x, height - barHeight, barWidth * 0.8, barHeight); x += barWidth; } } const rect = spectrumCanvas.getBoundingClientRect(); spectrumCanvas.width = rect.width * window.devicePixelRatio; spectrumCanvas.height = rect.height * window.devicePixelRatio; spectrumCtx.setTransform(1, 0, 0, 1, 0, 0); spectrumCtx.scale(window.devicePixelRatio, window.devicePixelRatio); draw(); } function cancelSpectrum() { if (state.spectrumAnimationId) { cancelAnimationFrame(state.spectrumAnimationId); state.spectrumAnimationId = null; } spectrumCtx.clearRect(0, 0, spectrumCanvas.width, spectrumCanvas.height); } function updateUIForTrack(index) { if (index < 0 || index >= state.tracks.length) { currentTrackTitleEl.textContent = "Aucune piste en cours"; currentTrackIndexEl.textContent = "–"; currentTrackCountEl.textContent = state.tracks.length + " titres"; miniTrackTitleEl.textContent = "Aucune piste"; miniTrackMetaEl.textContent = "Playlist RSS MoBBee.fr"; return; } const track = state.tracks[index]; currentTrackTitleEl.textContent = track.title || "Sans titre"; currentTrackIndexEl.textContent = "Titre " + (index + 1); currentTrackCountEl.textContent = state.tracks.length + " titres"; miniTrackTitleEl.textContent = track.title || "Sans titre"; miniTrackMetaEl.textContent = "MoBBee Podcast • #" + (index + 1); const items = playlistListEl.querySelectorAll(".playlist-item"); items.forEach((item, i) => { if (i === index) { item.classList.add("active"); } else { item.classList.remove("active"); } }); } function updatePlayPauseButtons() { const iconPlay = ` `; const iconPause = ` `; btnPlayPauseEl.innerHTML = state.isPlaying ? iconPause : iconPlay; miniPlayPauseEl.innerHTML = state.isPlaying ? iconPause : iconPlay; } function updateTimeline() { const current = audioEl.currentTime || 0; const duration = audioEl.duration || 0; currentTimeLabelEl.textContent = formatTime(current); durationLabelEl.textContent = formatTime(duration); const progress = duration > 0 ? current / duration : 0; timelineProgressEl.style.transform = "scaleX(" + progress + ")"; miniProgressBarEl.style.transform = "scaleX(" + progress + ")"; if (state.autoNext && duration > 0 && progress >= 0.995) { safeLog("AutoNext fallback via timeline"); triggerNextTrack("timeline-fallback"); } } function updateBufferBar() { try { const buffered = audioEl.buffered; if (!buffered || buffered.length === 0) { timelineBufferEl.style.transform = "scaleX(0)"; return; } const end = buffered.end(buffered.length - 1); const duration = audioEl.duration || 0; const ratio = duration > 0 ? end / duration : 0; timelineBufferEl.style.transform = "scaleX(" + Math.min(1, ratio) + ")"; } catch (e) { timelineBufferEl.style.transform = "scaleX(0)"; } } function clearStalledFallback() { if (state.stalledTimeoutId) { clearTimeout(state.stalledTimeoutId); state.stalledTimeoutId = null; } } function clearAutoNextFallbackTimer() { if (state.autoNextFallbackTimerId) { clearTimeout(state.autoNextFallbackTimerId); state.autoNextFallbackTimerId = null; } } function scheduleAutoNextFallback() { clearAutoNextFallbackTimer(); const duration = audioEl.duration || 0; if (!state.autoNext || !isFinite(duration) || duration { if (!audioEl.paused) { safeLog("AutoNext fallback timer déclenché"); triggerNextTrack("timer-fallback"); } }, delay * 1000); } function triggerNextTrack(reason) { safeLog("triggerNextTrack", reason); if (reason === "manual") { } else if (!state.autoNext) { return; } let nextIndex = state.currentIndex; if (state.repeatMode === "one") { nextIndex = state.currentIndex; } else if (state.shuffle) { const total = state.tracks.length; if (total = total) { state.shuffleHistory = []; used.clear(); } let candidate; let attempts = 0; do { candidate = Math.floor(Math.random() * total); attempts++; } while ((candidate === state.currentIndex || used.has(candidate)) && attempts < 20); nextIndex = candidate; state.shuffleHistory.push(candidate); } } else { nextIndex = state.currentIndex + 1; if (nextIndex >= state.tracks.length) { if (state.repeatMode === "all") { nextIndex = 0; } else { state.isPlaying = false; updatePlayPauseButtons(); return; } } } playTrackAtIndex(nextIndex, false); } function triggerPrevTrack() { if (state.shuffle) { if (state.shuffleHistory.length > 1) { state.shuffleHistory.pop(); const prevIndex = state.shuffleHistory[state.shuffleHistory.length - 1]; playTrackAtIndex(prevIndex, false); return; } } let prevIndex = state.currentIndex - 1; if (prevIndex < 0) { prevIndex = 0; } playTrackAtIndex(prevIndex, false); } function applyCrossfadeAndPlay(newIndex) { const fadeDuration = 1.8; const stepInterval = 60; function fadeOut(callback) { if (!state.gainNode) { callback(); return; } const steps = Math.max(1, Math.floor((fadeDuration * 1000) / stepInterval)); let currentStep = 0; const initialGain = state.gainNode.gain.value; const timer = setInterval(() => { currentStep++; const ratio = 1 - currentStep / steps; state.gainNode.gain.value = initialGain * Math.max(0, ratio); if (currentStep >= steps) { clearInterval(timer); state.gainNode.gain.value = 0; callback(); } }, stepInterval); } function fadeIn() { if (!state.gainNode) return; const steps = Math.max(1, Math.floor((fadeDuration * 1000) / stepInterval)); let currentStep = 0; const targetGain = 1; state.gainNode.gain.value = 0; const timer = setInterval(() => { currentStep++; const ratio = currentStep / steps; state.gainNode.gain.value = targetGain * Math.min(1, ratio); if (currentStep >= steps) { clearInterval(timer); state.gainNode.gain.value = targetGain; } }, stepInterval); } fadeOut(() => { const track = state.tracks[newIndex]; if (!track) return; audioEl.src = track.url; audioEl.load(); audioEl.play().then(() => { state.isPlaying = true; state.currentIndex = newIndex; updateUIForTrack(newIndex); updatePlayPauseButtons(); scheduleAutoNextFallback(); fadeIn(); }).catch(err => { safeLog("Erreur lecture piste", err); state.isPlaying = false; updatePlayPauseButtons(); }); }); } function playTrackAtIndex(index, fromUserClick) { if (index < 0 || index >= state.tracks.length) return; clearStalledFallback(); clearAutoNextFallbackTimer(); audioEl.pause(); audioEl.currentTime = 0; const track = state.tracks[index]; audioEl.src = track.url; audioEl.load(); if (state.gainNode && state.isPlaying && !fromUserClick) { applyCrossfadeAndPlay(index); } else { audioEl.play().then(() => { state.isPlaying = true; state.currentIndex = index; updateUIForTrack(index); updatePlayPauseButtons(); scheduleAutoNextFallback(); }).catch(err => { safeLog("Erreur lecture piste", err); state.isPlaying = false; updatePlayPauseButtons(); }); } } function togglePlayPause() { if (state.currentIndex < 0 && state.tracks.length > 0) { state.currentIndex = 0; playTrackAtIndex(0, true); return; } if (audioEl.paused) { resumeAudioContextOnGesture().then(() => { audioEl.play().then(() => { state.isPlaying = true; updatePlayPauseButtons(); scheduleAutoNextFallback(); }).catch(err => { safeLog("Erreur play", err); }); }); } else { audioEl.pause(); state.isPlaying = false; updatePlayPauseButtons(); clearAutoNextFallbackTimer(); } } function syncMiniVisibility() { if (state.miniVisible) { miniPlayerEl.classList.remove("hidden"); } else { miniPlayerEl.classList.add("hidden"); } } function closeMiniPlayer() { state.miniVisible = false; syncMiniVisibility(); } function updateOptionsUI() { optAutoNextEl.classList.toggle("active", state.autoNext); optShuffleEl.classList.toggle("active", state.shuffle); optRepeatEl.classList.toggle("active", true); optRepeatEl.textContent = "Repeat: " + state.repeatMode; } function cycleRepeatMode() { if (state.repeatMode === "none") { state.repeatMode = "one"; } else if (state.repeatMode === "one") { state.repeatMode = "all"; } else { state.repeatMode = "none"; } updateOptionsUI(); } function renderPlaylist() { playlistListEl.innerHTML = ""; state.tracks.forEach((track, index) => { const item = document.createElement("div"); item.className = "playlist-item"; item.dataset.index = index; const indexEl = document.createElement("div"); indexEl.className = "playlist-index"; indexEl.textContent = index + 1; const textEl = document.createElement("div"); textEl.className = "playlist-text"; const titleEl = document.createElement("div"); titleEl.className = "playlist-title-text"; titleEl.textContent = track.title || "Sans titre"; const metaEl = document.createElement("div"); metaEl.className = "playlist-meta-text"; metaEl.textContent = track.pubDate || ""; textEl.appendChild(titleEl); textEl.appendChild(metaEl); const durationEl = document.createElement("div"); durationEl.className = "playlist-duration"; durationEl.textContent = track.duration || ""; item.appendChild(indexEl); item.appendChild(textEl); item.appendChild(durationEl); item.addEventListener("click", () => { playTrackAtIndex(index, true); }); playlistListEl.appendChild(item); }); playlistCountLabelEl.textContent = state.tracks.length + " titres"; currentTrackCountEl.textContent = state.tracks.length + " titres"; updateUIForTrack(state.currentIndex); } function applyPlaylistSearch() { const query = playlistSearchInputEl.value.toLowerCase().trim(); const items = playlistListEl.querySelectorAll(".playlist-item"); items.forEach(item => { const idx = parseInt(item.dataset.index, 10); const track = state.tracks[idx]; const text = (track.title || "").toLowerCase(); const date = (track.pubDate || "").toLowerCase(); const match = !query || text.includes(query) || date.includes(query); item.style.display = match ? "" : "none"; }); } async function loadFeed() { try { const res = await fetch(FEED_URL, { cache: "no-store" }); if (!res.ok) throw new Error("HTTP " + res.status); const text = await res.text(); const parser = new DOMParser(); const xmlDoc = parser.parseFromString(text, "application/xml"); const items = Array.from(xmlDoc.querySelectorAll("item")); const tracks = []; items.forEach(item => { let title = item.querySelector("title")?.textContent?.trim() || ""; let url = ""; let duration = ""; let pubDate = item.querySelector("pubDate")?.textContent?.trim() || ""; const enclosure = item.querySelector("enclosure"); if (enclosure && enclosure.getAttribute("url")) { url = enclosure.getAttribute("url"); } else { const guid = item.querySelector("guid")?.textContent?.trim() || ""; const link = item.querySelector("link")?.textContent?.trim() || ""; if (guid) url = guid; else if (link) url = link; } const itunesDuration = item.querySelector("itunes\\:duration, duration"); if (itunesDuration) { duration = itunesDuration.textContent.trim(); } url = ensureHttps(url); if (url) { tracks.push({ title, url, duration, pubDate }); } }); state.tracks = tracks; if (tracks.length === 0) { feedStatusEl.innerHTML = 'Aucun média trouvé dans le flux.'; } else { feedStatusEl.innerHTML = 'Playlist chargée (' + tracks.length + ' titres).'; } renderPlaylist(); if (state.tracks.length > 0) { state.currentIndex = 0; updateUIForTrack(0); playTrackAtIndex(0, true); } } catch (e) { feedStatusEl.innerHTML = 'Erreur de chargement du flux.'; safeLog("Erreur flux", e); } } function setupAudioEvents() { audioEl.addEventListener("timeupdate", () => { updateTimeline(); updateBufferBar(); state.lastProgressTime = Date.now(); }); audioEl.addEventListener("progress", () => { updateBufferBar(); }); audioEl.addEventListener("ended", () => { clearAutoNextFallbackTimer(); if (state.autoNext) { triggerNextTrack("ended"); } else { state.isPlaying = false; updatePlayPauseButtons(); } }); audioEl.addEventListener("playing", () => { clearStalledFallback(); scheduleAutoNextFallback(); }); audioEl.addEventListener("waiting", () => { clearStalledFallback(); state.stalledTimeoutId = setTimeout(() => { const now = Date.now(); const diff = now - state.lastProgressTime; if (diff > 4000 && !audioEl.paused) { safeLog("Buffering prolongé, tentative de reprise"); audioEl.play().catch(() => {}); } }, 4500); }); audioEl.addEventListener("error", () => { safeLog("Erreur audio", audioEl.error); }); } function setupUIEvents() { btnPlayPauseEl.addEventListener("click", () => { initAudioContextIfNeeded(); resumeAudioContextOnGesture().then(() => { togglePlayPause(); }); }); btnNextEl.addEventListener("click", () => { initAudioContextIfNeeded(); resumeAudioContextOnGesture().then(() => { triggerNextTrack("manual"); }); }); btnPrevEl.addEventListener("click", () => { initAudioContextIfNeeded(); resumeAudioContextOnGesture().then(() => { triggerPrevTrack(); }); }); optAutoNextEl.addEventListener("click", () => { state.autoNext = !state.autoNext; updateOptionsUI(); }); optShuffleEl.addEventListener("click", () => { state.shuffle = !state.shuffle; if (!state.shuffle) { state.shuffleHistory = []; } updateOptionsUI(); }); optRepeatEl.addEventListener("click", () => { cycleRepeatMode(); }); toggleSpectrumEl.addEventListener("click", () => { state.spectrumEnabled = !state.spectrumEnabled; toggleSpectrumEl.classList.toggle("active", state.spectrumEnabled); if (state.spectrumEnabled) { initAudioContextIfNeeded(); startSpectrum(); } else { cancelSpectrum(); } }); toggleMiniEl.addEventListener("click", () => { state.miniVisible = !state.miniVisible; toggleMiniEl.classList.toggle("active", state.miniVisible); syncMiniVisibility(); }); miniPlayPauseEl.addEventListener("click", () => { initAudioContextIfNeeded(); resumeAudioContextOnGesture().then(() => { togglePlayPause(); }); }); miniNextEl.addEventListener("click", () => { initAudioContextIfNeeded(); resumeAudioContextOnGesture().then(() => { triggerNextTrack("manual"); }); }); miniToggleVisibilityEl.addEventListener("click", () => { state.miniVisible = !state.miniVisible; syncMiniVisibility(); }); miniCloseEl.addEventListener("click", () => { closeMiniPlayer(); }); playlistSearchInputEl.addEventListener("input", () => { applyPlaylistSearch(); }); timelineEl.addEventListener("click", (e) => { const rect = timelineEl.getBoundingClientRect(); const x = e.clientX - rect.left; const ratio = x / rect.width; const duration = audioEl.duration || 0; if (duration > 0) { audioEl.currentTime = duration * ratio; updateTimeline(); } }); document.addEventListener("click", () => { initAudioContextIfNeeded(); }, { once: true }); window.addEventListener("resize", () => { if (state.spectrumEnabled && state.analyser) { startSpectrum(); } }); } (function bootstrap() { updateOptionsUI(); setupAudioEvents(); setupUIEvents(); loadFeed().then(() => { initAudioContextIfNeeded(); if (state.spectrumEnabled && state.analyser) { toggleSpectrumEl.classList.add("active"); startSpectrum(); } }); syncMiniVisibility(); })();



MoBBee playlist Podcast Musiques

MoBBee playlist Podcast Musiques
Mardi 30 Juin 2026
MoBBee playlist Podcast Musiques
Recherche par Mots clés : : LECTEUR PODCAST
Dans la même rubrique sur MoBBee.fr: