diff --git a/Android/APIExample-Compose/app/src/main/java/io/agora/api/example/compose/samples/CustomAudioSource.kt b/Android/APIExample-Compose/app/src/main/java/io/agora/api/example/compose/samples/CustomAudioSource.kt index e9c4e949e..1b11b4605 100644 --- a/Android/APIExample-Compose/app/src/main/java/io/agora/api/example/compose/samples/CustomAudioSource.kt +++ b/Android/APIExample-Compose/app/src/main/java/io/agora/api/example/compose/samples/CustomAudioSource.kt @@ -143,6 +143,7 @@ fun CustomAudioSource() { Toast.makeText(context, "Permission Granted", Toast.LENGTH_LONG).show() option.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING option.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER + option.publishMicrophoneTrack = false TokenUtils.gen(channelName, 0){ rtcEngine.joinChannel(it, channelName, 0, option) } diff --git a/Android/APIExample-Compose/app/src/main/java/io/agora/api/example/compose/samples/MediaRecorder.kt b/Android/APIExample-Compose/app/src/main/java/io/agora/api/example/compose/samples/MediaRecorder.kt index d24df3d2d..83d41992b 100644 --- a/Android/APIExample-Compose/app/src/main/java/io/agora/api/example/compose/samples/MediaRecorder.kt +++ b/Android/APIExample-Compose/app/src/main/java/io/agora/api/example/compose/samples/MediaRecorder.kt @@ -6,6 +6,7 @@ import android.view.View import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material3.AlertDialog @@ -19,6 +20,7 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -29,6 +31,7 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.core.content.ContentProviderCompat.requireContext import io.agora.api.example.compose.BuildConfig import io.agora.api.example.compose.R import io.agora.api.example.compose.data.SettingPreferences @@ -47,6 +50,7 @@ import io.agora.rtc2.RtcEngine import io.agora.rtc2.RtcEngineConfig import io.agora.rtc2.video.VideoCanvas import io.agora.rtc2.video.VideoEncoderConfiguration +import kotlinx.coroutines.launch import java.io.File @Composable @@ -173,7 +177,7 @@ fun MediaRecorder() { Toast.makeText(context, "Permission Denied", Toast.LENGTH_LONG).show() } } - + val coroutineScope = rememberCoroutineScope() MediaRecorderView( channelName = channelName, isJoined = isJoined, @@ -200,58 +204,81 @@ fun MediaRecorder() { rtcEngine.setupRemoteVideo(VideoCanvas(view, Constants.RENDER_MODE_HIDDEN, id)) } }, - onRecorderClick = { id, isRecording -> - if (isRecording) { - val storagePath: String = - context.externalCacheDir?.absolutePath + File.separator + "media_recorder_" + channelName + "_" + id + ".mp4" - val recorder = rtcEngine.createMediaRecorder(RecorderStreamInfo(channelName, id,0)) - recorder.setMediaRecorderObserver(object : IMediaRecorderCallback { - override fun onRecorderStateChanged( - channelId: String?, - uid: Int, - state: Int, - reason: Int - ) { - Log.d( - "MediaRecorder", - "LocalMediaRecorder -- onRecorderStateChanged channelId=$channelId, uid=$uid, state=$state, reason=$reason" - ) - if (state == AgoraMediaRecorder.RECORDER_STATE_STOP) { - recorders.remove(uid) - recoderResult = storagePath - } - } + overlay = { _, id -> + var isRecording by remember { mutableStateOf(false) } + Button( + modifier = Modifier + .padding(8.dp) + .align(Alignment.BottomEnd), + onClick = { + isRecording = !isRecording + if (isRecording) { + val storagePath: String = + context.externalCacheDir?.absolutePath + File.separator + "media_recorder_" + channelName + "_" + id + ".mp4" + val recorder = + rtcEngine.createMediaRecorder(RecorderStreamInfo(channelName, id, 0)) + recorder.setMediaRecorderObserver(object : IMediaRecorderCallback { + override fun onRecorderStateChanged( + channelId: String?, + uid: Int, + state: Int, + reason: Int + ) { + Log.d( + "MediaRecorder", + "LocalMediaRecorder -- onRecorderStateChanged channelId=$channelId, uid=$uid, state=$state, reason=$reason" + ) + if (state == AgoraMediaRecorder.RECORDER_STATE_STOP) { + recorders.remove(uid) + recoderResult = storagePath + } else if (state == AgoraMediaRecorder.RECORDER_STATE_ERROR && reason == AgoraMediaRecorder.RECORDER_REASON_CONFIG_CHANGED) { + coroutineScope.launch { + isRecording = false + recorders[id]?.let { + it.stopRecording() + it.release() + } + } + } + } - override fun onRecorderInfoUpdated( - channelId: String?, - uid: Int, - info: RecorderInfo? - ) { - info ?: return - Log.d( - "MediaRecorder", - "LocalMediaRecorder -- onRecorderInfoUpdated channelId=" - + channelId + ", uid=" + uid + ", fileName=" + info.fileName - + ", durationMs=" + info.durationMs + ", fileSize=" + info.fileSize + override fun onRecorderInfoUpdated( + channelId: String?, + uid: Int, + info: RecorderInfo? + ) { + info ?: return + Log.d( + "MediaRecorder", + "LocalMediaRecorder -- onRecorderInfoUpdated channelId=" + + channelId + ", uid=" + uid + ", fileName=" + info.fileName + + ", durationMs=" + info.durationMs + ", fileSize=" + info.fileSize + ) + } + }) + recorder.startRecording( + AgoraMediaRecorder.MediaRecorderConfiguration( + storagePath, + AgoraMediaRecorder.CONTAINER_MP4, + AgoraMediaRecorder.STREAM_TYPE_BOTH, + 120000, + 0 + ) ) + recorders[id] = recorder + } else { + recorders[id]?.let { + it.stopRecording() + it.release() + } } - }) - recorder.startRecording( - AgoraMediaRecorder.MediaRecorderConfiguration( - storagePath, - AgoraMediaRecorder.CONTAINER_MP4, - AgoraMediaRecorder.STREAM_TYPE_BOTH, - 120000, - 0 + { + Text( + text = if (!isRecording) stringResource(id = R.string.start_recording) else stringResource( + id = R.string.stop_recording ) ) - recorders[id] = recorder - } else { - recorders[id]?.let { - it.stopRecording() - it.release() - } } }, onCameraSwitchClick = { @@ -284,7 +311,7 @@ private fun MediaRecorderView( videoIdList: List, setupVideo: (View, Int, Boolean) -> Unit, statsMap: Map = emptyMap(), - onRecorderClick: (id: Int, isRecording: Boolean) -> Unit = { _, _ -> }, + overlay: @Composable BoxScope.(index: Int, id: Int) -> Unit? = { _, _ -> }, onCameraSwitchClick: () -> Unit = { } ) { Column { @@ -292,22 +319,7 @@ private fun MediaRecorderView( modifier = Modifier.weight(1.0f), videoIdList = videoIdList, setupVideo = setupVideo, - overlay = { _, id -> - var isRecording by rememberSaveable { mutableStateOf(false) } - Button( - modifier = Modifier - .padding(8.dp) - .align(Alignment.BottomEnd), - onClick = { - isRecording = !isRecording - onRecorderClick(id, isRecording) - }) - { - Text(text = if (!isRecording) stringResource(id = R.string.start_recording) else stringResource( - id = R.string.stop_recording - )) - } - } + overlay = overlay ) Button( modifier = Modifier diff --git a/Android/APIExample-Compose/app/src/main/java/io/agora/api/example/compose/samples/PlayAudioFiles.kt b/Android/APIExample-Compose/app/src/main/java/io/agora/api/example/compose/samples/PlayAudioFiles.kt index a567c743e..3b72f57fa 100644 --- a/Android/APIExample-Compose/app/src/main/java/io/agora/api/example/compose/samples/PlayAudioFiles.kt +++ b/Android/APIExample-Compose/app/src/main/java/io/agora/api/example/compose/samples/PlayAudioFiles.kt @@ -201,7 +201,7 @@ private fun PlayAudioFilesView( modifier = Modifier .weight(1.0f) .padding(16.dp, 8.dp) - .clickable { + .clickable(isJoined) { rtcEngine?.startAudioMixing( "/assets/music_1.m4a", false, @@ -218,7 +218,7 @@ private fun PlayAudioFilesView( modifier = Modifier .weight(1.0f) .padding(16.dp, 8.dp) - .clickable { + .clickable(isJoined) { rtcEngine?.resumeAudioMixing() } ) @@ -230,7 +230,7 @@ private fun PlayAudioFilesView( modifier = Modifier .weight(1.0f) .padding(16.dp, 8.dp) - .clickable { + .clickable(isJoined) { rtcEngine?.pauseAudioMixing() } ) @@ -242,7 +242,7 @@ private fun PlayAudioFilesView( modifier = Modifier .weight(1.0f) .padding(16.dp, 8.dp) - .clickable { + .clickable(isJoined) { rtcEngine?.stopAudioMixing() } ) @@ -279,7 +279,7 @@ private fun PlayAudioFilesView( modifier = Modifier .weight(1.0f) .padding(16.dp, 8.dp) - .clickable { + .clickable(isJoined) { rtcEngine?.playEffect( EFFECT_SOUND_ID, // The sound ID of the audio effect file to be played. EFFECT_FILE_PATH, // The file path of the audio effect file. @@ -297,7 +297,7 @@ private fun PlayAudioFilesView( modifier = Modifier .weight(1.0f) .padding(16.dp, 8.dp) - .clickable { + .clickable(isJoined) { rtcEngine?.resumeEffect(EFFECT_SOUND_ID) }) Text(text = stringResource(id = R.string.pause), @@ -307,7 +307,7 @@ private fun PlayAudioFilesView( modifier = Modifier .weight(1.0f) .padding(16.dp, 8.dp) - .clickable { + .clickable (isJoined) { rtcEngine?.pauseEffect(EFFECT_SOUND_ID) }) Text(text = stringResource(id = R.string.stop), @@ -317,7 +317,7 @@ private fun PlayAudioFilesView( modifier = Modifier .weight(1.0f) .padding(16.dp, 8.dp) - .clickable { + .clickable(isJoined) { rtcEngine?.stopEffect(EFFECT_SOUND_ID) }) } diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/LiveStreaming.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/LiveStreaming.java index bc62d3c7a..746d1a8d6 100644 --- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/LiveStreaming.java +++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/LiveStreaming.java @@ -556,7 +556,7 @@ private void joinChannel(String channelId) { ChannelMediaOptions option = new ChannelMediaOptions(); option.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING; - option.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER; + option.clientRoleType = CLIENT_ROLE_AUDIENCE; option.autoSubscribeAudio = true; option.autoSubscribeVideo = true; int res; diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelAudio.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelAudio.java index 2b40e24a7..24176c0d5 100755 --- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelAudio.java +++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelAudio.java @@ -492,16 +492,16 @@ public void onTokenGen(String token) { } else { LocalAudioMixerConfiguration config = new LocalAudioMixerConfiguration(); config.syncWithLocalMic = false; - config.mixedAudioStreams.clear(); + config.audioInputStreams.clear(); LocalAudioMixerConfiguration.MixedAudioStream remoteStream = new LocalAudioMixerConfiguration.MixedAudioStream(); remoteStream.sourceType = AUDIO_SOURCE_REMOTE_CHANNEL; remoteStream.channelId = channelId; - config.mixedAudioStreams.add(remoteStream); + config.audioInputStreams.add(remoteStream); LocalAudioMixerConfiguration.MixedAudioStream remoteStream2 = new LocalAudioMixerConfiguration.MixedAudioStream(); remoteStream2.sourceType = AUDIO_SOURCE_MICROPHONE; remoteStream2.channelId = channelId; - config.mixedAudioStreams.add(remoteStream2); + config.audioInputStreams.add(remoteStream2); engine.startLocalAudioMixer(config); } }