From e06befeddd744b0510da1f63fd3fa202568c4ba5 Mon Sep 17 00:00:00 2001 From: withsalt Date: Wed, 27 Apr 2022 21:40:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=9B=B4=E6=92=AD=E7=9B=91?= =?UTF-8?q?=E6=8E=A7=E5=B7=A5=E5=85=B7;=E4=BC=98=E5=8C=96=E7=A8=B3?= =?UTF-8?q?=E5=AE=9A=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PublishProfiles/FolderProfile.pubxml | 6 +- .../Config/CacheKeyConstant.cs | 2 + .../Services/BilibiliLiveApiService.cs | 2 +- .../{Node => Models}/AppSettingsNode.cs | 0 .../{Node => Models}/EmailConfigNode.cs | 0 .../{Node => Models}/LiveSettingsNode.cs | 0 .../Jobs/MonitorClientJob.cs | 40 ++++-- .../PublishProfiles/FolderProfile.pubxml | 2 +- src/BilibiliLiveMonitor/appsettings.json | 2 +- src/BilibiliLiver/BilibiliLiver.csproj | 6 +- .../{LiveSetting.cs => LiveSettings.cs} | 6 +- .../DependencyInjection/RegisteConfigure.cs | 2 +- src/BilibiliLiver/Program.cs | 10 ++ .../PublishProfiles/FolderProfile.pubxml | 3 +- .../Services/ConfigureService.cs | 4 +- .../Services/PushStreamService.cs | 124 ++++++++---------- src/BilibiliLiver/appsettings.json | 8 +- src/BilibiliLiver/nlog.config | 51 +++++++ 18 files changed, 175 insertions(+), 93 deletions(-) rename src/BilibiliLiveMonitor/Configs/{Node => Models}/AppSettingsNode.cs (100%) rename src/BilibiliLiveMonitor/Configs/{Node => Models}/EmailConfigNode.cs (100%) rename src/BilibiliLiveMonitor/Configs/{Node => Models}/LiveSettingsNode.cs (100%) rename src/BilibiliLiver/Config/Models/{LiveSetting.cs => LiveSettings.cs} (76%) create mode 100644 src/BilibiliLiver/nlog.config diff --git a/src/BilibiliLiveAreaTool/Properties/PublishProfiles/FolderProfile.pubxml b/src/BilibiliLiveAreaTool/Properties/PublishProfiles/FolderProfile.pubxml index e272a85..7a9269a 100644 --- a/src/BilibiliLiveAreaTool/Properties/PublishProfiles/FolderProfile.pubxml +++ b/src/BilibiliLiveAreaTool/Properties/PublishProfiles/FolderProfile.pubxml @@ -9,6 +9,10 @@ https://go.microsoft.com/fwlink/?LinkID=208121. bin\Release\publish\ FileSystem net6.0 - false + true + win-x64 + true + false + false \ No newline at end of file diff --git a/src/BilibiliLiveCommon/Config/CacheKeyConstant.cs b/src/BilibiliLiveCommon/Config/CacheKeyConstant.cs index 70109fa..478ea55 100644 --- a/src/BilibiliLiveCommon/Config/CacheKeyConstant.cs +++ b/src/BilibiliLiveCommon/Config/CacheKeyConstant.cs @@ -5,5 +5,7 @@ public class CacheKeyConstant public const string COOKIE_KEY = "COOKIE_KEY"; public const string LIVE_STATUS_KEY = "LIVE_STATUS_KEY"; + + public const string MAIL_SEND_CACHE_KEY = "MAIL_{0}_SEND_STATUS_KEY"; } } diff --git a/src/BilibiliLiveCommon/Services/BilibiliLiveApiService.cs b/src/BilibiliLiveCommon/Services/BilibiliLiveApiService.cs index 44e044c..4c78daa 100644 --- a/src/BilibiliLiveCommon/Services/BilibiliLiveApiService.cs +++ b/src/BilibiliLiveCommon/Services/BilibiliLiveApiService.cs @@ -278,7 +278,7 @@ private async Task CheckArea(int areaId) private async Task SlowDownOperation(string operationName) { int sleepMsec = new Random().Next(5000, 10000); - _logger.LogInformation($"执行{operationName}操作完成,休眠{sleepMsec / 1000}秒(避免频繁操作)。"); + _logger.LogDebug($"执行{operationName}操作完成,休眠{sleepMsec / 1000}秒,避免被B站频繁操作。"); await Task.Delay(sleepMsec); } diff --git a/src/BilibiliLiveMonitor/Configs/Node/AppSettingsNode.cs b/src/BilibiliLiveMonitor/Configs/Models/AppSettingsNode.cs similarity index 100% rename from src/BilibiliLiveMonitor/Configs/Node/AppSettingsNode.cs rename to src/BilibiliLiveMonitor/Configs/Models/AppSettingsNode.cs diff --git a/src/BilibiliLiveMonitor/Configs/Node/EmailConfigNode.cs b/src/BilibiliLiveMonitor/Configs/Models/EmailConfigNode.cs similarity index 100% rename from src/BilibiliLiveMonitor/Configs/Node/EmailConfigNode.cs rename to src/BilibiliLiveMonitor/Configs/Models/EmailConfigNode.cs diff --git a/src/BilibiliLiveMonitor/Configs/Node/LiveSettingsNode.cs b/src/BilibiliLiveMonitor/Configs/Models/LiveSettingsNode.cs similarity index 100% rename from src/BilibiliLiveMonitor/Configs/Node/LiveSettingsNode.cs rename to src/BilibiliLiveMonitor/Configs/Models/LiveSettingsNode.cs diff --git a/src/BilibiliLiveMonitor/Jobs/MonitorClientJob.cs b/src/BilibiliLiveMonitor/Jobs/MonitorClientJob.cs index 8c21a69..42ff7df 100644 --- a/src/BilibiliLiveMonitor/Jobs/MonitorClientJob.cs +++ b/src/BilibiliLiveMonitor/Jobs/MonitorClientJob.cs @@ -7,6 +7,7 @@ using BilibiliLiveCommon.Model; using BilibiliLiveCommon.Services.Interface; using BilibiliLiveCommon.Config; +using BilibiliLiveCommon.Utils; namespace BilibiliLiveMonitor.Jobs { @@ -37,7 +38,7 @@ public async Task Execute(IJobExecutionContext context) try { _logger.LogInformation($"开始查询直播间{_liveSettings.RoomId}状态...."); - if(_liveSettings.RoomId <= 0) + if (_liveSettings.RoomId <= 0) { throw new Exception("获取需要查询的直播房间号失败,请检查配置文件是否正确配置房间号。"); } @@ -46,24 +47,19 @@ public async Task Execute(IJobExecutionContext context) { throw new Exception("获取直播间信息失败。"); } + _logger.LogInformation($"获取直播间{_liveSettings.RoomId}状态成功,当前状态:{(playInfo.is_living ? "直播中" : "停止直播")}"); RoomPlayInfo lastPlayInfo = _cache.Get(CacheKeyConstant.LIVE_STATUS_KEY); - if(lastPlayInfo == null) + if (lastPlayInfo == null) { //第一次存缓存强制设置直播状态为1,免得第一次开启时就发送通知 playInfo.live_status = 1; _cache.Set(CacheKeyConstant.LIVE_STATUS_KEY, playInfo); return; } - if(lastPlayInfo.is_living != playInfo.is_living) + if (lastPlayInfo.is_living != playInfo.is_living) { - if (playInfo.is_living) - { - //开启了直播 - } - else - { - //直播已关闭 - } + await SendEmailNotice(playInfo); + _cache.Set(CacheKeyConstant.LIVE_STATUS_KEY, playInfo); } } catch (Exception ex) @@ -77,11 +73,29 @@ public async Task Execute(IJobExecutionContext context) /// /// /// - private async Task SendEmailNotice(string reason) + private async Task SendEmailNotice(RoomPlayInfo info) { try { - var result = await _email.Send("直播停止通知", reason); + _logger.LogInformation($"直播间{info.room_id}直播状态发生改变,发送通知邮件"); + String reason = ""; + string cacheKey = string.Format(CacheKeyConstant.MAIL_SEND_CACHE_KEY, info.live_status); + if (info.is_living) + { + reason = $"开播提醒:\r\n您订阅的直播间(https://live.bilibili.com/{info.room_id})开始直播啦。"; + } + else + { + reason = $"关闭提醒:\r\n您订阅的直播间(https://live.bilibili.com/{info.room_id})已经停止直播。"; + } + long lastSendTime = _cache.Get(cacheKey); + if (TimeUtil.Timestamp() - lastSendTime < 600) + { + _logger.LogWarning($"邮件发送过于频繁,忽略本次发送。{reason}"); + return; + } + var result = await _email.Send("直播订阅通知", reason); + _cache.Set(cacheKey, TimeUtil.Timestamp()); if (result.Item1 != SendStatus.Success) { throw new Exception(result.Item2); diff --git a/src/BilibiliLiveMonitor/Properties/PublishProfiles/FolderProfile.pubxml b/src/BilibiliLiveMonitor/Properties/PublishProfiles/FolderProfile.pubxml index 0226252..033a89e 100644 --- a/src/BilibiliLiveMonitor/Properties/PublishProfiles/FolderProfile.pubxml +++ b/src/BilibiliLiveMonitor/Properties/PublishProfiles/FolderProfile.pubxml @@ -11,7 +11,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. net6.0 win-x64 true - false + true false false diff --git a/src/BilibiliLiveMonitor/appsettings.json b/src/BilibiliLiveMonitor/appsettings.json index f0d028e..d65487e 100644 --- a/src/BilibiliLiveMonitor/appsettings.json +++ b/src/BilibiliLiveMonitor/appsettings.json @@ -7,7 +7,7 @@ } }, "AppSettings": { - "IntervalTime": 30, //检查间隔时间 单位:秒 + "IntervalTime": 60, //检查间隔时间 单位:秒 "IsEnableEmailNotice": false, "EmailConfig": { "SmtpServer": "smtp.oncemi.com", diff --git a/src/BilibiliLiver/BilibiliLiver.csproj b/src/BilibiliLiver/BilibiliLiver.csproj index cb43a24..ce76584 100644 --- a/src/BilibiliLiver/BilibiliLiver.csproj +++ b/src/BilibiliLiver/BilibiliLiver.csproj @@ -6,7 +6,7 @@ 3.0.1 3.0.1 withsalt - 2.0.2 + 3.0.3 B站直播工具。 @@ -28,6 +28,7 @@ + @@ -41,6 +42,9 @@ Always + + Always + diff --git a/src/BilibiliLiver/Config/Models/LiveSetting.cs b/src/BilibiliLiver/Config/Models/LiveSettings.cs similarity index 76% rename from src/BilibiliLiver/Config/Models/LiveSetting.cs rename to src/BilibiliLiver/Config/Models/LiveSettings.cs index 0ca51f1..5d1740f 100644 --- a/src/BilibiliLiver/Config/Models/LiveSetting.cs +++ b/src/BilibiliLiver/Config/Models/LiveSettings.cs @@ -1,6 +1,6 @@ namespace BilibiliLiver.Config.Models { - public class LiveSetting + public class LiveSettings { public int LiveAreaId { get; set; } @@ -10,6 +10,8 @@ public class LiveSetting public bool AutoRestart { get; set; } - public static string Position { get { return "LiveSetting"; } } + public int RepushFailedExitMinutes { get; set; } + + public static string Position { get { return "LiveSettings"; } } } } diff --git a/src/BilibiliLiver/DependencyInjection/RegisteConfigure.cs b/src/BilibiliLiver/DependencyInjection/RegisteConfigure.cs index 012bb23..a9ea1f3 100644 --- a/src/BilibiliLiver/DependencyInjection/RegisteConfigure.cs +++ b/src/BilibiliLiver/DependencyInjection/RegisteConfigure.cs @@ -8,7 +8,7 @@ public static class RegisteConfigure { public static IServiceCollection ConfigureSettings(this IServiceCollection services, HostBuilderContext hostContext) { - services.Configure(hostContext.Configuration.GetSection(LiveSetting.Position)); + services.Configure(hostContext.Configuration.GetSection(LiveSettings.Position)); return services; } } diff --git a/src/BilibiliLiver/Program.cs b/src/BilibiliLiver/Program.cs index dd05461..574df1c 100644 --- a/src/BilibiliLiver/Program.cs +++ b/src/BilibiliLiver/Program.cs @@ -2,6 +2,8 @@ using BilibiliLiver.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using NLog.Web; using System; using System.Reflection; @@ -18,6 +20,14 @@ static void Main(string[] args) public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) + .ConfigureLogging(logging => + { + //移除已经注册的其他日志处理程序 + logging.ClearProviders(); + //设置最小的日志级别 + logging.SetMinimumLevel(LogLevel.Trace); + }) + .UseNLog() .ConfigureServices((hostContext, services) => { //配置初始化 diff --git a/src/BilibiliLiver/Properties/PublishProfiles/FolderProfile.pubxml b/src/BilibiliLiver/Properties/PublishProfiles/FolderProfile.pubxml index 1af32ef..9f277b7 100644 --- a/src/BilibiliLiver/Properties/PublishProfiles/FolderProfile.pubxml +++ b/src/BilibiliLiver/Properties/PublishProfiles/FolderProfile.pubxml @@ -10,8 +10,9 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem net6.0 true - linux-x64 + win-x64 true false + false \ No newline at end of file diff --git a/src/BilibiliLiver/Services/ConfigureService.cs b/src/BilibiliLiver/Services/ConfigureService.cs index 5b1317b..33ee0c5 100644 --- a/src/BilibiliLiver/Services/ConfigureService.cs +++ b/src/BilibiliLiver/Services/ConfigureService.cs @@ -19,14 +19,14 @@ public class ConfigureService : IHostedService private readonly IBilibiliLiveApiService _api; private readonly IBilibiliCookieService _cookie; private readonly IPushStreamService _push; - private readonly LiveSetting _liveSetting; + private readonly LiveSettings _liveSetting; public ConfigureService(ILogger logger , IBilibiliAccountService account , IBilibiliLiveApiService api , IBilibiliCookieService cookie , IPushStreamService push - , IOptions liveSettingOptions) + , IOptions liveSettingOptions) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _account = account ?? throw new ArgumentNullException(nameof(account)); diff --git a/src/BilibiliLiver/Services/PushStreamService.cs b/src/BilibiliLiver/Services/PushStreamService.cs index 7c72839..79eb6ef 100644 --- a/src/BilibiliLiver/Services/PushStreamService.cs +++ b/src/BilibiliLiver/Services/PushStreamService.cs @@ -22,7 +22,7 @@ public class PushStreamService : IPushStreamService private readonly ILogger _logger; private readonly IBilibiliAccountService _account; private readonly IBilibiliLiveApiService _api; - private readonly LiveSetting _liveSetting; + private readonly LiveSettings _liveSetting; private CancellationTokenSource _tokenSource; private Task _mainTask; @@ -30,7 +30,7 @@ public class PushStreamService : IPushStreamService public PushStreamService(ILogger logger , IBilibiliAccountService account , IBilibiliLiveApiService api - , IOptions liveSettingOptions) + , IOptions liveSettingOptions) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _account = account ?? throw new ArgumentNullException(nameof(account)); @@ -143,53 +143,55 @@ public async Task StopPush() /// private async Task InitLiveProcessStartInfo() { - try + //检查Cookie是否有效 + UserInfo userInfo = await _account.Login(); + if (userInfo == null || !userInfo.IsLogin) { - //检查Cookie是否有效 - UserInfo userInfo = await _account.Login(); - if (userInfo == null || !userInfo.IsLogin) - { - throw new Exception("登录失败,Cookie已失效"); - } - //获取直播间信息 - var liveRoomInfo = await _api.GetLiveRoomInfo(); - //开启直播 - StartLiveInfo startLiveInfo = await _api.StartLive(liveRoomInfo.room_id, _liveSetting.LiveAreaId); - string url = startLiveInfo.rtmp.addr + startLiveInfo.rtmp.code; - if (string.IsNullOrWhiteSpace(url)) - { - _logger.ThrowLogError("获取推流地址失败,请重试!"); - } - _logger.LogInformation($"获取推流地址成功,推流地址:{url}"); + throw new Exception("登录失败,Cookie已失效"); + } + //获取直播间信息 + var liveRoomInfo = await _api.GetLiveRoomInfo(); + if (liveRoomInfo.area_v2_id != _liveSetting.LiveAreaId) + { + await _api.UpdateLiveRoomArea(liveRoomInfo.room_id, _liveSetting.LiveAreaId); + } + if (liveRoomInfo.title != _liveSetting.LiveRoomName) + { + await _api.UpdateLiveRoomName(liveRoomInfo.room_id, _liveSetting.LiveRoomName); + } + //开启直播 + StartLiveInfo startLiveInfo = await _api.StartLive(liveRoomInfo.room_id, _liveSetting.LiveAreaId); + string url = startLiveInfo.rtmp.addr + startLiveInfo.rtmp.code; + if (string.IsNullOrWhiteSpace(url)) + { + throw new Exception("获取推流地址失败,请重试!"); + } + _logger.LogInformation($"获取推流地址成功,推流地址:{url}"); - string newCmd = _liveSetting.FFmpegCmd.Replace("[[URL]]", $"\"{url}\""); - int firstNullChar = newCmd.IndexOf((char)32); - if (firstNullChar < 0) - { - throw new Exception("无法获取命令执行名称,比如在命令ffmpeg -version中,无法获取ffmpeg。"); - } - string cmdName = newCmd.Substring(0, firstNullChar); - string cmdArgs = newCmd.Substring(firstNullChar); - if (string.IsNullOrEmpty(cmdArgs)) - { - throw new Exception("命令参数不能为空!"); - } - var psi = new ProcessStartInfo - { - FileName = cmdName, - Arguments = cmdArgs, - RedirectStandardOutput = true, - RedirectStandardError = false, - UseShellExecute = false, - CreateNoWindow = RuntimeInformation.IsOSPlatform(OSPlatform.Linux), - }; - return psi; + string newCmd = _liveSetting.FFmpegCmd + .Trim('\r', '\n', ' ') + .Replace("[[URL]]", $"\"{url}\""); + int firstNullChar = newCmd.IndexOf((char)32); + if (firstNullChar < 0) + { + throw new Exception("无法获取命令执行名称,比如在命令ffmpeg -version中,无法获取ffmpeg。"); } - catch (Exception ex) + string cmdName = newCmd.Substring(0, firstNullChar); + string cmdArgs = newCmd.Substring(firstNullChar); + if (string.IsNullOrEmpty(cmdArgs)) { - _logger.LogError(ex, "初始化直播参数时遇到错误。"); - return null; + throw new Exception("命令参数不能为空!"); } + var psi = new ProcessStartInfo + { + FileName = cmdName, + Arguments = cmdArgs, + RedirectStandardOutput = true, + RedirectStandardError = false, + UseShellExecute = false, + CreateNoWindow = RuntimeInformation.IsOSPlatform(OSPlatform.Linux), + }; + return psi; } /// @@ -198,9 +200,7 @@ private async Task InitLiveProcessStartInfo() /// private async Task PushStream() { - ProcessStartInfo psi = null; bool isAutoRestart = true; - while (isAutoRestart && !_tokenSource.IsCancellationRequested) { try @@ -213,12 +213,8 @@ private async Task PushStream() await Task.Delay(10000, _tokenSource.Token); } //start live - psi = await InitLiveProcessStartInfo(); - if (psi == null) - { - throw new Exception($"初始化直播参数失败。"); - } - _logger.LogInformation("正在初始化推流指令..."); + ProcessStartInfo psi = await InitLiveProcessStartInfo(); + _logger.LogInformation("推流参数初始化完成,即将开始推流..."); //启动 using (var proc = Process.Start(psi)) { @@ -233,20 +229,13 @@ private async Task PushStream() await Task.Delay(100); if (!_tokenSource.IsCancellationRequested) { - if (isAutoRestart) - { - _logger.LogWarning($"FFmpeg异常退出,将在60秒后重试推流。"); - } - else - { - _logger.LogWarning($"FFmpeg异常退出。"); - } + _logger.LogWarning($"FFmpeg异常退出。"); } } + //如果开启了自动重试 if (isAutoRestart && !_tokenSource.IsCancellationRequested) { - _logger.LogWarning($"等待重新推流..."); - //如果开启了自动重试,那么等待60s后再次尝试 + _logger.LogWarning($"等待60s后重新推流..."); await Task.Delay(60000, _tokenSource.Token); } } @@ -257,12 +246,15 @@ private async Task PushStream() catch (Exception ex) { _logger.LogError(ex, $"推流过程中发生错误,{ex.Message}"); - _logger.LogWarning($"等待60s后重新推流..."); - - //如果开启了自动重试,那么等待60s后再次尝试 - await Task.Delay(60000, _tokenSource.Token); + //如果开启了自动重试 + if (isAutoRestart && !_tokenSource.IsCancellationRequested) + { + _logger.LogWarning($"等待60s后重新推流..."); + await Task.Delay(60000, _tokenSource.Token); + } } } } + } } diff --git a/src/BilibiliLiver/appsettings.json b/src/BilibiliLiver/appsettings.json index 33ddf81..13b25e6 100644 --- a/src/BilibiliLiver/appsettings.json +++ b/src/BilibiliLiver/appsettings.json @@ -1,5 +1,5 @@ { - "LiveSetting": { + "LiveSettings": { //直播间分类 "LiveAreaId": 369, //直播间名称 @@ -8,7 +8,9 @@ //填写到此处时,请注意将命令中‘"’用‘\’进行转义,将推流的rtmp连接替换为[[URL]],[[URL]]不需要双引号。 //下面推流指令默认适配设备树莓派,使用USB摄像头,设备为/dev/video0 "FFmpegCmd": "ffmpeg -thread_queue_size 1024 -f v4l2 -s 1280*720 -input_format mjpeg -i \"/dev/video0\" -stream_loop -1 -i \"data/demo_music.m4a\" -vcodec h264_omx -pix_fmt yuv420p -r 30 -s 1280*720 -g 60 -b:v 10M -bufsize 10M -acodec aac -ac 2 -ar 44100 -ab 128k -f flv [[URL]]", - //ffmpeg异常退出后,是否自动重新启动 - "AutoRestart": true + //ffmpeg异常退出后,是否自动重新推流 + "AutoRestart": true, + //重新尝试推流失败后多久后退出,避免不断请求B站API,开启自动重试有效 + "RepushFailedExitMinutes": 30 } } \ No newline at end of file diff --git a/src/BilibiliLiver/nlog.config b/src/BilibiliLiver/nlog.config new file mode 100644 index 0000000..d121377 --- /dev/null +++ b/src/BilibiliLiver/nlog.config @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +