mirror of
https://github.com/BililiveRecorder/BililiveRecorder.git
synced 2024-11-16 11:42:22 +08:00
支持录制画质选择
This commit is contained in:
parent
f0f45bab39
commit
2b482a9c4b
|
@ -110,12 +110,12 @@ namespace BililiveRecorder.Core.Api.Http
|
||||||
return FetchAsync<UserInfo>(this.mainClient, url);
|
return FetchAsync<UserInfo>(this.mainClient, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<BilibiliApiResponse<RoomPlayInfo>> GetStreamUrlAsync(int roomid)
|
public Task<BilibiliApiResponse<RoomPlayInfo>> GetStreamUrlAsync(int roomid, int qn)
|
||||||
{
|
{
|
||||||
if (this.disposedValue)
|
if (this.disposedValue)
|
||||||
throw new ObjectDisposedException(nameof(HttpApiClient));
|
throw new ObjectDisposedException(nameof(HttpApiClient));
|
||||||
|
|
||||||
var url = $@"{this.config.LiveApiHost}/xlive/web-room/v2/index/getRoomPlayInfo?room_id={roomid}&protocol=0%2C1&format=0%2C2&codec=0%2C1&qn=10000&platform=web&ptype=16";
|
var url = $@"{this.config.LiveApiHost}/xlive/web-room/v2/index/getRoomPlayInfo?room_id={roomid}&protocol=0,1&format=0,1,2&codec=0,1&qn={qn}&platform=web&ptype=8";
|
||||||
return FetchAsync<RoomPlayInfo>(this.mainClient, url);
|
return FetchAsync<RoomPlayInfo>(this.mainClient, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,6 @@ namespace BililiveRecorder.Core.Api
|
||||||
{
|
{
|
||||||
Task<BilibiliApiResponse<RoomInfo>> GetRoomInfoAsync(int roomid);
|
Task<BilibiliApiResponse<RoomInfo>> GetRoomInfoAsync(int roomid);
|
||||||
Task<BilibiliApiResponse<UserInfo>> GetUserInfoAsync(int roomid);
|
Task<BilibiliApiResponse<UserInfo>> GetUserInfoAsync(int roomid);
|
||||||
Task<BilibiliApiResponse<RoomPlayInfo>> GetStreamUrlAsync(int roomid);
|
Task<BilibiliApiResponse<RoomPlayInfo>> GetStreamUrlAsync(int roomid, int qn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
26
BililiveRecorder.Core/Api/IApiClientExtensions.cs
Normal file
26
BililiveRecorder.Core/Api/IApiClientExtensions.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static BililiveRecorder.Core.Api.Model.RoomPlayInfo;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Core.Api
|
||||||
|
{
|
||||||
|
public static class IApiClientExtensions
|
||||||
|
{
|
||||||
|
public static async Task<CodecItem?> GetCodecItemInStreamUrlAsync(this IApiClient apiClient, int roomid, int qn)
|
||||||
|
{
|
||||||
|
var apiResp = await apiClient.GetStreamUrlAsync(roomid: roomid, qn: qn).ConfigureAwait(false);
|
||||||
|
var url_data = apiResp?.Data?.PlayurlInfo?.Playurl?.Streams;
|
||||||
|
|
||||||
|
if (url_data is null)
|
||||||
|
throw new Exception("playurl is null");
|
||||||
|
|
||||||
|
var url_http_stream_flv_avc =
|
||||||
|
url_data.FirstOrDefault(x => x.ProtocolName == "http_stream")
|
||||||
|
?.Formats?.FirstOrDefault(x => x.FormatName == "flv")
|
||||||
|
?.Codecs?.FirstOrDefault(x => x.CodecName == "avc");
|
||||||
|
|
||||||
|
return url_http_stream_flv_avc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,9 +27,9 @@ namespace BililiveRecorder.Core.Api
|
||||||
.ExecuteAsync(_ => this.client.GetRoomInfoAsync(roomid), new Context(PolicyNames.CacheKeyRoomInfo + ":" + roomid))
|
.ExecuteAsync(_ => this.client.GetRoomInfoAsync(roomid), new Context(PolicyNames.CacheKeyRoomInfo + ":" + roomid))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
public async Task<BilibiliApiResponse<RoomPlayInfo>> GetStreamUrlAsync(int roomid) => await this.policies
|
public async Task<BilibiliApiResponse<RoomPlayInfo>> GetStreamUrlAsync(int roomid, int qn) => await this.policies
|
||||||
.Get<IAsyncPolicy>(PolicyNames.PolicyStreamApiRequestAsync)
|
.Get<IAsyncPolicy>(PolicyNames.PolicyStreamApiRequestAsync)
|
||||||
.ExecuteAsync(_ => this.client.GetStreamUrlAsync(roomid), new Context(PolicyNames.CacheKeyStream + ":" + roomid))
|
.ExecuteAsync(_ => this.client.GetStreamUrlAsync(roomid, qn), new Context(PolicyNames.CacheKeyStream + ":" + roomid + ":" + qn))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
public async Task<BilibiliApiResponse<UserInfo>> GetUserInfoAsync(int roomid) => await this.policies
|
public async Task<BilibiliApiResponse<UserInfo>> GetUserInfoAsync(int roomid) => await this.policies
|
||||||
|
|
|
@ -92,6 +92,14 @@ namespace BililiveRecorder.Core.Config.V2
|
||||||
[JsonProperty(nameof(RecordDanmakuGuard)), EditorBrowsable(EditorBrowsableState.Never)]
|
[JsonProperty(nameof(RecordDanmakuGuard)), EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
public Optional<bool> OptionalRecordDanmakuGuard { get => this.GetPropertyValueOptional<bool>(nameof(this.RecordDanmakuGuard)); set => this.SetPropertyValueOptional(value, nameof(this.RecordDanmakuGuard)); }
|
public Optional<bool> OptionalRecordDanmakuGuard { get => this.GetPropertyValueOptional<bool>(nameof(this.RecordDanmakuGuard)); set => this.SetPropertyValueOptional(value, nameof(this.RecordDanmakuGuard)); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 录制的直播画质 qn 值,逗号分割,靠前的优先
|
||||||
|
/// </summary>
|
||||||
|
public string RecordingQuality { get => this.GetPropertyValue<string>(); set => this.SetPropertyValue(value); }
|
||||||
|
public bool HasRecordingQuality { get => this.GetPropertyHasValue(nameof(this.RecordingQuality)); set => this.SetPropertyHasValue<string>(value, nameof(this.RecordingQuality)); }
|
||||||
|
[JsonProperty(nameof(RecordingQuality)), EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public Optional<string> OptionalRecordingQuality { get => this.GetPropertyValueOptional<string>(nameof(this.RecordingQuality)); set => this.SetPropertyValueOptional(value, nameof(this.RecordingQuality)); }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 录制断开重连时间间隔 毫秒
|
/// 录制断开重连时间间隔 毫秒
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -317,6 +325,14 @@ namespace BililiveRecorder.Core.Config.V2
|
||||||
[JsonProperty(nameof(RecordDanmakuGuard)), EditorBrowsable(EditorBrowsableState.Never)]
|
[JsonProperty(nameof(RecordDanmakuGuard)), EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
public Optional<bool> OptionalRecordDanmakuGuard { get => this.GetPropertyValueOptional<bool>(nameof(this.RecordDanmakuGuard)); set => this.SetPropertyValueOptional(value, nameof(this.RecordDanmakuGuard)); }
|
public Optional<bool> OptionalRecordDanmakuGuard { get => this.GetPropertyValueOptional<bool>(nameof(this.RecordDanmakuGuard)); set => this.SetPropertyValueOptional(value, nameof(this.RecordDanmakuGuard)); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 录制的直播画质 qn 值,逗号分割,靠前的优先
|
||||||
|
/// </summary>
|
||||||
|
public string RecordingQuality { get => this.GetPropertyValue<string>(); set => this.SetPropertyValue(value); }
|
||||||
|
public bool HasRecordingQuality { get => this.GetPropertyHasValue(nameof(this.RecordingQuality)); set => this.SetPropertyHasValue<string>(value, nameof(this.RecordingQuality)); }
|
||||||
|
[JsonProperty(nameof(RecordingQuality)), EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public Optional<string> OptionalRecordingQuality { get => this.GetPropertyValueOptional<string>(nameof(this.RecordingQuality)); set => this.SetPropertyValueOptional(value, nameof(this.RecordingQuality)); }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed partial class DefaultConfig
|
public sealed partial class DefaultConfig
|
||||||
|
@ -364,6 +380,8 @@ namespace BililiveRecorder.Core.Config.V2
|
||||||
|
|
||||||
public bool RecordDanmakuGuard => true;
|
public bool RecordDanmakuGuard => true;
|
||||||
|
|
||||||
|
public string RecordingQuality => "10000";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ module.exports = {
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"desc": "是否显示直播间标题和分区",
|
"desc": "是否显示直播间标题和分区",
|
||||||
"default": "true",
|
"default": "true",
|
||||||
}, ],
|
},],
|
||||||
"room": [{
|
"room": [{
|
||||||
"name": "RoomId",
|
"name": "RoomId",
|
||||||
"type": "int",
|
"type": "int",
|
||||||
|
@ -117,5 +117,10 @@ module.exports = {
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"desc": "是否同时录制 上船",
|
"desc": "是否同时录制 上船",
|
||||||
"default": "true"
|
"default": "true"
|
||||||
}, ]
|
}, {
|
||||||
|
"name": "RecordingQuality",
|
||||||
|
"type": "string",
|
||||||
|
"desc": "录制的直播画质 qn 值,逗号分割,靠前的优先",
|
||||||
|
"default": "\"10000\""
|
||||||
|
},]
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,6 +139,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"RecordingQuality": {
|
||||||
|
"description": "录制的直播画质 qn 值,逗号分割,靠前的优先",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"HasValue": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"Value": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "10000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"TimingStreamRetry": {
|
"TimingStreamRetry": {
|
||||||
"description": "录制断开重连时间间隔 毫秒",
|
"description": "录制断开重连时间间隔 毫秒",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -471,6 +486,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"RecordingQuality": {
|
||||||
|
"description": "录制的直播画质 qn 值,逗号分割,靠前的优先",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"HasValue": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"Value": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "10000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"RoomId": {
|
"RoomId": {
|
||||||
"description": "房间号",
|
"description": "房间号",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
|
@ -236,24 +236,56 @@ namespace BililiveRecorder.Core.Recording
|
||||||
|
|
||||||
protected async Task<string> FetchStreamUrlAsync(int roomid)
|
protected async Task<string> FetchStreamUrlAsync(int roomid)
|
||||||
{
|
{
|
||||||
var apiResp = await this.apiClient.GetStreamUrlAsync(roomid: roomid).ConfigureAwait(false);
|
const int DefaultQn = 10000;
|
||||||
var url_data = apiResp?.Data?.PlayurlInfo?.Playurl?.Streams;
|
var selected_qn = DefaultQn;
|
||||||
|
int[] qns;
|
||||||
|
Api.Model.RoomPlayInfo.UrlInfoItem[]? url_infos;
|
||||||
|
|
||||||
if (url_data is null)
|
var codecItem = await this.apiClient.GetCodecItemInStreamUrlAsync(roomid: roomid, qn: DefaultQn).ConfigureAwait(false);
|
||||||
throw new Exception("playurl is null");
|
|
||||||
|
|
||||||
var url_http_stream_flv_avc =
|
if (codecItem is null)
|
||||||
url_data.FirstOrDefault(x => x.ProtocolName == "http_stream")
|
throw new Exception("no supported stream url, qn: " + DefaultQn);
|
||||||
?.Formats?.FirstOrDefault(x => x.FormatName == "flv")
|
|
||||||
?.Codecs?.FirstOrDefault(x => x.CodecName == "avc");
|
|
||||||
|
|
||||||
if (url_http_stream_flv_avc is null)
|
{
|
||||||
throw new Exception("no supported stream url");
|
try
|
||||||
|
{
|
||||||
|
qns = this.room.RoomConfig.RecordingQuality.Split(new[] { ',', ',', '、', ' ' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(x => int.TryParse(x, out var num) ? num : -1)
|
||||||
|
.Where(x => x > 0)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
if (url_http_stream_flv_avc.CurrentQn != 10000)
|
foreach (var qn in qns)
|
||||||
this.logger.Warning("当前录制的画质是 {CurrentQn}", url_http_stream_flv_avc.CurrentQn);
|
{
|
||||||
|
if (codecItem.AcceptQn.Contains(qn))
|
||||||
|
{
|
||||||
|
selected_qn = qn;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var url_infos = url_http_stream_flv_avc.UrlInfos;
|
this.logger.Debug("设置画质 {QnSettings}, 可用画质 {AcceptQn}, 最终选择 {SelectedQn}", qns, codecItem.AcceptQn, selected_qn);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.Warning(ex, "判断录制画质时出错,将默认使用 原画(10000)");
|
||||||
|
qns = new[] { DefaultQn };
|
||||||
|
url_infos = codecItem.UrlInfos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected_qn != DefaultQn)
|
||||||
|
{
|
||||||
|
// 最终选择的 qn 与默认不同,需要重新请求一次
|
||||||
|
codecItem = await this.apiClient.GetCodecItemInStreamUrlAsync(roomid: roomid, qn: selected_qn).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (codecItem is null)
|
||||||
|
throw new Exception("no supported stream url, qn: " + DefaultQn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codecItem.CurrentQn != selected_qn || !qns.Contains(codecItem.CurrentQn))
|
||||||
|
this.logger.Warning("当前录制的画质是 {CurrentQn}", codecItem.CurrentQn);
|
||||||
|
|
||||||
|
url_infos = codecItem.UrlInfos;
|
||||||
if (url_infos is null || url_infos.Length == 0)
|
if (url_infos is null || url_infos.Length == 0)
|
||||||
throw new Exception("no url_info");
|
throw new Exception("no url_info");
|
||||||
|
|
||||||
|
@ -264,7 +296,7 @@ namespace BililiveRecorder.Core.Recording
|
||||||
? url_infos_without_mcdn[this.random.Next(url_infos_without_mcdn.Length)]
|
? url_infos_without_mcdn[this.random.Next(url_infos_without_mcdn.Length)]
|
||||||
: url_infos[this.random.Next(url_infos.Length)];
|
: url_infos[this.random.Next(url_infos.Length)];
|
||||||
|
|
||||||
var fullUrl = url_info.Host + url_http_stream_flv_avc.BaseUrl + url_info.Extra;
|
var fullUrl = url_info.Host + codecItem.BaseUrl + url_info.Extra;
|
||||||
return fullUrl;
|
return fullUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,15 @@
|
||||||
</local:SettingWithDefault>
|
</local:SettingWithDefault>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
|
<GroupBox Header="录制画质">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="逗号分割的录制画质ID"/>
|
||||||
|
<local:SettingWithDefault IsSettingNotUsingDefault="{Binding HasRecordingQuality}">
|
||||||
|
<TextBox Text="{Binding RecordingQuality,UpdateSourceTrigger=PropertyChanged,Delay=1000}"
|
||||||
|
ui:TextBoxHelper.IsDeleteButtonVisible="False" Width="220" HorizontalAlignment="Left"/>
|
||||||
|
</local:SettingWithDefault>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
</ui:SimpleStackPanel>
|
</ui:SimpleStackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -99,6 +99,15 @@
|
||||||
Text="{Binding WebHookUrls,UpdateSourceTrigger=PropertyChanged,Delay=1000}" ui:TextBoxHelper.IsDeleteButtonVisible="False"/>
|
Text="{Binding WebHookUrls,UpdateSourceTrigger=PropertyChanged,Delay=1000}" ui:TextBoxHelper.IsDeleteButtonVisible="False"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
|
<GroupBox Header="录制画质">
|
||||||
|
<StackPanel MaxWidth="400" HorizontalAlignment="Left">
|
||||||
|
<TextBlock Text="逗号分割的录制画质ID"/>
|
||||||
|
<c:SettingWithDefault IsSettingNotUsingDefault="{Binding HasRecordingQuality}">
|
||||||
|
<TextBox Text="{Binding RecordingQuality,UpdateSourceTrigger=PropertyChanged,Delay=1000}"
|
||||||
|
ui:TextBoxHelper.IsDeleteButtonVisible="False" Width="220" HorizontalAlignment="Left"/>
|
||||||
|
</c:SettingWithDefault>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
</ui:SimpleStackPanel>
|
</ui:SimpleStackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</ui:Page>
|
</ui:Page>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user