void GetRemoteTrackEvents(MessagePrefixesPair <Message> msgPfx, Queue <Event> buffer, string loggableId)
        {
            Match m;
            var   msg = msgPfx.Message;

            if ((m = remoteTrackCtrRegex.Match(msg.Text)).Success)
            {
                buffer.Enqueue(new ObjectCreation(msg, loggableId, remoteTrackObjectType));
                var webRtcStreamId = m.Groups["webRtcStreamId"].Value;
                var remoteTracksId = m.Groups["remoteTracksId"].Value;
                buffer.Enqueue(new PropertyChange(msg, loggableId, remoteTrackObjectType, "WebRTC stream id", webRtcStreamId));
                buffer.Enqueue(new PropertyChange(msg, loggableId, remoteTrackObjectType, "type", m.Groups["type"].Value));
                buffer.Enqueue(new ParentChildRelationChange(msg, loggableId, remoteTrackObjectType, remoteTracksId));
                if (remoteTracksIdToParticipantsId.TryGetValue(remoteTracksId, out var participantsId) &&
                    participantsIdToSessionId.TryGetValue(participantsId, out var sessionId))
                {
                    buffer.Enqueue(new PropertyChange(
                                       msg, loggableId, remoteTrackObjectType,
                                       "stream", RemoteWebRTCStreamInfo.MakeStateInspectorObjectId(webRtcStreamId, sessionId),
                                       Postprocessing.StateInspector.ValueType.Reference));
                }
                var remoteTracksIdAndMediaType = $"{remoteTracksId}.{m.Groups["type"].Value}";
                if (remoteTracksIdAndMediaTypeToRemoteTrackId.TryGetValue(remoteTracksIdAndMediaType, out var oldRemoteTrackId))
                {
                    buffer.Enqueue(new ObjectDeletion(msg, oldRemoteTrackId, remoteTrackObjectType));
                }
                remoteTracksIdAndMediaTypeToRemoteTrackId[remoteTracksIdAndMediaType] = loggableId;
            }
        }
        void GetRemoteTrackEvents(MessagePrefixesPair msgPfx, Queue <Event> buffer, string loggableId)
        {
            Match m;
            var   msg = msgPfx.Message;

            if ((m = remoteTrackCtrRegex.Match(msg.Text)).Success)
            {
                buffer.Enqueue(new ObjectCreation(msg, loggableId, remoteTrackObjectType));
                var webRtcStreamId = m.Groups["webRtcStreamId"].Value;
                var remoteTracksId = m.Groups["remoteTracksId"].Value;
                buffer.Enqueue(new PropertyChange(msg, loggableId, remoteTrackObjectType, "WebRTC stream id", webRtcStreamId));
                buffer.Enqueue(new PropertyChange(msg, loggableId, remoteTrackObjectType, "type", m.Groups["type"].Value));
                buffer.Enqueue(new ParentChildRelationChange(msg, loggableId, remoteTrackObjectType, remoteTracksId));
                string participantsId, sessionId;
                if (remoteTracksIdToParticipantsId.TryGetValue(remoteTracksId, out participantsId) &&
                    participantsIdToSessionId.TryGetValue(participantsId, out sessionId))
                {
                    buffer.Enqueue(new PropertyChange(
                                       msg, loggableId, remoteTrackObjectType,
                                       "stream", RemoteWebRTCStreamInfo.MakeStateInspectorObjectId(webRtcStreamId, sessionId),
                                       Analytics.StateInspector.ValueType.Reference));
                }
            }
        }
        void GetRemoteMediaEvents(MessagePrefixesPair msgPfx, Queue <Event> buffer, string loggableId)
        {
            Func <string, string, string, bool, RemoteWebRTCStreamInfo> handleChange = (stmId, a, v, allowCreate) =>
            {
                string sessionId;
                if (!remoteMediaIdToSessionId.TryGetValue(loggableId, out sessionId))
                {
                    return(null);
                }
                Dictionary <string, RemoteWebRTCStreamInfo> sessionStreams;
                if (!remoteWebRtcStreamIdToInfo.TryGetValue(sessionId, out sessionStreams))
                {
                    remoteWebRtcStreamIdToInfo[sessionId] = sessionStreams = new Dictionary <string, RemoteWebRTCStreamInfo>();
                }
                RemoteWebRTCStreamInfo stmInfo;
                if (!sessionStreams.TryGetValue(stmId, out stmInfo))
                {
                    if (!allowCreate)
                    {
                        return(null);
                    }
                    sessionStreams[stmId] = stmInfo = new RemoteWebRTCStreamInfo()
                    {
                        stateInspectorObjectId = RemoteWebRTCStreamInfo.MakeStateInspectorObjectId(stmId, sessionId),
                        remoteMediaId          = loggableId
                    };
                    buffer.Enqueue(new ObjectCreation(msgPfx.Message, stmInfo.stateInspectorObjectId, remoteWebRTCStreamObjectType));
                    buffer.Enqueue(new ParentChildRelationChange(msgPfx.Message, stmInfo.stateInspectorObjectId, remoteWebRTCStreamObjectType, loggableId));
                }
                Action <string, string, Dictionary <string, RemoteWebRTCStreamInfo.TrackInfo> > handleList = (list, modalityName, dict) =>
                {
                    HashSet <string> newTrackIds =
                        remoteMediaStreamReceivedTrackRegex
                        .Matches(list)
                        .OfType <Match>()
                        .Select(x => x.Groups["id"].Value)
                        .ToHashSet();
                    bool changed = false;
                    foreach (var newTrackId in newTrackIds.Except(dict.Keys.ToArray()))
                    {
                        dict[newTrackId] = new RemoteWebRTCStreamInfo.TrackInfo()
                        {
                            added = msgPfx.Message
                        };
                        changed = true;
                    }
                    foreach (var knownTrack in dict.Where(t => t.Value.removed == null).ToArray())
                    {
                        if (!newTrackIds.Contains(knownTrack.Key))
                        {
                            dict[knownTrack.Key].removed = msgPfx.Message;
                            changed = true;
                        }
                    }
                    if (changed)
                    {
                        buffer.Enqueue(new PropertyChange(
                                           msgPfx.Message, stmInfo.stateInspectorObjectId, remoteWebRTCStreamObjectType,
                                           modalityName + " WebRTC track ids", list));
                    }
                };
                handleList(a, "audio", stmInfo.audioWebRtcTracks);
                handleList(v, "video", stmInfo.videoWebRtcTracks);
                return(stmInfo);
            };

            Match m;

            if ((m = remoteMediaStreamReceivedRegex.Match(msgPfx.Message.Text)).Success)
            {
                handleChange(m.Groups["streamId"].Value,
                             m.Groups["audioTracks"].Value, m.Groups["videoTracks"].Value, true);
            }
            else if ((m = remoteMediaStreamRemovedRegex.Match(msgPfx.Message.Text)).Success)
            {
                var stmInfo = handleChange(m.Groups["streamId"].Value, "", "", false);
                if (stmInfo != null)
                {
                    buffer.Enqueue(new ObjectDeletion(
                                       msgPfx.Message, stmInfo.stateInspectorObjectId, remoteWebRTCStreamObjectType));
                    stmInfo.deleted = true;
                }
            }
            else if (msgPfx.Message.Text == "disposed")
            {
                buffer.Enqueue(new ObjectDeletion(
                                   msgPfx.Message, loggableId, remoteMediaObjectType));
                foreach (var sessionStreams in remoteWebRtcStreamIdToInfo.Values)
                {
                    foreach (var stmInfo in sessionStreams.Values.Where(
                                 stm => !stm.deleted && stm.remoteMediaId == loggableId))
                    {
                        buffer.Enqueue(new ObjectDeletion(
                                           msgPfx.Message, stmInfo.stateInspectorObjectId, remoteWebRTCStreamObjectType));
                        stmInfo.deleted = true;
                    }
                }
            }
        }