WebRTC Video Chat

作者:追风剑情 发布于:2023-12-13 11:28 分类:Unity3d

WebRTC Video Chat

Unity跨平台视频通话插件WebRtcVideoChat,可实现PC、Android、IOS跨平台的视频通话,可语音、视频以及发送文字。
[百度网盘] WebRTC Video Chat 0.9854.unitypackage 提取码 ijys

[GitHub] WebRTC Signaling Server

如果想生成APK测试demo,需要将所有场景加入列表
111111.png

注意:在Android平台上创建两个网络对象会报错 Channel is unrecoverably broken and will be disposed! 导致闪退。

注意:4G/5G网络无法建立P2P

视频画面需要逆时针旋转90度再显示,下面是利用Shader旋转uv

fixed4 frag(v2f i) : SV_Target
{
	//逆时针旋转90度
	fixed2 uv = fixed2(i.uv.y, i.uv.x);
	fixed4 col = tex2D(_MainTex, uv);
	return col;
}

YUV与RGB相互转换公式


WebRTC辅助类

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Text;
  5. using UnityEngine;
  6. using Byn.Awrtc;
  7. using Byn.Awrtc.Unity;
  8. /// <summary>
  9. /// WebRtcVideoChat 辅助类
  10. /// 同一个网络对象上只能创建一个房间,但可以加入多个房间
  11. /// 注意:Android平台上不能同时创建多个网络对象(Network),会报错闪退。
  12. /// </summary>
  13. public sealed class WebRTCHelper
  14. {
  15. #region 变量
  16. //信令服务器地址 (这是官方的测试地址)
  17. public static string uSignalingUrl = "ws://signaling.because-why-not.com/chatapp";
  18. //信令服务器地址,基于WebGL的浏览器用这个地址 (这是官方的测试地址)
  19. public static string uSecureSignalingUrl = "wss://signaling.because-why-not.com/chatapp";
  20. //true: 强制使用 uSecureSignalingUrl
  21. public static bool uForceSecureSignaling = false;
  22.  
  23. //NAT穿透服务器地址, Stun server。(这是官方的测试地址)
  24. public static readonly string stunUrl = "stun:stun.because-why-not.com:443";
  25. //NAT穿透服务器地址, Turn server。(这是官方的测试地址)
  26. public static readonly string turnUrl = "turn:turn.because-why-not.com:443";
  27. //Turn server user
  28. public static readonly string turnUser = "testuser140";
  29. //Turn server password
  30. public static readonly string turnPass = "pass140";
  31. //NAT穿透服务器地址 (Turn server 或 Stun server)
  32. public static string uIceServer = turnUrl;
  33. //备用服务器
  34. public static string uIceServer2 = "stun:stun.l.google.com:19302";
  35. public static string uIceServerUser = turnUser;
  36. public static string uIceServerPassword = turnPass;
  37. //可以是 native webrtc 或 browser webrtc
  38. //BasicNetwork用于收发自定义数据
  39. private static IBasicNetwork mBasicNetwork = null;
  40. //谁开房间,谁就作为服务器方
  41. private static bool mIsServer = false;
  42. //房间名允许的最长字符个数,不要使用中文
  43. private const int MAX_CODE_LENGTH = 256;
  44. //连接ID列表
  45. private static List<ConnectionId> mConnections = new List<ConnectionId>();
  46. //用于音/视频聊天的对象
  47. private static ICall mCall;
  48. //true: 每次本地视频设备创建新帧时,FrameUpdateEventArgs都会更新
  49. private static bool mLocalFrameEvents = true;
  50. private static ConnectionId mRemoteUserId;
  51. private static MediaConfig mMediaConfig;
  52. private static MediaConfig mMediaConfigInUse;
  53.  
  54. public static Action<string> OnTextHandler;
  55. public static Action<string> OnLogHandler;
  56. public static Action<FrameUpdateEventArgs> OnVideoFrameUpdate;
  57.  
  58. //信令协议
  59. public static string SignalingProtocol
  60. {
  61. get
  62. {
  63.  
  64. string protocol = "ws";
  65. if (Application.platform == RuntimePlatform.WebGLPlayer)
  66. {
  67. protocol = "wss";
  68. }
  69. return protocol;
  70. }
  71. }
  72. #endregion
  73.  
  74. #region 初始化
  75. /// <summary>
  76. /// 初始WebRTC
  77. /// </summary>
  78. /// <param name="initBasicNetwork">初始化基本网络对象用于:收发消息</param>
  79. /// <param name="initCall">初始化Call对象用于:收发消息、音/视频通话</param>
  80. public static void Init(bool initBasicNetwork=true, bool initCall=true)
  81. {
  82. InitFactory();
  83. if (initBasicNetwork)
  84. InitBasicNetwork();
  85. if (initCall)
  86. InitCall();
  87. }
  88. #endregion
  89.  
  90. #region Factory
  91. //初始化 WebRtc Network Factory
  92. private static void InitFactory()
  93. {
  94. Debug.Log("Initializing webrtc factory");
  95. UnityCallFactory.EnsureInit(OnCallFactoryReady, OnCallFactoryFailed);
  96. }
  97.  
  98. private static void OnCallFactoryReady()
  99. {
  100. Debug.Log("WebRtcNetworkFactory created");
  101. UnityCallFactory.Instance.RequestLogLevel(UnityCallFactory.LogLevel.Info);
  102. }
  103.  
  104. private static void OnCallFactoryFailed(string error)
  105. {
  106. string fullErrorMsg = "The " + typeof(UnityCallFactory).Name + " failed to initialize with following error: " + error;
  107. Debug.LogError(fullErrorMsg);
  108. }
  109. #endregion
  110.  
  111. #region 基本网络
  112. //初始化 WebRTC BasicNetwork
  113. private static void InitBasicNetwork()
  114. {
  115. if (mBasicNetwork != null)
  116. return;
  117.  
  118. Debug.Log("Initializing webrtc network");
  119. string signalingUrl = uSignalingUrl;
  120.  
  121. if (Application.platform == RuntimePlatform.WebGLPlayer || uForceSecureSignaling)
  122. {
  123. signalingUrl = uSecureSignalingUrl;
  124. }
  125.  
  126. List<IceServer> iceServers = new List<IceServer>();
  127. if (string.IsNullOrEmpty(uIceServer) == false)
  128. iceServers.Add(new IceServer(uIceServer, uIceServerUser, uIceServerPassword));
  129.  
  130. if (string.IsNullOrEmpty(uIceServer2) == false)
  131. iceServers.Add(new IceServer(uIceServer2));
  132.  
  133. if (string.IsNullOrEmpty(signalingUrl))
  134. {
  135. throw new InvalidOperationException("set signaling url is null or empty");
  136. }
  137.  
  138. mBasicNetwork = UnityCallFactory.Instance.CreateBasicNetwork(signalingUrl, iceServers.ToArray());
  139. if (mBasicNetwork != null)
  140. {
  141. Debug.Log("WebRTCNetwork created");
  142. }
  143. else
  144. {
  145. Debug.Log("Failed to access webrtc ");
  146. }
  147. }
  148.  
  149. /// <summary>
  150. /// 断开某个连接
  151. /// </summary>
  152. /// <param name="id">连接ID</param>
  153. public static void Disconnect(ConnectionId id)
  154. {
  155. if (mBasicNetwork == null)
  156. return;
  157. mBasicNetwork.Disconnect(id);
  158. }
  159.  
  160. /// <summary>
  161. /// 断开所有连接
  162. /// </summary>
  163. public static void DisconnectAll()
  164. {
  165. if (mBasicNetwork == null)
  166. return;
  167. mBasicNetwork.Dispose();
  168. }
  169.  
  170. //检查网络事件,需在FixedUpdate()中调用此方法
  171. public static void HandleNetwork()
  172. {
  173. if (mBasicNetwork == null)
  174. return;
  175. //从底层读取数据
  176. mBasicNetwork.Update();
  177. //处理自上次更新以来发生的所有新事件
  178. NetworkEvent evt;
  179. //检查每个事件
  180. while (mBasicNetwork != null && mBasicNetwork.Dequeue(out evt))
  181. {
  182. Debug.LogFormat("Handler Network Event: {0}", evt);
  183. switch (evt.Type)
  184. {
  185. //服务器初始化完成
  186. case NetEventType.ServerInitialized:
  187. mIsServer = true;
  188. string address = evt.Info;
  189. Debug.Log("Server started. Address: " + address);
  190. break;
  191. //启动服务器失败,可能是信令服务器关闭了
  192. case NetEventType.ServerInitFailed:
  193. mIsServer = false;
  194. Debug.Log("Server start failed." + evt.Info);
  195. Dispose();
  196. break;
  197. //服务器关闭
  198. case NetEventType.ServerClosed:
  199. mIsServer = false;
  200. Debug.LogFormat("Server closed. {0}", evt.Info);
  201. break;
  202. //用户运行客户端并连接到服务器
  203. //用户运行该服务器,并连接了一个新的客户端
  204. case NetEventType.NewConnection:
  205. mConnections.Add(evt.ConnectionId);
  206. Debug.Log("New local connection! ID: " + evt.ConnectionId);
  207. //如果是服务器,请向所有人发送公告,并使用本地Id作为用户名
  208. if (mIsServer)
  209. {
  210. //用户运行一个服务器, 向大家宣布新的连接.
  211. //使用服务器端连接ID作为标识
  212. string msg = "New user " + evt.ConnectionId + " joined the room.";
  213. Debug.Log(msg);
  214. SendString(msg);
  215. }
  216. break;
  217. //服务器收到失败连接
  218. //与服务器建立连接失败
  219. case NetEventType.ConnectionFailed:
  220. if (mIsServer)
  221. {
  222. //收到一个失败连接
  223. //原因: 收到了连接请求,但信令传送失败。
  224. //1.防火墙阻止了直接连接,但允许信号启动连接过程(可能是本地防火墙,也可能是远端防火墙)
  225. //2.STUN/TURN 服务器未启动
  226. //3.用户在连接完全建立之前就切断了连接
  227. Debug.Log("An incoming connection failed.");
  228. }
  229. else
  230. {
  231. //与服务器建立连接失败
  232. Debug.Log("Connection failed. " + evt.Info);
  233. Dispose();
  234. }
  235. break;
  236. //连接中断
  237. case NetEventType.Disconnected:
  238. mConnections.Remove(evt.ConnectionId);
  239. Debug.Log("Local Connection ID " + evt.ConnectionId + " disconnected");
  240. if (mIsServer)
  241. {
  242. string userLeftMsg = "User " + evt.ConnectionId + " left the room.";
  243. Debug.Log(userLeftMsg);
  244. //通过其他客户端,有玩家离线
  245. if (mConnections.Count > 0)
  246. {
  247. SendString(userLeftMsg);
  248. }
  249. }
  250. else
  251. {
  252. Dispose();
  253. }
  254. break;
  255. //收到可靠消息
  256. case NetEventType.ReliableMessageReceived:
  257. //收到不可靠消息
  258. case NetEventType.UnreliableMessageReceived:
  259. HandleIncommingMessage(ref evt);
  260. break;
  261. }
  262. }
  263. //如果更新期间网络未中断,请刷新消息以完成此更新
  264. if (mBasicNetwork != null)
  265. mBasicNetwork.Flush();
  266. }
  267.  
  268. //处理收到的消息
  269. private static void HandleIncommingMessage(ref NetworkEvent evt)
  270. {
  271. MessageDataBuffer buffer = (MessageDataBuffer)evt.MessageData;
  272. string msg = Encoding.UTF8.GetString(buffer.Buffer, 0, buffer.ContentLength);
  273. OnTextHandler?.Invoke(msg);
  274. //如果服务器将消息转发给包括发件人在内的所有其他人
  275. if (mIsServer)
  276. {
  277. //我们使用服务器端连接id来标识客户端
  278. string idAndMessage = evt.ConnectionId + ":" + msg;
  279. Debug.Log(idAndMessage);
  280. //SendString(idAndMessage);
  281. }
  282. else
  283. {
  284. //客户端收到服务器发来的消息
  285. Debug.Log(msg);
  286. }
  287. //返回缓冲区,以便网络可以重用它
  288. buffer.Dispose();
  289. }
  290.  
  291. //发送消息
  292. public static void SendString(string msg, bool reliable = true)
  293. {
  294. if (mBasicNetwork != null)
  295. {
  296. byte[] msgData = Encoding.UTF8.GetBytes(msg);
  297. foreach (ConnectionId id in mConnections)
  298. {
  299. mBasicNetwork.SendData(id, msgData, 0, msgData.Length, reliable);
  300. }
  301. }
  302. else if (mCall != null)
  303. {
  304. mCall.Send(msg, reliable);
  305. }
  306. }
  307.  
  308. /// <summary>
  309. /// 创建房间 (仅支持文本聊天)
  310. /// </summary>
  311. /// <param name="roomName">房间名称(不能超过256个字符)</param>
  312. public static void OpenChatRoom(string roomName)
  313. {
  314. InitBasicNetwork();
  315. roomName = EnsureLength(roomName);
  316. //如果该地址正在使用中,则底层系统将返回服务器连接失败
  317. mBasicNetwork.StartServer(roomName);
  318. Debug.Log("Open Room " + roomName);
  319. }
  320.  
  321. /// <summary>
  322. /// 关闭房间
  323. /// 房间关闭后,将不再接受新用户的连接,但不影响现有已建立连接的用户
  324. /// 当房间达到预设的最大人数后,可以调用此方法
  325. /// </summary>
  326. public static void CloseChatRoom()
  327. {
  328. if (mBasicNetwork == null)
  329. return;
  330. mBasicNetwork.StopServer();
  331. Debug.Log("Close Room");
  332. }
  333.  
  334. /// <summary>
  335. /// 加入房间,可以同时加入不同的房间
  336. /// </summary>
  337. /// <param name="roomName">房间名称(不能超过256个字符)</param>
  338. public static void JoinChatRoom(string roomName)
  339. {
  340. InitBasicNetwork();
  341. roomName = EnsureLength(roomName);
  342. mBasicNetwork.Connect(roomName);
  343. Debug.Log("Join Room " + roomName + " ...");
  344. }
  345.  
  346. //确保房间名称长度不超过256
  347. private static string EnsureLength(string roomName)
  348. {
  349. if (roomName.Length > MAX_CODE_LENGTH)
  350. {
  351. roomName = roomName.Substring(0, MAX_CODE_LENGTH);
  352. }
  353. return roomName;
  354. }
  355. #endregion
  356.  
  357. #region 视频聊天
  358. //初始化Call, 音/视频聊天需要调用这个方法
  359. private static void InitCall()
  360. {
  361. if (mCall != null)
  362. return;
  363. NetworkConfig config = CreateNetworkConfig();
  364. mCall = CreateCall(config);
  365. if (mCall == null)
  366. {
  367. Debug.Log("Failed to create the call");
  368. return;
  369. }
  370. mCall.LocalFrameEvents = mLocalFrameEvents;
  371. mCall.CallEvent += OnCallEvent;
  372.  
  373. if (mMediaConfig == null)
  374. mMediaConfig = CreateMediaConfig();
  375. mMediaConfigInUse = mMediaConfig.DeepClone();
  376.  
  377. //如果没有配置摄像头设备名称,则自动设置为前置摄像头
  378. if (mMediaConfigInUse.Video && string.IsNullOrEmpty(mMediaConfigInUse.VideoDeviceName))
  379. {
  380. string[] devices = UnityCallFactory.Instance.GetVideoDevices();
  381. if (devices == null || devices.Length == 0)
  382. {
  383. Debug.Log("no device found or no device information available");
  384. }
  385. else
  386. {
  387. foreach (string s in devices)
  388. Debug.Log("device found: " + s + " IsFrontFacing: " + UnityCallFactory.Instance.IsFrontFacing(s));
  389. }
  390. mMediaConfigInUse.VideoDeviceName = UnityCallFactory.Instance.GetDefaultVideoDevice();
  391. }
  392.  
  393. Debug.Log("Configure call using MediaConfig: " + mMediaConfigInUse);
  394. //设置配置
  395. mCall.Configure(mMediaConfigInUse);
  396. }
  397.  
  398. //创建网络配置
  399. private static NetworkConfig CreateNetworkConfig()
  400. {
  401. NetworkConfig netConfig = new NetworkConfig();
  402. if (string.IsNullOrEmpty(uIceServer) == false)
  403. netConfig.IceServers.Add(new IceServer(uIceServer, uIceServerUser, uIceServerPassword));
  404. if (string.IsNullOrEmpty(uIceServer2) == false)
  405. netConfig.IceServers.Add(new IceServer(uIceServer2));
  406.  
  407. if (Application.platform == RuntimePlatform.WebGLPlayer || uForceSecureSignaling)
  408. {
  409. netConfig.SignalingUrl = uSecureSignalingUrl;
  410. }
  411. else
  412. {
  413. netConfig.SignalingUrl = uSignalingUrl;
  414. }
  415.  
  416. if (netConfig.SignalingUrl == "")
  417. {
  418. throw new InvalidOperationException("set signaling url is empty");
  419. }
  420. return netConfig;
  421. }
  422.  
  423. //创建Call对象
  424. private static ICall CreateCall(NetworkConfig netConfig)
  425. {
  426. return UnityCallFactory.Instance.Create(netConfig);
  427. }
  428.  
  429. //创建媒体配置
  430. public static MediaConfig CreateMediaConfig()
  431. {
  432. MediaConfig mediaConfig = new MediaConfig();
  433. //是否消除回音 (native only)
  434. bool useEchoCancellation = true;
  435. if (useEchoCancellation)
  436. {
  437. #if (!UNITY_WEBGL && !UNITY_WSA)
  438. var nativeConfig = new Byn.Awrtc.Native.NativeMediaConfig();
  439. nativeConfig.AudioOptions.echo_cancellation = true;
  440.  
  441. mediaConfig = nativeConfig;
  442. #endif
  443. }
  444.  
  445. #if UNITY_WSA && !UNITY_EDITOR
  446. var uwpConfig = new Byn.Awrtc.Uwp.UwpMediaConfig();
  447. uwpConfig.Mrc = true;
  448. //uwpConfig.ProcessLocalFrames = false;
  449. //uwpConfig.DefaultCodec = "H264";
  450. mediaConfig = uwpConfig;
  451. Debug.Log("Using uwp specific media config: " + mediaConfig);
  452. #endif
  453. //开启音频
  454. mediaConfig.Audio = true;
  455. //开启视频
  456. mediaConfig.Video = true;
  457. mediaConfig.VideoDeviceName = null;
  458. //图像格式
  459. mediaConfig.Format = FramePixelFormat.ABGR;
  460. //最小分辨率
  461. mediaConfig.MinWidth = 160;
  462. mediaConfig.MinHeight = 120;
  463. //分辨率越大编码/解码越慢
  464. mediaConfig.MaxWidth = 1920 * 2;
  465. mediaConfig.MaxHeight = 1080 * 2;
  466. //WebRTC会优先考虑IdealWidth、IdealHeight、IdealFrameRate
  467. //理想分辨率、帧率
  468. mediaConfig.IdealWidth = 160;
  469. mediaConfig.IdealHeight = 120;
  470. mediaConfig.IdealFrameRate = 30;
  471.  
  472. return mediaConfig;
  473. }
  474.  
  475. //创建视频聊天房间
  476. public static void OpenCallRoom(string roomName)
  477. {
  478. InitCall();
  479. mCall.Listen(roomName);
  480. }
  481.  
  482. //加入视频聊天房间
  483. public static void JoinCallRoom(string roomName)
  484. {
  485. InitCall();
  486. //不要在会议模式下使用Call接口
  487. mCall.Call(roomName);
  488. }
  489.  
  490. //是否设置成了静音
  491. public static bool IsMute()
  492. {
  493. if (mCall == null)
  494. return true;
  495. return mCall.IsMute();
  496. }
  497.  
  498. //设置静音
  499. public static void SetMute(bool val)
  500. {
  501. if (mCall == null)
  502. return;
  503. mCall.SetMute(val);
  504. }
  505.  
  506. //检查手机扬声器是否已打开。仅适用于移动本地平台
  507. public static bool GetLoudspeakerStatus()
  508. {
  509. if (mCall != null)
  510. {
  511. return UnityCallFactory.Instance.GetLoudspeakerStatus();
  512. }
  513. return false;
  514. }
  515.  
  516. //打开/关闭手机扬声器。仅适用于移动本地平台
  517. public static void SetLoudspeakerStatus(bool state)
  518. {
  519. if (mCall != null)
  520. {
  521. UnityCallFactory.Instance.SetLoudspeakerStatus(state);
  522. }
  523. }
  524.  
  525. //设置是否显示本地视频
  526. //如果不需要显示本地摄像头画面,可以设置成false
  527. public static void SetShowLocalVideo(bool showLocalVideo)
  528. {
  529. mLocalFrameEvents = showLocalVideo;
  530. }
  531.  
  532. //视频聊天驱动方法,需要每帧调用
  533. public static void CallUpdate()
  534. {
  535. if (mCall == null)
  536. return;
  537. //将底层事件转发到Unity线程中
  538. mCall.Update();
  539. }
  540.  
  541. //处理Call事件
  542. private static void OnCallEvent(object sender, CallEventArgs e)
  543. {
  544. Debug.Log(e.Type);
  545. switch (e.Type)
  546. {
  547. //呼出电话成功或来电到达
  548. case CallEventType.CallAccepted:
  549. {
  550. //HasAudioTrack() 判断对方麦克风是否可用
  551. //HasVideoTrack() 判断对方摄像头是否可用
  552. mRemoteUserId = ((CallAcceptedEventArgs)e).ConnectionId;
  553. string msg = "New connection with id: " + mRemoteUserId
  554. + " audio:" + mCall.HasAudioTrack(mRemoteUserId)
  555. + " video:" + mCall.HasVideoTrack(mRemoteUserId);
  556. Debug.Log(msg);
  557. OnLogHandler?.Invoke(msg);
  558. mConnections.Add(mRemoteUserId);
  559. }
  560. break;
  561. //通话结束或其中一个用户挂断电话
  562. case CallEventType.CallEnded:
  563. CleanupCall();
  564. break;
  565. //来电失败
  566. case CallEventType.ListeningFailed:
  567. {
  568. ErrorEventArgs args = e as ErrorEventArgs;
  569. string msg = "ListeningFailed: " + args.Info;
  570. Debug.Log(msg);
  571. OnLogHandler?.Invoke(msg);
  572. }
  573. break;
  574. //连接房间失败
  575. case CallEventType.ConnectionFailed:
  576. {
  577. ErrorEventArgs args = e as ErrorEventArgs;
  578. string msg = "ConnectionFailed: " + args.Info;
  579. Debug.Log(msg);
  580. OnLogHandler?.Invoke(msg);
  581. CleanupCall();
  582. }
  583. break;
  584. //当前系统不支持摄像头或麦克风,或不支持请求的分辨率,或权限不足
  585. case CallEventType.ConfigurationFailed:
  586. {
  587. ErrorEventArgs args = e as ErrorEventArgs;
  588. string msg = "ConfigurationFailed: " + args.Info;
  589. Debug.Log(msg);
  590. OnLogHandler?.Invoke(msg);
  591. CleanupCall();
  592. }
  593. break;
  594. //收到一个新的视频帧(来自于本地摄像头或网络)
  595. case CallEventType.FrameUpdate:
  596. if (e is FrameUpdateEventArgs)
  597. {
  598. var evt = e as FrameUpdateEventArgs;
  599. OnVideoFrameUpdate?.Invoke(evt);
  600. }
  601. break;
  602. //收到文本消息
  603. case CallEventType.Message:
  604. {
  605. MessageEventArgs args = e as MessageEventArgs;
  606. Debug.Log(args.Content);
  607. OnTextHandler?.Invoke(args.Content);
  608. }
  609. break;
  610. //接到来电
  611. case CallEventType.WaitForIncomingCall:
  612. {
  613. //聊天应用程序将等待另一个应用程序通过相同的字符串连接
  614. WaitForIncomingCallEventArgs args = e as WaitForIncomingCallEventArgs;
  615. string msg = "WaitForIncomingCall: " + args.Address;
  616. Debug.Log(msg);
  617. OnLogHandler?.Invoke(msg);
  618. }
  619. break;
  620. }
  621. }
  622.  
  623. //清理Call
  624. public static void CleanupCall()
  625. {
  626. if (mCall == null)
  627. return;
  628. mCall.CallEvent -= OnCallEvent;
  629. mCall.Dispose();
  630. mCall = null;
  631. mRemoteUserId = ConnectionId.INVALID;
  632. mConnections.Clear();
  633. GC.Collect();
  634. GC.WaitForPendingFinalizers();
  635. Debug.Log("Call destroyed");
  636. }
  637.  
  638. //请求权限
  639. public static IEnumerator RequestPermissions(bool audio = true, bool video = true)
  640. {
  641. if (audio)
  642. {
  643. yield return RequestAudioPermission();
  644. }
  645. if (video)
  646. {
  647. yield return RequestVideoPermission();
  648. }
  649.  
  650. yield return null;
  651.  
  652. }
  653.  
  654. //请求麦克风权限
  655. public static IEnumerator RequestAudioPermission()
  656. {
  657. #if UNITY_ANDROID && UNITY_2018_3_OR_NEWER
  658. if (!HasAudioPermission())
  659. {
  660. Debug.Log("Requesting microphone permissions");
  661. UnityEngine.Android.Permission.RequestUserPermission(UnityEngine.Android.Permission.Microphone);
  662. //同一帧中连续调用两次请求会导致请求被忽略
  663. //等待用户按下 "允许" 或 "拒绝"
  664. yield return new WaitForSeconds(0.1f);
  665. //极少数情况下即使用户选择"允许",也可能返回false
  666. Debug.Log("microphone permission: " + HasAudioPermission());
  667. }
  668. #endif
  669. yield return null;
  670. }
  671.  
  672. //请求摄像头权限
  673. public static IEnumerator RequestVideoPermission()
  674. {
  675. #if UNITY_ANDROID && UNITY_2018_3_OR_NEWER
  676. if (!HasVideoPermission())
  677. {
  678. Debug.Log("Requesting camera permissions");
  679. UnityEngine.Android.Permission.RequestUserPermission(UnityEngine.Android.Permission.Camera);
  680. yield return new WaitForSeconds(0.1f);
  681. Debug.Log("camera permission: " + HasVideoPermission());
  682. }
  683. #endif
  684. yield return null;
  685. }
  686.  
  687. //是否有使用麦克风的权限
  688. public static bool HasAudioPermission()
  689. {
  690. #if UNITY_ANDROID && UNITY_2018_3_OR_NEWER
  691. if (Application.platform == RuntimePlatform.Android)
  692. {
  693. return UnityEngine.Android.Permission.HasUserAuthorizedPermission(UnityEngine.Android.Permission.Microphone);
  694. }
  695. #endif
  696. return true;
  697. }
  698.  
  699. //是否有使用摄像头的权限
  700. public static bool HasVideoPermission()
  701. {
  702. #if UNITY_ANDROID && UNITY_2018_3_OR_NEWER
  703. if (Application.platform == RuntimePlatform.Android)
  704. {
  705. return UnityEngine.Android.Permission.HasUserAuthorizedPermission(UnityEngine.Android.Permission.Camera);
  706. }
  707. #endif
  708. return true;
  709. }
  710. #endregion
  711.  
  712. #region 释放资源
  713. //释放
  714. public static void Dispose()
  715. {
  716. if (mBasicNetwork != null)
  717. {
  718. mBasicNetwork.Dispose();
  719. mBasicNetwork = null;
  720. }
  721. mConnections.Clear();
  722. mIsServer = false;
  723. CleanupCall();
  724. }
  725. #endregion
  726. }


视频通话测试

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. using Byn.Awrtc;
  6. using Byn.Awrtc.Unity;
  7. /// <summary>
  8. /// 视频通话测试
  9. /// </summary>
  10. public class CallTest : MonoBehaviour
  11. {
  12. [SerializeField]
  13. private InputField roomNameInput;
  14. [SerializeField]
  15. private InputField messageInput;
  16. [SerializeField]
  17. private Text msgText;
  18. [SerializeField]
  19. private Text logText;
  20. [SerializeField]
  21. private RawImage localVideoImage;
  22. private Texture2D localTexture;
  23. [SerializeField]
  24. private RawImage remoteVideoImage;
  25. private Texture2D remoteTexture;
  26.  
  27. void Start()
  28. {
  29. //请求权限
  30. StartCoroutine(WebRTCHelper.RequestPermissions());
  31. //初始化
  32. WebRTCHelper.Init(false, true);
  33. WebRTCHelper.OnTextHandler = OnTextHandler;
  34. WebRTCHelper.OnLogHandler = OnLogHandler;
  35. WebRTCHelper.OnVideoFrameUpdate = OnVideoFrameUpdate;
  36. }
  37.  
  38. private void OnDestroy()
  39. {
  40. WebRTCHelper.Dispose();
  41. }
  42.  
  43. void Update()
  44. {
  45. WebRTCHelper.CallUpdate();
  46. }
  47.  
  48. private void FixedUpdate()
  49. {
  50. WebRTCHelper.HandleNetwork();
  51. }
  52.  
  53. public void OnClickOpenChatRoom()
  54. {
  55. WebRTCHelper.OpenChatRoom(roomNameInput.text);
  56. }
  57.  
  58. public void OnClickJoinChatRoom()
  59. {
  60. WebRTCHelper.JoinChatRoom(roomNameInput.text);
  61. }
  62.  
  63. public void OnClickOpenCallRoom()
  64. {
  65. WebRTCHelper.OpenCallRoom(roomNameInput.text);
  66. }
  67.  
  68. public void OnClickJoinCallRoom()
  69. {
  70. WebRTCHelper.JoinCallRoom(roomNameInput.text);
  71. }
  72.  
  73. public void OnClickSend()
  74. {
  75. WebRTCHelper.SendString(messageInput.text);
  76. }
  77.  
  78. public void OnClickHungup()
  79. {
  80. WebRTCHelper.CleanupCall();
  81. }
  82.  
  83. //处理收到的文本消息
  84. private void OnTextHandler(string msg)
  85. {
  86. msgText.text = msg;
  87. }
  88.  
  89. //处理收到的错误消息
  90. private void OnLogHandler(string error)
  91. {
  92. logText.text = error;
  93. }
  94.  
  95. //处理收到的视频帧
  96. private void OnVideoFrameUpdate(FrameUpdateEventArgs e)
  97. {
  98. if (e.IsRemote)
  99. {
  100. UpdateRemoteTexture(e.Frame);
  101. }
  102. else
  103. {
  104. UpdateLocalTexture(e.Frame);
  105. }
  106. }
  107.  
  108. //更新本地视频画面
  109. private void UpdateLocalTexture(IFrame frame)
  110. {
  111. if (frame == null)
  112. return;
  113. //UnityMediaHelper.UpdateRawImage(localVideoImage, frame);
  114. if (localTexture == null)
  115. localTexture = new Texture2D(frame.Width, frame.Height, TextureFormat.RGBA32, false);
  116. UnityMediaHelper.UpdateTexture(frame, ref localTexture);
  117. if (localVideoImage.texture == null)
  118. localVideoImage.texture = localTexture;
  119. }
  120.  
  121. //更新远程视频画面
  122. private void UpdateRemoteTexture(IFrame frame)
  123. {
  124. if (frame == null)
  125. return;
  126. //UnityMediaHelper.UpdateRawImage(remoteVideoImage, frame);
  127. if (remoteTexture == null)
  128. remoteTexture = new Texture2D(frame.Width, frame.Height, TextureFormat.RGBA32, false);
  129. UnityMediaHelper.UpdateTexture(frame, ref remoteTexture);
  130. if (remoteVideoImage.texture == null)
  131. remoteVideoImage.texture = remoteTexture;
  132. }
  133. }

标签: Unity3d

Powered by emlog  蜀ICP备18021003号-1   sitemap

川公网安备 51019002001593号