123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Net;
- using System.Threading;
- using Unity.WebRTC;
- using UnityEngine;
- namespace Unity.RenderStreaming.Signaling
- {
- public class HttpSignaling : ISignaling
- {
- private static HashSet<HttpSignaling> instances = new HashSet<HttpSignaling>();
- private readonly string m_url;
- private readonly int m_timeout;
- private readonly SynchronizationContext m_mainThreadContext;
- private bool m_running;
- private Thread m_signalingThread;
- private string m_sessionId;
- private long m_lastTimeGetAllRequest;
- public string Url { get { return m_url; } }
- public HttpSignaling(SignalingSettings signalingSettings, SynchronizationContext mainThreadContext)
- {
- if (signalingSettings == null)
- throw new ArgumentNullException(nameof(signalingSettings));
- if (!(signalingSettings is HttpSignalingSettings settings))
- throw new ArgumentException("signalingSettings is not HttpSignalingSettings");
- m_url = settings.url;
- m_timeout = settings.interval;
- m_mainThreadContext = mainThreadContext;
- if (m_url.StartsWith("https"))
- {
- ServicePointManager.ServerCertificateValidationCallback =
- (sender, certificate, chain, errors) => true;
- }
- if (instances.Any(x => x.Url == m_url))
- {
- Debug.LogWarning($"Other {nameof(HttpSignaling)} exists with same URL:{m_url}. Signaling process may be in conflict.");
- }
- instances.Add(this);
- }
- ~HttpSignaling()
- {
- Stop();
- instances.Remove(this);
- }
- public void Start()
- {
- if (m_running)
- throw new InvalidOperationException("This object is already started.");
- m_running = true;
- m_signalingThread = new Thread(HTTPPolling);
- m_signalingThread.Start();
- }
- public void Stop()
- {
- m_running = false;
- if (m_signalingThread != null)
- {
- if (m_signalingThread.ThreadState == ThreadState.WaitSleepJoin)
- {
- m_signalingThread.Abort();
- }
- else
- {
- m_signalingThread.Join(1000);
- }
- m_signalingThread = null;
- }
- }
- public event OnStartHandler OnStart;
- public event OnConnectHandler OnCreateConnection;
- public event OnDisconnectHandler OnDestroyConnection;
- public event OnOfferHandler OnOffer;
- public event OnAnswerHandler OnAnswer;
- public event OnIceCandidateHandler OnIceCandidate;
- public void SendOffer(string connectionId, RTCSessionDescription offer)
- {
- DescData data = new DescData();
- data.connectionId = connectionId;
- data.sdp = offer.sdp;
- data.type = "offer";
- ThreadPool.QueueUserWorkItem(_ => { HTTPPost("signaling/offer", data); });
- }
- public void SendAnswer(string connectionId, RTCSessionDescription answer)
- {
- DescData data = new DescData();
- data.connectionId = connectionId;
- data.sdp = answer.sdp;
- data.type = "answer";
- ThreadPool.QueueUserWorkItem(_ => { HTTPPost("signaling/answer", data); });
- }
- public void SendCandidate(string connectionId, RTCIceCandidate candidate)
- {
- CandidateData data = new CandidateData();
- data.connectionId = connectionId;
- data.candidate = candidate.Candidate;
- data.sdpMLineIndex = candidate.SdpMLineIndex.GetValueOrDefault(0);
- data.sdpMid = candidate.SdpMid;
- ThreadPool.QueueUserWorkItem(_ => { HTTPPost("signaling/candidate", data); });
- }
- public void OpenConnection(string connectionId)
- {
- ThreadPool.QueueUserWorkItem(_ => { HTTPConnect(connectionId); });
- }
- public void CloseConnection(string connectionId)
- {
- ThreadPool.QueueUserWorkItem(_ => { HTTPDisonnect(connectionId); });
- }
- private void HTTPPolling()
- {
- // ignore messages arrived before 30 secs ago
- m_lastTimeGetAllRequest = DateTime.UtcNow.Millisecond - 30000;
- while (m_running && string.IsNullOrEmpty(m_sessionId))
- {
- HTTPCreate();
- try
- {
- Thread.Sleep(m_timeout);
- }
- catch (ThreadAbortException)
- {
- // Thread.Abort() called from main thread. Ignore
- return;
- }
- }
- while (m_running)
- {
- try
- {
- HTTPGetAll();
- Thread.Sleep(m_timeout);
- }
- catch (ThreadAbortException)
- {
- // Thread.Abort() called from main thread. Ignore
- return;
- }
- catch (Exception e)
- {
- Debug.LogError("Signaling: HTTP polling error : " + e);
- }
- }
- HTTPDelete();
- Debug.Log("Signaling: HTTP polling thread ended");
- }
- private static HttpWebResponse HTTPGetResponse(HttpWebRequest request)
- {
- try
- {
- HttpWebResponse response = (HttpWebResponse)request.GetResponse();
- if (response.StatusCode == HttpStatusCode.OK)
- {
- return response;
- }
- else
- {
- Debug.LogError($"Signaling: {response.ResponseUri} HTTP request failed ({response.StatusCode})");
- response.Close();
- }
- }
- catch (ThreadAbortException)
- {
- // Thread.Abort() called from main thread. Ignore
- }
- catch (Exception e)
- {
- Debug.LogError($"Signaling: HTTP request error. url:{request.RequestUri} exception:{e}");
- }
- return null;
- }
- private static T HTTPParseJsonResponse<T>(HttpWebResponse response) where T : class
- {
- if (response == null) return null;
- T obj = null;
- using (Stream dataStream = response.GetResponseStream())
- {
- StreamReader reader = new StreamReader(dataStream);
- string responseFromServer = reader.ReadToEnd();
- obj = JsonUtility.FromJson<T>(responseFromServer);
- }
- response.Close();
- return obj;
- }
- private static string HTTPParseTextResponse(HttpWebResponse response)
- {
- if (response == null) return null;
- string str = null;
- using (Stream dataStream = response.GetResponseStream())
- {
- StreamReader reader = new StreamReader(dataStream);
- str = reader.ReadToEnd();
- }
- response.Close();
- return str;
- }
- private bool HTTPCreate()
- {
- HttpWebRequest request = (HttpWebRequest)WebRequest.Create($"{m_url}/signaling");
- request.Method = "PUT";
- request.ContentType = "application/json";
- request.KeepAlive = false;
- request.ContentLength = 0;
- Debug.Log($"Signaling: Connecting HTTP {m_url}");
- OpenSessionData resp = HTTPParseJsonResponse<OpenSessionData>(HTTPGetResponse(request));
- if (resp != null)
- {
- m_sessionId = resp.sessionId;
- Debug.Log("Signaling: HTTP connected, sessionId : " + m_sessionId);
- m_mainThreadContext.Post(d => OnStart?.Invoke(this), null);
- return true;
- }
- else
- {
- return false;
- }
- }
- private bool HTTPDelete()
- {
- HttpWebRequest request = (HttpWebRequest)WebRequest.Create($"{m_url}/signaling");
- request.Method = "DELETE";
- request.ContentType = "application/json";
- request.KeepAlive = false;
- request.Headers.Add("Session-Id", m_sessionId);
- Debug.Log($"Signaling: Removing HTTP connection from {m_url}");
- return (HTTPParseTextResponse(HTTPGetResponse(request)) != null);
- }
- private bool HTTPPost(string path, object data)
- {
- string str = JsonUtility.ToJson(data);
- byte[] bytes = new System.Text.UTF8Encoding().GetBytes(str);
- Debug.Log("Signaling: Posting HTTP data: " + str);
- HttpWebRequest request = (HttpWebRequest)WebRequest.Create($"{m_url}/{path}");
- request.Method = "POST";
- request.ContentType = "application/json";
- request.Headers.Add("Session-Id", m_sessionId);
- request.KeepAlive = false;
- using (Stream dataStream = request.GetRequestStream())
- {
- dataStream.Write(bytes, 0, bytes.Length);
- dataStream.Close();
- }
- return (HTTPParseTextResponse(HTTPGetResponse(request)) != null);
- }
- private bool HTTPConnect(string connectionId)
- {
- HttpWebRequest request =
- (HttpWebRequest)WebRequest.Create($"{m_url}/signaling/connection");
- request.Method = "PUT";
- request.ContentType = "application/json";
- request.Headers.Add("Session-Id", m_sessionId);
- request.KeepAlive = false;
- using (Stream dataStream = request.GetRequestStream())
- {
- byte[] bytes = new System.Text.UTF8Encoding().GetBytes($"{{\"connectionId\":\"{connectionId}\"}}");
- dataStream.Write(bytes, 0, bytes.Length);
- dataStream.Close();
- }
- HttpWebResponse response = HTTPGetResponse(request);
- CreateConnectionResData data = HTTPParseJsonResponse<CreateConnectionResData>(response);
- if (data == null) return false;
- Debug.Log($"Signaling: HTTP create connection, connectionId: {connectionId}, polite:{data.polite}");
- m_mainThreadContext.Post(d => OnCreateConnection?.Invoke(this, data.connectionId, data.polite), null);
- return true;
- }
- private bool HTTPDisonnect(string connectionId)
- {
- HttpWebRequest request =
- (HttpWebRequest)WebRequest.Create($"{m_url}/signaling/connection");
- request.Method = "Delete";
- request.ContentType = "application/json";
- request.Headers.Add("Session-Id", m_sessionId);
- request.KeepAlive = false;
- using (Stream dataStream = request.GetRequestStream())
- {
- byte[] bytes = new System.Text.UTF8Encoding().GetBytes($"{{\"connectionId\":\"{connectionId}\"}}");
- dataStream.Write(bytes, 0, bytes.Length);
- dataStream.Close();
- }
- var data = HTTPParseTextResponse(HTTPGetResponse(request));
- if (data == null) return false;
- Debug.Log("Signaling: HTTP delete connection, connectionId : " + connectionId);
- m_mainThreadContext.Post(d => OnDestroyConnection?.Invoke(this, connectionId), null);
- return true;
- }
- private bool HTTPGetAll()
- {
- HttpWebRequest request =
- (HttpWebRequest)WebRequest.Create($"{m_url}/signaling?fromtime={m_lastTimeGetAllRequest}");
- request.Method = "GET";
- request.ContentType = "application/json";
- request.Headers.Add("Session-Id", m_sessionId);
- request.KeepAlive = false;
- HttpWebResponse response = HTTPGetResponse(request);
- AllResData data = HTTPParseJsonResponse<AllResData>(response);
- if (data == null) return false;
- m_lastTimeGetAllRequest = DateTimeExtension.ParseHttpDate(response.Headers[HttpResponseHeader.Date])
- .ToJsMilliseconds();
- foreach (var msg in data.messages)
- {
- if (string.IsNullOrEmpty(msg.type))
- continue;
- if(msg.type == "disconnect")
- {
- m_mainThreadContext.Post(d => OnDestroyConnection?.Invoke(this, msg.connectionId), null);
- }
- else if (msg.type == "offer")
- {
- DescData offer = new DescData();
- offer.connectionId = msg.connectionId;
- offer.sdp = msg.sdp;
- offer.polite = msg.polite;
- m_mainThreadContext.Post(d => OnOffer?.Invoke(this, offer), null);
- }
- else if (msg.type == "answer")
- {
- DescData answer = new DescData
- {
- connectionId = msg.connectionId,
- sdp = msg.sdp
- };
- m_mainThreadContext.Post(d => OnAnswer?.Invoke(this, answer), null);
- }
- else if (msg.type == "candidate")
- {
- CandidateData candidate = new CandidateData
- {
- connectionId = msg.connectionId,
- candidate = msg.candidate,
- sdpMLineIndex = msg.sdpMLineIndex,
- sdpMid = msg.sdpMid
- };
- m_mainThreadContext.Post(d => OnIceCandidate?.Invoke(this, candidate), null);
- }
- }
- return true;
- }
- }
- }
|