fix(api): resolve mirror channel now playing state

This commit is contained in:
2026-07-02 20:02:01 +02:00
parent f01ffc6254
commit 07e9d9838d
2 changed files with 84 additions and 8 deletions
@@ -25,7 +25,27 @@ public class GetChannelStatesForApiHandler(
.ThenBy(c => c.Number)
.ToListAsync(cancellationToken);
int[] channelIds = channels.Map(c => c.Id).ToArray();
if (channels.Count == 0)
{
return [];
}
Dictionary<int, (int SourceChannelId, TimeSpan Offset, DateTime LookupTime)> channelLookup = channels
.ToDictionary(
c => c.Id,
c =>
{
TimeSpan offset = c.PlayoutOffset ?? TimeSpan.Zero;
return (c.MirrorSourceChannelId ?? c.Id, offset, now - offset);
});
int[] sourceChannelIds = channelLookup.Values
.Map(v => v.SourceChannelId)
.Distinct()
.ToArray();
DateTime minLookupTime = channelLookup.Values.Min(v => v.LookupTime);
DateTime maxLookupTime = channelLookup.Values.Max(v => v.LookupTime);
List<PlayoutItem> currentItems = await dbContext.PlayoutItems
.AsNoTracking()
@@ -51,26 +71,32 @@ public class GetChannelStatesForApiHandler(
.ThenInclude(mi => (mi as Image).ImageMetadata)
.Include(pi => pi.MediaItem)
.ThenInclude(mi => (mi as RemoteStream).RemoteStreamMetadata)
.Where(pi => channelIds.Contains(pi.Playout.ChannelId))
.Where(pi => pi.Start <= now && pi.Finish > now)
.Where(pi => sourceChannelIds.Contains(pi.Playout.ChannelId))
.Where(pi => pi.Start <= maxLookupTime && pi.Finish > minLookupTime)
.OrderBy(pi => pi.Start)
.ToListAsync(cancellationToken);
Dictionary<int, PlayoutItem> currentItemsByChannelId = currentItems
Dictionary<int, List<PlayoutItem>> currentItemsBySourceChannelId = currentItems
.GroupBy(pi => pi.Playout.ChannelId)
.ToDictionary(g => g.Key, g => g.First());
.ToDictionary(g => g.Key, g => g.OrderBy(pi => pi.Start).ToList());
return channels
.Map(channel =>
{
currentItemsByChannelId.TryGetValue(channel.Id, out PlayoutItem playoutItem);
(int sourceChannelId, TimeSpan offset, DateTime lookupTime) = channelLookup[channel.Id];
PlayoutItem playoutItem = null;
if (currentItemsBySourceChannelId.TryGetValue(sourceChannelId, out List<PlayoutItem> candidates))
{
playoutItem = candidates.FirstOrDefault(pi => pi.Start <= lookupTime && pi.Finish > lookupTime);
}
ChannelNowPlayingResponseModel nowPlaying = playoutItem is null
? null
: new ChannelNowPlayingResponseModel(
PlayoutMapper.GetDisplayTitle(playoutItem.MediaItem, Optional(playoutItem.ChapterTitle)),
new DateTimeOffset(playoutItem.Start, TimeSpan.Zero),
new DateTimeOffset(playoutItem.Finish, TimeSpan.Zero));
new DateTimeOffset(playoutItem.Start + offset, TimeSpan.Zero),
new DateTimeOffset(playoutItem.Finish + offset, TimeSpan.Zero));
return new ChannelStateResponseModel(
channel.Id,
@@ -105,6 +105,56 @@ public class GetChannelStatesForApiHandlerTests
state.NowPlaying.Title.ShouldBe("Live Source");
}
[Test]
public async Task Handle_Should_Resolve_Mirror_Channel_NowPlaying_From_Source_With_Offset()
{
TimeSpan offset = TimeSpan.FromHours(1);
DateTime sourceStart = DateTime.UtcNow.AddMinutes(-70);
DateTime sourceFinish = DateTime.UtcNow.AddMinutes(-40);
await using TvContext context = _db.CreateContext();
Channel source = MakeChannel(10, "10");
Channel mirror = MakeChannel(11, "11");
mirror.PlayoutSource = ChannelPlayoutSource.Mirror;
mirror.MirrorSourceChannelId = source.Id;
mirror.PlayoutOffset = offset;
var movie = new Movie
{
Id = 110,
MovieMetadata = [new MovieMetadata { Title = "Offset Feature" }]
};
var playout = new Playout { Id = 111, Channel = source, ChannelId = source.Id, Items = [] };
var item = new PlayoutItem
{
Id = 112,
MediaItem = movie,
MediaItemId = movie.Id,
Playout = playout,
PlayoutId = playout.Id,
Start = sourceStart,
Finish = sourceFinish,
ChapterTitle = string.Empty
};
context.Channels.AddRange(source, mirror);
context.Movies.Add(movie);
context.Playouts.Add(playout);
context.PlayoutItems.Add(item);
await context.SaveChangesAsync();
var handler = new GetChannelStatesForApiHandler(_db.Factory, _segmenter);
List<ChannelStateResponseModel> result =
await handler.Handle(new GetChannelStatesForApi(), CancellationToken.None);
ChannelStateResponseModel mirrorState = result.Single(s => s.ChannelId == mirror.Id);
mirrorState.NowPlaying.ShouldNotBeNull();
mirrorState.NowPlaying.Title.ShouldBe("Offset Feature");
mirrorState.NowPlaying.StartUtc.ShouldBe(new DateTimeOffset(sourceStart + offset, TimeSpan.Zero));
mirrorState.NowPlaying.FinishUtc.ShouldBe(new DateTimeOffset(sourceFinish + offset, TimeSpan.Zero));
}
private async Task SeedChannelWithMovie(DateTime start, DateTime finish)
{
await using TvContext context = _db.CreateContext();