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; }
|
|
|
|
|
|
|
|
|
|
public RecordStatus Status
|
|
|
|
|
{
|
|
|
|
|
get => _status;
|
|
|
|
|
set => SetField(ref _status, value, nameof(Status));
|
|
|
|
|
}
|
|
|
|
|
private RecordStatus _status;
|
2018-03-12 18:57:20 +08:00
|
|
|
|
|
2018-03-13 14:23:53 +08:00
|
|
|
|
public StreamMonitor streamMonitor;
|
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-13 14:23:53 +08:00
|
|
|
|
private HttpWebRequest webRequest;
|
2018-03-21 20:56:56 +08:00
|
|
|
|
private Stream flvStream;
|
2018-03-20 00:12:32 +08:00
|
|
|
|
private readonly Settings _settings;
|
2018-03-13 13:21:01 +08:00
|
|
|
|
|
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-21 22:41:34 +08:00
|
|
|
|
RecordInfo = new RecordInfo(StreamerName);
|
|
|
|
|
|
2018-03-21 20:56:56 +08:00
|
|
|
|
streamMonitor = new StreamMonitor(RealRoomid);
|
|
|
|
|
streamMonitor.StreamStatusChanged += StreamMonitor_StreamStatusChanged;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool Start()
|
|
|
|
|
{
|
|
|
|
|
var r = streamMonitor.Start();
|
|
|
|
|
if (r)
|
|
|
|
|
Status = RecordStatus.Waiting;
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Stop()
|
|
|
|
|
{
|
|
|
|
|
streamMonitor.Stop();
|
|
|
|
|
Status = RecordStatus.Idle;
|
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-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-21 20:56:56 +08:00
|
|
|
|
_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)
|
|
|
|
|
{
|
|
|
|
|
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-21 20:56:56 +08:00
|
|
|
|
Status = RecordStatus.Recording;
|
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秒后重试");
|
|
|
|
|
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(RecordInfo.GetStreamFilePath());
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
if (bytesRead == 0)
|
|
|
|
|
{
|
|
|
|
|
_CleanupFlvRequest();
|
|
|
|
|
logger.Log(RealRoomid, LogLevel.Info, "直播流下载连接已关闭。" + (triggerType != TriggerType.HttpApiRecheck ? "将在30秒后重试启动。" : ""));
|
|
|
|
|
if (triggerType != TriggerType.HttpApiRecheck)
|
|
|
|
|
streamMonitor.CheckAfterSeconeds(30);
|
|
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
|
logger.Log(RealRoomid, LogLevel.Info, "直播流下载连接出错。" + (triggerType != TriggerType.HttpApiRecheck ? "将在30秒后重试启动。" : ""), ex);
|
2018-03-24 02:27:58 +08:00
|
|
|
|
// 有时这里不算是“出错”,比如手动切断下载的情况。
|
|
|
|
|
// TODO: 优化此处提示 & 处理逻辑
|
2018-03-21 20:56:56 +08:00
|
|
|
|
if (triggerType != TriggerType.HttpApiRecheck)
|
|
|
|
|
streamMonitor.CheckAfterSeconeds(30);
|
2018-03-18 18:55:28 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-03-21 20:56:56 +08:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
_CleanupFlvRequest();
|
|
|
|
|
logger.Log(RealRoomid, LogLevel.Warn, "启动直播流下载出错。" + (triggerType != TriggerType.HttpApiRecheck ? "将在30秒后重试启动。" : ""), ex);
|
|
|
|
|
if (triggerType != TriggerType.HttpApiRecheck)
|
|
|
|
|
streamMonitor.CheckAfterSeconeds(30);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-03-18 18:55:28 +08:00
|
|
|
|
|
2018-03-21 20:56:56 +08:00
|
|
|
|
private void _CleanupFlvRequest()
|
|
|
|
|
{
|
|
|
|
|
Status = RecordStatus.Waiting;
|
|
|
|
|
if (Processor != null)
|
|
|
|
|
{
|
|
|
|
|
Processor.FinallizeFile();
|
|
|
|
|
Processor.Dispose();
|
|
|
|
|
Processor = null;
|
|
|
|
|
}
|
|
|
|
|
webRequest = null;
|
|
|
|
|
if (flvStream != null)
|
|
|
|
|
{
|
|
|
|
|
flvStream.Close();
|
|
|
|
|
flvStream.Dispose();
|
|
|
|
|
flvStream = null;
|
|
|
|
|
}
|
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-21 20:56:56 +08:00
|
|
|
|
protected bool SetField<T>(ref T field, T value, string propertyName)
|
|
|
|
|
{
|
|
|
|
|
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
|
|
|
|
|
field = value;
|
|
|
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-03-12 18:57:20 +08:00
|
|
|
|
}
|
|
|
|
|
}
|