PerfectNegotiationSample.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using Unity.WebRTC;
  6. using Unity.WebRTC.Samples;
  7. using UnityEngine.Assertions;
  8. using UnityEngine.UI;
  9. class PerfectNegotiationSample : MonoBehaviour
  10. {
  11. #pragma warning disable 0649
  12. [SerializeField] private Button politeSwapButton;
  13. [SerializeField] private Button impoliteSwapButton;
  14. [SerializeField] private Button swapPoliteFirstButton;
  15. [SerializeField] private Button swapImpoliteFirstButton;
  16. [SerializeField] private Camera politeSourceCamera1;
  17. [SerializeField] private Camera politeSourceCamera2;
  18. [SerializeField] private Camera impoliteSourceCamera1;
  19. [SerializeField] private Camera impoliteSourceCamera2;
  20. [SerializeField] private RawImage politeSourceImage1;
  21. [SerializeField] private RawImage politeSourceImage2;
  22. [SerializeField] private RawImage impoliteSourceImage1;
  23. [SerializeField] private RawImage impoliteSourceImage2;
  24. [SerializeField] private RawImage politeReceiveImage;
  25. [SerializeField] private RawImage impoliteReceiveImage;
  26. #pragma warning restore 0649
  27. private Peer politePeer, impolitePeer;
  28. private readonly Color red = Color.red;
  29. private readonly Color magenta = Color.magenta;
  30. private readonly Color blue = Color.blue;
  31. private readonly Color cyan = Color.cyan;
  32. private readonly Color green = Color.green;
  33. private readonly Color yellow = Color.yellow;
  34. private int count = 0;
  35. private const int MAX = 120;
  36. private float lerp = 0.0f;
  37. private void Awake()
  38. {
  39. WebRTC.Initialize(WebRTCSettings.LimitTextureSize);
  40. }
  41. private void OnDestroy()
  42. {
  43. politePeer?.Dispose();
  44. impolitePeer?.Dispose();
  45. WebRTC.Dispose();
  46. }
  47. private void Start()
  48. {
  49. politePeer = new Peer(this, true, politeSourceCamera1, politeSourceCamera2, politeReceiveImage);
  50. impolitePeer = new Peer(this, false, impoliteSourceCamera1, impoliteSourceCamera2, impoliteReceiveImage);
  51. politeSourceImage1.texture = politeSourceCamera1.targetTexture;
  52. politeSourceImage2.texture = politeSourceCamera2.targetTexture;
  53. impoliteSourceImage1.texture = impoliteSourceCamera1.targetTexture;
  54. impoliteSourceImage2.texture = impoliteSourceCamera2.targetTexture;
  55. politeSwapButton.onClick.AddListener(politePeer.SwapTransceivers);
  56. impoliteSwapButton.onClick.AddListener(impolitePeer.SwapTransceivers);
  57. swapPoliteFirstButton.onClick.AddListener(() =>
  58. {
  59. politePeer.SwapTransceivers();
  60. impolitePeer.SwapTransceivers();
  61. });
  62. swapImpoliteFirstButton.onClick.AddListener(() =>
  63. {
  64. impolitePeer.SwapTransceivers();
  65. politePeer.SwapTransceivers();
  66. });
  67. StartCoroutine(WebRTC.Update());
  68. }
  69. private void Update()
  70. {
  71. count++;
  72. count %= MAX;
  73. lerp = (float) count / MAX;
  74. politeSourceCamera1.backgroundColor = Color.LerpUnclamped(red, magenta, lerp);
  75. politeSourceCamera2.backgroundColor = Color.LerpUnclamped(magenta, yellow, lerp);
  76. impoliteSourceCamera1.backgroundColor = Color.LerpUnclamped(blue, cyan, lerp);
  77. impoliteSourceCamera2.backgroundColor = Color.LerpUnclamped(cyan, green, lerp);
  78. }
  79. void PostMessage(Peer from, Message message)
  80. {
  81. var other = from == politePeer ? impolitePeer : politePeer;
  82. other.OnMessage(message);
  83. }
  84. class Message
  85. {
  86. public RTCSessionDescription description;
  87. public RTCIceCandidate candidate;
  88. }
  89. class Peer : IDisposable
  90. {
  91. private readonly PerfectNegotiationSample parent;
  92. private readonly bool polite;
  93. private readonly RTCPeerConnection pc;
  94. private readonly VideoStreamTrack sourceVideoTrack1;
  95. private readonly VideoStreamTrack sourceVideoTrack2;
  96. private readonly List<RTCRtpTransceiver> sendingTransceiverList = new List<RTCRtpTransceiver>();
  97. private bool makingOffer;
  98. private bool ignoreOffer;
  99. private bool srdAnswerPending;
  100. private bool sldGetBackStable;
  101. private const int width = 256;
  102. private const int height = 256;
  103. public Peer(
  104. PerfectNegotiationSample parent,
  105. bool polite,
  106. Camera source1,
  107. Camera source2,
  108. RawImage receive)
  109. {
  110. this.parent = parent;
  111. this.polite = polite;
  112. var config = GetSelectedSdpSemantics();
  113. pc = new RTCPeerConnection(ref config);
  114. pc.OnTrack = e =>
  115. {
  116. Debug.Log($"{this} OnTrack");
  117. if (e.Track is VideoStreamTrack video)
  118. {
  119. video.OnVideoReceived += tex =>
  120. {
  121. receive.texture = tex;
  122. };
  123. }
  124. };
  125. pc.OnIceCandidate = candidate =>
  126. {
  127. var message = new Message { candidate = candidate };
  128. this.parent.PostMessage(this, message);
  129. };
  130. pc.OnNegotiationNeeded = () =>
  131. {
  132. this.parent.StartCoroutine(NegotiationProcess());
  133. };
  134. sourceVideoTrack1 = source1.CaptureStreamTrack(width, height);
  135. sourceVideoTrack2 = source2.CaptureStreamTrack(width, height);
  136. }
  137. private IEnumerator NegotiationProcess()
  138. {
  139. Debug.Log($"{this} SLD due to negotiationneeded");
  140. yield return new WaitWhile(() => sldGetBackStable);
  141. Assert.AreEqual(pc.SignalingState, RTCSignalingState.Stable,
  142. $"{this} negotiationneeded always fires in stable state");
  143. Assert.AreEqual(makingOffer, false, $"{this} negotiationneeded not already in progress");
  144. makingOffer = true;
  145. var op = pc.SetLocalDescription();
  146. yield return op;
  147. if (op.IsError)
  148. {
  149. Debug.LogError($"{this} {op.Error.message}");
  150. makingOffer = false;
  151. yield break;
  152. }
  153. Assert.AreEqual(pc.SignalingState, RTCSignalingState.HaveLocalOffer,
  154. $"{this} negotiationneeded always fires in stable state");
  155. Assert.AreEqual(pc.LocalDescription.type, RTCSdpType.Offer, $"{this} negotiationneeded SLD worked");
  156. makingOffer = false;
  157. var offer = new Message { description = pc.LocalDescription };
  158. parent.PostMessage(this, offer);
  159. }
  160. public void Dispose()
  161. {
  162. sendingTransceiverList.Clear();
  163. sourceVideoTrack1.Dispose();
  164. sourceVideoTrack2.Dispose();
  165. pc.Dispose();
  166. }
  167. public override string ToString()
  168. {
  169. var str = polite ? "polite" : "impolite";
  170. return $"[{str}-{base.ToString()}]";
  171. }
  172. private static RTCConfiguration GetSelectedSdpSemantics()
  173. {
  174. RTCConfiguration config = default;
  175. config.iceServers = new[] { new RTCIceServer { urls = new[] { "stun:stun.l.google.com:19302" } } };
  176. return config;
  177. }
  178. public void OnMessage(Message message)
  179. {
  180. if (message.candidate != null)
  181. {
  182. if (!pc.AddIceCandidate(message.candidate) && !ignoreOffer)
  183. {
  184. Debug.LogError($"{this} this candidate can't accept current signaling state {pc.SignalingState}.");
  185. }
  186. return;
  187. }
  188. parent.StartCoroutine(OfferAnswerProcess(message.description));
  189. }
  190. private IEnumerator OfferAnswerProcess(RTCSessionDescription description)
  191. {
  192. var isStable =
  193. pc.SignalingState == RTCSignalingState.Stable ||
  194. (pc.SignalingState == RTCSignalingState.HaveLocalOffer && srdAnswerPending);
  195. ignoreOffer =
  196. description.type == RTCSdpType.Offer && !polite && (makingOffer || !isStable);
  197. if (ignoreOffer)
  198. {
  199. Debug.Log($"{this} glare - ignoring offer");
  200. yield break;
  201. }
  202. yield return new WaitWhile(() => makingOffer);
  203. srdAnswerPending = description.type == RTCSdpType.Answer;
  204. Debug.Log($"{this} SRD {description.type} SignalingState {pc.SignalingState}");
  205. var op1 = pc.SetRemoteDescription(ref description);
  206. yield return op1;
  207. Assert.IsFalse(op1.IsError, $"{this} {op1.Error.message}");
  208. srdAnswerPending = false;
  209. if (description.type == RTCSdpType.Offer)
  210. {
  211. Assert.AreEqual(pc.RemoteDescription.type, RTCSdpType.Offer, $"{this} SRD worked");
  212. Assert.AreEqual(pc.SignalingState, RTCSignalingState.HaveRemoteOffer, $"{this} Remote offer");
  213. Debug.Log($"{this} SLD to get back to stable");
  214. sldGetBackStable = true;
  215. var op2 = pc.SetLocalDescription();
  216. yield return op2;
  217. Assert.IsFalse(op2.IsError, $"{this} {op2.Error.message}");
  218. Assert.AreEqual(pc.LocalDescription.type, RTCSdpType.Answer, $"{this} onmessage SLD worked");
  219. Assert.AreEqual(pc.SignalingState, RTCSignalingState.Stable,
  220. $"{this} onmessage not racing with negotiationneeded");
  221. sldGetBackStable = false;
  222. var answer = new Message { description = pc.LocalDescription };
  223. parent.PostMessage(this, answer);
  224. }
  225. else
  226. {
  227. Assert.AreEqual(pc.RemoteDescription.type, RTCSdpType.Answer, $"{this} Answer was set");
  228. Assert.AreEqual(pc.SignalingState, RTCSignalingState.Stable, $"{this} answered");
  229. }
  230. }
  231. public void SwapTransceivers()
  232. {
  233. Debug.Log($"{this} swapTransceivers");
  234. if (sendingTransceiverList.Count == 0)
  235. {
  236. var transceiver1 = pc.AddTransceiver(sourceVideoTrack1);
  237. transceiver1.Direction = RTCRtpTransceiverDirection.SendOnly;
  238. var transceiver2 = pc.AddTransceiver(sourceVideoTrack2);
  239. transceiver2.Direction = RTCRtpTransceiverDirection.Inactive;
  240. if (WebRTCSettings.UseVideoCodec != null)
  241. {
  242. var codecs = new[] { WebRTCSettings.UseVideoCodec };
  243. transceiver1.SetCodecPreferences(codecs);
  244. transceiver2.SetCodecPreferences(codecs);
  245. }
  246. sendingTransceiverList.Add(transceiver1);
  247. sendingTransceiverList.Add(transceiver2);
  248. return;
  249. }
  250. if (sendingTransceiverList[0].CurrentDirection == RTCRtpTransceiverDirection.SendOnly)
  251. {
  252. sendingTransceiverList[0].Direction = RTCRtpTransceiverDirection.Inactive;
  253. sendingTransceiverList[0].Sender.ReplaceTrack(null);
  254. sendingTransceiverList[1].Direction = RTCRtpTransceiverDirection.SendOnly;
  255. sendingTransceiverList[1].Sender.ReplaceTrack(sourceVideoTrack2);
  256. }
  257. else
  258. {
  259. sendingTransceiverList[1].Direction = RTCRtpTransceiverDirection.Inactive;
  260. sendingTransceiverList[1].Sender.ReplaceTrack(null);
  261. sendingTransceiverList[0].Direction = RTCRtpTransceiverDirection.SendOnly;
  262. sendingTransceiverList[0].Sender.ReplaceTrack(sourceVideoTrack1);
  263. }
  264. }
  265. }
  266. }