mirror of
https://github.com/BililiveRecorder/BililiveRecorder.git
synced 2024-11-15 19:22:19 +08:00
add webhook
This commit is contained in:
parent
456d851847
commit
f175aac1df
|
@ -5,12 +5,18 @@ root = true
|
|||
|
||||
# all files
|
||||
[*]
|
||||
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
|
||||
[NLog.{Debug,Release}.config]
|
||||
|
||||
indent_size = 2
|
||||
|
||||
[.csproj]
|
||||
|
||||
indent_size = 2
|
||||
|
||||
[*.cs]
|
||||
|
@ -41,8 +47,8 @@ csharp_new_line_before_finally = true
|
|||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
#require members of object intializers to be on separate lines
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
#require braces to be on a new line for anonymous_types, lambdas, properties, accessors, methods, object_collection_array_initializers, control_blocks, and types (also known as "Allman" style)
|
||||
csharp_new_line_before_open_brace = anonymous_types, lambdas, properties, accessors, methods, object_collection_array_initializers, control_blocks, types
|
||||
#require braces to be on a new line for methods and types (also known as "Allman" style)
|
||||
csharp_new_line_before_open_brace = all
|
||||
|
||||
#Formatting - organize using options
|
||||
|
||||
|
@ -80,7 +86,7 @@ csharp_preserve_single_line_statements = true
|
|||
#Style - Code block preferences
|
||||
|
||||
#prefer curly braces even for one line of code
|
||||
csharp_prefer_braces = true:suggestion
|
||||
csharp_prefer_braces = false:suggestion
|
||||
|
||||
#Style - expression bodied member options
|
||||
|
||||
|
@ -90,8 +96,8 @@ csharp_style_expression_bodied_accessors = true:suggestion
|
|||
csharp_style_expression_bodied_constructors = false:suggestion
|
||||
#prefer expression-bodied members for indexers
|
||||
csharp_style_expression_bodied_indexers = true:suggestion
|
||||
#prefer block bodies for methods
|
||||
csharp_style_expression_bodied_methods = false:suggestion
|
||||
#prefer expression-bodied members for methods
|
||||
csharp_style_expression_bodied_methods = true:suggestion
|
||||
#prefer expression-bodied members for properties
|
||||
csharp_style_expression_bodied_properties = true:suggestion
|
||||
|
||||
|
@ -149,11 +155,11 @@ csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
|||
|
||||
#Style - qualification options
|
||||
|
||||
#prefer events not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_event = false:suggestion
|
||||
#prefer fields not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_field = false:suggestion
|
||||
#prefer methods not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_method = false:suggestion
|
||||
#prefer properties not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_property = false:suggestion
|
||||
#prefer events to be prefaced with this. in C# or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_event = true:suggestion
|
||||
#prefer fields to be prefaced with this. in C# or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_field = true:suggestion
|
||||
#prefer methods to be prefaced with this. in C# or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_method = true:suggestion
|
||||
#prefer properties to be prefaced with this. in C# or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_property = true:suggestion
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<Version>0.0.0.0</Version>
|
||||
<Authors>Genteure</Authors>
|
||||
<Company>Genteure</Company>
|
||||
<Copyright>Copyright © 2018 - 2021 Genteure</Copyright>
|
||||
<AssemblyVersion>0.0.0.0</AssemblyVersion>
|
||||
<FileVersion>0.0.0.0</FileVersion>
|
||||
<FileUpgradeFlags>
|
||||
</FileUpgradeFlags>
|
||||
<UpgradeBackupLocation>
|
||||
</UpgradeBackupLocation>
|
||||
<OldToolsVersion>2.0</OldToolsVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
|
64
BililiveRecorder.Core/Callback/BasicWebhook.cs
Normal file
64
BililiveRecorder.Core/Callback/BasicWebhook.cs
Normal file
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BililiveRecorder.Core.Config;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
|
||||
#nullable enable
|
||||
namespace BililiveRecorder.Core.Callback
|
||||
{
|
||||
public class BasicWebhook
|
||||
{
|
||||
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||
private static readonly HttpClient client;
|
||||
|
||||
private readonly ConfigV1 Config;
|
||||
|
||||
static BasicWebhook()
|
||||
{
|
||||
client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("User-Agent", $"BililiveRecorder/{typeof(BasicWebhook).Assembly.GetName().Version}-{BuildInfo.HeadShaShort}");
|
||||
}
|
||||
|
||||
public BasicWebhook(ConfigV1 config)
|
||||
{
|
||||
this.Config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
public async void Send(RecordEndData data)
|
||||
{
|
||||
var urls = this.Config.WebHookUrls;
|
||||
if (string.IsNullOrWhiteSpace(urls)) return;
|
||||
|
||||
var dataStr = JsonConvert.SerializeObject(data, Formatting.None);
|
||||
using var body = new ByteArrayContent(Encoding.UTF8.GetBytes(dataStr));
|
||||
body.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||
|
||||
var tasks = urls
|
||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim())
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Select(x => this.SendImplAsync(x, body));
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task SendImplAsync(string url, HttpContent data)
|
||||
{
|
||||
for (var i = 0; i < 3; i++)
|
||||
try
|
||||
{
|
||||
var result = await client.PostAsync(url, data).ConfigureAwait(false);
|
||||
result.EnsureSuccessStatusCode();
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Warn(ex, "发送 Webhook 到 {url} 失败", url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
BililiveRecorder.Core/Callback/RecordEndData.cs
Normal file
18
BililiveRecorder.Core/Callback/RecordEndData.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
|
||||
#nullable enable
|
||||
namespace BililiveRecorder.Core.Callback
|
||||
{
|
||||
public class RecordEndData
|
||||
{
|
||||
public Guid EventRandomId { get; set; } = Guid.NewGuid();
|
||||
|
||||
public int RoomId { get; set; } = 0;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string RelativePath { get; set; } = string.Empty;
|
||||
public long FileSize { get; set; }
|
||||
public DateTimeOffset StartRecordTime { get; set; }
|
||||
public DateTimeOffset EndRecordTime { get; set; }
|
||||
}
|
||||
}
|
|
@ -148,6 +148,16 @@ namespace BililiveRecorder.Core.Config
|
|||
set => SetField(ref _clip_filename_format, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Webhook 地址 每行一个
|
||||
/// </summary>
|
||||
[JsonProperty("webhook_urls")]
|
||||
public string WebHookUrls
|
||||
{
|
||||
get => _webhook_urls;
|
||||
set => SetField(ref _webhook_urls, value);
|
||||
}
|
||||
|
||||
#region INotifyPropertyChanged
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
protected virtual void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
|
@ -186,5 +196,7 @@ namespace BililiveRecorder.Core.Config
|
|||
private uint _recordDanmakuFlushInterval = 20;
|
||||
|
||||
private string _liveApiHost = "https://api.live.bilibili.com";
|
||||
|
||||
private string _webhook_urls = "";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Net.Sockets;
|
||||
using Autofac;
|
||||
using BililiveRecorder.Core.Callback;
|
||||
using BililiveRecorder.Core.Config;
|
||||
|
||||
namespace BililiveRecorder.Core
|
||||
|
@ -15,6 +16,7 @@ namespace BililiveRecorder.Core
|
|||
{
|
||||
builder.RegisterType<ConfigV1>().AsSelf().InstancePerMatchingLifetimeScope("recorder_root");
|
||||
builder.RegisterType<BililiveAPI>().AsSelf().InstancePerMatchingLifetimeScope("recorder_root");
|
||||
builder.RegisterType<BasicWebhook>().AsSelf().InstancePerMatchingLifetimeScope("recorder_root");
|
||||
builder.RegisterType<TcpClient>().AsSelf().ExternallyOwned();
|
||||
builder.RegisterType<StreamMonitor>().As<IStreamMonitor>().ExternallyOwned();
|
||||
builder.RegisterType<RecordedRoom>().As<IRecordedRoom>().ExternallyOwned();
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using BililiveRecorder.Core.Callback;
|
||||
using BililiveRecorder.FlvProcessor;
|
||||
|
||||
#nullable enable
|
||||
namespace BililiveRecorder.Core
|
||||
{
|
||||
public interface IRecordedRoom : INotifyPropertyChanged, IDisposable
|
||||
|
@ -11,9 +13,12 @@ namespace BililiveRecorder.Core
|
|||
int ShortRoomId { get; }
|
||||
int RoomId { get; }
|
||||
string StreamerName { get; }
|
||||
string Title { get; }
|
||||
|
||||
event EventHandler<RecordEndData>? RecordEnded;
|
||||
|
||||
IStreamMonitor StreamMonitor { get; }
|
||||
IFlvStreamProcessor Processor { get; }
|
||||
IFlvStreamProcessor? Processor { get; }
|
||||
|
||||
bool IsMonitoring { get; }
|
||||
bool IsRecording { get; }
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using BililiveRecorder.Core.Callback;
|
||||
using BililiveRecorder.Core.Config;
|
||||
using BililiveRecorder.FlvProcessor;
|
||||
using NLog;
|
||||
|
@ -56,7 +57,6 @@ namespace BililiveRecorder.Core
|
|||
TriggerPropertyChanged(nameof(StreamerName));
|
||||
}
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
|
@ -82,6 +82,9 @@ namespace BililiveRecorder.Core
|
|||
}
|
||||
}
|
||||
|
||||
private RecordEndData recordEndData;
|
||||
public event EventHandler<RecordEndData> RecordEnded;
|
||||
|
||||
private readonly IBasicDanmakuWriter basicDanmakuWriter;
|
||||
private readonly Func<IFlvStreamProcessor> newIFlvStreamProcessor;
|
||||
private IFlvStreamProcessor _processor;
|
||||
|
@ -191,10 +194,7 @@ namespace BililiveRecorder.Core
|
|||
|
||||
public bool Start()
|
||||
{
|
||||
if (disposedValue)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(RecordedRoom));
|
||||
}
|
||||
if (disposedValue) throw new ObjectDisposedException(nameof(RecordedRoom));
|
||||
|
||||
var r = StreamMonitor.Start();
|
||||
TriggerPropertyChanged(nameof(IsMonitoring));
|
||||
|
@ -203,10 +203,7 @@ namespace BililiveRecorder.Core
|
|||
|
||||
public void Stop()
|
||||
{
|
||||
if (disposedValue)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(RecordedRoom));
|
||||
}
|
||||
if (disposedValue) throw new ObjectDisposedException(nameof(RecordedRoom));
|
||||
|
||||
StreamMonitor.Stop();
|
||||
TriggerPropertyChanged(nameof(IsMonitoring));
|
||||
|
@ -214,41 +211,26 @@ namespace BililiveRecorder.Core
|
|||
|
||||
public void RefreshRoomInfo()
|
||||
{
|
||||
if (disposedValue)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(RecordedRoom));
|
||||
}
|
||||
|
||||
if (disposedValue) throw new ObjectDisposedException(nameof(RecordedRoom));
|
||||
StreamMonitor.FetchRoomInfoAsync();
|
||||
}
|
||||
|
||||
private void StreamMonitor_StreamStarted(object sender, StreamStartedArgs e)
|
||||
{
|
||||
lock (StartupTaskLock)
|
||||
{
|
||||
if (!IsRecording && (StartupTask?.IsCompleted ?? true))
|
||||
{
|
||||
StartupTask = _StartRecordAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void StartRecord()
|
||||
{
|
||||
if (disposedValue)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(RecordedRoom));
|
||||
}
|
||||
|
||||
if (disposedValue) throw new ObjectDisposedException(nameof(RecordedRoom));
|
||||
StreamMonitor.Check(TriggerType.Manual);
|
||||
}
|
||||
|
||||
public void StopRecord()
|
||||
{
|
||||
if (disposedValue)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(RecordedRoom));
|
||||
}
|
||||
if (disposedValue) throw new ObjectDisposedException(nameof(RecordedRoom));
|
||||
|
||||
_retry = false;
|
||||
try
|
||||
|
@ -347,6 +329,16 @@ namespace BililiveRecorder.Core
|
|||
Processor.ClipLengthPast = _config.ClipLengthPast;
|
||||
Processor.CuttingNumber = _config.CuttingNumber;
|
||||
Processor.StreamFinalized += (sender, e) => { basicDanmakuWriter.Disable(); };
|
||||
Processor.FileFinalized += (sender, size) =>
|
||||
{
|
||||
if (recordEndData is null) return;
|
||||
var data = recordEndData;
|
||||
recordEndData = null;
|
||||
|
||||
data.EndRecordTime = DateTimeOffset.Now;
|
||||
data.FileSize = size;
|
||||
RecordEnded?.Invoke(this, data);
|
||||
};
|
||||
Processor.OnMetaData += (sender, e) =>
|
||||
{
|
||||
e.Metadata["BililiveRecorder"] = new Dictionary<string, object>()
|
||||
|
@ -484,40 +476,43 @@ namespace BililiveRecorder.Core
|
|||
}
|
||||
|
||||
// Called by API or GUI
|
||||
public void Clip()
|
||||
{
|
||||
Processor?.Clip();
|
||||
}
|
||||
public void Clip() => Processor?.Clip();
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
public void Shutdown() => Dispose(true);
|
||||
|
||||
private string GetStreamFilePath()
|
||||
private (string fullPath, string relativePath) GetStreamFilePath()
|
||||
{
|
||||
string path = FormatFilename(_config.RecordFilenameFormat);
|
||||
var path = FormatFilename(_config.RecordFilenameFormat);
|
||||
|
||||
// 有点脏的写法,不过凑合吧
|
||||
if (_config.RecordDanmaku)
|
||||
{
|
||||
var xmlpath = Path.ChangeExtension(path, "xml");
|
||||
var xmlpath = Path.ChangeExtension(path.fullPath, "xml");
|
||||
basicDanmakuWriter.EnableWithPath(xmlpath, this);
|
||||
}
|
||||
|
||||
recordEndData = new RecordEndData
|
||||
{
|
||||
RoomId = RoomId,
|
||||
Title = Title,
|
||||
Name = StreamerName,
|
||||
StartRecordTime = DateTimeOffset.Now,
|
||||
RelativePath = path.relativePath,
|
||||
};
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private string GetClipFilePath() => FormatFilename(_config.ClipFilenameFormat);
|
||||
private string GetClipFilePath() => FormatFilename(_config.ClipFilenameFormat).fullPath;
|
||||
|
||||
private string FormatFilename(string formatString)
|
||||
private (string fullPath, string relativePath) FormatFilename(string formatString)
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
string date = now.ToString("yyyyMMdd");
|
||||
string time = now.ToString("HHmmss");
|
||||
string randomStr = random.Next(100, 999).ToString();
|
||||
|
||||
var filename = formatString
|
||||
var relativePath = formatString
|
||||
.Replace(@"{date}", date)
|
||||
.Replace(@"{time}", time)
|
||||
.Replace(@"{random}", randomStr)
|
||||
|
@ -525,26 +520,28 @@ namespace BililiveRecorder.Core
|
|||
.Replace(@"{title}", Title.RemoveInvalidFileName())
|
||||
.Replace(@"{name}", StreamerName.RemoveInvalidFileName());
|
||||
|
||||
if (!filename.EndsWith(".flv", StringComparison.OrdinalIgnoreCase))
|
||||
filename += ".flv";
|
||||
if (!relativePath.EndsWith(".flv", StringComparison.OrdinalIgnoreCase))
|
||||
relativePath += ".flv";
|
||||
|
||||
filename = filename.RemoveInvalidFileName(ignore_slash: true);
|
||||
filename = Path.Combine(_config.WorkDirectory, filename);
|
||||
filename = Path.GetFullPath(filename);
|
||||
relativePath = relativePath.RemoveInvalidFileName(ignore_slash: true);
|
||||
var fullPath = Path.Combine(_config.WorkDirectory, relativePath);
|
||||
fullPath = Path.GetFullPath(fullPath);
|
||||
|
||||
if (!CheckPath(_config.WorkDirectory, Path.GetDirectoryName(filename)))
|
||||
if (!CheckPath(_config.WorkDirectory, Path.GetDirectoryName(fullPath)))
|
||||
{
|
||||
logger.Log(RoomId, LogLevel.Warn, "录制文件位置超出允许范围,请检查设置。将写入到默认路径。");
|
||||
filename = Path.Combine(_config.WorkDirectory, RoomId.ToString(), $"{RoomId}-{date}-{time}-{randomStr}.flv");
|
||||
relativePath = Path.Combine(RoomId.ToString(), $"{RoomId}-{date}-{time}-{randomStr}.flv");
|
||||
fullPath = Path.Combine(_config.WorkDirectory, relativePath);
|
||||
}
|
||||
|
||||
if (new FileInfo(filename).Exists)
|
||||
if (new FileInfo(relativePath).Exists)
|
||||
{
|
||||
logger.Log(RoomId, LogLevel.Warn, "录制文件名冲突,请检查设置。将写入到默认路径。");
|
||||
filename = Path.Combine(_config.WorkDirectory, RoomId.ToString(), $"{RoomId}-{date}-{time}-{randomStr}.flv");
|
||||
relativePath = Path.Combine(RoomId.ToString(), $"{RoomId}-{date}-{time}-{randomStr}.flv");
|
||||
fullPath = Path.Combine(_config.WorkDirectory, relativePath);
|
||||
}
|
||||
|
||||
return filename;
|
||||
return (fullPath, relativePath);
|
||||
}
|
||||
|
||||
private static bool CheckPath(string parent, string child)
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.ComponentModel;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BililiveRecorder.Core.Callback;
|
||||
using BililiveRecorder.Core.Config;
|
||||
using NLog;
|
||||
|
||||
|
@ -26,14 +27,17 @@ namespace BililiveRecorder.Core
|
|||
|
||||
public ConfigV1 Config { get; }
|
||||
|
||||
private BasicWebhook Webhook { get; }
|
||||
|
||||
public int Count => Rooms.Count;
|
||||
public bool IsReadOnly => true;
|
||||
public IRecordedRoom this[int index] => Rooms[index];
|
||||
|
||||
public Recorder(ConfigV1 config, Func<int, IRecordedRoom> iRecordedRoom)
|
||||
public Recorder(ConfigV1 config, BasicWebhook webhook, Func<int, IRecordedRoom> iRecordedRoom)
|
||||
{
|
||||
newIRecordedRoom = iRecordedRoom;
|
||||
Config = config;
|
||||
newIRecordedRoom = iRecordedRoom ?? throw new ArgumentNullException(nameof(iRecordedRoom));
|
||||
Config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
Webhook = webhook ?? throw new ArgumentNullException(nameof(webhook));
|
||||
|
||||
tokenSource = new CancellationTokenSource();
|
||||
Repeat.Interval(TimeSpan.FromSeconds(3), DownloadWatchdog, tokenSource.Token);
|
||||
|
@ -96,6 +100,7 @@ namespace BililiveRecorder.Core
|
|||
}
|
||||
|
||||
logger.Debug("AddRoom 添加了 {roomid} 直播间 ", rr.RoomId);
|
||||
rr.RecordEnded += this.RecordedRoom_RecordEnded;
|
||||
Rooms.Add(rr);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -113,6 +118,7 @@ namespace BililiveRecorder.Core
|
|||
if (rr is null) return;
|
||||
if (!_valid) { throw new InvalidOperationException("Not Initialized"); }
|
||||
rr.Shutdown();
|
||||
rr.RecordEnded -= RecordedRoom_RecordEnded;
|
||||
logger.Debug("RemoveRoom 移除了直播间 {roomid}", rr.RoomId);
|
||||
Rooms.Remove(rr);
|
||||
}
|
||||
|
@ -133,6 +139,8 @@ namespace BililiveRecorder.Core
|
|||
Rooms.Clear();
|
||||
}
|
||||
|
||||
private void RecordedRoom_RecordEnded(object sender, RecordEndData e) => Webhook.Send(e);
|
||||
|
||||
public void SaveConfigToFile()
|
||||
{
|
||||
Config.RoomList = new List<RoomV1>();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<Version>0.0.0.0</Version>
|
||||
<Authors>Genteure</Authors>
|
||||
<Company>Genteure</Company>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
|
||||
namespace BililiveRecorder.FlvProcessor
|
||||
{
|
||||
|
@ -50,16 +50,16 @@ namespace BililiveRecorder.FlvProcessor
|
|||
|
||||
public int TotalMaxTimestamp { get; private set; } = 0;
|
||||
public int CurrentMaxTimestamp { get => TotalMaxTimestamp - _writeTimeStamp; }
|
||||
public DateTime StartDateTime { get; private set; } = DateTime.Now;
|
||||
|
||||
private readonly Func<IFlvClipProcessor> funcFlvClipProcessor;
|
||||
private readonly Func<byte[], IFlvMetadata> funcFlvMetadata;
|
||||
private readonly Func<IFlvTag> funcFlvTag;
|
||||
|
||||
private Func<string> GetStreamFileName;
|
||||
private Func<(string fullPath, string relativePath)> GetStreamFileName;
|
||||
private Func<string> GetClipFileName;
|
||||
|
||||
public event TagProcessedEvent TagProcessed;
|
||||
public event EventHandler<long> FileFinalized;
|
||||
public event StreamFinalizedEvent StreamFinalized;
|
||||
public event FlvMetadataEvent OnMetaData;
|
||||
|
||||
|
@ -81,7 +81,7 @@ namespace BililiveRecorder.FlvProcessor
|
|||
|
||||
}
|
||||
|
||||
public IFlvStreamProcessor Initialize(Func<string> getStreamFileName, Func<string> getClipFileName, EnabledFeature enabledFeature, AutoCuttingMode autoCuttingMode)
|
||||
public IFlvStreamProcessor Initialize(Func<(string fullPath, string relativePath)> getStreamFileName, Func<string> getClipFileName, EnabledFeature enabledFeature, AutoCuttingMode autoCuttingMode)
|
||||
{
|
||||
GetStreamFileName = getStreamFileName;
|
||||
GetClipFileName = getClipFileName;
|
||||
|
@ -93,10 +93,10 @@ namespace BililiveRecorder.FlvProcessor
|
|||
|
||||
private void OpenNewRecordFile()
|
||||
{
|
||||
string path = GetStreamFileName();
|
||||
logger.Debug("打开新录制文件: " + path);
|
||||
try { Directory.CreateDirectory(Path.GetDirectoryName(path)); } catch (Exception) { }
|
||||
_targetFile = new FileStream(path, FileMode.CreateNew, FileAccess.ReadWrite);
|
||||
var (fullPath, relativePath) = GetStreamFileName();
|
||||
logger.Debug("打开新录制文件: " + fullPath);
|
||||
try { Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); } catch (Exception) { }
|
||||
_targetFile = new FileStream(fullPath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read | FileShare.Delete);
|
||||
|
||||
if (_headerParsed)
|
||||
{
|
||||
|
@ -309,7 +309,6 @@ namespace BililiveRecorder.FlvProcessor
|
|||
{
|
||||
_baseTimeStamp = tag.TimeStamp;
|
||||
_hasOffset = true;
|
||||
StartDateTime = DateTime.Now;
|
||||
logger.Debug("重设时间戳 {0} 毫秒", _baseTimeStamp);
|
||||
}
|
||||
}
|
||||
|
@ -325,7 +324,6 @@ namespace BililiveRecorder.FlvProcessor
|
|||
{
|
||||
_baseTimeStamp = tag.TimeStamp;
|
||||
_hasOffset = true;
|
||||
StartDateTime = DateTime.Now;
|
||||
logger.Debug("重设时间戳 {0} 毫秒", _baseTimeStamp);
|
||||
}
|
||||
}
|
||||
|
@ -374,6 +372,7 @@ namespace BililiveRecorder.FlvProcessor
|
|||
{
|
||||
try
|
||||
{
|
||||
var fileSize = _targetFile?.Length ?? -1;
|
||||
logger.Debug("正在关闭当前录制文件: " + _targetFile?.Name);
|
||||
Metadata["duration"] = CurrentMaxTimestamp / 1000.0;
|
||||
Metadata["lasttimestamp"] = (double)CurrentMaxTimestamp;
|
||||
|
@ -383,6 +382,9 @@ namespace BililiveRecorder.FlvProcessor
|
|||
// 11 for 1st tag header
|
||||
_targetFile?.Seek(13 + 11, SeekOrigin.Begin);
|
||||
_targetFile?.Write(metadata, 0, metadata.Length);
|
||||
|
||||
if (fileSize > 0)
|
||||
FileFinalized?.Invoke(this, fileSize);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
#nullable enable
|
||||
namespace BililiveRecorder.FlvProcessor
|
||||
{
|
||||
public interface IFlvStreamProcessor : IDisposable
|
||||
{
|
||||
event TagProcessedEvent TagProcessed;
|
||||
event EventHandler<long> FileFinalized;
|
||||
event StreamFinalizedEvent StreamFinalized;
|
||||
event FlvMetadataEvent OnMetaData;
|
||||
|
||||
int TotalMaxTimestamp { get; }
|
||||
int CurrentMaxTimestamp { get; }
|
||||
DateTime StartDateTime { get; }
|
||||
|
||||
IFlvMetadata Metadata { get; set; }
|
||||
ObservableCollection<IFlvClipProcessor> Clips { get; }
|
||||
|
@ -19,7 +20,7 @@ namespace BililiveRecorder.FlvProcessor
|
|||
uint ClipLengthFuture { get; set; }
|
||||
uint CuttingNumber { get; set; }
|
||||
|
||||
IFlvStreamProcessor Initialize(Func<string> getStreamFileName, Func<string> getClipFileName, EnabledFeature enabledFeature, AutoCuttingMode autoCuttingMode);
|
||||
IFlvStreamProcessor Initialize(Func<(string fullPath, string relativePath)> getStreamFileName, Func<string> getClipFileName, EnabledFeature enabledFeature, AutoCuttingMode autoCuttingMode);
|
||||
IFlvClipProcessor Clip();
|
||||
void AddBytes(byte[] data);
|
||||
void FinallizeFile();
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using BililiveRecorder.Core;
|
||||
using BililiveRecorder.Core.Callback;
|
||||
using BililiveRecorder.FlvProcessor;
|
||||
|
||||
#nullable enable
|
||||
namespace BililiveRecorder.WPF.MockData
|
||||
{
|
||||
#if DEBUG
|
||||
|
@ -28,9 +30,11 @@ namespace BililiveRecorder.WPF.MockData
|
|||
|
||||
public string StreamerName { get; set; }
|
||||
|
||||
public IStreamMonitor StreamMonitor { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
public IFlvStreamProcessor Processor { get; set; }
|
||||
public IStreamMonitor StreamMonitor { get; set; } = null!;
|
||||
|
||||
public IFlvStreamProcessor? Processor { get; set; }
|
||||
|
||||
public bool IsMonitoring { get; set; }
|
||||
|
||||
|
@ -48,7 +52,9 @@ namespace BililiveRecorder.WPF.MockData
|
|||
|
||||
public Guid Guid { get; } = Guid.NewGuid();
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public event EventHandler<RecordEndData>? RecordEnded;
|
||||
|
||||
public void Clip()
|
||||
{
|
||||
|
|
|
@ -113,6 +113,13 @@
|
|||
Visibility="{Binding ElementName=EnabledFeatureRecordOnlyRadioButton,Path=IsChecked,Converter={StaticResource InvertBooleanToVisibilityCollapsedConverter}}"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
<GroupBox Header="Webhook">
|
||||
<StackPanel MaxWidth="400" HorizontalAlignment="Left">
|
||||
<TextBlock Text="Webhook 地址,一行一个"/>
|
||||
<TextBox AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible"
|
||||
Text="{Binding WebHookUrls,UpdateSourceTrigger=PropertyChanged,Delay=1000}" ui:TextBoxHelper.IsDeleteButtonVisible="False"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</ui:SimpleStackPanel>
|
||||
</ScrollViewer>
|
||||
</ui:Page>
|
||||
|
|
Loading…
Reference in New Issue
Block a user