2018-03-21 20:56:56 +08:00
|
|
|
|
using BililiveRecorder.FlvProcessor;
|
|
|
|
|
using NLog;
|
|
|
|
|
using System;
|
2018-03-12 18:57:20 +08:00
|
|
|
|
using System.Collections.ObjectModel;
|
|
|
|
|
using System.ComponentModel;
|
2018-03-13 13:21:01 +08:00
|
|
|
|
using System.Diagnostics;
|
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;
|
2018-10-31 06:22:38 +08:00
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
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();
|
2018-03-15 21:55:01 +08:00
|
|
|
|
|
2018-10-31 06:22:38 +08:00
|
|
|
|
private int _roomid;
|
|
|
|
|
private int _realRoomid;
|
|
|
|
|
private string _streamerName;
|
|
|
|
|
|
|
|
|
|
public int Roomid
|
|
|
|
|
{
|
|
|
|
|
get => _roomid;
|
|
|
|
|
private set
|
|
|
|
|
{
|
|
|
|
|
if (value == _roomid) { return; }
|
|
|
|
|
_roomid = value;
|
|
|
|
|
TriggerPropertyChanged(nameof(Roomid));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public int RealRoomid
|
|
|
|
|
{
|
|
|
|
|
get => _realRoomid;
|
|
|
|
|
private set
|
|
|
|
|
{
|
|
|
|
|
if (value == _realRoomid) { return; }
|
|
|
|
|
_realRoomid = value;
|
|
|
|
|
TriggerPropertyChanged(nameof(RealRoomid));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public string StreamerName
|
|
|
|
|
{
|
|
|
|
|
get => _streamerName;
|
|
|
|
|
private set
|
|
|
|
|
{
|
|
|
|
|
if (value == _streamerName) { return; }
|
|
|
|
|
_streamerName = value;
|
|
|
|
|
TriggerPropertyChanged(nameof(StreamerName));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-24 14:33:05 +08:00
|
|
|
|
public IRecordInfo RecordInfo { get; private set; }
|
2018-03-21 20:56:56 +08:00
|
|
|
|
|
2018-10-29 02:13:54 +08:00
|
|
|
|
public bool IsMonitoring => StreamMonitor.Receiver.IsConnected;
|
2018-10-31 06:22:38 +08:00
|
|
|
|
public bool IsRecording => !(StreamDownloadTask?.IsCompleted ?? true);
|
2018-03-12 18:57:20 +08:00
|
|
|
|
|
2018-10-31 06:22:38 +08:00
|
|
|
|
private readonly Func<IFlvStreamProcessor> newIFlvStreamProcessor;
|
|
|
|
|
public IFlvStreamProcessor Processor { get; private set; } // FlvProcessor
|
|
|
|
|
private ObservableCollection<IFlvClipProcessor> Clips { get; set; } = new ObservableCollection<IFlvClipProcessor>();
|
2018-03-24 09:48:06 +08:00
|
|
|
|
|
2018-10-24 14:33:05 +08:00
|
|
|
|
public IStreamMonitor StreamMonitor { get; }
|
|
|
|
|
private ISettings _settings { get; }
|
2018-10-31 06:22:38 +08:00
|
|
|
|
|
|
|
|
|
public Task StreamDownloadTask;
|
|
|
|
|
public CancellationTokenSource cancellationTokenSource;
|
|
|
|
|
|
2018-03-13 13:21:01 +08:00
|
|
|
|
|
2018-03-26 06:14:01 +08:00
|
|
|
|
public double DownloadSpeedKiBps
|
|
|
|
|
{
|
|
|
|
|
get { return _DownloadSpeedKiBps; }
|
2018-04-14 05:22:56 +08:00
|
|
|
|
private set { if (value != _DownloadSpeedKiBps) { _DownloadSpeedKiBps = value; TriggerPropertyChanged(nameof(DownloadSpeedKiBps)); } }
|
2018-03-26 06:14:01 +08:00
|
|
|
|
}
|
|
|
|
|
private double _DownloadSpeedKiBps = 0;
|
2018-04-14 05:22:56 +08:00
|
|
|
|
public DateTime LastUpdateDateTime { get; private set; } = DateTime.Now;
|
|
|
|
|
public long LastUpdateSize { get; private set; } = 0;
|
2018-03-26 06:14:01 +08:00
|
|
|
|
|
2018-10-25 19:20:23 +08:00
|
|
|
|
public RecordedRoom(ISettings settings,
|
|
|
|
|
Func<string, IRecordInfo> newIRecordInfo,
|
|
|
|
|
Func<int, IStreamMonitor> newIStreamMonitor,
|
2018-10-31 06:22:38 +08:00
|
|
|
|
Func<IFlvStreamProcessor> newIFlvStreamProcessor,
|
2018-10-25 19:20:23 +08:00
|
|
|
|
int roomid)
|
2018-03-13 13:21:01 +08:00
|
|
|
|
{
|
2018-10-25 19:20:23 +08:00
|
|
|
|
this.newIFlvStreamProcessor = newIFlvStreamProcessor;
|
|
|
|
|
|
2018-03-20 00:12:32 +08:00
|
|
|
|
_settings = settings;
|
2018-10-29 02:13:54 +08:00
|
|
|
|
// _settings.PropertyChanged += _settings_PropertyChanged;
|
|
|
|
|
// TODO: 事件导致的内存泄漏
|
2018-03-20 00:12:32 +08:00
|
|
|
|
|
2018-03-21 20:56:56 +08:00
|
|
|
|
Roomid = roomid;
|
2018-03-15 21:55:01 +08:00
|
|
|
|
|
2018-10-31 06:22:38 +08:00
|
|
|
|
{
|
|
|
|
|
var roomInfo = BililiveAPI.GetRoomInfo(Roomid);
|
|
|
|
|
RealRoomid = roomInfo.RealRoomid;
|
|
|
|
|
StreamerName = roomInfo.Username;
|
|
|
|
|
}
|
2018-03-21 20:56:56 +08:00
|
|
|
|
|
2018-10-25 19:20:23 +08:00
|
|
|
|
RecordInfo = newIRecordInfo(StreamerName);
|
2018-03-21 22:41:34 +08:00
|
|
|
|
|
2018-10-25 19:20:23 +08:00
|
|
|
|
StreamMonitor = newIStreamMonitor(RealRoomid);
|
2018-03-27 15:53:03 +08:00
|
|
|
|
StreamMonitor.StreamStatusChanged += StreamMonitor_StreamStatusChanged;
|
2018-03-21 20:56:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool Start()
|
|
|
|
|
{
|
2018-03-27 15:53:03 +08:00
|
|
|
|
var r = StreamMonitor.Start();
|
2018-03-25 15:14:27 +08:00
|
|
|
|
TriggerPropertyChanged(nameof(IsMonitoring));
|
2018-03-21 20:56:56 +08:00
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Stop()
|
|
|
|
|
{
|
2018-03-27 15:53:03 +08:00
|
|
|
|
StreamMonitor.Stop();
|
2018-03-25 15:14:27 +08:00
|
|
|
|
TriggerPropertyChanged(nameof(IsMonitoring));
|
2018-03-13 14:23:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-20 00:12:32 +08:00
|
|
|
|
private void _settings_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
|
|
|
{
|
2018-10-29 02:13:54 +08:00
|
|
|
|
// TODO: 事件导致的内存泄漏
|
|
|
|
|
/**
|
2018-03-20 00:12:32 +08:00
|
|
|
|
if (e.PropertyName == nameof(_settings.Clip_Past))
|
|
|
|
|
{
|
|
|
|
|
if (Processor != null)
|
2018-10-24 14:33:05 +08:00
|
|
|
|
{
|
2018-03-20 00:12:32 +08:00
|
|
|
|
Processor.Clip_Past = _settings.Clip_Past;
|
2018-10-24 14:33:05 +08:00
|
|
|
|
}
|
2018-03-20 00:12:32 +08:00
|
|
|
|
}
|
|
|
|
|
else if (e.PropertyName == nameof(_settings.Clip_Future))
|
|
|
|
|
{
|
|
|
|
|
if (Processor != null)
|
2018-10-24 14:33:05 +08:00
|
|
|
|
{
|
2018-03-20 00:12:32 +08:00
|
|
|
|
Processor.Clip_Future = _settings.Clip_Future;
|
2018-10-24 14:33:05 +08:00
|
|
|
|
}
|
2018-03-20 00:12:32 +08:00
|
|
|
|
}
|
2018-10-29 02:13:54 +08:00
|
|
|
|
|
2018-03-24 08:58:46 +08:00
|
|
|
|
else if (e.PropertyName == nameof(_settings.SavePath))
|
|
|
|
|
{
|
|
|
|
|
if (RecordInfo != null)
|
2018-10-24 14:33:05 +08:00
|
|
|
|
{
|
2018-03-24 08:58:46 +08:00
|
|
|
|
RecordInfo.SavePath = _settings.SavePath;
|
2018-10-24 14:33:05 +08:00
|
|
|
|
}
|
2018-03-24 08:58:46 +08:00
|
|
|
|
}
|
2018-10-25 19:20:23 +08:00
|
|
|
|
*/
|
2018-03-20 00:12:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-13 14:23:53 +08:00
|
|
|
|
private void StreamMonitor_StreamStatusChanged(object sender, StreamStatusChangedArgs e)
|
|
|
|
|
{
|
2018-10-31 06:22:38 +08:00
|
|
|
|
_StartRecordAsync(e.type);
|
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
|
|
|
|
{
|
2018-03-27 15:53:03 +08:00
|
|
|
|
StreamMonitor.Check(TriggerType.Manual);
|
2018-03-26 04:50:08 +08:00
|
|
|
|
// _StartRecord(TriggerType.Manual);
|
2018-03-13 13:21:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-23 06:57:22 +08:00
|
|
|
|
public void StopRecord()
|
|
|
|
|
{
|
2018-10-31 06:22:38 +08:00
|
|
|
|
if (cancellationTokenSource != null)
|
2018-03-23 06:57:22 +08:00
|
|
|
|
{
|
2018-10-31 06:22:38 +08:00
|
|
|
|
cancellationTokenSource.Cancel();
|
|
|
|
|
StreamDownloadTask.Wait();
|
2018-03-23 06:57:22 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-31 06:22:38 +08:00
|
|
|
|
private async Task _StartRecordAsync(TriggerType triggerType)
|
2018-03-15 21:55:01 +08:00
|
|
|
|
{
|
2018-03-21 20:56:56 +08:00
|
|
|
|
/* *
|
|
|
|
|
* if (recording) return;
|
|
|
|
|
* try catch {
|
|
|
|
|
* if type == retry return;
|
|
|
|
|
* else retry()
|
|
|
|
|
* }
|
|
|
|
|
* */
|
|
|
|
|
|
2018-10-31 06:22:38 +08:00
|
|
|
|
// if (webRequest != null || flvStream != null || Processor != null)
|
|
|
|
|
if (IsRecording)
|
2018-03-15 21:55:01 +08:00
|
|
|
|
{
|
2018-03-21 20:56:56 +08:00
|
|
|
|
logger.Log(RealRoomid, LogLevel.Debug, "已经在录制中了");
|
|
|
|
|
return;
|
2018-03-15 21:55:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-31 06:22:38 +08:00
|
|
|
|
HttpWebRequest request = null;
|
|
|
|
|
HttpWebResponse response = null;
|
|
|
|
|
Stream stream = null;
|
|
|
|
|
|
|
|
|
|
cancellationTokenSource = new CancellationTokenSource();
|
|
|
|
|
var token = cancellationTokenSource.Token;
|
2018-03-21 20:56:56 +08:00
|
|
|
|
try
|
2018-03-15 21:55:01 +08:00
|
|
|
|
{
|
|
|
|
|
|
2018-10-31 06:22:38 +08:00
|
|
|
|
string flv_path = BililiveAPI.GetPlayUrl(RealRoomid);
|
|
|
|
|
|
|
|
|
|
request = WebRequest.CreateHttp(flv_path);
|
|
|
|
|
request.Accept = "*/*";
|
|
|
|
|
request.AllowAutoRedirect = true;
|
|
|
|
|
request.Referer = "https://live.bilibili.com";
|
|
|
|
|
request.Headers["Origin"] = "https://live.bilibili.com";
|
|
|
|
|
request.UserAgent = Utils.UserAgent;
|
|
|
|
|
|
|
|
|
|
response = await request.GetResponseAsync() as HttpWebResponse;
|
2018-03-18 18:55:28 +08:00
|
|
|
|
|
2018-03-21 20:56:56 +08:00
|
|
|
|
if (response.StatusCode != HttpStatusCode.OK)
|
2018-03-18 18:55:28 +08:00
|
|
|
|
{
|
2018-03-21 20:56:56 +08:00
|
|
|
|
logger.Log(RealRoomid, LogLevel.Info, string.Format("尝试下载直播流时服务器返回了 ({0}){1}", response.StatusCode, response.StatusDescription));
|
|
|
|
|
response.Close();
|
2018-10-31 06:22:38 +08:00
|
|
|
|
request = null;
|
2018-03-21 20:56:56 +08:00
|
|
|
|
if (response.StatusCode == HttpStatusCode.NotFound)
|
2018-03-18 18:55:28 +08:00
|
|
|
|
{
|
2018-03-21 20:56:56 +08:00
|
|
|
|
logger.Log(RealRoomid, LogLevel.Info, "将在30秒后重试");
|
2018-10-31 06:22:38 +08:00
|
|
|
|
StreamMonitor.CheckAfterSeconeds(30); // TODO: 重试次数和时间
|
2018-03-18 18:55:28 +08:00
|
|
|
|
}
|
2018-03-21 20:56:56 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2018-03-29 13:02:43 +08:00
|
|
|
|
if (triggerType == TriggerType.HttpApiRecheck)
|
|
|
|
|
{
|
|
|
|
|
triggerType = TriggerType.HttpApi;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-31 06:22:38 +08:00
|
|
|
|
Processor = newIFlvStreamProcessor().Initialize(RecordInfo.GetStreamFilePath, RecordInfo.GetClipFilePath, _settings.Feature);
|
|
|
|
|
Processor.ClipLengthFuture = _settings.Clip_Future;
|
|
|
|
|
Processor.ClipLengthPast = _settings.Clip_Past;
|
2018-03-21 20:56:56 +08:00
|
|
|
|
|
2018-10-31 06:22:38 +08:00
|
|
|
|
stream = response.GetResponseStream();
|
2018-03-21 20:56:56 +08:00
|
|
|
|
|
2018-10-31 06:22:38 +08:00
|
|
|
|
StreamDownloadTask = Task.Run(async () =>
|
2018-03-18 18:55:28 +08:00
|
|
|
|
{
|
2018-10-31 06:22:38 +08:00
|
|
|
|
const int BUF_SIZE = 1024 * 8;
|
|
|
|
|
byte[] buffer = new byte[BUF_SIZE];
|
|
|
|
|
while (!token.IsCancellationRequested)
|
2018-03-18 18:55:28 +08:00
|
|
|
|
{
|
2018-10-31 06:22:38 +08:00
|
|
|
|
int bytesRead = await stream.ReadAsync(buffer, 0, BUF_SIZE, token);
|
2018-03-26 06:14:01 +08:00
|
|
|
|
_UpdateDownloadSpeed(bytesRead);
|
2018-03-21 20:56:56 +08:00
|
|
|
|
if (bytesRead == 0)
|
|
|
|
|
{
|
2018-10-31 06:22:38 +08:00
|
|
|
|
// 录制已结束
|
|
|
|
|
// TODO: 重试次数和时间
|
|
|
|
|
// TODO: 用户操作停止时不重新继续
|
|
|
|
|
|
|
|
|
|
logger.Log(RealRoomid, LogLevel.Info,
|
|
|
|
|
(token.IsCancellationRequested ? "用户操作" : "直播已结束") + ",停止录制。"
|
|
|
|
|
+ (triggerType != TriggerType.HttpApiRecheck ? "将在30秒后重试启动。" : ""));
|
2018-03-21 20:56:56 +08:00
|
|
|
|
if (triggerType != TriggerType.HttpApiRecheck)
|
2018-10-24 14:33:05 +08:00
|
|
|
|
{
|
2018-03-27 15:53:03 +08:00
|
|
|
|
StreamMonitor.CheckAfterSeconeds(30);
|
2018-10-24 14:33:05 +08:00
|
|
|
|
}
|
2018-10-31 06:22:38 +08:00
|
|
|
|
break;
|
2018-03-21 20:56:56 +08:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2018-10-31 06:22:38 +08:00
|
|
|
|
if (bytesRead != BUF_SIZE)
|
2018-10-24 14:33:05 +08:00
|
|
|
|
{
|
2018-03-21 20:56:56 +08:00
|
|
|
|
Processor.AddBytes(buffer.Take(bytesRead).ToArray());
|
2018-10-24 14:33:05 +08:00
|
|
|
|
}
|
2018-03-21 20:56:56 +08:00
|
|
|
|
else
|
2018-10-24 14:33:05 +08:00
|
|
|
|
{
|
2018-03-21 20:56:56 +08:00
|
|
|
|
Processor.AddBytes(buffer);
|
2018-10-24 14:33:05 +08:00
|
|
|
|
}
|
2018-03-24 08:58:46 +08:00
|
|
|
|
}
|
2018-10-31 06:22:38 +08:00
|
|
|
|
} // while(true)
|
|
|
|
|
// outside of while
|
|
|
|
|
_CleanupFlvRequest();
|
|
|
|
|
});
|
2018-03-21 20:56:56 +08:00
|
|
|
|
|
2018-03-25 15:14:27 +08:00
|
|
|
|
TriggerPropertyChanged(nameof(IsRecording));
|
2018-03-18 18:55:28 +08:00
|
|
|
|
}
|
2018-03-21 20:56:56 +08:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_CleanupFlvRequest();
|
|
|
|
|
logger.Log(RealRoomid, LogLevel.Warn, "启动直播流下载出错。" + (triggerType != TriggerType.HttpApiRecheck ? "将在30秒后重试启动。" : ""), ex);
|
|
|
|
|
if (triggerType != TriggerType.HttpApiRecheck)
|
2018-10-24 14:33:05 +08:00
|
|
|
|
{
|
2018-03-27 15:53:03 +08:00
|
|
|
|
StreamMonitor.CheckAfterSeconeds(30);
|
2018-10-24 14:33:05 +08:00
|
|
|
|
}
|
2018-03-21 20:56:56 +08:00
|
|
|
|
}
|
2018-10-31 06:22:38 +08:00
|
|
|
|
void _CleanupFlvRequest()
|
2018-03-21 20:56:56 +08:00
|
|
|
|
{
|
2018-10-31 06:22:38 +08:00
|
|
|
|
if (Processor != null)
|
|
|
|
|
{
|
|
|
|
|
Processor.FinallizeFile();
|
|
|
|
|
Processor.Dispose();
|
|
|
|
|
Processor = null;
|
|
|
|
|
}
|
|
|
|
|
request = null;
|
|
|
|
|
stream?.Dispose();
|
|
|
|
|
stream = null;
|
|
|
|
|
response?.Dispose();
|
|
|
|
|
response = null;
|
|
|
|
|
|
|
|
|
|
DownloadSpeedKiBps = 0d;
|
|
|
|
|
TriggerPropertyChanged(nameof(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;
|
|
|
|
|
double sec = (now - LastUpdateDateTime).TotalSeconds;
|
|
|
|
|
LastUpdateSize += bytesRead;
|
|
|
|
|
if (sec > 1)
|
|
|
|
|
{
|
|
|
|
|
var speed = LastUpdateSize / sec;
|
|
|
|
|
LastUpdateDateTime = now;
|
|
|
|
|
LastUpdateSize = 0;
|
|
|
|
|
DownloadSpeedKiBps = speed / 1024; // KiB per sec
|
|
|
|
|
}
|
2018-03-15 21:55:01 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-13 13:21:01 +08:00
|
|
|
|
// Called by API or GUI
|
|
|
|
|
public void Clip()
|
|
|
|
|
{
|
2018-10-31 06:22:38 +08:00
|
|
|
|
Processor?.Clip();
|
2018-03-13 13:21:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-30 20:33:44 +08:00
|
|
|
|
public void Shutdown()
|
|
|
|
|
{
|
|
|
|
|
Stop();
|
|
|
|
|
StopRecord();
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-13 13:21:01 +08:00
|
|
|
|
private void CallBack_ClipFinalized(object sender, ClipFinalizedArgs e)
|
|
|
|
|
{
|
2018-10-30 20:33:44 +08:00
|
|
|
|
e.ClipProcessor.ClipFinalized -= CallBack_ClipFinalized;
|
2018-03-13 13:21:01 +08:00
|
|
|
|
if (Clips.Remove(e.ClipProcessor))
|
|
|
|
|
{
|
|
|
|
|
Debug.WriteLine("Clip Finalized");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Debug.WriteLine("Warning! Clip Finalized but was not in Collection.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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));
|
2018-03-12 18:57:20 +08:00
|
|
|
|
}
|
|
|
|
|
}
|