mirror of
https://github.com/opensim/opensim.git
synced 2026-08-04 16:22:50 +08:00
some more changes adapted from Manni patch
This commit is contained in:
@@ -141,17 +141,17 @@ namespace osWebRtcVoice
|
||||
}
|
||||
public string sender
|
||||
{
|
||||
get { return m_message.TryGetString("sender", out string sender) ? sender : string.Empty; }
|
||||
get { return m_message is not null && m_message.TryGetString("sender", out string sender) ? sender : string.Empty; }
|
||||
}
|
||||
|
||||
public virtual string ToJson()
|
||||
{
|
||||
return m_message.ToString();
|
||||
return m_message is null ? "'null'": m_message.ToString();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return m_message.ToString();
|
||||
return m_message is null ? "'null'": m_message.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,7 +438,7 @@ namespace osWebRtcVoice
|
||||
{
|
||||
public OSDMap m_pluginData;
|
||||
public OSDMap m_data;
|
||||
public PluginMsgResp(JanusMessageResp pResp) : base(pResp.RawBody)
|
||||
public PluginMsgResp(JanusMessageResp pResp) : base(pResp?.RawBody)
|
||||
{
|
||||
if (m_message is not null && m_message.TryGetOSDMap("plugindata", out m_pluginData))
|
||||
{
|
||||
|
||||
@@ -446,18 +446,19 @@ namespace osWebRtcVoice
|
||||
}
|
||||
];
|
||||
resp = await viewerSession.Session.TrickleCandidates(viewerSession, candidatesArray);
|
||||
_log.DebugFormat("{0} VoiceSignalingRequest: single candidate", LogHeader);
|
||||
_log.Debug($"{LogHeader} VoiceSignalingRequest: single candidate");
|
||||
}
|
||||
}
|
||||
else if (pRequest.TryGetOSDArray("candidates", out OSDArray candidates))
|
||||
{
|
||||
OSDArray candidatesArray = [];
|
||||
int sourceCount = candidates.Count;
|
||||
int candidateLimit = _MaxSignalingCandidatesPerRequest;
|
||||
//int sourceCount = candidates.Count;
|
||||
//int candidateLimit = _MaxSignalingCandidatesPerRequest;
|
||||
foreach (OSDMap cand in candidates)
|
||||
{
|
||||
if (candidateLimit > 0 && candidatesArray.Count >= candidateLimit)
|
||||
break;
|
||||
// TODO: can not limit candidates blindly
|
||||
// if (candidateLimit > 0 && candidatesArray.Count >= candidateLimit)
|
||||
// break;
|
||||
|
||||
candidatesArray.Add(new OSDMap() {
|
||||
{ "candidate", cand["candidate"].AsString() },
|
||||
@@ -466,15 +467,15 @@ namespace osWebRtcVoice
|
||||
});
|
||||
}
|
||||
resp = await viewerSession.Session.TrickleCandidates(viewerSession, candidatesArray).ConfigureAwait(false);
|
||||
if (candidateLimit > 0 && sourceCount > candidatesArray.Count)
|
||||
{
|
||||
_log.WarnFormat("{0} VoiceSignalingRequest: capped candidates {1}/{2} (MaxSignalingCandidatesPerRequest={3})",
|
||||
LogHeader, candidatesArray.Count, sourceCount, candidateLimit);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if (candidateLimit > 0 && sourceCount > candidatesArray.Count)
|
||||
// {
|
||||
// _log.WarnFormat("{0} VoiceSignalingRequest: capped candidates {1}/{2} (MaxSignalingCandidatesPerRequest={3})",
|
||||
// LogHeader, candidatesArray.Count, sourceCount, candidateLimit);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
_log.DebugFormat("{0} VoiceSignalingRequest: {1} candidates", LogHeader, candidatesArray.Count);
|
||||
}
|
||||
// }
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -48,11 +48,7 @@ namespace osWebRtcVoice
|
||||
}
|
||||
public string ViewerSessionID { get; set; }
|
||||
public IWebRtcVoiceService VoiceService { get; set; }
|
||||
public string VoiceServiceSessionId
|
||||
{
|
||||
get => throw new System.NotImplementedException();
|
||||
set => throw new System.NotImplementedException();
|
||||
}
|
||||
public string VoiceServiceSessionId { get; set; }
|
||||
public UUID RegionId { get; set; }
|
||||
public UUID AgentId { get; set; }
|
||||
|
||||
@@ -60,6 +56,7 @@ namespace osWebRtcVoice
|
||||
// ViewerSessions hold the connection information for the client connection through to the voice service.
|
||||
// This collection is static and is simulator wide so there will be sessions for all regions and all clients.
|
||||
public static Dictionary<string, IVoiceViewerSession> ViewerSessions = new Dictionary<string, IVoiceViewerSession>();
|
||||
|
||||
// Get a viewer session by the viewer session ID
|
||||
public static bool TryGetViewerSession(string pViewerSessionId, out IVoiceViewerSession pViewerSession)
|
||||
{
|
||||
@@ -68,6 +65,7 @@ namespace osWebRtcVoice
|
||||
return ViewerSessions.TryGetValue(pViewerSessionId, out pViewerSession);
|
||||
}
|
||||
}
|
||||
|
||||
// public static bool TryGetViewerSessionByAgentId(UUID pAgentId, out IVoiceViewerSession pViewerSession)
|
||||
public static bool TryGetViewerSessionByAgentId(UUID pAgentId, out IEnumerable<KeyValuePair<string, IVoiceViewerSession>> pViewerSessions)
|
||||
{
|
||||
@@ -77,6 +75,7 @@ namespace osWebRtcVoice
|
||||
return pViewerSessions.Count() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Get a viewer session by the VoiceService session ID
|
||||
public static bool TryGetViewerSessionByVSSessionId(string pVSSessionId, out IVoiceViewerSession pViewerSession)
|
||||
{
|
||||
@@ -92,6 +91,22 @@ namespace osWebRtcVoice
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetViewerSessionByAgentAndRegion(UUID pAgentId, UUID pRegionId, out IVoiceViewerSession pViewerSession)
|
||||
{
|
||||
lock (ViewerSessions)
|
||||
{
|
||||
IVoiceViewerSession session = ViewerSessions.Values.FirstOrDefault(v => v.AgentId == pAgentId && v.RegionId == pRegionId);
|
||||
if (session is not null)
|
||||
{
|
||||
pViewerSession = session;
|
||||
return true;
|
||||
}
|
||||
pViewerSession = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddViewerSession(IVoiceViewerSession pSession)
|
||||
{
|
||||
lock (ViewerSessions)
|
||||
@@ -125,8 +140,11 @@ namespace osWebRtcVoice
|
||||
|
||||
public Task Shutdown()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
if (!string.IsNullOrEmpty(ViewerSessionID))
|
||||
{
|
||||
RemoveViewerSession(ViewerSessionID);
|
||||
}
|
||||
return Task.CompletedTask; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,8 @@ namespace osWebRtcVoice
|
||||
|
||||
public WebRtcVoiceServerConnector(IConfigSource pConfig, IHttpServer pServer, string pConfigName)
|
||||
{
|
||||
// WebRtcDebugControl.ApplyFromConfig(pConfig);
|
||||
|
||||
IConfig moduleConfig = pConfig.Configs["WebRtcVoice"];
|
||||
|
||||
if (moduleConfig is not null)
|
||||
@@ -72,15 +74,13 @@ namespace osWebRtcVoice
|
||||
// The local service provides the IWebRtcVoiceService interface and directs the requests
|
||||
// to the WebRTC service.
|
||||
string localServiceModule = moduleConfig.GetString("LocalServiceModule", "WebRtcVoiceServiceModule.dll:WebRtcVoiceServiceModule");
|
||||
m_log.DebugFormat("{0} loading {1}", LogHeader, localServiceModule);
|
||||
|
||||
object[] args = new object[0];
|
||||
m_WebRtcVoiceService = ServerUtils.LoadPlugin<IWebRtcVoiceService>(localServiceModule, args);
|
||||
m_log.Debug($"{LogHeader} loading {localServiceModule}");
|
||||
m_WebRtcVoiceService = ServerUtils.LoadPlugin<IWebRtcVoiceService>(localServiceModule, []);
|
||||
|
||||
// The WebRtcVoiceServiceModule is both an IWebRtcVoiceService and a ISharedRegionModule
|
||||
// so we can initialize it as if it was the region module.
|
||||
ISharedRegionModule sharedModule = m_WebRtcVoiceService as ISharedRegionModule;
|
||||
if (sharedModule is null)
|
||||
if (m_WebRtcVoiceService is not ISharedRegionModule sharedModule)
|
||||
{
|
||||
m_log.ErrorFormat("{0} local service module does not implement ISharedRegionModule", LogHeader);
|
||||
m_Enabled = false;
|
||||
@@ -98,7 +98,7 @@ namespace osWebRtcVoice
|
||||
private bool Handle_ProvisionVoiceAccountRequest(OSDMap pJson, ref JsonRpcResponse pResponse)
|
||||
{
|
||||
bool ret = false;
|
||||
m_log.DebugFormat("{0} Handle_ProvisionVoiceAccountRequest", LogHeader);
|
||||
m_log.Debug($"{LogHeader} Handle_ProvisionVoiceAccountRequest");
|
||||
if (m_MessageDetails) m_log.DebugFormat("{0} PVAR: req={1}", LogHeader, pJson.ToString());
|
||||
|
||||
if (pJson.ContainsKey("params") && pJson["params"] is OSDMap paramsMap)
|
||||
|
||||
@@ -44,14 +44,16 @@ namespace osWebRtcVoice
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private static readonly string LogHeader = "[WEBRTC VOICE SERVICE CONNECTOR]";
|
||||
private bool m_Enabled = false;
|
||||
private bool m_MessageDetails = false;
|
||||
private readonly bool m_Enabled = false;
|
||||
private readonly bool m_MessageDetails = false;
|
||||
private IConfigSource m_Config;
|
||||
|
||||
string m_serverURI = "http://localhost:8080";
|
||||
|
||||
public WebRtcVoiceServiceConnector(IConfigSource pConfig)
|
||||
{
|
||||
// WebRtcDebugControl.ApplyFromConfig(pConfig);
|
||||
|
||||
m_Config = pConfig;
|
||||
IConfig moduleConfig = m_Config.Configs["WebRtcVoice"];
|
||||
|
||||
@@ -65,13 +67,12 @@ namespace osWebRtcVoice
|
||||
{
|
||||
m_log.Error($"{LogHeader} WebRtcVoiceServiceConnector enabled but no WebRtcVoiceServerURI specified");
|
||||
m_Enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_log.Info($"{LogHeader} WebRtcVoiceServiceConnector enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
m_MessageDetails = moduleConfig.GetBoolean("MessageDetails", false);
|
||||
|
||||
m_log.Info($"{LogHeader} WebRtcVoiceServiceConnector enabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,7 +83,7 @@ namespace osWebRtcVoice
|
||||
public IVoiceViewerSession CreateViewerSession(OSDMap pRequest, UUID pUserID, UUID pSceneID)
|
||||
{
|
||||
m_log.Debug($"{LogHeader} CreateViewerSession");
|
||||
return new VoiceViewerSession(this, pUserID, pSceneID);
|
||||
return new VoiceViewerSession(this, pSceneID, pUserID);
|
||||
}
|
||||
|
||||
public OSDMap ProvisionVoiceAccountRequest(OSDMap pRequest, UUID pUserID, UUID pSceneID)
|
||||
@@ -122,7 +123,7 @@ namespace osWebRtcVoice
|
||||
|
||||
public OSDMap VoiceSignalingRequest(IVoiceViewerSession pVSession, OSDMap pRequest, UUID pUserID, UUID pSceneID)
|
||||
{
|
||||
m_log.DebugFormat("{0} VoiceSignalingRequest. uID={1}, sID={2}", LogHeader, pUserID, pSceneID);
|
||||
m_log.Debug($"{LogHeader} VoiceSignalingRequest. uID={pUserID}, sID={pSceneID}");
|
||||
OSDMap req = new()
|
||||
{
|
||||
{ "request", pRequest },
|
||||
@@ -139,66 +140,74 @@ namespace osWebRtcVoice
|
||||
if(string.IsNullOrWhiteSpace(uri))
|
||||
return null;
|
||||
|
||||
OSDMap request = new()
|
||||
OSDMap request = new()
|
||||
{
|
||||
{ "jsonrpc", OSD.FromString("2.0") },
|
||||
{ "id", OSD.FromString(jsonId) },
|
||||
{ "method", OSD.FromString(method) },
|
||||
{ "params", pParams }
|
||||
};
|
||||
|
||||
OSDMap outerResponse = null;
|
||||
try
|
||||
{
|
||||
if (m_MessageDetails) m_log.Debug($"{LogHeader}: request: {request}");
|
||||
|
||||
outerResponse = WebUtil.PostToService(uri, request, 10000, true);
|
||||
|
||||
if (m_MessageDetails) m_log.Debug($"{LogHeader}: response: {outerResponse}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.Error($"{LogHeader}: JsonRpc request '{method}' to {uri} failed: {e.Message}");
|
||||
m_log.Debug($"{LogHeader}: request: {request}");
|
||||
return new OSDMap()
|
||||
{
|
||||
{ "jsonrpc", OSD.FromString("2.0") },
|
||||
{ "id", OSD.FromString(jsonId) },
|
||||
{ "method", OSD.FromString(method) },
|
||||
{ "params", pParams }
|
||||
{ "error", OSD.FromString(e.Message) }
|
||||
};
|
||||
}
|
||||
|
||||
OSDMap outerResponse = null;
|
||||
try
|
||||
if (outerResponse is null || outerResponse.Count == 0)
|
||||
{
|
||||
string errm = $"JsonRpc request '{method}' to {uri} returned an empty response";
|
||||
m_log.Error(errm);
|
||||
return new OSDMap()
|
||||
{
|
||||
if (m_MessageDetails) m_log.Debug($"{LogHeader}: request: {request}");
|
||||
{ "error", errm }
|
||||
};
|
||||
}
|
||||
|
||||
outerResponse = WebUtil.PostToService(uri, request, 10000, true);
|
||||
|
||||
if (m_MessageDetails) m_log.Debug($"{LogHeader}: response: {outerResponse}");
|
||||
}
|
||||
catch (Exception e)
|
||||
if (!outerResponse.TryGetOSDMap("_Result", out OSDMap response))
|
||||
{
|
||||
string errm = $"JsonRpc request '{method}' to {uri} returned an invalid response: {OSDParser.SerializeJsonString(outerResponse)}";
|
||||
m_log.Error(errm);
|
||||
return new OSDMap()
|
||||
{
|
||||
m_log.Error($"{LogHeader}: JsonRpc request '{method}' to {uri} failed: {e.Message}");
|
||||
m_log.Debug($"{LogHeader}: request: {request}");
|
||||
return new OSDMap()
|
||||
{
|
||||
{ "error", OSD.FromString(e.Message) }
|
||||
};
|
||||
}
|
||||
{ "error", errm }
|
||||
};
|
||||
}
|
||||
|
||||
if (!outerResponse.TryGetOSDMap("_Result", out OSDMap response))
|
||||
if (response.TryGetValue("error", out OSD osdtmp))
|
||||
{
|
||||
string errm = $"JsonRpc request '{method}' to {uri} returned an error: {OSDParser.SerializeJsonString(osdtmp)}";
|
||||
m_log.Error(errm);
|
||||
return new OSDMap()
|
||||
{
|
||||
string errm = $"JsonRpc request '{method}' to {1} returned an invalid response: {OSDParser.SerializeJsonString(outerResponse)}";
|
||||
m_log.Error(errm);
|
||||
return new OSDMap()
|
||||
{
|
||||
{ "error", errm }
|
||||
};
|
||||
}
|
||||
{ "error", errm }
|
||||
};
|
||||
}
|
||||
|
||||
OSD osdtmp;
|
||||
if (response.TryGetValue("error", out osdtmp))
|
||||
if (!response.TryGetOSDMap("result", out OSDMap resultmap ))
|
||||
{
|
||||
string errm = $"JsonRpc request '{method}' to {uri} returned result as non-OSDMap: {OSDParser.SerializeJsonString(outerResponse)}";
|
||||
m_log.Error(errm);
|
||||
return new OSDMap()
|
||||
{
|
||||
string errm = $"JsonRpc request '{method}' to {uri} returned an error: {OSDParser.SerializeJsonString(osdtmp)}";
|
||||
m_log.Error(errm);
|
||||
return new OSDMap()
|
||||
{
|
||||
{ "error", errm }
|
||||
};
|
||||
}
|
||||
|
||||
if (!response.TryGetOSDMap("result", out OSDMap resultmap ))
|
||||
{
|
||||
string errm = $"JsonRpc request '{method}' to {uri} returned result as non-OSDMap: {OSDParser.SerializeJsonString(outerResponse)}";
|
||||
m_log.Error(errm);
|
||||
return new OSDMap()
|
||||
{
|
||||
{ "error", errm }
|
||||
};
|
||||
}
|
||||
{ "error", errm }
|
||||
};
|
||||
}
|
||||
|
||||
return resultmap;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ namespace osWebRtcVoice
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private static readonly string logHeader = "[REGION WEBRTC VOICE]";
|
||||
|
||||
private static byte[] llsdUndefAnswerBytes = Util.UTF8.GetBytes("<llsd><undef /></llsd>");
|
||||
private static byte[] llsdUndefAnswerBytes = Util.UTF8.GetBytes("<llsd><undef /></llsd>");
|
||||
private bool _MessageDetails = false;
|
||||
|
||||
// Control info
|
||||
@@ -79,6 +79,8 @@ namespace osWebRtcVoice
|
||||
// ISharedRegionModule.Initialize
|
||||
public void Initialise(IConfigSource config)
|
||||
{
|
||||
// WebRtcDebugControl.ApplyFromConfig(config);
|
||||
|
||||
m_Config = config.Configs["WebRtcVoice"];
|
||||
if (m_Config is not null)
|
||||
{
|
||||
@@ -100,7 +102,7 @@ namespace osWebRtcVoice
|
||||
// ISharedRegionModule.AddRegion
|
||||
public void AddRegion(Scene scene)
|
||||
{
|
||||
// todo register module to get parcels changes etc
|
||||
// TODO: register module to get parcels changes etc
|
||||
}
|
||||
|
||||
// ISharedRegionModule.RemoveRegion
|
||||
@@ -161,7 +163,7 @@ namespace osWebRtcVoice
|
||||
public void OnRegisterCaps(Scene scene, UUID agentID, Caps caps)
|
||||
{
|
||||
m_log.Debug(
|
||||
$"{logHeader}: OnRegisterCaps called with agentID {agentID} caps {caps} in scene {scene.Name}");
|
||||
$"{logHeader}: OnRegisterCaps called with agentID {agentID} in scene {scene.Name}");
|
||||
|
||||
caps.RegisterSimpleHandler("ProvisionVoiceAccountRequest",
|
||||
new SimpleStreamHandler("/" + UUID.Random(), (IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) =>
|
||||
@@ -224,7 +226,10 @@ namespace osWebRtcVoice
|
||||
{
|
||||
if (vstosd is OSDString vst && !((string)vst).Equals("webrtc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
m_log.Warn($"{logHeader}[ProvisionVoice]: voice_server_type is not 'webrtc'. Request: {map}");
|
||||
m_log.Warn($"{logHeader}[ProvisionVoice]: voice_server_type is not 'webrtc'");
|
||||
if (m_log.IsDebugEnabled)
|
||||
m_log.Warn($"{logHeader}[ProvisionVoice]: Request detail: {map}");
|
||||
|
||||
response.RawBuffer = llsdUndefAnswerBytes;
|
||||
response.StatusCode = (int)HttpStatusCode.OK;
|
||||
return;
|
||||
@@ -289,6 +294,9 @@ namespace osWebRtcVoice
|
||||
|
||||
if ((land.Flags & (uint)ParcelFlags.UseEstateVoiceChan) != 0)
|
||||
{
|
||||
// By removing the parcel_local_id, the voice service will treat this as an estate channel
|
||||
// request and return the appropriate voice credentials for the estate channel
|
||||
// instead of a parcel channel
|
||||
map.Remove("parcel_local_id"); // estate channel
|
||||
}
|
||||
else if(parcel.IsRestrictedFromLand(agentID) || parcel.IsBannedFromLand(agentID))
|
||||
@@ -330,7 +338,7 @@ namespace osWebRtcVoice
|
||||
IWebRtcVoiceService voiceService = scene.RequestModuleInterface<IWebRtcVoiceService>();
|
||||
if (voiceService is null)
|
||||
{
|
||||
m_log.Error($"{logHeader}[VoiceSignalingRequest]: avatar \"{agentID}\": no voice service");
|
||||
m_log.Error($"{logHeader}[VoiceSignalingRequest]: No voice service, Agent={agentID}");
|
||||
response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
return;
|
||||
}
|
||||
@@ -366,8 +374,8 @@ namespace osWebRtcVoice
|
||||
|
||||
if (_MessageDetails) m_log.Debug($"{logHeader}[VoiceSignalingRequest]: Response: {resp}");
|
||||
|
||||
// TODO: check for errors and package the response
|
||||
|
||||
// TODO: check for errors
|
||||
// viewers ignore response
|
||||
response.RawBuffer = llsdUndefAnswerBytes;
|
||||
response.StatusCode = (int)HttpStatusCode.OK;
|
||||
return;
|
||||
@@ -480,9 +488,9 @@ namespace osWebRtcVoice
|
||||
{
|
||||
try
|
||||
{
|
||||
using Stream inputStream = request.InputStream;
|
||||
if (inputStream.Length > 0)
|
||||
{
|
||||
if (request.InputStream.Length > 0)
|
||||
{
|
||||
using Stream inputStream = request.InputStream;
|
||||
OSD tmp = OSDParser.DeserializeLLSDXml(inputStream);
|
||||
if (_MessageDetails)
|
||||
m_log.Debug($"{pCaller} BodyToMap: Request: {tmp}");
|
||||
|
||||
@@ -74,6 +74,8 @@ namespace osWebRtcVoice
|
||||
// Get configuration and load the modules that will handle spatial and non-spatial voice.
|
||||
public void Initialise(IConfigSource pConfig)
|
||||
{
|
||||
// WebRtcDebugControl.ApplyFromConfig(pConfig);
|
||||
|
||||
m_Config = pConfig;
|
||||
IConfig moduleConfig = m_Config.Configs["WebRtcVoice"];
|
||||
|
||||
@@ -83,6 +85,11 @@ namespace osWebRtcVoice
|
||||
if (m_Enabled)
|
||||
{
|
||||
// Get the DLLs for the two voice services
|
||||
// TODO: spacial/nonspacial names are wrong
|
||||
// spacial here means service for region parcels, that can be spacial or not
|
||||
// non spacial means for other uses like IMs, that just happen to be non spacial
|
||||
// in fact this needs more consideration than just this 2 options
|
||||
|
||||
string spatialDllName = moduleConfig.GetString("SpatialVoiceService", string.Empty);
|
||||
string nonSpatialDllName = moduleConfig.GetString("NonSpatialVoiceService", string.Empty);
|
||||
if (string.IsNullOrEmpty(spatialDllName) && string.IsNullOrEmpty(nonSpatialDllName))
|
||||
@@ -205,49 +212,206 @@ namespace osWebRtcVoice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetViewerSessionByAgentAndScene(UUID pAgentID, UUID pSceneID, out IVoiceViewerSession pViewerSession)
|
||||
{
|
||||
if (VoiceViewerSession.TryGetViewerSessionByAgentId(pAgentID, out IEnumerable<KeyValuePair<string, IVoiceViewerSession>> vSessions))
|
||||
{
|
||||
foreach (KeyValuePair<string, IVoiceViewerSession> v in vSessions)
|
||||
{
|
||||
if (v.Value.RegionId == pSceneID)
|
||||
{
|
||||
pViewerSession = v.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
pViewerSession = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static List<KeyValuePair<string, IVoiceViewerSession>> GetViewerSessionsByAgentAndScene(UUID pAgentID, UUID pSceneID)
|
||||
{
|
||||
List<KeyValuePair<string, IVoiceViewerSession>> matches = [];
|
||||
if (VoiceViewerSession.TryGetViewerSessionByAgentId(pAgentID, out IEnumerable<KeyValuePair<string, IVoiceViewerSession>> vSessions))
|
||||
{
|
||||
foreach (KeyValuePair<string, IVoiceViewerSession> v in vSessions)
|
||||
{
|
||||
if (v.Value.RegionId == pSceneID)
|
||||
{
|
||||
matches.Add(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
private static object TryGetPropertyValue(object pSource, string pPropertyName)
|
||||
{
|
||||
if (pSource is null || string.IsNullOrEmpty(pPropertyName))
|
||||
return null;
|
||||
|
||||
PropertyInfo propertyInfo = pSource.GetType().GetProperty(pPropertyName);
|
||||
if (propertyInfo is null)
|
||||
return null;
|
||||
|
||||
return propertyInfo.GetValue(pSource);
|
||||
}
|
||||
|
||||
private static bool IsViewerSessionReusable(IVoiceViewerSession pViewerSession)
|
||||
{
|
||||
if (pViewerSession is null)
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrEmpty(pViewerSession.ViewerSessionID) || string.IsNullOrEmpty(pViewerSession.VoiceServiceSessionId))
|
||||
return false;
|
||||
|
||||
object disconnectReason = TryGetPropertyValue(pViewerSession, "DisconnectReason");
|
||||
if (disconnectReason is string reason && !string.IsNullOrEmpty(reason))
|
||||
return false;
|
||||
|
||||
object sessionObj = TryGetPropertyValue(pViewerSession, "Session");
|
||||
if (sessionObj is not null)
|
||||
{
|
||||
object isConnectedObj = TryGetPropertyValue(sessionObj, "IsConnected");
|
||||
if (isConnectedObj is bool isConnected && !isConnected)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CleanupDuplicateSessions(UUID pAgentID, UUID pSceneID, string pKeepViewerSessionId)
|
||||
{
|
||||
List<KeyValuePair<string, IVoiceViewerSession>> candidates = GetViewerSessionsByAgentAndScene(pAgentID, pSceneID);
|
||||
foreach (KeyValuePair<string, IVoiceViewerSession> candidate in candidates)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(pKeepViewerSessionId) && candidate.Key == pKeepViewerSessionId)
|
||||
continue;
|
||||
|
||||
m_log.Warn(
|
||||
$"{LogHeader} CleanupDuplicateSessions: removing stale viewer_session {candidate.Key} for agent {pAgentID}, scene {pSceneID}");
|
||||
|
||||
VoiceViewerSession.RemoveViewerSession(candidate.Key);
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await candidate.Value.Shutdown();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
m_log.Debug(
|
||||
$"{LogHeader} CleanupDuplicateSessions: shutdown failed for viewer_session {candidate.Key}: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
private bool TryGetReusableViewerSession(UUID pAgentID, UUID pSceneID, out IVoiceViewerSession pViewerSession)
|
||||
{
|
||||
List<KeyValuePair<string, IVoiceViewerSession>> sessions = GetViewerSessionsByAgentAndScene(pAgentID, pSceneID);
|
||||
foreach (KeyValuePair<string, IVoiceViewerSession> candidate in sessions)
|
||||
{
|
||||
if (IsViewerSessionReusable(candidate.Value))
|
||||
{
|
||||
pViewerSession = candidate.Value;
|
||||
CleanupDuplicateSessions(pAgentID, pSceneID, candidate.Key);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (sessions.Count > 0)
|
||||
{
|
||||
// No reusable session found: remove all stale sessions to force a clean create path.
|
||||
CleanupDuplicateSessions(pAgentID, pSceneID, null);
|
||||
}
|
||||
|
||||
pViewerSession = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// IWebRtcVoiceService
|
||||
|
||||
// IWebRtcVoiceService.ProvisionVoiceAccountRequest
|
||||
public OSDMap ProvisionVoiceAccountRequest(OSDMap pRequest, UUID pUserID, UUID pSceneID)
|
||||
{
|
||||
OSDMap response = null;
|
||||
IVoiceViewerSession vSession = null;
|
||||
if (pRequest.TryGetString("viewer_session", out string viewerSessionId))
|
||||
{
|
||||
// request has a viewer session. Use that to find the voice service
|
||||
if (!VoiceViewerSession.TryGetViewerSession(viewerSessionId, out vSession))
|
||||
if (VoiceViewerSession.TryGetViewerSession(viewerSessionId, out vSession))
|
||||
{
|
||||
m_log.Error($"{0} ProvisionVoiceAccountRequest: viewer session {viewerSessionId} not found");
|
||||
CleanupDuplicateSessions(pUserID, pSceneID, viewerSessionId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TryGetReusableViewerSession(pUserID, pSceneID, out vSession))
|
||||
m_log.Info(
|
||||
$"{LogHeader} ProvisionVoiceAccountRequest: viewer session {viewerSessionId} not found, reconnect fallback reused {vSession.ViewerSessionID}");
|
||||
else
|
||||
m_log.Error($"{LogHeader} ProvisionVoiceAccountRequest: viewer session {viewerSessionId} not found");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the request does not have a viewer session. See if it's an initial request
|
||||
if (pRequest.TryGetString("channel_type", out string channelType))
|
||||
{
|
||||
if (channelType == "local")
|
||||
if (TryGetReusableViewerSession(pUserID, pSceneID, out vSession))
|
||||
{
|
||||
// TODO: check if this userId is making a new session (case that user is reconnecting)
|
||||
vSession = m_spatialVoiceService.CreateViewerSession(pRequest, pUserID, pSceneID);
|
||||
VoiceViewerSession.AddViewerSession(vSession);
|
||||
m_log.Info(
|
||||
$"{LogHeader} ProvisionVoiceAccountRequest: reconnect reuse for agent {pUserID}, scene {pSceneID}, viewer_session {vSession.ViewerSessionID}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: check if this userId is making a new session (case that user is reconnecting)
|
||||
vSession = m_nonSpatialVoiceService.CreateViewerSession(pRequest, pUserID, pSceneID);
|
||||
VoiceViewerSession.AddViewerSession(vSession);
|
||||
// Ensure stale sessions are cleared before creating a new one.
|
||||
CleanupDuplicateSessions(pUserID, pSceneID, null);
|
||||
if (channelType == "local")
|
||||
{
|
||||
// TODO: check if this userId is making a new session (case that user is reconnecting)
|
||||
vSession = m_spatialVoiceService.CreateViewerSession(pRequest, pUserID, pSceneID);
|
||||
VoiceViewerSession.AddViewerSession(vSession);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: check if this userId is making a new session (case that user is reconnecting)
|
||||
vSession = m_nonSpatialVoiceService.CreateViewerSession(pRequest, pUserID, pSceneID);
|
||||
VoiceViewerSession.AddViewerSession(vSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_log.Error($"{LogHeader} ProvisionVoiceAccountRequest: no channel_type in request");
|
||||
if (TryGetReusableViewerSession(pUserID, pSceneID, out vSession))
|
||||
{
|
||||
m_log.Info(
|
||||
$"{LogHeader} ProvisionVoiceAccountRequest: missing channel_type, reused existing session for agent {pUserID}, scene {pSceneID}, viewer_session {vSession.ViewerSessionID}");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_log.Error(
|
||||
$"{LogHeader} ProvisionVoiceAccountRequest: no channel_type in request and no existing session for agent {pUserID}, scene {pSceneID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OSDMap response = null;
|
||||
|
||||
if (vSession is not null)
|
||||
{
|
||||
response = vSession.VoiceService.ProvisionVoiceAccountRequest(vSession, pRequest, pUserID, pSceneID);
|
||||
}
|
||||
|
||||
if (response is null)
|
||||
{
|
||||
return new OSDMap
|
||||
{
|
||||
{ "response", "error" },
|
||||
{ "message", "Unable to provision voice session (missing viewer_session/channel_type or session not found)" }
|
||||
};
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -265,12 +429,12 @@ namespace osWebRtcVoice
|
||||
}
|
||||
else
|
||||
{
|
||||
m_log.ErrorFormat("{0} VoiceSignalingRequest: viewer session {1} not found", LogHeader, viewerSessionId);
|
||||
m_log.Error($"{LogHeader} VoiceSignalingRequest: viewer session {viewerSessionId} not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_log.ErrorFormat("{0} VoiceSignalingRequest: no viewer_session in request", LogHeader);
|
||||
m_log.Error($"{LogHeader} VoiceSignalingRequest: no viewer_session in request");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user