call: две фазы исходящего вызова — индикация + разные гудки (desktop/Android)
This commit is contained in:
1 parent
7198154106
commit
b90dc752d2
21 files changed
+170
-21
No files matched your search
@@ -113,7 +113,7 @@ class MainActivity : ComponentActivity() {
|
||||
if (screen != Screen.Call) screenBeforeCall = screen
|
||||
screen = Screen.Call
|
||||
}
|
||||
null -> if (screen == Screen.Call) screen = Screen.ChannelList
|
||||
null -> if (screen == Screen.Call) screen = screenBeforeCall ?: Screen.ChannelList
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
@@ -254,7 +254,7 @@ class MainActivity : ComponentActivity() {
|
||||
Screen.Logs -> LogScreen(onBack = { screen = Screen.ChannelList })
|
||||
Screen.Call -> CallScreen(
|
||||
viewModel = vm,
|
||||
onClose = { vm.dismissCall(); screen = Screen.ChannelList },
|
||||
onClose = { vm.dismissCall(); screen = screenBeforeCall ?: Screen.ChannelList },
|
||||
onMinimize = { screen = screenBeforeCall ?: Screen.ChannelList }
|
||||
)
|
||||
Screen.QrScan -> {
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -44,8 +44,8 @@ fun ActiveCallBanner(
|
||||
val name = st.peerName.ifEmpty { "0x%016x".format(st.peerNodeId) }
|
||||
val status = when (phase) {
|
||||
CallPhase.ACTIVE -> "Разговор " + fmtDuration(st.durationMs)
|
||||
CallPhase.RINGING -> "Гудки…"
|
||||
else -> "Звоним…"
|
||||
CallPhase.RINGING -> "Звоним…"
|
||||
else -> "Пытаемся связаться…"
|
||||
}
|
||||
|
||||
Row(
|
||||
|
||||
@@ -40,8 +40,8 @@ private fun fmtDuration(sec: Int): String {
|
||||
}
|
||||
|
||||
private fun callStatusText(phase: CallPhase): String = when (phase) {
|
||||
CallPhase.OUTGOING -> "Звоним…"
|
||||
CallPhase.RINGING -> "Гудки…"
|
||||
CallPhase.OUTGOING -> "Пытаемся связаться…"
|
||||
CallPhase.RINGING -> "Звоним…"
|
||||
CallPhase.ACTIVE -> "Разговор"
|
||||
CallPhase.ENDED -> "Завершён"
|
||||
else -> ""
|
||||
|
||||
@@ -574,6 +574,7 @@ class ChatViewModel : ViewModel() {
|
||||
val st = _callState.value
|
||||
if (st != null && st.callId == callId) {
|
||||
_callState.value = st.copy(phase = CallPhase.RINGING)
|
||||
CallRingback.startRinging()
|
||||
LogManager.addLog("INFO", "VM", "CALL_RINGING id=0x%016x".format(callId))
|
||||
}
|
||||
}
|
||||
@@ -583,6 +584,7 @@ class ChatViewModel : ViewModel() {
|
||||
val st = _callState.value
|
||||
if (st != null && st.callId == callId) {
|
||||
LogManager.addLog("INFO", "VM", "CALL_ACCEPTED id=0x%016x".format(callId))
|
||||
CallRingback.stop()
|
||||
callAudio.router.setPreferredRoute(
|
||||
ChatApplication.instance.configProvider.getCallAudioRoute())
|
||||
if (!callAudio.start(callId)) {
|
||||
@@ -666,6 +668,7 @@ class ChatViewModel : ViewModel() {
|
||||
LogManager.addLog("INFO", "VM", "CALL_$tag id=0x%016x reason=$reason".format(callId))
|
||||
if (st == null || st.callId != callId) return
|
||||
callAudio.stop()
|
||||
CallRingback.stop()
|
||||
IncomingCallManager.stopRinging()
|
||||
callIncomingAtMs = 0L
|
||||
_muted.value = false
|
||||
@@ -691,6 +694,7 @@ class ChatViewModel : ViewModel() {
|
||||
_callState.value = CallState(callId = callId, channelId = channelId, peerNodeId = peerNodeId,
|
||||
peerName = name, phase = CallPhase.OUTGOING)
|
||||
NativeLib.callStart(channelId, peerNodeId, callId)
|
||||
CallRingback.startConnecting()
|
||||
LogManager.addLog("INFO", "VM", "startCall ch=$channelId peer=0x%016x id=0x%016x".format(peerNodeId, callId))
|
||||
}
|
||||
|
||||
@@ -727,6 +731,7 @@ class ChatViewModel : ViewModel() {
|
||||
|
||||
fun dismissCall() {
|
||||
callAudio.stop()
|
||||
CallRingback.stop()
|
||||
callIncomingAtMs = 0L
|
||||
_callState.value = null
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user