From 9819c4f495c873096e8d3872add05b023496f692 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sat, 21 Feb 2026 05:34:06 +0100 Subject: [PATCH] fix: move hub/onDeck API processing to isolates --- lib/services/plex_client.dart | 160 ++++++++++++++++------------------ 1 file changed, 75 insertions(+), 85 deletions(-) diff --git a/lib/services/plex_client.dart b/lib/services/plex_client.dart index f8f25c79..cdf33a56 100644 --- a/lib/services/plex_client.dart +++ b/lib/services/plex_client.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:isolate'; import 'dart:math'; import 'dart:ui' show VoidCallback; @@ -33,6 +34,61 @@ import '../utils/plex_url_helper.dart'; import '../utils/watch_state_notifier.dart'; import 'plex_api_cache.dart'; +/// Process hub JSON response in an isolate. +/// Top-level function so it can be passed to [Isolate.run]. +List _processHubResponse(String jsonStr, String serverId, String? serverName) { + final decoded = jsonDecode(jsonStr) as Map; + final container = decoded['MediaContainer'] as Map?; + if (container == null || container['Hub'] == null) return []; + + final hubs = []; + for (final hubJson in container['Hub'] as List) { + try { + final hub = PlexHub.fromJson(hubJson as Map); + if (hub.items.isEmpty) continue; + + final videoItems = hub.items + .where((item) => item.isVideoContent) + .map((item) => item.copyWith(serverId: serverId, serverName: serverName)) + .toList(); + + if (videoItems.isNotEmpty) { + hubs.add( + PlexHub( + hubKey: hub.hubKey, + title: hub.title, + type: hub.type, + hubIdentifier: hub.hubIdentifier, + size: hub.size, + more: hub.more, + items: videoItems, + serverId: serverId, + serverName: serverName, + ), + ); + } + } catch (_) { + // Skip hubs that fail to parse + } + } + return hubs; +} + +/// Process on-deck JSON response in an isolate. +/// Top-level function so it can be passed to [Isolate.run]. +List _processOnDeckResponse(String jsonStr, String serverId, String? serverName) { + final decoded = jsonDecode(jsonStr) as Map; + final container = decoded['MediaContainer'] as Map?; + if (container == null || container['Metadata'] == null) return []; + + final allItems = (container['Metadata'] as List) + .map((json) => PlexMetadata.fromJsonWithImages(json as Map) + .copyWith(serverId: serverId, serverName: serverName)) + .toList(); + + return allItems.where((item) => !item.isMusicContent).toList(); +} + /// Constants for Plex stream types class PlexStreamType { static const int video = 1; @@ -702,17 +758,13 @@ class PlexClient { /// Get on deck items (continue watching, filtered to video content only) Future> getOnDeck() async { - final response = await _dio.get('/library/onDeck'); - final container = _getMediaContainer(response); - if (container != null && container['Metadata'] != null) { - final allItems = (container['Metadata'] as List) - .map((json) => _tagMetadata(PlexMetadata.fromJsonWithImages(json))) - .toList(); - - // Filter out music content (artists, albums, tracks) - return allItems.where((item) => !item.isMusicContent).toList(); - } - return []; + final response = await _dio.get( + '/library/onDeck', + options: Options(responseType: ResponseType.plain), + ); + final sid = serverId; + final sname = serverName; + return Isolate.run(() => _processOnDeckResponse(response.data as String, sid, sname)); } /// Get on deck items filtered by a specific library section @@ -1305,44 +1357,11 @@ class PlexClient { final response = await _dio.get( '/hubs/sections/$sectionId', queryParameters: {'count': limit, 'includeGuids': 1}, + options: Options(responseType: ResponseType.plain), ); - - final container = _getMediaContainer(response); - if (container != null && container['Hub'] != null) { - final hubs = []; - for (final hubJson in container['Hub'] as List) { - try { - final hub = PlexHub.fromJson(hubJson); - // Only include hubs that have items and are movie/show content - if (hub.items.isNotEmpty) { - // Filter to only video content (movies, shows, seasons, episodes) and tag with server info - final videoItems = hub.items - .where((item) => item.isVideoContent) - .map((item) => item.copyWith(serverId: serverId, serverName: serverName)) - .toList(); - - if (videoItems.isNotEmpty) { - hubs.add( - PlexHub( - hubKey: hub.hubKey, - title: hub.title, - type: hub.type, - hubIdentifier: hub.hubIdentifier, - size: hub.size, - more: hub.more, - items: videoItems, - serverId: serverId, - serverName: serverName, - ), - ); - } - } - } catch (e) { - appLogger.w('Failed to parse hub', error: e); - } - } - return hubs; - } + final sid = serverId; + final sname = serverName; + return Isolate.run(() => _processHubResponse(response.data as String, sid, sname)); } catch (e) { appLogger.e('Failed to get library hubs: $e'); } @@ -1354,43 +1373,14 @@ class PlexClient { /// This matches the official Plex client's home page layout. Future> getGlobalHubs({int limit = 10}) async { try { - final response = await _dio.get('/hubs', queryParameters: {'count': limit, 'includeGuids': 1}); - - final container = _getMediaContainer(response); - if (container != null && container['Hub'] != null) { - final hubs = []; - for (final hubJson in container['Hub'] as List) { - try { - final hub = PlexHub.fromJson(hubJson); - if (hub.items.isEmpty) continue; - - // Filter to only video content (movies, shows, seasons, episodes) and tag with server info - final videoItems = hub.items - .where((item) => item.isVideoContent) - .map((item) => item.copyWith(serverId: serverId, serverName: serverName)) - .toList(); - - if (videoItems.isNotEmpty) { - hubs.add( - PlexHub( - hubKey: hub.hubKey, - title: hub.title, - type: hub.type, - hubIdentifier: hub.hubIdentifier, - size: hub.size, - more: hub.more, - items: videoItems, - serverId: serverId, - serverName: serverName, - ), - ); - } - } catch (e) { - appLogger.w('Failed to parse global hub', error: e); - } - } - return hubs; - } + final response = await _dio.get( + '/hubs', + queryParameters: {'count': limit, 'includeGuids': 1}, + options: Options(responseType: ResponseType.plain), + ); + final sid = serverId; + final sname = serverName; + return Isolate.run(() => _processHubResponse(response.data as String, sid, sname)); } catch (e) { appLogger.e('Failed to get global hubs: $e'); }