BililiveRecorder/BililiveRecorder.Core/RecordedRoom.cs

667 lines
26 KiB
C#
Raw Normal View History

2018-03-21 20:56:56 +08:00
using System;
2019-01-17 00:28:09 +08:00
using System.Collections.Generic;
2018-03-12 18:57:20 +08:00
using System.ComponentModel;
2018-03-18 18:55:28 +08:00
using System.IO;
2018-03-13 13:21:01 +08:00
using System.Linq;
2018-03-13 14:23:53 +08:00
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
2018-10-31 06:22:38 +08:00
using System.Threading;
using System.Threading.Tasks;
2021-01-01 14:46:27 +08:00
using BililiveRecorder.Core.Callback;
using BililiveRecorder.Core.Config.V2;
using BililiveRecorder.FlvProcessor;
using NLog;
2018-03-12 18:57:20 +08:00
namespace BililiveRecorder.Core
{
2018-10-24 14:33:05 +08:00
public class RecordedRoom : IRecordedRoom
2018-03-12 18:57:20 +08:00
{
2018-03-21 20:56:56 +08:00
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
private static readonly Random random = new Random();
2020-08-14 17:01:04 +08:00
private static readonly Version VERSION_1_0 = new Version(1, 0);
2018-03-15 21:55:01 +08:00
2021-01-08 18:54:50 +08:00
#nullable enable
2021-01-01 14:46:27 +08:00
private int _shortRoomid;
2018-10-31 06:22:38 +08:00
private string _streamerName;
private string _title;
2021-01-08 18:54:50 +08:00
private string _parentAreaName = string.Empty;
private string _areaName = string.Empty;
private bool _isStreaming;
2018-10-31 06:22:38 +08:00
2019-08-22 01:26:18 +08:00
public int ShortRoomId
2018-10-31 06:22:38 +08:00
{
2021-01-01 14:46:27 +08:00
get => this._shortRoomid;
2018-10-31 06:22:38 +08:00
private set
{
2021-01-01 14:46:27 +08:00
if (value == this._shortRoomid) { return; }
this._shortRoomid = value;
this.TriggerPropertyChanged(nameof(this.ShortRoomId));
2018-10-31 06:22:38 +08:00
}
}
2019-08-22 01:26:18 +08:00
public int RoomId
2018-10-31 06:22:38 +08:00
{
2021-01-01 14:46:27 +08:00
get => this.RoomConfig.RoomId;
2018-10-31 06:22:38 +08:00
private set
{
2021-01-01 14:46:27 +08:00
if (value == this.RoomConfig.RoomId) { return; }
this.RoomConfig.RoomId = value;
this.TriggerPropertyChanged(nameof(this.RoomId));
2018-10-31 06:22:38 +08:00
}
}
public string StreamerName
{
2021-01-01 14:46:27 +08:00
get => this._streamerName;
2018-10-31 06:22:38 +08:00
private set
{
2021-01-01 14:46:27 +08:00
if (value == this._streamerName) { return; }
this._streamerName = value;
this.TriggerPropertyChanged(nameof(this.StreamerName));
2018-10-31 06:22:38 +08:00
}
}
public string Title
{
2021-01-01 14:46:27 +08:00
get => this._title;
private set
{
2021-01-01 14:46:27 +08:00
if (value == this._title) { return; }
this._title = value;
this.TriggerPropertyChanged(nameof(this.Title));
}
}
2021-01-08 18:54:50 +08:00
public string ParentAreaName
{
get => this._parentAreaName;
private set
{
if (value == this._parentAreaName) { return; }
this._parentAreaName = value;
this.TriggerPropertyChanged(nameof(this.ParentAreaName));
}
}
public string AreaName
{
get => this._areaName;
private set
{
if (value == this._areaName) { return; }
this._areaName = value;
this.TriggerPropertyChanged(nameof(this.AreaName));
}
}
2021-01-01 14:46:27 +08:00
public bool IsMonitoring => this.StreamMonitor.IsMonitoring;
public bool IsRecording => !(this.StreamDownloadTask?.IsCompleted ?? true);
public bool IsDanmakuConnected => this.StreamMonitor.IsDanmakuConnected;
public bool IsStreaming
{
2021-01-01 14:46:27 +08:00
get => this._isStreaming;
private set
{
2021-01-01 14:46:27 +08:00
if (value == this._isStreaming) { return; }
this._isStreaming = value;
this.TriggerPropertyChanged(nameof(this.IsStreaming));
}
}
2018-03-12 18:57:20 +08:00
2021-01-01 14:46:27 +08:00
public RoomConfig RoomConfig { get; }
2021-01-08 18:54:50 +08:00
#nullable restore
2020-12-20 20:56:40 +08:00
private RecordEndData recordEndData;
public event EventHandler<RecordEndData> RecordEnded;
2020-11-23 17:35:42 +08:00
private readonly IBasicDanmakuWriter basicDanmakuWriter;
2018-10-31 06:22:38 +08:00
private readonly Func<IFlvStreamProcessor> newIFlvStreamProcessor;
2018-11-03 07:45:56 +08:00
private IFlvStreamProcessor _processor;
2018-10-31 06:23:01 +08:00
public IFlvStreamProcessor Processor
{
2021-01-01 14:46:27 +08:00
get => this._processor;
2018-10-31 06:23:01 +08:00
private set
{
2021-01-01 14:46:27 +08:00
if (value == this._processor) { return; }
this._processor = value;
this.TriggerPropertyChanged(nameof(this.Processor));
2018-10-31 06:23:01 +08:00
}
}
2018-03-24 09:48:06 +08:00
2020-11-27 18:51:02 +08:00
private BililiveAPI BililiveAPI { get; }
2018-11-03 07:45:56 +08:00
public IStreamMonitor StreamMonitor { get; }
2018-10-31 06:22:38 +08:00
2018-12-17 21:24:52 +08:00
private bool _retry = true;
private HttpResponseMessage _response;
2018-11-03 07:45:56 +08:00
private Stream _stream;
2018-10-31 06:23:01 +08:00
private Task StartupTask = null;
private readonly object StartupTaskLock = new object();
2018-10-31 06:23:01 +08:00
public Task StreamDownloadTask = null;
public CancellationTokenSource cancellationTokenSource = null;
2018-03-13 13:21:01 +08:00
2018-11-03 07:45:56 +08:00
private double _DownloadSpeedPersentage = 0;
2019-11-24 09:08:29 +08:00
private double _DownloadSpeedMegaBitps = 0;
2018-11-03 07:45:56 +08:00
private long _lastUpdateSize = 0;
private int _lastUpdateTimestamp = 0;
public DateTime LastUpdateDateTime { get; private set; } = DateTime.Now;
public double DownloadSpeedPersentage
{
2021-01-01 14:46:27 +08:00
get { return this._DownloadSpeedPersentage; }
private set { if (value != this._DownloadSpeedPersentage) { this._DownloadSpeedPersentage = value; this.TriggerPropertyChanged(nameof(this.DownloadSpeedPersentage)); } }
2018-11-03 07:45:56 +08:00
}
2019-11-24 09:08:29 +08:00
public double DownloadSpeedMegaBitps
2018-03-26 06:14:01 +08:00
{
2021-01-01 14:46:27 +08:00
get { return this._DownloadSpeedMegaBitps; }
private set { if (value != this._DownloadSpeedMegaBitps) { this._DownloadSpeedMegaBitps = value; this.TriggerPropertyChanged(nameof(this.DownloadSpeedMegaBitps)); } }
2018-03-26 06:14:01 +08:00
}
2020-12-05 18:30:04 +08:00
public Guid Guid { get; } = Guid.NewGuid();
2021-01-01 14:46:27 +08:00
// TODO: 重构 DI
public RecordedRoom(Func<RoomConfig, IBasicDanmakuWriter> newBasicDanmakuWriter,
Func<RoomConfig, IStreamMonitor> newIStreamMonitor,
2018-10-31 06:22:38 +08:00
Func<IFlvStreamProcessor> newIFlvStreamProcessor,
2020-11-27 18:51:02 +08:00
BililiveAPI bililiveAPI,
2021-01-01 14:46:27 +08:00
RoomConfig roomConfig)
2018-03-13 13:21:01 +08:00
{
2021-01-01 14:46:27 +08:00
this.RoomConfig = roomConfig;
this.StreamerName = "获取中...";
this.BililiveAPI = bililiveAPI;
2018-10-25 19:20:23 +08:00
this.newIFlvStreamProcessor = newIFlvStreamProcessor;
2021-01-01 14:46:27 +08:00
this.basicDanmakuWriter = newBasicDanmakuWriter(this.RoomConfig);
2018-03-20 00:12:32 +08:00
2021-01-01 14:46:27 +08:00
this.StreamMonitor = newIStreamMonitor(this.RoomConfig);
this.StreamMonitor.RoomInfoUpdated += this.StreamMonitor_RoomInfoUpdated;
this.StreamMonitor.StreamStarted += this.StreamMonitor_StreamStarted;
this.StreamMonitor.ReceivedDanmaku += this.StreamMonitor_ReceivedDanmaku;
this.StreamMonitor.PropertyChanged += this.StreamMonitor_PropertyChanged;
2020-11-23 17:35:42 +08:00
2021-01-01 14:46:27 +08:00
this.PropertyChanged += this.RecordedRoom_PropertyChanged;
2018-03-15 21:55:01 +08:00
2021-01-01 14:46:27 +08:00
this.StreamMonitor.FetchRoomInfoAsync();
2021-01-01 14:46:27 +08:00
if (this.RoomConfig.AutoRecord)
this.Start();
}
private void RecordedRoom_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(this.IsMonitoring):
this.RoomConfig.AutoRecord = this.IsMonitoring;
break;
default:
break;
}
}
2018-03-21 20:56:56 +08:00
2020-12-15 19:38:35 +08:00
private void StreamMonitor_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(IStreamMonitor.IsDanmakuConnected):
2021-01-01 14:46:27 +08:00
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.IsDanmakuConnected)));
2020-12-15 19:38:35 +08:00
break;
default:
break;
}
}
2020-11-23 17:35:42 +08:00
private void StreamMonitor_ReceivedDanmaku(object sender, ReceivedDanmakuArgs e)
{
switch (e.Danmaku.MsgType)
{
case MsgTypeEnum.LiveStart:
2021-01-01 14:46:27 +08:00
this.IsStreaming = true;
break;
case MsgTypeEnum.LiveEnd:
2021-01-01 14:46:27 +08:00
this.IsStreaming = false;
break;
2021-01-08 18:54:50 +08:00
case MsgTypeEnum.RoomChange:
this.Title = e.Danmaku.Title ?? string.Empty;
this.ParentAreaName = e.Danmaku.ParentAreaName ?? string.Empty;
this.AreaName = e.Danmaku.AreaName ?? string.Empty;
break;
default:
break;
}
2021-01-01 14:46:27 +08:00
this.basicDanmakuWriter.Write(e.Danmaku);
2020-11-23 17:35:42 +08:00
}
private void StreamMonitor_RoomInfoUpdated(object sender, RoomInfoUpdatedArgs e)
{
2021-01-01 14:46:27 +08:00
// TODO: StreamMonitor 里的 RoomInfoUpdated Handler 也会设置一次 RoomId
// 暂时保持不变,此处的 RoomId 需要触发 PropertyChanged 事件
this.RoomId = e.RoomInfo.RoomId;
this.ShortRoomId = e.RoomInfo.ShortRoomId;
2021-01-08 18:54:50 +08:00
this.IsStreaming = e.RoomInfo.IsStreaming;
2021-01-01 14:46:27 +08:00
this.StreamerName = e.RoomInfo.UserName;
this.Title = e.RoomInfo.Title;
2021-01-08 18:54:50 +08:00
this.ParentAreaName = e.RoomInfo.ParentAreaName;
this.AreaName = e.RoomInfo.AreaName;
2018-03-21 20:56:56 +08:00
}
public bool Start()
{
2021-01-01 14:46:27 +08:00
// TODO: 重构: 删除 Start() Stop() 通过 RoomConfig.AutoRecord 控制监控状态和逻辑
if (this.disposedValue) throw new ObjectDisposedException(nameof(RecordedRoom));
2021-01-01 14:46:27 +08:00
var r = this.StreamMonitor.Start();
this.TriggerPropertyChanged(nameof(this.IsMonitoring));
2018-03-21 20:56:56 +08:00
return r;
}
public void Stop()
{
2021-01-01 14:46:27 +08:00
// TODO: 见 Start()
if (this.disposedValue) throw new ObjectDisposedException(nameof(RecordedRoom));
2021-01-01 14:46:27 +08:00
this.StreamMonitor.Stop();
this.TriggerPropertyChanged(nameof(this.IsMonitoring));
2018-03-13 14:23:53 +08:00
}
public void RefreshRoomInfo()
{
2021-01-01 14:46:27 +08:00
if (this.disposedValue) throw new ObjectDisposedException(nameof(RecordedRoom));
this.StreamMonitor.FetchRoomInfoAsync();
}
private void StreamMonitor_StreamStarted(object sender, StreamStartedArgs e)
2018-03-20 00:12:32 +08:00
{
2021-01-01 14:46:27 +08:00
lock (this.StartupTaskLock)
if (!this.IsRecording && (this.StartupTask?.IsCompleted ?? true))
this.StartupTask = this._StartRecordAsync();
2018-03-13 14:23:53 +08:00
}
2018-03-15 21:55:01 +08:00
public void StartRecord()
2018-03-13 14:23:53 +08:00
{
2021-01-01 14:46:27 +08:00
if (this.disposedValue) throw new ObjectDisposedException(nameof(RecordedRoom));
this.StreamMonitor.Check(TriggerType.Manual);
2018-03-13 13:21:01 +08:00
}
2018-03-23 06:57:22 +08:00
public void StopRecord()
{
2021-01-01 14:46:27 +08:00
if (this.disposedValue) throw new ObjectDisposedException(nameof(RecordedRoom));
2021-01-01 14:46:27 +08:00
this._retry = false;
try
{
2021-01-01 14:46:27 +08:00
if (this.cancellationTokenSource != null)
{
2021-01-01 14:46:27 +08:00
this.cancellationTokenSource.Cancel();
if (!(this.StreamDownloadTask?.Wait(TimeSpan.FromSeconds(2)) ?? true))
{
2021-01-01 14:46:27 +08:00
logger.Log(this.RoomId, LogLevel.Warn, "停止录制超时,尝试强制关闭连接,请检查网络连接是否稳定");
2021-01-01 14:46:27 +08:00
this._stream?.Close();
this._stream?.Dispose();
this._response?.Dispose();
this.StreamDownloadTask?.Wait();
}
}
}
catch (Exception ex)
2018-03-23 06:57:22 +08:00
{
2021-01-01 14:46:27 +08:00
logger.Log(this.RoomId, LogLevel.Warn, "在尝试停止录制时发生错误,请检查网络连接是否稳定", ex);
2018-03-23 06:57:22 +08:00
}
2018-12-17 21:24:52 +08:00
finally
{
2021-01-01 14:46:27 +08:00
this._retry = true;
2018-12-17 21:24:52 +08:00
}
2018-03-23 06:57:22 +08:00
}
2018-12-17 21:24:52 +08:00
private async Task _StartRecordAsync()
2018-03-15 21:55:01 +08:00
{
2021-01-01 14:46:27 +08:00
if (this.IsRecording)
2018-03-15 21:55:01 +08:00
{
// TODO: 这里逻辑可能有问题StartupTask 会变成当前这个已经结束的
2021-01-01 14:46:27 +08:00
logger.Log(this.RoomId, LogLevel.Warn, "已经在录制中了");
2018-03-21 20:56:56 +08:00
return;
2018-03-15 21:55:01 +08:00
}
2021-01-01 14:46:27 +08:00
this.cancellationTokenSource = new CancellationTokenSource();
var token = this.cancellationTokenSource.Token;
2018-03-21 20:56:56 +08:00
try
2018-03-15 21:55:01 +08:00
{
2021-01-01 14:46:27 +08:00
var flv_path = await this.BililiveAPI.GetPlayUrlAsync(this.RoomId);
if (string.IsNullOrWhiteSpace(flv_path))
{
2021-01-01 14:46:27 +08:00
if (this._retry)
{
2021-01-01 14:46:27 +08:00
this.StreamMonitor.Check(TriggerType.HttpApiRecheck, (int)this.RoomConfig.TimingStreamRetry);
}
return;
}
2019-10-31 21:04:08 +08:00
unwrap_redir:
using (var client = new HttpClient(new HttpClientHandler
{
2019-10-31 21:04:08 +08:00
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
}))
{
2021-01-01 14:46:27 +08:00
client.Timeout = TimeSpan.FromMilliseconds(this.RoomConfig.TimingStreamConnect);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
client.DefaultRequestHeaders.UserAgent.Clear();
client.DefaultRequestHeaders.UserAgent.ParseAdd(Utils.UserAgent);
client.DefaultRequestHeaders.Referrer = new Uri("https://live.bilibili.com");
client.DefaultRequestHeaders.Add("Origin", "https://live.bilibili.com");
2019-10-31 21:04:08 +08:00
2021-01-01 14:46:27 +08:00
logger.Log(this.RoomId, LogLevel.Info, "连接直播服务器 " + new Uri(flv_path).Host);
logger.Log(this.RoomId, LogLevel.Debug, "直播流地址: " + flv_path);
2021-01-01 14:46:27 +08:00
this._response = await client.GetAsync(flv_path, HttpCompletionOption.ResponseHeadersRead);
}
2018-03-18 18:55:28 +08:00
2021-01-01 14:46:27 +08:00
if (this._response.StatusCode == HttpStatusCode.Redirect || this._response.StatusCode == HttpStatusCode.Moved)
2019-10-31 21:04:08 +08:00
{
// workaround for missing Referrer
2021-01-01 14:46:27 +08:00
flv_path = this._response.Headers.Location.OriginalString;
this._response.Dispose();
2019-10-31 21:04:08 +08:00
goto unwrap_redir;
}
2021-01-01 14:46:27 +08:00
else if (this._response.StatusCode != HttpStatusCode.OK)
2018-03-18 18:55:28 +08:00
{
2021-01-01 14:46:27 +08:00
logger.Log(this.RoomId, LogLevel.Info, string.Format("尝试下载直播流时服务器返回了 ({0}){1}", this._response.StatusCode, this._response.ReasonPhrase));
2021-01-01 14:46:27 +08:00
this.StreamMonitor.Check(TriggerType.HttpApiRecheck, (int)this.RoomConfig.TimingStreamRetry);
2018-12-18 00:16:24 +08:00
_CleanupFlvRequest();
2018-03-21 20:56:56 +08:00
return;
}
else
{
2021-01-01 14:46:27 +08:00
this.Processor = this.newIFlvStreamProcessor().Initialize(this.GetStreamFilePath, this.GetClipFilePath, this.RoomConfig.EnabledFeature, this.RoomConfig.CuttingMode);
this.Processor.ClipLengthFuture = this.RoomConfig.ClipLengthFuture;
this.Processor.ClipLengthPast = this.RoomConfig.ClipLengthPast;
this.Processor.CuttingNumber = this.RoomConfig.CuttingNumber;
this.Processor.StreamFinalized += (sender, e) => { this.basicDanmakuWriter.Disable(); };
this.Processor.FileFinalized += (sender, size) =>
2020-12-20 20:56:40 +08:00
{
2021-01-01 14:46:27 +08:00
if (this.recordEndData is null) return;
var data = this.recordEndData;
this.recordEndData = null;
2020-12-20 20:56:40 +08:00
data.EndRecordTime = DateTimeOffset.Now;
data.FileSize = size;
RecordEnded?.Invoke(this, data);
};
2021-01-01 14:46:27 +08:00
this.Processor.OnMetaData += (sender, e) =>
2019-01-17 00:28:09 +08:00
{
e.Metadata["BililiveRecorder"] = new Dictionary<string, object>()
{
{
"starttime",
DateTime.UtcNow
},
{
"version",
BuildInfo.Version + " " + BuildInfo.HeadShaShort
2019-01-17 00:28:09 +08:00
},
{
"roomid",
2021-01-01 14:46:27 +08:00
this.RoomId.ToString()
2019-01-17 00:28:09 +08:00
},
{
"streamername",
2021-01-01 14:46:27 +08:00
this.StreamerName
2019-01-17 00:28:09 +08:00
},
};
};
2018-03-21 20:56:56 +08:00
2021-01-01 14:46:27 +08:00
this._stream = await this._response.Content.ReadAsStreamAsync();
2020-08-14 17:01:04 +08:00
try
{
2021-01-01 14:46:27 +08:00
if (this._response.Headers.ConnectionClose == false || (this._response.Headers.ConnectionClose is null && this._response.Version != VERSION_1_0))
this._stream.ReadTimeout = 3 * 1000;
2020-08-14 17:01:04 +08:00
}
catch (InvalidOperationException) { }
2018-03-21 20:56:56 +08:00
2021-01-01 14:46:27 +08:00
this.StreamDownloadTask = Task.Run(_ReadStreamLoop);
this.TriggerPropertyChanged(nameof(this.IsRecording));
2018-03-18 18:55:28 +08:00
}
2018-03-21 20:56:56 +08:00
}
catch (TaskCanceledException)
{
// client.GetAsync timed out
// useless exception message :/
_CleanupFlvRequest();
2021-01-01 14:46:27 +08:00
logger.Log(this.RoomId, LogLevel.Warn, "连接直播服务器超时。");
this.StreamMonitor.Check(TriggerType.HttpApiRecheck, (int)this.RoomConfig.TimingStreamRetry);
}
2018-03-21 20:56:56 +08:00
catch (Exception ex)
{
_CleanupFlvRequest();
2021-01-01 14:46:27 +08:00
logger.Log(this.RoomId, LogLevel.Error, "启动直播流下载出错。" + (this._retry ? "将重试启动。" : ""), ex);
if (this._retry)
2018-10-24 14:33:05 +08:00
{
2021-01-01 14:46:27 +08:00
this.StreamMonitor.Check(TriggerType.HttpApiRecheck, (int)this.RoomConfig.TimingStreamRetry);
2018-10-24 14:33:05 +08:00
}
2018-03-21 20:56:56 +08:00
}
return;
async Task _ReadStreamLoop()
{
try
{
const int BUF_SIZE = 1024 * 8;
byte[] buffer = new byte[BUF_SIZE];
while (!token.IsCancellationRequested)
{
2021-01-01 14:46:27 +08:00
int bytesRead = await this._stream.ReadAsync(buffer, 0, BUF_SIZE, token);
_UpdateDownloadSpeed(bytesRead);
if (bytesRead != 0)
{
if (bytesRead != BUF_SIZE)
{
2021-01-01 14:46:27 +08:00
this.Processor.AddBytes(buffer.Take(bytesRead).ToArray());
}
else
{
2021-01-01 14:46:27 +08:00
this.Processor.AddBytes(buffer);
}
}
else
{
break;
}
}
2018-12-17 21:24:52 +08:00
2021-01-01 14:46:27 +08:00
logger.Log(this.RoomId, LogLevel.Info,
(token.IsCancellationRequested ? "本地操作结束当前录制。" : "服务器关闭直播流,可能是直播已结束。")
2021-01-01 14:46:27 +08:00
+ (this._retry ? "将重试启动。" : ""));
if (this._retry)
2018-12-17 21:24:52 +08:00
{
2021-01-01 14:46:27 +08:00
this.StreamMonitor.Check(TriggerType.HttpApiRecheck, (int)this.RoomConfig.TimingStreamRetry);
2018-12-17 21:24:52 +08:00
}
}
catch (Exception e)
{
2018-12-17 21:24:52 +08:00
if (e is ObjectDisposedException && token.IsCancellationRequested) { return; }
2021-01-01 14:46:27 +08:00
logger.Log(this.RoomId, LogLevel.Warn, "录播发生错误", e);
}
finally
{
_CleanupFlvRequest();
}
}
2018-10-31 06:22:38 +08:00
void _CleanupFlvRequest()
2018-03-21 20:56:56 +08:00
{
2021-01-01 14:46:27 +08:00
if (this.Processor != null)
2018-10-31 06:22:38 +08:00
{
2021-01-01 14:46:27 +08:00
this.Processor.FinallizeFile();
this.Processor.Dispose();
this.Processor = null;
2018-10-31 06:22:38 +08:00
}
2021-01-01 14:46:27 +08:00
this._stream?.Dispose();
this._stream = null;
this._response?.Dispose();
this._response = null;
this._lastUpdateTimestamp = 0;
this.DownloadSpeedMegaBitps = 0d;
this.DownloadSpeedPersentage = 0d;
this.TriggerPropertyChanged(nameof(this.IsRecording));
2018-03-15 21:55:01 +08:00
}
2018-10-31 06:22:38 +08:00
void _UpdateDownloadSpeed(int bytesRead)
2018-03-15 21:55:01 +08:00
{
2018-10-31 06:22:38 +08:00
DateTime now = DateTime.Now;
2021-01-01 14:46:27 +08:00
double passedSeconds = (now - this.LastUpdateDateTime).TotalSeconds;
this._lastUpdateSize += bytesRead;
2018-11-03 07:45:56 +08:00
if (passedSeconds > 1.5)
2018-10-31 06:22:38 +08:00
{
2021-01-01 14:46:27 +08:00
this.DownloadSpeedMegaBitps = this._lastUpdateSize / passedSeconds * 8d / 1_000_000d; // mega bit per second
this.DownloadSpeedPersentage = (this.DownloadSpeedPersentage / 2) + ((this.Processor.TotalMaxTimestamp - this._lastUpdateTimestamp) / passedSeconds / 1000 / 2); // ((RecordedTime/1000) / RealTime)%
this._lastUpdateTimestamp = this.Processor.TotalMaxTimestamp;
this._lastUpdateSize = 0;
this.LastUpdateDateTime = now;
2018-10-31 06:22:38 +08:00
}
2018-03-15 21:55:01 +08:00
}
}
2018-03-13 13:21:01 +08:00
// Called by API or GUI
2021-01-01 14:46:27 +08:00
public void Clip() => this.Processor?.Clip();
2018-03-13 13:21:01 +08:00
2021-01-01 14:46:27 +08:00
public void Shutdown() => this.Dispose(true);
2020-12-20 20:56:40 +08:00
private (string fullPath, string relativePath) GetStreamFilePath()
2020-11-23 17:35:42 +08:00
{
2021-01-01 14:46:27 +08:00
var path = this.FormatFilename(this.RoomConfig.RecordFilenameFormat);
2020-11-23 17:35:42 +08:00
// 有点脏的写法,不过凑合吧
2021-01-01 14:46:27 +08:00
if (this.RoomConfig.RecordDanmaku)
2020-11-23 17:35:42 +08:00
{
2020-12-20 20:56:40 +08:00
var xmlpath = Path.ChangeExtension(path.fullPath, "xml");
2021-01-01 14:46:27 +08:00
this.basicDanmakuWriter.EnableWithPath(xmlpath, this);
2020-11-23 17:35:42 +08:00
}
2021-01-01 14:46:27 +08:00
this.recordEndData = new RecordEndData
2020-12-20 20:56:40 +08:00
{
RoomId = RoomId,
Title = Title,
Name = StreamerName,
StartRecordTime = DateTimeOffset.Now,
RelativePath = path.relativePath,
};
2020-11-23 17:35:42 +08:00
return path;
}
2021-01-01 14:46:27 +08:00
private string GetClipFilePath() => this.FormatFilename(this.RoomConfig.ClipFilenameFormat).fullPath;
2020-12-20 20:56:40 +08:00
private (string fullPath, string relativePath) FormatFilename(string formatString)
{
2021-01-08 18:54:50 +08:00
var now = DateTime.Now;
var date = now.ToString("yyyyMMdd");
var time = now.ToString("HHmmss");
var randomStr = random.Next(100, 999).ToString();
2020-04-25 01:27:39 +08:00
2020-12-20 20:56:40 +08:00
var relativePath = formatString
2020-04-25 01:27:39 +08:00
.Replace(@"{date}", date)
.Replace(@"{time}", time)
.Replace(@"{random}", randomStr)
2021-01-01 14:46:27 +08:00
.Replace(@"{roomid}", this.RoomId.ToString())
.Replace(@"{title}", this.Title.RemoveInvalidFileName())
2021-01-08 18:54:50 +08:00
.Replace(@"{name}", this.StreamerName.RemoveInvalidFileName())
.Replace(@"{parea}", this.ParentAreaName.RemoveInvalidFileName())
.Replace(@"{area}", this.AreaName.RemoveInvalidFileName())
;
2020-04-25 01:27:39 +08:00
2020-12-20 20:56:40 +08:00
if (!relativePath.EndsWith(".flv", StringComparison.OrdinalIgnoreCase))
relativePath += ".flv";
2020-04-25 01:27:39 +08:00
2020-12-20 20:56:40 +08:00
relativePath = relativePath.RemoveInvalidFileName(ignore_slash: true);
2021-01-01 14:46:27 +08:00
var workDirectory = this.RoomConfig.WorkDirectory;
var fullPath = Path.Combine(workDirectory, relativePath);
2020-12-20 20:56:40 +08:00
fullPath = Path.GetFullPath(fullPath);
2020-04-25 01:27:39 +08:00
2021-01-01 14:46:27 +08:00
if (!CheckPath(workDirectory, Path.GetDirectoryName(fullPath)))
2020-04-25 01:27:39 +08:00
{
2021-01-01 14:46:27 +08:00
logger.Log(this.RoomId, LogLevel.Warn, "录制文件位置超出允许范围,请检查设置。将写入到默认路径。");
relativePath = Path.Combine(this.RoomId.ToString(), $"{this.RoomId}-{date}-{time}-{randomStr}.flv");
fullPath = Path.Combine(workDirectory, relativePath);
2020-04-25 01:27:39 +08:00
}
2020-12-20 20:56:40 +08:00
if (new FileInfo(relativePath).Exists)
2020-04-25 01:27:39 +08:00
{
2021-01-01 14:46:27 +08:00
logger.Log(this.RoomId, LogLevel.Warn, "录制文件名冲突,请检查设置。将写入到默认路径。");
relativePath = Path.Combine(this.RoomId.ToString(), $"{this.RoomId}-{date}-{time}-{randomStr}.flv");
fullPath = Path.Combine(workDirectory, relativePath);
2020-04-25 01:27:39 +08:00
}
2020-12-20 20:56:40 +08:00
return (fullPath, relativePath);
2020-04-25 01:27:39 +08:00
}
private static bool CheckPath(string parent, string child)
{
DirectoryInfo di_p = new DirectoryInfo(parent);
DirectoryInfo di_c = new DirectoryInfo(child);
2020-05-01 08:37:56 +08:00
if (di_c.FullName == di_p.FullName)
return true;
2020-04-25 01:27:39 +08:00
bool isParent = false;
while (di_c.Parent != null)
{
if (di_c.Parent.FullName == di_p.FullName)
{
isParent = true;
break;
}
else
di_c = di_c.Parent;
}
return isParent;
}
2018-03-12 18:57:20 +08:00
public event PropertyChangedEventHandler PropertyChanged;
2018-03-24 09:48:06 +08:00
protected void TriggerPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
#region IDisposable Support
private bool disposedValue = false; // 要检测冗余调用
protected virtual void Dispose(bool disposing)
{
2021-01-01 14:46:27 +08:00
if (!this.disposedValue)
{
if (disposing)
{
2021-01-01 14:46:27 +08:00
this.Stop();
this.StopRecord();
this.Processor?.FinallizeFile();
this.Processor?.Dispose();
this.StreamMonitor?.Dispose();
this._response?.Dispose();
this._stream?.Dispose();
this.cancellationTokenSource?.Dispose();
this.basicDanmakuWriter?.Dispose();
}
2021-01-01 14:46:27 +08:00
this.Processor = null;
this._response = null;
this._stream = null;
this.cancellationTokenSource = null;
2021-01-01 14:46:27 +08:00
this.disposedValue = true;
}
}
public void Dispose()
{
// 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
2021-01-01 14:46:27 +08:00
this.Dispose(true);
}
#endregion
2018-03-12 18:57:20 +08:00
}
}