diff --git a/atox/src/main/AndroidManifest.xml b/atox/src/main/AndroidManifest.xml
index 9698eaa8c..533c45e27 100644
--- a/atox/src/main/AndroidManifest.xml
+++ b/atox/src/main/AndroidManifest.xml
@@ -13,6 +13,8 @@
+
+
contactRepository.setStatusMessage(publicKey, message)
@@ -197,7 +202,14 @@ class EventListenerCallbacks @Inject constructor(
callStateHandler = { pk, callState ->
Log.e(TAG, "callState ${pk.fingerprint()} $callState")
- if (callState.contains(ToxavFriendCallState.FINISHED) || callState.contains(ToxavFriendCallState.ERROR)) {
+ if (callState.contains(ToxavFriendCallState.SENDING_A) ||
+ callState.contains(ToxavFriendCallState.ACCEPTING_A)
+ ) {
+ callManager.setAnswered(PublicKey(pk))
+ }
+ if (callState.contains(ToxavFriendCallState.FINISHED) ||
+ callState.contains(ToxavFriendCallState.ERROR)
+ ) {
audioPlayer?.stop()
audioPlayer?.release()
audioPlayer = null
@@ -238,8 +250,10 @@ class EventListenerCallbacks @Inject constructor(
if (audioPlayer == null) {
audioPlayer = AudioPlayer(samplingRate, channels)
audioPlayer?.start()
+ frameCount.set(0)
}
audioPlayer?.buffer(pcm)
+ frameCount.incrementAndGet()
}
}
}
diff --git a/atox/src/main/kotlin/ui/call/CallFragment.kt b/atox/src/main/kotlin/ui/call/CallFragment.kt
index 1b2fa540e..609332e0c 100644
--- a/atox/src/main/kotlin/ui/call/CallFragment.kt
+++ b/atox/src/main/kotlin/ui/call/CallFragment.kt
@@ -6,7 +6,11 @@
package ltd.evilcorp.atox.ui.call
import android.Manifest
+import android.media.MediaPlayer
+import android.os.Build
import android.os.Bundle
+import android.os.SystemClock
+import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
@@ -14,8 +18,14 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.viewModels
-import androidx.lifecycle.asLiveData
+import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
import ltd.evilcorp.atox.R
import ltd.evilcorp.atox.databinding.FragmentCallBinding
import ltd.evilcorp.atox.hasPermission
@@ -24,9 +34,11 @@ import ltd.evilcorp.atox.ui.BaseFragment
import ltd.evilcorp.atox.ui.chat.CONTACT_PUBLIC_KEY
import ltd.evilcorp.atox.vmFactory
import ltd.evilcorp.core.vo.PublicKey
-import ltd.evilcorp.domain.feature.CallState
+import ltd.evilcorp.domain.feature.Call
+import ltd.evilcorp.domain.feature.inCall
private const val PERMISSION = Manifest.permission.RECORD_AUDIO
+private const val TAG = "CallFragment"
class CallFragment : BaseFragment(FragmentCallBinding::inflate) {
private val vm: CallViewModel by viewModels { vmFactory }
@@ -35,13 +47,20 @@ class CallFragment : BaseFragment(FragmentCallBinding::infl
ActivityResultContracts.RequestPermission(),
) { granted ->
if (granted) {
- vm.startSendingAudio()
+ vm.setMicrophoneOn()
+ updateMicrophoneControlIcon()
} else {
+ Log.d(TAG, "Got no permission")
Toast.makeText(requireContext(), getString(R.string.call_mic_permission_needed), Toast.LENGTH_LONG).show()
}
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) = binding.run {
+ private var mediaPlayer: MediaPlayer? = null
+ private var timerNHandle: Job? = null
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?): Unit = binding.run {
+ Log.d(TAG, "onViewCreated here")
+
ViewCompat.setOnApplyWindowInsetsListener(view) { _, compat ->
val insets = compat.getInsets(WindowInsetsCompat.Type.systemBars())
controlContainer.updatePadding(bottom = insets.bottom + controlContainer.paddingTop)
@@ -51,31 +70,31 @@ class CallFragment : BaseFragment(FragmentCallBinding::infl
vm.setActiveContact(PublicKey(requireStringArg(CONTACT_PUBLIC_KEY)))
vm.contact.observe(viewLifecycleOwner) {
avatarImageView.setFrom(it)
+ tvData.setText(it.name)
}
endCall.setOnClickListener {
+ Log.d(TAG, "finishing by End Call")
vm.endCall()
- findNavController().popBackStack()
+ adoptState(Call.State.IDLE)
}
- vm.sendingAudio.asLiveData().observe(viewLifecycleOwner) { sending ->
- if (sending) {
- microphoneControl.setImageResource(R.drawable.ic_mic)
- } else {
- microphoneControl.setImageResource(R.drawable.ic_mic_off)
- }
- }
+ vm.micOn = requireContext().hasPermission(PERMISSION)
+ updateMicrophoneControlIcon()
microphoneControl.setOnClickListener {
- if (vm.sendingAudio.value) {
- vm.stopSendingAudio()
+ if (!requireContext().hasPermission(PERMISSION)) {
+ vm.micOn = false
+ /*Toast.makeText(
+ context,
+ R.string.call_mic_permission_needed,
+ Toast.LENGTH_LONG
+ ).show()*/
+ requestPermissionLauncher.launch(PERMISSION)
} else {
- if (requireContext().hasPermission(PERMISSION)) {
- vm.startSendingAudio()
- } else {
- requestPermissionLauncher.launch(PERMISSION)
- }
+ vm.toggleMicrophoneControl()
}
+ updateMicrophoneControlIcon()
}
updateSpeakerphoneIcon()
@@ -88,33 +107,110 @@ class CallFragment : BaseFragment(FragmentCallBinding::infl
findNavController().popBackStack()
}
- if (vm.inCall.value is CallState.InCall) {
- vm.inCall.asLiveData().observe(viewLifecycleOwner) { inCall ->
- if (inCall == CallState.NotInCall) {
- findNavController().popBackStack()
- }
- }
- return
+ vm.callLiveData.observe(viewLifecycleOwner) { call ->
+ Log.d(TAG, "observer here")
+ adoptState()
}
- startCall()
-
- if (requireContext().hasPermission(PERMISSION)) {
- vm.startSendingAudio()
+ if (vm.call.value.state != Call.State.IDLE &&
+ vm.call.value.state != Call.State.PENDING
+ ) {
+ adoptState()
+ return@run
}
- }
+ binding.tvState.setText("startinng a call...") // normally, not to be seen
+ vm.startCall()
+ } // end onViewCreated
+
+ /*override fun onResume() = binding.run {
+ val nme = vm.call.value.state
+ Log.d(TAG, "onResume here, state=$nme")
+ super.onResume()
+ }*/
private fun updateSpeakerphoneIcon() {
- val icon = if (vm.speakerphoneOn) R.drawable.ic_speakerphone else R.drawable.ic_speakerphone_off
+ val icon = if (vm.speakerphoneOn) {
+ R.drawable.ic_speakerphone
+ } else {
+ R.drawable.ic_speakerphone_off
+ }
binding.speakerphone.setImageResource(icon)
}
- private fun startCall() {
- vm.startCall()
- vm.inCall.asLiveData().observe(viewLifecycleOwner) { inCall ->
- if (inCall == CallState.NotInCall) {
+ private fun updateMicrophoneControlIcon() {
+ val icon = if (vm.micOn) {
+ R.drawable.ic_mic
+ } else {
+ R.drawable.ic_mic_off
+ }
+ binding.microphoneControl.setImageResource(icon)
+ }
+ private fun adoptState() {
+ adoptState(vm.call.value.state)
+ }
+ private fun adoptState(state: Call.State) {
+ // may be called repeatedly, so must be idempotent
+ Log.d(TAG, "adoptState, state = $state")
+ when (state) {
+ Call.State.CALLING_OUT -> {
+ binding.tvState.setText(getString(R.string.ringing))
+ playConnecting()
+ }
+ Call.State.ANSWERED -> {
+ stopPlay()
+ binding.tvState.setText("talking")
+ startTimer()
+ if (!vm.sendingAudio.value && vm.micOn) {
+ if (requireContext().hasPermission(PERMISSION)) {
+ vm.startSendingAudio()
+ }
+ }
+ }
+ // as LiveData never emits its init value, IDLE means the call is finished
+ Call.State.IDLE -> {
+ binding.tvState.setText("00000")
+ stopPlay()
findNavController().popBackStack()
}
+ else -> Log.e(TAG, "STATE = $state")
+ }
+ }
+
+ private fun playConnecting() {
+ val audioAttrContext =
+ if (Build.VERSION.SDK_INT >= 30) {
+ context?.createAttributionContext("audioPlayback")
+ } else {
+ context
+ }
+ if (mediaPlayer == null) {
+ mediaPlayer = MediaPlayer.create(audioAttrContext, R.raw.connecting_ringtone)
+ mediaPlayer?.setLooping(true)
+ }
+ mediaPlayer?.start()
+ }
+
+ private fun stopPlay() {
+ mediaPlayer?.stop()
+ mediaPlayer = null
+ }
+
+ private fun startTimer() {
+ if (!vm.call.value.inCall()) return
+ if (timerNHandle?.isActive == true) return
+ val from: Long = vm.call.value.data?.startTime ?: 0
+ timerNHandle = lifecycleScope.launch(Dispatchers.IO) {
+ while (vm.call.value.inCall()) {
+ lifecycleScope.launch {
+ val elapsed: Duration = (SystemClock.elapsedRealtime() - from).milliseconds
+ val s = elapsed.toComponents { hours, minutes, seconds, nanoseconds ->
+ // String.format("%01d:%02d:%02d", hours, minutes, seconds)
+ vm.presentTime(hours, minutes, seconds, nanoseconds)
+ }
+ binding.tvState.setText(s)
+ }
+ delay(vm.screenTimerMs)
+ }
}
}
}
diff --git a/atox/src/main/kotlin/ui/call/CallViewModel.kt b/atox/src/main/kotlin/ui/call/CallViewModel.kt
index 5adcbfacd..44af8f776 100644
--- a/atox/src/main/kotlin/ui/call/CallViewModel.kt
+++ b/atox/src/main/kotlin/ui/call/CallViewModel.kt
@@ -8,14 +8,17 @@ package ltd.evilcorp.atox.ui.call
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
+import androidx.lifecycle.viewModelScope
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import ltd.evilcorp.atox.ProximityScreenOff
+import ltd.evilcorp.atox.tox.EventListenerCallbacks
import ltd.evilcorp.atox.ui.NotificationHelper
import ltd.evilcorp.core.vo.Contact
import ltd.evilcorp.core.vo.PublicKey
+import ltd.evilcorp.domain.feature.Call
import ltd.evilcorp.domain.feature.CallManager
import ltd.evilcorp.domain.feature.ContactManager
@@ -26,6 +29,13 @@ class CallViewModel @Inject constructor(
private val contactManager: ContactManager,
private val proximityScreenOff: ProximityScreenOff,
) : ViewModel() {
+ val vmContext = viewModelScope.coroutineContext
+ var storedFrameCount: Int = 0
+
+ @Inject
+ lateinit var eventListenerCallbacks: EventListenerCallbacks
+
+ val screenTimerMs = 1000L
private var publicKey = PublicKey("")
val contact: LiveData by lazy {
@@ -58,8 +68,58 @@ class CallViewModel @Inject constructor(
}
}
- val inCall = callManager.inCall
- val sendingAudio = callManager.sendingAudio
+ var micOn = false
+ fun toggleMicrophoneControl() {
+ if (micOn) {
+ micOn = false
+ if (sendingAudio.value) {
+ stopSendingAudio()
+ }
+ } else {
+ setMicrophoneOn()
+ }
+ }
+
+ fun setMicrophoneOn() {
+ micOn = true
+ if (!sendingAudio.value && call.value.state == Call.State.ANSWERED) {
+ startSendingAudio()
+ }
+ }
+
+ fun presentTime(hours: Long, minutes: Int, seconds: Int, nanoseconds: Int): String {
+ var sf: String = when (call.value.data?.inOrOut) {
+ Call.InOrOut.INCOMING -> "in "
+ Call.InOrOut.OUTGOING -> "out "
+ else -> ""
+ }
+ sf += if (hours == 0L) {
+ String.format("%02d:%02d", minutes, seconds)
+ } else {
+ String.format("%01d:%02d:%02d", hours, minutes, seconds)
+ }
+
+ sf += presentFrameRate()
+ return sf
+ }
+ fun presentFrameRate(): String {
+ val new = eventListenerCallbacks.getFrameCount()
+ var delta = new - storedFrameCount
+ if (delta < 0) delta = 0
+ storedFrameCount = new
+ // normal fps = screenTimerMs / CallManager.AUDIO_SEND_INTERVAL_MS = 50
+ val asSymbol = if (delta > 25) {
+ " +" // audio link is working
+ } else {
+ " X" // few or no frames are coming in
+ }
+ return asSymbol
+ // return " ${delta}fps"
+ }
+
+ val call = callManager.call
+ val callLiveData = callManager.call.asLiveData(vmContext)
+ val sendingAudio = callManager.sendingAudio
var speakerphoneOn by callManager::speakerphoneOn
}
diff --git a/atox/src/main/kotlin/ui/chat/ChatFragment.kt b/atox/src/main/kotlin/ui/chat/ChatFragment.kt
index f9a504063..4d2beb8e3 100644
--- a/atox/src/main/kotlin/ui/chat/ChatFragment.kt
+++ b/atox/src/main/kotlin/ui/chat/ChatFragment.kt
@@ -55,7 +55,7 @@ import ltd.evilcorp.core.vo.Message
import ltd.evilcorp.core.vo.MessageType
import ltd.evilcorp.core.vo.PublicKey
import ltd.evilcorp.core.vo.isComplete
-import ltd.evilcorp.domain.feature.CallState
+import ltd.evilcorp.domain.feature.inCall
private const val TAG = "ChatFragment"
const val CONTACT_PUBLIC_KEY = "publicKey"
@@ -252,11 +252,11 @@ class ChatFragment : BaseFragment(FragmentChatBinding::infl
}
viewModel.ongoingCall.observe(viewLifecycleOwner) {
- if (it is CallState.InCall && it.publicKey.string() == contactPubKey) {
+ if (it.inCall() && it.data?.publicKey?.string() == contactPubKey) {
ongoingCall.container.visibility = View.VISIBLE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
ongoingCall.duration.visibility = View.VISIBLE
- ongoingCall.duration.base = it.startTime
+ ongoingCall.duration.base = it.data?.startTime!!
ongoingCall.duration.isCountDown = false
ongoingCall.duration.start()
} else {
diff --git a/atox/src/main/kotlin/ui/chat/ChatViewModel.kt b/atox/src/main/kotlin/ui/chat/ChatViewModel.kt
index 15a676845..6689887b8 100644
--- a/atox/src/main/kotlin/ui/chat/ChatViewModel.kt
+++ b/atox/src/main/kotlin/ui/chat/ChatViewModel.kt
@@ -37,11 +37,11 @@ import ltd.evilcorp.core.vo.Message
import ltd.evilcorp.core.vo.MessageType
import ltd.evilcorp.core.vo.PublicKey
import ltd.evilcorp.domain.feature.CallManager
-import ltd.evilcorp.domain.feature.CallState
import ltd.evilcorp.domain.feature.ChatManager
import ltd.evilcorp.domain.feature.ContactManager
import ltd.evilcorp.domain.feature.ExportManager
import ltd.evilcorp.domain.feature.FileTransferManager
+import ltd.evilcorp.domain.feature.inCall
private const val TAG = "ChatViewModel"
@@ -73,13 +73,24 @@ class ChatViewModel @Inject constructor(
val fileTransfers: LiveData> by lazy { fileTransferManager.transfersFor(publicKey).asLiveData() }
fun callingNeedsConfirmation(): Boolean = settings.confirmCalling
- val ongoingCall = callManager.inCall.asLiveData()
+ val ongoingCall = callManager.call.asLiveData()
val callState get() = contactManager.get(publicKey)
.filterNotNull()
.transform { emit(it.connectionStatus != ConnectionStatus.None) }
- .combine(callManager.inCall) { contactOnline, callState ->
+ .combine(callManager.call) { contactOnline, callValue ->
if (!contactOnline) return@combine CallAvailability.Unavailable
+ if (callValue.inCall()) {
+ if (callValue.data?.publicKey == publicKey) {
+ CallAvailability.Active
+ } else {
+ CallAvailability.Unavailable
+ }
+ } else {
+ CallAvailability.Available
+ }
+ /*
+ val callState = callValue.state
when (callState) {
CallState.NotInCall -> CallAvailability.Available
is CallState.InCall -> {
@@ -89,7 +100,7 @@ class ChatViewModel @Inject constructor(
CallAvailability.Unavailable
}
}
- }
+ }*/
}.asLiveData()
var contactOnline = false
diff --git a/atox/src/main/res/layout/fragment_call.xml b/atox/src/main/res/layout/fragment_call.xml
index 2fc54c936..7978cc9b4 100644
--- a/atox/src/main/res/layout/fragment_call.xml
+++ b/atox/src/main/res/layout/fragment_call.xml
@@ -12,6 +12,25 @@
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:layout_above="@id/control_container">
+
+
Export history
Message history exported
Message history export failed: %1$s
+ ringing...
+ This app plays audio
\ No newline at end of file
diff --git a/domain/src/main/kotlin/feature/CallManager.kt b/domain/src/main/kotlin/feature/CallManager.kt
index 7a8240991..b97f99015 100644
--- a/domain/src/main/kotlin/feature/CallManager.kt
+++ b/domain/src/main/kotlin/feature/CallManager.kt
@@ -16,17 +16,27 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ltd.evilcorp.core.vo.Contact
import ltd.evilcorp.core.vo.PublicKey
import ltd.evilcorp.domain.av.AudioCapture
import ltd.evilcorp.domain.tox.Tox
-sealed class CallState {
- object NotInCall : CallState()
- data class InCall(val publicKey: PublicKey, val startTime: Long) : CallState()
+data class Call(public val state: State = State.IDLE, public val data: Data? = null) {
+ enum class State { IDLE, CALLING_OUT, PENDING, ANSWERED }
+ enum class InOrOut { NA, INCOMING, OUTGOING }
+ data class Data(val publicKey: PublicKey, val inOrOut: InOrOut, val startTime: Long)
}
+fun Call.setData(aPk: PublicKey, aInOrOut: Call.InOrOut, aSt: Long): Call =
+ this.copy(data = Call.Data(aPk, aInOrOut, aSt))
+fun Call.setState(newState: Call.State): Call {
+ if (newState == Call.State.IDLE) return Call(newState, null)
+ return Call(newState, data)
+}
+fun Call.inCall(): Boolean = state == Call.State.CALLING_OUT || state == Call.State.ANSWERED
+
private const val TAG = "CallManager"
private const val AUDIO_CHANNELS = 1
@@ -35,8 +45,8 @@ private const val AUDIO_SEND_INTERVAL_MS = 20
@Singleton
class CallManager @Inject constructor(private val tox: Tox, private val scope: CoroutineScope, context: Context) {
- private val _inCall = MutableStateFlow(CallState.NotInCall)
- val inCall: StateFlow get() = _inCall
+ private val _call = MutableStateFlow(Call(Call.State.IDLE, null))
+ val call: StateFlow get() = _call
private val _pendingCalls = MutableStateFlow>(mutableSetOf())
val pendingCalls: StateFlow> get() = _pendingCalls
@@ -46,6 +56,23 @@ class CallManager @Inject constructor(private val tox: Tox, private val scope: C
private val audioManager = ContextCompat.getSystemService(context, AudioManager::class.java)
+ private fun toState(newState: Call.State) {
+ _call.update { current ->
+ current.setState(newState)
+ }
+ }
+
+ private fun addCallData(newState: Call.State, aPk: PublicKey, aInOrOut: Call.InOrOut, aSt: Long) {
+ _call.update { current ->
+ current.setState(newState).setData(aPk, aInOrOut, aSt)
+ }
+ }
+ private fun addCallData(newState: Call.State, aPk: PublicKey, aInOrOut: Call.InOrOut) {
+ _call.update { current ->
+ current.setState(newState).setData(aPk, aInOrOut, SystemClock.elapsedRealtime())
+ }
+ }
+
fun addPendingCall(from: Contact) {
val calls = mutableSetOf().apply { addAll(_pendingCalls.value) }
calls.addAll(_pendingCalls.value)
@@ -53,6 +80,13 @@ class CallManager @Inject constructor(private val tox: Tox, private val scope: C
Log.i(TAG, "Added pending call ${from.publicKey.take(8)}")
_pendingCalls.value = calls
}
+ if (!_pendingCalls.value.isEmpty()) {
+ if (_call.value.state == Call.State.IDLE) {
+ toState(Call.State.PENDING)
+ } else {
+ Log.e(TAG, "Got pending call while state=${_call.value.state}")
+ }
+ }
}
fun removePendingCall(pk: PublicKey) {
@@ -63,24 +97,32 @@ class CallManager @Inject constructor(private val tox: Tox, private val scope: C
calls.remove(removed)
_pendingCalls.value = calls
}
+ if (_pendingCalls.value.isEmpty() && _call.value.state == Call.State.PENDING) {
+ toState(Call.State.IDLE)
+ }
}
fun startCall(publicKey: PublicKey) {
- if (pendingCalls.value.any { it.publicKey == publicKey.string() }) {
+ val toAnswer = pendingCalls.value.any { it.publicKey == publicKey.string() }
+ if (toAnswer) {
tox.answerCall(publicKey)
} else {
tox.startCall(publicKey)
}
- _inCall.value = CallState.InCall(publicKey, SystemClock.elapsedRealtime())
audioManager?.mode = AudioManager.MODE_IN_COMMUNICATION
removePendingCall(publicKey)
+ if (toAnswer) {
+ addCallData(Call.State.ANSWERED, publicKey, Call.InOrOut.INCOMING)
+ } else {
+ addCallData(Call.State.CALLING_OUT, publicKey, Call.InOrOut.OUTGOING)
+ }
}
fun endCall(publicKey: PublicKey) {
- val state = inCall.value
- if (state is CallState.InCall && state.publicKey == publicKey) {
+ if (call.value.inCall() && call.value.data?.publicKey == publicKey) {
audioManager?.mode = AudioManager.MODE_NORMAL
- _inCall.value = CallState.NotInCall
+ // move to below ?
+ toState(Call.State.IDLE)
}
removePendingCall(publicKey)
@@ -95,9 +137,16 @@ class CallManager @Inject constructor(private val tox: Tox, private val scope: C
}
fun startSendingAudio(): Boolean {
- val to = (inCall.value as CallState.InCall?)?.publicKey ?: return false
- val recorder =
- AudioCapture.create(AUDIO_SAMPLING_RATE_HZ, AUDIO_CHANNELS, AUDIO_SEND_INTERVAL_MS) ?: return false
+ if (!call.value.inCall()) return false
+ val to = call.value.data?.publicKey ?: return false
+ if (_sendingAudio.value) {
+ return true
+ }
+ val recorder = AudioCapture.create(
+ AUDIO_SAMPLING_RATE_HZ,
+ AUDIO_CHANNELS,
+ AUDIO_SEND_INTERVAL_MS,
+ ) ?: return false
startAudioSender(recorder, to)
return true
}
@@ -116,11 +165,16 @@ class CallManager @Inject constructor(private val tox: Tox, private val scope: C
scope.launch {
recorder.start()
_sendingAudio.value = true
- while (inCall.value is CallState.InCall && sendingAudio.value) {
+ while (call.value.inCall() && sendingAudio.value) {
val start = System.currentTimeMillis()
val audioFrame = recorder.read()
try {
- tox.sendAudio(to, audioFrame, AUDIO_CHANNELS, AUDIO_SAMPLING_RATE_HZ)
+ tox.sendAudio(
+ to,
+ audioFrame,
+ AUDIO_CHANNELS,
+ AUDIO_SAMPLING_RATE_HZ,
+ )
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
@@ -134,4 +188,11 @@ class CallManager @Inject constructor(private val tox: Tox, private val scope: C
_sendingAudio.value = false
}
}
+
+ fun setAnswered(pk: PublicKey) {
+ if (_call.value.state != Call.State.CALLING_OUT) {
+ Log.e(TAG, "Cot answer while in state ${_call.value.state}")
+ }
+ addCallData(Call.State.ANSWERED, pk, Call.InOrOut.OUTGOING)
+ }
}