Merge pull request #16 from Bililive/autofac

使用 Autofac
This commit is contained in:
Genteure 2018-10-30 19:05:22 +08:00 committed by GitHub
commit b3fea61c30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 492 additions and 143 deletions

View File

@ -10,6 +10,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac" Version="4.8.1" />
<PackageReference Include="NLog" Version="4.5.0-rc07" /> <PackageReference Include="NLog" Version="4.5.0-rc07" />
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,24 @@
using Autofac;
using System.Net.Sockets;
namespace BililiveRecorder.Core
{
public class CoreModule : Module
{
public CoreModule()
{
}
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<Settings>().As<ISettings>().InstancePerMatchingLifetimeScope("recorder_root");
builder.RegisterType<TcpClient>().AsSelf().ExternallyOwned();
builder.RegisterType<DanmakuReceiver>().As<IDanmakuReceiver>();
builder.RegisterType<StreamMonitor>().As<IStreamMonitor>();
builder.RegisterType<RecordInfo>().As<IRecordInfo>();
builder.RegisterType<RecordedRoom>().As<IRecordedRoom>();
builder.RegisterType<Recorder>().AsSelf().InstancePerMatchingLifetimeScope("recorder_root");
}
}
}

View File

@ -10,15 +10,17 @@ using System.Xml;
namespace BililiveRecorder.Core namespace BililiveRecorder.Core
{ {
public class DanmakuReceiver public class DanmakuReceiver : IDanmakuReceiver
{ {
private static readonly Logger logger = LogManager.GetCurrentClassLogger(); private static readonly Logger logger = LogManager.GetCurrentClassLogger();
private const string defaulthosts = "broadcastlv.chat.bilibili.com"; private const string defaulthosts = "broadcastlv.chat.bilibili.com";
private string CIDInfoUrl = "http://live.bilibili.com/api/player?id=cid:"; private const string CIDInfoUrl = "http://live.bilibili.com/api/player?id=cid:";
private string ChatHost = defaulthosts; private string ChatHost = defaulthosts;
private int ChatPort = 2243; private int ChatPort = 2243;
private readonly Func<TcpClient> funcTcpClient;
private TcpClient Client; private TcpClient Client;
private NetworkStream NetStream; private NetworkStream NetStream;
@ -30,10 +32,20 @@ namespace BililiveRecorder.Core
get => _isConnected; get => _isConnected;
private set private set
{ {
_isConnected = value; if (!value) HeartbeatLoopSource.Cancel(); _isConnected = value;
if (!value)
{
HeartbeatLoopSource.Cancel();
}
} }
} }
private bool _isConnected = false; private bool _isConnected = false;
public DanmakuReceiver(Func<TcpClient> funcTcpClient)
{
this.funcTcpClient = funcTcpClient;
}
public Exception Error { get; private set; } = null; public Exception Error { get; private set; } = null;
public uint ViewerCount { get; private set; } = 0; public uint ViewerCount { get; private set; } = 0;
@ -45,7 +57,11 @@ namespace BililiveRecorder.Core
{ {
try try
{ {
if (this.IsConnected) throw new InvalidOperationException(); if (IsConnected)
{
throw new InvalidOperationException();
}
int channelId = roomId; int channelId = roomId;
try try
@ -83,7 +99,8 @@ namespace BililiveRecorder.Core
logger.Log(roomId, LogLevel.Warn, "获取弹幕服务器地址时出现未知错误", ex); logger.Log(roomId, LogLevel.Warn, "获取弹幕服务器地址时出现未知错误", ex);
} }
Client = new TcpClient(); Client = funcTcpClient();
Client.Connect(ChatHost, ChatPort); Client.Connect(ChatHost, ChatPort);
if (!Client.Connected) if (!Client.Connected)
{ {
@ -93,7 +110,7 @@ namespace BililiveRecorder.Core
SendSocketData(7, "{\"roomid\":" + channelId + ",\"uid\":0}"); SendSocketData(7, "{\"roomid\":" + channelId + ",\"uid\":0}");
IsConnected = true; IsConnected = true;
ReceiveMessageLoopThread = new Thread(this.ReceiveMessageLoop) ReceiveMessageLoopThread = new Thread(ReceiveMessageLoop)
{ {
Name = "ReceiveMessageLoop " + channelId, Name = "ReceiveMessageLoop " + channelId,
IsBackground = true IsBackground = true
@ -101,7 +118,7 @@ namespace BililiveRecorder.Core
ReceiveMessageLoopThread.Start(); ReceiveMessageLoopThread.Start();
HeartbeatLoopSource = new CancellationTokenSource(); HeartbeatLoopSource = new CancellationTokenSource();
Repeat.Interval(TimeSpan.FromSeconds(30), this.SendHeartbeat, HeartbeatLoopSource.Token); Repeat.Interval(TimeSpan.FromSeconds(30), SendHeartbeat, HeartbeatLoopSource.Token);
// HeartbeatLoopThread = new Thread(this.HeartbeatLoop); // HeartbeatLoopThread = new Thread(this.HeartbeatLoop);
// HeartbeatLoopThread.IsBackground = true; // HeartbeatLoopThread.IsBackground = true;
@ -111,7 +128,7 @@ namespace BililiveRecorder.Core
} }
catch (Exception ex) catch (Exception ex)
{ {
this.Error = ex; Error = ex;
logger.Log(roomId, LogLevel.Error, "连接弹幕服务器时发生了未知错误", ex); logger.Log(roomId, LogLevel.Error, "连接弹幕服务器时发生了未知错误", ex);
return false; return false;
} }
@ -136,7 +153,7 @@ namespace BililiveRecorder.Core
try try
{ {
var stableBuffer = new byte[Client.ReceiveBufferSize]; var stableBuffer = new byte[Client.ReceiveBufferSize];
while (this.IsConnected) while (IsConnected)
{ {
NetStream.ReadB(stableBuffer, 0, 4); NetStream.ReadB(stableBuffer, 0, 4);
@ -144,7 +161,9 @@ namespace BililiveRecorder.Core
packetlength = IPAddress.NetworkToHostOrder(packetlength); packetlength = IPAddress.NetworkToHostOrder(packetlength);
if (packetlength < 16) if (packetlength < 16)
{
throw new NotSupportedException("协议失败: (L:" + packetlength + ")"); throw new NotSupportedException("协议失败: (L:" + packetlength + ")");
}
NetStream.ReadB(stableBuffer, 0, 2);//magic NetStream.ReadB(stableBuffer, 0, 2);//magic
NetStream.ReadB(stableBuffer, 0, 2);//protocol_version NetStream.ReadB(stableBuffer, 0, 2);//protocol_version
@ -155,7 +174,10 @@ namespace BililiveRecorder.Core
NetStream.ReadB(stableBuffer, 0, 4);//magic, params? NetStream.ReadB(stableBuffer, 0, 4);//magic, params?
var playloadlength = packetlength - 16; var playloadlength = packetlength - 16;
if (playloadlength == 0) if (playloadlength == 0)
{
continue;//没有内容了 continue;//没有内容了
}
typeId = typeId - 1;//和反编译的代码对应 typeId = typeId - 1;//和反编译的代码对应
var buffer = new byte[playloadlength]; var buffer = new byte[playloadlength];
NetStream.ReadB(buffer, 0, playloadlength); NetStream.ReadB(buffer, 0, playloadlength);
@ -203,7 +225,7 @@ namespace BililiveRecorder.Core
{ {
if (IsConnected) if (IsConnected)
{ {
this.Error = ex; Error = ex;
logger.Error(ex); logger.Error(ex);
_disconnect(); _disconnect();
} }

View File

@ -0,0 +1,18 @@
using System;
namespace BililiveRecorder.Core
{
public interface IDanmakuReceiver
{
bool IsConnected { get; }
Exception Error { get; }
uint ViewerCount { get; }
event DisconnectEvt Disconnected;
event ReceivedRoomCountEvt ReceivedRoomCount;
event ReceivedDanmakuEvt ReceivedDanmaku;
bool Connect(int roomId);
void Disconnect();
}
}

View File

@ -0,0 +1,12 @@
namespace BililiveRecorder.Core
{
public interface IRecordInfo
{
string SavePath { get; }
string StreamFilePrefix { get; set; }
string ClipFilePrefix { get; set; }
string StreamName { get; set; }
string GetStreamFilePath();
string GetClipFilePath();
}
}

View File

@ -0,0 +1,32 @@
using System;
using System.ComponentModel;
namespace BililiveRecorder.Core
{
public interface IRecordedRoom : INotifyPropertyChanged
{
int Roomid { get; }
int RealRoomid { get; }
string StreamerName { get; }
RoomInfo RoomInfo { get; }
IRecordInfo RecordInfo { get; }
IStreamMonitor StreamMonitor { get; }
bool IsMonitoring { get; }
bool IsRecording { get; }
double DownloadSpeedKiBps { get; }
DateTime LastUpdateDateTime { get; }
bool Start();
void Stop();
void StartRecord();
void StopRecord();
bool UpdateRoomInfo();
void Clip();
}
}

View File

@ -0,0 +1,12 @@
using System.ComponentModel;
namespace BililiveRecorder.Core
{
public interface ISettings : INotifyPropertyChanged
{
uint Clip_Past { get; set; }
uint Clip_Future { get; set; }
string SavePath { get; set; }
EnabledFeature Feature { get; set; }
}
}

View File

@ -0,0 +1,13 @@
namespace BililiveRecorder.Core
{
public interface IStreamMonitor
{
int Roomid { get; }
event StreamStatusChangedEvent StreamStatusChanged;
IDanmakuReceiver Receiver { get; } // TODO: 改掉这个写法
bool Start();
void Stop();
void Check(TriggerType type = TriggerType.HttpApiRecheck);
void CheckAfterSeconeds(int seconds, TriggerType type = TriggerType.HttpApiRecheck);
}
}

View File

@ -3,16 +3,17 @@ using System.IO;
namespace BililiveRecorder.Core namespace BililiveRecorder.Core
{ {
public class RecordInfo public class RecordInfo : IRecordInfo
{ {
private static readonly Random random = new Random(); private static readonly Random random = new Random();
public string SavePath; private ISettings Settings { get; }
public string SavePath { get => Settings.SavePath; }
public string StreamFilePrefix = "录制"; public string StreamFilePrefix { get; set; } = "录制";
public string ClipFilePrefix = "剪辑"; public string ClipFilePrefix { get; set; } = "剪辑";
public string StreamName = "某直播间"; public string StreamName { get; set; } = "某直播间";
public string GetStreamFilePath() public string GetStreamFilePath()
=> Path.Combine(SavePath, RemoveInvalidFileName($@"{StreamFilePrefix}-{StreamName}-{DateTime.Now.ToString("yyyyMMddHHmmss")}-{random.Next(100, 999)}.flv")); => Path.Combine(SavePath, RemoveInvalidFileName($@"{StreamFilePrefix}-{StreamName}-{DateTime.Now.ToString("yyyyMMddHHmmss")}-{random.Next(100, 999)}.flv"));
@ -23,13 +24,17 @@ namespace BililiveRecorder.Core
private static string RemoveInvalidFileName(string name) private static string RemoveInvalidFileName(string name)
{ {
foreach (char c in Path.GetInvalidFileNameChars()) foreach (char c in Path.GetInvalidFileNameChars())
{
name = name.Replace(c, '_'); name = name.Replace(c, '_');
}
return name; return name;
} }
public RecordInfo(string name) public RecordInfo(string name, ISettings settings)
{ {
StreamName = name; StreamName = name;
Settings = settings;
} }
} }

View File

@ -1,7 +1,6 @@
using BililiveRecorder.FlvProcessor; using BililiveRecorder.FlvProcessor;
using NLog; using NLog;
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
@ -11,7 +10,7 @@ using System.Net;
namespace BililiveRecorder.Core namespace BililiveRecorder.Core
{ {
public class RecordedRoom : INotifyPropertyChanged public class RecordedRoom : IRecordedRoom
{ {
private static readonly Logger logger = LogManager.GetCurrentClassLogger(); private static readonly Logger logger = LogManager.GetCurrentClassLogger();
@ -19,16 +18,17 @@ namespace BililiveRecorder.Core
public int RealRoomid { get => RoomInfo?.RealRoomid ?? Roomid; } public int RealRoomid { get => RoomInfo?.RealRoomid ?? Roomid; }
public string StreamerName { get => RoomInfo?.Username ?? string.Empty; } public string StreamerName { get => RoomInfo?.Username ?? string.Empty; }
public RoomInfo RoomInfo { get; private set; } public RoomInfo RoomInfo { get; private set; }
public RecordInfo RecordInfo { get; private set; } public IRecordInfo RecordInfo { get; private set; }
public bool IsMonitoring => StreamMonitor.receiver.IsConnected; public bool IsMonitoring => StreamMonitor.Receiver.IsConnected;
public bool IsRecording => flvStream != null; public bool IsRecording => flvStream != null;
public FlvStreamProcessor Processor; // FlvProcessor private readonly Func<string, bool, IFlvStreamProcessor> newIFlvStreamProcessor;
public ObservableCollection<FlvClipProcessor> Clips { get; private set; } = new ObservableCollection<FlvClipProcessor>(); public IFlvStreamProcessor Processor { get; set; } // FlvProcessor
public ObservableCollection<IFlvClipProcessor> Clips { get; private set; } = new ObservableCollection<IFlvClipProcessor>();
internal StreamMonitor StreamMonitor { get; } public IStreamMonitor StreamMonitor { get; }
private Settings _settings { get; } private ISettings _settings { get; }
private HttpWebRequest webRequest; private HttpWebRequest webRequest;
private Stream flvStream; private Stream flvStream;
private bool flv_shutdown = false; private bool flv_shutdown = false;
@ -42,21 +42,25 @@ namespace BililiveRecorder.Core
public DateTime LastUpdateDateTime { get; private set; } = DateTime.Now; public DateTime LastUpdateDateTime { get; private set; } = DateTime.Now;
public long LastUpdateSize { get; private set; } = 0; public long LastUpdateSize { get; private set; } = 0;
public RecordedRoom(Settings settings, int roomid) public RecordedRoom(ISettings settings,
Func<string, IRecordInfo> newIRecordInfo,
Func<int, IStreamMonitor> newIStreamMonitor,
Func<string, bool, IFlvStreamProcessor> newIFlvStreamProcessor,
int roomid)
{ {
this.newIFlvStreamProcessor = newIFlvStreamProcessor;
_settings = settings; _settings = settings;
_settings.PropertyChanged += _settings_PropertyChanged; // _settings.PropertyChanged += _settings_PropertyChanged;
// TODO: 事件导致的内存泄漏
Roomid = roomid; Roomid = roomid;
UpdateRoomInfo(); UpdateRoomInfo();
RecordInfo = new RecordInfo(StreamerName) RecordInfo = newIRecordInfo(StreamerName);
{
SavePath = _settings.SavePath
};
StreamMonitor = new StreamMonitor(RealRoomid); StreamMonitor = newIStreamMonitor(RealRoomid);
StreamMonitor.StreamStatusChanged += StreamMonitor_StreamStatusChanged; StreamMonitor.StreamStatusChanged += StreamMonitor_StreamStatusChanged;
} }
@ -75,21 +79,31 @@ namespace BililiveRecorder.Core
private void _settings_PropertyChanged(object sender, PropertyChangedEventArgs e) private void _settings_PropertyChanged(object sender, PropertyChangedEventArgs e)
{ {
// TODO: 事件导致的内存泄漏
/**
if (e.PropertyName == nameof(_settings.Clip_Past)) if (e.PropertyName == nameof(_settings.Clip_Past))
{ {
if (Processor != null) if (Processor != null)
{
Processor.Clip_Past = _settings.Clip_Past; Processor.Clip_Past = _settings.Clip_Past;
}
} }
else if (e.PropertyName == nameof(_settings.Clip_Future)) else if (e.PropertyName == nameof(_settings.Clip_Future))
{ {
if (Processor != null) if (Processor != null)
{
Processor.Clip_Future = _settings.Clip_Future; Processor.Clip_Future = _settings.Clip_Future;
}
} }
else if (e.PropertyName == nameof(_settings.SavePath)) else if (e.PropertyName == nameof(_settings.SavePath))
{ {
if (RecordInfo != null) if (RecordInfo != null)
{
RecordInfo.SavePath = _settings.SavePath; RecordInfo.SavePath = _settings.SavePath;
}
} }
*/
} }
private void StreamMonitor_StreamStatusChanged(object sender, StreamStatusChangedArgs e) private void StreamMonitor_StreamStatusChanged(object sender, StreamStatusChangedArgs e)
@ -159,7 +173,8 @@ namespace BililiveRecorder.Core
triggerType = TriggerType.HttpApi; triggerType = TriggerType.HttpApi;
} }
Processor = new FlvStreamProcessor(savepath, _settings.Feature == EnabledFeature.RecordOnly); // Processor = new FlvStreamProcessor(savepath, _settings.Feature == EnabledFeature.RecordOnly);
Processor = newIFlvStreamProcessor(savepath, _settings.Feature == EnabledFeature.RecordOnly);
Processor.TagProcessed += Processor_TagProcessed; Processor.TagProcessed += Processor_TagProcessed;
Processor.StreamFinalized += Processor_StreamFinalized; Processor.StreamFinalized += Processor_StreamFinalized;
Processor.GetFileName = RecordInfo.GetStreamFilePath; Processor.GetFileName = RecordInfo.GetStreamFilePath;
@ -183,14 +198,20 @@ namespace BililiveRecorder.Core
_CleanupFlvRequest(); _CleanupFlvRequest();
logger.Log(RealRoomid, LogLevel.Info, "直播已结束,停止录制。" + (triggerType != TriggerType.HttpApiRecheck ? "将在30秒后重试启动。" : "")); logger.Log(RealRoomid, LogLevel.Info, "直播已结束,停止录制。" + (triggerType != TriggerType.HttpApiRecheck ? "将在30秒后重试启动。" : ""));
if (triggerType != TriggerType.HttpApiRecheck) if (triggerType != TriggerType.HttpApiRecheck)
{
StreamMonitor.CheckAfterSeconeds(30); StreamMonitor.CheckAfterSeconeds(30);
}
} }
else else
{ {
if (bytesRead != buffer.Length) if (bytesRead != buffer.Length)
{
Processor.AddBytes(buffer.Take(bytesRead).ToArray()); Processor.AddBytes(buffer.Take(bytesRead).ToArray());
}
else else
{
Processor.AddBytes(buffer); Processor.AddBytes(buffer);
}
flvStream.BeginRead(buffer, 0, BUF_SIZE, callback, null); flvStream.BeginRead(buffer, 0, BUF_SIZE, callback, null);
} }
@ -202,7 +223,9 @@ namespace BililiveRecorder.Core
{ {
logger.Log(RealRoomid, LogLevel.Info, "直播流下载连接出错。" + (triggerType != TriggerType.HttpApiRecheck ? "将在30秒后重试启动。" : ""), ex); logger.Log(RealRoomid, LogLevel.Info, "直播流下载连接出错。" + (triggerType != TriggerType.HttpApiRecheck ? "将在30秒后重试启动。" : ""), ex);
if (triggerType != TriggerType.HttpApiRecheck) if (triggerType != TriggerType.HttpApiRecheck)
{
StreamMonitor.CheckAfterSeconeds(30); StreamMonitor.CheckAfterSeconeds(30);
}
} }
else else
{ {
@ -220,7 +243,9 @@ namespace BililiveRecorder.Core
_CleanupFlvRequest(); _CleanupFlvRequest();
logger.Log(RealRoomid, LogLevel.Warn, "启动直播流下载出错。" + (triggerType != TriggerType.HttpApiRecheck ? "将在30秒后重试启动。" : ""), ex); logger.Log(RealRoomid, LogLevel.Warn, "启动直播流下载出错。" + (triggerType != TriggerType.HttpApiRecheck ? "将在30秒后重试启动。" : ""), ex);
if (triggerType != TriggerType.HttpApiRecheck) if (triggerType != TriggerType.HttpApiRecheck)
{
StreamMonitor.CheckAfterSeconeds(30); StreamMonitor.CheckAfterSeconeds(30);
}
} }
} }
@ -286,7 +311,11 @@ namespace BililiveRecorder.Core
// Called by API or GUI // Called by API or GUI
public void Clip() public void Clip()
{ {
if (Processor == null) return; if (Processor == null)
{
return;
}
var clip = Processor.Clip(); var clip = Processor.Clip();
clip.ClipFinalized += CallBack_ClipFinalized; clip.ClipFinalized += CallBack_ClipFinalized;
clip.GetFileName = RecordInfo.GetClipFilePath; clip.GetFileName = RecordInfo.GetClipFilePath;
@ -295,6 +324,7 @@ namespace BililiveRecorder.Core
private void CallBack_ClipFinalized(object sender, ClipFinalizedArgs e) private void CallBack_ClipFinalized(object sender, ClipFinalizedArgs e)
{ {
e.ClipProcessor.ClipFinalized -= CallBack_ClipFinalized;
if (Clips.Remove(e.ClipProcessor)) if (Clips.Remove(e.ClipProcessor))
{ {
Debug.WriteLine("Clip Finalized"); Debug.WriteLine("Clip Finalized");

View File

@ -1,9 +1,7 @@
using NLog; using NLog;
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -13,13 +11,17 @@ namespace BililiveRecorder.Core
{ {
private static readonly Logger logger = LogManager.GetCurrentClassLogger(); private static readonly Logger logger = LogManager.GetCurrentClassLogger();
public ObservableCollection<RecordedRoom> Rooms { get; } = new ObservableCollection<RecordedRoom>(); public ObservableCollection<IRecordedRoom> Rooms { get; } = new ObservableCollection<IRecordedRoom>();
public Settings Settings { get; } = new Settings(); public ISettings Settings { get; }
private readonly Func<int, IRecordedRoom> newIRecordedRoom;
private CancellationTokenSource tokenSource; private CancellationTokenSource tokenSource;
public Recorder() public Recorder(ISettings settings, Func<int, IRecordedRoom> iRecordedRoom)
{ {
Settings = settings;
newIRecordedRoom = iRecordedRoom;
tokenSource = new CancellationTokenSource(); tokenSource = new CancellationTokenSource();
Repeat.Interval(TimeSpan.FromSeconds(6), DownloadWatchdog, tokenSource.Token); Repeat.Interval(TimeSpan.FromSeconds(6), DownloadWatchdog, tokenSource.Token);
} }
@ -55,10 +57,16 @@ namespace BililiveRecorder.Core
public void AddRoom(int roomid, bool enabled = false) public void AddRoom(int roomid, bool enabled = false)
{ {
if (roomid <= 0) if (roomid <= 0)
{
throw new ArgumentOutOfRangeException(nameof(roomid), "房间号需要大于0"); throw new ArgumentOutOfRangeException(nameof(roomid), "房间号需要大于0");
var rr = new RecordedRoom(Settings, roomid); }
// var rr = new RecordedRoom(Settings, roomid);
var rr = newIRecordedRoom(roomid);
if (enabled) if (enabled)
{
Task.Run(() => rr.Start()); Task.Run(() => rr.Start());
}
Rooms.Add(rr); Rooms.Add(rr);
} }
@ -66,7 +74,7 @@ namespace BililiveRecorder.Core
/// 从录播姬移除直播间 /// 从录播姬移除直播间
/// </summary> /// </summary>
/// <param name="rr">直播间</param> /// <param name="rr">直播间</param>
public void RemoveRoom(RecordedRoom rr) public void RemoveRoom(IRecordedRoom rr)
{ {
rr.Stop(); rr.Stop();
rr.StopRecord(); rr.StopRecord();

View File

@ -4,7 +4,7 @@ using System.ComponentModel;
namespace BililiveRecorder.Core namespace BililiveRecorder.Core
{ {
public class Settings : INotifyPropertyChanged public class Settings : ISettings
{ {
private static readonly Logger logger = LogManager.GetCurrentClassLogger(); private static readonly Logger logger = LogManager.GetCurrentClassLogger();
@ -45,7 +45,11 @@ namespace BililiveRecorder.Core
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
protected bool SetField<T>(ref T field, T value, string propertyName) protected bool SetField<T>(ref T field, T value, string propertyName)
{ {
if (EqualityComparer<T>.Default.Equals(field, value)) return false; if (EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}
logger.Debug("设置 [{0}] 的值已从 [{1}] 修改到 [{2}]", propertyName, field, value); logger.Debug("设置 [{0}] 的值已从 [{1}] 修改到 [{2}]", propertyName, field, value);
field = value; field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

View File

@ -5,22 +5,23 @@ using System.Threading.Tasks;
namespace BililiveRecorder.Core namespace BililiveRecorder.Core
{ {
public class StreamMonitor public class StreamMonitor : IStreamMonitor
{ {
private static readonly Logger logger = LogManager.GetCurrentClassLogger(); private static readonly Logger logger = LogManager.GetCurrentClassLogger();
public int Roomid { get; private set; } = 0; public int Roomid { get; private set; } = 0;
public event StreamStatusChangedEvent StreamStatusChanged; public event StreamStatusChangedEvent StreamStatusChanged;
public readonly DanmakuReceiver receiver = new DanmakuReceiver(); public IDanmakuReceiver Receiver { get; }
private CancellationTokenSource TokenSource = null; private CancellationTokenSource TokenSource = null;
public StreamMonitor(int roomid) public StreamMonitor(int roomid, IDanmakuReceiver danmakuReceiver)
{ {
Receiver = danmakuReceiver;
Roomid = roomid; Roomid = roomid;
receiver.Disconnected += Receiver_Disconnected; Receiver.Disconnected += Receiver_Disconnected;
receiver.ReceivedDanmaku += Receiver_ReceivedDanmaku; Receiver.ReceivedDanmaku += Receiver_ReceivedDanmaku;
receiver.ReceivedRoomCount += Receiver_ReceivedRoomCount; Receiver.ReceivedRoomCount += Receiver_ReceivedRoomCount;
} }
private void Receiver_ReceivedRoomCount(object sender, ReceivedRoomCountArgs e) private void Receiver_ReceivedRoomCount(object sender, ReceivedRoomCountArgs e)
@ -46,11 +47,11 @@ namespace BililiveRecorder.Core
{ {
logger.Warn(e.Error, "弹幕连接被断开将每30秒尝试重连一次"); logger.Warn(e.Error, "弹幕连接被断开将每30秒尝试重连一次");
bool connect_result = false; bool connect_result = false;
while (!receiver.IsConnected && !TokenSource.Token.IsCancellationRequested) while (!Receiver.IsConnected && !TokenSource.Token.IsCancellationRequested)
{ {
Thread.Sleep(1000 * 30); // 备注:这是运行在 ReceiveMessageLoop 线程上的 Thread.Sleep(1000 * 30); // 备注:这是运行在 ReceiveMessageLoop 线程上的
logger.Log(Roomid, LogLevel.Info, "重连弹幕服务器..."); logger.Log(Roomid, LogLevel.Info, "重连弹幕服务器...");
connect_result = receiver.Connect(Roomid); connect_result = Receiver.Connect(Roomid);
} }
if (connect_result) if (connect_result)
@ -69,7 +70,9 @@ namespace BililiveRecorder.Core
try try
{ {
if (BililiveAPI.GetRoomInfo(Roomid).isStreaming) if (BililiveAPI.GetRoomInfo(Roomid).isStreaming)
{
_StartRecord(TriggerType.HttpApi); _StartRecord(TriggerType.HttpApi);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -79,9 +82,14 @@ namespace BililiveRecorder.Core
public bool Start() public bool Start()
{ {
if (!receiver.IsConnected) if (!Receiver.IsConnected)
if (!receiver.Connect(Roomid)) {
if (!Receiver.Connect(Roomid))
{
return false; return false;
}
}
logger.Log(Roomid, LogLevel.Info, "弹幕服务器连接成功"); logger.Log(Roomid, LogLevel.Info, "弹幕服务器连接成功");
// Run 96 times a day. // Run 96 times a day.
@ -95,8 +103,11 @@ namespace BililiveRecorder.Core
public void Stop() public void Stop()
{ {
if (receiver.IsConnected) if (Receiver.IsConnected)
receiver.Disconnect(); {
Receiver.Disconnect();
}
TokenSource?.Cancel(); TokenSource?.Cancel();
TokenSource = null; TokenSource = null;
} }
@ -113,7 +124,9 @@ namespace BililiveRecorder.Core
public void CheckAfterSeconeds(int seconds, TriggerType type = TriggerType.HttpApiRecheck) public void CheckAfterSeconeds(int seconds, TriggerType type = TriggerType.HttpApiRecheck)
{ {
if (seconds < 0) if (seconds < 0)
{
throw new ArgumentOutOfRangeException(nameof(seconds), "不能小于0"); throw new ArgumentOutOfRangeException(nameof(seconds), "不能小于0");
}
Task.Run(() => Task.Run(() =>
{ {

View File

@ -29,7 +29,7 @@ namespace BililiveRecorder.Core
} }
} }
public static void ApplyTo(this Settings val1, Settings val2) public static void ApplyTo(this ISettings val1, ISettings val2)
{ {
foreach (var p in val1.GetType().GetProperties()) foreach (var p in val1.GetType().GetProperties())
{ {

View File

@ -10,6 +10,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac" Version="4.8.1" />
<PackageReference Include="NLog" Version="4.5.0-rc07" /> <PackageReference Include="NLog" Version="4.5.0-rc07" />
</ItemGroup> </ItemGroup>

View File

@ -1,26 +1,21 @@
using System; namespace BililiveRecorder.FlvProcessor
using System.Collections.Generic;
using System.Text;
namespace BililiveRecorder.FlvProcessor
{ {
public delegate void TagProcessedEvent(object sender, TagProcessedArgs e); public delegate void TagProcessedEvent(object sender, TagProcessedArgs e);
public class TagProcessedArgs public class TagProcessedArgs
{ {
public FlvTag Tag; public IFlvTag Tag;
} }
public delegate void ClipFinalizedEvent(object sender, ClipFinalizedArgs e); public delegate void ClipFinalizedEvent(object sender, ClipFinalizedArgs e);
public class ClipFinalizedArgs public class ClipFinalizedArgs
{ {
public FlvClipProcessor ClipProcessor; public IFlvClipProcessor ClipProcessor;
} }
public delegate void StreamFinalizedEvent(object sender, StreamFinalizedArgs e); public delegate void StreamFinalizedEvent(object sender, StreamFinalizedArgs e);
public class StreamFinalizedArgs public class StreamFinalizedArgs
{ {
public FlvStreamProcessor StreamProcessor; public IFlvStreamProcessor StreamProcessor;
} }
} }

View File

@ -2,32 +2,37 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text;
namespace BililiveRecorder.FlvProcessor namespace BililiveRecorder.FlvProcessor
{ {
public class FlvClipProcessor public class FlvClipProcessor : IFlvClipProcessor
{ {
private static readonly Logger logger = LogManager.GetCurrentClassLogger(); private static readonly Logger logger = LogManager.GetCurrentClassLogger();
public readonly FlvMetadata Header; public IFlvMetadata Header { get; private set; }
public readonly List<FlvTag> HTags; public List<IFlvTag> HTags { get; private set; }
public readonly List<FlvTag> Tags; public List<IFlvTag> Tags { get; private set; }
private int target = -1; private int target = -1;
public Func<string> GetFileName; public Func<string> GetFileName { get; set; }
public FlvClipProcessor(FlvMetadata header, List<FlvTag> head, List<FlvTag> past, uint future) public FlvClipProcessor()
{ {
Header = header;
HTags = head;
Tags = past;
target = Tags[Tags.Count - 1].TimeStamp + (int)(future * FlvStreamProcessor.SEC_TO_MS);
logger.Debug("Clip 创建 Tags.Count={0} Tags[0].TimeStamp={1} Tags[Tags.Count-1].TimeStamp={2} Tags里秒数={3}",
Tags.Count, Tags[0].TimeStamp, Tags[Tags.Count - 1].TimeStamp, (Tags[Tags.Count - 1].TimeStamp - Tags[0].TimeStamp) / 1000d);
} }
public void AddTag(FlvTag tag) public IFlvClipProcessor Initialize(IFlvMetadata metadata, List<IFlvTag> head, List<IFlvTag> data, uint seconds)
{
Header = metadata;
HTags = head;
Tags = data;
target = Tags[Tags.Count - 1].TimeStamp + (int)(seconds * FlvStreamProcessor.SEC_TO_MS);
logger.Debug("Clip 创建 Tags.Count={0} Tags[0].TimeStamp={1} Tags[Tags.Count-1].TimeStamp={2} Tags里秒数={3}",
Tags.Count, Tags[0].TimeStamp, Tags[Tags.Count - 1].TimeStamp, (Tags[Tags.Count - 1].TimeStamp - Tags[0].TimeStamp) / 1000d);
return this;
}
public void AddTag(IFlvTag tag)
{ {
Tags.Add(tag); Tags.Add(tag);
if (tag.TimeStamp >= target) if (tag.TimeStamp >= target)

View File

@ -6,23 +6,29 @@ using System.Text;
namespace BililiveRecorder.FlvProcessor namespace BililiveRecorder.FlvProcessor
{ {
public class FlvMetadata public class FlvMetadata : IFlvMetadata
{ {
public IDictionary<string, object> Meta = new Dictionary<string, object>(); public IDictionary<string, object> Meta { get; set; } = new Dictionary<string, object>();
public static FlvMetadata Parse(byte[] data) public FlvMetadata()
{ {
var m = new FlvMetadata Meta["duration"] = 0.0;
Meta["lasttimestamp"] = 0.0;
}
public FlvMetadata(byte[] data)
{
Meta = _Decode(data);
if (!Meta.ContainsKey("duration"))
{ {
Meta = _Decode(data) Meta["duration"] = 0.0;
}; }
if (!m.Meta.ContainsKey("duration")) if (!Meta.ContainsKey("lasttimestamp"))
m.Meta["duration"] = 0.0; {
if (!m.Meta.ContainsKey("lasttimestamp")) Meta["lasttimestamp"] = 0.0;
m.Meta["lasttimestamp"] = 0.0; }
return m;
} }
public byte[] ToBytes() public byte[] ToBytes()

View File

@ -0,0 +1,20 @@
using Autofac;
namespace BililiveRecorder.FlvProcessor
{
public class FlvProcessorModule : Module
{
public FlvProcessorModule()
{
}
protected override void Load(ContainerBuilder builder)
{
builder.Register(c => new FlvTag()).As<IFlvTag>();
builder.RegisterType<FlvMetadata>().As<IFlvMetadata>();
builder.RegisterType<FlvClipProcessor>().As<IFlvClipProcessor>();
builder.RegisterType<FlvStreamProcessor>().As<IFlvStreamProcessor>();
}
}
}

View File

@ -1,18 +1,15 @@
using NLog; using NLog;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Text;
namespace BililiveRecorder.FlvProcessor namespace BililiveRecorder.FlvProcessor
{ {
// TODO: 重构 Tag 解析流程 // TODO: 重构 Tag 解析流程
// TODO: 添加测试 // TODO: 添加测试
// 注:下载时会按照 4 11 N bytes 下载 // 注:下载时会按照 4 11 N bytes 下载
public class FlvStreamProcessor : IDisposable public class FlvStreamProcessor : IFlvStreamProcessor
{ {
private static readonly Logger logger = LogManager.GetCurrentClassLogger(); private static readonly Logger logger = LogManager.GetCurrentClassLogger();
@ -35,22 +32,22 @@ namespace BililiveRecorder.FlvProcessor
// 0x00, // the "0th" tag has a length of 0 // 0x00, // the "0th" tag has a length of 0
}; };
public FlvMetadata Metadata = null; public IFlvMetadata Metadata { get; set; } = null;
public event TagProcessedEvent TagProcessed; public event TagProcessedEvent TagProcessed;
public event StreamFinalizedEvent StreamFinalized; public event StreamFinalizedEvent StreamFinalized;
public Func<string> GetFileName; public Func<string> GetFileName { get; set; }
public uint Clip_Past = 90; public uint Clip_Past { get; set; } = 90;
public uint Clip_Future = 30; public uint Clip_Future { get; set; } = 30;
private readonly bool _noClip = false; private readonly bool _noClip = false;
private bool _headerParsed = false; private bool _headerParsed = false;
private readonly List<FlvTag> HTags = new List<FlvTag>(); private readonly List<IFlvTag> HTags = new List<IFlvTag>();
private readonly List<FlvTag> Tags = new List<FlvTag>(); private readonly List<IFlvTag> Tags = new List<IFlvTag>();
private readonly MemoryStream _buffer = new MemoryStream(); private readonly MemoryStream _buffer = new MemoryStream();
private readonly MemoryStream _data = new MemoryStream(); private readonly MemoryStream _data = new MemoryStream();
private FlvTag currentTag = null; private IFlvTag currentTag = null;
private object _writelock = new object(); private readonly object _writelock = new object();
private bool Finallized = false; private bool Finallized = false;
private readonly FileStream _fs; private readonly FileStream _fs;
@ -62,8 +59,16 @@ namespace BililiveRecorder.FlvProcessor
public int TagAudioCount { get; private set; } = 0; public int TagAudioCount { get; private set; } = 0;
private bool hasOffset = false; private bool hasOffset = false;
public FlvStreamProcessor(string path, bool noclip) private readonly Func<IFlvClipProcessor> funcFlvClipProcessor;
private readonly Func<byte[], IFlvMetadata> funcFlvMetadata;
private readonly Func<IFlvTag> funcFlvTag;
public FlvStreamProcessor(Func<IFlvClipProcessor> funcFlvClipProcessor, Func<byte[], IFlvMetadata> funcFlvMetadata, Func<IFlvTag> funcFlvTag, string path, bool noclip)
{ {
this.funcFlvClipProcessor = funcFlvClipProcessor;
this.funcFlvMetadata = funcFlvMetadata;
this.funcFlvTag = funcFlvTag;
_noClip = noclip; _noClip = noclip;
if (path == null) if (path == null)
{ {
@ -85,7 +90,9 @@ namespace BililiveRecorder.FlvProcessor
public void AddBytes(byte[] data) public void AddBytes(byte[] data)
{ {
lock (_writelock) lock (_writelock)
{
_AddBytes(data); _AddBytes(data);
}
} }
private void _AddBytes(byte[] data) private void _AddBytes(byte[] data)
@ -104,10 +111,15 @@ namespace BililiveRecorder.FlvProcessor
} }
var r = new bool[FLV_HEADER_BYTES.Length]; var r = new bool[FLV_HEADER_BYTES.Length];
for (int i = 0; i < FLV_HEADER_BYTES.Length; i++) for (int i = 0; i < FLV_HEADER_BYTES.Length; i++)
{
r[i] = data[i] == FLV_HEADER_BYTES[i]; r[i] = data[i] == FLV_HEADER_BYTES[i];
}
bool succ = r.All(x => x); bool succ = r.All(x => x);
if (!succ) if (!succ)
{
throw new NotSupportedException("Not FLV Stream or Not Supported"); // TODO: custom Exception. throw new NotSupportedException("Not FLV Stream or Not Supported"); // TODO: custom Exception.
}
_headerParsed = true; _headerParsed = true;
_AddBytes(data.Skip(FLV_HEADER_BYTES.Length).ToArray()); _AddBytes(data.Skip(FLV_HEADER_BYTES.Length).ToArray());
@ -140,7 +152,7 @@ namespace BililiveRecorder.FlvProcessor
} }
} }
private void _TagCreated(FlvTag tag) private void _TagCreated(IFlvTag tag)
{ {
if (Metadata == null) if (Metadata == null)
{ {
@ -149,7 +161,7 @@ namespace BililiveRecorder.FlvProcessor
_fs?.Write(FLV_HEADER_BYTES, 0, FLV_HEADER_BYTES.Length); _fs?.Write(FLV_HEADER_BYTES, 0, FLV_HEADER_BYTES.Length);
_fs?.Write(new byte[] { 0, 0, 0, 0, }, 0, 4); _fs?.Write(new byte[] { 0, 0, 0, 0, }, 0, 4);
Metadata = FlvMetadata.Parse(tag.Data); Metadata = funcFlvMetadata(tag.Data);
// TODO: 添加录播姬标记、录制信息 // TODO: 添加录播姬标记、录制信息
@ -202,7 +214,10 @@ namespace BililiveRecorder.FlvProcessor
{ {
tag.TimeStamp -= BaseTimeStamp; // 修复时间戳 tag.TimeStamp -= BaseTimeStamp; // 修复时间戳
if (tag.TimeStamp < 0) if (tag.TimeStamp < 0)
{
tag.TimeStamp = 0; tag.TimeStamp = 0;
}
MaxTimeStamp = Math.Max(MaxTimeStamp, tag.TimeStamp); MaxTimeStamp = Math.Max(MaxTimeStamp, tag.TimeStamp);
} }
else else
@ -221,7 +236,9 @@ namespace BililiveRecorder.FlvProcessor
LasttimeRemovedTimestamp = MaxTimeStamp; LasttimeRemovedTimestamp = MaxTimeStamp;
int max_remove_index = Tags.FindLastIndex(x => x.IsVideoKeyframe && ((MaxTimeStamp - x.TimeStamp) > (Clip_Past * SEC_TO_MS))); int max_remove_index = Tags.FindLastIndex(x => x.IsVideoKeyframe && ((MaxTimeStamp - x.TimeStamp) > (Clip_Past * SEC_TO_MS)));
if (max_remove_index > 0) if (max_remove_index > 0)
{
Tags.RemoveRange(0, max_remove_index); Tags.RemoveRange(0, max_remove_index);
}
// Tags.RemoveRange(0, max_remove_index + 1 - 1); // Tags.RemoveRange(0, max_remove_index + 1 - 1);
// 给将来的备注:这里是故意 + 1 - 1 的,因为要保留选中的那个关键帧, + 1 就把关键帧删除了 // 给将来的备注:这里是故意 + 1 - 1 的,因为要保留选中的那个关键帧, + 1 就把关键帧删除了
} }
@ -242,7 +259,7 @@ namespace BililiveRecorder.FlvProcessor
_buffer.Write(data, 0, data.Length); _buffer.Write(data, 0, data.Length);
long dataLen = _buffer.Position; long dataLen = _buffer.Position;
_buffer.Position = 0; _buffer.Position = 0;
FlvTag tag = new FlvTag(); IFlvTag tag = funcFlvTag();
// Previous Tag Size // Previous Tag Size
_buffer.Read(b, 0, 4); _buffer.Read(b, 0, 4);
@ -272,7 +289,7 @@ namespace BililiveRecorder.FlvProcessor
_AddBytes(rest); _AddBytes(rest);
} }
public FlvClipProcessor Clip() public IFlvClipProcessor Clip()
{ {
// 如果禁用 clip 功能 或者 已经结束处理了 // 如果禁用 clip 功能 或者 已经结束处理了
if (_noClip || Finallized) if (_noClip || Finallized)
@ -284,7 +301,7 @@ namespace BililiveRecorder.FlvProcessor
lock (_writelock) lock (_writelock)
{ {
logger.Info("剪辑处理中,将会保存过去 {0} 秒和将来 {1} 秒的直播流", (Tags[Tags.Count - 1].TimeStamp - Tags[0].TimeStamp) / 1000d, Clip_Future); logger.Info("剪辑处理中,将会保存过去 {0} 秒和将来 {1} 秒的直播流", (Tags[Tags.Count - 1].TimeStamp - Tags[0].TimeStamp) / 1000d, Clip_Future);
return new FlvClipProcessor(Metadata, HTags, new List<FlvTag>(Tags.ToArray()), Clip_Future); return ((funcFlvClipProcessor()).Initialize(Metadata, HTags, new List<IFlvTag>(Tags.ToArray()), Clip_Future));
} }
} }
} }
@ -292,6 +309,7 @@ namespace BililiveRecorder.FlvProcessor
public void FinallizeFile() public void FinallizeFile()
{ {
if (!Finallized) if (!Finallized)
{
lock (_writelock) lock (_writelock)
{ {
try try
@ -322,6 +340,7 @@ namespace BililiveRecorder.FlvProcessor
StreamFinalized?.Invoke(this, new StreamFinalizedArgs() { StreamProcessor = this }); StreamFinalized?.Invoke(this, new StreamFinalizedArgs() { StreamProcessor = this });
} }
} }
}
} }
#region IDisposable Support #region IDisposable Support

View File

@ -1,22 +1,19 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
namespace BililiveRecorder.FlvProcessor namespace BililiveRecorder.FlvProcessor
{ {
public class FlvTag public class FlvTag : IFlvTag
{ {
public TagType TagType = 0; public TagType TagType { get; set; } = 0;
public int TagSize = 0; public int TagSize { get; set; } = 0;
public int TimeStamp = 0; public int TimeStamp { get; set; } = 0;
public byte[] StreamId = new byte[3]; public byte[] StreamId { get; set; } = new byte[3];
public bool IsVideoKeyframe => _IsVideoKeyframe != -1 ? _IsVideoKeyframe == 1 : 1 == (_IsVideoKeyframe = _ParseIsVideoKeyframe()); public bool IsVideoKeyframe => _IsVideoKeyframe != -1 ? _IsVideoKeyframe == 1 : 1 == (_IsVideoKeyframe = _ParseIsVideoKeyframe());
private int _IsVideoKeyframe = -1; private int _IsVideoKeyframe = -1;
public byte[] Data = null; public byte[] Data { get; set; } = null;
public byte[] ToBytes(bool useDataSize, int offset = 0) public byte[] ToBytes(bool useDataSize, int offset = 0)
{ {
@ -38,9 +35,14 @@ namespace BililiveRecorder.FlvProcessor
private int _ParseIsVideoKeyframe() private int _ParseIsVideoKeyframe()
{ {
if (TagType != TagType.VIDEO) if (TagType != TagType.VIDEO)
{
return 0; return 0;
}
if (Data.Length < 1) if (Data.Length < 1)
{
return -1; return -1;
}
const byte mask = 0b00001111; const byte mask = 0b00001111;
const byte compare = 0b00011111; const byte compare = 0b00011111;

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
namespace BililiveRecorder.FlvProcessor
{
public interface IFlvClipProcessor
{
IFlvMetadata Header { get; }
List<IFlvTag> HTags { get; }
List<IFlvTag> Tags { get; }
Func<string> GetFileName { get; set; }
IFlvClipProcessor Initialize(IFlvMetadata metadata, List<IFlvTag> head, List<IFlvTag> data, uint seconds);
void AddTag(IFlvTag tag);
void FinallizeFile();
event ClipFinalizedEvent ClipFinalized;
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace BililiveRecorder.FlvProcessor
{
public interface IFlvMetadata
{
IDictionary<string, object> Meta { get; set; }
byte[] ToBytes();
}
}

View File

@ -0,0 +1,24 @@
using System;
namespace BililiveRecorder.FlvProcessor
{
public interface IFlvStreamProcessor : IDisposable
{
event TagProcessedEvent TagProcessed;
event StreamFinalizedEvent StreamFinalized;
IFlvMetadata Metadata { get; set; }
Func<string> GetFileName { get; set; }
uint Clip_Past { get; set; }
uint Clip_Future { get; set; }
int LasttimeRemovedTimestamp { get; }
int MaxTimeStamp { get; }
int BaseTimeStamp { get; }
int TagVideoCount { get; }
int TagAudioCount { get; }
void AddBytes(byte[] data);
IFlvClipProcessor Clip();
void FinallizeFile();
}
}

View File

@ -0,0 +1,17 @@
using System.IO;
namespace BililiveRecorder.FlvProcessor
{
public interface IFlvTag
{
TagType TagType { get; set; }
int TagSize { get; set; }
int TimeStamp { get; set; }
byte[] StreamId { get; set; }
bool IsVideoKeyframe { get; }
byte[] Data { get; set; }
byte[] ToBytes(bool useDataSize, int offset = 0);
void WriteTo(Stream stream, int offset = 0);
}
}

View File

@ -46,7 +46,7 @@ namespace BililiveRecorder.WPF
else else
{ {
MessageBox.Show("请使用桌面上或开始菜单里的快捷方式打开", "你的打开方式不正确", MessageBoxButton.OK, MessageBoxImage.Warning); MessageBox.Show("请使用桌面上或开始菜单里的快捷方式打开", "你的打开方式不正确", MessageBoxButton.OK, MessageBoxImage.Warning);
Current.Shutdown(1); // Current.Shutdown(1);
} }
} }

View File

@ -75,6 +75,9 @@
<SignAssembly>false</SignAssembly> <SignAssembly>false</SignAssembly>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Autofac, Version=4.8.1.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<HintPath>..\packages\Autofac.4.8.1\lib\net45\Autofac.dll</HintPath>
</Reference>
<Reference Include="Microsoft.WindowsAPICodePack, Version=1.1.2.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="Microsoft.WindowsAPICodePack, Version=1.1.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\WindowsAPICodePack-Core.1.1.2\lib\Microsoft.WindowsAPICodePack.dll</HintPath> <HintPath>..\packages\WindowsAPICodePack-Core.1.1.2\lib\Microsoft.WindowsAPICodePack.dll</HintPath>
</Reference> </Reference>
@ -192,6 +195,10 @@
<Project>{cb9f2d58-181d-49f7-9560-d35a9b9c1d8c}</Project> <Project>{cb9f2d58-181d-49f7-9560-d35a9b9c1d8c}</Project>
<Name>BililiveRecorder.Core</Name> <Name>BililiveRecorder.Core</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\BililiveRecorder.FlvProcessor\BililiveRecorder.FlvProcessor.csproj">
<Project>{51748048-1949-4218-8ded-94014abe7633}</Project>
<Name>BililiveRecorder.FlvProcessor</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.6.2"> <BootstrapperPackage Include=".NETFramework,Version=v4.6.2">

View File

@ -1,25 +1,16 @@
using BililiveRecorder.Core; using Autofac;
using BililiveRecorder.Core;
using BililiveRecorder.FlvProcessor;
using NLog; using NLog;
using NLog.Config;
using NLog.Targets;
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Deployment.Application; using System.Deployment.Application;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace BililiveRecorder.WPF namespace BililiveRecorder.WPF
@ -34,6 +25,9 @@ namespace BililiveRecorder.WPF
private const int MAX_LOG_ROW = 25; private const int MAX_LOG_ROW = 25;
private IContainer Container { get; set; }
private ILifetimeScope RootScope { get; set; }
public Recorder Recorder { get; set; } public Recorder Recorder { get; set; }
public ObservableCollection<string> Logs { get; set; } = public ObservableCollection<string> Logs { get; set; } =
new ObservableCollection<string>() new ObservableCollection<string>()
@ -48,11 +42,17 @@ namespace BililiveRecorder.WPF
public MainWindow() public MainWindow()
{ {
_AddLog = (message) => Log.Dispatcher.Invoke(() => { Logs.Add(message); while (Logs.Count > MAX_LOG_ROW) Logs.RemoveAt(0); }); var builder = new ContainerBuilder();
builder.RegisterModule<FlvProcessorModule>();
builder.RegisterModule<CoreModule>();
Container = builder.Build();
RootScope = Container.BeginLifetimeScope("recorder_root");
_AddLog = (message) => Log.Dispatcher.Invoke(() => { Logs.Add(message); while (Logs.Count > MAX_LOG_ROW) { Logs.RemoveAt(0); } });
InitializeComponent(); InitializeComponent();
Recorder = new Recorder(); Recorder = RootScope.Resolve<Recorder>();
DataContext = this; DataContext = this;
@ -109,7 +109,10 @@ namespace BililiveRecorder.WPF
return; return;
} }
if (e.Cancelled) if (e.Cancelled)
{
return; return;
}
if (e.UpdateAvailable) if (e.UpdateAvailable)
{ {
if (e.IsUpdateRequired) if (e.IsUpdateRequired)
@ -148,7 +151,7 @@ namespace BililiveRecorder.WPF
{ {
UpdateBar.Dispatcher.Invoke(() => UpdateBar.Dispatcher.Invoke(() =>
{ {
var p = (e.BytesTotal == 0) ? 100d : ((double)e.BytesCompleted / (double)e.BytesTotal) * 100d; var p = (e.BytesTotal == 0) ? 100d : (e.BytesCompleted / (double)e.BytesTotal) * 100d;
UpdateBar.Progress = p; UpdateBar.Progress = p;
UpdateBar.ProgressText = string.Format("{0}KiB / {1}KiB - {2}%", e.BytesCompleted / 1024, e.BytesTotal / 1024, p.ToString("0.##")); UpdateBar.ProgressText = string.Format("{0}KiB / {1}KiB - {2}%", e.BytesCompleted / 1024, e.BytesTotal / 1024, p.ToString("0.##"));
}); });
@ -228,7 +231,9 @@ namespace BililiveRecorder.WPF
if (int.TryParse(r[0], out int roomid) && bool.TryParse(r[1], out bool enabled)) if (int.TryParse(r[0], out int roomid) && bool.TryParse(r[1], out bool enabled))
{ {
if (roomid > 0) if (roomid > 0)
{
Recorder.AddRoom(roomid, enabled); Recorder.AddRoom(roomid, enabled);
}
} }
}); });
@ -250,7 +255,11 @@ namespace BililiveRecorder.WPF
private void Clip_Click(object sender, RoutedEventArgs e) private void Clip_Click(object sender, RoutedEventArgs e)
{ {
var rr = _GetSenderAsRecordedRoom(sender); var rr = _GetSenderAsRecordedRoom(sender);
if (rr == null) return; if (rr == null)
{
return;
}
Task.Run(() => rr.Clip()); Task.Run(() => rr.Clip());
} }
@ -262,7 +271,11 @@ namespace BililiveRecorder.WPF
private void EnableAutoRec(object sender, RoutedEventArgs e) private void EnableAutoRec(object sender, RoutedEventArgs e)
{ {
var rr = _GetSenderAsRecordedRoom(sender); var rr = _GetSenderAsRecordedRoom(sender);
if (rr == null) return; if (rr == null)
{
return;
}
Task.Run(() => rr.Start()); Task.Run(() => rr.Start());
} }
@ -274,7 +287,11 @@ namespace BililiveRecorder.WPF
private void DisableAutoRec(object sender, RoutedEventArgs e) private void DisableAutoRec(object sender, RoutedEventArgs e)
{ {
var rr = _GetSenderAsRecordedRoom(sender); var rr = _GetSenderAsRecordedRoom(sender);
if (rr == null) return; if (rr == null)
{
return;
}
Task.Run(() => rr.Stop()); Task.Run(() => rr.Stop());
} }
@ -286,7 +303,11 @@ namespace BililiveRecorder.WPF
private void TriggerRec(object sender, RoutedEventArgs e) private void TriggerRec(object sender, RoutedEventArgs e)
{ {
var rr = _GetSenderAsRecordedRoom(sender); var rr = _GetSenderAsRecordedRoom(sender);
if (rr == null) return; if (rr == null)
{
return;
}
Task.Run(() => rr.StartRecord()); Task.Run(() => rr.StartRecord());
} }
@ -298,7 +319,11 @@ namespace BililiveRecorder.WPF
private void CutRec(object sender, RoutedEventArgs e) private void CutRec(object sender, RoutedEventArgs e)
{ {
var rr = _GetSenderAsRecordedRoom(sender); var rr = _GetSenderAsRecordedRoom(sender);
if (rr == null) return; if (rr == null)
{
return;
}
Task.Run(() => rr.StopRecord()); Task.Run(() => rr.StopRecord());
} }
@ -310,7 +335,11 @@ namespace BililiveRecorder.WPF
private void RemoveRecRoom(object sender, RoutedEventArgs e) private void RemoveRecRoom(object sender, RoutedEventArgs e)
{ {
var rr = _GetSenderAsRecordedRoom(sender); var rr = _GetSenderAsRecordedRoom(sender);
if (rr == null) return; if (rr == null)
{
return;
}
Recorder.RemoveRoom(rr); Recorder.RemoveRoom(rr);
} }
@ -373,7 +402,7 @@ namespace BililiveRecorder.WPF
} }
} }
private RecordedRoom _GetSenderAsRecordedRoom(object sender) => (sender as Button)?.DataContext as RecordedRoom; private IRecordedRoom _GetSenderAsRecordedRoom(object sender) => (sender as Button)?.DataContext as IRecordedRoom;
} }

View File

@ -21,9 +21,9 @@ namespace BililiveRecorder.WPF
/// </summary> /// </summary>
public partial class SettingsWindow : Window public partial class SettingsWindow : Window
{ {
public Settings Settings { get; set; } = new Settings(); public ISettings Settings { get; set; } = new Settings();
public SettingsWindow(MainWindow mainWindow, Settings settings) public SettingsWindow(MainWindow mainWindow, ISettings settings)
{ {
Owner = mainWindow; Owner = mainWindow;
settings.ApplyTo(Settings); settings.ApplyTo(Settings);

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Autofac" version="4.8.1" targetFramework="net462" />
<package id="NLog" version="4.5.0-rc07" targetFramework="net462" /> <package id="NLog" version="4.5.0-rc07" targetFramework="net462" />
<package id="NLog.Config" version="4.5.0-rc07" targetFramework="net462" /> <package id="NLog.Config" version="4.5.0-rc07" targetFramework="net462" />
<package id="NLog.Schema" version="4.5.0-rc07" targetFramework="net462" /> <package id="NLog.Schema" version="4.5.0-rc07" targetFramework="net462" />