mirror of
https://github.com/BililiveRecorder/BililiveRecorder.git
synced 2024-11-16 11:42:22 +08:00
commit
b3fea61c30
|
@ -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>
|
||||||
|
|
||||||
|
|
24
BililiveRecorder.Core/CoreModule.cs
Normal file
24
BililiveRecorder.Core/CoreModule.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
18
BililiveRecorder.Core/IDanmakuReceiver.cs
Normal file
18
BililiveRecorder.Core/IDanmakuReceiver.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
12
BililiveRecorder.Core/IRecordInfo.cs
Normal file
12
BililiveRecorder.Core/IRecordInfo.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
32
BililiveRecorder.Core/IRecordedRoom.cs
Normal file
32
BililiveRecorder.Core/IRecordedRoom.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
12
BililiveRecorder.Core/ISettings.cs
Normal file
12
BililiveRecorder.Core/ISettings.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
13
BililiveRecorder.Core/IStreamMonitor.cs
Normal file
13
BililiveRecorder.Core/IStreamMonitor.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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(() =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -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())
|
||||||
{
|
{
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
20
BililiveRecorder.FlvProcessor/FlvProcessorModule.cs
Normal file
20
BililiveRecorder.FlvProcessor/FlvProcessorModule.cs
Normal 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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
19
BililiveRecorder.FlvProcessor/IFlvClipProcessor.cs
Normal file
19
BililiveRecorder.FlvProcessor/IFlvClipProcessor.cs
Normal 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
10
BililiveRecorder.FlvProcessor/IFlvMetadata.cs
Normal file
10
BililiveRecorder.FlvProcessor/IFlvMetadata.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.FlvProcessor
|
||||||
|
{
|
||||||
|
public interface IFlvMetadata
|
||||||
|
{
|
||||||
|
IDictionary<string, object> Meta { get; set; }
|
||||||
|
byte[] ToBytes();
|
||||||
|
}
|
||||||
|
}
|
24
BililiveRecorder.FlvProcessor/IFlvStreamProcessor.cs
Normal file
24
BililiveRecorder.FlvProcessor/IFlvStreamProcessor.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
17
BililiveRecorder.FlvProcessor/IFlvTag.cs
Normal file
17
BililiveRecorder.FlvProcessor/IFlvTag.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
Loading…
Reference in New Issue
Block a user