Added iLBC audio codec
This commit is contained in:
@ -34,6 +34,10 @@ add_library(lib_g729 STATIC IMPORTED)
|
||||
set_target_properties(lib_g729 PROPERTIES IMPORTED_LOCATION
|
||||
${distribution_DIR}/g729/lib/${ANDROID_ABI}/libbcg729.a)
|
||||
|
||||
add_library(lib_ilbc STATIC IMPORTED)
|
||||
set_target_properties(lib_ilbc PROPERTIES IMPORTED_LOCATION
|
||||
${distribution_DIR}/ilbc/lib/${ANDROID_ABI}/libilbc.a)
|
||||
|
||||
add_library(lib_codec2 STATIC IMPORTED)
|
||||
set_target_properties(lib_codec2 PROPERTIES IMPORTED_LOCATION
|
||||
${distribution_DIR}/codec2/lib/${ANDROID_ABI}/libcodec2.a)
|
||||
@ -83,6 +87,7 @@ target_link_libraries(
|
||||
lib_g722
|
||||
lib_g722_1
|
||||
lib_g729
|
||||
lib_ilbc
|
||||
lib_codec2
|
||||
lib_amrnb
|
||||
lib_amrwb
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.tutpro.baresip
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.media.RingtoneManager
|
||||
import android.net.Uri
|
||||
@ -44,11 +43,11 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@ -58,6 +57,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
@ -71,10 +71,12 @@ enum class Result {
|
||||
fun NavGraphBuilder.audioScreenRoute(navController: NavController) {
|
||||
composable("audio") {
|
||||
val ctx = LocalContext.current
|
||||
val audioViewModel = viewModel<AudioViewModel>()
|
||||
AudioScreen(
|
||||
viewModel = audioViewModel,
|
||||
onBack = { navController.navigateUp() },
|
||||
checkOnClick = {
|
||||
when (checkOnClick(ctx)) {
|
||||
when (audioViewModel.saveSettings(ctx)) {
|
||||
Result.OK -> navController.navigateUp()
|
||||
Result.RESTART -> {
|
||||
navController.previousBackStackEntry
|
||||
@ -91,7 +93,16 @@ fun NavGraphBuilder.audioScreenRoute(navController: NavController) {
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun AudioScreen(onBack: () -> Unit, checkOnClick: () -> Unit) {
|
||||
private fun AudioScreen(
|
||||
viewModel: AudioViewModel,
|
||||
onBack: () -> Unit,
|
||||
checkOnClick: () -> Unit
|
||||
) {
|
||||
val ctx = LocalContext.current
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.loadSettings(ctx)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize().imePadding(),
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
@ -126,42 +137,16 @@ private fun AudioScreen(onBack: () -> Unit, checkOnClick: () -> Unit) {
|
||||
}
|
||||
}
|
||||
) {
|
||||
contentPadding -> AudioContent(contentPadding)
|
||||
contentPadding -> AudioContent(viewModel, contentPadding)
|
||||
}
|
||||
}
|
||||
|
||||
private var newCallVolume = BaresipService.callVolume
|
||||
private var oldMicGain = ""
|
||||
private var newMicGain = ""
|
||||
private var oldSpeakerPhone = BaresipService.speakerPhone
|
||||
private var newSpeakerPhone = oldSpeakerPhone
|
||||
private var oldAudioModules = ArrayList<String>()
|
||||
private var newAudioModules = mutableMapOf<String, Boolean>()
|
||||
private var oldOpusBitrate = ""
|
||||
private var newOpusBitrate = oldOpusBitrate
|
||||
private var oldOpusPacketLoss = ""
|
||||
private var newOpusPacketLoss = oldOpusPacketLoss
|
||||
private var newAudioDelay = BaresipService.audioDelay.toString()
|
||||
private var newToneCountry = BaresipService.toneCountry
|
||||
private var newRingtoneUri = ""
|
||||
|
||||
private var save = false
|
||||
|
||||
private val alertTitle = mutableStateOf("")
|
||||
private val alertMessage = mutableStateOf("")
|
||||
private val showAlert = mutableStateOf(false)
|
||||
|
||||
@Composable
|
||||
private fun AudioContent(contentPadding: PaddingValues) {
|
||||
|
||||
oldSpeakerPhone = Config.variable("speaker_phone") == "yes"
|
||||
newSpeakerPhone = oldSpeakerPhone
|
||||
oldAudioModules = Config.variables("module")
|
||||
for (module in Config.audioModules)
|
||||
newAudioModules[module] = oldAudioModules.contains("${module}.so")
|
||||
oldOpusBitrate = Config.variable("opus_bitrate")
|
||||
oldOpusPacketLoss = Config.variable("opus_packet_loss")
|
||||
if (!BaresipService.agcAvailable) oldMicGain = Config.variable("augain")
|
||||
private fun AudioContent(viewModel: AudioViewModel, contentPadding: PaddingValues) {
|
||||
|
||||
if (showAlert.value)
|
||||
AlertDialog(
|
||||
@ -177,37 +162,30 @@ private fun AudioContent(contentPadding: PaddingValues) {
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(contentPadding)
|
||||
.padding(top = 8.dp, bottom = 8.dp, start = 16.dp, end = 4.dp)
|
||||
.padding(top = 16.dp, bottom = 8.dp, start = 16.dp, end = 4.dp)
|
||||
.verticalScrollbar(scrollState)
|
||||
.verticalScroll(state = scrollState),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Ringtone()
|
||||
ToneCountry()
|
||||
SpeakerPhone()
|
||||
CallVolume()
|
||||
MicGain()
|
||||
AudioModules()
|
||||
OpusBitRate()
|
||||
OpusPacketLoss()
|
||||
AudioDelay()
|
||||
Ringtone(viewModel)
|
||||
ToneCountry(viewModel)
|
||||
SpeakerPhone(viewModel)
|
||||
CallVolume(viewModel)
|
||||
MicGain(viewModel)
|
||||
AudioModules(viewModel)
|
||||
OpusBitRate(viewModel)
|
||||
OpusPacketLoss(viewModel)
|
||||
IlbcMode(viewModel)
|
||||
AudioDelay(viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Ringtone() {
|
||||
private fun Ringtone(viewModel: AudioViewModel) {
|
||||
val ringToneTitle = stringResource(R.string.ringtone)
|
||||
val selectRingToneMessage = stringResource(R.string.select_ringtone)
|
||||
val ctx = LocalContext.current
|
||||
var ringtoneUri by remember {
|
||||
mutableStateOf(
|
||||
if (Preferences(ctx).ringtoneUri == "")
|
||||
RingtoneManager.getActualDefaultRingtoneUri(ctx, RingtoneManager.TYPE_RINGTONE).toString()
|
||||
else
|
||||
Preferences(ctx).ringtoneUri!!
|
||||
)
|
||||
}
|
||||
newRingtoneUri = ringtoneUri
|
||||
val ringtoneUri by viewModel.ringtoneUri.collectAsState()
|
||||
|
||||
val launcher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result: ActivityResult ->
|
||||
@ -218,8 +196,7 @@ private fun Ringtone() {
|
||||
@Suppress("DEPRECATION")
|
||||
result.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
|
||||
if (uri != null) {
|
||||
ringtoneUri = uri.toString()
|
||||
newRingtoneUri = ringtoneUri
|
||||
viewModel.ringtoneUri.value = uri.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -256,7 +233,7 @@ private fun Ringtone() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ToneCountry() {
|
||||
private fun ToneCountry(viewModel: AudioViewModel) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@ -275,20 +252,19 @@ private fun ToneCountry() {
|
||||
},
|
||||
fontSize = 18.sp
|
||||
)
|
||||
val currentToneCountry by viewModel.toneCountry.collectAsState()
|
||||
val isDropDownExpanded = remember { mutableStateOf(false) }
|
||||
val countryNames = arrayListOf("BG", "BR", "DE", "CZ", "ES", "FI", "FR", "GB", "JP", "NO", "NZ", "SE", "RU", "US")
|
||||
val countryValues = arrayListOf("bg", "br", "de", "cz", "es", "fi", "fr", "uk", "jp", "no", "nz", "se", "ru", "us")
|
||||
val itemPosition = remember {
|
||||
val index = countryValues.indexOf(BaresipService.toneCountry)
|
||||
mutableIntStateOf(if (index != -1) index else countryValues.indexOf("us"))
|
||||
}
|
||||
val itemPosition = countryValues.indexOf(currentToneCountry).let { if (it != -1) it else countryValues.indexOf("us") }
|
||||
|
||||
Box {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.clickable { isDropDownExpanded.value = true }
|
||||
) {
|
||||
Text(text = countryNames[itemPosition.intValue])
|
||||
Text(text = countryNames[itemPosition])
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowDropDown,
|
||||
contentDescription = null,
|
||||
@ -304,8 +280,7 @@ private fun ToneCountry() {
|
||||
text = { Text(text = name) },
|
||||
onClick = {
|
||||
isDropDownExpanded.value = false
|
||||
itemPosition.intValue = index
|
||||
newToneCountry = countryValues[index]
|
||||
viewModel.toneCountry.value = countryValues[index]
|
||||
}
|
||||
)
|
||||
if (index < 10)
|
||||
@ -317,7 +292,7 @@ private fun ToneCountry() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SpeakerPhone() {
|
||||
private fun SpeakerPhone(viewModel: AudioViewModel) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(end=10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@ -335,19 +310,18 @@ private fun SpeakerPhone() {
|
||||
},
|
||||
fontSize = 18.sp
|
||||
)
|
||||
var speakerPhone by remember { mutableStateOf(oldSpeakerPhone) }
|
||||
val speakerPhone by viewModel.speakerPhone.collectAsState()
|
||||
Switch(
|
||||
checked = speakerPhone,
|
||||
onCheckedChange = {
|
||||
speakerPhone = it
|
||||
newSpeakerPhone = speakerPhone
|
||||
viewModel.speakerPhone.value = it
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CallVolume() {
|
||||
private fun CallVolume(viewModel: AudioViewModel) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(end=10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@ -365,20 +339,19 @@ private fun CallVolume() {
|
||||
},
|
||||
fontSize = 18.sp
|
||||
)
|
||||
val currentCallVolume by viewModel.callVolume.collectAsState()
|
||||
val isDropDownExpanded = remember { mutableStateOf(false) }
|
||||
val volNames = listOf("--", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10")
|
||||
val volValues = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
val itemPosition = remember {
|
||||
val index = volValues.indexOf(BaresipService.callVolume)
|
||||
mutableIntStateOf(if (index != -1) index else 0)
|
||||
}
|
||||
val itemPosition = volValues.indexOf(currentCallVolume).let { if (it != -1) it else 0 }
|
||||
|
||||
Box {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.clickable { isDropDownExpanded.value = true }
|
||||
) {
|
||||
Text(text = volNames[itemPosition.intValue])
|
||||
Text(text = volNames[itemPosition])
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowDropDown,
|
||||
contentDescription = null,
|
||||
@ -394,8 +367,7 @@ private fun CallVolume() {
|
||||
text = { Text(text = vol) },
|
||||
onClick = {
|
||||
isDropDownExpanded.value = false
|
||||
itemPosition.intValue = index
|
||||
newCallVolume = volValues[index]
|
||||
viewModel.callVolume.value = volValues[index]
|
||||
})
|
||||
if (index < 10)
|
||||
HorizontalDivider(thickness = 1.dp)
|
||||
@ -406,7 +378,7 @@ private fun CallVolume() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MicGain() {
|
||||
private fun MicGain(viewModel: AudioViewModel) {
|
||||
if (!BaresipService.agcAvailable)
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||
@ -415,15 +387,11 @@ private fun MicGain() {
|
||||
) {
|
||||
val microphoneGainTitle = stringResource(R.string.microphone_gain)
|
||||
val microphoneGainHelp = stringResource(R.string.microphone_gain_help)
|
||||
var micGain by remember { mutableStateOf(oldMicGain) }
|
||||
newMicGain = micGain
|
||||
val micGain by viewModel.micGain.collectAsState()
|
||||
OutlinedTextField(
|
||||
value = micGain,
|
||||
placeholder = { Text(microphoneGainTitle) },
|
||||
onValueChange = {
|
||||
micGain = it
|
||||
newMicGain = micGain
|
||||
},
|
||||
onValueChange = { viewModel.micGain.value = it },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
@ -439,7 +407,8 @@ private fun MicGain() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AudioModules() {
|
||||
private fun AudioModules(viewModel: AudioViewModel) {
|
||||
val audioModules by viewModel.audioModules.collectAsState()
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
@ -454,27 +423,29 @@ private fun AudioModules() {
|
||||
alertMessage.value = audioModulesHelp
|
||||
showAlert.value = true
|
||||
})
|
||||
for (module in Config.audioModules)
|
||||
for (module in Config.audioModules) {
|
||||
Row(horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(start = 18.dp, end = 10.dp)
|
||||
) {
|
||||
Text(text = String.format(stringResource(R.string.bullet_item), module), fontSize = 18.sp)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
var checked by remember { mutableStateOf(oldAudioModules.contains("${module}.so")) }
|
||||
val checked = audioModules[module] ?: false
|
||||
Switch(
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
checked = it
|
||||
newAudioModules[module] = checked
|
||||
val newMap = audioModules.toMutableMap()
|
||||
newMap[module] = it
|
||||
viewModel.audioModules.value = newMap
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OpusBitRate() {
|
||||
private fun OpusBitRate(viewModel: AudioViewModel) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@ -482,15 +453,11 @@ private fun OpusBitRate() {
|
||||
) {
|
||||
val opusBitRateTitle = stringResource(R.string.opus_bit_rate)
|
||||
val opusBitRateHelp = stringResource(R.string.opus_bit_rate_help)
|
||||
var opusBitrate by remember { mutableStateOf(oldOpusBitrate) }
|
||||
newOpusBitrate = opusBitrate
|
||||
val opusBitrate by viewModel.opusBitrate.collectAsState()
|
||||
OutlinedTextField(
|
||||
value = opusBitrate,
|
||||
placeholder = { Text(opusBitRateTitle) },
|
||||
onValueChange = {
|
||||
opusBitrate = it
|
||||
newOpusBitrate = opusBitrate
|
||||
},
|
||||
onValueChange = { viewModel.opusBitrate.value = it },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
@ -506,7 +473,7 @@ private fun OpusBitRate() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OpusPacketLoss() {
|
||||
private fun OpusPacketLoss(viewModel: AudioViewModel) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(end = 10.dp, top = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@ -514,15 +481,11 @@ private fun OpusPacketLoss() {
|
||||
) {
|
||||
val opusPacketLossTitle = stringResource(R.string.opus_packet_loss)
|
||||
val opusPacketLossHelp = stringResource(R.string.opus_packet_loss_help)
|
||||
var opusPacketLoss by remember { mutableStateOf(oldOpusPacketLoss) }
|
||||
newOpusPacketLoss = opusPacketLoss
|
||||
val opusPacketLoss by viewModel.opusPacketLoss.collectAsState()
|
||||
OutlinedTextField(
|
||||
value = opusPacketLoss,
|
||||
placeholder = { Text(opusPacketLossTitle) },
|
||||
onValueChange = {
|
||||
opusPacketLoss = it
|
||||
newOpusPacketLoss = opusPacketLoss
|
||||
},
|
||||
onValueChange = { viewModel.opusPacketLoss.value = it },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
@ -538,7 +501,66 @@ private fun OpusPacketLoss() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AudioDelay() {
|
||||
private fun IlbcMode(viewModel: AudioViewModel) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start
|
||||
) {
|
||||
val ilbcModeTitle = stringResource(R.string.ilbc_mode)
|
||||
val ilbcModeHelp = stringResource(R.string.ilbc_mode_help)
|
||||
Text(
|
||||
text = ilbcModeTitle,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.clickable {
|
||||
alertTitle.value = ilbcModeTitle
|
||||
alertMessage.value = ilbcModeHelp
|
||||
showAlert.value = true
|
||||
},
|
||||
fontSize = 18.sp
|
||||
)
|
||||
val currentIlbcMode by viewModel.ilbcMode.collectAsState()
|
||||
val isDropDownExpanded = remember { mutableStateOf(false) }
|
||||
val modeNames = listOf("20ms", "30ms")
|
||||
val modeValues = listOf("20", "30")
|
||||
val itemPosition = modeValues.indexOf(currentIlbcMode).let { if (it != -1) it else 1 }
|
||||
|
||||
Box {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.clickable { isDropDownExpanded.value = true }
|
||||
) {
|
||||
Text(text = modeNames[itemPosition])
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowDropDown,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(36.dp)
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = isDropDownExpanded.value,
|
||||
onDismissRequest = { isDropDownExpanded.value = false }
|
||||
) {
|
||||
modeNames.forEachIndexed { index, name ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = name) },
|
||||
onClick = {
|
||||
isDropDownExpanded.value = false
|
||||
viewModel.ilbcMode.value = modeValues[index]
|
||||
}
|
||||
)
|
||||
if (index < modeNames.size - 1)
|
||||
HorizontalDivider(thickness = 1.dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AudioDelay(viewModel: AudioViewModel) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(end = 10.dp, top = 8.dp, bottom = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@ -546,15 +568,11 @@ private fun AudioDelay() {
|
||||
) {
|
||||
val audioDelayTitle = stringResource(R.string.audio_delay)
|
||||
val audioDelayHelp = stringResource(R.string.audio_delay_help)
|
||||
var audioDelay by remember { mutableStateOf(BaresipService.audioDelay.toString()) }
|
||||
newAudioDelay = audioDelay
|
||||
val currentAudioDelay by viewModel.audioDelay.collectAsState()
|
||||
OutlinedTextField(
|
||||
value = audioDelay,
|
||||
value = currentAudioDelay,
|
||||
placeholder = { Text(audioDelayTitle) },
|
||||
onValueChange = {
|
||||
audioDelay = it
|
||||
newAudioDelay = audioDelay
|
||||
},
|
||||
onValueChange = { viewModel.audioDelay.value = it },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
@ -568,155 +586,3 @@ private fun AudioDelay() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkOnClick(ctx: Context): Result {
|
||||
|
||||
var restart = false
|
||||
save = false
|
||||
|
||||
if (Preferences(ctx).ringtoneUri != newRingtoneUri) {
|
||||
Preferences(ctx).ringtoneUri = newRingtoneUri
|
||||
BaresipService.rt = RingtoneManager.getRingtone(ctx, newRingtoneUri.toUri())
|
||||
}
|
||||
|
||||
if (BaresipService.toneCountry != newToneCountry) {
|
||||
BaresipService.toneCountry = newToneCountry
|
||||
Config.replaceVariable("tone_country", newToneCountry)
|
||||
save = true
|
||||
}
|
||||
|
||||
if (BaresipService.callVolume != newCallVolume) {
|
||||
BaresipService.callVolume = newCallVolume
|
||||
Config.replaceVariable("call_volume", newCallVolume.toString())
|
||||
save = true
|
||||
}
|
||||
|
||||
if (!BaresipService.agcAvailable) {
|
||||
var gain = newMicGain.trim()
|
||||
if (!gain.contains(".")) gain = "$gain.0"
|
||||
if (gain != oldMicGain) {
|
||||
if (!checkMicGain(gain)) {
|
||||
alertTitle.value = ctx.getString(R.string.notice)
|
||||
alertMessage.value = "${ctx.getString(R.string.invalid_microphone_gain)}: $gain."
|
||||
showAlert.value = true
|
||||
return Result.ERROR
|
||||
}
|
||||
if (gain == "1.0") {
|
||||
Api.module_unload("augain")
|
||||
Config.removeVariableValue("module", "augain.so")
|
||||
Config.replaceVariable("augain", "1.0")
|
||||
}
|
||||
else {
|
||||
if (oldMicGain == "1.0") {
|
||||
if (Api.module_load("augain") != 0) {
|
||||
alertTitle.value = ctx.getString(R.string.error)
|
||||
alertMessage.value = ctx.getString(R.string.failed_to_load_module) + ": augain.so"
|
||||
showAlert.value = true
|
||||
return Result.ERROR
|
||||
}
|
||||
Config.addVariable("module", "augain.so")
|
||||
}
|
||||
Config.replaceVariable("augain", gain)
|
||||
Api.cmd_exec("augain $gain")
|
||||
}
|
||||
save = true
|
||||
}
|
||||
}
|
||||
|
||||
if (newSpeakerPhone != oldSpeakerPhone) {
|
||||
Config.replaceVariable("speaker_phone", if (newSpeakerPhone) "yes" else "no")
|
||||
BaresipService.speakerPhoneAuto = newSpeakerPhone
|
||||
save = true
|
||||
}
|
||||
|
||||
for (module in Config.audioModules) {
|
||||
if (newAudioModules[module] != null) {
|
||||
if (newAudioModules[module]!!) {
|
||||
if (!oldAudioModules.contains("${module}.so")) {
|
||||
if (Api.module_load("${module}.so") != 0) {
|
||||
alertTitle.value = ctx.getString(R.string.error)
|
||||
alertMessage.value = "${ctx.getString(R.string.failed_to_load_module)}: ${module}.so"
|
||||
showAlert.value = true
|
||||
return Result.ERROR
|
||||
}
|
||||
Config.addVariable("module", "${module}.so")
|
||||
save = true
|
||||
}
|
||||
}
|
||||
else if (oldAudioModules.contains("${module}.so")) {
|
||||
Api.module_unload("${module}.so")
|
||||
Config.removeVariableValue("module", "${module}.so")
|
||||
for (ua in BaresipService.uas.value)
|
||||
ua.account.removeAudioCodecs(module)
|
||||
Account.saveAccounts()
|
||||
save = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newOpusBitrate != oldOpusBitrate) {
|
||||
if (!checkOpusBitRate(newOpusBitrate)) {
|
||||
alertTitle.value = ctx.getString(R.string.notice)
|
||||
alertMessage.value = "${ctx.getString(R.string.invalid_opus_bitrate)}: $newOpusBitrate."
|
||||
showAlert.value = true
|
||||
return Result.ERROR
|
||||
}
|
||||
Config.replaceVariable("opus_bitrate", newOpusBitrate)
|
||||
restart = true
|
||||
save = true
|
||||
}
|
||||
|
||||
if (newOpusPacketLoss != oldOpusPacketLoss) {
|
||||
if (!checkOpusPacketLoss(newOpusPacketLoss)) {
|
||||
alertTitle.value = ctx.getString(R.string.notice)
|
||||
alertMessage.value = "${ctx.getString(R.string.invalid_opus_packet_loss)}: $newOpusPacketLoss"
|
||||
showAlert.value = true
|
||||
return Result.ERROR
|
||||
}
|
||||
Config.replaceVariable("opus_packet_loss", newOpusPacketLoss)
|
||||
restart = true
|
||||
save = true
|
||||
}
|
||||
|
||||
val audioDelay = newAudioDelay.trim()
|
||||
if (audioDelay != BaresipService.audioDelay.toString()) {
|
||||
if (!checkAudioDelay(audioDelay)) {
|
||||
alertTitle.value = ctx.getString(R.string.notice)
|
||||
alertMessage.value = String.format(ctx.getString(R.string.invalid_audio_delay), audioDelay)
|
||||
showAlert.value = true
|
||||
return Result.ERROR
|
||||
}
|
||||
Config.replaceVariable("audio_delay", audioDelay)
|
||||
BaresipService.audioDelay = audioDelay.toLong()
|
||||
save = true
|
||||
}
|
||||
|
||||
if (save) Config.save()
|
||||
|
||||
return if (restart) Result.RESTART else Result.OK
|
||||
}
|
||||
|
||||
private fun checkMicGain(micGain: String): Boolean {
|
||||
val number =
|
||||
try {
|
||||
micGain.toDouble()
|
||||
} catch (_: NumberFormatException) {
|
||||
return false
|
||||
}
|
||||
return number >= 1.0
|
||||
}
|
||||
|
||||
private fun checkOpusBitRate(opusBitRate: String): Boolean {
|
||||
val number = opusBitRate.toIntOrNull() ?: return false
|
||||
return (number >= 6000) && (number <= 510000)
|
||||
}
|
||||
|
||||
private fun checkOpusPacketLoss(opusPacketLoss: String): Boolean {
|
||||
val number = opusPacketLoss.toIntOrNull() ?: return false
|
||||
return (number >= 0) && (number <= 100)
|
||||
}
|
||||
|
||||
private fun checkAudioDelay(audioDelay: String): Boolean {
|
||||
val number = audioDelay.toIntOrNull() ?: return false
|
||||
return (number >= 100) && (number <= 3000)
|
||||
}
|
||||
|
||||
194
app/src/main/kotlin/com/tutpro/baresip/AudioViewModel.kt
Normal file
194
app/src/main/kotlin/com/tutpro/baresip/AudioViewModel.kt
Normal file
@ -0,0 +1,194 @@
|
||||
package com.tutpro.baresip
|
||||
|
||||
import android.content.Context
|
||||
import android.media.RingtoneManager
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class AudioViewModel : ViewModel() {
|
||||
|
||||
val speakerPhone = MutableStateFlow(false)
|
||||
val callVolume = MutableStateFlow(0)
|
||||
val micGain = MutableStateFlow("")
|
||||
val audioModules = MutableStateFlow(mutableMapOf<String, Boolean>())
|
||||
val opusBitrate = MutableStateFlow("")
|
||||
val opusPacketLoss = MutableStateFlow("")
|
||||
val ilbcMode = MutableStateFlow("")
|
||||
val audioDelay = MutableStateFlow("")
|
||||
val toneCountry = MutableStateFlow("")
|
||||
val ringtoneUri = MutableStateFlow("")
|
||||
|
||||
private var isLoaded = false
|
||||
var oldSpeakerPhone = false
|
||||
var oldCallVolume = 0
|
||||
var oldMicGain = ""
|
||||
var oldAudioModules = ArrayList<String>()
|
||||
var oldOpusBitrate = ""
|
||||
var oldOpusPacketLoss = ""
|
||||
var oldIlbcMode = ""
|
||||
var oldAudioDelay = ""
|
||||
var oldToneCountry = ""
|
||||
var oldRingtoneUri = ""
|
||||
|
||||
fun loadSettings(ctx: Context) {
|
||||
if (isLoaded || !Config.isInitialized()) return else isLoaded = true
|
||||
|
||||
oldSpeakerPhone = Config.variable("speaker_phone") == "yes"
|
||||
speakerPhone.value = oldSpeakerPhone
|
||||
|
||||
oldCallVolume = BaresipService.callVolume
|
||||
callVolume.value = oldCallVolume
|
||||
|
||||
if (!BaresipService.agcAvailable) {
|
||||
oldMicGain = Config.variable("augain")
|
||||
micGain.value = oldMicGain
|
||||
}
|
||||
|
||||
oldAudioModules = Config.variables("module")
|
||||
val modulesMap = mutableMapOf<String, Boolean>()
|
||||
for (module in Config.audioModules)
|
||||
modulesMap[module] = oldAudioModules.contains("${module}.so")
|
||||
|
||||
audioModules.value = modulesMap
|
||||
|
||||
oldOpusBitrate = Config.variable("opus_bitrate")
|
||||
opusBitrate.value = oldOpusBitrate
|
||||
|
||||
oldOpusPacketLoss = Config.variable("opus_packet_loss")
|
||||
opusPacketLoss.value = oldOpusPacketLoss
|
||||
|
||||
oldIlbcMode = Config.variable("ilbc_mode")
|
||||
ilbcMode.value = oldIlbcMode
|
||||
|
||||
oldAudioDelay = Config.variable("audio_delay")
|
||||
if (oldAudioDelay == "") oldAudioDelay = BaresipService.audioDelay.toString()
|
||||
audioDelay.value = oldAudioDelay
|
||||
|
||||
oldToneCountry = BaresipService.toneCountry
|
||||
toneCountry.value = oldToneCountry
|
||||
|
||||
oldRingtoneUri = Preferences(ctx).ringtoneUri ?: ""
|
||||
ringtoneUri.value = oldRingtoneUri
|
||||
}
|
||||
|
||||
fun saveSettings(ctx: Context): Result {
|
||||
var restart = false
|
||||
var save = false
|
||||
|
||||
if (Preferences(ctx).ringtoneUri != ringtoneUri.value) {
|
||||
Preferences(ctx).ringtoneUri = ringtoneUri.value
|
||||
BaresipService.rt = RingtoneManager.getRingtone(ctx, ringtoneUri.value.toUri())
|
||||
}
|
||||
|
||||
if (BaresipService.toneCountry != toneCountry.value) {
|
||||
BaresipService.toneCountry = toneCountry.value
|
||||
Config.replaceVariable("tone_country", toneCountry.value)
|
||||
save = true
|
||||
}
|
||||
|
||||
if (BaresipService.callVolume != callVolume.value) {
|
||||
BaresipService.callVolume = callVolume.value
|
||||
Config.replaceVariable("call_volume", callVolume.value.toString())
|
||||
save = true
|
||||
}
|
||||
|
||||
if (!BaresipService.agcAvailable) {
|
||||
var gain = micGain.value.trim()
|
||||
if (gain.isNotEmpty() && !gain.contains(".")) gain = "$gain.0"
|
||||
if (gain != oldMicGain) {
|
||||
if (!checkMicGain(gain)) return Result.ERROR
|
||||
if (gain == "1.0") {
|
||||
Api.module_unload("augain")
|
||||
Config.removeVariableValue("module", "augain.so")
|
||||
Config.replaceVariable("augain", "1.0")
|
||||
}
|
||||
else {
|
||||
if (oldMicGain == "1.0") {
|
||||
if (Api.module_load("augain") != 0) return Result.ERROR
|
||||
Config.addVariable("module", "augain.so")
|
||||
}
|
||||
Config.replaceVariable("augain", gain)
|
||||
Api.cmd_exec("augain $gain")
|
||||
}
|
||||
save = true
|
||||
}
|
||||
}
|
||||
|
||||
if (speakerPhone.value != oldSpeakerPhone) {
|
||||
Config.replaceVariable("speaker_phone", if (speakerPhone.value) "yes" else "no")
|
||||
BaresipService.speakerPhoneAuto = speakerPhone.value
|
||||
save = true
|
||||
}
|
||||
|
||||
for (module in Config.audioModules) {
|
||||
val enabled = audioModules.value[module] ?: false
|
||||
if (enabled != oldAudioModules.contains("${module}.so")) {
|
||||
if (enabled) {
|
||||
if (Api.module_load("${module}.so") != 0) return Result.ERROR
|
||||
Config.addVariable("module", "${module}.so")
|
||||
}
|
||||
else {
|
||||
Api.module_unload("${module}.so")
|
||||
Config.removeVariableValue("module", "${module}.so")
|
||||
for (ua in BaresipService.uas.value)
|
||||
ua.account.removeAudioCodecs(module)
|
||||
Account.saveAccounts()
|
||||
}
|
||||
save = true
|
||||
}
|
||||
}
|
||||
|
||||
if (opusBitrate.value != oldOpusBitrate) {
|
||||
if (!checkOpusBitRate(opusBitrate.value)) return Result.ERROR
|
||||
Config.replaceVariable("opus_bitrate", opusBitrate.value)
|
||||
restart = true
|
||||
save = true
|
||||
}
|
||||
|
||||
if (opusPacketLoss.value != oldOpusPacketLoss) {
|
||||
if (!checkOpusPacketLoss(opusPacketLoss.value)) return Result.ERROR
|
||||
Config.replaceVariable("opus_packet_loss", opusPacketLoss.value)
|
||||
restart = true
|
||||
save = true
|
||||
}
|
||||
|
||||
if (ilbcMode.value != oldIlbcMode) {
|
||||
Config.replaceVariable("ilbc_mode", ilbcMode.value)
|
||||
restart = true
|
||||
save = true
|
||||
}
|
||||
|
||||
val delay = audioDelay.value.trim()
|
||||
if (delay != oldAudioDelay) {
|
||||
if (!checkAudioDelay(delay)) return Result.ERROR
|
||||
Config.replaceVariable("audio_delay", delay)
|
||||
BaresipService.audioDelay = delay.toLong()
|
||||
save = true
|
||||
}
|
||||
|
||||
if (save) Config.save()
|
||||
|
||||
return if (restart) Result.RESTART else Result.OK
|
||||
}
|
||||
|
||||
private fun checkMicGain(micGain: String): Boolean {
|
||||
val number = micGain.toDoubleOrNull() ?: return false
|
||||
return number >= 1.0
|
||||
}
|
||||
|
||||
private fun checkOpusBitRate(opusBitRate: String): Boolean {
|
||||
val number = opusBitRate.toIntOrNull() ?: return false
|
||||
return (number >= 6000) && (number <= 510000)
|
||||
}
|
||||
|
||||
private fun checkOpusPacketLoss(opusPacketLoss: String): Boolean {
|
||||
val number = opusPacketLoss.toIntOrNull() ?: return false
|
||||
return (number >= 0) && (number <= 100)
|
||||
}
|
||||
|
||||
private fun checkAudioDelay(audioDelay: String): Boolean {
|
||||
val number = audioDelay.toIntOrNull() ?: return false
|
||||
return (number >= 100) && (number <= 3000)
|
||||
}
|
||||
}
|
||||
@ -10,7 +10,7 @@ import java.nio.charset.StandardCharsets
|
||||
object Config {
|
||||
|
||||
private val configPath = BaresipService.filesPath + "/config"
|
||||
val audioModules = listOf("opus", "amr", "libg722", "g7221", "g729", "codec2", "g711")
|
||||
val audioModules = listOf("opus", "amr", "ilbc", "libg722", "g7221", "g729", "codec2", "g711")
|
||||
private lateinit var config: String
|
||||
private lateinit var previousConfig: String
|
||||
private lateinit var previousLines: List<String>
|
||||
@ -213,8 +213,9 @@ object Config {
|
||||
val previousModules = previousVariables("module")
|
||||
for (module in audioModules)
|
||||
if ("${module}.so" in previousModules ||
|
||||
(module == "libg722" && "g722.so" in previousModules))
|
||||
config = "${config}module ${module}.so\n"
|
||||
(module == "libg722" && "g722.so" in previousModules) ||
|
||||
module == "ilbc")
|
||||
config = "${config}module ${module}.so\n"
|
||||
|
||||
Utils.aecAgcNsCheck()
|
||||
|
||||
@ -236,6 +237,12 @@ object Config {
|
||||
else
|
||||
"${config}opus_packet_loss $opusPacketLoss\n"
|
||||
|
||||
val ilbcMode = previousVariable("ilbc_mode")
|
||||
config = if (ilbcMode == "")
|
||||
"${config}ilbc_mode 30\n"
|
||||
else
|
||||
"${config}ilbc_mode $ilbcMode\n"
|
||||
|
||||
val audioDelay = previousVariable("audio_delay")
|
||||
if (audioDelay != "") {
|
||||
config = "${config}audio_delay $audioDelay\n"
|
||||
|
||||
@ -89,9 +89,7 @@ import java.io.FileInputStream
|
||||
import java.util.Locale
|
||||
import androidx.core.net.toUri
|
||||
|
||||
private var restart = false
|
||||
private val showRestartDialog = mutableStateOf(false)
|
||||
private var save = false
|
||||
|
||||
fun NavGraphBuilder.settingsScreenRoute(
|
||||
navController: NavController,
|
||||
@ -105,14 +103,14 @@ fun NavGraphBuilder.settingsScreenRoute(
|
||||
navController = navController,
|
||||
settingsViewModel = viewModel,
|
||||
onBack = {
|
||||
if (restart)
|
||||
if (viewModel.restart)
|
||||
showRestartDialog.value = true
|
||||
else
|
||||
navController.navigateUp()
|
||||
},
|
||||
checkOnClick = {
|
||||
if (checkOnClick(ctx, viewModel)) {
|
||||
if (restart)
|
||||
if (viewModel.restart)
|
||||
showRestartDialog.value = true
|
||||
else
|
||||
navController.navigateUp()
|
||||
@ -142,19 +140,23 @@ private fun SettingsScreen(
|
||||
?.observeAsState()
|
||||
|
||||
LaunchedEffect(audioResult?.value) {
|
||||
Log.d(TAG, "audio_settings_result observed: ${audioResult?.value}")
|
||||
if (audioResult?.value == true) {
|
||||
Log.d(TAG, "Got result from AudioSettings: true")
|
||||
restart = true
|
||||
settingsViewModel.restart = true
|
||||
showRestartDialog.value = true
|
||||
navController.currentBackStackEntry
|
||||
?.savedStateHandle
|
||||
?.remove<Boolean>("audio_settings_result")
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(null) {
|
||||
save = false
|
||||
restart = false
|
||||
settingsViewModel.loadSettings(ctx)
|
||||
LaunchedEffect(Unit) {
|
||||
if (!settingsViewModel.restart) {
|
||||
settingsViewModel.save = false
|
||||
settingsViewModel.restart = false
|
||||
settingsViewModel.loadSettings(ctx)
|
||||
}
|
||||
areSettingsLoaded = true
|
||||
}
|
||||
|
||||
@ -502,8 +504,8 @@ private fun SettingsContent(
|
||||
inputStream.close()
|
||||
Config.replaceVariable("sip_certificate", certPath)
|
||||
viewModel.tlsCertificateFile.value = true
|
||||
save = true
|
||||
restart = true
|
||||
viewModel.save = true
|
||||
viewModel.restart = true
|
||||
} catch (e: Error) {
|
||||
alertTitle.value = errorTitleText
|
||||
alertMessage.value = readCertError + ": " + e.message
|
||||
@ -561,8 +563,8 @@ private fun SettingsContent(
|
||||
Utils.putFileContents(certPath, content)
|
||||
Config.replaceVariable("sip_certificate", certPath)
|
||||
viewModel.tlsCertificateFile.value = true
|
||||
save = true
|
||||
restart = true
|
||||
viewModel.save = true
|
||||
viewModel.restart = true
|
||||
}
|
||||
shouldShowRequestPermissionRationale(activity, permission) ->
|
||||
showAlertDialog.value = true
|
||||
@ -575,8 +577,8 @@ private fun SettingsContent(
|
||||
else {
|
||||
Config.removeVariable("sip_certificate")
|
||||
Utils.deleteFile(File(BaresipService.filesPath + "/cert.pem"))
|
||||
save = true
|
||||
restart = true
|
||||
viewModel.save = true
|
||||
viewModel.restart = true
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -645,7 +647,7 @@ private fun SettingsContent(
|
||||
val inputStream = ctx.contentResolver.openInputStream(uri) as FileInputStream
|
||||
caCertsFile.copyInputStreamToFile(inputStream)
|
||||
inputStream.close()
|
||||
restart = true
|
||||
viewModel.restart = true
|
||||
} catch (e: Error) {
|
||||
alertTitle.value = errorTitleText
|
||||
alertMessage.value = readCaCertsError + ": " + e.message
|
||||
@ -696,7 +698,7 @@ private fun SettingsContent(
|
||||
}
|
||||
File(BaresipService.filesPath + "/ca_certs.crt").writeBytes(content)
|
||||
viewModel.caFile.value = true
|
||||
restart = true
|
||||
viewModel.restart = true
|
||||
}
|
||||
shouldShowRequestPermissionRationale(activity, permission) -> {
|
||||
dialogTitle.value = noticeTitleText
|
||||
@ -716,7 +718,7 @@ private fun SettingsContent(
|
||||
Utils.selectInputFile(caCertsRequest)
|
||||
else {
|
||||
Utils.deleteFile(File(BaresipService.filesPath + "/ca_certs.crt"))
|
||||
restart = true
|
||||
viewModel.restart = true
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -991,7 +993,7 @@ private fun SettingsContent(
|
||||
if (results[Manifest.permission.READ_PHONE_NUMBERS] == true)
|
||||
Log.d(TAG, "READ_PHONE_NUMBERS permission granted")
|
||||
BaresipService.instance?.addMobileUserAgent()
|
||||
restart = true
|
||||
viewModel.restart = true
|
||||
}
|
||||
|
||||
val dialerRoleRequest = rememberLauncherForActivityResult(
|
||||
@ -1006,14 +1008,14 @@ private fun SettingsContent(
|
||||
)
|
||||
if (Utils.checkPermissions(ctx, permissions)) {
|
||||
BaresipService.instance?.addMobileUserAgent()
|
||||
restart = true
|
||||
viewModel.restart = true
|
||||
}
|
||||
else
|
||||
requestPermissionLauncher.launch(permissions)
|
||||
}
|
||||
else
|
||||
BaresipService.instance?.addMobileUserAgent()
|
||||
restart = true
|
||||
viewModel.restart = true
|
||||
}
|
||||
Switch(
|
||||
checked = defaultDialer,
|
||||
@ -1037,7 +1039,7 @@ private fun SettingsContent(
|
||||
Log.e(TAG, "ActivityNotFound exception: ${e.message}")
|
||||
}
|
||||
BaresipService.instance?.addMobileUserAgent()
|
||||
restart = true
|
||||
viewModel.restart = true
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -1067,7 +1069,7 @@ private fun SettingsContent(
|
||||
Config.replaceVariable("mobile_account", if (it) "yes" else "no")
|
||||
Config.save()
|
||||
BaresipService.instance?.addMobileUserAgent()
|
||||
restart = true
|
||||
viewModel.restart = true
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -1105,7 +1107,7 @@ private fun SettingsContent(
|
||||
) { _ ->
|
||||
val isHeld = roleManager.isRoleHeld(RoleManager.ROLE_SMS)
|
||||
viewModel.defaultMessaging.value = isHeld
|
||||
restart = true
|
||||
viewModel.restart = true
|
||||
}
|
||||
Switch(
|
||||
checked = defaultMessaging,
|
||||
@ -1280,7 +1282,7 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
|
||||
|
||||
if ((Config.variable("auto_start") == "yes") != viewModel.autoStart.value) {
|
||||
Config.replaceVariable("auto_start", if (viewModel.autoStart.value) "yes" else "no")
|
||||
save = true
|
||||
viewModel.save = true
|
||||
}
|
||||
|
||||
val listenAddr = viewModel.listenAddress.value.trim()
|
||||
@ -1292,14 +1294,14 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
|
||||
return false
|
||||
}
|
||||
Config.replaceVariable("sip_listen", listenAddr)
|
||||
save = true
|
||||
restart = true
|
||||
viewModel.save = true
|
||||
viewModel.restart = true
|
||||
}
|
||||
|
||||
if (Config.variable("net_af").lowercase() != viewModel.addressFamily.value) {
|
||||
Config.replaceVariable("net_af", viewModel.addressFamily.value)
|
||||
save = true
|
||||
restart = true
|
||||
viewModel.save = true
|
||||
viewModel.restart = true
|
||||
}
|
||||
|
||||
val transportProtocols = viewModel.transportProtocols.value
|
||||
@ -1315,8 +1317,8 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
|
||||
Config.removeVariable("sip_transports")
|
||||
if (transportProtocols.isNotEmpty())
|
||||
Config.replaceVariable("sip_transports", transportProtocols)
|
||||
save = true
|
||||
restart = true
|
||||
viewModel.save = true
|
||||
viewModel.restart = true
|
||||
}
|
||||
|
||||
val dnsServers = addMissingPorts(viewModel.dnsServers.value
|
||||
@ -1345,13 +1347,13 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
|
||||
Config.updateDnsServers(BaresipService.dnsServers)
|
||||
}
|
||||
// Api.net_dns_debug()
|
||||
save = true
|
||||
viewModel.save = true
|
||||
}
|
||||
|
||||
if ((Config.variable("sip_verify_server") == "yes") != viewModel.verifyServer.value) {
|
||||
Config.replaceVariable("sip_verify_server", if (viewModel.verifyServer.value) "yes" else "no")
|
||||
Api.config_verify_server_set(viewModel.verifyServer.value)
|
||||
save = true
|
||||
viewModel.save = true
|
||||
}
|
||||
|
||||
val userAgent = viewModel.userAgent.value.trim()
|
||||
@ -1367,14 +1369,14 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
|
||||
Config.replaceVariable("user_agent", userAgent)
|
||||
else
|
||||
Config.removeVariable("user_agent")
|
||||
save = true
|
||||
restart = true
|
||||
viewModel.save = true
|
||||
viewModel.restart = true
|
||||
}
|
||||
|
||||
if ((Config.variable("sip_cuser_random") == "yes") != viewModel.uniqueContactUri.value) {
|
||||
Config.replaceVariable("sip_cuser_random", if (viewModel.uniqueContactUri.value) "yes" else "no")
|
||||
save = true
|
||||
restart = true
|
||||
viewModel.save = true
|
||||
viewModel.restart = true
|
||||
}
|
||||
|
||||
val darkTheme = viewModel.darkTheme.value
|
||||
@ -1387,14 +1389,14 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
|
||||
BaresipService.darkTheme.value = darkTheme
|
||||
AppCompatDelegate.setDefaultNightMode(newDisplayTheme)
|
||||
Config.replaceVariable("dark_theme", if (darkTheme) "yes" else "no")
|
||||
save = true
|
||||
viewModel.save = true
|
||||
}
|
||||
|
||||
val dynamicColors = viewModel.dynamicColors.value
|
||||
if (BaresipService.dynamicColors.value != dynamicColors) {
|
||||
BaresipService.dynamicColors.value = dynamicColors
|
||||
Config.replaceVariable("dynamic_colors", if (dynamicColors) "yes" else "no")
|
||||
save = true
|
||||
viewModel.save = true
|
||||
}
|
||||
|
||||
val colorblind = viewModel.colorblind.value
|
||||
@ -1405,14 +1407,14 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
|
||||
val baresipService = Intent(ctx, BaresipService::class.java)
|
||||
baresipService.action = "Update Notification"
|
||||
ContextCompat.startForegroundService(ctx, baresipService)
|
||||
save = true
|
||||
viewModel.save = true
|
||||
}
|
||||
|
||||
val proximitySensing = viewModel.proximitySensing.value
|
||||
if ((Config.variable("proximity_sensing") == "yes") != proximitySensing) {
|
||||
Config.replaceVariable("proximity_sensing", if (proximitySensing) "yes" else "no")
|
||||
BaresipService.proximitySensing = proximitySensing
|
||||
save = true
|
||||
viewModel.save = true
|
||||
}
|
||||
|
||||
val debug = viewModel.debug.value
|
||||
@ -1421,7 +1423,7 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
|
||||
Config.replaceVariable("log_level", logLevelString)
|
||||
Api.log_level_set(logLevelString.toInt())
|
||||
Log.logLevelSet(logLevelString.toInt())
|
||||
save = true
|
||||
viewModel.save = true
|
||||
}
|
||||
|
||||
val sipTrace = if (debug) viewModel.sipTrace.value else false
|
||||
@ -1430,7 +1432,7 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
|
||||
Api.uag_enable_sip_trace(sipTrace)
|
||||
}
|
||||
|
||||
if (save) Config.save()
|
||||
if (viewModel.save) Config.save()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -7,7 +7,9 @@ import android.content.Context.ROLE_SERVICE
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import java.io.File
|
||||
@ -35,6 +37,9 @@ class SettingsViewModel: ViewModel() {
|
||||
val debug = MutableStateFlow(false)
|
||||
val sipTrace = MutableStateFlow(false)
|
||||
|
||||
var restart by mutableStateOf(false)
|
||||
var save by mutableStateOf(false)
|
||||
|
||||
private var isLoaded = false
|
||||
|
||||
fun loadSettings(ctx: Context) {
|
||||
@ -45,10 +50,7 @@ class SettingsViewModel: ViewModel() {
|
||||
|
||||
listenAddress.value = Config.variable("sip_listen")
|
||||
|
||||
val familyValues = listOf("", "ipv4", "ipv6")
|
||||
val itemPosition =
|
||||
mutableIntStateOf(familyValues.indexOf(Config.variable("net_af").lowercase()))
|
||||
addressFamily.value = familyValues[itemPosition.intValue]
|
||||
addressFamily.value = Config.variable("net_af").lowercase()
|
||||
|
||||
transportProtocols.value = Config.variable("sip_transports")
|
||||
|
||||
@ -87,4 +89,4 @@ class SettingsViewModel: ViewModel() {
|
||||
sipTrace.value = BaresipService.sipTrace
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -450,6 +450,9 @@
|
||||
<string name="audio_delay_help">Audion odotusviive (millisekunneissa) soitetun puhelun alkaessa.
|
||||
Aseta korkeampi arvo, jos et kuule vastaajan ääntä heti, kun puhelu alkaa.</string>
|
||||
<string name="invalid_audio_delay">Virheellinen audioviive \'%1$s\'. Sallittu arvo on välillä 100–3000.</string>
|
||||
<string name="ilbc_mode">iLBC-moodi</string>
|
||||
<string name="ilbc_mode_help">Valitsee iLBC-koodekin kehyksen keston.
|
||||
30ms tarjoaa paremman äänenlaadun alhaisilla bittinopeuksilla, kun taas 20ms:ssä on pienempi viive.</string>
|
||||
<string name="default_call_volume">Oletus äänen voimakkuus</string>
|
||||
<string name="default_call_volume_help">Jos valittu, puhelun äänen voimakkuus
|
||||
asteikolla 1–10.</string>
|
||||
|
||||
@ -429,6 +429,9 @@
|
||||
<string name="audio_delay_help">Time (in milliseconds) to wait audio from callee when call is established.
|
||||
Set to a higher value if you miss audio from callee at the beginning of the call.</string>
|
||||
<string name="invalid_audio_delay">Invalid Audio Delay \'%1$s\'. Valid values are from 100 to 3000.</string>
|
||||
<string name="ilbc_mode">iLBC Mode</string>
|
||||
<string name="ilbc_mode_help">Selects the default iLBC frame duration.
|
||||
30ms provides better low-bitrate quality, while 20ms has lower latency.</string>
|
||||
<string name="default_call_volume">Default Call Volume</string>
|
||||
<string name="default_call_volume_help">If set, default call audio volume at scale 1–10.</string>
|
||||
<string name="tone_country">Tone Country</string>
|
||||
|
||||
Reference in New Issue
Block a user