2018-11-01 23:40:50 +08:00
|
|
|
|
using BililiveRecorder.Core.Config;
|
|
|
|
|
using NLog;
|
2018-03-21 20:56:56 +08:00
|
|
|
|
using System;
|
2018-11-28 22:29:35 +08:00
|
|
|
|
using System.Collections;
|
2018-11-02 21:02:25 +08:00
|
|
|
|
using System.Collections.Generic;
|
2018-03-12 18:57:20 +08:00
|
|
|
|
using System.Collections.ObjectModel;
|
2018-11-28 22:29:35 +08:00
|
|
|
|
using System.Collections.Specialized;
|
|
|
|
|
using System.ComponentModel;
|
2018-03-24 09:48:06 +08:00
|
|
|
|
using System.Linq;
|
2018-04-14 05:22:56 +08:00
|
|
|
|
using System.Threading;
|
2018-03-25 15:14:27 +08:00
|
|
|
|
using System.Threading.Tasks;
|
2018-03-12 18:57:20 +08:00
|
|
|
|
|
|
|
|
|
namespace BililiveRecorder.Core
|
|
|
|
|
{
|
2018-11-28 22:29:35 +08:00
|
|
|
|
public class Recorder : IRecorder
|
2018-03-12 18:57:20 +08:00
|
|
|
|
{
|
2018-03-21 20:56:56 +08:00
|
|
|
|
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
|
|
|
|
|
2018-11-28 22:29:35 +08:00
|
|
|
|
private ObservableCollection<IRecordedRoom> Rooms { get; } = new ObservableCollection<IRecordedRoom>();
|
2018-11-01 23:40:50 +08:00
|
|
|
|
public ConfigV1 Config { get; }
|
2018-03-20 00:12:32 +08:00
|
|
|
|
|
2018-11-28 22:29:35 +08:00
|
|
|
|
ConfigV1 IRecorder.Config => Config;
|
|
|
|
|
|
|
|
|
|
public int Count => Rooms.Count;
|
|
|
|
|
|
|
|
|
|
public bool IsReadOnly => true;
|
|
|
|
|
|
|
|
|
|
int ICollection<IRecordedRoom>.Count => Rooms.Count;
|
|
|
|
|
|
|
|
|
|
bool ICollection<IRecordedRoom>.IsReadOnly => true;
|
|
|
|
|
|
2018-10-25 19:20:23 +08:00
|
|
|
|
private readonly Func<int, IRecordedRoom> newIRecordedRoom;
|
2018-04-14 05:22:56 +08:00
|
|
|
|
private CancellationTokenSource tokenSource;
|
|
|
|
|
|
2018-11-01 23:40:50 +08:00
|
|
|
|
private bool _valid = false;
|
|
|
|
|
|
2018-11-28 22:29:35 +08:00
|
|
|
|
public IRecordedRoom this[int index] => Rooms[index];
|
|
|
|
|
|
2018-11-01 23:40:50 +08:00
|
|
|
|
public Recorder(ConfigV1 config, Func<int, IRecordedRoom> iRecordedRoom)
|
2018-03-20 00:12:32 +08:00
|
|
|
|
{
|
2018-11-01 23:40:50 +08:00
|
|
|
|
Config = config;
|
2018-10-25 19:20:23 +08:00
|
|
|
|
newIRecordedRoom = iRecordedRoom;
|
|
|
|
|
|
2018-04-14 05:22:56 +08:00
|
|
|
|
tokenSource = new CancellationTokenSource();
|
2018-12-19 21:24:39 +08:00
|
|
|
|
Repeat.Interval(TimeSpan.FromSeconds(3), DownloadWatchdog, tokenSource.Token);
|
2018-11-10 12:17:41 +08:00
|
|
|
|
|
|
|
|
|
Rooms.CollectionChanged += (sender, e) =>
|
|
|
|
|
{
|
|
|
|
|
logger.Debug($"Rooms.CollectionChanged;{e.Action};" +
|
2019-08-22 01:26:18 +08:00
|
|
|
|
$"O:{e.OldItems?.Cast<IRecordedRoom>()?.Select(rr => rr.RoomId.ToString())?.Aggregate((current, next) => current + "," + next)};" +
|
|
|
|
|
$"N:{e.NewItems?.Cast<IRecordedRoom>()?.Select(rr => rr.RoomId.ToString())?.Aggregate((current, next) => current + "," + next)}");
|
2018-11-10 12:17:41 +08:00
|
|
|
|
};
|
2019-10-31 22:02:19 +08:00
|
|
|
|
|
|
|
|
|
var debouncing = new SemaphoreSlim(1, 1);
|
|
|
|
|
Config.PropertyChanged += async (sender, e) =>
|
|
|
|
|
{
|
|
|
|
|
if (e.PropertyName == nameof(Config.Cookie))
|
|
|
|
|
{
|
|
|
|
|
if (await debouncing.WaitAsync(0))
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
logger.Debug("设置 Cookie 等待...");
|
|
|
|
|
await Task.Delay(100);
|
|
|
|
|
logger.Debug("设置 Cookie 信息...");
|
|
|
|
|
await BililiveAPI.ApplyCookieSettings(Config.Cookie);
|
|
|
|
|
logger.Debug("设置成功");
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
debouncing.Release();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2018-04-14 05:22:56 +08:00
|
|
|
|
}
|
2018-03-20 00:12:32 +08:00
|
|
|
|
|
2018-11-28 22:29:35 +08:00
|
|
|
|
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);
|
|
|
|
|
|
2018-11-01 23:40:50 +08:00
|
|
|
|
public bool Initialize(string workdir)
|
2018-04-14 05:22:56 +08:00
|
|
|
|
{
|
2018-11-10 12:17:41 +08:00
|
|
|
|
logger.Debug("Initialize: " + workdir);
|
2018-11-01 23:40:50 +08:00
|
|
|
|
if (ConfigParser.Load(directory: workdir, config: Config))
|
2018-04-14 05:22:56 +08:00
|
|
|
|
{
|
2018-11-01 23:40:50 +08:00
|
|
|
|
_valid = true;
|
|
|
|
|
Config.WorkDirectory = workdir;
|
|
|
|
|
if ((Config.RoomList?.Count ?? 0) > 0)
|
2018-04-14 05:22:56 +08:00
|
|
|
|
{
|
2018-11-01 23:40:50 +08:00
|
|
|
|
Config.RoomList.ForEach((r) => AddRoom(r.Roomid, r.Enabled));
|
|
|
|
|
}
|
|
|
|
|
ConfigParser.Save(Config.WorkDirectory, Config);
|
|
|
|
|
return true;
|
2018-04-14 05:22:56 +08:00
|
|
|
|
}
|
2018-11-01 23:40:50 +08:00
|
|
|
|
else
|
2018-04-14 05:22:56 +08:00
|
|
|
|
{
|
2018-11-01 23:40:50 +08:00
|
|
|
|
return false;
|
2018-04-14 05:22:56 +08:00
|
|
|
|
}
|
2018-03-20 00:12:32 +08:00
|
|
|
|
}
|
2018-03-12 18:57:20 +08:00
|
|
|
|
|
2018-03-24 02:27:58 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 添加直播间到录播姬
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="roomid">房间号(支持短号)</param>
|
|
|
|
|
/// <exception cref="ArgumentOutOfRangeException"/>
|
2018-11-10 12:17:41 +08:00
|
|
|
|
public void AddRoom(int roomid) => AddRoom(roomid, false);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 添加直播间到录播姬
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="roomid">房间号(支持短号)</param>
|
|
|
|
|
/// <param name="enabled">是否默认启用</param>
|
|
|
|
|
/// <exception cref="ArgumentOutOfRangeException"/>
|
|
|
|
|
public void AddRoom(int roomid, bool enabled)
|
2018-03-21 20:56:56 +08:00
|
|
|
|
{
|
2019-03-01 18:00:20 +08:00
|
|
|
|
try
|
2018-10-25 19:20:23 +08:00
|
|
|
|
{
|
2019-03-01 18:00:20 +08:00
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-22 01:26:18 +08:00
|
|
|
|
logger.Debug("AddRoom 添加了 {roomid} 直播间 ", rr.RoomId);
|
2019-03-01 18:00:20 +08:00
|
|
|
|
Rooms.Add(rr);
|
2018-10-25 19:20:23 +08:00
|
|
|
|
}
|
2019-03-01 18:00:20 +08:00
|
|
|
|
catch (Exception ex)
|
2018-10-25 19:20:23 +08:00
|
|
|
|
{
|
2019-03-01 18:00:20 +08:00
|
|
|
|
logger.Debug(ex, "AddRoom 添加 {roomid} 直播间错误 ", roomid);
|
2018-10-25 19:20:23 +08:00
|
|
|
|
}
|
2018-03-21 20:56:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-24 02:27:58 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 从录播姬移除直播间
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="rr">直播间</param>
|
2018-10-24 14:33:05 +08:00
|
|
|
|
public void RemoveRoom(IRecordedRoom rr)
|
2018-03-24 02:27:58 +08:00
|
|
|
|
{
|
2018-11-01 23:40:50 +08:00
|
|
|
|
if (!_valid) { throw new InvalidOperationException("Not Initialized"); }
|
2018-10-30 20:33:44 +08:00
|
|
|
|
rr.Shutdown();
|
2019-08-22 01:26:18 +08:00
|
|
|
|
logger.Debug("RemoveRoom 移除了直播间 {roomid}", rr.RoomId);
|
2018-11-10 14:57:44 +08:00
|
|
|
|
Rooms.Remove(rr);
|
2018-03-24 02:27:58 +08:00
|
|
|
|
}
|
2018-03-24 09:48:06 +08:00
|
|
|
|
|
|
|
|
|
public void Shutdown()
|
|
|
|
|
{
|
2018-11-01 23:40:50 +08:00
|
|
|
|
if (!_valid) { throw new InvalidOperationException("Not Initialized"); }
|
2018-11-10 12:17:41 +08:00
|
|
|
|
logger.Debug("Shutdown called.");
|
2018-04-14 05:22:56 +08:00
|
|
|
|
tokenSource.Cancel();
|
|
|
|
|
|
2019-08-14 21:41:41 +08:00
|
|
|
|
SaveConfigToFile();
|
|
|
|
|
|
|
|
|
|
Rooms.ToList().ForEach(rr =>
|
|
|
|
|
{
|
|
|
|
|
rr.Shutdown();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SaveConfigToFile()
|
|
|
|
|
{
|
2018-11-02 21:02:25 +08:00
|
|
|
|
Config.RoomList = new List<RoomV1>();
|
|
|
|
|
Rooms.ToList().ForEach(rr =>
|
|
|
|
|
{
|
|
|
|
|
Config.RoomList.Add(new RoomV1()
|
|
|
|
|
{
|
2019-08-22 01:26:18 +08:00
|
|
|
|
Roomid = rr.RoomId,
|
2018-11-02 21:02:25 +08:00
|
|
|
|
Enabled = rr.IsMonitoring,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2018-11-01 23:57:15 +08:00
|
|
|
|
ConfigParser.Save(Config.WorkDirectory, Config);
|
2018-03-24 09:48:06 +08:00
|
|
|
|
}
|
2018-11-01 23:40:50 +08:00
|
|
|
|
|
|
|
|
|
private void DownloadWatchdog()
|
|
|
|
|
{
|
|
|
|
|
if (!_valid) { return; }
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Rooms.ToList().ForEach(room =>
|
|
|
|
|
{
|
|
|
|
|
if (room.IsRecording)
|
|
|
|
|
{
|
2018-12-19 21:24:39 +08:00
|
|
|
|
if (DateTime.Now - room.LastUpdateDateTime > TimeSpan.FromMilliseconds(Config.TimingWatchdogTimeout))
|
2018-11-01 23:40:50 +08:00
|
|
|
|
{
|
2019-08-22 01:26:18 +08:00
|
|
|
|
logger.Warn("服务器停止提供 [{roomid}] 直播间的直播数据,通常是录制时网络不稳定导致,将会断开重连", room.RoomId);
|
2018-11-01 23:40:50 +08:00
|
|
|
|
room.StopRecord();
|
2018-12-18 00:16:24 +08:00
|
|
|
|
room.StartRecord();
|
2018-11-01 23:40:50 +08:00
|
|
|
|
}
|
2018-11-03 07:45:56 +08:00
|
|
|
|
else if (room.Processor != null &&
|
|
|
|
|
((DateTime.Now - room.Processor.StartDateTime).TotalMilliseconds
|
|
|
|
|
>
|
2018-12-19 21:24:39 +08:00
|
|
|
|
(room.Processor.TotalMaxTimestamp + Config.TimingWatchdogBehind))
|
2018-11-03 07:45:56 +08:00
|
|
|
|
)
|
|
|
|
|
{
|
2019-08-22 01:26:18 +08:00
|
|
|
|
logger.Warn("直播间 [{roomid}] 的下载速度达不到录制标准,将断开重连。请检查网络是否稳定", room.RoomId);
|
2018-11-03 07:45:56 +08:00
|
|
|
|
room.StopRecord();
|
2018-12-18 00:16:24 +08:00
|
|
|
|
room.StartRecord();
|
2018-11-03 07:45:56 +08:00
|
|
|
|
}
|
2018-11-01 23:40:50 +08:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
logger.Error(ex, "直播流下载监控出错");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-28 22:29:35 +08:00
|
|
|
|
void ICollection<IRecordedRoom>.Add(IRecordedRoom item) => throw new NotSupportedException("Collection is readonly");
|
|
|
|
|
|
|
|
|
|
void ICollection<IRecordedRoom>.Clear() => throw new NotSupportedException("Collection is readonly");
|
|
|
|
|
|
|
|
|
|
bool ICollection<IRecordedRoom>.Remove(IRecordedRoom item) => throw new NotSupportedException("Collection is readonly");
|
|
|
|
|
|
|
|
|
|
bool ICollection<IRecordedRoom>.Contains(IRecordedRoom item) => Rooms.Contains(item);
|
|
|
|
|
|
|
|
|
|
void ICollection<IRecordedRoom>.CopyTo(IRecordedRoom[] array, int arrayIndex) => Rooms.CopyTo(array, arrayIndex);
|
|
|
|
|
|
|
|
|
|
public IEnumerator<IRecordedRoom> GetEnumerator() => Rooms.GetEnumerator();
|
|
|
|
|
IEnumerator<IRecordedRoom> IEnumerable<IRecordedRoom>.GetEnumerator() => Rooms.GetEnumerator();
|
|
|
|
|
|
|
|
|
|
IEnumerator IEnumerable.GetEnumerator() => Rooms.GetEnumerator();
|
|
|
|
|
|
2018-03-12 18:57:20 +08:00
|
|
|
|
}
|
|
|
|
|
}
|