Skip to content

Commit

Permalink
Merge pull request #42 from AgoraIO-Community/dev/audio_call
Browse files Browse the repository at this point in the history
Dev/audio call
  • Loading branch information
tamworth authored Jun 7, 2024
2 parents 2fee789 + 0244e2e commit 167e840
Show file tree
Hide file tree
Showing 37 changed files with 1,510 additions and 1,176 deletions.
17 changes: 17 additions & 0 deletions Android/.idea/deploymentTargetDropDown.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 35 additions & 1 deletion Android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,41 @@
请参考官网文档 [开通服务](https://doc.shengwang.cn/doc/one-to-one-live/android/get-started/enable-service)

## 2. 运行示例
请参考官网文档 [跑通示例项目](https://doc.shengwang.cn/doc/one-to-one-live/android/get-started/run-example)

- 克隆或者直接下载项目源码
- 打开 Android Studio,并用它来打开项目的 [Android](../Android) 目录。IDE 会自动开始构建项目

- 获取声网 App ID -------- [声网Agora - 文档中心 - 如何获取 App ID](https://docs.agora.io/cn/Agora%20Platform/get_appid_token?platform=All%20Platforms#%E8%8E%B7%E5%8F%96-app-id)

> 点击创建应用。
> <br><img src="https://accktvpic.oss-cn-beijing.aliyuncs.com/pic/github_readme/create_app_1.jpg" width="500px">
> <br>选择你要创建的应用类型。
> <br><img src="https://accktvpic.oss-cn-beijing.aliyuncs.com/pic/github_readme/create_app_2.jpg" width="500px">
- 获取 App 证书 ----- [声网Agora - 文档中心 - 获取 App 证书](https://docs.agora.io/cn/Agora%20Platform/get_appid_token?platform=All%20Platforms#%E8%8E%B7%E5%8F%96-app-%E8%AF%81%E4%B9%A6)

> 在声网控制台的项目管理页面,找到你的项目,点击配置。
> <br><img src="https://fullapp.oss-cn-beijing.aliyuncs.com/scenario_api/callapi/config/1641871111769.png" width="500px">
> <br>点击主要证书下面的复制图标,即可获取项目的 App 证书。
> <br><img src="https://fullapp.oss-cn-beijing.aliyuncs.com/scenario_api/callapi/config/1637637672988.png" width="500px">
- 开启 RTM
> <br><img src="https://fullapp.oss-cn-beijing.aliyuncs.com/scenario_api/callapi/config/rtm_config1.jpg" width="500px">
> <br><img src="https://fullapp.oss-cn-beijing.aliyuncs.com/scenario_api/callapi/config/rtm_config2.jpg" width="500px">
> <br><img src="https://fullapp.oss-cn-beijing.aliyuncs.com/scenario_api/callapi/config/rtm_config3.jpg" width="500px">
- <a id="custom-report">开通声网自定义数据上报和分析服务</a>

> 该服务当前处于免费内测期,如需试用该服务,请联系 sales@agora.io
- 在项目根目录找到 [gradle.properties](gradle.properties),填入声网的 AppId 和 Certificate 以及环信 AppKey(如果不需要体验环信自定义信令流程,IMAppKey 可以设置为`""`)
```
AG_APP_ID=
AG_APP_CERTIFICATE=
IM_APP_KEY=
```
- 在 Android Studio 顶部工具栏中,单击“File”->选择“Sync Project With Gradle File”,等待 Gradle 同步完成,即可运行项目并进行调试


## 3. 项目介绍
请参考官网文档 [场景介绍](https://doc.shengwang.cn/doc/one-to-one-live/android/overview/product-overview)
Expand Down
2 changes: 1 addition & 1 deletion Android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ android {
minSdk 24
targetSdk 33
versionCode 1
versionName "2.0.0"
versionName "2.1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

Expand Down
236 changes: 128 additions & 108 deletions Android/app/src/main/java/io/agora/onetoone/LivingActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class LivingActivity : AppCompatActivity(), ICallApiListener {
private val mCenterCanvas by lazy { TextureView(this) }

private var callDialog: AlertDialog? = null
private var callTypeDialog: AlertDialog ?= null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand Down Expand Up @@ -102,14 +103,13 @@ class LivingActivity : AppCompatActivity(), ICallApiListener {
prepareConfig = PrepareConfig()
prepareConfig.rtcToken = enterModel.rtcToken
prepareConfig.rtmToken = enterModel.rtmToken
prepareConfig.autoJoinRTC = enterModel.autoJoinRTC

role = if (enterModel.isBrodCaster) CallRole.CALLEE else CallRole.CALLER

rtcEngine = _createRtcEngine()
setupView()
mCallState = CallStateType.Idle
updateCallState(CallStateType.Idle)
updateCallState(CallStateType.Idle, null)

// 初始化
initMessageManager {}
Expand Down Expand Up @@ -150,18 +150,6 @@ class LivingActivity : AppCompatActivity(), ICallApiListener {
}
}

override fun onConnectionLost() {
// 表示rtm超时断连了,需要重新登录,这里模拟了3s重新登录
mViewBinding.root.post {
Toasty.normal(this@LivingActivity, "rtm连接错误,需要重新登录", Toast.LENGTH_SHORT)
.show()
}
mViewBinding.root.postDelayed({
rtmManager?.logout()
rtmManager?.login(prepareConfig.rtmToken) {}
}, 3000)
}

override fun onTokenPrivilegeWillExpire(channelName: String) {
// 重新获取token
tokenPrivilegeWillExpire()
Expand Down Expand Up @@ -204,10 +192,17 @@ class LivingActivity : AppCompatActivity(), ICallApiListener {
closeAction()
}

private fun updateCallState(state: CallStateType) {
private fun updateCallState(state: CallStateType, stateReason: CallStateReason?) {
runOnUiThread {
when (state) {
CallStateType.Calling -> {
if (stateReason == CallStateReason.LocalVideoCall || stateReason == CallStateReason.RemoteVideoCall) {
mViewBinding.vRight.isVisible = true
mViewBinding.vLeft.isVisible = true
} else if (stateReason == CallStateReason.LocalAudioCall || stateReason == CallStateReason.RemoteAudioCall) {
mViewBinding.vRight.isVisible = false
mViewBinding.vLeft.isVisible = false
}
publishMedia(false)
setupCanvas(null)
mViewBinding.vRight.alpha = 1f
Expand Down Expand Up @@ -426,12 +421,27 @@ class LivingActivity : AppCompatActivity(), ICallApiListener {
// 检查信令通道链接状态
if (!checkConnectionAndNotify()) return
publishMedia(false)
api.call(enterModel.showUserId.toInt()) { error ->
// call 失败立刻挂断
if (error != null && mCallState == CallStateType.Calling) {
api.cancelCall { }
}
}

callTypeDialog = AlertDialog.Builder(this)
.setTitle("通话类型选择")
.setMessage("选择音频或视频通话")
.setPositiveButton("音频") { p0, p1 ->
api.call(enterModel.showUserId.toInt(), CallType.Audio, mapOf("key1" to "value1", "key2" to "value2")) { error ->
// call 失败立刻挂断
if (error != null && mCallState == CallStateType.Calling) {
api.cancelCall { }
}
}
}.setNegativeButton("视频") { p0, p1 ->
api.call(enterModel.showUserId.toInt()) { error ->
// call 失败立刻挂断
if (error != null && mCallState == CallStateType.Calling) {
api.cancelCall { }
}
}
}.create()
callTypeDialog?.setCancelable(false)
callTypeDialog?.show()
}

private fun hangupAction() {
Expand All @@ -453,109 +463,119 @@ class LivingActivity : AppCompatActivity(), ICallApiListener {
eventReason: String,
eventInfo: Map<String, Any>
) {
val publisher = eventInfo.getOrDefault(CallApiImpl.kPublisher, enterModel.currentUid)
if (publisher != enterModel.currentUid) {
when (state) {
CallStateType.Calling, CallStateType.Connecting, CallStateType.Connected -> {
setupCanvas(null)
}
else -> {
setupCanvas(mCenterCanvas)
}
}
return
}
Log.d(TAG, "onCallStateChanged state: ${state.value}, stateReason: ${stateReason.value}, eventReason: $eventReason, eventInfo: $eventInfo publisher: $publisher / ${enterModel.currentUid}")
mCallState = state
updateCallState(state)

when (state) {
CallStateType.Calling -> {
val fromUserId = eventInfo[CallApiImpl.kFromUserId] as? Int ?: 0
val toUserId = eventInfo[CallApiImpl.kRemoteUserId] as? Int ?: 0
if (connectedUserId != null && connectedUserId != fromUserId) {
// 检查信令通道链接状态
if (!checkConnectionAndNotify()) return
api.reject(fromUserId, "already calling") {
runOnUiThread {
val publisher = eventInfo.getOrDefault(CallApiImpl.kPublisher, enterModel.currentUid)
if (publisher != enterModel.currentUid) {
when (state) {
CallStateType.Calling, CallStateType.Connecting, CallStateType.Connected -> {
setupCanvas(null)
}
return
}
// 触发状态的用户是自己才处理
if (enterModel.currentUid.toIntOrNull() == toUserId) {
connectedUserId = fromUserId
// 检查信令通道链接状态
if (!checkConnectionAndNotify()) return
api.accept(remoteUserId = fromUserId) { err ->
if (err != null) {
//如果接受消息出错,则发起拒绝,回到初始状态
api.reject(fromUserId, err.msg) {}
}
else -> {
setupCanvas(mCenterCanvas)
}
} else if (enterModel.currentUid.toIntOrNull() == fromUserId) {
connectedUserId = toUserId
callDialog = AlertDialog.Builder(this)
.setTitle("提示")
.setMessage("呼叫用户 $toUserId")
.setNegativeButton("取消") { p0, p1 ->
// 检查信令通道链接状态
if (!checkConnectionAndNotify()) return@setNegativeButton
api.cancelCall { err ->
}
}.create()
callDialog?.setCancelable(false)
callDialog?.show()
}
return@runOnUiThread
}
CallStateType.Connected -> {
callDialog?.dismiss()
callDialog = null

Toasty.normal(this, "通话开始${eventInfo.getOrDefault(CallApiImpl.kCostTimeMap, "")}", Toast.LENGTH_LONG).show()

//setup configuration after join channel
videoEncoderConfig?.let { config ->
rtcEngine.setVideoEncoderConfiguration(config)
val cameraConfig = CameraCapturerConfiguration(CameraCapturerConfiguration.CAMERA_DIRECTION.CAMERA_FRONT)
cameraConfig.captureFormat.width = config.dimensions.width
cameraConfig.captureFormat.height = config.dimensions.height
cameraConfig.captureFormat.fps = config.frameRate
rtcEngine.setCameraCapturerConfiguration(cameraConfig)
}
}
CallStateType.Prepared -> {
when (stateReason) {
CallStateReason.LocalHangup,
CallStateReason.RemoteHangup -> {
Toasty.normal(this, "通话结束", Toast.LENGTH_SHORT).show()
Log.d(
TAG,
"onCallStateChanged state: ${state.value}, stateReason: ${stateReason.value}, eventReason: $eventReason, eventInfo: $eventInfo publisher: $publisher / ${enterModel.currentUid}"
)
mCallState = state
updateCallState(state, stateReason)

when (state) {
CallStateType.Calling -> {
val fromUserId = eventInfo[CallApiImpl.kFromUserId] as? Int ?: 0
val toUserId = eventInfo[CallApiImpl.kRemoteUserId] as? Int ?: 0
if (connectedUserId != null && connectedUserId != fromUserId) {
// 检查信令通道链接状态
if (!checkConnectionAndNotify()) return@runOnUiThread
api.reject(fromUserId, "already calling") {
}
return@runOnUiThread
}
CallStateReason.LocalRejected,
CallStateReason.RemoteRejected -> {
Toasty.normal(this, "通话被拒绝", Toast.LENGTH_SHORT).show()
// 触发状态的用户是自己才处理
if (enterModel.currentUid.toIntOrNull() == toUserId) {
connectedUserId = fromUserId
// 检查信令通道链接状态
if (!checkConnectionAndNotify()) return@runOnUiThread
api.accept(remoteUserId = fromUserId) { err ->
if (err != null) {
//如果接受消息出错,则发起拒绝,回到初始状态
api.reject(fromUserId, err.msg) {}
}
}
} else if (enterModel.currentUid.toIntOrNull() == fromUserId) {
connectedUserId = toUserId
callDialog = AlertDialog.Builder(this)
.setTitle("提示")
.setMessage("呼叫用户 $toUserId")
.setNegativeButton("取消") { p0, p1 ->
// 检查信令通道链接状态
if (!checkConnectionAndNotify()) return@setNegativeButton
api.cancelCall { err ->
}
}.create()
callDialog?.setCancelable(false)
callDialog?.show()
}
CallStateReason.CallingTimeout -> {
Toasty.normal(this, "无应答", Toast.LENGTH_SHORT).show()
}
CallStateType.Connected -> {
callDialog?.dismiss()
callDialog = null

Toasty.normal(
this,
"通话开始${eventInfo.getOrDefault(CallApiImpl.kCostTimeMap, "")}",
Toast.LENGTH_LONG
).show()

//setup configuration after join channel
videoEncoderConfig?.let { config ->
rtcEngine.setVideoEncoderConfiguration(config)
val cameraConfig =
CameraCapturerConfiguration(CameraCapturerConfiguration.CAMERA_DIRECTION.CAMERA_FRONT)
cameraConfig.captureFormat.width = config.dimensions.width
cameraConfig.captureFormat.height = config.dimensions.height
cameraConfig.captureFormat.fps = config.frameRate
rtcEngine.setCameraCapturerConfiguration(cameraConfig)
}
CallStateReason.RemoteCallBusy -> {
Toasty.normal(this, "用户正忙", Toast.LENGTH_SHORT).show()
}
CallStateType.Prepared -> {
when (stateReason) {
CallStateReason.LocalHangup,
CallStateReason.RemoteHangup -> {
Toasty.normal(this, "通话结束", Toast.LENGTH_SHORT).show()
}
CallStateReason.LocalRejected,
CallStateReason.RemoteRejected -> {
Toasty.normal(this, "通话被拒绝", Toast.LENGTH_SHORT).show()
}
CallStateReason.CallingTimeout -> {
Toasty.normal(this, "无应答", Toast.LENGTH_SHORT).show()
}
CallStateReason.RemoteCallBusy -> {
Toasty.normal(this, "用户正忙", Toast.LENGTH_SHORT).show()
}
else -> {}
}
else -> {}
connectedUserId = null
callDialog?.dismiss()
callDialog = null
}
connectedUserId = null
callDialog?.dismiss()
callDialog = null
}
CallStateType.Failed -> {
Toasty.normal(this, eventReason, Toast.LENGTH_LONG).show()
closeAction()
CallStateType.Failed -> {
Toasty.normal(this, eventReason, Toast.LENGTH_LONG).show()
closeAction()
}
else -> {}
}
else -> {}
}
}

override fun onCallEventChanged(event: CallEvent, eventReason: String?) {
Log.d(TAG, "onCallEventChanged: $event, eventReason: $eventReason")
when(event) {
CallEvent.RemoteLeave -> {
CallEvent.RemoteLeft -> {
hangupAction()
} else -> {}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ class MainActivity : AppCompatActivity() {
enterModel.frameRate = mViewBinding.etFps.text.toString()

enterModel.autoAccept = mViewBinding.cbAutoAccept.isChecked
enterModel.autoJoinRTC = mViewBinding.cbJoinRTC.isChecked
// enterModel.autoJoinRTC = mViewBinding.cbJoinRTC.isChecked

val runnable = Runnable {
if (isShowMode) {
Expand Down
Loading

0 comments on commit 167e840

Please sign in to comment.