Initial commit
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
part of '../di_providers.dart';
|
||||
|
||||
typedef SeriesSeasonsKey = ({String serverId, String seriesId});
|
||||
typedef EpisodesKey = ({String serverId, String seriesId, String seasonId});
|
||||
typedef CrossServerSearchKey = ({
|
||||
String activeServerId,
|
||||
String anchorType,
|
||||
String providerIdQuery,
|
||||
String seriesProviderIdQuery,
|
||||
int parentIndexNumber,
|
||||
int indexNumber,
|
||||
String itemName,
|
||||
String excludedServerId,
|
||||
});
|
||||
|
||||
typedef MediaDetailKey = ({String serverId, String itemId});
|
||||
|
||||
|
||||
final mediaDetailDataProvider =
|
||||
FutureProvider.family<MediaDetailRes, MediaDetailKey>((ref, key) async {
|
||||
final uc = await ref.read(mediaDetailUseCaseProvider.future);
|
||||
final sw = Stopwatch()..start();
|
||||
final res = await uc
|
||||
.execute(MediaDetailReq(itemId: key.itemId))
|
||||
.timeout(const Duration(seconds: 30));
|
||||
final rawSources = res.base.extra['MediaSources'];
|
||||
final srcCount = rawSources is List ? rawSources.length : 0;
|
||||
AppLogger.debug(
|
||||
'DetailTiming',
|
||||
'base.fetch type=${res.base.Type} sources=$srcCount ms=${sw.elapsedMilliseconds}',
|
||||
);
|
||||
return res;
|
||||
});
|
||||
|
||||
final similarItemsDataProvider =
|
||||
FutureProvider.family<List<EmbyRawItem>, String>((ref, itemId) async {
|
||||
final uc = await ref.read(similarItemsUseCaseProvider.future);
|
||||
final res = await uc.execute(SimilarItemsReq(itemId: itemId));
|
||||
return res.items;
|
||||
});
|
||||
|
||||
final additionalPartsDataProvider =
|
||||
FutureProvider.family<List<EmbyRawItem>, String>((ref, itemId) async {
|
||||
final uc = await ref.read(additionalPartsUseCaseProvider.future);
|
||||
final res = await uc.execute(AdditionalPartsReq(itemId: itemId));
|
||||
return res.items;
|
||||
});
|
||||
|
||||
final specialFeaturesDataProvider = FutureProvider.autoDispose
|
||||
.family<List<EmbyRawItem>, String>((ref, itemId) async {
|
||||
final uc = await ref.read(specialFeaturesUseCaseProvider.future);
|
||||
return uc.execute(itemId);
|
||||
});
|
||||
|
||||
final crossServerSearchDataProvider = StreamProvider.autoDispose
|
||||
.family<List<CrossServerSourceCard>, CrossServerSearchKey>((
|
||||
ref,
|
||||
key,
|
||||
) async* {
|
||||
final uc = await ref.read(crossServerSearchUseCaseProvider.future);
|
||||
final cancelToken = CancellationToken();
|
||||
ref.onDispose(() => cancelToken.cancel('provider disposed'));
|
||||
|
||||
final req = CrossServerSearchReq(
|
||||
anchorType: key.anchorType,
|
||||
itemName: key.itemName,
|
||||
providerIds: _decodeProviderIdQuery(key.providerIdQuery),
|
||||
seriesProviderIds: key.seriesProviderIdQuery.isEmpty
|
||||
? null
|
||||
: _decodeProviderIdQuery(key.seriesProviderIdQuery),
|
||||
parentIndexNumber: key.parentIndexNumber < 0
|
||||
? null
|
||||
: key.parentIndexNumber,
|
||||
indexNumber: key.indexNumber < 0 ? null : key.indexNumber,
|
||||
excludedServerId: key.excludedServerId.isEmpty
|
||||
? null
|
||||
: key.excludedServerId,
|
||||
);
|
||||
|
||||
yield const <CrossServerSourceCard>[];
|
||||
yield* uc.executeStream(req, cancelToken: cancelToken);
|
||||
});
|
||||
|
||||
|
||||
final tmdbLibraryAvailabilityProvider = StreamProvider.autoDispose
|
||||
.family<
|
||||
List<TmdbLibraryHit>,
|
||||
({String mediaType, String tmdbId, String imdbId})
|
||||
>((ref, key) async* {
|
||||
if (key.tmdbId.isEmpty) {
|
||||
yield const <TmdbLibraryHit>[];
|
||||
return;
|
||||
}
|
||||
final uc = await ref.read(tmdbAvailabilityUseCaseProvider.future);
|
||||
final cancelToken = CancellationToken();
|
||||
ref.onDispose(() => cancelToken.cancel('provider disposed'));
|
||||
yield* uc.executeStream(
|
||||
TmdbAvailabilityReq(
|
||||
tmdbId: key.tmdbId,
|
||||
mediaType: key.mediaType,
|
||||
imdbId: key.imdbId.isEmpty ? null : key.imdbId,
|
||||
),
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
typedef ServerScopedItemKey = ({String serverId, String itemId});
|
||||
|
||||
|
||||
final tmdbServerScopedDetailProvider = FutureProvider.autoDispose
|
||||
.family<MediaDetailRes, ServerScopedItemKey>((ref, key) async {
|
||||
final uc = await ref.read(mediaDetailUseCaseProvider.future);
|
||||
return uc.executeForServer(serverId: key.serverId, itemId: key.itemId);
|
||||
});
|
||||
|
||||
|
||||
typedef ServerScopedAuth = ({
|
||||
String baseUrl,
|
||||
String token,
|
||||
String userId,
|
||||
Map<String, String> imageHeaders,
|
||||
});
|
||||
|
||||
final tmdbServerScopedAuthProvider = FutureProvider.autoDispose
|
||||
.family<ServerScopedAuth?, String>((ref, serverId) async {
|
||||
final sessionRepo = await ref.read(sessionRepositoryProvider.future);
|
||||
final serverRepo = await ref.read(serverRepositoryProvider.future);
|
||||
final session = await sessionRepo.getByServerId(serverId);
|
||||
if (session == null) return null;
|
||||
final server = await serverRepo.findById(serverId);
|
||||
if (server == null) return null;
|
||||
final deviceId = await ref.read(deviceIdProvider.future);
|
||||
return (
|
||||
baseUrl: server.baseUrl,
|
||||
token: session.token,
|
||||
userId: session.userId,
|
||||
imageHeaders: EmbyRequestHeaders.image(
|
||||
deviceId: deviceId,
|
||||
deviceName: kEmbyDeviceName,
|
||||
token: session.token,
|
||||
userId: session.userId,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
final tmdbServerScopedAdditionalPartsProvider = FutureProvider.autoDispose
|
||||
.family<List<EmbyRawItem>, ServerScopedItemKey>((ref, key) async {
|
||||
final uc = await ref.read(additionalPartsUseCaseProvider.future);
|
||||
final res = await uc.executeForServer(
|
||||
serverId: key.serverId,
|
||||
itemId: key.itemId,
|
||||
);
|
||||
return res.items;
|
||||
});
|
||||
|
||||
typedef SeriesPrewarmKey = ({
|
||||
String activeServerId,
|
||||
String seriesProviderIdQuery,
|
||||
});
|
||||
|
||||
|
||||
final seriesPrewarmProvider = FutureProvider.autoDispose
|
||||
.family<void, SeriesPrewarmKey>((ref, key) async {
|
||||
if (key.seriesProviderIdQuery.isEmpty) return;
|
||||
final keepAlive = ref.keepAlive();
|
||||
final uc = await ref.read(crossServerSearchUseCaseProvider.future);
|
||||
final cancelToken = CancellationToken();
|
||||
var disposed = false;
|
||||
ref.onDispose(() {
|
||||
disposed = true;
|
||||
cancelToken.cancel('prewarm disposed');
|
||||
});
|
||||
try {
|
||||
await uc.prewarmSeriesIdentity(
|
||||
_decodeProviderIdQuery(key.seriesProviderIdQuery),
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
} finally {
|
||||
if (!disposed) keepAlive.close();
|
||||
}
|
||||
});
|
||||
|
||||
Map<String, String> _decodeProviderIdQuery(String q) {
|
||||
final out = <String, String>{};
|
||||
if (q.isEmpty) return out;
|
||||
for (final part in q.split(',')) {
|
||||
final dotIdx = part.indexOf('.');
|
||||
if (dotIdx > 0) {
|
||||
out[part.substring(0, dotIdx)] = part.substring(dotIdx + 1);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
final seriesSeasonsDataProvider =
|
||||
FutureProvider.family<List<EmbyRawSeason>, SeriesSeasonsKey>((
|
||||
ref,
|
||||
key,
|
||||
) async {
|
||||
final uc = await ref.read(seriesSeasonsUseCaseProvider.future);
|
||||
final res = await uc.executeForServer(
|
||||
serverId: key.serverId,
|
||||
input: SeriesSeasonsReq(seriesId: key.seriesId),
|
||||
);
|
||||
return res.items;
|
||||
});
|
||||
|
||||
final seriesEpisodesDataProvider =
|
||||
FutureProvider.family<List<EmbyRawItem>, EpisodesKey>((ref, key) async {
|
||||
final uc = await ref.read(seriesEpisodesUseCaseProvider.future);
|
||||
final res = await uc.executeForServer(
|
||||
serverId: key.serverId,
|
||||
input: MediaEpisodesReq(seriesId: key.seriesId, seasonId: key.seasonId),
|
||||
);
|
||||
return res.items;
|
||||
});
|
||||
|
||||
|
||||
typedef SeriesResumeTargetKey = ({String serverId, String seriesId});
|
||||
|
||||
|
||||
final seriesResumeTargetProvider = FutureProvider.autoDispose
|
||||
.family<SeriesResumeTarget?, SeriesResumeTargetKey>((ref, key) async {
|
||||
try {
|
||||
final sessionRepo = await ref.read(sessionRepositoryProvider.future);
|
||||
final serverRepo = await ref.read(serverRepositoryProvider.future);
|
||||
final ctx = await resolveAuthedContextForServer(
|
||||
sessionRepository: sessionRepo,
|
||||
serverRepository: serverRepo,
|
||||
serverId: key.serverId,
|
||||
);
|
||||
if (ctx == null) return null;
|
||||
final gw = await ref.read(embyGatewayProvider.future);
|
||||
return resolveSeriesResumeTarget(gw, ctx, key.seriesId);
|
||||
} catch (e) {
|
||||
AppLogger.warn('SeriesResumeTarget', 'resolve failed', e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
final tmdbTvRecentHitProvider = FutureProvider.autoDispose
|
||||
.family<
|
||||
TmdbLibraryHit?,
|
||||
({String mediaType, String tmdbId, String imdbId})
|
||||
>((ref, key) async {
|
||||
final hits = await ref.watch(tmdbLibraryAvailabilityProvider(key).future);
|
||||
final seriesHits = hits
|
||||
.where((h) => h.itemType == 'Series')
|
||||
.toList(growable: false);
|
||||
if (seriesHits.length < 2) return null;
|
||||
final uc = await ref.read(mediaDetailUseCaseProvider.future);
|
||||
DateTime? bestDate;
|
||||
TmdbLibraryHit? best;
|
||||
for (final h in seriesHits) {
|
||||
try {
|
||||
final res = await uc.executeForServer(
|
||||
serverId: h.serverId,
|
||||
itemId: h.itemId,
|
||||
);
|
||||
final lp = parseEmbyDate(res.base.UserData?.extra['LastPlayedDate']);
|
||||
if (lp != null && (bestDate == null || lp.isAfter(bestDate))) {
|
||||
bestDate = lp;
|
||||
best = h;
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
return best;
|
||||
});
|
||||
Reference in New Issue
Block a user