TrickleIceSample.cs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using UnityEngine;
  6. using Unity.WebRTC;
  7. using Unity.WebRTC.Samples;
  8. using UnityEngine.UI;
  9. using Button = UnityEngine.UI.Button;
  10. class TrickleIceSample : MonoBehaviour
  11. {
  12. #pragma warning disable 0649
  13. [SerializeField] private Button addServerButton;
  14. [SerializeField] private Button removeServerButton;
  15. [SerializeField] private Button resetToDefaultButton;
  16. [SerializeField] private Button gatherCandidatesButton;
  17. [SerializeField] private InputField urlInputField;
  18. [SerializeField] private InputField usernameInputField;
  19. [SerializeField] private InputField passwordInputField;
  20. [SerializeField] private GameObject optionElement;
  21. [SerializeField] private Transform optionParent;
  22. [SerializeField] private GameObject candidateElement;
  23. [SerializeField] private Transform candidateParent;
  24. [SerializeField] private ToggleGroup iceTransportOption;
  25. [SerializeField] private Slider candidatePoolSizeSlider;
  26. [SerializeField] private Text candidatePoolSizeText;
  27. #pragma warning restore 0649
  28. private RTCPeerConnection _pc1;
  29. private RTCRtpTransceiver _transceiver;
  30. private float beginTime = 0f;
  31. private Dictionary<GameObject, RTCIceServer> iceServers
  32. = new Dictionary<GameObject, RTCIceServer>();
  33. private GameObject selectedOption = null;
  34. private void Awake()
  35. {
  36. WebRTC.Initialize(WebRTCSettings.LimitTextureSize);
  37. addServerButton.onClick.AddListener(OnAddServer);
  38. removeServerButton.onClick.AddListener(OnRemoveServer);
  39. resetToDefaultButton.onClick.AddListener(OnResetToDefault);
  40. gatherCandidatesButton.onClick.AddListener(OnGatherCandidate);
  41. candidatePoolSizeSlider.onValueChanged.AddListener(OnChangedCandidatePoolSize);
  42. }
  43. private void OnDestroy()
  44. {
  45. WebRTC.Dispose();
  46. }
  47. private void Start()
  48. {
  49. OnResetToDefault();
  50. }
  51. void OnAddServer()
  52. {
  53. string url = urlInputField.text;
  54. string username = usernameInputField.text;
  55. string password = passwordInputField.text;
  56. string scheme = url.Split(':')[0];
  57. if (scheme != "stun" && scheme != "turn" && scheme != "turns")
  58. {
  59. Debug.LogError(
  60. $"URI scheme `{scheme}` is not valid parameter. \n" +
  61. $"ex. `stun:192.168.11.1`, `turn:192.168.11.2:3478?transport=udp`");
  62. return;
  63. }
  64. AddServer(url, username, password);
  65. }
  66. void AddServer(string url, string username = null, string password = null)
  67. {
  68. // Store the ICE server as a stringified JSON object in option.value.
  69. GameObject option = Instantiate(optionElement, optionParent);
  70. Text optionText = option.GetComponentInChildren<Text>();
  71. Button optionButton = option.GetComponentInChildren<Button>();
  72. RTCIceServer iceServer = new RTCIceServer
  73. {
  74. urls = new[] { url },
  75. username = usernameInputField.text,
  76. credential = passwordInputField.text
  77. };
  78. optionText.text = url;
  79. if (!string.IsNullOrEmpty(username) || !string.IsNullOrEmpty(password))
  80. {
  81. optionText.text += $"[{username}:{password}]";
  82. }
  83. optionButton.onClick.AddListener(() => OnSelectServer(option));
  84. iceServers.Add(option, iceServer);
  85. urlInputField.text = string.Empty;
  86. usernameInputField.text = string.Empty;
  87. passwordInputField.text = string.Empty;
  88. }
  89. void OnRemoveServer()
  90. {
  91. if (selectedOption == null)
  92. return;
  93. iceServers.Remove(selectedOption);
  94. Destroy(selectedOption);
  95. selectedOption = null;
  96. }
  97. void OnResetToDefault()
  98. {
  99. const string url = "stun:stun.l.google.com:19302";
  100. foreach (Transform child in optionParent)
  101. {
  102. Destroy(child.gameObject);
  103. }
  104. iceServers.Clear();
  105. AddServer(url);
  106. }
  107. void OnSelectServer(GameObject option)
  108. {
  109. selectedOption = option;
  110. }
  111. private RTCConfiguration GetSelectedSdpSemantics()
  112. {
  113. List<Toggle> toggles = iceTransportOption.ActiveToggles().ToList();
  114. int index = toggles.FindIndex(toggle => toggle.isOn);
  115. RTCIceTransportPolicy policy = 0 == index ? RTCIceTransportPolicy.All: RTCIceTransportPolicy.Relay;
  116. RTCConfiguration config = default;
  117. config.iceServers = iceServers.Values.ToArray();
  118. config.iceTransportPolicy = policy;
  119. config.iceCandidatePoolSize = (int)candidatePoolSizeSlider.value;
  120. return config;
  121. }
  122. IEnumerator CreateOffer(RTCPeerConnection pc)
  123. {
  124. var op = pc.CreateOffer();
  125. yield return op;
  126. if (!op.IsError)
  127. {
  128. if (pc.SignalingState != RTCSignalingState.Stable)
  129. {
  130. Debug.LogError($"signaling state is not stable.");
  131. yield break;
  132. }
  133. beginTime = Time.realtimeSinceStartup;
  134. yield return StartCoroutine(OnCreateOfferSuccess(pc, op.Desc));
  135. }
  136. else
  137. {
  138. OnCreateSessionDescriptionError(op.Error);
  139. }
  140. }
  141. private void OnChangedCandidatePoolSize(float value)
  142. {
  143. int value_ = (int)value;
  144. candidatePoolSizeText.text = value_.ToString();
  145. }
  146. private void OnGatherCandidate()
  147. {
  148. foreach (Transform child in candidateParent)
  149. {
  150. Destroy(child.gameObject);
  151. }
  152. gatherCandidatesButton.interactable = false;
  153. var configuration = GetSelectedSdpSemantics();
  154. _pc1 = new RTCPeerConnection(ref configuration);
  155. _pc1.OnIceCandidate = OnIceCandidate;
  156. _pc1.OnIceGatheringStateChange = OnIceGatheringStateChange;
  157. _transceiver = _pc1.AddTransceiver(TrackKind.Video);
  158. StartCoroutine(CreateOffer(_pc1));
  159. }
  160. private void OnIceCandidate(RTCIceCandidate candidate)
  161. {
  162. GameObject newCandidate = Instantiate(candidateElement, candidateParent);
  163. Text[] texts = newCandidate.GetComponentsInChildren<Text>();
  164. foreach (Text text in texts)
  165. {
  166. switch (text.name)
  167. {
  168. case "Time":
  169. text.text = (Time.realtimeSinceStartup - beginTime).ToString("F");
  170. break;
  171. case "Component":
  172. text.text = candidate.Component.Value.ToString();
  173. break;
  174. case "Type":
  175. text.text = candidate.Type.Value.ToString();
  176. break;
  177. case "Foundation":
  178. text.text = candidate.Foundation;
  179. break;
  180. case "Protocol":
  181. text.text = candidate.Protocol.Value.ToString();
  182. break;
  183. case "Address":
  184. text.text = candidate.Address;
  185. break;
  186. case "Port":
  187. text.text = candidate.Port.ToString();
  188. break;
  189. case "Priority":
  190. text.text = FormatPriority(candidate.Priority);
  191. break;
  192. }
  193. }
  194. }
  195. private void OnIceGatheringStateChange(RTCIceGatheringState state)
  196. {
  197. if (state != RTCIceGatheringState.Complete)
  198. {
  199. return;
  200. }
  201. string elapsed = (Time.realtimeSinceStartup - beginTime).ToString("F");
  202. GameObject newCandidate = Instantiate(candidateElement, candidateParent);
  203. Text[] texts = newCandidate.GetComponentsInChildren<Text>();
  204. foreach (Text text in texts)
  205. {
  206. switch (text.name)
  207. {
  208. case "Time":
  209. text.text = (Time.realtimeSinceStartup - beginTime).ToString("F");
  210. break;
  211. case "Priority":
  212. text.text = "Done";
  213. break;
  214. default:
  215. text.text = string.Empty;
  216. break;
  217. }
  218. }
  219. _transceiver.Dispose();
  220. _pc1.Close();
  221. _pc1 = null;
  222. gatherCandidatesButton.interactable = true;
  223. }
  224. private IEnumerator OnCreateOfferSuccess(RTCPeerConnection pc, RTCSessionDescription desc)
  225. {
  226. Debug.Log("setLocalDescription start");
  227. var op = pc.SetLocalDescription(ref desc);
  228. yield return op;
  229. if (!op.IsError)
  230. {
  231. OnSetLocalSuccess(pc);
  232. }
  233. else
  234. {
  235. var error = op.Error;
  236. OnSetSessionDescriptionError(ref error);
  237. }
  238. }
  239. private void OnSetLocalSuccess(RTCPeerConnection pc)
  240. {
  241. Debug.Log("SetLocalDescription complete");
  242. }
  243. static void OnSetSessionDescriptionError(ref RTCError error)
  244. {
  245. Debug.LogError($"Error Detail Type: {error.message}");
  246. }
  247. static string FormatPriority(uint priority)
  248. {
  249. return $"{priority >> 24} | {(priority >> 8) & 0xFFFF} | {priority & 0xFF}";
  250. }
  251. private static void OnCreateSessionDescriptionError(RTCError error)
  252. {
  253. Debug.LogError($"Error Detail Type: {error.message}");
  254. }
  255. }