import 'plex_client.dart'; import '../models/plex_media_info.dart'; import '../models/plex_metadata.dart'; import '../mpv/mpv.dart'; import '../utils/app_logger.dart'; import '../i18n/strings.g.dart'; /// Service responsible for fetching video playback data from the Plex server class PlaybackInitializationService { final PlexClient client; PlaybackInitializationService({required this.client}); /// Fetch playback data for the given metadata /// /// Returns a PlaybackInitializationResult with video URL and available versions Future getPlaybackData({ required PlexMetadata metadata, required int selectedMediaIndex, }) async { try { // Get consolidated playback data (URL, media info, and versions) in a single API call final playbackData = await client.getVideoPlaybackData( metadata.ratingKey, mediaIndex: selectedMediaIndex, ); if (!playbackData.hasValidVideoUrl) { throw PlaybackException(t.messages.fileInfoNotAvailable); } // Build list of external subtitle tracks final externalSubtitles = _buildExternalSubtitles(playbackData.mediaInfo); // Return result with available versions and video URL return PlaybackInitializationResult( availableVersions: playbackData.availableVersions, videoUrl: playbackData.videoUrl, mediaInfo: playbackData.mediaInfo, externalSubtitles: externalSubtitles, ); } catch (e) { if (e is PlaybackException) { rethrow; } throw PlaybackException(t.messages.errorLoading(error: e.toString())); } } /// Build list of external subtitle tracks from media info List _buildExternalSubtitles(PlexMediaInfo? mediaInfo) { final externalSubtitles = []; if (mediaInfo == null) { return externalSubtitles; } final externalTracks = mediaInfo.subtitleTracks .where((PlexSubtitleTrack track) => track.isExternal) .toList(); if (externalTracks.isNotEmpty) { appLogger.d('Found ${externalTracks.length} external subtitle track(s)'); } for (final plexTrack in externalTracks) { try { // Skip if no auth token is available final token = client.config.token; if (token == null) { appLogger.w('No auth token available for external subtitles'); continue; } final url = plexTrack.getSubtitleUrl(client.config.baseUrl, token); // Skip if URL couldn't be constructed if (url == null) continue; externalSubtitles.add( SubtitleTrack.uri( url, title: plexTrack.displayTitle ?? plexTrack.language ?? 'Track ${plexTrack.id}', language: plexTrack.languageCode, ), ); } catch (e) { // Silent fallback - log error but continue with other subtitles appLogger.w( 'Failed to add external subtitle track ${plexTrack.id}', error: e, ); } } return externalSubtitles; } } /// Result of playback initialization class PlaybackInitializationResult { final List availableVersions; final String? videoUrl; final PlexMediaInfo? mediaInfo; final List externalSubtitles; PlaybackInitializationResult({ required this.availableVersions, this.videoUrl, this.mediaInfo, this.externalSubtitles = const [], }); } /// Exception thrown when playback initialization fails class PlaybackException implements Exception { final String message; PlaybackException(this.message); @override String toString() => message; }