21 changed files with 170 additions and 21 deletions
@ -0,0 +1,72 @@ |
|||||||
|
package com.utun.chat.data |
||||||
|
|
||||||
|
import android.media.AudioAttributes |
||||||
|
import android.media.MediaPlayer |
||||||
|
import com.utun.chat.ChatApplication |
||||||
|
import com.utun.chat.R |
||||||
|
|
||||||
|
/** |
||||||
|
* Гудки исходящего звонка (ringback) для вызывающего через зацикленный MediaPlayer. |
||||||
|
* |
||||||
|
* Две фазы с разными гудками: |
||||||
|
* - фаза 1 «пытаемся связаться» — call_connecting (короткие бипы 425 Гц); |
||||||
|
* - фаза 2 «звоним» (пир принял вызов, у него звонит) — call_ringing (длинные гудки). |
||||||
|
* |
||||||
|
* ToneGenerator для call-тонов на современном Android (API 31+) молчит, поэтому |
||||||
|
* гудки — готовые MP3 из res/raw, как входящая мелодия (RingtonePlayer). |
||||||
|
* На ACCEPTED/ENDED/hangup вызывающий останавливает гудок. Идемпотентный stop. |
||||||
|
*/ |
||||||
|
object CallRingback { |
||||||
|
|
||||||
|
@Volatile private var player: MediaPlayer? = null |
||||||
|
|
||||||
|
/** Фаза 1: короткие бипы «устанавливаем соединение». Идемпотентно. */ |
||||||
|
fun startConnecting() { |
||||||
|
start(R.raw.call_connecting, "connecting") |
||||||
|
} |
||||||
|
|
||||||
|
/** Фаза 2: длинные гудки (у пира идёт вызов). Идемпотентно. */ |
||||||
|
fun startRinging() { |
||||||
|
start(R.raw.call_ringing, "ringing") |
||||||
|
} |
||||||
|
|
||||||
|
/** Остановить текущий гудок. Идемпотентно. */ |
||||||
|
fun stop() { |
||||||
|
release() |
||||||
|
LogManager.addLog("INFO", "Ringback", "stopped") |
||||||
|
} |
||||||
|
|
||||||
|
private fun start(resId: Int, tag: String) { |
||||||
|
release() |
||||||
|
val ctx = ChatApplication.instance |
||||||
|
val mp = try { |
||||||
|
MediaPlayer.create(ctx, resId) |
||||||
|
} catch (e: Exception) { |
||||||
|
LogManager.addLog("ERROR", "Ringback", "MediaPlayer.create failed ($tag): ${e.message}") |
||||||
|
null |
||||||
|
} ?: return |
||||||
|
try { |
||||||
|
mp.setAudioAttributes( |
||||||
|
AudioAttributes.Builder() |
||||||
|
.setUsage(AudioAttributes.USAGE_MEDIA) |
||||||
|
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) |
||||||
|
.build() |
||||||
|
) |
||||||
|
mp.isLooping = true |
||||||
|
mp.start() |
||||||
|
} catch (e: Exception) { |
||||||
|
LogManager.addLog("ERROR", "Ringback", "MediaPlayer start failed ($tag): ${e.message}") |
||||||
|
try { mp.release() } catch (_: Exception) {} |
||||||
|
return |
||||||
|
} |
||||||
|
player = mp |
||||||
|
LogManager.addLog("INFO", "Ringback", "started $tag") |
||||||
|
} |
||||||
|
|
||||||
|
private fun release() { |
||||||
|
val p = player ?: return |
||||||
|
player = null |
||||||
|
try { p.stop() } catch (_: Exception) {} |
||||||
|
try { p.release() } catch (_: Exception) {} |
||||||
|
} |
||||||
|
} |
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue