From d9acc0efb4c4999c8cd87af29ecab90f5b081f4b Mon Sep 17 00:00:00 2001 From: nk Date: Sat, 6 Jun 2026 18:21:04 +0300 Subject: [PATCH] =?UTF-8?q?fix(sleep):=20=D0=B7=D0=B2=D1=83=D0=BA=20=D1=81?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B2=D0=BF=D0=BB=D1=8B=D0=B2=D0=B0=D0=B5=D1=82?= =?UTF-8?q?=20=D0=B2=20=D0=BA=D0=BE=D0=BD=D1=86=D0=B5=20=D1=82=D0=B0=D0=B9?= =?UTF-8?q?=D0=BC=D0=B5=D1=80=D0=B0,=20=D0=B0=20=D0=BD=D0=B5=20=D0=B2=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B2=D1=8B=D0=B5=2090=20=D1=81=D0=B5=D0=BA?= =?UTF-8?q?=D1=83=D0=BD=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Было: при таймере со звуком радио кроссфейдилось в шум в НАЧАЛЕ (CROSSFADE_MS=90с), и для 15-мин таймера уже через ~1.5 мин играл полный белый шум всё оставшееся время. Стало: радио играет почти весь таймер; в последние SOUND_OUTRO_MS (3 мин, но не больше половины таймера) включается звук сна — радио кроссфейдится в шум, шум держится, в самом конце затухает в тишину. Генератор шума стартует лениво (только в аутро, не молотит весь таймер). Засыпаешь под радио, а не под резкий шум сразу. --- .../com/radiola/service/PlayerController.kt | 68 +++++++++++-------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/radiola/service/PlayerController.kt b/app/src/main/java/com/radiola/service/PlayerController.kt index 90e1b44..92b4d09 100644 --- a/app/src/main/java/com/radiola/service/PlayerController.kt +++ b/app/src/main/java/com/radiola/service/PlayerController.kt @@ -86,8 +86,9 @@ class PlayerController @Inject constructor( private companion object { const val FADE_MS = 20_000L // длительность плавного затухания в конце таймера - const val CROSSFADE_MS = 90_000L // переход радио → звук для сна + const val CROSSFADE_MS = 90_000L // переход радио → звук для сна (внутри аутро) const val SOUND_VOL = 0.6f // комфортная громкость шума + const val SOUND_OUTRO_MS = 180_000L // финальное окно со звуком сна (последние ~3 мин) } private val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager @@ -253,8 +254,10 @@ class PlayerController @Inject constructor( /** * Запустить таймер сна на [durationMs] мс. * Без [sound]: в последние FADE_MS радио экспоненциально затухает, затем пауза. - * Со [sound] («Smart Sleep Fade»): в начале радио кроссфейдится в звук для сна - * (радио ↓, шум ↑), затем шум играет, в конце затухает — как в спеке. + * Со [sound]: радио играет почти весь таймер; в последние SOUND_OUTRO_MS (не больше + * половины таймера) включается звук для сна — радио кроссфейдится в шум (радио ↓, + * шум ↑), шум держится, в самом конце затухает в тишину. Засыпаешь под радио, а не + * под резкий белый шум в первые же полторы минуты. */ fun startSleepTimer(durationMs: Long, sound: SleepSound? = null) { sleepJob?.cancel() @@ -263,41 +266,52 @@ class PlayerController @Inject constructor( sleepJob = timerScope.launch { val start = SystemClock.elapsedRealtime() val end = start + durationMs - // Кроссфейд не длиннее трети таймера (для коротких интервалов). - val crossfade = if (sound != null) CROSSFADE_MS.coerceAtMost(durationMs / 3) else 0L - if (sound != null) sleepSoundPlayer.start(sound) + // Финальное окно со звуком — не длиннее половины таймера (для коротких). + val outro = if (sound != null) SOUND_OUTRO_MS.coerceAtMost(durationMs / 2) else 0L + // Кроссфейд радио→шум занимает первую половину аутро. + val crossfade = CROSSFADE_MS.coerceAtMost(outro / 2).coerceAtLeast(1L) + var soundStarted = false while (true) { val now = SystemClock.elapsedRealtime() val remaining = end - now if (remaining <= 0L) break _sleepRemainingMs.value = remaining - if (sound != null) { - val elapsed = now - start - when { - elapsed < crossfade -> { - // Кроссфейд: радио вниз, шум вверх. - val f = elapsed.toFloat() / crossfade - exoPlayer.volume = (1f - f).coerceIn(0f, 1f) - sleepSoundPlayer.setVolume(f * SOUND_VOL) + when { + sound != null && remaining <= outro -> { + // Генератор шума стартуем лениво — только в аутро, не весь таймер. + if (!soundStarted) { + sleepSoundPlayer.start(sound) + soundStarted = true } - remaining <= FADE_MS -> { - // Финальное затухание шума. - val frac = remaining.toFloat() / FADE_MS - sleepSoundPlayer.setVolume((frac * frac) * SOUND_VOL) - } - else -> { - // Радио отыграло — на паузу, шум на комфортной громкости. - if (exoPlayer.isPlaying) exoPlayer.pause() - sleepSoundPlayer.setVolume(SOUND_VOL) + val outroElapsed = outro - remaining + when { + outroElapsed < crossfade -> { + // Кроссфейд: радио вниз, шум вверх. + val f = outroElapsed.toFloat() / crossfade + exoPlayer.volume = (1f - f).coerceIn(0f, 1f) + sleepSoundPlayer.setVolume(f * SOUND_VOL) + } + remaining <= FADE_MS -> { + // Финальное затухание шума в тишину. + val frac = remaining.toFloat() / FADE_MS + sleepSoundPlayer.setVolume((frac * frac) * SOUND_VOL) + } + else -> { + // Радио отыграло — пауза, шум на комфортной громкости. + if (exoPlayer.isPlaying) exoPlayer.pause() + sleepSoundPlayer.setVolume(SOUND_VOL) + } } + delay(150) } - delay(150) - } else { - if (remaining <= FADE_MS) { + sound == null && remaining <= FADE_MS -> { + // Без звука: экспоненциальное затухание радио в конце. val frac = remaining.toFloat() / FADE_MS exoPlayer.volume = (frac * frac).coerceIn(0f, 1f) delay(150) - } else { + } + else -> { + // Основная фаза: радио играет как обычно. delay(1_000) } }