Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swift UI #408

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
54ecea1
update podfile source to china
Mar 8, 2024
24fde98
update podfile source to china
Mar 8, 2024
0e3d569
gitee sync test
Mar 8, 2024
2623b4b
[Android] gitee sync >> use china repos.
Mar 8, 2024
cb080da
fix sync shell
Mar 8, 2024
0289b30
[Android]revert to use common repo url.
xgfd3 Mar 8, 2024
a5bae8a
update sync script.
xgfd3 Mar 8, 2024
bbd4d66
update sync script.
xgfd3 Mar 8, 2024
9af882e
update sync script.
xgfd3 Mar 8, 2024
b6f4099
Add download agora SDK when executing pod install and modify dependen…
Mar 13, 2024
1d061ad
Merge branch 'dev/4.3.1' of github.com:AgoraIO/API-Examples into dev/…
Mar 13, 2024
9495e16
gitee sync modify source url
Mar 14, 2024
c07d690
modify podfile file script
Mar 14, 2024
c2056f0
modify podfile file script
Mar 15, 2024
4f4852d
update download ijk and swiftlint
Mar 18, 2024
d07217f
update ijk download script
Mar 18, 2024
bb4c19d
iOS Adapt to RTC 4.3.1 version
Mar 27, 2024
1affca5
[windows] Adapt to 4.3.1 sdk and ajust examples.
xgfd3 Mar 27, 2024
bfd60a9
mac Adapt to RTC 4.3.1 version
Apr 1, 2024
7effca5
Merge branch 'dev/4.3.1' of github.com:AgoraIO/API-Examples into dev/…
Apr 1, 2024
688e8e8
Add SwiftUI Project
Apr 1, 2024
789c058
update SwiftUI Case
Apr 24, 2024
5a20743
Merge dev/new-ui branch.
xgfd3 May 7, 2024
80cf4fe
custom render pip by SwiftUI
Aug 13, 2024
60fcf09
fix bugs
Aug 14, 2024
f78a4f0
add scene of RTC live with SwiftUI for APIExample project
Sep 27, 2024
b6ce0ab
add scene of RTC live with SwiftUI for APIExample project
Sep 29, 2024
b3e5a07
voice changer
Sep 29, 2024
61a95a4
add scene of RTMP, sound effect for SwiftUI
Oct 9, 2024
97f7c01
custom audio render
Oct 9, 2024
d54e0a9
Merge branch 'dev/4.5.0' into swiftUI
Oct 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
package io.agora.api.example.compose.samples

import android.Manifest
import android.os.Build
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.agora.api.example.compose.BuildConfig
import io.agora.api.example.compose.data.SettingPreferences
import io.agora.api.example.compose.ui.common.AudioGrid
import io.agora.api.example.compose.ui.common.AudioStatsInfo
import io.agora.api.example.compose.ui.common.ChannelNameInput
import io.agora.api.example.compose.ui.common.DropdownMenuRaw
import io.agora.api.example.compose.ui.common.SliderRaw
import io.agora.api.example.compose.ui.common.SwitchRaw
import io.agora.api.example.compose.utils.TokenUtils
import io.agora.rtc2.ChannelMediaOptions
import io.agora.rtc2.Constants
import io.agora.rtc2.IRtcEngineEventHandler
import io.agora.rtc2.RtcEngine
import io.agora.rtc2.RtcEngineConfig

@Composable
fun JoinChannelAudio() {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val keyboard = LocalSoftwareKeyboardController.current
var isJoined by rememberSaveable { mutableStateOf(false) }
var channelName by rememberSaveable { mutableStateOf("") }
var localUid by rememberSaveable { mutableIntStateOf(0) }
var videoIdList by rememberSaveable { mutableStateOf(listOf<Int>()) }
val statsMap = remember { mutableStateMapOf(0 to AudioStatsInfo()) }

val rtcEngine = remember {
RtcEngine.create(RtcEngineConfig().apply {
mAreaCode = SettingPreferences.getArea()
mContext = context
mAppId = BuildConfig.AGORA_APP_ID
mEventHandler = object : IRtcEngineEventHandler() {
override fun onJoinChannelSuccess(channel: String?, uid: Int, elapsed: Int) {
super.onJoinChannelSuccess(channel, uid, elapsed)
isJoined = true
localUid = uid
videoIdList = videoIdList + uid
statsMap[uid] = AudioStatsInfo()
}

override fun onLeaveChannel(stats: RtcStats?) {
super.onLeaveChannel(stats)
isJoined = false
videoIdList = emptyList()
statsMap.clear()
}

override fun onUserJoined(uid: Int, elapsed: Int) {
super.onUserJoined(uid, elapsed)
videoIdList = videoIdList + uid
statsMap[uid] = AudioStatsInfo()
}

override fun onUserOffline(uid: Int, reason: Int) {
super.onUserOffline(uid, reason)
videoIdList = videoIdList - uid
statsMap.remove(uid)
}

override fun onLocalAudioStats(stats: LocalAudioStats?) {
super.onLocalAudioStats(stats)
statsMap[localUid]?.copy(localAudioStats = stats)?.let {
statsMap[localUid] = it
}
}

override fun onRemoteAudioStats(stats: RemoteAudioStats?) {
super.onRemoteAudioStats(stats)
val uid = stats?.uid ?: return
statsMap[uid]?.copy(remoteAudioStats = stats)?.let {
statsMap[uid] = it
}
}
}
}).apply {
enableAudio()
}
}
DisposableEffect(lifecycleOwner) {
onDispose {
if (isJoined) {
rtcEngine.leaveChannel()
}
RtcEngine.destroy()
}
}
val permissionLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestMultiplePermissions()) { grantedMap ->
val allGranted = grantedMap.values.all { it }
if (allGranted) {
// Permission is granted
Toast.makeText(context, "Permission Granted", Toast.LENGTH_LONG).show()
val mediaOptions = ChannelMediaOptions()
mediaOptions.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING
mediaOptions.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER
TokenUtils.gen(channelName, 0) {
rtcEngine.joinChannel("", channelName, 0, mediaOptions)
}

} else {
// Permission is denied
Toast.makeText(context, "Permission Denied", Toast.LENGTH_LONG).show()
}
}

JoinChannelAudioView(
rtcEngine = rtcEngine,
videoIdList = videoIdList,
statsMap = statsMap,
channelName = channelName,
isJoined = isJoined,
onJoinClick = {
keyboard?.hide()
channelName = it
permissionLauncher.launch(
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
arrayOf(
Manifest.permission.RECORD_AUDIO,
)
} else {
arrayOf(
Manifest.permission.RECORD_AUDIO,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.BLUETOOTH_CONNECT,
)
}
)
},
onLeaveClick = {
rtcEngine.leaveChannel()
}
)
}

@Composable
fun JoinChannelAudioView(
videoIdList: List<Int>,
statsMap: Map<Int, AudioStatsInfo> = mapOf(),
rtcEngine: RtcEngine? = null,
channelName: String = "",
isJoined: Boolean = false,
onJoinClick: (String) -> Unit = {},
onLeaveClick: () -> Unit = {}
) {
Column(
Modifier
.fillMaxHeight()
.fillMaxWidth()
) {
AudioGrid(
modifier = Modifier.height(380.dp),
videoIdList = videoIdList,
statsMap = statsMap
)
val lazyListState = rememberLazyListState(Int.MAX_VALUE)

LazyColumn(
modifier = Modifier.weight(1.0f),
verticalArrangement = Arrangement.Bottom,
state = lazyListState
) {
item {
DropdownMenuRaw(
title = "Audio Route",
options = listOf(
"Speaker" to Constants.AUDIO_ROUTE_SPEAKERPHONE,
"Headset" to Constants.AUDIO_ROUTE_HEADSET,
"Earphone" to Constants.AUDIO_ROUTE_EARPIECE,
"Bluetooth" to Constants.AUDIO_ROUTE_BLUETOOTH_DEVICE_HFP
),
selected = 0
) { _, option ->
rtcEngine?.setRouteInCommunicationMode(option.second)
}
}
item {
Column {
var inEarOpen by remember { mutableStateOf(false) }
SwitchRaw(title = "InEar Monitor", checked = inEarOpen) {
inEarOpen = it
rtcEngine?.enableInEarMonitoring(it)
}
if (inEarOpen) {
SliderRaw(title = "InEar Monitor Vol") {
rtcEngine?.setInEarMonitoringVolume((it * 100).toInt())
}
}
}
}
item {
SliderRaw(title = "Playout Vol"){
rtcEngine?.adjustPlaybackSignalVolume((it * 100).toInt())
}
}
item {
SliderRaw(title = "Recording Vol"){
rtcEngine?.adjustRecordingSignalVolume((it * 100).toInt())
}
}
item {
DropdownMenuRaw(
title = "Scenario",
options = listOf(
"Default" to Constants.AUDIO_SCENARIO_DEFAULT,
"Game Streaming" to Constants.AUDIO_SCENARIO_GAME_STREAMING,
"Chatroom" to Constants.AUDIO_SCENARIO_CHATROOM,
"Chorus" to Constants.AUDIO_SCENARIO_CHORUS,
"Meeting" to Constants.AUDIO_SCENARIO_MEETING,
),
selected = 0
) { _, option ->
rtcEngine?.setAudioScenario(option.second)
}
}
item {
DropdownMenuRaw(
title = "Audio Profile",
options = listOf(
"Default" to Constants.AUDIO_PROFILE_DEFAULT,
"Speech Standard" to Constants.AUDIO_PROFILE_SPEECH_STANDARD,
"Music Standard" to Constants.AUDIO_PROFILE_MUSIC_STANDARD,
"Music Standard Stereo" to Constants.AUDIO_PROFILE_MUSIC_STANDARD_STEREO,
"Music High Quality" to Constants.AUDIO_PROFILE_MUSIC_HIGH_QUALITY,
"Music High Quality Stereo" to Constants.AUDIO_PROFILE_MUSIC_HIGH_QUALITY_STEREO,
),
selected = 0
) { _, option ->
rtcEngine?.setAudioScenario(option.second)
}
}
item {
DropdownMenuRaw(
title = "Channel Profile",
options = listOf(
"Communication" to Constants.CHANNEL_PROFILE_COMMUNICATION,
"Live Broadcasting" to Constants.CHANNEL_PROFILE_LIVE_BROADCASTING,
"Game" to Constants.CHANNEL_PROFILE_GAME,
"Cloud Gaming" to Constants.CHANNEL_PROFILE_CLOUD_GAMING,
"Communication 1v1" to Constants.CHANNEL_PROFILE_COMMUNICATION_1v1,
"Communication 1v1" to Constants.CHANNEL_PROFILE_COMMUNICATION_1v1,
)
) { _, option ->
rtcEngine?.setChannelProfile(option.second)
}
}
}

ChannelNameInput(
channelName = channelName,
isJoined = isJoined,
onJoinClick = onJoinClick,
onLeaveClick = onLeaveClick
)
}
}

@Preview
@Composable
fun JoinChannelAudioViewPreview() {
JoinChannelAudioView(
listOf(0, 1, 2, 3),
mapOf(
0 to AudioStatsInfo(
localAudioStats = IRtcEngineEventHandler.LocalAudioStats().apply {
numChannels = 2
sentSampleRate = 48000
sentBitrate = 128
},
),
1 to AudioStatsInfo(),
2 to AudioStatsInfo(),
3 to AudioStatsInfo()
)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Loading