using BililiveRecorder.Core.Config; using NLog; using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace BililiveRecorder.Core { public class Recorder : IRecorder { private static readonly Logger logger = LogManager.GetCurrentClassLogger(); private ObservableCollection Rooms { get; } = new ObservableCollection(); public ConfigV1 Config { get; } ConfigV1 IRecorder.Config => Config; public int Count => Rooms.Count; public bool IsReadOnly => true; int ICollection.Count => Rooms.Count; bool ICollection.IsReadOnly => true; private readonly Func newIRecordedRoom; private CancellationTokenSource tokenSource; private bool _valid = false; public IRecordedRoom this[int index] => Rooms[index]; public Recorder(ConfigV1 config, Func iRecordedRoom) { Config = config; newIRecordedRoom = iRecordedRoom; tokenSource = new CancellationTokenSource(); Repeat.Interval(TimeSpan.FromSeconds(3), DownloadWatchdog, tokenSource.Token); Rooms.CollectionChanged += (sender, e) => { logger.Debug($"Rooms.CollectionChanged;{e.Action};" + $"O:{e.OldItems?.Cast()?.Select(rr => rr.RealRoomid.ToString())?.Aggregate((current, next) => current + "," + next)};" + $"N:{e.NewItems?.Cast()?.Select(rr => rr.RealRoomid.ToString())?.Aggregate((current, next) => current + "," + next)}"); }; } public event PropertyChangedEventHandler PropertyChanged { add => (Rooms as INotifyPropertyChanged).PropertyChanged += value; remove => (Rooms as INotifyPropertyChanged).PropertyChanged -= value; } public event NotifyCollectionChangedEventHandler CollectionChanged { add => (Rooms as INotifyCollectionChanged).CollectionChanged += value; remove => (Rooms as INotifyCollectionChanged).CollectionChanged -= value; } event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged { add => (Rooms as INotifyPropertyChanged).PropertyChanged += value; remove => (Rooms as INotifyPropertyChanged).PropertyChanged -= value; } event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged { add => (Rooms as INotifyCollectionChanged).CollectionChanged += value; remove => (Rooms as INotifyCollectionChanged).CollectionChanged -= value; } bool IRecorder.Initialize(string workdir) => Initialize(workdir); public bool Initialize(string workdir) { logger.Debug("Initialize: " + workdir); if (ConfigParser.Load(directory: workdir, config: Config)) { _valid = true; Config.WorkDirectory = workdir; if ((Config.RoomList?.Count ?? 0) > 0) { Config.RoomList.ForEach((r) => AddRoom(r.Roomid, r.Enabled)); } ConfigParser.Save(Config.WorkDirectory, Config); return true; } else { return false; } } /// /// 添加直播间到录播姬 /// /// 房间号(支持短号) /// public void AddRoom(int roomid) => AddRoom(roomid, false); /// /// 添加直播间到录播姬 /// /// 房间号(支持短号) /// 是否默认启用 /// public void AddRoom(int roomid, bool enabled) { try { if (!_valid) { throw new InvalidOperationException("Not Initialized"); } if (roomid <= 0) { throw new ArgumentOutOfRangeException(nameof(roomid), "房间号需要大于0"); } var rr = newIRecordedRoom(roomid); if (enabled) { Task.Run(() => rr.Start()); } logger.Debug("AddRoom 添加了 {roomid} 直播间 ", rr.RealRoomid); Rooms.Add(rr); } catch (Exception ex) { logger.Debug(ex, "AddRoom 添加 {roomid} 直播间错误 ", roomid); } } /// /// 从录播姬移除直播间 /// /// 直播间 public void RemoveRoom(IRecordedRoom rr) { if (!_valid) { throw new InvalidOperationException("Not Initialized"); } rr.Shutdown(); logger.Debug("RemoveRoom 移除了直播间 {roomid}", rr.RealRoomid); Rooms.Remove(rr); } public void Shutdown() { if (!_valid) { throw new InvalidOperationException("Not Initialized"); } logger.Debug("Shutdown called."); tokenSource.Cancel(); Config.RoomList = new List(); Rooms.ToList().ForEach(rr => { Config.RoomList.Add(new RoomV1() { Roomid = rr.RealRoomid, Enabled = rr.IsMonitoring, }); }); Rooms.ToList().ForEach(rr => { rr.Shutdown(); }); ConfigParser.Save(Config.WorkDirectory, Config); } private void DownloadWatchdog() { if (!_valid) { return; } try { Rooms.ToList().ForEach(room => { if (room.IsRecording) { if (DateTime.Now - room.LastUpdateDateTime > TimeSpan.FromMilliseconds(Config.TimingWatchdogTimeout)) { logger.Warn("服务器停止提供 [{roomid}] 直播间的直播数据,通常是录制时网络不稳定导致,将会断开重连", room.Roomid); room.StopRecord(); room.StartRecord(); } else if (room.Processor != null && ((DateTime.Now - room.Processor.StartDateTime).TotalMilliseconds > (room.Processor.TotalMaxTimestamp + Config.TimingWatchdogBehind)) ) { logger.Warn("直播间 [{roomid}] 的下载速度达不到录制标准,将断开重连。请检查网络是否稳定", room.Roomid); room.StopRecord(); room.StartRecord(); } } }); } catch (Exception ex) { logger.Error(ex, "直播流下载监控出错"); } } void ICollection.Add(IRecordedRoom item) => throw new NotSupportedException("Collection is readonly"); void ICollection.Clear() => throw new NotSupportedException("Collection is readonly"); bool ICollection.Remove(IRecordedRoom item) => throw new NotSupportedException("Collection is readonly"); bool ICollection.Contains(IRecordedRoom item) => Rooms.Contains(item); void ICollection.CopyTo(IRecordedRoom[] array, int arrayIndex) => Rooms.CopyTo(array, arrayIndex); public IEnumerator GetEnumerator() => Rooms.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => Rooms.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => Rooms.GetEnumerator(); } }