Files
plezy/lib/utils/download_version_utils.dart
T

125 lines
4.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../media/media_item.dart';
import '../media/media_kind.dart';
import '../media/media_server_client.dart';
import '../media/media_version.dart';
import '../media/episode_collection.dart';
import '../utils/app_logger.dart';
import '../utils/dialogs.dart';
import '../utils/media_version_resolver.dart';
import '../i18n/strings.g.dart';
/// Configuration for download version selection, threaded through the queue pipeline.
class DownloadVersionConfig {
final int mediaIndex;
final Set<String> acceptedSignatures;
final Future<int?> Function(MediaItem episode, List<MediaVersion> versions)? onVersionMismatch;
DownloadVersionConfig({this.mediaIndex = 0, Set<String>? acceptedSignatures, this.onVersionMismatch})
: acceptedSignatures = acceptedSignatures ?? {};
/// Create from a selected version's signature.
factory DownloadVersionConfig.fromSignature(
String signature, {
int mediaIndex = 0,
Future<int?> Function(MediaItem, List<MediaVersion>)? onVersionMismatch,
}) {
return DownloadVersionConfig(
mediaIndex: mediaIndex,
acceptedSignatures: {signature},
onVersionMismatch: onVersionMismatch,
);
}
}
/// Resolve version selection for a download. Shows picker if needed.
/// Returns null if the user cancels, or a config with the selection.
Future<DownloadVersionConfig?> resolveDownloadVersion(
BuildContext context,
MediaItem metadata,
MediaServerClient client, {
List<MediaVersion>? fallbackVersions,
}) async {
final kind = metadata.kind;
if (kind == MediaKind.movie || kind == MediaKind.episode) {
final versions = await resolveMediaVersions(metadata, client, fallbackVersions: fallbackVersions);
if (!context.mounted) return null;
if (versions.length > 1) {
final selectedIndex = await showVersionPickerDialog(context, versions, t.downloads.selectVersion);
if (selectedIndex == null || !context.mounted) return null;
return DownloadVersionConfig(mediaIndex: selectedIndex);
}
return DownloadVersionConfig();
}
if (kind == MediaKind.show || kind == MediaKind.season) {
final versions = await fetchRepresentativeVersions(client, metadata);
if (versions != null && versions.length > 1) {
if (!context.mounted) return null;
final selectedIndex = await showVersionPickerDialog(context, versions, t.downloads.selectVersion);
if (selectedIndex == null || !context.mounted) return null;
return DownloadVersionConfig.fromSignature(
versions[selectedIndex].signature,
mediaIndex: selectedIndex,
onVersionMismatch: (episode, episodeVersions) async {
if (!context.mounted) return null;
return showVersionPickerDialog(
context,
episodeVersions,
'${episode.displayTitle} - ${t.downloads.selectVersion}',
);
},
);
}
return DownloadVersionConfig();
}
return DownloadVersionConfig();
}
/// Show a dialog for selecting a media version.
/// Returns the selected index, or null if cancelled.
Future<int?> showVersionPickerDialog(BuildContext context, List<MediaVersion> versions, String title) {
return showOptionPickerDialog<int>(
context,
title: title,
options: List.generate(
versions.length,
(index) => (icon: Symbols.video_file_rounded, label: versions[index].displayLabel, value: index),
),
);
}
/// Fetch media versions from a representative episode (first episode of first season).
Future<List<MediaVersion>?> fetchRepresentativeVersions(MediaServerClient client, MediaItem metadata) async {
try {
String? episodeRatingKey;
if (metadata.kind == MediaKind.season) {
final firstEpisode = await fetchFirstEpisodeForSeason(
client,
metadata.id,
seriesId: metadata.grandparentId ?? metadata.parentId,
);
episodeRatingKey = firstEpisode?.id;
} else if (metadata.kind == MediaKind.show) {
final seasons = await client.fetchChildren(metadata.id);
final firstSeason = defaultPlaybackSeason(seasons);
if (firstSeason != null) {
final firstEpisode = await fetchFirstEpisodeForSeason(client, firstSeason.id, seriesId: metadata.id);
episodeRatingKey = firstEpisode?.id;
}
}
if (episodeRatingKey == null) return null;
final fullMetadata = await client.fetchItem(episodeRatingKey);
return fullMetadata?.mediaVersions;
} catch (e) {
appLogger.w('Failed to fetch representative versions', error: e);
return null;
}
}