PeerConnectionSample.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEngine;
  5. using Unity.WebRTC;
  6. using Unity.WebRTC.Samples;
  7. using UnityEngine.UI;
  8. using Button = UnityEngine.UI.Button;
  9. class PeerConnectionSample : MonoBehaviour
  10. {
  11. #pragma warning disable 0649
  12. [SerializeField] private Button startButton;
  13. [SerializeField] private Button callButton;
  14. [SerializeField] private Button restartButton;
  15. [SerializeField] private Button hangUpButton;
  16. [SerializeField] private Text localCandidateId;
  17. [SerializeField] private Text remoteCandidateId;
  18. [SerializeField] private Dropdown dropDownProtocol;
  19. [SerializeField] private Camera cam;
  20. [SerializeField] private RawImage sourceImage;
  21. [SerializeField] private RawImage receiveImage;
  22. [SerializeField] private Transform rotateObject;
  23. #pragma warning restore 0649
  24. enum ProtocolOption
  25. {
  26. Default,
  27. UDP,
  28. TCP
  29. }
  30. private RTCPeerConnection _pc1, _pc2;
  31. private List<RTCRtpSender> pc1Senders;
  32. private MediaStream videoStream, receiveStream;
  33. private DelegateOnIceConnectionChange pc1OnIceConnectionChange;
  34. private DelegateOnIceConnectionChange pc2OnIceConnectionChange;
  35. private DelegateOnIceCandidate pc1OnIceCandidate;
  36. private DelegateOnIceCandidate pc2OnIceCandidate;
  37. private DelegateOnTrack pc2Ontrack;
  38. private DelegateOnNegotiationNeeded pc1OnNegotiationNeeded;
  39. private bool videoUpdateStarted;
  40. private void Awake()
  41. {
  42. WebRTC.Initialize(WebRTCSettings.LimitTextureSize);
  43. startButton.onClick.AddListener(OnStart);
  44. callButton.onClick.AddListener(Call);
  45. restartButton.onClick.AddListener(RestartIce);
  46. hangUpButton.onClick.AddListener(HangUp);
  47. receiveStream = new MediaStream();
  48. }
  49. private void OnDestroy()
  50. {
  51. WebRTC.Dispose();
  52. }
  53. private void Start()
  54. {
  55. pc1Senders = new List<RTCRtpSender>();
  56. callButton.interactable = false;
  57. restartButton.interactable = false;
  58. hangUpButton.interactable = false;
  59. pc1OnIceConnectionChange = state => { OnIceConnectionChange(_pc1, state); };
  60. pc2OnIceConnectionChange = state => { OnIceConnectionChange(_pc2, state); };
  61. pc1OnIceCandidate = candidate => { OnIceCandidate(_pc1, candidate); };
  62. pc2OnIceCandidate = candidate => { OnIceCandidate(_pc2, candidate); };
  63. pc2Ontrack = e =>
  64. {
  65. receiveStream.AddTrack(e.Track);
  66. };
  67. pc1OnNegotiationNeeded = () => { StartCoroutine(PeerNegotiationNeeded(_pc1)); };
  68. receiveStream.OnAddTrack = e =>
  69. {
  70. if (e.Track is VideoStreamTrack track)
  71. {
  72. track.OnVideoReceived += tex =>
  73. {
  74. receiveImage.texture = tex;
  75. receiveImage.color = Color.white;
  76. };
  77. }
  78. };
  79. }
  80. private void OnStart()
  81. {
  82. startButton.interactable = false;
  83. callButton.interactable = true;
  84. if (videoStream == null)
  85. {
  86. videoStream = cam.CaptureStream(WebRTCSettings.StreamSize.x, WebRTCSettings.StreamSize.y);
  87. }
  88. sourceImage.texture = cam.targetTexture;
  89. sourceImage.color = Color.white;
  90. }
  91. private void Update()
  92. {
  93. if (rotateObject != null)
  94. {
  95. float t = Time.deltaTime;
  96. rotateObject.Rotate(100 * t, 200 * t, 300 * t);
  97. }
  98. }
  99. private static RTCConfiguration GetSelectedSdpSemantics()
  100. {
  101. RTCConfiguration config = default;
  102. config.iceServers = new[] {new RTCIceServer {urls = new[] {"stun:stun.l.google.com:19302"}}};
  103. return config;
  104. }
  105. private void OnIceConnectionChange(RTCPeerConnection pc, RTCIceConnectionState state)
  106. {
  107. Debug.Log($"{GetName(pc)} IceConnectionState: {state}");
  108. if (state == RTCIceConnectionState.Connected || state == RTCIceConnectionState.Completed)
  109. {
  110. StartCoroutine(CheckStats(pc));
  111. }
  112. }
  113. // Display the video codec that is actually used.
  114. IEnumerator CheckStats(RTCPeerConnection pc)
  115. {
  116. yield return new WaitForSeconds(0.1f);
  117. if (pc == null)
  118. yield break;
  119. var op = pc.GetStats();
  120. yield return op;
  121. if (op.IsError)
  122. {
  123. Debug.LogErrorFormat("RTCPeerConnection.GetStats failed: {0}", op.Error);
  124. yield break;
  125. }
  126. RTCStatsReport report = op.Value;
  127. RTCIceCandidatePairStats activeCandidatePairStats = null;
  128. RTCIceCandidateStats remoteCandidateStats = null;
  129. foreach (var transportStatus in report.Stats.Values.OfType<RTCTransportStats>())
  130. {
  131. if (report.Stats.TryGetValue(transportStatus.selectedCandidatePairId, out var tmp))
  132. {
  133. activeCandidatePairStats = tmp as RTCIceCandidatePairStats;
  134. }
  135. }
  136. if (activeCandidatePairStats == null || string.IsNullOrEmpty(activeCandidatePairStats.remoteCandidateId))
  137. {
  138. yield break;
  139. }
  140. foreach (var iceCandidateStatus in report.Stats.Values.OfType<RTCIceCandidateStats>())
  141. {
  142. if (iceCandidateStatus.Id == activeCandidatePairStats.remoteCandidateId)
  143. {
  144. remoteCandidateStats = iceCandidateStatus;
  145. }
  146. }
  147. if (remoteCandidateStats == null || string.IsNullOrEmpty(remoteCandidateStats.Id))
  148. {
  149. yield break;
  150. }
  151. Debug.Log($"{GetName(pc)} candidate stats Id:{remoteCandidateStats.Id}, Type:{remoteCandidateStats.candidateType}");
  152. var updateText = GetName(pc) == "pc1" ? localCandidateId : remoteCandidateId;
  153. updateText.text = remoteCandidateStats.Id;
  154. }
  155. IEnumerator PeerNegotiationNeeded(RTCPeerConnection pc)
  156. {
  157. var op = pc.CreateOffer();
  158. yield return op;
  159. if (!op.IsError)
  160. {
  161. if (pc.SignalingState != RTCSignalingState.Stable)
  162. {
  163. Debug.LogError($"{GetName(pc)} signaling state is not stable.");
  164. yield break;
  165. }
  166. yield return StartCoroutine(OnCreateOfferSuccess(pc, op.Desc));
  167. }
  168. else
  169. {
  170. OnCreateSessionDescriptionError(op.Error);
  171. }
  172. }
  173. private void AddTracks()
  174. {
  175. foreach (var track in videoStream.GetTracks())
  176. {
  177. pc1Senders.Add(_pc1.AddTrack(track, videoStream));
  178. }
  179. if (WebRTCSettings.UseVideoCodec != null)
  180. {
  181. var codecs = new[] {WebRTCSettings.UseVideoCodec};
  182. foreach (var transceiver in _pc1.GetTransceivers())
  183. {
  184. if (pc1Senders.Contains(transceiver.Sender))
  185. {
  186. transceiver.SetCodecPreferences(codecs);
  187. }
  188. }
  189. }
  190. if (!videoUpdateStarted)
  191. {
  192. StartCoroutine(WebRTC.Update());
  193. videoUpdateStarted = true;
  194. }
  195. }
  196. private void RemoveTracks()
  197. {
  198. foreach (var sender in pc1Senders)
  199. {
  200. _pc1.RemoveTrack(sender);
  201. }
  202. pc1Senders.Clear();
  203. var tracks = receiveStream.GetTracks().ToArray();
  204. foreach (var track in tracks)
  205. {
  206. receiveStream.RemoveTrack(track);
  207. }
  208. }
  209. private void Call()
  210. {
  211. callButton.interactable = false;
  212. hangUpButton.interactable = true;
  213. restartButton.interactable = true;
  214. var configuration = GetSelectedSdpSemantics();
  215. _pc1 = new RTCPeerConnection(ref configuration);
  216. _pc1.OnIceCandidate = pc1OnIceCandidate;
  217. _pc1.OnIceConnectionChange = pc1OnIceConnectionChange;
  218. _pc1.OnNegotiationNeeded = pc1OnNegotiationNeeded;
  219. _pc2 = new RTCPeerConnection(ref configuration);
  220. _pc2.OnIceCandidate = pc2OnIceCandidate;
  221. _pc2.OnIceConnectionChange = pc2OnIceConnectionChange;
  222. _pc2.OnTrack = pc2Ontrack;
  223. AddTracks();
  224. }
  225. private void RestartIce()
  226. {
  227. restartButton.interactable = false;
  228. _pc1.RestartIce();
  229. }
  230. private void HangUp()
  231. {
  232. RemoveTracks();
  233. _pc1.Close();
  234. _pc2.Close();
  235. _pc1.Dispose();
  236. _pc2.Dispose();
  237. _pc1 = null;
  238. _pc2 = null;
  239. callButton.interactable = true;
  240. restartButton.interactable = false;
  241. hangUpButton.interactable = false;
  242. receiveImage.color = Color.black;
  243. }
  244. private void OnIceCandidate(RTCPeerConnection pc, RTCIceCandidate candidate)
  245. {
  246. switch((ProtocolOption)dropDownProtocol.value)
  247. {
  248. case ProtocolOption.Default:
  249. break;
  250. case ProtocolOption.UDP:
  251. if (candidate.Protocol != RTCIceProtocol.Udp)
  252. return;
  253. break;
  254. case ProtocolOption.TCP:
  255. if (candidate.Protocol != RTCIceProtocol.Tcp)
  256. return;
  257. break;
  258. }
  259. GetOtherPc(pc).AddIceCandidate(candidate);
  260. Debug.Log($"{GetName(pc)} ICE candidate:\n {candidate.Candidate}");
  261. }
  262. private string GetName(RTCPeerConnection pc)
  263. {
  264. return (pc == _pc1) ? "pc1" : "pc2";
  265. }
  266. private RTCPeerConnection GetOtherPc(RTCPeerConnection pc)
  267. {
  268. return (pc == _pc1) ? _pc2 : _pc1;
  269. }
  270. private IEnumerator OnCreateOfferSuccess(RTCPeerConnection pc, RTCSessionDescription desc)
  271. {
  272. Debug.Log($"Offer from {GetName(pc)}\n{desc.sdp}");
  273. Debug.Log($"{GetName(pc)} setLocalDescription start");
  274. var op = pc.SetLocalDescription(ref desc);
  275. yield return op;
  276. if (!op.IsError)
  277. {
  278. OnSetLocalSuccess(pc);
  279. }
  280. else
  281. {
  282. var error = op.Error;
  283. OnSetSessionDescriptionError(ref error);
  284. yield break;
  285. }
  286. var otherPc = GetOtherPc(pc);
  287. Debug.Log($"{GetName(otherPc)} setRemoteDescription start");
  288. var op2 = otherPc.SetRemoteDescription(ref desc);
  289. yield return op2;
  290. if (!op2.IsError)
  291. {
  292. OnSetRemoteSuccess(otherPc);
  293. }
  294. else
  295. {
  296. var error = op2.Error;
  297. OnSetSessionDescriptionError(ref error);
  298. yield break;
  299. }
  300. Debug.Log($"{GetName(otherPc)} createAnswer start");
  301. // Since the 'remote' side has no media stream we need
  302. // to pass in the right constraints in order for it to
  303. // accept the incoming offer of audio and video.
  304. var op3 = otherPc.CreateAnswer();
  305. yield return op3;
  306. if (!op3.IsError)
  307. {
  308. yield return OnCreateAnswerSuccess(otherPc, op3.Desc);
  309. }
  310. else
  311. {
  312. OnCreateSessionDescriptionError(op3.Error);
  313. }
  314. }
  315. private void OnSetLocalSuccess(RTCPeerConnection pc)
  316. {
  317. Debug.Log($"{GetName(pc)} SetLocalDescription complete");
  318. }
  319. void OnSetSessionDescriptionError(ref RTCError error)
  320. {
  321. Debug.LogError($"Error Detail Type: {error.message}");
  322. HangUp();
  323. }
  324. private void OnSetRemoteSuccess(RTCPeerConnection pc)
  325. {
  326. Debug.Log($"{GetName(pc)} SetRemoteDescription complete");
  327. }
  328. IEnumerator OnCreateAnswerSuccess(RTCPeerConnection pc, RTCSessionDescription desc)
  329. {
  330. Debug.Log($"Answer from {GetName(pc)}:\n{desc.sdp}");
  331. Debug.Log($"{GetName(pc)} setLocalDescription start");
  332. var op = pc.SetLocalDescription(ref desc);
  333. yield return op;
  334. if (!op.IsError)
  335. {
  336. OnSetLocalSuccess(pc);
  337. }
  338. else
  339. {
  340. var error = op.Error;
  341. OnSetSessionDescriptionError(ref error);
  342. }
  343. var otherPc = GetOtherPc(pc);
  344. Debug.Log($"{GetName(otherPc)} setRemoteDescription start");
  345. var op2 = otherPc.SetRemoteDescription(ref desc);
  346. yield return op2;
  347. if (!op2.IsError)
  348. {
  349. OnSetRemoteSuccess(otherPc);
  350. }
  351. else
  352. {
  353. var error = op2.Error;
  354. OnSetSessionDescriptionError(ref error);
  355. }
  356. }
  357. private static void OnCreateSessionDescriptionError(RTCError error)
  358. {
  359. Debug.LogError($"Error Detail Type: {error.message}");
  360. }
  361. }