using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Vinno.FIS.TRTCClient.Common.Enum; using Vinno.IUS.Common.Log; using Vinno.vCloud.Common.FIS.Helper; using Vinno.vCloud.FIS.CrossPlatform.Common; using Vinno.vCloud.FIS.CrossPlatform.Common.Enum; using Vinno.vCloud.FIS.CrossPlatform.Common.LiveVideo; using Vinno.vCloud.FIS.CrossPlatform.Common.LiveVideo.Interface; using WingInterfaceLibrary.Enum; using WingInterfaceLibrary.Interface; using WingInterfaceLibrary.LiveConsultation; using WingInterfaceLibrary.Request.Device; namespace Vinno.vCloud.Common.FIS.LiveVideos { public class LiveVideoPusherV2 : IDisposable { private readonly object _locker = new object(); private readonly object _currentDeviceInfolocker = new object(); private readonly ConcurrentDictionary> _pusherCreations; private readonly List _currentVideoDeviceInfoList; private readonly string _token; private readonly ILiveConsultationService _liveConsultationService; private readonly IDeviceService _deviceService; private readonly IEducationService _educationService; private readonly string _deviceModel; private readonly string _deviceType; private readonly string _softwareVersion; private ManualResetEvent _manualResetEvent = new ManualResetEvent(false); private IExtendedData _currentExtendData; private EnumPusherType _currentPushType; private ILiveVideoPusherV2 _pusher; private bool _hasReportedStatus; private int _rtcRoomNo; private PushHeartRateKeeperV2 _pushHeartRateKeeper; private bool _isPaused; /// /// 是否正在推流 /// public bool IsPushing { get; private set; } public ILiveVideoPusherV2 LiveVideoPusher => _pusher; /// /// 推流状态改变时触发,主要给预览用 /// public event EventHandler PusherStateChanged; /// /// The event to notificate the US to show or hide the live video out put window /// public event EventHandler LiveNotification; public LiveVideoPusherV2(string token, ILiveConsultationService liveConsultationService, IDeviceService deviceService, IEducationService educationService, string deviceModel, string deviceType, string softwareVersion) { _token = token; _liveConsultationService = liveConsultationService; _deviceService = deviceService; _educationService = educationService; _deviceModel = deviceModel; _deviceType = deviceType; _softwareVersion = softwareVersion; _currentVideoDeviceInfoList = new List(); _pusherCreations = new ConcurrentDictionary>(); } /// /// 注册具体推流类 /// /// 推流类型 /// 推流类的创建方法 public void RegisterPusher(EnumPusherType type, Func pusher) { _pusherCreations.TryAdd(type, pusher); } public void UpdateCurrentVideoDeviceInfoList(List infos) { lock (_currentDeviceInfolocker) { _currentVideoDeviceInfoList.Clear(); foreach (var info in infos) { var item = info.Clone() as CPVideoDeviceOutputInfo; _currentVideoDeviceInfoList.Add(item); } } } public void SetIsPaused(bool isPaused) { Logger.WriteLineInfo($"LiveVideoPusherV2 Set IsPaused:{isPaused}"); _isPaused = isPaused; if (isPaused) { StopPusher(isPaused); } } public void LiveStateChanged(LiveEventArgs e) { try { if (e.IsLive && IsPushing) { Logger.WriteLineError($"LiveVideoPusherV2 LiveStateChanged Error,it is already pushing."); return; } if (e.IsLive) { _currentPushType = ConvertToPusherType(e); Logger.WriteLineInfo($"LiveVideoPusherV2 Current Push Type:{_currentPushType}"); InitPusher(_currentPushType); StartPusher(e.ExtendedData); } else { StopPusher(); } } catch (Exception ex) { Logger.WriteLineError($"LiveVideoPusherV2 Receive Live State Error:{ex}"); } } public void SetMute(bool isMute) { lock (_locker) { _pusher?.SetMute(isMute); } } public void SwitchMic(string micId) { lock (_locker) { _pusher?.SwitchMic(micId); } } /// /// 开始推流 /// /// protected virtual void StartPusher(IExtendedData pushParams) { PusherStateChanged?.Invoke(this, PusherState.Preparing); try { lock (_locker) { _hasReportedStatus = false; _manualResetEvent.Reset(); _pusher.ChannelStateChanged += OnChannelStateChanged; var success = _pusher.StartPusher(pushParams, _currentVideoDeviceInfoList); if (success) { Task.Run(ReportLiveStateTask); Logger.WriteLineInfo("LiveVideoPusherV2 Start Pusher Success!"); _currentExtendData = pushParams; IsPushing = true; LiveVideoStatusChecker.Instance.IsPushing = true; if (pushParams is RtcExtendedData rtcExtendedData) { _rtcRoomNo = rtcExtendedData.RoomId; if (!CommonParameter.Instance.IsSonopost) { var deviceInfo = rtcExtendedData.UserInfos.FirstOrDefault(x => x.Category == EnumLiveChannelCategory.Main); if (deviceInfo != null && deviceInfo.Width > 0 && deviceInfo.Height > 0) { LiveNotification?.Invoke(this, new LiveNotificationArgs(true, deviceInfo.Width, deviceInfo.Height)); } } } else if (pushParams is RtmpExtendedData rtmpExtendedData) { _rtcRoomNo = 0; if (!CommonParameter.Instance.IsSonopost) { var deviceInfo = rtmpExtendedData.UserInfos.FirstOrDefault(x => x.Category == EnumLiveChannelCategory.Main); if (deviceInfo != null && deviceInfo.Width > 0 && deviceInfo.Height > 0) { LiveNotification?.Invoke(this, new LiveNotificationArgs(true, deviceInfo.Width, deviceInfo.Height)); } } } } else { _hasReportedStatus = true; UpdateChannelState(DeviceLiveStateEnum.Error, "Terminal Start Pushing Failed"); Logger.WriteLineError("LiveVideoPusherV2 Start Pusher Fail!"); } } } catch (Exception e) { Logger.WriteLineError($"LiveVideoPusherV2 Start Pusher Error:{e}"); } PusherStateChanged?.Invoke(this, PusherState.Prepared); } private void ReportLiveStateTask() { try { _manualResetEvent.WaitOne(30000);//30秒钟内没有上报,就认为是推流失败 if (!_hasReportedStatus) { UpdateChannelState(DeviceLiveStateEnum.Error, "Start Pushing Error"); _hasReportedStatus = true; } } catch (Exception ex) { Logger.WriteLineError($"ReportLiveStateTask Error:{ex}"); } } /// /// 结束推流 /// public void StopPusher(bool isPaused = false, bool needInform = false) { PusherStateChanged?.Invoke(this, PusherState.Preparing); try { lock (_locker) { if (_pusher == null) return; _hasReportedStatus = true; _manualResetEvent.Set(); _pusher.ChannelStateChanged -= OnChannelStateChanged; var success = _pusher.StopPusher(); if (success) { Logger.WriteLineInfo("LiveVideoPusherV2 Stop Pusher Success!"); StopHeartRateKeeper(); _pusher.Dispose(); _pusher = null; IsPushing = false; LiveVideoStatusChecker.Instance.IsPushing = false; } else { Logger.WriteLineError("LiveVideoPusherV2 Stop Pusher Fail!"); } UpdateChannelState(DeviceLiveStateEnum.Closed, "Terminal Close Pushing"); if (!CommonParameter.Instance.IsSonopost) { LiveNotification?.Invoke(this, new LiveNotificationArgs(false, 0, 0)); } if (needInform) { Task.Run(() => { LiveVideoStatusChecker.Instance.CurrentMachineConsultationStopPushingEvent?.Invoke(this, EventArgs.Empty); }); } } } catch (Exception e) { Logger.WriteLineError($"LiveVideoPusherV2 Stop Pusher Error:{e}"); } if (!isPaused) { PusherStateChanged?.Invoke(this, PusherState.Prepared); } } /// /// 重新推流 /// public void ReStartPusher() { if (!IsPushing) { return; } Logger.WriteLineInfo("LiveVideoPusherV2 Restart Pusher"); StopPusher(); InitPusher(_currentPushType); StartPusher(_currentExtendData); } private void OnChannelStateChanged(object sender, ChannelStateEventArgsV2 e) { try { Logger.WriteLineInfo($"LiveVideoPusherV2 Update Channel State:{e.Category}_{e.State}"); if (!_hasReportedStatus) { UpdateChannelState((DeviceLiveStateEnum)e.State, "Terminal Start Pushing"); _hasReportedStatus = true; _manualResetEvent.Set(); } } catch (Exception ex) { Logger.WriteLineError($"LiveVideoPusherV2 Update Channel State Error:{ex}"); } } public void UpdateChannelState(DeviceLiveStateEnum state, string message) { try { if (CommonParameter.Instance.IsFeatureReleased) { Logger.WriteLineError($"UpdateChannelState Skipped,because the feature released"); return; } var reportLiveStateRequest = new ReportLiveStateRequest { Token = _token, RoomNo = _rtcRoomNo, LiveState = state, Message = message }; bool result = JsonRpcHelper.ReportLiveState(_deviceService, reportLiveStateRequest); if (result) { Logger.WriteLineInfo($"JsonRPCHelper ReportLiveState Sucess: State:{state},Message:{message}"); } else { Logger.WriteLineError($"JsonRPCHelper ReportLiveState Fail: State:{state},Message:{message}"); } } catch (Exception ex) { Logger.WriteLineError($"LiveVideoPusherV2 UpdateChannelState Error:{ex}"); } } public void StartHeartRateKeeper(string heartRateCode, int interval, EnumHeartRateType heartRateType) { lock (_locker) { if (!IsPushing) { Logger.WriteLineError($"LiveVideoPusherV2 Start PushHeartRateKeeper Fail,Because it isn't Pushing"); return; } if (string.IsNullOrWhiteSpace(heartRateCode)) { Logger.WriteLineError($"LiveVideoPusherV2 Start PushHeartRateKeeper Fail,Because heartRateCode is null"); return; } if (_pushHeartRateKeeper != null) { Logger.WriteLineError($"LiveVideoPusherV2 Start PushHeartRateKeeper Fail,Because it is busy"); return; } else { if (_currentExtendData != null) { _currentExtendData.HeartRateCode = heartRateCode; } switch (heartRateType) { case EnumHeartRateType.LiveConsultation: _pushHeartRateKeeper = new PushHeartRateKeeperV2(_token, heartRateCode, interval, _liveConsultationService); LiveVideoStatusChecker.Instance.IsConsultationLiving = true; LiveVideoStatusChecker.Instance.CurrentConsultationCode = heartRateCode; break; case EnumHeartRateType.Education: _pushHeartRateKeeper = new PushHeartRateKeeperV2(_token, heartRateCode, interval, _educationService); LiveVideoStatusChecker.Instance.IsEducationLiving = true; break; } if (_pushHeartRateKeeper != null) { _pushHeartRateKeeper.Offlined += OnOfflined; _pushHeartRateKeeper.Start(); } Logger.WriteLineInfo($"LiveVideoPusherV2 Start PushHeartRateKeeper,HeartRateType:{heartRateType}"); } } } public void StopHeartRateKeeper() { lock (_locker) { if (_pushHeartRateKeeper != null) { _pushHeartRateKeeper.Offlined -= OnOfflined; _pushHeartRateKeeper.Stop(); _pushHeartRateKeeper = null; Logger.WriteLineInfo("LiveVideoPusherV2 Stop PushHeartRateKeeper"); } } LiveVideoStatusChecker.Instance.IsConsultationLiving = false; LiveVideoStatusChecker.Instance.IsEducationLiving = false; LiveVideoStatusChecker.Instance.CurrentConsultationCode = null; } private void OnOfflined(object sender, EventArgs e) { Logger.WriteLineInfo($"LiveVideoPusherV2 Pusher is offline"); } private void InitPusher(EnumPusherType type) { lock (_locker) { if (_pusherCreations.TryGetValue(type, out var creation)) { if (_pusher != null) { StopPusher(); } _pusher = creation.Invoke(); } else { throw new ArgumentException($"LiveVideoPusherV2 Not support Mode {type}"); } } } private EnumPusherType ConvertToPusherType(LiveEventArgs liveEventArgs) { if (liveEventArgs.Protocol == EnumLiveProtocol.RTC) { var rtcExtendedData = liveEventArgs.ExtendedData as RtcExtendedData; if (liveEventArgs.PushMode == EnumLiveDataMode.MergeLive) { if (liveEventArgs.ExtendedData.MergeType == EnumMergeType.Merge1280X720) { return EnumPusherType.USRtcMerge; } else if (liveEventArgs.ExtendedData.MergeType == EnumMergeType.Merge1920X1080) { return EnumPusherType.RtcMerge; } } else if (liveEventArgs.PushMode == EnumLiveDataMode.OnlyLive) { if (rtcExtendedData.UserInfos.Count() <= 1) { return EnumPusherType.RtcSingle; } else { return EnumPusherType.RtcMulti; } } } else if (liveEventArgs.Protocol == EnumLiveProtocol.Rtmp) { var rtmpExtendedData = liveEventArgs.ExtendedData as RtmpExtendedData; if (liveEventArgs.PushMode == EnumLiveDataMode.MergeLive) { if (liveEventArgs.ExtendedData.MergeType == EnumMergeType.Merge1280X720) { return EnumPusherType.USRtmpMerge; } else if (liveEventArgs.ExtendedData.MergeType == EnumMergeType.Merge1920X1080) { return EnumPusherType.RtmpMerge; } } else if (liveEventArgs.PushMode == EnumLiveDataMode.OnlyLive) { if (rtmpExtendedData.UserInfos.Count() <= 1) { return EnumPusherType.RtmpSingle; } else { return EnumPusherType.RtmpMulti; } } } throw new ArgumentException("LiveVideoPusherV2 Unknown Pusher type"); } public void Dispose() { StopPusher(); _pusherCreations.Clear(); } public void ChangeConsultationCode(string consultationCode) { _pushHeartRateKeeper?.SetHeartRateCode(consultationCode); if (_currentExtendData != null) { _currentExtendData.HeartRateCode = consultationCode; } if (LiveVideoStatusChecker.Instance.IsConsultationLiving) { LiveVideoStatusChecker.Instance.CurrentConsultationCode = consultationCode; } } public bool StartSpeedTest() { try { var createLiveRoomInfoRequest = new CreateLiveRoomInfoRequest { DeviceModel = _deviceModel, DeviceType = _deviceType, DeviceUniqueCode = CommonParameter.Instance.MachineId, SoftwareVersion = _softwareVersion, }; var result = JsonRpcHelper.CreateLiveRoomInfo(_deviceService, createLiveRoomInfoRequest); if (result == null) { throw new InvalidDataException($"JsonRPCHelper CreateLiveRoomInfo Result is null"); } else { var pusher = CrossPlatformHelper.Instance.LiveVideoPusherCreatorV2.CreateRTCSinglePusherV2(); return pusher.StartSpeedTest((uint)result.AppId, result.UserCode, result.UserSign); } } catch (Exception ex) { Logger.WriteLineError($"LiveVideoPusherV2 StartSpeedTest Fail:{ex}"); return false; } } } }