import '../media/media_item.dart'; import '../media/media_server_client.dart'; import '../media/media_source_info.dart'; import '../media/media_version.dart'; import '../models/transcode_quality_preset.dart'; import 'playback_context.dart'; import 'playback_initialization_types.dart'; import 'playback_subtitle_resolver.dart'; /// Immutable snapshot of everything that describes the item currently loaded /// in the player: the resolver output plus the effective (post-fallback, /// post-clamping) source selections. /// /// Built once per resolve and swapped atomically on the screen — failure /// before the swap means the previous session (and the state derived from /// it) stays untouched, which replaces the old field-by-field /// snapshot/rollback bookkeeping. class PlaybackSession { final PlaybackContext context; /// Effective quality preset — the requested preset, downgraded to /// original when the backend reported a transcode fallback. final TranscodeQualityPreset qualityPreset; /// Effective media version id, refined from the resolver's clamped /// version index when the version list provides one. final String? mediaSourceId; final PlaybackSubtitleSelection subtitleSelection; const PlaybackSession({ required this.context, required this.qualityPreset, this.mediaSourceId, this.subtitleSelection = const PlaybackSubtitleSelection.off(), }); /// Derives the effective selections from a resolved [context]: /// quality falls back to original when the backend rejected the requested /// preset, and the media source id follows the clamped version index. factory PlaybackSession.fromContext( PlaybackContext context, { required TranscodeQualityPreset requestedQualityPreset, String? requestedMediaSourceId, PlaybackSubtitleSelection subtitleSelection = const PlaybackSubtitleSelection.off(), }) { final result = context.result; final fellBackToOriginal = result.fallbackReason != null && !requestedQualityPreset.isOriginal; return PlaybackSession( context: context, qualityPreset: fellBackToOriginal ? TranscodeQualityPreset.original : requestedQualityPreset, mediaSourceId: result.selectedMediaSourceId ?? mediaSourceIdForIndex(result.availableVersions, result.selectedMediaIndex) ?? requestedMediaSourceId, subtitleSelection: subtitleSelection, ); } PlaybackSession withSubtitleSelection(PlaybackSubtitleSelection selection) { return PlaybackSession( context: context, qualityPreset: qualityPreset, mediaSourceId: mediaSourceId, subtitleSelection: selection, ); } static String? mediaSourceIdForIndex(List versions, int index) { if (index < 0 || index >= versions.length) return null; return versions[index].id; } PlaybackInitializationResult get result => context.result; MediaItem get metadata => context.metadata; MediaServerClient? get reportingClient => context.reportingClient; bool get isTranscoding => result.isTranscoding; bool get isOffline => result.isOffline; String? get playSessionId => result.playSessionId; String? get playMethod => result.playMethod; int? get audioStreamId => result.activeAudioStreamId; int get mediaIndex => result.selectedMediaIndex; List get availableVersions => result.availableVersions; MediaSourceInfo? get mediaInfo => result.mediaInfo; Map? get streamHeaders => context.streamHeaders; }