package com.radiola.service import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent import android.util.Log import androidx.core.app.NotificationCompat import androidx.media3.common.util.UnstableApi import androidx.media3.session.MediaSession import androidx.media3.session.MediaSessionService import com.radiola.MainActivity import com.radiola.data.local.dao.AlarmDao import com.radiola.data.local.dao.StationDao import com.radiola.data.remote.LoveStreamResolver import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import javax.inject.Inject @AndroidEntryPoint @UnstableApi class PlayerService : MediaSessionService() { companion object { const val ACTION_ALARM = "com.radiola.ALARM" private const val CHANNEL_ALARM = "radiola_alarm" private const val NOTIF_ID_ALARM = 9001 } @Inject lateinit var playerController: PlayerController @Inject lateinit var alarmDao: AlarmDao @Inject lateinit var stationDao: StationDao @Inject lateinit var loveStreamResolver: LoveStreamResolver @Inject lateinit var alarmScheduler: AlarmScheduler private val serviceScope = CoroutineScope(Dispatchers.Main + SupervisorJob()) private var mediaSession: MediaSession? = null override fun onCreate() { super.onCreate() mediaSession = MediaSession.Builder(this, playerController.player) .setSessionActivity( PendingIntent.getActivity( this, 0, Intent(this, MainActivity::class.java), PendingIntent.FLAG_IMMUTABLE ) ) .build() ensureAlarmChannel() } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if (intent?.action == ACTION_ALARM) { val alarmId = intent.getIntExtra("alarm_id", -1) if (alarmId >= 0) handleAlarm(alarmId) } return START_NOT_STICKY } private fun handleAlarm(alarmId: Int) { // Немедленно поднимаем foreground-уведомление (Android требует ≤5 с после startForegroundService) startForeground(NOTIF_ID_ALARM, buildAlarmNotification()) serviceScope.launch { try { val alarm = alarmDao.getById(alarmId) if (alarm == null) { Log.w("PlayerService", "Будильник #$alarmId не найден в БД") stopForeground(STOP_FOREGROUND_REMOVE) return@launch } val station = stationDao.getByIdOnce(alarm.stationId) if (station == null) { Log.w("PlayerService", "Станция #${alarm.stationId} не найдена, будильник #$alarmId") stopForeground(STOP_FOREGROUND_REMOVE) return@launch } val url = loveStreamResolver.resolve(station.streamUrl) playerController.startAlarmPlayback( url = url, prefix = station.prefix, name = station.name, id = station.id, fadeInMs = alarm.fadeInSec * 1000L ) // Перепланируем или деактивируем будильник if (alarm.daysMask != 0) { // Повторяющийся — планируем следующее срабатывание alarmScheduler.schedule(alarm) } else { // Разовый — отключаем alarmDao.setEnabled(alarmId, false) } // MediaSession-уведомление возьмёт на себя отображение воспроизведения stopForeground(STOP_FOREGROUND_DETACH) } catch (e: Exception) { Log.e("PlayerService", "Ошибка воспроизведения будильника #$alarmId", e) stopForeground(STOP_FOREGROUND_REMOVE) } } } private fun ensureAlarmChannel() { val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (nm.getNotificationChannel(CHANNEL_ALARM) == null) { val channel = NotificationChannel( CHANNEL_ALARM, "Будильник", NotificationManager.IMPORTANCE_HIGH ).apply { description = "Уведомления о срабатывании будильника" } nm.createNotificationChannel(channel) } } private fun buildAlarmNotification(): Notification { val mainIntent = PendingIntent.getActivity( this, 0, Intent(this, MainActivity::class.java), PendingIntent.FLAG_IMMUTABLE ) return NotificationCompat.Builder(this, CHANNEL_ALARM) .setSmallIcon(android.R.drawable.ic_lock_idle_alarm) .setContentTitle("Будильник") .setContentText("Запуск радио…") .setContentIntent(mainIntent) .setOngoing(true) .build() } override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession override fun onTaskRemoved(rootIntent: Intent?) { if (!playerController.isPlaying.value) { stopSelf() } } override fun onDestroy() { mediaSession?.release() mediaSession = null // serviceScope — поле этого сервиса (пере-создаётся при рестарте), отменяем. // playerController — @Singleton (переживает рестарт сервиса), его НЕ релизим: // иначе новый PlayerService построит MediaSession на освобождённом плеере. serviceScope.cancel() super.onDestroy() } }