您的位置:首页 > 财经 > 金融 > 国庆节网页设计模板免费下载_哈尔滨网络科技公司哪家好_网络营销课程_百度小说风云榜

国庆节网页设计模板免费下载_哈尔滨网络科技公司哪家好_网络营销课程_百度小说风云榜

2025/4/8 10:41:20 来源:https://blog.csdn.net/m0_50774720/article/details/147039050  浏览:    关键词:国庆节网页设计模板免费下载_哈尔滨网络科技公司哪家好_网络营销课程_百度小说风云榜
国庆节网页设计模板免费下载_哈尔滨网络科技公司哪家好_网络营销课程_百度小说风云榜

一、前期准备

1、注册声网账号

声网官网

2、创建项目

拿到AppID,主要证书

二、代码部分

先上一下官方提供的demo地址:

Agora-RTC-QuickStart: 此仓库包含 Agora RTC Native SDK 的QuickStart示例项目。 - Gitee.comhttps://gitee.com/agoraio-community/Agora-RTC-QuickStart/tree/main/Android/Agora-RTC-QuickStart-Android可以在声网的帮助文档中看下图的教程很详细,或者无脑跑上面的demo,只需要填入声网控制台上获取到的appid,证书,和生成的临时token,以及生成临时token时填入的渠道号,但是控制台生成的临时token只有一天的有效期,下面会给出服务端生成临时token的代码,自己部署到服务器上,用客户端去调用接口

服务端:

提供一个获取token的接口

//还没要到代码,后续会补充上来,或者自行去帮助文档中查看,注意是rtc_token

客户端:

1、配置仓库

在settings.gradle中配置,主要是配置镜像

pluginManagement {repositories {maven { url "https://maven.aliyun.com/repository/public" }google()mavenCentral()gradlePluginPortal()}
}
dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {maven { url "https://maven.aliyun.com/repository/public" }google()mavenCentral()}
}rootProject.name = "你的项目名称"
include ':app'

2、导入声网的sdk

在app模块下的build.gradle的dependencies中加入下面这行,注意下面这个是轻量级的库,详细的库在声网自行搜索

 implementation 'io.agora.rtc:lite-sdk:4.5.1' //替换为最新的

 3、添加防混淆规则

在app模块下的proguard-rules.pro文件中加入下面代码

-keep class io.agora.**{*;}
-dontwarn io.agora.**

4、 静态声明权限

在AndroidManifest.XML文件中声明如下权限

  <uses-featureandroid:name="android.hardware.camera"android:required="true" /><!--必要权限--><uses-permission android:name="android.permission.INTERNET"/><!--可选权限--><uses-permission android:name="android.permission.CAMERA"/><uses-permission android:name="android.permission.RECORD_AUDIO"/><uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/><uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permission android:name="android.permission.BLUETOOTH"/><!-- 对于 Android 12.0 及以上且集成 v4.1.0 以下 SDK 的设备,还需要添加以下权限 --><uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/><!-- 对于 Android 12.0 及以上设备,还需要添加以下权限 --><uses-permission android:name="android.permission.READ_PHONE_STATE"/><uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>

5、具体代码部分

注:如果需要两个客户端互相传输音视频的话,直接用上面给的官方demo的代码就行,下面介绍的是对官方代码的一些封装,可以满足单西向传输,动态申请权限,调用临时token的接口,token本地存储及校验和不同页面的使用方法

(1)创建bean对象:
import java.io.Serializable/*
* name :相机的名称
* channelName :频道名称 必须唯一
* uid :用户唯一id
* token : 临时token
* lastPostTime :上次成功获取到数据的时间
*/
data class Camera(val name: String,val channelName: String = "",var uid:Int = 0,var token: String? = null,var lastPostTime: Long = 0
) : Serializable
(2)token的本地存储工具类
object SpUtil {private val context = App.app!!val sharedPreferences: SharedPreferences =context.getSharedPreferences("camera", Context.MODE_PRIVATE)//获取绑定的摄像头列表fun getCameraData(): List<Camera> {val listStr = sharedPreferences.getString("list", "")if (listStr == "") {return listOf<Camera>()} else {val typeToken = object : TypeToken<List<Camera>>() {}.typereturn Gson().fromJson(listStr, typeToken)}}//保存绑定的摄像头列表fun saveCameraListData(list: List<Camera>) {sharedPreferences.edit().apply {putString("list", Gson().toJson(list))apply()}}//更新绑定的摄像头列表fun updateCameraData(channelName: String, token: String, uid: Int) {val list = getCameraData().toMutableList()val localCameraList = list.filter { it.channelName == channelName }if (localCameraList.isNotEmpty()) {val localCamera = localCameraList.first()localCameraList.forEach {list.remove(it)}localCamera.token = tokenlocalCamera.lastPostTime = System.currentTimeMillis()localCamera.uid = uidlist.add(localCamera)}saveCameraListData(list)}//检查绑定摄像头的token是否过期fun checkToken(camera: Camera): Boolean {val list = getCameraData()val localCameraList = list.filter { it.channelName == camera.channelName }if (localCameraList.isNotEmpty()) {val localCamera = localCameraList.first()// 判断token是否过期val checkTime = System.currentTimeMillis() - localCamera.lastPostTime <  43200000if (localCamera.token != null && checkTime) {return true}}return false}//获取本地摄像头的数据fun getLocalCameraData(): Camera {val str = sharedPreferences.getString("localCamera", "")if (str == "") {val localCamera = Camera("本机", PlatformApp.getInstance().oaid)saveLocalCameraData(localCamera)return localCamera} else {val typeToken = object : TypeToken<Camera>() {}.typereturn Gson().fromJson(str, typeToken)}}//保存本地摄像头的数据fun saveLocalCameraData(camera: Camera) {sharedPreferences.edit().apply {putString("localCamera", Gson().toJson(camera))apply()}}//检查本地摄像头的token是否过期fun checkLocalCameraData(): Boolean {val localCamera = getLocalCameraData()// 判断token是否过期val checkTime = System.currentTimeMillis() - localCamera.lastPostTime < 43200000return localCamera.token != null && checkTime}
}
(3)RTC的管理类

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import android.view.SurfaceView
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
import com.kwad.sdk.utils.bt.runOnUiThread
import fczs.colorscol.rrjj.base.App
import fczs.colorscol.rrjj.beans.Camera
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
import io.agora.rtc2.video.VideoCanvas
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Call
import okhttp3.Callback
import okhttp3.FormBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.json.JSONObject
import java.io.IOException
import kotlin.coroutines.resumeclass RtcManger {private val baseContext = App.app!!// 填写项目的 App ID,可在声网控制台中生成private val appId = "你的AppId"//临时tokenvar token = ""//uid每个渠道应保持唯一性,为0的话,sdk会自动分配一个,但如果临时token是自己服务器生成的,那就应该保持和服务给的一致,否则token鉴权不过,无法加入渠道var uid = 0//要加入的渠道var channelName = "";private var mRtcEngine: RtcEngine? = null//权限回调码val PERMISSION_REQ_ID: Int = 22//远程视频视图容器var remoteVideoViewContainer: FrameLayout? = null//本地视图容器var localVideoViewContainer: FrameLayout? = nullprivate val mRtcEventHandler: IRtcEngineEventHandler = object : IRtcEngineEventHandler() {// 监听频道内的远端用户,获取用户的 uid 信息override fun onUserJoined(uid: Int, elapsed: Int) {runOnUiThread { // 获取 uid 后,设置远端视频视图setupRemoteVideo(uid)}}override fun onUserOffline(uid: Int, reason: Int) {super.onUserOffline(uid, reason)runOnUiThread {remoteVideoViewContainer?.removeAllViews()}}}fun init(camera: Camera) {this.channelName = camera.channelNamethis.token = camera.token ?: ""this.uid = camera.uid}/** clientRoleType: 用户角色类型,默认为 Constants.BROADCASTER (主播) 发送方 ,还可以是Constants.CLIENT_ROLE_AUDIENCE(观众) 接收方* localVideoViewContainer: 本地视频视图容器* remoteVideoViewContainer: 远端视频视图容器*/fun initializeAndJoinChannel(clientRoleType: Int = Constants.CLIENT_ROLE_BROADCASTER,localVideoViewContainer: FrameLayout? = null,remoteVideoViewContainer: FrameLayout? = null) {this.remoteVideoViewContainer = remoteVideoViewContainerthis.localVideoViewContainer = localVideoViewContainertry {// 创建 RtcEngineConfig 对象,并进行配置val config = RtcEngineConfig()config.mContext = baseContextconfig.mAppId = appId//添加远端视频视图handlerif (remoteVideoViewContainer != null) {config.mEventHandler = mRtcEventHandler}// 创建并初始化 RtcEnginemRtcEngine = RtcEngine.create(config)} catch (e: Exception) {throw RuntimeException("Check the error.")}// 启用视频模块mRtcEngine!!.enableVideo()//本地视图显示if (localVideoViewContainer != null) {// 开启本地预览mRtcEngine!!.startPreview()// 创建一个 SurfaceView 对象,并将其作为 FrameLayout 的子对象val container = localVideoViewContainerval surfaceView = SurfaceView(baseContext)container.addView(surfaceView)// 将 SurfaceView 对象传入声网实时互动 SDK,设置本地视图mRtcEngine!!.setupLocalVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0))}// 创建 ChannelMediaOptions 对象,并进行配置val options = ChannelMediaOptions()// 根据场景将用户角色设置为 BROADCASTER (主播) 或 AUDIENCE (观众)options.clientRoleType = clientRoleType// 直播场景下,设置频道场景为 BROADCASTING (直播场景)options.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING// 使用临时 Token 加入频道,自行指定用户 ID 并确保其在频道内的唯一性mRtcEngine!!.joinChannel(token, channelName, uid, options)}// 获取体验实时音视频互动所需的录音、摄像头等权限fun getRequiredPermissions(): Array<String> {// 判断 targetSDKVersion 31 及以上时所需的权限return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {arrayOf(Manifest.permission.RECORD_AUDIO,  // 录音权限Manifest.permission.CAMERA,  // 摄像头权限Manifest.permission.READ_PHONE_STATE,  // 读取电话状态权限Manifest.permission.BLUETOOTH_CONNECT, // 蓝牙连接权限)} else {arrayOf(Manifest.permission.RECORD_AUDIO,Manifest.permission.CAMERA)}}private fun setupRemoteVideo(uid: Int) {if (remoteVideoViewContainer != null) {val container = remoteVideoViewContainer!!val surfaceView = SurfaceView(baseContext)surfaceView.setZOrderMediaOverlay(true)container.addView(surfaceView)// 将 SurfaceView 对象传入声网实时互动 SDK,设置远端视图mRtcEngine!!.setupRemoteVideo(VideoCanvas(surfaceView,VideoCanvas.RENDER_MODE_FIT,uid))}}fun checkPermissions(context: Context): Boolean {for (permission in getRequiredPermissions()) {val permissionCheck = ContextCompat.checkSelfPermission(context, permission)if (permissionCheck != PackageManager.PERMISSION_GRANTED) {return false}}return true}fun close() {// 停止本地视频预览mRtcEngine?.stopPreview()// 离开频道mRtcEngine?.leaveChannel()localVideoViewContainer?.removeAllViews()}//获取后台数据  channelName:渠道名称  expire: 获取到的临时token的有效时间  localCamera: 是否是本地摄像头(摄像)suspend fun requestData(channelName: String, localCamera: Boolean = false): String {return suspendCancellableCoroutine { continuation ->val fromBody = FormBody.Builder().add("channelName",channelName).add("uid","能保持唯一性的字符串,如设备的oaid").build()val request = Request.Builder().url("自己服务器接口的url").post(fromBody).build()// 发起异步请求OkHttpClient().newCall(request).enqueue(object : Callback {override fun onResponse(call: Call, response: Response) {if (response.isSuccessful) {try {val responseBody = response.body?.string() ?: ""val jsonObject = JSONObject(responseBody)val code = jsonObject.getInt("code")if (code == 200) {var data = jsonObject.getJSONObject("data")val token = data.getString("token")val uid = data.getInt("uid")this@RtcManger.channelName = channelNamethis@RtcManger.token = tokenthis@RtcManger.uid = uidif (localCamera) {//更新本机摄像头发送视图时的数据SpUtil.saveLocalCameraData(Camera("本机", channelName, uid, token, System.currentTimeMillis()))} else {//更新本地存储的绑定摄像头的数据SpUtil.updateCameraData(channelName, token, uid)}continuation.resume("true")} else {continuation.resume("false")}} catch (e: Exception) {// JSON 解析失败continuation.resume("false")Log.e("Request failed", "Json解析失败:" + e.message.toString())}}}override fun onFailure(call: Call, e: IOException) {continuation.resume("false")Log.e("Request failed", "请求失败:" + e.message.toString())}})}}
}
(4)发送音视频界面
界面:

确报有一个下面的布局就行

  <FrameLayoutandroid:id="@+id/local_video_view_container"android:layout_width="match_parent"android:layout_height="match_parent"/>
代码:

在activity/fragment中声明如下变量和方法,示例中发送端是fragment,后面接收端是activity的示例代码

private val rtcManger = RtcManger()
private val localCamera by lazy { SpUtil.getLocalCameraData() }//正常动态申请权限应该在activity的结果回调方法中写,但如果在onResume中执行判断就可以不用写结果回调方法
override fun onResume() {super.onResume()// 如果已经授权,则初始化 RtcEngine 并加入频道if (rtcManger.checkPermissions(requireContext())) {binding.permissionLL.container.visibility = View.GONEcheckToken()} else {//显示无权限布局binding.permissionLL.apply {container.visibility = View.VISIBLEpermissionBt.setOnClickListener {ActivityCompat.requestPermissions(requireActivity(),rtcManger.getRequiredPermissions(),rtcManger.PERMISSION_REQ_ID)}}}}private fun checkToken() {binding.permissionLL.container.visibility = View.GONEif (SpUtil.checkLocalCameraData()) {rtcManger.init(localCamera)start()} else {CoroutineScope(Dispatchers.Main).launch {connectDialog.show()val result = withContext(Dispatchers.IO) {rtcManger.requestData(localCamera.channelName, true)}.toBoolean()connectDialog.dismiss()if (result) {start()} else {Toast.makeText(requireContext(), "服务器异常,请稍后重试", Toast.LENGTH_SHORT).show()}}}}private fun start() {rtcManger.initializeAndJoinChannel(Constants.CLIENT_ROLE_BROADCASTER,binding.localVideoViewContainer)}private fun stop() {rtcManger.close()}override fun onPause() {super.onPause()stop()}
 (5)接收音视频界面
界面:
    <FrameLayoutandroid:id="@+id/remote_video_view_container"android:layout_width="match_parent"android:layout_height="match_parent" />
代码: 

和上面发送的大差不差,只是进入acticity时传递了要发送端的Camera对象,里面有渠道号,token等信息,这些发送端在拉token的时候已经通过SpUtil存到本地了,自己读取一下需要的

private lateinit var camera: Camerapublic override fun onCreate(savedInstanceState: Bundle?) {camera = intent.getSerializableExtra("camera") as Camera}override fun onResume() {super.onResume()// 如果已经授权,则初始化 RtcEngine 并加入频道if (rtcManger.checkPermissions(this)) {binding.permissionLL.container.visibility = View.GONEcheckToken()} else {binding.permissionLL.apply {container.visibility = View.VISIBLEpermissionBt.setOnClickListener {ActivityCompat.requestPermissions(this@CameraActivity,rtcManger.getRequiredPermissions(),rtcManger.PERMISSION_REQ_ID)}}}
}@Deprecated("Deprecated in Java")override fun onRequestPermissionsResult(requestCode: Int,permissions: Array<String?>,grantResults: IntArray) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)// 系统权限申请回调if (rtcManger.checkPermissions(this)) {checkToken()}}private fun checkToken() {binding.permissionLL.container.visibility = View.GONEif (SpUtil.checkToken(camera)) {rtcManger.init(camera)start()} else {CoroutineScope(Dispatchers.Main).launch {connectDialog.show()val result = withContext(Dispatchers.IO) {rtcManger.requestData(camera.channelName)}.toBoolean()if (result) {connectDialog.dismiss()start()} else {connectDialog.showFailView {finish()}}}}}fun start() {rtcManger.initializeAndJoinChannel(Constants.CLIENT_ROLE_AUDIENCE,null,binding.remoteVideoViewContainer)}override fun onPause() {super.onPause()binding.remoteVideoViewContainer.removeAllViews() rtcManger.close()}

ok,就是这样,总体来说是很简单的,发送端和接收端只是start方法不一样,其余的都差不多,希望上面的经验能帮到你

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com