@@ -6,6 +6,7 @@ import android.view.View
66import android.widget.Toast
77import androidx.activity.compose.rememberLauncherForActivityResult
88import androidx.activity.result.contract.ActivityResultContracts
9+ import androidx.compose.foundation.layout.BoxScope
910import androidx.compose.foundation.layout.Column
1011import androidx.compose.foundation.layout.padding
1112import androidx.compose.material3.AlertDialog
@@ -19,6 +20,7 @@ import androidx.compose.runtime.mutableIntStateOf
1920import androidx.compose.runtime.mutableStateMapOf
2021import androidx.compose.runtime.mutableStateOf
2122import androidx.compose.runtime.remember
23+ import androidx.compose.runtime.rememberCoroutineScope
2224import androidx.compose.runtime.saveable.rememberSaveable
2325import androidx.compose.runtime.setValue
2426import androidx.compose.ui.Alignment
@@ -29,6 +31,7 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
2931import androidx.compose.ui.res.stringResource
3032import androidx.compose.ui.tooling.preview.Preview
3133import androidx.compose.ui.unit.dp
34+ import androidx.core.content.ContentProviderCompat.requireContext
3235import io.agora.api.example.compose.BuildConfig
3336import io.agora.api.example.compose.R
3437import io.agora.api.example.compose.data.SettingPreferences
@@ -47,6 +50,7 @@ import io.agora.rtc2.RtcEngine
4750import io.agora.rtc2.RtcEngineConfig
4851import io.agora.rtc2.video.VideoCanvas
4952import io.agora.rtc2.video.VideoEncoderConfiguration
53+ import kotlinx.coroutines.launch
5054import java.io.File
5155
5256@Composable
@@ -173,7 +177,7 @@ fun MediaRecorder() {
173177 Toast .makeText(context, " Permission Denied" , Toast .LENGTH_LONG ).show()
174178 }
175179 }
176-
180+ val coroutineScope = rememberCoroutineScope()
177181 MediaRecorderView (
178182 channelName = channelName,
179183 isJoined = isJoined,
@@ -200,58 +204,81 @@ fun MediaRecorder() {
200204 rtcEngine.setupRemoteVideo(VideoCanvas (view, Constants .RENDER_MODE_HIDDEN , id))
201205 }
202206 },
203- onRecorderClick = { id, isRecording ->
204- if (isRecording) {
205- val storagePath: String =
206- context.externalCacheDir?.absolutePath + File .separator + " media_recorder_" + channelName + " _" + id + " .mp4"
207- val recorder = rtcEngine.createMediaRecorder(RecorderStreamInfo (channelName, id,0 ))
208- recorder.setMediaRecorderObserver(object : IMediaRecorderCallback {
209- override fun onRecorderStateChanged (
210- channelId : String? ,
211- uid : Int ,
212- state : Int ,
213- reason : Int
214- ) {
215- Log .d(
216- " MediaRecorder" ,
217- " LocalMediaRecorder -- onRecorderStateChanged channelId=$channelId , uid=$uid , state=$state , reason=$reason "
218- )
219- if (state == AgoraMediaRecorder .RECORDER_STATE_STOP ) {
220- recorders.remove(uid)
221- recoderResult = storagePath
222- }
223- }
207+ overlay = { _, id ->
208+ var isRecording by remember { mutableStateOf(false ) }
209+ Button (
210+ modifier = Modifier
211+ .padding(8 .dp)
212+ .align(Alignment .BottomEnd ),
213+ onClick = {
214+ isRecording = ! isRecording
215+ if (isRecording) {
216+ val storagePath: String =
217+ context.externalCacheDir?.absolutePath + File .separator + " media_recorder_" + channelName + " _" + id + " .mp4"
218+ val recorder =
219+ rtcEngine.createMediaRecorder(RecorderStreamInfo (channelName, id, 0 ))
220+ recorder.setMediaRecorderObserver(object : IMediaRecorderCallback {
221+ override fun onRecorderStateChanged (
222+ channelId : String? ,
223+ uid : Int ,
224+ state : Int ,
225+ reason : Int
226+ ) {
227+ Log .d(
228+ " MediaRecorder" ,
229+ " LocalMediaRecorder -- onRecorderStateChanged channelId=$channelId , uid=$uid , state=$state , reason=$reason "
230+ )
231+ if (state == AgoraMediaRecorder .RECORDER_STATE_STOP ) {
232+ recorders.remove(uid)
233+ recoderResult = storagePath
234+ } else if (state == AgoraMediaRecorder .RECORDER_STATE_ERROR && reason == AgoraMediaRecorder .RECORDER_REASON_CONFIG_CHANGED ) {
235+ coroutineScope.launch {
236+ isRecording = false
237+ recorders[id]?.let {
238+ it.stopRecording()
239+ it.release()
240+ }
241+ }
242+ }
243+ }
224244
225- override fun onRecorderInfoUpdated (
226- channelId : String? ,
227- uid : Int ,
228- info : RecorderInfo ?
229- ) {
230- info ? : return
231- Log .d(
232- " MediaRecorder" ,
233- " LocalMediaRecorder -- onRecorderInfoUpdated channelId="
234- + channelId + " , uid=" + uid + " , fileName=" + info.fileName
235- + " , durationMs=" + info.durationMs + " , fileSize=" + info.fileSize
245+ override fun onRecorderInfoUpdated (
246+ channelId : String? ,
247+ uid : Int ,
248+ info : RecorderInfo ?
249+ ) {
250+ info ? : return
251+ Log .d(
252+ " MediaRecorder" ,
253+ " LocalMediaRecorder -- onRecorderInfoUpdated channelId="
254+ + channelId + " , uid=" + uid + " , fileName=" + info.fileName
255+ + " , durationMs=" + info.durationMs + " , fileSize=" + info.fileSize
256+ )
257+ }
258+ })
259+ recorder.startRecording(
260+ AgoraMediaRecorder .MediaRecorderConfiguration (
261+ storagePath,
262+ AgoraMediaRecorder .CONTAINER_MP4 ,
263+ AgoraMediaRecorder .STREAM_TYPE_BOTH ,
264+ 120000 ,
265+ 0
266+ )
236267 )
268+ recorders[id] = recorder
269+ } else {
270+ recorders[id]?.let {
271+ it.stopRecording()
272+ it.release()
273+ }
237274 }
238-
239275 })
240- recorder.startRecording(
241- AgoraMediaRecorder .MediaRecorderConfiguration (
242- storagePath,
243- AgoraMediaRecorder .CONTAINER_MP4 ,
244- AgoraMediaRecorder .STREAM_TYPE_BOTH ,
245- 120000 ,
246- 0
276+ {
277+ Text (
278+ text = if (! isRecording) stringResource(id = R .string.start_recording) else stringResource(
279+ id = R .string.stop_recording
247280 )
248281 )
249- recorders[id] = recorder
250- } else {
251- recorders[id]?.let {
252- it.stopRecording()
253- it.release()
254- }
255282 }
256283 },
257284 onCameraSwitchClick = {
@@ -284,30 +311,15 @@ private fun MediaRecorderView(
284311 videoIdList : List <Int >,
285312 setupVideo : (View , Int , Boolean ) -> Unit ,
286313 statsMap : Map <Int , VideoStatsInfo > = emptyMap(),
287- onRecorderClick : (id : Int , isRecording: Boolean ) -> Unit = { _, _ -> },
314+ overlay : @Composable BoxScope .(index : Int , id: Int ) -> Unit? = { _, _ -> },
288315 onCameraSwitchClick : () -> Unit = { }
289316) {
290317 Column {
291318 VideoGrid (
292319 modifier = Modifier .weight(1.0f ),
293320 videoIdList = videoIdList,
294321 setupVideo = setupVideo,
295- overlay = { _, id ->
296- var isRecording by rememberSaveable { mutableStateOf(false ) }
297- Button (
298- modifier = Modifier
299- .padding(8 .dp)
300- .align(Alignment .BottomEnd ),
301- onClick = {
302- isRecording = ! isRecording
303- onRecorderClick(id, isRecording)
304- })
305- {
306- Text (text = if (! isRecording) stringResource(id = R .string.start_recording) else stringResource(
307- id = R .string.stop_recording
308- ))
309- }
310- }
322+ overlay = overlay
311323 )
312324 Button (
313325 modifier = Modifier
0 commit comments