From 2e429840d2d0440eeff517ca83485b6d31285095 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Fri, 17 Apr 2026 03:34:51 +0200 Subject: [PATCH] fix: fan out multi-airing EPG metadata close #563 --- lib/models/livetv_program.dart | 34 ++++++++++++++++++++++++++-------- lib/services/plex_client.dart | 12 +++++++++++- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/lib/models/livetv_program.dart b/lib/models/livetv_program.dart index df826b96..83d8ed33 100644 --- a/lib/models/livetv_program.dart +++ b/lib/models/livetv_program.dart @@ -44,11 +44,30 @@ class LiveTvProgram { this.premiere, }); - factory LiveTvProgram.fromJson(Map json) { - // Grid endpoint nests timing/channel info inside Media[0] and Channel[0] - final media = (json['Media'] as List?)?.firstOrNull as Map?; + factory LiveTvProgram.fromJson( + Map json, { + Map? mediaOverride, + }) { + // Grid endpoint nests timing/channel info inside Media[] and Channel[]. + // When mediaOverride is supplied, the caller is pinning this parse to a + // specific airing (one Media entry); treat it as authoritative for + // begin/end/channel fields. + final hasOverride = mediaOverride != null; + final media = mediaOverride ?? (json['Media'] as List?)?.firstOrNull as Map?; final channel = (json['Channel'] as List?)?.firstOrNull as Map?; + int? pickInt(String key) { + final fromMedia = (media?[key] as num?)?.toInt(); + final fromJson = (json[key] as num?)?.toInt(); + return hasOverride ? (fromMedia ?? fromJson) : (fromJson ?? fromMedia); + } + + String? pickString(String key) { + final fromMedia = media?[key]?.toString(); + final fromJson = json[key] as String?; + return hasOverride ? (fromMedia ?? fromJson) : (fromJson ?? fromMedia); + } + return LiveTvProgram( key: json['key'] as String?, ratingKey: json['ratingKey'] as String?, @@ -57,17 +76,16 @@ class LiveTvProgram { summary: json['summary'] as String?, type: json['type'] as String?, year: (json['year'] as num?)?.toInt(), - beginsAt: (json['beginsAt'] as num?)?.toInt() ?? (media?['beginsAt'] as num?)?.toInt(), - endsAt: (json['endsAt'] as num?)?.toInt() ?? (media?['endsAt'] as num?)?.toInt(), + beginsAt: pickInt('beginsAt'), + endsAt: pickInt('endsAt'), grandparentTitle: json['grandparentTitle'] as String?, parentTitle: json['parentTitle'] as String?, index: (json['index'] as num?)?.toInt(), parentIndex: (json['parentIndex'] as num?)?.toInt(), thumb: json['thumb'] as String? ?? json['grandparentThumb'] as String?, art: json['art'] as String?, - channelIdentifier: - json['channelIdentifier'] as String? ?? media?['channelIdentifier']?.toString() ?? channel?['id']?.toString(), - channelCallSign: json['channelCallSign'] as String? ?? media?['channelCallSign'] as String?, + channelIdentifier: pickString('channelIdentifier') ?? channel?['id']?.toString(), + channelCallSign: pickString('channelCallSign'), live: flexibleBool(json['live']), premiere: flexibleBool(json['premiere']), ); diff --git a/lib/services/plex_client.dart b/lib/services/plex_client.dart index 22ed518f..9b64d359 100644 --- a/lib/services/plex_client.dart +++ b/lib/services/plex_client.dart @@ -2332,11 +2332,21 @@ class PlexClient { } /// Parse a list of JSON items into [LiveTvProgram] objects, skipping any that fail. + /// A single Metadata entry may carry multiple Media entries representing back-to-back + /// airings of the same program on the same channel; emit one program per airing. List _parseLiveTvPrograms(List items) { final programs = []; for (final item in items) { try { - programs.add(LiveTvProgram.fromJson(item as Map)); + final map = item as Map; + final mediaList = (map['Media'] as List?)?.whereType>().toList(); + if (mediaList != null && mediaList.length > 1) { + for (final media in mediaList) { + programs.add(LiveTvProgram.fromJson(map, mediaOverride: media)); + } + } else { + programs.add(LiveTvProgram.fromJson(map)); + } } catch (_) {} } return programs;