using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Vinno.IUS.Common.Log; using Vinno.IUS.Common.Network; using Vinno.IUS.Common.Network.Leaf; using Vinno.IUS.Common.Network.Transfer; using Vinno.vCloud.Protocol.Infrastructures; using Vinno.vCloud.Protocol.Messages.Client.RemoteDiagnosis; using Vinno.vCloud.Protocol.Messages.Live; namespace Vinno.vCloud.Common.FIS.Consultation { internal class ConsultationRecipient : ConsultationClient { private readonly ManualResetEvent _timingManualResetEvent = new ManualResetEvent(false); private readonly string _terminalId; private readonly string _uniqueId; private string _initiatorId; private bool _isEmergencyRequesting; private string _cameraId; private string _micId; private string _speakerId; /// /// Raised when receive a Consultation request from server. /// public event EventHandler ConsultationRequestArrived; public ConsultationRecipient(ConsultationLiveVideoProvider videoProvider, string terminalId, string uniqueId) : base(videoProvider) { _terminalId = terminalId; _uniqueId = uniqueId; } /// /// Send a join Rtc Consultation Live request /// /// public LiveStates AcceptConsultation(string cameraHardwareId, string micHardwareId, string speakerHardwareId) { var liveState = LiveStates.UnknowException; try { if (CurrentLiveStatus == LiveStates.RecipientAcceptting || CurrentLiveStatus == LiveStates.RecipientAcceptted) { Logger.WriteLineInfo($"Consultation Live-- {FLYINSONOUser.Name} accept Consultation failed due to CurrentLiveStatus is {CurrentLiveStatus}"); return LiveStates.TalkIsBusy; } CurrentLiveStatus = LiveStates.RecipientAcceptting; Logger.WriteLineInfo($"Consultation Live-- {FLYINSONOUser.Name} accept Consultation request begin"); using (var request = new AcceptMeetingRequest()) { request.RoomId = ConsultationInfo.RoomId; request.InitiatorId = _initiatorId; request.RecipientId = FLYINSONOUser.AccountId; request.FeatureSource = FeatureSource.RemoteDiagnosis; request.Source = FLYINSONOUser.LoginSource; request.PushLiveState = ConsultationInfo.State; request.ClientLanguage = GetCurrentLanguage(); var result = ClientLeaf.Send(request); Logger.WriteLineInfo($"Send AcceptMeetingRequest To Server"); if (result != null) { var resultMessage = AcceptMeetingResult.Convert(result); if (resultMessage != null) { liveState = resultMessage.State; if (resultMessage != null && resultMessage.State == LiveStates.OK) { lock (ConsultationInfoLocker) { ConsultationInfo.State = resultMessage.State; } StartConsultation(cameraHardwareId, micHardwareId, speakerHardwareId); CurrentLiveStatus = LiveStates.RecipientAcceptted; return CurrentLiveStatus; } Logger.WriteLineInfo($"Consultation Live-- {FLYINSONOUser.Name} accept meeConsultationting request end"); } } } } catch (ConnectionTimeoutException ex) { Logger.WriteLineError($"ConnectionTimeoutException happened when accept Consultation{ex}"); } catch (Exception ex) { Logger.WriteLineError($"Exception happened when accept Consultation{ex}"); } var recipientId = ConsultationInfo?.ConsultationMemberInfos?.FirstOrDefault(x => x.RoleType == ConsultationRoleType.Recipient)?.Id; if (recipientId == FLYINSONOUser.AccountId) { Hangup(true); } else { Hangup(false); } return liveState; } /// /// Send a Rtc Consultation Live rejectation request /// /// public LiveStates RejectConsultation() { Logger.WriteLineInfo($"Consultation Live-- {FLYINSONOUser.Name} reject meet request begin"); CurrentLiveStatus = LiveStates.RecipientRejected; StopConsultation(); var roomId = ConsultationInfo.RoomId; var initiatorId = ConsultationInfo.ConsultationMemberInfos.FirstOrDefault(x => x.RoleType == ConsultationRoleType.Initiator)?.Id; lock (ConsultationInfoLocker) { ConsultationInfo = null; } Logger.WriteLineInfo($"Consultation Live-- {FLYINSONOUser.Name} reject Consultation request end"); using (var request = new RejectMeetingRequest()) { request.RoomId = roomId; request.RecipientId = FLYINSONOUser.AccountId; request.InitiatorId = initiatorId; request.LoginSource = FLYINSONOUser.LoginSource; request.ClientLanguage = GetCurrentLanguage(); var result = ClientLeaf.Send(request); Logger.WriteLineInfo($"Send RejectMeetingRequest To Server"); if (result != null) { var resultMessage = MeetingResult.Convert(result); //拒绝会诊result if (resultMessage != null) { return resultMessage.State; } } } return LiveStates.UnknowException; } protected override void HandleConsultationMemberNotificationArrived(MeetingMemberNotification meetingMemberNotification) { if (ConsultationInfo != null) { var mem = meetingMemberNotification.Members.FirstOrDefault(f => f.RoleType == MeetingRoleType.Recipient && f.OperationType == ClientMessageOperationType.Delete); if (mem != null) { OnDisconnected(ConsultationDisconnectedType.HangupByOther); } else { ConsultationMemberChange(meetingMemberNotification); foreach (var member in meetingMemberNotification.Members) { var memberInfo = ConsultationInfo?.ConsultationMemberInfos.FirstOrDefault(f => f.Id == member.Id); if (member.OperationType == ClientMessageOperationType.Add) { ConsultationRoleType roleType = ConsultationRoleType.Initiator; switch (member.RoleType) { case MeetingRoleType.Initiator: roleType = ConsultationRoleType.Initiator; break; case MeetingRoleType.Recipient: roleType = ConsultationRoleType.Recipient; break; case MeetingRoleType.Expert: roleType = ConsultationRoleType.Expert; break; case MeetingRoleType.Administrator: roleType = ConsultationRoleType.Administrator; break; } var meetingMemberInfo = new ConsultationMemberInfo { OperationType = member.OperationType, RoleType = roleType, RoomId = member.RoomId, LoginSource = member.LoginSource, State = member.State, Id = member.Id, Name = member.Name, DisplayName = member.DisplayName, Avatar = member.Avatar, UserSign = member.UserSign, PushUrl = member.PushUrl, PullRtmpUrl = member.PullRtmpUrl, PullFlvUrl = member.PullFlvUrl, PullHlsUrl = member.PullHlsUrl, PushLiveState = member.PushLiveState, }; if (memberInfo == null) { ConsultationInfo.ConsultationMemberInfos.Add(meetingMemberInfo); } else { memberInfo = meetingMemberInfo; } } else if (member.OperationType == ClientMessageOperationType.Delete) { if (memberInfo != null) { ConsultationInfo?.ConsultationMemberInfos.Remove(memberInfo); } } } base.HandleConsultationMemberNotificationArrived(meetingMemberNotification); } } } protected override void OnMessageArrived(object sender, Message e) { try { base.OnMessageArrived(sender, e); var startMeetingNotification = StartMeetingNotification.Convert(e); //接收者收到被邀请通知 if (startMeetingNotification != null) { HandleStartMeetingNotification(startMeetingNotification); } var cancelStartMeetingNotification = CancelStartMeetingNotification.Convert(e); //取消会诊通知 if (cancelStartMeetingNotification != null) { HandleCancelStartMeetingNotification(); } var rejectEmergencyConsultationNotification = RejectEmergencyConsultationNotification.Convert(e); //专家拒绝紧急会诊通知 if (rejectEmergencyConsultationNotification != null) { HandleRejectEmergencyConsultationNotification(rejectEmergencyConsultationNotification); } } catch (Exception ex) { Logger.WriteLineError($"Consultation Recipient OnMessageArrived Error:{ex}"); } } private void HandleRejectEmergencyConsultationNotification(RejectEmergencyConsultationNotification rejectEmergencyConsultationNotification) { Logger.WriteLineInfo($"MeetingRecipient OnRejectEmergencyConsultationNotificationArrived,reason:{rejectEmergencyConsultationNotification.Reason}"); if (EmergencyConsultationInfo != null && EmergencyConsultationInfo.SortExperts != null && EmergencyConsultationInfo.ConsultationId == rejectEmergencyConsultationNotification.ConsultationId) { _timingManualResetEvent.Set(); var prevSortExpert = EmergencyConsultationInfo.SortExperts.FirstOrDefault(f => f.UserId == rejectEmergencyConsultationNotification.ExpertId); if (prevSortExpert != null) { var nextSortExpert = EmergencyConsultationInfo.SortExperts.FirstOrDefault(f => f.Sort == prevSortExpert.Sort + 1); if (nextSortExpert != null) { if (!CreateAndStartEmergencyConsultation(nextSortExpert)) { _timingManualResetEvent.Set(); _isEmergencyRequesting = false; CurrentLiveStatus = LiveStates.RecipientRejected; OnRejectConsultationNotifyArrived(new ConsultationSubscriberInfo { RoomId = null, Id = rejectEmergencyConsultationNotification.ExpertId }); lock (EmergencyConsultationLocker) { EmergencyConsultationInfo = null; } } } else { _isEmergencyRequesting = false; CurrentLiveStatus = LiveStates.RecipientRejected; OnRejectConsultationNotifyArrived(new ConsultationSubscriberInfo { RoomId = null, Id = rejectEmergencyConsultationNotification.ExpertId }); lock (EmergencyConsultationLocker) { EmergencyConsultationInfo = null; } } } } } private void HandleCancelStartMeetingNotification() { if (ConsultationInfo != null) { OnDisconnected(ConsultationDisconnectedType.CancelledByInitiator); Logger.WriteLineInfo($"CancelStartMeetingNotification handled"); } } private void HandleStartMeetingNotification(StartMeetingNotification startMeetingNotification) { Logger.WriteLineInfo("LiveMeeting StartMeetingNotification Arrived"); CurrentLiveStatus = LiveStates.ChatRequestArrived; var mode = startMeetingNotification.Mode; var roomId = startMeetingNotification.RoomId; var protocol = startMeetingNotification.LiveProtocol; List meetingMemberInfos = new List(); foreach (var member in startMeetingNotification.Members) { ConsultationRoleType roleType = ConsultationRoleType.Initiator; switch (member.RoleType) { case MeetingRoleType.Initiator: roleType = ConsultationRoleType.Initiator; break; case MeetingRoleType.Recipient: roleType = ConsultationRoleType.Recipient; break; case MeetingRoleType.Expert: roleType = ConsultationRoleType.Expert; break; case MeetingRoleType.Administrator: roleType = ConsultationRoleType.Administrator; break; } var meetingMemberInfo = new ConsultationMemberInfo { OperationType = member.OperationType, RoleType = roleType, RoomId = member.RoomId, LoginSource = member.LoginSource, State = member.State, Id = member.Id, Name = member.Name, DisplayName = member.DisplayName, Avatar = member.Avatar, UserSign = member.UserSign, PushUrl = member.PushUrl, PullRtmpUrl = member.PullRtmpUrl, PullFlvUrl = member.PullFlvUrl, PullHlsUrl = member.PullHlsUrl, PushLiveState = member.PushLiveState, }; meetingMemberInfos.Add(meetingMemberInfo); } var terminalInfo = new TerminalInfo(); var liveTerminal = startMeetingNotification.LiveTerminals.FirstOrDefault(); var channels = new List(); if (liveTerminal.Channels != null) { foreach (var channelInfo in liveTerminal.Channels) { var channel = new PullChannnelUrl { Enable = channelInfo.Enable, FlvUrl = channelInfo.FlvUrl, Height = channelInfo.Height, HlsUrl = channelInfo.HlsUrl, Name = channelInfo.Name, RtmpUrl = channelInfo.RtmpUrl, UserId = channelInfo.UserId, Width = channelInfo.Width, }; channels.Add(channel); } } if (liveTerminal.IsMultiChannels)//Windows魔盒 { var mainTerminalInfo = liveTerminal.Channels.Select(f => f.Name).Contains("Main") ? liveTerminal.Channels.FirstOrDefault(f => f.Name == "Main") : liveTerminal.Channels.FirstOrDefault(); var cameraInfo = liveTerminal.Channels.Count > 1 ? liveTerminal.Channels.FirstOrDefault(x => x.Name != "Main") : null; terminalInfo = new TerminalInfo() { OperationType = liveTerminal.OperationType, TerminalLiveEnabled = mainTerminalInfo.Enable, CameraLiveEnabled = cameraInfo?.Enable ?? false, IsMergeChannel = liveTerminal.IsMergeChannel, TerminalWidth = mainTerminalInfo.Width, TerminalHeight = mainTerminalInfo.Height, TerminalUrl = mainTerminalInfo.RtmpUrl, CameraWidth = cameraInfo?.Width ?? 0, CameraHeight = cameraInfo?.Height ?? 0, CameraUrl = cameraInfo?.RtmpUrl, TerminalLiveProtocol = liveTerminal.LiveProtocol, IntegerRoomId = liveTerminal.IntegerRoomId, TerminalIntegerRoomId = liveTerminal.TerminalIntegerRoomId, State = liveTerminal.State, Id = string.IsNullOrEmpty(mainTerminalInfo.UserId) ? liveTerminal.Id : mainTerminalInfo.UserId, CameraId = cameraInfo?.UserId, IsMultiChannels = liveTerminal.IsMultiChannels, PullDataMode = liveTerminal.PullDataMode, TerminalRoomId = liveTerminal.RoomId, Channels = channels, }; } else { terminalInfo = new TerminalInfo() { OperationType = liveTerminal.OperationType, TerminalLiveEnabled = liveTerminal.TerminalLiveEnabled, CameraLiveEnabled = liveTerminal.CameraLiveEnabled, IsMergeChannel = liveTerminal.IsMergeChannel, TerminalWidth = liveTerminal.TerminalWidth, TerminalHeight = liveTerminal.TerminalHeight, TerminalUrl = liveTerminal.TerminalUrl, CameraWidth = liveTerminal.CameraWidth, CameraHeight = liveTerminal.CameraHeight, CameraUrl = liveTerminal.CameraUrl, TerminalLiveProtocol = liveTerminal.LiveProtocol, IntegerRoomId = liveTerminal.IntegerRoomId, TerminalIntegerRoomId = liveTerminal.TerminalIntegerRoomId, State = liveTerminal.State, Id = liveTerminal.Id, IsMultiChannels = liveTerminal.IsMultiChannels, PullDataMode = liveTerminal.PullDataMode, TerminalRoomId = liveTerminal.RoomId, Channels = channels, }; } lock (ConsultationInfoLocker) { var info = new ConsultationInfo(ChatRole.Recipient); info.AppId = startMeetingNotification.AppId; info.RoomId = startMeetingNotification.RoomId; info.LiveTalkingMode = mode; info.IntegerRoomId = startMeetingNotification.IntegerRoomId; info.LiveProtocol = startMeetingNotification.LiveProtocol; info.ConsultationMemberInfos = meetingMemberInfos; info.TerminalInfo = terminalInfo; info.IsCurrentTerminal = info.TerminalInfo?.Id == _terminalId; ConsultationInfo = info; _initiatorId = ConsultationInfo?.ConsultationMemberInfos?.FirstOrDefault(x => x.RoleType == ConsultationRoleType.Initiator)?.Id; } var userName = GetAccoutNameByAccountId(startMeetingNotification.InitiatorId); Logger.WriteLineInfo($"StartMeetingNotification handled RoomId:{ConsultationInfo.RoomId}"); if (!_isEmergencyRequesting) { ConsultationRequestArrived?.Invoke(this, ConsultationInfo); } else { _timingManualResetEvent.Set(); _isEmergencyRequesting = false; AcceptConsultation(_cameraId, _micId, _speakerId); if (CurrentLiveStatus == LiveStates.RecipientAcceptted) { OnJoinConsultationNotifyArrived(); } } } public EnumEmergencyConsultationResult RequestEmergencyConsultation(string cameraId, string micId, string speakerId) { var liveState = EnumEmergencyConsultationResult.UnknowException; try { if (CurrentLiveStatus == LiveStates.RecipientAcceptting || CurrentLiveStatus == LiveStates.RecipientAcceptted) { Logger.WriteLineInfo($"Consultation Live-- {FLYINSONOUser.Name} Request Emergency Consultation failed due to CurrentLiveStatus is {CurrentLiveStatus}"); return EnumEmergencyConsultationResult.IsBusy; } if (_isEmergencyRequesting) { Logger.WriteLineInfo($"Consultation Live-- {FLYINSONOUser.Name} Request Emergency Consultation failed due to is Requesting Emergency Consultation"); return EnumEmergencyConsultationResult.IsBusy; } _cameraId = cameraId; _micId = micId; _speakerId = speakerId; var emergencyConsultationInfo = GetEmergencyConsultationExpertsInfo(); lock (EmergencyConsultationLocker) { EmergencyConsultationInfo = emergencyConsultationInfo; } switch (EmergencyConsultationInfo.NoExpertCause) { case NoExpertCause.ExpertsOfflineAll: return EnumEmergencyConsultationResult.ExpertsOfflineAll; case NoExpertCause.ExpertsDeficiencyOfTimeAll: return EnumEmergencyConsultationResult.ExpertsDeficiencyOfTimeAll; case NoExpertCause.ExpertsOfflineOrDeficiencyOfTime: return EnumEmergencyConsultationResult.ExpertsOfflineOrDeficiencyOfTime; } if (EmergencyConsultationInfo.SortExperts == null || EmergencyConsultationInfo.SortExperts.Count == 0) { return EnumEmergencyConsultationResult.ExpertsIsNotExist; } CurrentLiveStatus = LiveStates.InitiatorRequestingChat; Logger.WriteLineInfo($"Consultation Live-- {FLYINSONOUser.Name} request Emergency Consultation request begin"); if (CreateAndStartEmergencyConsultation(EmergencyConsultationInfo.SortExperts.FirstOrDefault())) { _isEmergencyRequesting = true; return EnumEmergencyConsultationResult.OK; } else { _isEmergencyRequesting = false; _timingManualResetEvent.Set(); return EnumEmergencyConsultationResult.Failed; } } catch (Exception ex) { Logger.WriteLineError($"Exception happened when RequestEmergencyConsultation Consultation{ex}"); } _isEmergencyRequesting = false; return liveState; } private void StartTimingTask() { _timingManualResetEvent.Reset(); Task.Run(() => { try { var currentSort = EmergencyConsultationInfo.CurrentSort; var currentExpert = EmergencyConsultationInfo.SortExperts.FirstOrDefault(f => f.Sort == currentSort); Logger.WriteLineInfo($"Consultation Recipient Timing Task Start,Current Sort is {currentSort},ExpertName:{currentExpert?.UserName},ExpertId:{currentExpert?.UserId}"); if (!_timingManualResetEvent.WaitOne(20000)) { Logger.WriteLineError($"Consultation Emergency Consultation Call Time out,Will Cancel and Request Next Automatic"); if (EmergencyConsultationInfo != null) { CancelEmergencyConsultation(); var emergencyConsultationInfo = GetEmergencyConsultationExpertsInfo(); lock (EmergencyConsultationLocker) { EmergencyConsultationInfo = emergencyConsultationInfo; } var nextSortExpert = EmergencyConsultationInfo.SortExperts.FirstOrDefault(f => f.Sort == currentSort + 1); if (nextSortExpert != null) { if (CreateAndStartEmergencyConsultation(nextSortExpert)) { _isEmergencyRequesting = true; } else { _timingManualResetEvent.Set(); _isEmergencyRequesting = false; CurrentLiveStatus = LiveStates.RecipientRejected; OnRejectConsultationNotifyArrived(new ConsultationSubscriberInfo { RoomId = null, Id = currentExpert?.UserId, }); lock (EmergencyConsultationLocker) { EmergencyConsultationInfo = null; } } } else { _timingManualResetEvent.Set(); _isEmergencyRequesting = false; CurrentLiveStatus = LiveStates.RecipientRejected; OnRejectConsultationNotifyArrived(new ConsultationSubscriberInfo { RoomId = null, Id = currentExpert?.UserId, }); lock (EmergencyConsultationLocker) { EmergencyConsultationInfo = null; } } } } else { Logger.WriteLineInfo($"Consultation Recipient Timing Task Ended"); } } catch (Exception ex) { Logger.WriteLineError($"ConsultationRecipient Timing Task Error:{ex}"); } }); } /// /// 发起紧急会诊接口 /// private bool CreateAndStartEmergencyConsultation(EmergencyConsultationExpertInfo expertDoctor) { try { if (expertDoctor == null) { Logger.WriteLineError($"Consultation Recipient CreateAndStartEmergencyConsultation Failed,ExpertDoctor is null"); return false; } EmergencyConsultationInfo.CurrentSort = expertDoctor.Sort; StartTimingTask(); using (var request = new CreateAndStartEmergencyConsultationRequest()) { request.EquCode = _uniqueId; request.ApplyDoctor = FLYINSONOUser.Name; request.ExpertDoctor = expertDoctor.UserName; var result = ClientLeaf?.Send(request); if (result != null) { var resultMessage = CreateAndStartEmergencyConsultationResult.Convert(result); if (resultMessage != null) { EmergencyConsultationInfo.ConsultationId = resultMessage.Id; return resultMessage.IsSuccess; } } } } catch (Exception ex) { Logger.WriteLineError($"CreateAndStartEmergencyConsultation Error:{ex}"); } return false; } public LiveStates CancelEmergencyConsultation() { var livestates = LiveStates.Failed; try { Logger.WriteLineInfo($"Consultation Recipient-- {FLYINSONOUser.Name} CancelEmergencyConsultation begin"); using (var request = new CancelEmergencyConsultationRequest()) { request.ConsultationId = EmergencyConsultationInfo.ConsultationId; request.ExpertId = EmergencyConsultationInfo.SortExperts.FirstOrDefault(x => x.Sort == EmergencyConsultationInfo.CurrentSort)?.UserId; var result = ClientLeaf.Send(request); if (result != null) { var resultMessage = CancelEmergencyConsultationResult.Convert(request); if (resultMessage != null) { if (resultMessage.IsSuccess) { CurrentLiveStatus = LiveStates.Cancelled; } } } } lock (EmergencyConsultationLocker) { EmergencyConsultationInfo = null; } _timingManualResetEvent.Set(); _isEmergencyRequesting = false; Logger.WriteLineInfo($"Consultation Recipient-- {FLYINSONOUser.Name} CancelEmergencyConsultation end"); return livestates; } catch (Exception ex) { Logger.WriteLineError($"Consultation Recipient-- {FLYINSONOUser.Name} CancelEmergencyConsultation error:{ex}"); return LiveStates.UnknowException; } } public override void Dispose() { Logger.WriteLineInfo("Consultation Recipient is dispose begin"); _timingManualResetEvent?.Set(); _isEmergencyRequesting = false; base.Dispose(); Logger.WriteLineInfo("Consultation Recipient is dispose end"); } } }