视频通话APP的实现(利用声网SDK)2022.7
1.在控制台获取App_Id 及 App_secrect(APP主要证书)
2.申请token
token生成器代码:
设置参数(appId ,appCertificate,channelName(频道名)等参数),调用 RtcTokenBuilderSample的main方法可得token
3APP的实现
3.1集成sdk
下载sdk:docs.agora.io/cn/Video/do…
3.2
3.3在build.gradle的dependencies添加以下代码导入jdk
implementation files('libs\agora-rtc-sdk.jar')
在build.gradle的android添加
sourceSets {
main {
jniLibs.srcDirs = ['../../../libs']
jniLibs.srcDirs = ['src/main/jniLibs']
}
}
3.4权限声明
在AndroidManifest添加以下代码
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.BLUETOOTH" />
3.5Layout
以FrameLayout来作为本地预览视频的容器,RelativeLayout作为对方视频的容器
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/local_video_view_container"
android:layout_width="414dp"
android:layout_height="293dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:background="#827b92"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tv_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="本地预览视频"
android:textSize="20sp" />
</FrameLayout>
<Button
android:id="@+id/btn_call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="call"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/local_video_view_container" />
<RelativeLayout
android:id="@+id/remote_video_view_container"
android:layout_width="match_parent"
android:layout_height="301dp"
android:layout_marginTop="24dp"
android:background="#827b92"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_call" >
<TextView
android:id="@+id/tv_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="left"
android:text="对方视频"
android:textSize="20sp" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
3.6 Activity
需要设置appid,token以及频道名
class MainActivity : AppCompatActivity() {
private val TAG: String = MainActivity::class.java.getSimpleName()
private val PERMISSION_REQ_ID = 22
// Permission WRITE_EXTERNAL_STORAGE is not mandatory
// for Agora RTC SDK, just in case if you wanna save
// logs to external sdcard.
private val REQUESTED_PERMISSIONS = arrayOf(
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA
)
private var mRtcEngine: RtcEngine? = null
private var mCallEnd = false
private var mMuted = false
private var mLocalContainer: FrameLayout? = null
private var mRemoteContainer: RelativeLayout? = null
private var mLocalVideo: VideoCanvas? = null
private var mRemoteVideo: VideoCanvas? = null
private lateinit var mCallBtn: Button
private val app_id="你的appId"
private val token="你申请的token"
private val channelName="你的频道号"
// Customized logger view
// private val mLogView: LoggerRecyclerView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initUi()
if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID)&&
checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID)) {
initEngineAndJoinChannel();//初始化rtc
}
}
fun initUi(){
mRemoteContainer = findViewById(R.id.remote_video_view_container)
mCallBtn =findViewById(R.id.btn_call)
mLocalContainer = findViewById(R.id.local_video_view_container)
mCallBtn.setOnClickListener {
onCallClicked()
}
}
//申请权限
private fun checkSelfPermission(permission: String, requestCode: Int): Boolean {
if (ContextCompat.checkSelfPermission(this, permission) !==
PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
this.REQUESTED_PERMISSIONS,
requestCode
)
return false
}
return true
}
private fun initEngineAndJoinChannel() {
// This is our usual steps for joining
// a channel and starting a call.
initializeEngine()
setupVideoConfig()
setupLocalVideo()
joinChannel()
}
private fun initializeEngine() {
mRtcEngine = try {
RtcEngine.create(baseContext, app_id, mRtcEventHandler)
} catch (e: Exception) {
Log.e(TAG, Log.getStackTraceString(e))
throw RuntimeException(
"""
NEED TO check rtc sdk init fatal error
${Log.getStackTraceString(e)}
""".trimIndent()
)
}
}
private val mRtcEventHandler: IRtcEngineEventHandler = object : IRtcEngineEventHandler() {
override fun onJoinChannelSuccess(channel: String, uid: Int, elapsed: Int) {
Log.d("join success " ,uid.toString() )
}
override fun onUserJoined(uid: Int, elapsed: Int) {
runOnUiThread {
Log.d("join First remote video" ,uid.toString() )
setupRemoteVideo(uid)
}
}
override fun onUserOffline(uid: Int, reason: Int) {
runOnUiThread {
Log.d("join User offine" ,uid.toString() )
onRemoteUserLeft(uid)
}
}
}
private fun setupRemoteVideo(uid: Int) {
var parent: ViewGroup = mRemoteContainer!!
if (parent.indexOfChild(mLocalVideo!!.view) > -1) {
parent = mLocalContainer!!
}
if (mRemoteVideo != null) {
return
}
val view = RtcEngine.CreateRendererView(baseContext)
view.setZOrderMediaOverlay(parent === mLocalContainer)
parent.addView(view)
mRemoteVideo = VideoCanvas(view, VideoCanvas.RENDER_MODE_HIDDEN, uid)
// Initializes the video view of a remote user.
mRtcEngine!!.setupRemoteVideo(mRemoteVideo)
}
private fun onRemoteUserLeft(uid: Int) {
if (mRemoteVideo != null && mRemoteVideo!!.uid == uid) {
removeFromParent(mRemoteVideo)
// Destroys remote view
mRemoteVideo = null
}
}
private fun removeFromParent(canvas: VideoCanvas?): ViewGroup? {
if (canvas != null) {
val parent = canvas.view.parent
if (parent != null) {
val group = parent as ViewGroup
group.removeView(canvas.view)
return group
}
}
return null
}
private fun setupVideoConfig() {
// In simple use cases, we only need to enable video capturing
// and rendering once at the initialization step.
// Note: audio recording and playing is enabled by default.
mRtcEngine!!.enableVideo()
// Please go to this page for detailed explanation
// https://docs.agora.io/en/Video/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#af5f4de754e2c1f493096641c5c5c1d8f
mRtcEngine!!.setVideoEncoderConfiguration(
VideoEncoderConfiguration(
VideoEncoderConfiguration.VD_640x360,
VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
VideoEncoderConfiguration.STANDARD_BITRATE,
VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT
)
)
}
private fun setupLocalVideo() {
// This is used to set a local preview.
// The steps setting local and remote view are very similar.
// But note that if the local user do not have a uid or do
// not care what the uid is, he can set his uid as ZERO.
// Our server will assign one and return the uid via the event
// handler callback function (onJoinChannelSuccess) after
// joining the channel successfully.
val view = RtcEngine.CreateRendererView(baseContext)
view.setZOrderMediaOverlay(true)
mLocalContainer!!.addView(view)
// Initializes the local video view.
// RENDER_MODE_HIDDEN: Uniformly scale the video until it fills the visible boundaries. One dimension of the video may have clipped contents.
mLocalVideo = VideoCanvas(view, VideoCanvas.RENDER_MODE_HIDDEN, 0)
mRtcEngine!!.setupLocalVideo(mLocalVideo)
}
private fun joinChannel() {
// 1. Users can only see each other after they join the
// same channel successfully using the same app id.
// 2. One token is only valid for the channel name that
// you use to generate this token.
var token: String? = token
if (TextUtils.isEmpty(token) || TextUtils.equals(token, "#YOUR ACCESS TOKEN#")) {
token = null // default, no token
}
mRtcEngine!!.joinChannel(token, channelName, "Extra Optional Data", 0)
}
fun onCallClicked() {
if (mCallEnd) {
startCall()
mCallEnd = false
// mCallBtn.setImageResource(R.drawable.btn_endcall)
} else {
endCall()
mCallEnd = true
// mCallBtn.setImageResource(R.drawable.btn_startcall)
}
// showButtons(!mCallEnd)
}
private fun startCall() {
setupLocalVideo()
joinChannel()
}
private fun endCall() {
removeFromParent(mLocalVideo)
mLocalVideo = null
removeFromParent(mRemoteVideo)
mRemoteVideo = null
leaveChannel()
}
private fun leaveChannel() {
mRtcEngine!!.leaveChannel()
}
}