前言
1.直播逻辑
经过我长时间的查阅官方文档,我发现了在每一个实例包括大疆的直播中,都包含了APPID、TOken和Channel三部分,但是我并没有发现有单独的创建Channel隧道的地方。后来我想,是不是说,这个Channel是随建随取的呢?也就是说,只要是直播端和观察端,输入了相同的Channel隧道,那就算是建立了一个Channel呢?
教程 Tutorial:快速跑通声网 RTC Web Demo 这篇文章中,提到 “Channel:字符串,要音视频双方保证输入的字符串相同 ” 这样也证实了我的说法,下面就是需要实践了,我记得我无意中获取到了一个在线的例子,可是后来还是不知道放到哪里去了,后来还是找到了:基于声网 Web SDK 实现视频通话场景 这里也有一个在线的实例,输入两个相同的Channel名字,就可以实现实时直播。
实现直播的基本逻辑如下:
1.调用 createClient 方法创建 AgoraRTCClient 对象。
2.调用 setClientRole 方法,将角色 role 设为 “audience”(观众), 将延时等级 level 设为 1(低延时)。
3.调用 join 方法加入一个 RTC 频道,你需要在该方法中传入 App ID 、用户 ID、Token、频道名称。
4.当一个远端用户加入频道并发布音视频轨道时:
监听 client.on(“user-published”) 事件。当 SDK 触发该事件时,在这个事件回调函数的参数中你可以获取远端用户 AgoraRTCRemoteUser 对象 。
调用 subscribe 方法订阅远端用户 AgoraRTCRemoteUser 对象,获取远端用户的远端音频轨RemoteAudioTrack 和远端视频轨道 RemoteVideoTrack 对象。
调用 play 方法播放远端音视频轨道。
2.Token鉴权
Token申请总共分为 9 个步骤:
1.客户端根据需要,向 app 服务端申请 Token
2.App 服务端生成并返回 Token
3.客户端以 UID、频道名以及获取到的 Token 加入频道
4.声网平台读取该 Token 中包含的信息,并进行校验。调用 joinChannel 方法,使用 Token、用户 ID 和频道名加入频道。用户 ID 和频道名必须和用于生成 Token 的用户 ID 和频道名一致。
5.客户端收到加入频道成功回调,并获取用户 UID
6.Token 最大有效期为 24 小时。当即将过期时,客户端会收到 Token 即将过期的回调
7.此时,如果客户端需要继续进行音视频互动,需要申请新的 Token
8.App 服务端生成并返回 Token
9.客户端更新 Token
问题
(1) AgoraRTCError CAN_NOT_GET_GATEWAY_SERVER: flag: 4096, message: AgoraRTCError CAN_NOT_GET_GATEWAY_SERVER: dynamic key or token timeout
这个多半是token的问题,token过期了会提示这个错误。
3.Java生成token
到官方给的代码仓库中,找到java部分代码,然后把 main/java/io/agora/media 文件夹拷贝到自己的项目中。然后编写下面的代码,通过 RtcTokenBuilder2 生成token。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @RequestMapping("/token") public HttpResultResponse token(@RequestParam(value = "uid")Integer uid, @RequestParam(value = "channelName")String channelName, @RequestParam(value = "role",defaultValue = "2")Integer role){ String result=""; Object agoraToken = RedisOpsUtils.get("agoraToken"); if(agoraToken!=null){ String agoraTokenStr=String.valueOf(agoraToken); result=agoraTokenStr; }else { RtcTokenBuilder2 tokenBuilder2=new RtcTokenBuilder2(); Role roleBuild=Role.ROLE_PUBLISHER; if(role==2){ roleBuild=Role.ROLE_SUBSCRIBER; } result =tokenBuilder2.buildTokenWithUid(appId, appCertificate, channelName, uid, roleBuild , tokenExpirationInSeconds, privilegeExpirationInSeconds); RedisOpsUtils.setWithExpire("agoraToken",result,tokenExpirationInSeconds); }
return HttpResultResponse.success(result); }
|
4.Web集成
(1)安装sdk
1
| pnpm add agora-rtc-sdk-ng
|
(2)初始化直播能力,监听直播频道
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| let agoraPara={ appid:"xxx", channel:"xxx", token:"xxx" } let agoraClient = AgoraRTC.createClient({ mode: 'live', codec: 'vp8' }) agoraClient.setClientRole('audience', { level: 2 }) console.log(agoraClient.connectionState) if (agoraClient.connectionState === 'DISCONNECTED') { agoraClient.join(agoraPara.appid, agoraPara.channel, agoraPara.token) }
agoraClient.on('user-joined', async (user) => { console.log('user[' + user.uid + '] join') }) agoraClient.on('user-published', async (user, mediaType) => { console.log("订阅") await agoraClient.subscribe(user, mediaType) if (mediaType === 'video') { console.log('subscribe success') const remoteVideoTrack = user.videoTrack remoteVideoTrack.play(document.getElementById('drone-1')) } }) agoraClient.on('user-unpublished', async (user) => { console.log('unpublish live:', user) }) agoraClient.on('exception', async (e) => { console.log(e) })
|
(3) 开启
这个地方其实使用直播发布就可以搞定了,因为测试的时候使用了大疆无人机的直播,这个暂时还没有时间进行摘录。
(4) 退出直播
5.视频截屏
云端截图不是截了一张图,而是截了很多张图,只有调用截图之后,然后直接关闭截图,这样才能停止截图。云端录制生成的截图文件的命名规则为 __uid_s___uid_e_video.jpg
(1)启动云端录制
在项目管理,配置里面,打开云端录制功能。
(2)配置Restful API
在用户管理里面,找到 Restful API,然后创建一个新的 restappid 和restappscret
(3)请求资源id
通过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
private String getResourceId(Integer uid,String cname){ String resourceId="";
JSONObject bodyJSON=new JSONObject(); bodyJSON.put("cname",cname); bodyJSON.put("uid",String.valueOf(uid));
JSONObject clientRequest=new JSONObject(); clientRequest.put("resourceExpiredHour",24); clientRequest.put("scene",0); bodyJSON.put("clientRequest",clientRequest);
log.info("resourceid send:"+bodyJSON.toJSONString());
String functionUrl="https://api.sd-rtn.com/v1/apps/"+appId+"/cloud_recording/acquire"; log.info(functionUrl);
HttpRequest httpRequest= HttpRequest.post(functionUrl); String Authorization=getResultApiAuthorization(); HttpResponse response= httpRequest .header("Authorization",Authorization) .header("Content-Type","application/json") .body(bodyJSON.toJSONString()) .execute();
Integer resultStatus=response.getStatus(); String resultBody=response.body(); log.info("resourceid result:"+resultBody); if(resultStatus==200){ JSONObject resultObj=JSONObject.parseObject(resultBody); resourceId=resultObj.getString("resourceId"); } return resourceId; }
|
(4)调用接口进行截图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
|
@RequestMapping("/screenshot/start") private HttpResultResponse screenshotStart(@RequestParam(value = "uid")Integer uid, @RequestParam(value = "cname")String cname){
JSONObject bodyJSON=new JSONObject(); bodyJSON.put("cname",cname); bodyJSON.put("uid",String.valueOf(uid));
JSONObject clientRequest=new JSONObject(); String token=getToken(uid,cname,2); clientRequest.put("token",token);
JSONObject recordingConfig=new JSONObject(); recordingConfig.put("channelType",1); recordingConfig.put("subscribeUidGroup",0); clientRequest.put("recordingConfig",recordingConfig);
JSONObject transcodeOptions=new JSONObject(); JSONObject transConfig=new JSONObject(); transConfig.put("transMode","audioMix"); transcodeOptions.put("transConfig",transConfig);
JSONObject snapshotConfig=new JSONObject(); snapshotConfig.put("captureInterval",5); JSONArray fileType=new JSONArray(); fileType.add("jpg"); snapshotConfig.put("fileType",fileType); clientRequest.put("snapshotConfig",snapshotConfig);
JSONObject storageConfig=new JSONObject(); storageConfig.put("vendor",2); storageConfig.put("region",0); storageConfig.put("bucket",bucket); storageConfig.put("accessKey",accessKey); storageConfig.put("secretKey",secretKey); JSONArray fileNamePrefix=new JSONArray(); fileNamePrefix.add("snapshot"); storageConfig.put("fileNamePrefix",fileNamePrefix); clientRequest.put("storageConfig",storageConfig);
bodyJSON.put("clientRequest",clientRequest);
String bodyStr=bodyJSON.toJSONString(); log.info("resourceid send:"+bodyStr);
String resourceid=getResourceId(uid,cname); String functionUrl="https://api.sd-rtn.com/v1/apps/"+appId+"/cloud_recording/resourceid/"+resourceid+"/mode" + "/individual/start"; log.info(functionUrl);
HttpRequest httpRequest= HttpRequest.post(functionUrl); String Authorization=getResultApiAuthorization(); HttpResponse response= httpRequest .header("Authorization",Authorization) .header("Content-Type","application/json;charset=utf-8 ") .body(bodyStr) .execute();
Integer resultStatus=response.getStatus(); String resultStr=response.body(); log.info("resourceid result:"+resultStr); if(resultStatus==200){ JSONObject resultObj=JSONObject.parseObject(resultStr); String result_cname=resultObj.getString("cname"); String result_uid=resultObj.getString("uid"); String result_resourceId=resultObj.getString("resourceId"); String result_sid=resultObj.getString("sid"); Object screenshot=RedisOpsUtils.get("screenshot"); JSONObject screenObj=new JSONObject(); if(screenshot!=null){ screenObj=(JSONObject)screenshot; } screenObj.put(result_sid,resultObj); RedisOpsUtils.setWithExpire("screenshot",screenObj,20*60);
return screenshotStop(result_uid,result_cname,result_resourceId,result_sid); } return HttpResultResponse.error(); }
|
(5)停止截图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
|
@RequestMapping("/screenshot/stop") private HttpResultResponse screenshotStop( @RequestParam(value = "uid")String uid, @RequestParam(value = "cname")String cname, @RequestParam(value = "resourceId")String resourceid, @RequestParam(value = "sid")String sid){
JSONObject bodyJSON=new JSONObject(); bodyJSON.put("cname",cname); bodyJSON.put("uid",uid);
JSONObject clientRequest=new JSONObject(); clientRequest.put("async_stop",false);
bodyJSON.put("clientRequest",clientRequest);
log.info("screenshotStart send:"+bodyJSON.toJSONString());
String functionUrl="https://api.sd-rtn.com/v1/apps/"+appId+"/cloud_recording/resourceid/"+resourceid+"/sid/" +sid+"/mode" + "/individual/stop"; log.info(functionUrl);
HttpRequest httpRequest= HttpRequest.post(functionUrl); String Authorization=getResultApiAuthorization(); HttpResponse response= httpRequest .header("Authorization",Authorization) .header("Content-Type","application/json;charset=utf-8 ") .body(bodyJSON.toJSONString()) .execute();
Integer resultStatus=response.getStatus(); String resultStr=response.body(); log.info("resourceid result:"+resultStr); if(resultStatus==200){
HttpResultResponse.success("停止截图"); }
return HttpResultResponse.error(); }
|
问题
(1) response detail error:2,errMsg:post method api body check failed!
【解决方法】
尝试查看那个uid是不是字符串类型的
(2) INVALID_OPERATION
非法操作,通常是因为在当前状态不能进行该操作。确认操作的先后顺序,比如发布前请确认已经加入频道。