Plain Play requests the default media version (index 0 or the saved preference), but the offline resolvers rejected the single downloaded row when a non-default version was downloaded, then threw "No video URL available" with no client to fall back to. Three-part fix sharing one matcher (downloadedVersionMatches): - getPlaybackData falls back to the downloaded version when there is no client to stream from; the result now carries the effective mediaIndex/mediaSourceId so cached media info and the committed session describe the file actually played. - navigateToVideoPlayer seeds the selection from the download record for isOffline plays with no explicit version, covering the external player branch and offline-library plays with a reachable server. - PlaybackSession.fromContext prefers the result source id over the requested one, keeping in-player state in sync after a fallback. Online pinning is untouched: with a live client an explicitly requested non-downloaded version still streams from the server.
79 lines
2.9 KiB
Dart
79 lines
2.9 KiB
Dart
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';
|
|
|
|
/// 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;
|
|
|
|
const PlaybackSession({required this.context, required this.qualityPreset, this.mediaSourceId});
|
|
|
|
/// 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,
|
|
}) {
|
|
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,
|
|
);
|
|
}
|
|
|
|
static String? mediaSourceIdForIndex(List<MediaVersion> 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<MediaVersion> get availableVersions => result.availableVersions;
|
|
|
|
MediaSourceInfo? get mediaInfo => result.mediaInfo;
|
|
|
|
Map<String, String>? get streamHeaders => context.streamHeaders;
|
|
}
|