Fix buffering issue and app bugs: improve stream delivery, fixed reset UI desync, and EQ naming
This commit is contained in:
parent
1776f631ef
commit
02f72e2372
12
script.js
12
script.js
|
|
@ -2242,7 +2242,17 @@ function initListenerMode() {
|
||||||
socket.on('broadcast_started', () => {
|
socket.on('broadcast_started', () => {
|
||||||
const nowPlayingEl = document.getElementById('listener-now-playing');
|
const nowPlayingEl = document.getElementById('listener-now-playing');
|
||||||
if (nowPlayingEl) nowPlayingEl.textContent = '🎵 Stream is live!';
|
if (nowPlayingEl) nowPlayingEl.textContent = '🎵 Stream is live!';
|
||||||
// Reset MediaSource for fresh stream if needed
|
|
||||||
|
// Force a reload of the audio element to capture the fresh stream
|
||||||
|
if (window.listenerAudio) {
|
||||||
|
console.log('🔄 Broadcast started: Refreshing audio stream...');
|
||||||
|
const wasPlaying = !window.listenerAudio.paused;
|
||||||
|
window.listenerAudio.src = getMp3FallbackUrl();
|
||||||
|
window.listenerAudio.load();
|
||||||
|
if (wasPlaying || window.listenerAudioEnabled) {
|
||||||
|
window.listenerAudio.play().catch(e => console.warn('Auto-play after refresh blocked:', e));
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('stream_status', (data) => {
|
socket.on('stream_status', (data) => {
|
||||||
|
|
|
||||||
21
server.py
21
server.py
|
|
@ -55,7 +55,7 @@ _transcode_threads_started = False
|
||||||
_transcoder_bytes_out = 0
|
_transcoder_bytes_out = 0
|
||||||
_transcoder_last_error = None
|
_transcoder_last_error = None
|
||||||
_last_audio_chunk_ts = 0.0
|
_last_audio_chunk_ts = 0.0
|
||||||
_mp3_preroll = collections.deque(maxlen=60) # Pre-roll (~2.5s at 192k)
|
_mp3_preroll = collections.deque(maxlen=256) # Pre-roll (~10s at 192k with 1KB chunks)
|
||||||
|
|
||||||
|
|
||||||
def _start_transcoder_if_needed(is_mp3_input=False):
|
def _start_transcoder_if_needed(is_mp3_input=False):
|
||||||
|
|
@ -141,7 +141,8 @@ def _start_transcoder_if_needed(is_mp3_input=False):
|
||||||
break
|
break
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Transcoder writer error: {e}")
|
if _ffmpeg_proc is not None:
|
||||||
|
print(f"⚠️ Transcoder writer error: {e}")
|
||||||
_transcoder_last_error = f'stdin write failed: {e}'
|
_transcoder_last_error = f'stdin write failed: {e}'
|
||||||
break
|
break
|
||||||
_transcode_threads_started = False
|
_transcode_threads_started = False
|
||||||
|
|
@ -153,8 +154,9 @@ def _start_transcoder_if_needed(is_mp3_input=False):
|
||||||
proc = _ffmpeg_proc
|
proc = _ffmpeg_proc
|
||||||
while proc and proc.poll() is None:
|
while proc and proc.poll() is None:
|
||||||
try:
|
try:
|
||||||
# Use a larger read for efficiency
|
# Smaller read for smoother delivery (1KB)
|
||||||
data = proc.stdout.read(4096)
|
# This prevents buffering delays at lower bitrates
|
||||||
|
data = proc.stdout.read(1024)
|
||||||
if not data:
|
if not data:
|
||||||
break
|
break
|
||||||
_transcoder_bytes_out += len(data)
|
_transcoder_bytes_out += len(data)
|
||||||
|
|
@ -199,6 +201,17 @@ def _stop_transcoder():
|
||||||
|
|
||||||
if proc is None:
|
if proc is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Signal all listening clients to finish their stream
|
||||||
|
with _mp3_lock:
|
||||||
|
clients = list(_mp3_clients)
|
||||||
|
for q in clients:
|
||||||
|
try:
|
||||||
|
q.put_nowait(None)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
_mp3_clients.clear()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
proc.terminate()
|
proc.terminate()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
||||||
27
techdj_qt.py
27
techdj_qt.py
|
|
@ -57,6 +57,7 @@ class AudioEngine:
|
||||||
'loop_active': False,
|
'loop_active': False,
|
||||||
'repeat': False,
|
'repeat': False,
|
||||||
'queue': [],
|
'queue': [],
|
||||||
|
'needs_next_track': False,
|
||||||
},
|
},
|
||||||
'B': {
|
'B': {
|
||||||
'audio_data': None,
|
'audio_data': None,
|
||||||
|
|
@ -75,6 +76,7 @@ class AudioEngine:
|
||||||
'loop_active': False,
|
'loop_active': False,
|
||||||
'repeat': False,
|
'repeat': False,
|
||||||
'queue': [],
|
'queue': [],
|
||||||
|
'needs_next_track': False,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -410,8 +412,8 @@ class BroadcastThread(QThread):
|
||||||
|
|
||||||
# Thread to read encoded chunks from stdout
|
# Thread to read encoded chunks from stdout
|
||||||
def read_output():
|
def read_output():
|
||||||
# Smaller buffer for more frequent updates (4KB = ~0.15s @ 192k)
|
# Smaller buffer for more frequent updates (2KB = ~0.08s @ 192k)
|
||||||
buffer_size = 4096
|
buffer_size = 2048
|
||||||
while self.running:
|
while self.running:
|
||||||
try:
|
try:
|
||||||
data = self.process.stdout.read(buffer_size)
|
data = self.process.stdout.read(buffer_size)
|
||||||
|
|
@ -806,8 +808,9 @@ class DeckWidget(QWidget):
|
||||||
eq_widget = QWidget()
|
eq_widget = QWidget()
|
||||||
eq_layout = QHBoxLayout(eq_widget)
|
eq_layout = QHBoxLayout(eq_widget)
|
||||||
eq_layout.setSpacing(8)
|
eq_layout.setSpacing(8)
|
||||||
|
self.eq_sliders = {}
|
||||||
|
|
||||||
for band in ['HI', 'MID', 'LO']:
|
for band in ['HIGH', 'MID', 'LOW']:
|
||||||
band_widget = QWidget()
|
band_widget = QWidget()
|
||||||
band_layout = QVBoxLayout(band_widget)
|
band_layout = QVBoxLayout(band_widget)
|
||||||
band_layout.setSpacing(2)
|
band_layout.setSpacing(2)
|
||||||
|
|
@ -819,6 +822,7 @@ class DeckWidget(QWidget):
|
||||||
slider.setFixedHeight(80)
|
slider.setFixedHeight(80)
|
||||||
slider.setStyleSheet(self.get_slider_style())
|
slider.setStyleSheet(self.get_slider_style())
|
||||||
slider.valueChanged.connect(lambda v, b=band.lower(): self.on_eq_change(b, v))
|
slider.valueChanged.connect(lambda v, b=band.lower(): self.on_eq_change(b, v))
|
||||||
|
self.eq_sliders[band.lower()] = slider
|
||||||
|
|
||||||
label = QLabel(band)
|
label = QLabel(band)
|
||||||
label.setStyleSheet("color: #888; font-size: 9px;")
|
label.setStyleSheet("color: #888; font-size: 9px;")
|
||||||
|
|
@ -1001,20 +1005,23 @@ class DeckWidget(QWidget):
|
||||||
|
|
||||||
def reset_deck(self):
|
def reset_deck(self):
|
||||||
"""Reset all deck controls to default values"""
|
"""Reset all deck controls to default values"""
|
||||||
|
# Setting values on sliders will trigger the valueChanged signal
|
||||||
|
# which will in turn update the audio engine.
|
||||||
|
|
||||||
# Reset volume to 80%
|
# Reset volume to 80%
|
||||||
self.volume_slider.setValue(80)
|
self.volume_slider.setValue(80)
|
||||||
|
|
||||||
# Reset speed to 100%
|
# Reset speed to 100%
|
||||||
self.speed_slider.setValue(100)
|
self.speed_slider.setValue(100)
|
||||||
|
|
||||||
# Reset EQ to 0 (just update the engine, sliders will update via signals)
|
# Reset EQ sliders to 0
|
||||||
self.audio_engine.set_eq(self.deck_id, 'high', 0)
|
if hasattr(self, 'eq_sliders'):
|
||||||
self.audio_engine.set_eq(self.deck_id, 'mid', 0)
|
for band, slider in self.eq_sliders.items():
|
||||||
self.audio_engine.set_eq(self.deck_id, 'low', 0)
|
slider.setValue(0)
|
||||||
|
|
||||||
# Reset filters
|
# Reset filter sliders
|
||||||
self.audio_engine.set_filter(self.deck_id, 'lowpass', 100)
|
self.lp_slider.setValue(100)
|
||||||
self.audio_engine.set_filter(self.deck_id, 'highpass', 0)
|
self.hp_slider.setValue(0)
|
||||||
|
|
||||||
print(f"🔄 Deck {self.deck_id} reset to defaults")
|
print(f"🔄 Deck {self.deck_id} reset to defaults")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue