diff --git a/src/LiveRecorder.Infrastructure/Platforms/Douyin/DouyinHttpClient.cs b/src/LiveRecorder.Infrastructure/Platforms/Douyin/DouyinHttpClient.cs index c0c2e67..3814897 100644 --- a/src/LiveRecorder.Infrastructure/Platforms/Douyin/DouyinHttpClient.cs +++ b/src/LiveRecorder.Infrastructure/Platforms/Douyin/DouyinHttpClient.cs @@ -61,6 +61,10 @@ public sealed class DouyinHttpClient "\"roomStore\":{[\\s\\S]*?\"roomInfo\":{[\\s\\S]*?\"anchor\":{[\\s\\S]*?\"nickname\":\"(?[\\s\\S]*?)\"", RegexOptions.Compiled | RegexOptions.CultureInvariant); + private static readonly Regex HtmlStatusRegex = new( + "\"roomStore\":{[\\s\\S]*?\"roomInfo\":{[\\s\\S]*?\"room\":{[\\s\\S]*?\"status\":(?\\d+)", + RegexOptions.Compiled | RegexOptions.CultureInvariant); + private readonly PlatformHttpClientFactory _platformHttpClientFactory; private readonly IServiceScopeFactory _serviceScopeFactory; private readonly DouyinXBogusSigner _xBogusSigner; @@ -129,6 +133,23 @@ public sealed class DouyinHttpClient return await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken); } + public static int? TryExtractLiveStatusIntFromHtml(string? html) + { + var normalized = TryExtractNormalizedStatePayload(html); + if (string.IsNullOrWhiteSpace(normalized)) + { + return null; + } + + var match = HtmlStatusRegex.Match(normalized); + if (match.Success && int.TryParse(match.Groups["status"].Value, out var status)) + { + return status; + } + + return null; + } + public async Task GetStreamInputHeadersAsync(string roomId, CancellationToken cancellationToken = default) { var settings = await GetSystemSettingsAsync(cancellationToken); diff --git a/src/LiveRecorder.Infrastructure/Platforms/Douyin/DouyinLivePlatformAdapter.cs b/src/LiveRecorder.Infrastructure/Platforms/Douyin/DouyinLivePlatformAdapter.cs index 7c85022..1e17b20 100644 --- a/src/LiveRecorder.Infrastructure/Platforms/Douyin/DouyinLivePlatformAdapter.cs +++ b/src/LiveRecorder.Infrastructure/Platforms/Douyin/DouyinLivePlatformAdapter.cs @@ -103,9 +103,35 @@ public sealed class DouyinLivePlatformAdapter : ILivePlatformAdapter var coverUrl = GetCoverUrl(room); var isLive = statusCode == 2 || GetInt(room, "live_status") is 1 or 2; + if (!isLive) + { + var pageStatus = await TryDetectLiveStatusFromPageAsync(roomId, cancellationToken); + if (pageStatus is 1 or 2) + { + isLive = true; + statusCode ??= pageStatus; + } + } + return new LiveStatusSnapshot(isLive, title, anchorName, anchorId, avatarUrl, coverUrl, statusCode, statusCode?.ToString()); } + private async Task TryDetectLiveStatusFromPageAsync(string roomId, CancellationToken cancellationToken) + { + try + { + var (_, html) = await _douyinHttpClient.ResolvePageAsync( + $"https://live.douyin.com/{roomId}", + cancellationToken); + + return DouyinHttpClient.TryExtractLiveStatusIntFromHtml(html); + } + catch + { + return null; + } + } + public async Task GetStreamUrlAsync( string roomId, string? preferredQuality = null,