webrtc change ID and desc of rooms, add pin to multiuser

This commit is contained in:
UbitUmarov
2026-03-26 21:36:11 +00:00
parent f024cd21d8
commit f83ec8bebd
6 changed files with 99 additions and 74 deletions

View File

@@ -84,12 +84,12 @@ namespace osWebRtcVoice
/// <param name="pSpatial">boolean on whether room will be spatial or non-spatial</param>
/// <param name="pRoomDesc">added as "description" to the created room</param>
/// <returns></returns>
public async Task<JanusRoom> CreateRoom(int pRoomId, bool pSpatial, string pRoomDesc)
public async Task<JanusRoom> CreateRoom(int pRoomId, bool pSpatial, string pRoomDesc, string credentials)
{
JanusRoom ret = null;
try
{
JanusMessageResp resp = await SendPluginMsg(new AudioBridgeCreateRoomReq(pRoomId, pSpatial, pRoomDesc)).ConfigureAwait(false);
JanusMessageResp resp = await SendPluginMsg(new AudioBridgeCreateRoomReq(pRoomId, pSpatial, pRoomDesc, credentials)).ConfigureAwait(false);
AudioBridgeResp abResp = new(resp);
m_log.Debug($"{LogHeader} CreateRoom. ReturnCode: '{abResp.AudioBridgeReturnCode}'");
@@ -156,7 +156,7 @@ namespace osWebRtcVoice
// The attempt is to deterministicly create a room number so all regions will generate the
// same room number across sessions and across the grid.
// getHashCode() is not deterministic across sessions.
public static int CalcRoomNumber(string pRegionId, string pChannelType, int pParcelLocalID, string pChannelID)
public static int CalcRoomNumber(string regionID, string gridhash, string pChannelType, int pParcelLocalID, string pChannelID)
{
BHasherMdjb2 hasher = new();
// If there is a channel specified it must be group
@@ -164,13 +164,12 @@ namespace osWebRtcVoice
{
case "local":
// A "local" channel is unique to the region and parcel
hasher.Add(pRegionId);
hasher.Add(regionID);
hasher.Add(pChannelType);
hasher.Add(pParcelLocalID);
break;
case "multiagent":
// A "multiagent" channel is unique to the grid
// should add a GridId here
hasher.Add(gridhash);
hasher.Add(pChannelID);
hasher.Add(pChannelType);
break;
@@ -184,61 +183,61 @@ namespace osWebRtcVoice
return roomNumber;
}
public async Task<JanusRoom> SelectRoom(string pRegionId, string pChannelType, bool pSpatial, int pParcelLocalID, string pChannelID)
public async Task<JanusRoom> SelectRoom(string pRegionId, string pgridhash, string pChannelType, bool pSpatial, int pParcelLocalID, string pChannelID, string credentials)
{
int roomNumber = CalcRoomNumber(pRegionId, pChannelType, pParcelLocalID, pChannelID);
int roomNumber = CalcRoomNumber(pRegionId, pgridhash, pChannelType, pParcelLocalID, pChannelID);
// Should be unique for the given use and channel type
m_log.DebugFormat("{0} SelectRoom: roomNumber={1}", LogHeader, roomNumber);
m_log.Debug($"{LogHeader} SelectRoom: roomNumber={roomNumber}");
// Check to see if the room has already been created
JanusRoom existingRoom;
lock (_rooms)
{
if (_rooms.ContainsKey(roomNumber))
if (_rooms.TryGetValue(roomNumber, out existingRoom))
{
return _rooms[roomNumber];
return existingRoom;
}
}
// The room doesn't exist. Create it.
string roomDesc = pRegionId + "/" + pChannelType + "/" + pParcelLocalID + "/" + pChannelID;
JanusRoom ret = await CreateRoom(roomNumber, pSpatial, roomDesc).ConfigureAwait(false);
string roomDesc;
if(pChannelType == "local")
roomDesc = $"{pRegionId}/{pChannelType}/{pParcelLocalID}";
else
roomDesc = $"{pgridhash}/{pChannelType}/{pChannelID}";
JanusRoom ret = await CreateRoom(roomNumber, pSpatial, roomDesc, credentials).ConfigureAwait(false);
JanusRoom existingRoom = null;
if (ret is not null)
{
lock (_rooms)
{
if (_rooms.ContainsKey(roomNumber))
if(!_rooms.TryGetValue(roomNumber, out existingRoom))
{
// If the room was created while we were waiting,
existingRoom = _rooms[roomNumber];
}
else
{
// Our room is the first one created. Save it.
_rooms[roomNumber] = ret;
}
}
}
if (existingRoom is not null)
{
// The room we created was already created by someone else. Delete ours and use the existing one
await DestroyRoom(ret);
ret = existingRoom;
await DestroyRoom(ret).ConfigureAwait(false);
return existingRoom;
}
return ret;
}
// Return the room with the given room ID or 'null' if no such room
public JanusRoom GetRoom(int pRoomId)
public bool GetRoom(int pRoomId, out JanusRoom room)
{
JanusRoom ret = null;
lock (_rooms)
{
_rooms.TryGetValue(pRoomId, out ret);
return _rooms.TryGetValue(pRoomId, out room);
}
return ret;
}
public override void Handle_Event(JanusMessageResp pResp)
@@ -250,8 +249,8 @@ namespace osWebRtcVoice
// An audio bridge event!
m_log.DebugFormat("{0} Handle_Event. {1}", LogHeader, abResp.ToString());
}
}
public override void Handle_Message(JanusMessageResp pResp)
{
base.Handle_Message(pResp);
@@ -261,7 +260,6 @@ namespace osWebRtcVoice
// An audio bridge event!
m_log.DebugFormat("{0} Handle_Event. {1}", LogHeader, abResp.ToString());
}
}
}
}

View File

@@ -514,11 +514,11 @@ namespace osWebRtcVoice
// ==============================================================
public class AudioBridgeCreateRoomReq : PluginMsgReq
{
public AudioBridgeCreateRoomReq(int pRoomId) : this(pRoomId, false, null)
public AudioBridgeCreateRoomReq(int pRoomId) : this(pRoomId, false, null, null)
{
}
public AudioBridgeCreateRoomReq(int pRoomId, bool pSpatial, string pDesc) : base(new OSDMap() {
public AudioBridgeCreateRoomReq(int pRoomId, bool pSpatial, string pDesc, string credentials) : base(new OSDMap() {
{ "room", pRoomId },
{ "request", "create" },
{ "is_private", false },
@@ -531,6 +531,8 @@ namespace osWebRtcVoice
{
if (!string.IsNullOrEmpty(pDesc))
AddStringToBody("description", pDesc);
if (!string.IsNullOrEmpty(credentials))
AddStringToBody("pin", credentials);
}
}

View File

@@ -59,7 +59,6 @@ namespace osWebRtcVoice
public async Task<bool> JoinRoom(JanusViewerSession pVSession)
{
bool ret = false;
try
{
// m_log.DebugFormat("{0} JoinRoom. New joinReq for room {1}", LogHeader, RoomId);
@@ -79,10 +78,11 @@ namespace osWebRtcVoice
{
pVSession.ParticipantId = joinResp.ParticipantId;
pVSession.Answer = joinResp.Jsep;
ret = true;
m_log.Debug($"{LogHeader} JoinRoom. Joined room {RoomId}. Participant={pVSession.ParticipantId}");
return true;
}
else if (joinResp is not null && joinResp.AudioBridgeErrorCode == 491)
if (joinResp is not null && (joinResp.AudioBridgeErrorCode == 490 || joinResp.AudioBridgeErrorCode == 491))
{
m_log.Warn($"{LogHeader} JoinRoom. Already in a room for agent {pVSession.AgentId}. Attempting recovery.");
@@ -98,13 +98,11 @@ namespace osWebRtcVoice
{
pVSession.ParticipantId = retryJoinResp.ParticipantId;
pVSession.Answer = retryJoinResp.Jsep;
ret = true;
m_log.Info($"{LogHeader} JoinRoom. Recovery succeeded for room {RoomId}. Participant={pVSession.ParticipantId}");
return true;
}
else
{
m_log.Error($"{LogHeader} JoinRoom. Recovery retry failed for room {RoomId}. Resp={retryJoinResp?.ToString() ?? "null"}");
}
m_log.Error($"{LogHeader} JoinRoom. Recovery retry failed for room {RoomId}. Resp={retryJoinResp?.ToString() ?? "null"}");
}
else
{
@@ -120,18 +118,19 @@ namespace osWebRtcVoice
if (m_log.IsDebugEnabled)
m_log.DebugFormat("{0} JoinRoom. Invalid participant detail: {1}", LogHeader, joinResp.ToString());
}
m_log.ErrorFormat("{0} JoinRoom. Failed to join room {1}", LogHeader, RoomId);
if (m_log.IsDebugEnabled)
m_log.DebugFormat("{0} JoinRoom. Failure detail: {1}", LogHeader, joinResp?.ToString() ?? "null");
else
{
m_log.ErrorFormat("{0} JoinRoom. Failed to join room {1}", LogHeader, RoomId);
if (m_log.IsDebugEnabled)
m_log.DebugFormat("{0} JoinRoom. Failure detail: {1}", LogHeader, joinResp?.ToString() ?? "null");
}
}
}
catch (Exception e)
{
m_log.Error($"{LogHeader} JoinRoom. Exception ", e);
}
return ret;
return false;
}
private async Task<bool> RecoverAlreadyInRoomAndLeave(string pDisplay)
@@ -222,7 +221,6 @@ namespace osWebRtcVoice
public async Task<bool> LeaveRoom(JanusViewerSession pAttendeeSession)
{
bool ret = false;
try
{
JanusMessageResp resp = await _AudioBridge.SendPluginMsg(
@@ -238,33 +236,31 @@ namespace osWebRtcVoice
string returnCode = abResp.AudioBridgeReturnCode;
string janusReturnCode = resp.ReturnCode;
int errorCode = abResp.AudioBridgeErrorCode;
bool isBenignAlreadyLeft = errorCode == 487 &&
(returnCode == "event" || janusReturnCode == "event" || janusReturnCode == "ack");
if (errorCode == 0 &&
(abResp.isSuccess || returnCode == "left" || returnCode == "event" || returnCode == "success" || janusReturnCode == "ack"))
{
ret = true;
if (janusReturnCode == "ack" && string.IsNullOrEmpty(returnCode))
{
m_log.Debug($"{LogHeader} LeaveRoom. Ack accepted for room {RoomId}, participant={pAttendeeSession.ParticipantId}");
m_log.Debug($"{LogHeader} LeaveRoom. Ack for room {RoomId}, participant={pAttendeeSession.ParticipantId}");
}
return true;
}
else if (isBenignAlreadyLeft)
if (errorCode == 487 &&
(returnCode == "event" || janusReturnCode == "event" || janusReturnCode == "ack"))
{
ret = true;
m_log.Info($"{LogHeader} LeaveRoom. Participant already left room {RoomId}, participant={pAttendeeSession.ParticipantId} (errorCode=487)");
return true;
}
else
{
m_log.Error($"{LogHeader} LeaveRoom. Failed room {RoomId}, participant={pAttendeeSession.ParticipantId}, janus={janusReturnCode}, audiobridge={returnCode}, errorCode={errorCode}");
}
m_log.Error($"{LogHeader} LeaveRoom. Failed room {RoomId}, participant={pAttendeeSession.ParticipantId}, janus={janusReturnCode}, audiobridge={returnCode}, errorCode={errorCode}");
}
catch (Exception e)
{
m_log.Error($"{LogHeader} LeaveRoom. Exception ", e);
}
return ret;
return false;
}
}
}

View File

@@ -119,8 +119,11 @@ namespace osWebRtcVoice
{
JanusSession s = Session;
Session = null;
_ = await s.DestroySession().ConfigureAwait(false);
s.Dispose();
if(s != null)
{
_ = await s.DestroySession().ConfigureAwait(false);
s.Dispose();
}
}
}
}

View File

@@ -164,12 +164,12 @@ namespace osWebRtcVoice
// Requests through the capabilities will create rooms
janusSession.AddPlugin(audioBridge);
pViewerSession.VoiceServiceSessionId = janusSession.SessionId;
pViewerSession.Session = janusSession;
pViewerSession.AudioBridge = audioBridge;
janusSession.OnDisconnect += Handle_Hangup;
janusSession.OnHangup += Handle_Hangup;
return true;
}
_log.Error($"{LogHeader} JanusPluginHandle not created");
@@ -245,9 +245,9 @@ namespace osWebRtcVoice
_log.InfoFormat("{0} ProvisionVoiceAccountRequest: disconnected by {1}. agent={2}, scene={3}, room={4}, participant={5}, viewer_session={6}",
LogHeader, pReason, pViewerSession.AgentId, pViewerSession.RegionId, roomId, pViewerSession.ParticipantId, pViewerSession.ViewerSessionID);
VoiceViewerSession.RemoveViewerSession(pViewerSession.ViewerSessionID);
Task.Run(() =>
{
VoiceViewerSession.RemoveViewerSession(pViewerSession.ViewerSessionID);
// No need to wait for the session to be shutdown
_ = pViewerSession.Shutdown();
});
@@ -265,23 +265,31 @@ namespace osWebRtcVoice
public async Task<OSDMap> ProvisionVoiceAccountRequestBAD(IVoiceViewerSession pSession, OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
long flowId = Interlocked.Increment(ref _VoiceFlowCounter);
if(pRequest.TryGetString("voice_server_type", out string voice_server_type))
{
if(!"webrtc".Equals(voice_server_type,StringComparison. CurrentCultureIgnoreCase))
{
_log.Error($"{LogHeader} ProvisionVoiceAccountRequest: invalid server type {voice_server_type ?? "null"}");
return new OSDMap
{
{ "response", "failed" },
{ "error", "Invalid server type" }
};
}
}
OSDMap ret = null;
string errorMsg = null;
JanusViewerSession viewerSession = pSession as JanusViewerSession;
long flowId = Interlocked.Increment(ref _VoiceFlowCounter);
if (viewerSession is not null)
{
if (viewerSession.Session is null)
{
// This is a new session so we must create a new session and handle to the audio bridge
await ConnectToSessionAndAudioBridge(viewerSession).ConfigureAwait(false);
}
// TODO: need to keep count of users in a room to know when to close a room
bool isLogout = pRequest.TryGetBool("logout", out bool lgout) && lgout;
if (isLogout)
{
// The client is logging out. Exit the room.
// Exit the room.
if (viewerSession.Room is not null)
{
_ = await viewerSession.Room.LeaveRoom(viewerSession).ConfigureAwait(false);
@@ -296,14 +304,20 @@ namespace osWebRtcVoice
};
}
if (viewerSession.Session is null)
{
// This is a new session so we must create a new session and handle to the audio bridge
await ConnectToSessionAndAudioBridge(viewerSession).ConfigureAwait(false);
}
// Get the parameters that select the room
// To get here, voice_server_type has already been checked to be 'webrtc' and channel_type='local'
int parcel_local_id = pRequest.TryGetInt("parcel_local_id", out int pli) ? pli : JanusAudioBridge.REGION_ROOM_ID;
string channel_id = pRequest.TryGetString("channel_id", out string cli) ? cli : string.Empty;
string channel_credentials = pRequest.TryGetString("credentials", out string cred) ? cred : string.Empty;
string channel_type = pRequest["channel_type"].AsString();
string gridHash = pRequest.TryGetString("gridhash", out string ghash) ? ghash : string.Empty;
bool isSpatial = channel_type == "local";
string voice_server_type = pRequest["voice_server_type"].AsString();
_log.DebugFormat("{0} ProvisionVoiceAccountRequest: parcel_id={1} channel_id={2} channel_type={3} voice_server_type={4}", LogHeader, parcel_local_id, channel_id, channel_type, voice_server_type);
@@ -320,14 +334,15 @@ namespace osWebRtcVoice
{
// The client is sending an offer. Find the right room and join it.
// _log.DebugFormat("{0} ProvisionVoiceAccountRequest: jsep type={1} sdp={2}", LogHeader, jsepType, jsepSdp);
viewerSession.Room = await viewerSession.AudioBridge.SelectRoom(pSceneID.ToString(),
channel_type, isSpatial, parcel_local_id, channel_id).ConfigureAwait(false);
viewerSession.Room = await viewerSession.AudioBridge.SelectRoom(pSceneID.ToString(), gridHash,
channel_type, isSpatial, parcel_local_id, channel_id, channel_credentials).ConfigureAwait(false);
if (viewerSession.Room is null)
{
errorMsg = "room selection failed";
_log.Error($"{LogHeader} ProvisionVoiceAccountRequest: room selection failed");
}
else {
else
{
viewerSession.Offer = jsepSdp;
viewerSession.OfferOrig = jsepSdp;
viewerSession.AgentId = pUserID;
@@ -399,7 +414,7 @@ namespace osWebRtcVoice
JanusViewerSession viewerSession = pSession as JanusViewerSession;
JanusMessageResp resp = null;
long flowId = Interlocked.Increment(ref _VoiceFlowCounter);
if (viewerSession is not null)
if (viewerSession is not null && viewerSession.Session is not null)
{
// The request should be an array of candidates
if (pRequest.TryGetOSDMap("candidate", out OSDMap candidate))

View File

@@ -79,6 +79,8 @@ namespace osWebRtcVoice
private IWebRtcVoiceService m_spatialVoiceService;
private IWebRtcVoiceService m_nonSpatialVoiceService;
private UUID gridHash = UUID.Zero;
// ISharedRegionModule.Initialize
public void Initialise(IConfigSource config)
{
@@ -175,6 +177,14 @@ namespace osWebRtcVoice
scene.EventManager.OnNewClient += OnNewClient;
if(gridHash.IsZero())
{
if(!string.IsNullOrEmpty(scene.SceneGridInfo.GridUrl))
gridHash = Util.ComputeShake128UUID(scene.SceneGridInfo.GridUrl + scene.SceneGridInfo.GridName);
else if (!string.IsNullOrEmpty(scene.SceneGridInfo.HomeURL + scene.SceneGridInfo.GridName))
gridHash = Util.ComputeShake128UUID(scene.SceneGridInfo.HomeURLNoEndSlash);
}
ISimulatorFeaturesModule simFeatures = scene.RequestModuleInterface<ISimulatorFeaturesModule>();
simFeatures?.AddFeature("VoiceServerType", OSD.FromString("webrtc"));
}
@@ -552,7 +562,7 @@ namespace osWebRtcVoice
}
else
{
flags = IVoiceViewerSession.VFlags.IsEstate;
flags = IVoiceViewerSession.VFlags.IsEstate;
}
// TODO: check if this userId is making a new session (case that user is reconnecting)
@@ -583,6 +593,7 @@ namespace osWebRtcVoice
{
vSession.Flags = IVoiceViewerSession.VFlags.IsAdmin;
VoiceViewerSession.AddViewerSession(vSession);
map["gridhash"] = gridHash;
}
}
}