Enhance configuration and routing for streaming features: update .env.example and appsettings.json to include new scheduler and streaming options, add streaming endpoint mapping in Program.cs, and integrate streaming services in DependencyInjection. Introduce segment path resolution in MediaPathResolver for asset management.
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
using LiteCqrs;
|
||||
using TeleWave.Application.Common.Models;
|
||||
|
||||
namespace TeleWave.Application.Streaming.GetLivePlaylist;
|
||||
|
||||
public sealed record GetLivePlaylistQuery(string Slug, DateTimeOffset Now)
|
||||
: IQuery<Result<LivePlaylistDto>>;
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
using LiteCqrs;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using TeleWave.Application.Broadcast;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Application.Common.Models;
|
||||
using TeleWave.Domain.Broadcast.Live;
|
||||
using TeleWave.Domain.Media;
|
||||
|
||||
namespace TeleWave.Application.Streaming.GetLivePlaylist;
|
||||
|
||||
public sealed class GetLivePlaylistQueryHandler(
|
||||
IAppDbContext dbContext,
|
||||
IOptions<StreamingOptions> options
|
||||
) : IQueryHandler<GetLivePlaylistQuery, Result<LivePlaylistDto>>
|
||||
{
|
||||
private readonly StreamingOptions _options = options.Value;
|
||||
|
||||
public async Task<Result<LivePlaylistDto>> Handle(
|
||||
GetLivePlaylistQuery query,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var channel = await dbContext.Channels.AsNoTracking()
|
||||
.Where(c => c.Slug == query.Slug && c.IsEnabled)
|
||||
.Select(c => new { c.Id, c.EpochUtc, c.FillerAssetId })
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
if (channel is null)
|
||||
return Result.Failure<LivePlaylistDto>(ChannelErrors.NotFound);
|
||||
|
||||
var seg = _options.SegmentSeconds;
|
||||
var window = _options.LiveWindowSegments;
|
||||
|
||||
// Загружаем записи, пересекающиеся с окном (с небольшим запасом назад).
|
||||
var windowStartTime = query.Now.AddSeconds(-(window + 2) * seg);
|
||||
var rawEntries = await dbContext.ScheduleEntries.AsNoTracking()
|
||||
.Where(e =>
|
||||
e.ChannelId == channel.Id
|
||||
&& e.StartsAtUtc < query.Now
|
||||
&& e.EndsAtUtc > windowStartTime
|
||||
)
|
||||
.OrderBy(e => e.StartsAtUtc)
|
||||
.Select(e => new
|
||||
{
|
||||
e.StartsAtUtc,
|
||||
e.EndsAtUtc,
|
||||
e.MediaAssetId,
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var assetIds = rawEntries.Select(e => e.MediaAssetId).ToList();
|
||||
if (channel.FillerAssetId is { } fillerId)
|
||||
assetIds.Add(fillerId);
|
||||
|
||||
var segmentCounts = await dbContext.MediaAssets.AsNoTracking()
|
||||
.Where(a =>
|
||||
assetIds.Contains(a.Id)
|
||||
&& a.Status == MediaAssetStatus.Ready
|
||||
&& a.SegmentCount != null
|
||||
)
|
||||
.Select(a => new { a.Id, a.SegmentCount })
|
||||
.ToDictionaryAsync(a => a.Id, a => a.SegmentCount!.Value, cancellationToken);
|
||||
|
||||
var entries = rawEntries
|
||||
.Where(e => segmentCounts.ContainsKey(e.MediaAssetId))
|
||||
.Select(e => new LiveEntry(
|
||||
e.StartsAtUtc,
|
||||
e.EndsAtUtc,
|
||||
e.MediaAssetId,
|
||||
segmentCounts[e.MediaAssetId]
|
||||
))
|
||||
.ToList();
|
||||
|
||||
LiveFiller? filler = null;
|
||||
if (channel.FillerAssetId is { } fid && segmentCounts.TryGetValue(fid, out var fillerSegments))
|
||||
filler = new LiveFiller(fid, fillerSegments);
|
||||
|
||||
var playlist = LiveWindowCalculator.Build(
|
||||
new LiveInput(query.Now, channel.EpochUtc, seg, window, entries, filler)
|
||||
);
|
||||
|
||||
var dto = new LivePlaylistDto(
|
||||
playlist.MediaSequence,
|
||||
playlist.TargetDuration,
|
||||
playlist.Segments
|
||||
.Select(s => new LiveSegmentDto(s.AssetId, s.LocalIndex, s.Discontinuity))
|
||||
.ToList()
|
||||
);
|
||||
return Result.Success(dto);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user