Spaces:
Running
Running
| import os | |
| from typing import Optional | |
| import streamlit as st | |
| def _get_audio_dir() -> str: | |
| return os.path.join(os.path.dirname(__file__), "assets", "audio") | |
| def get_audio_tracks() -> list[tuple[str, str]]: | |
| """Return list of (label, absolute_path) for .mp3 files in assets/audio.""" | |
| audio_dir = _get_audio_dir() | |
| if not os.path.isdir(audio_dir): | |
| return [] | |
| files = [f for f in os.listdir(audio_dir) if f.lower().endswith(".mp3")] | |
| files.sort() | |
| return [(os.path.splitext(f)[0].replace("_", " ").title(), os.path.join(audio_dir, f)) for f in files] | |
| def _load_audio_data_url(path: str) -> str: | |
| """Return a data: URL for the given audio file so the browser can play it.""" | |
| import base64, mimetypes | |
| mime, _ = mimetypes.guess_type(path) | |
| if not mime: | |
| mime = "audio/mpeg" | |
| with open(path, "rb") as fp: | |
| encoded = base64.b64encode(fp.read()).decode("ascii") | |
| return f"data:{mime};base64,{encoded}" | |
| def _mount_background_audio(enabled: bool, src_data_url: Optional[str], volume: float) -> None: | |
| """Create/update a single hidden <audio> element in the top page and play/pause it.""" | |
| from streamlit.components.v1 import html as _html | |
| if not enabled or not src_data_url: | |
| _html( | |
| """ | |
| <script> | |
| (function(){ | |
| const doc = window.parent?.document || document; | |
| const el = doc.getElementById('bw-bg-audio'); | |
| if (el) { try { el.pause(); } catch(e){} } | |
| })(); | |
| </script> | |
| """, | |
| height=0, | |
| ) | |
| return | |
| # Clamp volume | |
| vol = max(0.0, min(1.0, float(volume))) | |
| # Inject or update a single persistent audio element and make sure it starts after interaction if autoplay is blocked | |
| _html( | |
| f""" | |
| <script> | |
| (function(){{ | |
| const doc = window.parent?.document || document; | |
| let audio = doc.getElementById('bw-bg-audio'); | |
| if (!audio) {{ | |
| audio = doc.createElement('audio'); | |
| audio.id = 'bw-bg-audio'; | |
| audio.style.display = 'none'; | |
| audio.setAttribute('loop', ''); | |
| audio.setAttribute('autoplay', ''); | |
| doc.body.appendChild(audio); | |
| }} | |
| const newSrc = "{src_data_url}"; | |
| if (audio.src !== newSrc) {{ | |
| audio.src = newSrc; | |
| }} | |
| audio.muted = false; | |
| audio.volume = {vol:.3f}; | |
| const tryPlay = () => {{ | |
| const p = audio.play(); | |
| if (p && p.catch) {{ p.catch(() => {{ /* ignore autoplay block until user gesture */ }}); }} | |
| }}; | |
| tryPlay(); | |
| const unlock = () => {{ | |
| tryPlay(); | |
| }}; | |
| // Add once-only listeners to resume playback after first user interaction | |
| doc.addEventListener('pointerdown', unlock, {{ once: true }}); | |
| doc.addEventListener('keydown', unlock, {{ once: true }}); | |
| doc.addEventListener('touchstart', unlock, {{ once: true }}); | |
| }})(); | |
| </script> | |
| """, | |
| height=0, | |
| ) | |
| def _inject_audio_control_sync(): | |
| """Inject JS to sync volume and enable/disable state immediately.""" | |
| from streamlit.components.v1 import html as _html | |
| _html( | |
| ''' | |
| <script> | |
| (function(){ | |
| const doc = window.parent?.document || document; | |
| const audio = doc.getElementById('bw-bg-audio'); | |
| if (!audio) return; | |
| // Get values from Streamlit DOM | |
| const volInput = doc.querySelector('input[type="range"][aria-label="Volume"]'); | |
| const enableInput = doc.querySelector('input[type="checkbox"][aria-label="Enable music"]'); | |
| if (volInput) { | |
| volInput.addEventListener('input', function(){ | |
| audio.volume = parseFloat(this.value)/100; | |
| }); | |
| // Set initial volume | |
| audio.volume = parseFloat(volInput.value)/100; | |
| } | |
| if (enableInput) { | |
| enableInput.addEventListener('change', function(){ | |
| if (this.checked) { | |
| audio.muted = false; | |
| audio.play().catch(()=>{}); | |
| } else { | |
| audio.muted = true; | |
| audio.pause(); | |
| } | |
| }); | |
| // Set initial mute state | |
| if (enableInput.checked) { | |
| audio.muted = false; | |
| audio.play().catch(()=>{}); | |
| } else { | |
| audio.muted = true; | |
| audio.pause(); | |
| } | |
| } | |
| })(); | |
| </script> | |
| ''', | |
| height=0, | |
| ) |