BililiveRecorder/BililiveRecorder.Core/RecordedRoom.cs

317 lines
12 KiB
C#
Raw Normal View History

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.Generic;
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-03-12 18:57:20 +08:00
namespace BililiveRecorder.Core
{
public class RecordedRoom : INotifyPropertyChanged
{
2018-03-21 20:56:56 +08:00
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
2018-03-15 21:55:01 +08:00
2018-03-21 20:56:56 +08:00
public int Roomid { get; private set; }
public int RealRoomid { get => RoomInfo?.RealRoomid ?? Roomid; }
public string StreamerName { get => RoomInfo?.Username ?? string.Empty; }
public RoomInfo RoomInfo { get; private set; }
public RecordInfo RecordInfo { get; private set; }
2018-03-27 15:53:03 +08:00
public bool IsMonitoring => StreamMonitor.receiver.IsConnected;
public bool IsRecording => flvStream != null;
2018-03-12 18:57:20 +08:00
public FlvStreamProcessor Processor; // FlvProcessor
2018-03-21 22:41:34 +08:00
public ObservableCollection<FlvClipProcessor> Clips { get; private set; } = new ObservableCollection<FlvClipProcessor>();
2018-03-24 09:48:06 +08:00
private Settings _settings { get; }
2018-03-27 15:53:03 +08:00
private StreamMonitor StreamMonitor { get; }
2018-03-13 14:23:53 +08:00
private HttpWebRequest webRequest;
2018-03-21 20:56:56 +08:00
private Stream flvStream;
2018-03-24 08:58:46 +08:00
private bool flv_shutdown = false;
2018-03-13 13:21:01 +08:00
2018-03-26 06:14:01 +08:00
public double DownloadSpeedKiBps
{
get { return _DownloadSpeedKiBps; }
set { if (value != _DownloadSpeedKiBps) { _DownloadSpeedKiBps = value; TriggerPropertyChanged(nameof(DownloadSpeedKiBps)); } }
}
private double _DownloadSpeedKiBps = 0;
private DateTime lastUpdateDateTime;
private long lastUpdateSize = 0;
2018-03-21 20:56:56 +08:00
public RecordedRoom(Settings settings, int roomid)
2018-03-13 13:21:01 +08:00
{
2018-03-20 00:12:32 +08:00
_settings = settings;
2018-03-24 08:34:57 +08:00
_settings.PropertyChanged += _settings_PropertyChanged;
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
UpdateRoomInfo();
2018-03-21 20:56:56 +08:00
2018-03-24 08:58:46 +08:00
RecordInfo = new RecordInfo(StreamerName)
{
SavePath = _settings.SavePath
};
2018-03-21 22:41:34 +08:00
2018-03-27 15:53:03 +08:00
StreamMonitor = new StreamMonitor(RealRoomid);
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();
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();
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)
{
if (e.PropertyName == nameof(_settings.Clip_Past))
{
if (Processor != null)
Processor.Clip_Past = _settings.Clip_Past;
}
else if (e.PropertyName == nameof(_settings.Clip_Future))
{
if (Processor != null)
Processor.Clip_Future = _settings.Clip_Future;
}
2018-03-24 08:58:46 +08:00
else if (e.PropertyName == nameof(_settings.SavePath))
{
if (RecordInfo != null)
RecordInfo.SavePath = _settings.SavePath;
}
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-03-21 20:56:56 +08:00
_StartRecord(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);
// _StartRecord(TriggerType.Manual);
2018-03-13 13:21:01 +08:00
}
2018-03-23 06:57:22 +08:00
public void StopRecord()
{
if (flvStream != null)
{
2018-03-24 08:58:46 +08:00
flv_shutdown = true;
2018-03-23 06:57:22 +08:00
flvStream.Close();
}
}
2018-03-21 20:56:56 +08:00
private void _StartRecord(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()
* }
* */
if (webRequest != null || flvStream != null || Processor != null)
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-03-21 20:56:56 +08:00
try
2018-03-15 21:55:01 +08:00
{
2018-03-24 08:58:46 +08:00
flv_shutdown = false;
2018-03-15 21:55:01 +08:00
2018-03-21 20:56:56 +08:00
string flv_path = BililiveAPI.GetPlayUrl(RoomInfo.RealRoomid);
2018-03-18 18:55:28 +08:00
2018-03-21 20:56:56 +08:00
webRequest = WebRequest.CreateHttp(flv_path);
_SetupFlvRequest(webRequest);
HttpWebResponse response = webRequest.GetResponse() as HttpWebResponse;
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();
webRequest = null;
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-03-27 15:53:03 +08:00
StreamMonitor.CheckAfterSeconeds(30);
2018-03-18 18:55:28 +08:00
}
2018-03-21 20:56:56 +08:00
return;
}
else
{
// response.StatusCode == HttpStatusCode.OK
Processor = new FlvStreamProcessor(_settings.Feature != EnabledFeature.ClipOnly ? RecordInfo.GetStreamFilePath() : null, _settings.Feature == EnabledFeature.RecordOnly);
2018-03-21 20:56:56 +08:00
Processor.TagProcessed += Processor_TagProcessed;
Processor.StreamFinalized += Processor_StreamFinalized;
Processor.GetFileName = RecordInfo.GetStreamFilePath;
2018-03-24 08:34:57 +08:00
Processor.Clip_Future = _settings.Clip_Future;
Processor.Clip_Past = _settings.Clip_Past;
2018-03-21 20:56:56 +08:00
flvStream = response.GetResponseStream();
const int BUF_SIZE = 1024 * 8;// 8 KiB
byte[] buffer = new byte[BUF_SIZE];
void callback(IAsyncResult ar)
2018-03-18 18:55:28 +08:00
{
2018-03-21 20:56:56 +08:00
try
2018-03-18 18:55:28 +08:00
{
2018-03-21 20:56:56 +08:00
int bytesRead = flvStream.EndRead(ar);
2018-03-26 06:14:01 +08:00
_UpdateDownloadSpeed(bytesRead);
2018-03-21 20:56:56 +08:00
if (bytesRead == 0)
{
_CleanupFlvRequest();
2018-03-24 08:58:46 +08:00
logger.Log(RealRoomid, LogLevel.Info, "直播已结束,停止录制。" + (triggerType != TriggerType.HttpApiRecheck ? "将在30秒后重试启动。" : ""));
2018-03-21 20:56:56 +08:00
if (triggerType != TriggerType.HttpApiRecheck)
2018-03-27 15:53:03 +08:00
StreamMonitor.CheckAfterSeconeds(30);
2018-03-21 20:56:56 +08:00
}
else
{
if (bytesRead != buffer.Length)
Processor.AddBytes(buffer.Take(bytesRead).ToArray());
else
Processor.AddBytes(buffer);
flvStream.BeginRead(buffer, 0, BUF_SIZE, callback, null);
}
2018-03-18 18:55:28 +08:00
}
2018-03-21 20:56:56 +08:00
catch (Exception ex)
2018-03-18 18:55:28 +08:00
{
2018-03-21 20:56:56 +08:00
_CleanupFlvRequest();
2018-03-24 08:58:46 +08:00
if (!flv_shutdown)
{
logger.Log(RealRoomid, LogLevel.Info, "直播流下载连接出错。" + (triggerType != TriggerType.HttpApiRecheck ? "将在30秒后重试启动。" : ""), ex);
if (triggerType != TriggerType.HttpApiRecheck)
2018-03-27 15:53:03 +08:00
StreamMonitor.CheckAfterSeconeds(30);
2018-03-24 08:58:46 +08:00
}
else
{
logger.Log(RealRoomid, LogLevel.Info, "直播流下载已结束。");
}
2018-03-18 18:55:28 +08:00
}
}
2018-03-21 20:56:56 +08:00
flvStream.BeginRead(buffer, 0, BUF_SIZE, callback, null);
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-03-27 15:53:03 +08:00
StreamMonitor.CheckAfterSeconeds(30);
2018-03-21 20:56:56 +08:00
}
}
2018-03-18 18:55:28 +08:00
2018-03-26 06:14:01 +08:00
private void _UpdateDownloadSpeed(int bytesRead)
{
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-21 20:56:56 +08:00
private void _CleanupFlvRequest()
{
if (Processor != null)
{
Processor.FinallizeFile();
Processor.Dispose();
Processor = null;
}
webRequest = null;
if (flvStream != null)
{
flvStream.Close();
flvStream.Dispose();
flvStream = null;
}
2018-03-26 06:14:01 +08:00
DownloadSpeedKiBps = 0d;
TriggerPropertyChanged(nameof(IsRecording));
2018-03-15 21:55:01 +08:00
}
private static void _SetupFlvRequest(HttpWebRequest r)
{
r.Accept = "*/*";
r.AllowAutoRedirect = true;
2018-03-21 22:41:34 +08:00
// r.Connection = "keep-alive";
2018-03-15 21:55:01 +08:00
r.Referer = "https://live.bilibili.com";
r.Headers["Origin"] = "https://live.bilibili.com";
r.UserAgent = "Mozilla/5.0 BililiveRecorder/0.0.0.0 (+https://github.com/Bililive/BililiveRecorder;bliverec@genteure.com)";
}
public bool UpdateRoomInfo()
{
try
{
2018-03-21 20:56:56 +08:00
RoomInfo = BililiveAPI.GetRoomInfo(Roomid);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(RealRoomid)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StreamerName)));
2018-03-15 21:55:01 +08:00
return true;
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
return false;
}
}
2018-03-13 13:21:01 +08:00
// Called by API or GUI
public void Clip()
{
2018-03-21 22:41:34 +08:00
if (Processor == null) return;
2018-03-13 13:21:01 +08:00
var clip = Processor.Clip();
clip.ClipFinalized += CallBack_ClipFinalized;
2018-03-21 20:56:56 +08:00
clip.GetFileName = RecordInfo.GetClipFilePath;
2018-03-13 13:21:01 +08:00
Clips.Add(clip);
}
private void CallBack_ClipFinalized(object sender, ClipFinalizedArgs e)
{
if (Clips.Remove(e.ClipProcessor))
{
Debug.WriteLine("Clip Finalized");
}
else
{
Debug.WriteLine("Warning! Clip Finalized but was not in Collection.");
}
}
2018-03-19 01:05:02 +08:00
private void Processor_TagProcessed(object sender, TagProcessedArgs e)
2018-03-13 13:21:01 +08:00
{
2018-03-19 16:51:35 +08:00
Clips.ToList().ForEach(fcp => fcp.AddTag(e.Tag));
}
private void Processor_StreamFinalized(object sender, StreamFinalizedArgs e)
{
Clips.ToList().ForEach(fcp => fcp.FinallizeFile());
2018-03-13 13:21:01 +08:00
}
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
}
}