Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 29 additions & 15 deletions src/main_stream_service/main_stream_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,25 +304,35 @@ async def _stop_or_recover_paused_track(
async def _resume_radio_if_silent(
self, track: Track, ctx: "_CycleContext"
) -> None:
"""Возобновляет радио, если станция играет, а Ruark молчит."""
if not (
ctx.last_track_progress != track.progress
and track.type == "FmRadio"
and not await self._ruark_controls.is_playing()
):
"""Возобновляет радио, если станция играет, а Ruark молчит.

Пересылка не чаще SILENCE_RESEND_GRACE после последней отправки:
свежепривязанному потоку нужно время на буферизацию HLS, иначе
ежесекундные перепривязки сами не дают радио стартовать.
"""
if track.type != "FmRadio":
return
if ctx.last_track_progress == track.progress:
return
# До первой привязки потока возобновлять нечего
if not ctx.last_stream_sent_at:
return
if time.monotonic() - ctx.last_stream_sent_at < SILENCE_RESEND_GRACE:
return
if await self._ruark_controls.is_playing():
return

logger.info("🔁 Возобновляем воспроизведение радио")
radio_url = await self._station_controls.get_radio_url()
if radio_url:
await self._send_track_to_stream_server(
track_url=radio_url,
radio=True,
title=track.title,
)
await asyncio.sleep(1)
else:
if not radio_url:
logger.warning("⚠️ Не удалось получить URL радиостанции")
return
ctx.last_stream_sent_at = time.monotonic()
await self._send_track_to_stream_server(
track_url=radio_url,
radio=True,
title=track.title,
)

async def _refresh_track_if_unchanged(
self, track: Track, ctx: "_CycleContext"
Expand Down Expand Up @@ -577,7 +587,10 @@ async def _restore_ruark_after_speech(
logger.info("🔁 Возвращаем громкость Ruark")
await self._ruark_controls.set_volume(self._ruark_volume)

for _ in range(30):
# Радио буферизует HLS дольше, чем стартует трек, —
# ждём дольше, чтобы не перепривязывать поток зря
attempts = 80 if track.type == "FmRadio" else 30
for _ in range(attempts):
if await self._ruark_controls.is_playing():
logger.info("▶️ Ruark начал играть")
await self._station_controls.fade_out_alice_volume()
Expand All @@ -592,6 +605,7 @@ async def _restore_ruark_after_speech(
if ctx.track_url:
# Для трека продолжаем с текущей позиции станции,
# а не с начала — иначе рассинхрон
ctx.last_stream_sent_at = time.monotonic()
await self._send_track_to_stream_server(
ctx.track_url,
radio=track.type == "FmRadio",
Expand Down
41 changes: 41 additions & 0 deletions tests/test_streaming_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,47 @@ async def test_radio_track_sent_with_radio_flag(fast_sleep):
assert call.kwargs["radio"] is True


async def test_silent_radio_not_respammed_within_grace(fast_sleep):
"""Молчащий Ruark на радио не бомбардируется перепривязками."""
# Прогресс радио тикает каждую итерацию — как на живой станции
tracks = [
make_track(
id="fm_jazz", type="FmRadio", duration=0.0, progress=float(p)
)
for p in range(1, 60)
]
station = make_station_controls(tracks=tracks)
manager = make_manager(station, make_ruark(is_playing=False))
send = manager._send_track_to_stream_server

await drive_streaming(manager, until=lambda: False, timeout=0.3)

# Только первоначальный свитч: грейс не даёт слать каждую итерацию
assert send.call_count == 1


async def test_silent_radio_resent_after_grace(fast_sleep, monkeypatch):
"""После грейса радио пересылается, если Ruark так и молчит."""
monkeypatch.setattr(
"main_stream_service.main_stream_manager.SILENCE_RESEND_GRACE", 0.0
)
tracks = [
make_track(
id="fm_jazz", type="FmRadio", duration=0.0, progress=float(p)
)
for p in range(1, 60)
]
station = make_station_controls(tracks=tracks)
manager = make_manager(station, make_ruark(is_playing=False))
send = manager._send_track_to_stream_server

await drive_streaming(manager, until=lambda: send.call_count >= 2)

assert send.call_count >= 2
resume_call = send.call_args_list[1]
assert resume_call.kwargs["radio"] is True


async def test_alice_speech_ducks_ruark_volume(fast_sleep):
track = make_track()
# Первый вызов — инициализация last_alice_state, затем смена состояния
Expand Down
Loading