android, desktop: sound prompts and vibration

This commit is contained in:
Avently
2024-04-07 01:20:16 +07:00
parent 2968cd0460
commit 4d5deff487
7 changed files with 123 additions and 8 deletions
@@ -2,17 +2,15 @@ package chat.simplex.common.helpers
import android.media.*
import android.net.Uri
import android.os.VibrationEffect
import android.os.Vibrator
import android.os.*
import androidx.core.content.ContextCompat
import chat.simplex.common.R
import chat.simplex.common.platform.SoundPlayerInterface
import chat.simplex.common.platform.androidAppContext
import chat.simplex.common.platform.*
import kotlinx.coroutines.*
object SoundPlayer: SoundPlayerInterface {
private var player: MediaPlayer? = null
var playing = false
private var playing = false
override fun start(scope: CoroutineScope, sound: Boolean) {
player?.reset()
@@ -32,7 +30,7 @@ object SoundPlayer: SoundPlayerInterface {
scope.launch {
while (playing) {
if (sound) player?.start()
vibrator?.vibrate(effect)
vibrator?.vibrateApiVersionAware(effect)
delay(3500)
}
}
@@ -43,3 +41,61 @@ object SoundPlayer: SoundPlayerInterface {
player?.stop()
}
}
object CallSoundsPlayer: CallSoundsPlayerInterface {
private var player: MediaPlayer? = null
private var playing = false
override fun startWaitingAnswerSound(scope: CoroutineScope) {
player?.reset()
player = MediaPlayer().apply {
setAudioAttributes(
AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
.build()
)
setDataSource(androidAppContext, Uri.parse("android.resource://" + androidAppContext.packageName + "/" + R.raw.call_sound_before_answer))
prepare()
}
playing = true
scope.launch {
while (playing) {
player?.start()
delay(2000)
}
}
}
override fun vibrate() {
val vibrator = ContextCompat.getSystemService(androidAppContext, Vibrator::class.java)
val effect = VibrationEffect.createOneShot(50, VibrationEffect.DEFAULT_AMPLITUDE)
vibrator?.vibrateApiVersionAware(effect)
}
override fun stop() {
playing = false
player?.stop()
}
}
private fun Vibrator.vibrateApiVersionAware(effect: VibrationEffect) {
if (Build.VERSION.SDK_INT >= 33) {
vibrate(
effect,
VibrationAttributes.Builder()
.setUsage(VibrationAttributes.USAGE_ALARM)
.build()
)
} else if (Build.VERSION.SDK_INT >= 29) {
vibrate(
effect,
AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ALARM)
.build()
)
} else {
vibrate(effect)
}
}
@@ -296,6 +296,7 @@ actual object AudioPlayer: AudioPlayerInterface {
}
actual typealias SoundPlayer = chat.simplex.common.helpers.SoundPlayer
actual typealias CallSoundsPlayer = chat.simplex.common.helpers.CallSoundsPlayer
class CryptoMediaSource(val data: ByteArray) : MediaDataSource() {
override fun readAt(position: Long, buffer: ByteArray, offset: Int, size: Int): Int {
@@ -69,6 +69,8 @@ fun activeCallDestroyWebView() = withApi {
@SuppressLint("SourceLockedOrientationActivity")
@Composable
actual fun ActiveCallView() {
val call = remember { chatModel.activeCall }.value
val scope = rememberCoroutineScope()
val audioViaBluetooth = rememberSaveable { mutableStateOf(false) }
val proximityLock = remember {
val pm = (androidAppContext.getSystemService(Context.POWER_SERVICE) as PowerManager)
@@ -78,6 +80,16 @@ actual fun ActiveCallView() {
null
}
}
val wasConnected = rememberSaveable { mutableStateOf(false) }
LaunchedEffect(call) {
if (call?.callState == CallState.Connected && !wasConnected.value) {
CallSoundsPlayer.vibrate()
wasConnected.value = true
}
if ((call?.callState ?: CallState.WaitCapabilities) >= CallState.OfferReceived) {
CallSoundsPlayer.stop()
}
}
DisposableEffect(Unit) {
val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
var btDeviceCount = 0
@@ -106,7 +118,15 @@ actual fun ActiveCallView() {
}
}
am.registerAudioDeviceCallback(audioCallback, null)
if (call?.callState == CallState.WaitCapabilities || call?.callState == CallState.InvitationSent) {
setCallSound(call.soundSpeaker, audioViaBluetooth)
CallSoundsPlayer.startWaitingAnswerSound(scope)
}
onDispose {
CallSoundsPlayer.stop()
if (wasConnected.value) {
CallSoundsPlayer.vibrate()
}
dropAudioManagerOverrides()
am.unregisterAudioDeviceCallback(audioCallback)
if (proximityLock?.isHeld == true) {
@@ -122,8 +142,6 @@ actual fun ActiveCallView() {
if (proximityLock?.isHeld == false) proximityLock.acquire()
}
}
val scope = rememberCoroutineScope()
val call = chatModel.activeCall.value
Box(Modifier.fillMaxSize()) {
WebRTCView(chatModel.callCommand) { apiMsg ->
Log.d(TAG, "received from WebRTCView: $apiMsg")
@@ -39,4 +39,12 @@ interface SoundPlayerInterface {
fun stop()
}
interface CallSoundsPlayerInterface {
fun startWaitingAnswerSound(scope: CoroutineScope)
fun stop()
fun vibrate()
}
expect object SoundPlayer: SoundPlayerInterface
expect object CallSoundsPlayer: CallSoundsPlayerInterface
@@ -228,3 +228,35 @@ actual object SoundPlayer: SoundPlayerInterface {
AudioPlayer.stop()
}
}
actual object CallSoundsPlayer: CallSoundsPlayerInterface {
private var player: MediaPlayer? = null
private var playing = false
override fun startWaitingAnswerSound(scope: CoroutineScope) {
val tmpFile = File(tmpDir, UUID.randomUUID().toString())
tmpFile.deleteOnExit()
SoundPlayer::class.java.getResource("/media/call_sound_before_answer.mp3").openStream()!!.use { it.copyTo(tmpFile.outputStream()) }
SoundPlayer.playing = true
scope.launch {
while (SoundPlayer.playing) {
AudioPlayer.play(CryptoFile.plain(tmpFile.absolutePath), mutableStateOf(true), mutableStateOf(0), mutableStateOf(0), true)
delay(3500)
}
}
playing = true
scope.launch {
while (playing) {
player?.start()
delay(3500)
}
}
}
override fun vibrate() {}
override fun stop() {
playing = false
AudioPlayer.stop()
}
}