658 lines
31 KiB
C#
658 lines
31 KiB
C#
/*
|
|
* Copyright (c) Contributors, http://opensimulator.org/
|
|
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of the OpenSimulator Project nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net.Http;
|
|
using System.Net.Mime;
|
|
using System.Reflection;
|
|
using System.Threading.Tasks;
|
|
|
|
using OpenMetaverse.StructuredData;
|
|
|
|
using log4net;
|
|
using log4net.Core;
|
|
using System.Reflection.Metadata;
|
|
using System.Threading;
|
|
|
|
namespace osWebRtcVoice
|
|
{
|
|
// Encapsulization of a Session to the Janus server
|
|
public class JanusSession : IDisposable
|
|
{
|
|
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
|
private static readonly string LogHeader = "[JANUS SESSION]";
|
|
|
|
// Set to 'true' to get the messages send and received from Janus
|
|
private bool _MessageDetails = false;
|
|
|
|
private string _JanusServerURI = String.Empty;
|
|
private string _JanusAPIToken = String.Empty;
|
|
private string _JanusAdminURI = String.Empty;
|
|
private string _JanusAdminToken = String.Empty;
|
|
|
|
public string JanusServerURI => _JanusServerURI;
|
|
public string JanusAdminURI => _JanusAdminURI;
|
|
|
|
public string SessionId { get; private set; }
|
|
public string SessionUri { get ; private set ; }
|
|
|
|
public string PluginId { get; set; }
|
|
|
|
private CancellationTokenSource _CancelTokenSource = new CancellationTokenSource();
|
|
private HttpClient _HttpClient = new HttpClient();
|
|
|
|
public bool IsConnected { get; set; }
|
|
|
|
// Wrapper around the session connection to Janus-gateway
|
|
public JanusSession(string pServerURI, string pAPIToken, string pAdminURI, string pAdminToken, bool pDebugMessages = false)
|
|
{
|
|
m_log.DebugFormat("{0} JanusSession constructor", LogHeader);
|
|
_JanusServerURI = pServerURI;
|
|
_JanusAPIToken = pAPIToken;
|
|
_JanusAdminURI = pAdminURI;
|
|
_JanusAdminToken = pAdminToken;
|
|
_MessageDetails = pDebugMessages;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
ClearEventSubscriptions();
|
|
if (IsConnected)
|
|
{
|
|
_ = DestroySession();
|
|
}
|
|
if (_HttpClient is not null)
|
|
{
|
|
_HttpClient.Dispose();
|
|
_HttpClient = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Make the create session request to the Janus server, get the
|
|
/// sessionID and return TRUE if successful.
|
|
/// </summary>
|
|
/// <returns>TRUE if session was created successfully</returns>
|
|
public async Task<bool> CreateSession()
|
|
{
|
|
bool ret = false;
|
|
try
|
|
{
|
|
var resp = await SendToJanus(new CreateSessionReq());
|
|
if (resp is not null && resp.isSuccess)
|
|
{
|
|
var sessionResp = new CreateSessionResp(resp);
|
|
SessionId = sessionResp.returnedId;
|
|
IsConnected = true;
|
|
SessionUri = _JanusServerURI + "/" + SessionId;
|
|
m_log.DebugFormat("{0} CreateSession. Created. ID={1}, URL={2}", LogHeader, SessionId, SessionUri);
|
|
ret = true;
|
|
StartLongPoll();
|
|
}
|
|
else
|
|
{
|
|
m_log.ErrorFormat("{0} CreateSession: failed", LogHeader);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_log.ErrorFormat("{0} CreateSession: exception {1}", LogHeader, e);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
public async Task<bool> DestroySession()
|
|
{
|
|
bool ret = false;
|
|
try
|
|
{
|
|
JanusMessageResp resp = await SendToSession(new DestroySessionReq());
|
|
if (resp is not null && resp.isSuccess)
|
|
{
|
|
// Note that setting IsConnected to false will cause the long poll to exit
|
|
m_log.DebugFormat("{0} DestroySession. Destroyed", LogHeader);
|
|
}
|
|
else
|
|
{
|
|
if (resp.isError)
|
|
{
|
|
ErrorResp eResp = new ErrorResp(resp);
|
|
switch (eResp.errorCode)
|
|
{
|
|
case 458:
|
|
// This is the error code for a session that is already destroyed
|
|
m_log.DebugFormat("{0} DestroySession: session already destroyed", LogHeader);
|
|
break;
|
|
case 459:
|
|
// This is the error code for handle already destroyed
|
|
m_log.DebugFormat("{0} DestroySession: Handle not found", LogHeader);
|
|
break;
|
|
default:
|
|
m_log.ErrorFormat("{0} DestroySession: failed {1}", LogHeader, eResp.errorReason);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_log.ErrorFormat("{0} DestroySession: failed. Resp: {1}", LogHeader, resp.ToString());
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_log.ErrorFormat("{0} DestroySession: exception {1}", LogHeader, e);
|
|
}
|
|
IsConnected = false;
|
|
_CancelTokenSource.Cancel();
|
|
|
|
return ret;
|
|
}
|
|
|
|
// ====================================================================
|
|
public async Task<JanusMessageResp> TrickleCandidates(JanusViewerSession pVSession, OSDArray pCandidates)
|
|
{
|
|
JanusMessageResp ret = null;
|
|
// if the audiobridge is active, the trickle message is sent to it
|
|
if (pVSession.AudioBridge is null)
|
|
{
|
|
ret = await SendToJanusNoWait(new TrickleReq(pVSession));
|
|
}
|
|
else
|
|
{
|
|
ret = await SendToJanusNoWait(new TrickleReq(pVSession), pVSession.AudioBridge.PluginUri);
|
|
}
|
|
return ret;
|
|
}
|
|
// ====================================================================
|
|
public async Task<JanusMessageResp> TrickleCompleted(JanusViewerSession pVSession)
|
|
{
|
|
JanusMessageResp ret = null;
|
|
// if the audiobridge is active, the trickle message is sent to it
|
|
if (pVSession.AudioBridge is null)
|
|
{
|
|
ret = await SendToJanusNoWait(new TrickleReq(pVSession));
|
|
}
|
|
else
|
|
{
|
|
ret = await SendToJanusNoWait(new TrickleReq(pVSession), pVSession.AudioBridge.PluginUri);
|
|
}
|
|
return ret;
|
|
}
|
|
// ====================================================================
|
|
public Dictionary<string, JanusPlugin> _Plugins = new Dictionary<string, JanusPlugin>();
|
|
public void AddPlugin(JanusPlugin pPlugin)
|
|
{
|
|
_Plugins.Add(pPlugin.PluginName, pPlugin);
|
|
}
|
|
// ====================================================================
|
|
// Post to the session
|
|
public async Task<JanusMessageResp> SendToSession(JanusMessageReq pReq)
|
|
{
|
|
return await SendToJanus(pReq, SessionUri);
|
|
}
|
|
|
|
private class OutstandingRequest
|
|
{
|
|
public string TransactionId;
|
|
public DateTime RequestTime;
|
|
public TaskCompletionSource<JanusMessageResp> TaskCompletionSource;
|
|
}
|
|
private Dictionary<string, OutstandingRequest> _OutstandingRequests = new Dictionary<string, OutstandingRequest>();
|
|
|
|
// Send a request directly to the Janus server.
|
|
// NOTE: this is probably NOT what you want to do. This is a direct call that is outside the session.
|
|
private async Task<JanusMessageResp> SendToJanus(JanusMessageReq pReq)
|
|
{
|
|
return await SendToJanus(pReq, _JanusServerURI);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send a request to the Janus server. This is the basic call that sends a request to the server.
|
|
/// The transaction ID is used to match the response to the request.
|
|
/// If the request returns an 'ack' response, the code waits for the matching event
|
|
/// before returning the response.
|
|
/// </summary>
|
|
/// <param name="pReq"></param>
|
|
/// <param name="pURI"></param>
|
|
/// <returns></returns>
|
|
public async Task<JanusMessageResp> SendToJanus(JanusMessageReq pReq, string pURI)
|
|
{
|
|
AddJanusHeaders(pReq);
|
|
// m_log.DebugFormat("{0} SendToJanus", LogHeader);
|
|
if (_MessageDetails) m_log.DebugFormat("{0} SendToJanus. URI={1}, req={2}", LogHeader, pURI, pReq.ToJson());
|
|
|
|
JanusMessageResp ret = null;
|
|
try
|
|
{
|
|
OutstandingRequest outReq = new OutstandingRequest
|
|
{
|
|
TransactionId = pReq.TransactionId,
|
|
RequestTime = DateTime.Now,
|
|
TaskCompletionSource = new TaskCompletionSource<JanusMessageResp>()
|
|
};
|
|
_OutstandingRequests.Add(pReq.TransactionId, outReq);
|
|
|
|
string reqStr = pReq.ToJson();
|
|
|
|
HttpRequestMessage reqMsg = new HttpRequestMessage(HttpMethod.Post, pURI);
|
|
reqMsg.Content = new StringContent(reqStr, System.Text.Encoding.UTF8, MediaTypeNames.Application.Json);
|
|
reqMsg.Headers.Add("Accept", "application/json");
|
|
HttpResponseMessage response = await _HttpClient.SendAsync(reqMsg, _CancelTokenSource.Token);
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
string respStr = await response.Content.ReadAsStringAsync();
|
|
ret = JanusMessageResp.FromJson(respStr);
|
|
if (ret.CheckReturnCode("ack"))
|
|
{
|
|
// Some messages are asynchronous and completed with an event
|
|
if (_MessageDetails) m_log.DebugFormat("{0} SendToJanus: ack response {1}", LogHeader, respStr);
|
|
if (_OutstandingRequests.TryGetValue(pReq.TransactionId, out OutstandingRequest outstandingRequest))
|
|
{
|
|
ret = await outstandingRequest.TaskCompletionSource.Task;
|
|
_OutstandingRequests.Remove(pReq.TransactionId);
|
|
}
|
|
// If there is no OutstandingRequest, the request was not waiting for an event or already processed
|
|
}
|
|
else
|
|
{
|
|
// If the response is not an ack, that means a synchronous request/response so return the response
|
|
_OutstandingRequests.Remove(pReq.TransactionId);
|
|
if (_MessageDetails) m_log.DebugFormat("{0} SendToJanus: response {1}", LogHeader, respStr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_log.ErrorFormat("{0} SendToJanus: response not successful {1}", LogHeader, response);
|
|
_OutstandingRequests.Remove(pReq.TransactionId);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_log.ErrorFormat("{0} SendToJanus: exception {1}", LogHeader, e.Message);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
/// <summary>
|
|
/// Send a request to the Janus server but we just return the response and don't wait for any
|
|
/// event or anything.
|
|
/// There are some requests that are just fire-and-forget.
|
|
/// </summary>
|
|
/// <param name="pReq"></param>
|
|
/// <returns></returns>
|
|
private async Task<JanusMessageResp> SendToJanusNoWait(JanusMessageReq pReq, string pURI)
|
|
{
|
|
JanusMessageResp ret = new JanusMessageResp();
|
|
|
|
AddJanusHeaders(pReq);
|
|
|
|
try {
|
|
HttpRequestMessage reqMsg = new HttpRequestMessage(HttpMethod.Post, pURI);
|
|
string reqStr = pReq.ToJson();
|
|
reqMsg.Content = new StringContent(reqStr, System.Text.Encoding.UTF8, MediaTypeNames.Application.Json);
|
|
reqMsg.Headers.Add("Accept", "application/json");
|
|
HttpResponseMessage response = await _HttpClient.SendAsync(reqMsg);
|
|
string respStr = await response.Content.ReadAsStringAsync();
|
|
ret = JanusMessageResp.FromJson(respStr);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_log.ErrorFormat("{0} SendToJanusNoWait: exception {1}", LogHeader, e.Message);
|
|
}
|
|
return ret;
|
|
|
|
}
|
|
private async Task<JanusMessageResp> SendToJanusNoWait(JanusMessageReq pReq)
|
|
{
|
|
return await SendToJanusNoWait(pReq, SessionUri);
|
|
}
|
|
|
|
// There are various headers that are in most Janus requests. Add them here.
|
|
private void AddJanusHeaders(JanusMessageReq pReq)
|
|
{
|
|
// Authentication token
|
|
if (!String.IsNullOrEmpty(_JanusAPIToken))
|
|
{
|
|
pReq.AddAPIToken(_JanusAPIToken);
|
|
}
|
|
// Transaction ID that matches responses to requests
|
|
if (String.IsNullOrEmpty(pReq.TransactionId))
|
|
{
|
|
pReq.TransactionId = Guid.NewGuid().ToString();
|
|
}
|
|
// The following two are required for the WebSocket interface. They are optional for the
|
|
// HTTP interface since the session and plugin handle are in the URL.
|
|
// SessionId is added to the message if not already there
|
|
if (!pReq.hasSessionId && !String.IsNullOrEmpty(SessionId))
|
|
{
|
|
pReq.AddSessionId(SessionId);
|
|
}
|
|
// HandleId connects to the plugin
|
|
if (!pReq.hasHandleId && !String.IsNullOrEmpty(PluginId))
|
|
{
|
|
pReq.AddHandleId(PluginId);
|
|
}
|
|
}
|
|
|
|
bool TryGetOutstandingRequest(string pTransactionId, out OutstandingRequest pOutstandingRequest)
|
|
{
|
|
if (String.IsNullOrEmpty(pTransactionId))
|
|
{
|
|
pOutstandingRequest = null;
|
|
return false;
|
|
}
|
|
|
|
bool ret = false;
|
|
lock (_OutstandingRequests)
|
|
{
|
|
if (_OutstandingRequests.TryGetValue(pTransactionId, out pOutstandingRequest))
|
|
{
|
|
_OutstandingRequests.Remove(pTransactionId);
|
|
ret = true;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
public Task<JanusMessageResp> SendToJanusAdmin(JanusMessageReq pReq)
|
|
{
|
|
return SendToJanus(pReq, _JanusAdminURI);
|
|
}
|
|
|
|
public Task<JanusMessageResp> GetFromJanus()
|
|
{
|
|
return GetFromJanus(_JanusServerURI);
|
|
}
|
|
/// <summary>
|
|
/// Do a GET to the Janus server and return the response.
|
|
/// If the response is an HTTP error, we return fake JanusMessageResp with the error.
|
|
/// </summary>
|
|
/// <param name="pURI"></param>
|
|
/// <returns></returns>
|
|
public async Task<JanusMessageResp> GetFromJanus(string pURI)
|
|
{
|
|
if (!String.IsNullOrEmpty(_JanusAPIToken))
|
|
{
|
|
pURI += "?apisecret=" + _JanusAPIToken;
|
|
}
|
|
|
|
JanusMessageResp ret = null;
|
|
try
|
|
{
|
|
// m_log.DebugFormat("{0} GetFromJanus: URI = \"{1}\"", LogHeader, pURI);
|
|
HttpRequestMessage reqMsg = new HttpRequestMessage(HttpMethod.Get, pURI);
|
|
reqMsg.Headers.Add("Accept", "application/json");
|
|
HttpResponseMessage response = null;
|
|
try
|
|
{
|
|
response = await _HttpClient.SendAsync(reqMsg, _CancelTokenSource.Token);
|
|
if (response is not null && response.IsSuccessStatusCode)
|
|
{
|
|
string respStr = await response.Content.ReadAsStringAsync();
|
|
ret = JanusMessageResp.FromJson(respStr);
|
|
// m_log.DebugFormat("{0} GetFromJanus: response {1}", LogHeader, respStr);
|
|
}
|
|
else
|
|
{
|
|
m_log.ErrorFormat("{0} GetFromJanus: response not successful {1}", LogHeader, response);
|
|
var eResp = new ErrorResp("GETERROR");
|
|
// Add the sessionId so the proper session can be shut down
|
|
eResp.AddSessionId(SessionId);
|
|
if (response is not null)
|
|
{
|
|
eResp.SetError((int)response.StatusCode, response.ReasonPhrase);
|
|
}
|
|
else
|
|
{
|
|
eResp.SetError(0, "Connection refused");
|
|
}
|
|
ret = eResp;
|
|
}
|
|
}
|
|
catch (TaskCanceledException e)
|
|
{
|
|
m_log.DebugFormat("{0} GetFromJanus: task canceled: {1}", LogHeader, e.Message);
|
|
var eResp = new ErrorResp("GETERROR");
|
|
eResp.SetError(499, "Task canceled");
|
|
ret = eResp;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_log.ErrorFormat("{0} GetFromJanus: exception {1}", LogHeader, e.Message);
|
|
var eResp = new ErrorResp("GETERROR");
|
|
eResp.SetError(400, "Exception: " + e.Message);
|
|
ret = eResp;
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_log.ErrorFormat("{0} GetFromJanus: exception {1}", LogHeader, e);
|
|
var eResp = new ErrorResp("GETERROR");
|
|
eResp.SetError(400, "Exception: " + e.Message);
|
|
ret = eResp;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// ====================================================================
|
|
public delegate void JanusEventHandler(EventResp pResp);
|
|
|
|
// Not all the events are used. CS0067 is to suppress the warning that the event is not used.
|
|
#pragma warning disable CS0067,CS0414
|
|
public event JanusEventHandler OnKeepAlive;
|
|
public event JanusEventHandler OnServerInfo;
|
|
public event JanusEventHandler OnTrickle;
|
|
public event JanusEventHandler OnHangup;
|
|
public event JanusEventHandler OnDetached;
|
|
public event JanusEventHandler OnError;
|
|
public event JanusEventHandler OnEvent;
|
|
public event JanusEventHandler OnMessage;
|
|
public event JanusEventHandler OnJoined;
|
|
public event JanusEventHandler OnLeaving;
|
|
public event JanusEventHandler OnDisconnect;
|
|
#pragma warning restore CS0067,CS0414
|
|
public void ClearEventSubscriptions()
|
|
{
|
|
OnKeepAlive = null;
|
|
OnServerInfo = null;
|
|
OnTrickle = null;
|
|
OnHangup = null;
|
|
OnDetached = null;
|
|
OnError = null;
|
|
OnEvent = null;
|
|
OnMessage = null;
|
|
OnJoined = null;
|
|
OnLeaving = null;
|
|
OnDisconnect = null;
|
|
}
|
|
// ====================================================================
|
|
/// <summary>
|
|
/// In the REST API, events are returned by a long poll. This
|
|
/// starts the poll and calls the registed event handler when
|
|
/// an event is received.
|
|
/// </summary>
|
|
private void StartLongPoll()
|
|
{
|
|
bool running = true;
|
|
|
|
m_log.DebugFormat("{0} EventLongPoll", LogHeader);
|
|
Task.Run(async () => {
|
|
while (running && IsConnected)
|
|
{
|
|
try
|
|
{
|
|
var resp = await GetFromJanus(SessionUri);
|
|
if (resp is not null)
|
|
{
|
|
_ = Task.Run(() =>
|
|
{
|
|
EventResp eventResp = new EventResp(resp);
|
|
switch (resp.ReturnCode)
|
|
{
|
|
case "keepalive":
|
|
// These should happen every 30 seconds
|
|
// m_log.DebugFormat("{0} EventLongPoll: keepalive {1}", LogHeader, resp.ToString());
|
|
break;
|
|
case "server_info":
|
|
// Just info on the Janus instance
|
|
m_log.DebugFormat("{0} EventLongPoll: server_info {1}", LogHeader, resp.ToString());
|
|
break;
|
|
case "ack":
|
|
// 'ack' says the request was received and an event will follow
|
|
if (_MessageDetails) m_log.DebugFormat("{0} EventLongPoll: ack {1}", LogHeader, resp.ToString());
|
|
break;
|
|
case "success":
|
|
// success is a sync response that says the request was completed
|
|
if (_MessageDetails) m_log.DebugFormat("{0} EventLongPoll: success {1}", LogHeader, resp.ToString());
|
|
break;
|
|
case "trickle":
|
|
// got a trickle ICE candidate from Janus
|
|
// this is for reverse communication from Janus to the client and we don't do that
|
|
if (_MessageDetails) m_log.DebugFormat("{0} EventLongPoll: trickle {1}", LogHeader, resp.ToString());
|
|
OnTrickle?.Invoke(eventResp);
|
|
break;
|
|
case "webrtcup":
|
|
// ICE and DTLS succeeded, and so Janus correctly established a PeerConnection with the user/application;
|
|
m_log.DebugFormat("{0} EventLongPoll: webrtcup {1}", LogHeader, resp.ToString());
|
|
break;
|
|
case "hangup":
|
|
// The PeerConnection was closed, either by the user/application or by Janus itself;
|
|
// If one is in the room, when a "hangup" event happens, it means that the user left the room.
|
|
m_log.DebugFormat("{0} EventLongPoll: hangup {1}", LogHeader, resp.ToString());
|
|
OnHangup?.Invoke(eventResp);
|
|
break;
|
|
case "detached":
|
|
// a plugin asked the core to detach one of our handles
|
|
m_log.DebugFormat("{0} EventLongPoll: event {1}", LogHeader, resp.ToString());
|
|
OnDetached?.Invoke(eventResp);
|
|
break;
|
|
case "media":
|
|
// Janus is receiving (receiving: true/false) audio/video (type: "audio/video") on this PeerConnection;
|
|
m_log.DebugFormat("{0} EventLongPoll: media {1}", LogHeader, resp.ToString());
|
|
break;
|
|
case "slowlink":
|
|
// Janus detected a slowlink (uplink: true/false) on this PeerConnection;
|
|
m_log.DebugFormat("{0} EventLongPoll: slowlink {1}", LogHeader, resp.ToString());
|
|
break;
|
|
case "error":
|
|
m_log.DebugFormat("{0} EventLongPoll: error {1}", LogHeader, resp.ToString());
|
|
if (TryGetOutstandingRequest(resp.TransactionId, out OutstandingRequest outstandingRequest))
|
|
{
|
|
outstandingRequest.TaskCompletionSource.SetResult(resp);
|
|
}
|
|
else
|
|
{
|
|
OnError?.Invoke(eventResp);
|
|
m_log.ErrorFormat("{0} EventLongPoll: error with no transaction. {1}", LogHeader, resp.ToString());
|
|
}
|
|
break;
|
|
case "event":
|
|
if (_MessageDetails) m_log.DebugFormat("{0} EventLongPoll: event {1}", LogHeader, resp.ToString());
|
|
if (TryGetOutstandingRequest(resp.TransactionId, out OutstandingRequest outstandingRequest2))
|
|
{
|
|
// Someone is waiting for this event
|
|
outstandingRequest2.TaskCompletionSource.SetResult(resp);
|
|
}
|
|
else
|
|
{
|
|
m_log.ErrorFormat("{0} EventLongPoll: event no outstanding request {1}", LogHeader, resp.ToString());
|
|
OnEvent?.Invoke(eventResp);
|
|
}
|
|
break;
|
|
case "message":
|
|
m_log.DebugFormat("{0} EventLongPoll: message {1}", LogHeader, resp.ToString());
|
|
OnMessage?.Invoke(eventResp);
|
|
break;
|
|
case "timeout":
|
|
// Events for the audio bridge
|
|
m_log.DebugFormat("{0} EventLongPoll: timeout {1}", LogHeader, resp.ToString());
|
|
break;
|
|
case "joined":
|
|
// Events for the audio bridge
|
|
OnJoined?.Invoke(eventResp);
|
|
m_log.DebugFormat("{0} EventLongPoll: joined {1}", LogHeader, resp.ToString());
|
|
break;
|
|
case "leaving":
|
|
// Events for the audio bridge
|
|
OnLeaving?.Invoke(eventResp);
|
|
m_log.DebugFormat("{0} EventLongPoll: leaving {1}", LogHeader, resp.ToString());
|
|
break;
|
|
case "GETERROR":
|
|
// Special error response from the GET
|
|
var errorResp = new ErrorResp(resp);
|
|
switch (errorResp.errorCode)
|
|
{
|
|
case 404:
|
|
// "Not found" means there is a Janus server but the session is gone
|
|
m_log.ErrorFormat("{0} EventLongPoll: GETERROR Not Found. URI={1}: {2}",
|
|
LogHeader, SessionUri, resp.ToString());
|
|
break;
|
|
case 400:
|
|
// "Bad request" means the session is gone
|
|
m_log.ErrorFormat("{0} EventLongPoll: Bad Request. URI={1}: {2}",
|
|
LogHeader, SessionUri, resp.ToString());
|
|
break;
|
|
case 499:
|
|
// "Task canceled" means the long poll was canceled
|
|
m_log.DebugFormat("{0} EventLongPoll: Task canceled. URI={1}", LogHeader, SessionUri);
|
|
break;
|
|
default:
|
|
m_log.DebugFormat("{0} EventLongPoll: unknown response. URI={1}: {2}",
|
|
LogHeader, SessionUri, resp.ToString());
|
|
break;
|
|
}
|
|
// This will cause the long poll to exit
|
|
running = false;
|
|
OnDisconnect?.Invoke(eventResp);
|
|
break;
|
|
default:
|
|
m_log.DebugFormat("{0} EventLongPoll: unknown response {1}", LogHeader, resp.ToString());
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
m_log.ErrorFormat("{0} EventLongPoll: failed. Response is null", LogHeader);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// This will cause the long poll to exit
|
|
running = false;
|
|
m_log.ErrorFormat("{0} EventLongPoll: exception {1}", LogHeader, e);
|
|
}
|
|
}
|
|
m_log.InfoFormat("{0} EventLongPoll: Exiting long poll loop", LogHeader);
|
|
});
|
|
}
|
|
}
|
|
} |