Work on call recording
This commit is contained in:
@ -60,6 +60,10 @@ add_library(lib_zrtpcppcore STATIC IMPORTED)
|
||||
set_target_properties(lib_zrtpcppcore PROPERTIES IMPORTED_LOCATION
|
||||
${distribution_DIR}/gzrtp/lib/${ANDROID_ABI}/libzrtpcppcore.a)
|
||||
|
||||
add_library(lib_sndfile STATIC IMPORTED)
|
||||
set_target_properties(lib_sndfile PROPERTIES IMPORTED_LOCATION
|
||||
${distribution_DIR}/sndfile/lib/${ANDROID_ABI}/libsndfile.a)
|
||||
|
||||
add_library(lib_baresip STATIC IMPORTED)
|
||||
set_target_properties(lib_baresip PROPERTIES IMPORTED_LOCATION
|
||||
${distribution_DIR}/baresip/lib/${ANDROID_ABI}/libbaresip.a)
|
||||
@ -90,5 +94,6 @@ target_link_libraries(baresip
|
||||
lib_amrwbenc
|
||||
lib_webrtc
|
||||
lib_zrtpcppcore
|
||||
lib_sndfile
|
||||
z
|
||||
log)
|
||||
|
||||
@ -147,7 +147,8 @@ static void ua_event_handler(struct ua *ua, enum ua_event ev,
|
||||
const char *tone;
|
||||
char event_buf[256];
|
||||
enum sdp_dir ardir;
|
||||
int len;
|
||||
int len, err;
|
||||
struct pl module, module_event, data;
|
||||
|
||||
LOGD("ua event (%s) %s\n", uag_event_str(ev), prm);
|
||||
|
||||
@ -225,6 +226,15 @@ static void ua_event_handler(struct ua *ua, enum ua_event ev,
|
||||
case UA_EVENT_MWI_NOTIFY:
|
||||
len = re_snprintf(event_buf, sizeof event_buf, "mwi notify,%s", prm);
|
||||
break;
|
||||
case UA_EVENT_MODULE:
|
||||
err = re_regex(prm, strlen(prm), "[^,]*,[^,]*,[~]*",
|
||||
&module, &module_event, &data);
|
||||
if (err)
|
||||
return;
|
||||
if (!pl_strcmp(&module_event, "dump")) {
|
||||
len = re_snprintf(event_buf, sizeof event_buf, "sndfile dump,%r", &data);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
@ -1088,7 +1098,7 @@ Java_com_tutpro_baresip_Api_ua_1answer(JNIEnv *env, jobject thiz, jlong ua, jlon
|
||||
re_thread_leave();
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_tutpro_baresip_Api_ua_1add_1custom_1header(JNIEnv *env, jobject thiz, jlong ua,
|
||||
jstring jname, jstring jbody) {
|
||||
const char *name = (*env)->GetStringUTFChars(env, jname, 0);
|
||||
@ -1104,7 +1114,6 @@ Java_com_tutpro_baresip_Api_ua_1add_1custom_1header(JNIEnv *env, jobject thiz, j
|
||||
LOGW("adding custom header to ua %ld failed with error %d\n", (long)ua, err);
|
||||
(*env)->ReleaseStringUTFChars(env, jname, name);
|
||||
(*env)->ReleaseStringUTFChars(env, jbody, body);
|
||||
return err;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
|
||||
@ -43,6 +43,7 @@ import androidx.media.AudioManagerCompat
|
||||
import java.io.File
|
||||
import java.net.InetAddress
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.concurrent.schedule
|
||||
import kotlin.math.roundToInt
|
||||
@ -384,6 +385,23 @@ class BaresipService: Service() {
|
||||
if (File(filesDir, "history").exists())
|
||||
File(filesDir, "history").renameTo(File(filesDir, "calls"))
|
||||
|
||||
if (!File(filesDir, "tmp").exists())
|
||||
File(filesDir, "tmp").mkdir()
|
||||
|
||||
if (!File(filesDir, "recordings").exists())
|
||||
File(filesDir, "recordings").mkdir()
|
||||
else {
|
||||
File(filesDir, "recordings").walk().forEach { f ->
|
||||
Log.d(TAG, "***** file ${f.name}")
|
||||
val recording = f.name.substringBeforeLast("-")
|
||||
if (recording.startsWith("dump")) {
|
||||
val time = recording.takeLast(19)
|
||||
if (!recordings.containsKey(time))
|
||||
recordings[time] = filesDir.absolutePath + "/recordings/" + recording
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "Recordings $recordings")
|
||||
}
|
||||
|
||||
if (contactsMode != "android")
|
||||
Contact.restoreBaresipContacts()
|
||||
@ -573,6 +591,11 @@ class BaresipService: Service() {
|
||||
return
|
||||
}
|
||||
|
||||
if (ev[0] == "sndfile dump") {
|
||||
Log.d(TAG, "Got sndfile dump ${ev[1]}")
|
||||
return
|
||||
}
|
||||
|
||||
val ua = UserAgent.ofUap(uap)
|
||||
if (ua == null) {
|
||||
Log.w(TAG, "uaEvent $event did not find ua $uap")
|
||||
@ -877,6 +900,23 @@ class BaresipService: Service() {
|
||||
NewCallHistory.add(history)
|
||||
NewCallHistory.save()
|
||||
ua.account.missedCalls = ua.account.missedCalls || missed
|
||||
if (isRecOn && call.startTime != null) {
|
||||
val format = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss")
|
||||
val startTime = call.startTime!!
|
||||
var time = format.format(startTime.time).toString()
|
||||
var recording = filesDir.absolutePath + "/recordings/" +
|
||||
"dump-$aor=>${call.peerUri}-$time"
|
||||
if (File("$recording-dec.wav").exists()) {
|
||||
recordings[time] = recording
|
||||
} else {
|
||||
startTime.add(Calendar.SECOND, 1)
|
||||
time = format.format(startTime.time).toString()
|
||||
recording = filesDir.absolutePath + "/recordings/" +
|
||||
"dump-$aor=>${call.peerUri}-$time"
|
||||
recordings[time] = recording
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (!Utils.isVisible()) {
|
||||
if (missed) {
|
||||
@ -1496,6 +1536,7 @@ class BaresipService: Service() {
|
||||
var callActionUri = ""
|
||||
var isMainVisible = false
|
||||
var isMicMuted = false
|
||||
var isRecOn = false
|
||||
|
||||
val uas = ArrayList<UserAgent>()
|
||||
val calls = ArrayList<Call>()
|
||||
@ -1514,6 +1555,7 @@ class BaresipService: Service() {
|
||||
var dnsServers = listOf<InetAddress>()
|
||||
val serviceEvent = MutableLiveData<Event<Long>>()
|
||||
val serviceEvents = mutableListOf<ServiceEvent>()
|
||||
val recordings: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
private var audioFocusRequest: AudioFocusRequestCompat? = null
|
||||
private var btAdapter: BluetoothAdapter? = null
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package com.tutpro.baresip
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
import android.text.format.DateUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@ -9,6 +11,7 @@ import android.widget.ArrayAdapter
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
import java.util.*
|
||||
|
||||
@ -23,6 +26,7 @@ class CallDetailsAdapter(private val ctx: Context, private val rows: ArrayList<C
|
||||
val durationView = view?.findViewById(R.id.duration) as TextView
|
||||
}
|
||||
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
override fun getView(position: Int, view: View?, parent: ViewGroup): View {
|
||||
|
||||
val viewHolder: ViewHolder
|
||||
@ -59,7 +63,7 @@ class CallDetailsAdapter(private val ctx: Context, private val rows: ArrayList<C
|
||||
viewHolder.durationView.text = ""
|
||||
} else {
|
||||
val startText = if (DateUtils.isToday(startTime.timeInMillis)) {
|
||||
val fmt = DateFormat.getTimeInstance(DateFormat.LONG)
|
||||
val fmt = DateFormat.getTimeInstance(DateFormat.MEDIUM)
|
||||
ctx.getString(R.string.today) + " " + fmt.format(startTime.time)
|
||||
} else {
|
||||
val fmt = DateFormat.getDateTimeInstance()
|
||||
@ -68,6 +72,14 @@ class CallDetailsAdapter(private val ctx: Context, private val rows: ArrayList<C
|
||||
viewHolder.timeView.text = startText
|
||||
val duration = (stopTime.time.time - startTime.time.time) / 1000
|
||||
viewHolder.durationView.text = DateUtils.formatElapsedTime(duration)
|
||||
val format = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss")
|
||||
val time = format.format(startTime.time).toString()
|
||||
if (BaresipService.recordings.containsKey(time)) {
|
||||
viewHolder.durationView.typeface = Typeface.DEFAULT_BOLD
|
||||
viewHolder.durationView.setOnClickListener {
|
||||
Utils.playRecording(ctx, BaresipService.recordings[time]!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -115,6 +115,8 @@ object Config {
|
||||
if (!config.contains("dtls_srtp_use_ec"))
|
||||
config = "${config}dtls_srtp_use_ec prime256v1\n"
|
||||
|
||||
replaceVariable("snd_path", "${BaresipService.filesPath}/recordings")
|
||||
|
||||
Utils.putFileContents(configPath, config.toByteArray())
|
||||
BaresipService.isConfigInitialized = true
|
||||
Log.i(TAG, "Initialized config to '$config'")
|
||||
|
||||
@ -76,6 +76,7 @@ class MainActivity : AppCompatActivity() {
|
||||
private lateinit var kgm: KeyguardManager
|
||||
private lateinit var screenEventReceiver: BroadcastReceiver
|
||||
private lateinit var serviceEventObserver: Observer<Event<Long>>
|
||||
private var recIcon: MenuItem? = null
|
||||
private var micIcon: MenuItem? = null
|
||||
private var speakerIcon: MenuItem? = null
|
||||
private lateinit var swipeRefresh: SwipeRefreshLayout
|
||||
@ -1258,6 +1259,13 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
menuInflater.inflate(R.menu.main_menu, menu)
|
||||
|
||||
menuInflater.inflate(R.menu.rec_icon, menu)
|
||||
recIcon = menu.findItem(R.id.recIcon)
|
||||
if (BaresipService.isRecOn)
|
||||
recIcon!!.setIcon(R.drawable.rec_on)
|
||||
else
|
||||
recIcon!!.setIcon(R.drawable.rec_off)
|
||||
|
||||
menuInflater.inflate(R.menu.mic_icon, menu)
|
||||
micIcon = menu.findItem(R.id.micIcon)
|
||||
if (BaresipService.isMicMuted)
|
||||
@ -1279,6 +1287,19 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
when (item.itemId) {
|
||||
|
||||
R.id.recIcon -> {
|
||||
if (Call.call("connected") == null) {
|
||||
BaresipService.isRecOn = !BaresipService.isRecOn
|
||||
if (BaresipService.isRecOn) {
|
||||
item.setIcon(R.drawable.rec_on)
|
||||
Api.module_load("sndfile")
|
||||
} else {
|
||||
item.setIcon(R.drawable.rec_off)
|
||||
Api.module_unload("sndfile")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
R.id.micIcon -> {
|
||||
if (Call.call("connected") != null) {
|
||||
BaresipService.isMicMuted = !BaresipService.isMicMuted
|
||||
|
||||
@ -10,6 +10,7 @@ import android.graphics.Bitmap.createScaledBitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.media.AudioAttributes
|
||||
import android.net.Uri
|
||||
import android.net.wifi.WifiManager
|
||||
import android.provider.DocumentsContract
|
||||
@ -24,10 +25,12 @@ import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.media.AudioDeviceInfo
|
||||
import android.media.AudioManager
|
||||
import android.media.MediaPlayer
|
||||
import android.os.*
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.text.isDigitsOnly
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
@ -992,4 +995,105 @@ object Utils {
|
||||
}
|
||||
}
|
||||
|
||||
fun playRecording(ctx: Context, recording: String) {
|
||||
Log.d(TAG, "Playing recording $recording")
|
||||
val decPlayer = MediaPlayer()
|
||||
decPlayer.apply {
|
||||
setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.build()
|
||||
)
|
||||
setOnPreparedListener {
|
||||
val encPlayer = MediaPlayer()
|
||||
encPlayer.apply {
|
||||
setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.build()
|
||||
)
|
||||
setOnPreparedListener {
|
||||
it.start()
|
||||
decPlayer.start()
|
||||
Log.d(TAG, "Started players")
|
||||
}
|
||||
setOnCompletionListener {
|
||||
Log.d(TAG, "Stopping encPlayer")
|
||||
it.stop()
|
||||
it.release()
|
||||
}
|
||||
try {
|
||||
val file = "$recording-enc.wav"
|
||||
val encFile = File(file).copyTo(File(BaresipService.filesPath + "/tmp/encode.wav"), true)
|
||||
val encUri = encFile.toUri()
|
||||
Log.d(TAG, "Setting data source $encUri")
|
||||
setDataSource(ctx, encUri)
|
||||
prepareAsync()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "encPlayer IllegalArgumentException: $e")
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "encPlayer IOException: $e")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "encPlayer Exception: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
setOnCompletionListener {
|
||||
Log.d(TAG, "Stopping decPlayer")
|
||||
it.stop()
|
||||
it.release()
|
||||
}
|
||||
try {
|
||||
val file = "$recording-dec.wav"
|
||||
val decFile = File(file).copyTo(File(BaresipService.filesPath + "/tmp/decode.wav"), true)
|
||||
val decUri = decFile.toUri()
|
||||
Log.d(TAG, "Setting data source $decUri")
|
||||
setDataSource(ctx, decUri)
|
||||
prepareAsync()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "decPlayer IllegalArgumentException: $e")
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "decPlayer IOException: $e")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "decPlayer Exception: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun playFile(ctx: Context, path: String) {
|
||||
Log.d(TAG, "Playing file $path")
|
||||
MediaPlayer().apply {
|
||||
setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.build()
|
||||
)
|
||||
setOnPreparedListener {
|
||||
Log.d(TAG, "Starting MediaPlayer")
|
||||
it.start()
|
||||
Log.d(TAG, "MediaPlayer started")
|
||||
}
|
||||
setOnCompletionListener {
|
||||
Log.d(TAG, "Stopping MediaPlayer")
|
||||
it.stop()
|
||||
it.release()
|
||||
}
|
||||
try {
|
||||
Log.d(TAG, "Preparing $path")
|
||||
setDataSource(ctx, path.toUri())
|
||||
prepareAsync()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "MediaPlayer IllegalArgumentException: ${e.printStackTrace()}")
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "MediaPlayer IOException: ${e.printStackTrace()}")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "MediaPlayer Exception: ${e.printStackTrace()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user