diff --git a/lib/screens/livetv/tabs/guide_tab.dart b/lib/screens/livetv/tabs/guide_tab.dart index 37fc6f94..39e88903 100644 --- a/lib/screens/livetv/tabs/guide_tab.dart +++ b/lib/screens/livetv/tabs/guide_tab.dart @@ -309,19 +309,34 @@ class GuideTabState extends State with MountedSetStateMixin { try { final grabs = await client.liveTv.fetchScheduledRecordings(); for (final grab in grabs) { - if (!_isActiveScheduledGrab(grab)) continue; - final program = grab.program; - if (program == null) continue; - keys.addAll(_recordingKeysForProgram(program, fallbackServerId: serverId)); + _addRecordingKeysForGrab(grab, serverId: serverId, keys: keys); } } catch (e) { appLogger.d('Failed to load scheduled recordings for $serverId', error: e); } + + try { + final rules = await client.liveTv.fetchRecordingRules(includeGrabs: true, includeStorage: false); + for (final rule in rules) { + for (final grab in rule.grabOperations) { + _addRecordingKeysForGrab(grab, serverId: serverId, keys: keys); + } + } + } catch (e) { + appLogger.d('Failed to load active recording grabs for $serverId', error: e); + } + } + + void _addRecordingKeysForGrab(MediaGrabOperation grab, {required String serverId, required Set keys}) { + if (!_isActiveScheduledGrab(grab)) return; + final program = grab.program; + if (program == null) return; + keys.addAll(_recordingKeysForProgram(program, fallbackServerId: serverId)); } bool _isActiveScheduledGrab(MediaGrabOperation grab) { - final status = grab.status?.toLowerCase(); - return status == null || status.isEmpty || status == 'scheduled' || status == 'grabbing'; + final status = grab.status?.trim().toLowerCase(); + return status == null || status.isEmpty || status == 'scheduled' || status == 'grabbing' || status == 'recording'; } bool _isRecordingScheduled(LiveTvProgram program) { diff --git a/test/services/plex_live_tv_support_test.dart b/test/services/plex_live_tv_support_test.dart index 2a83d5f3..b783f554 100644 --- a/test/services/plex_live_tv_support_test.dart +++ b/test/services/plex_live_tv_support_test.dart @@ -237,6 +237,50 @@ void main() { expect(updated?.key, '18'); }); + test('recording rules parse active grab operation metadata', () async { + late http.Request captured; + final client = makeClient((request) async { + captured = request; + return jsonResponse({ + 'MediaContainer': { + 'MediaSubscription': [ + { + 'key': '18', + 'type': 4, + 'title': 'Episode', + 'MediaGrabOperation': [ + { + 'id': 'grab-active', + 'status': 'grabbing', + 'Metadata': { + 'title': 'Live Episode', + 'ratingKey': 'episode-1', + 'guid': 'plex://episode/1', + 'Media': [ + {'beginsAt': '1466060400', 'endsAt': '1466062200', 'channelIdentifier': '004'}, + ], + }, + }, + ], + }, + ], + }, + }); + }); + addTearDown(client.close); + + final rules = await client.liveTv.fetchRecordingRules(includeGrabs: true, includeStorage: false); + + expect(captured.url.path, '/media/subscriptions'); + expect(captured.url.queryParameters['includeGrabs'], '1'); + expect(captured.url.queryParameters['includeStorage'], '0'); + final grab = rules.single.grabOperations.single; + expect(grab.status, 'grabbing'); + expect(grab.program?.ratingKey, 'episode-1'); + expect(grab.program?.guid, 'plex://episode/1'); + expect(grab.program?.channelIdentifier, '004'); + }); + test('cancelGrab deletes the operation key path', () async { late http.Request captured; final client = makeClient((request) async {