mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-07 21:48:14 -08:00
feat: Sync offline/online playback when able (#431)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
parent
15ac3566e2
commit
092836328f
42 changed files with 1002 additions and 497 deletions
|
|
@ -4,6 +4,7 @@ import 'dart:typed_data';
|
|||
import 'package:chopper/chopper.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart';
|
||||
|
||||
import 'package:fladder/fake/fake_jellyfin_open_api.dart';
|
||||
|
|
@ -12,10 +13,13 @@ import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
|||
import 'package:fladder/models/account_model.dart';
|
||||
import 'package:fladder/models/credentials_model.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/episode_model.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/media_segments_model.dart';
|
||||
import 'package:fladder/models/items/trick_play_model.dart';
|
||||
import 'package:fladder/providers/auth_provider.dart';
|
||||
import 'package:fladder/providers/image_provider.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/util/jellyfin_extension.dart';
|
||||
|
||||
|
|
@ -84,21 +88,78 @@ class JellyService {
|
|||
Future<Response<ItemBaseModel>> usersUserIdItemsItemIdGet({
|
||||
String? itemId,
|
||||
}) async {
|
||||
final response = await api.itemsItemIdGet(
|
||||
userId: account?.id,
|
||||
itemId: itemId,
|
||||
);
|
||||
return response.copyWith(body: ItemBaseModel.fromBaseDto(response.bodyOrThrow, ref));
|
||||
try {
|
||||
final response = await api.itemsItemIdGet(
|
||||
userId: account?.id,
|
||||
itemId: itemId,
|
||||
);
|
||||
return response.copyWith(body: ItemBaseModel.fromBaseDto(response.bodyOrThrow, ref));
|
||||
} catch (e) {
|
||||
final item = (await ref.read(syncProvider.notifier).getSyncedItem(itemId))?.itemModel;
|
||||
return Response<ItemBaseModel>(
|
||||
http.Response("", 202),
|
||||
item,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response<BaseItemDto>> usersUserIdItemsItemIdGetBaseItem({
|
||||
String? itemId,
|
||||
}) async {
|
||||
final response = await api.itemsItemIdGet(
|
||||
try {
|
||||
return await api.itemsItemIdGet(
|
||||
userId: account?.id,
|
||||
itemId: itemId,
|
||||
);
|
||||
} catch (e) {
|
||||
return ref.read(syncProvider.notifier).getSyncedItem(itemId).then(
|
||||
(value) => value?.data != null
|
||||
? Response<BaseItemDto>(
|
||||
http.Response("", 202),
|
||||
value?.data,
|
||||
)
|
||||
: Response<BaseItemDto>(
|
||||
http.Response("", 404),
|
||||
null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response<UserData>> userItemsItemIdUserDataGet({
|
||||
String? itemId,
|
||||
}) async {
|
||||
final response = await api.userItemsItemIdUserDataGet(
|
||||
userId: account?.id,
|
||||
itemId: itemId,
|
||||
);
|
||||
return response;
|
||||
return response.copyWith(
|
||||
body: UserData.fromDto(response.bodyOrThrow),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Response<UserData>?> userItemsItemIdUserDataPost({
|
||||
String? itemId,
|
||||
required UserData? body,
|
||||
}) async {
|
||||
if (body == null) {
|
||||
return null;
|
||||
}
|
||||
final response = await api.userItemsItemIdUserDataPost(
|
||||
userId: account?.id,
|
||||
itemId: itemId,
|
||||
body: UpdateUserItemDataDto(
|
||||
playCount: body.playCount,
|
||||
playbackPositionTicks: body.playbackPositionTicks,
|
||||
isFavorite: body.isFavourite,
|
||||
played: body.played,
|
||||
lastPlayedDate: body.lastPlayed,
|
||||
itemId: itemId,
|
||||
),
|
||||
);
|
||||
return response.copyWith(
|
||||
body: UserData.fromDto(response.bodyOrThrow),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Response<ServerQueryResult>> itemsGet({
|
||||
|
|
@ -491,8 +552,15 @@ class JellyService {
|
|||
|
||||
Future<Response> sessionsPlayingStoppedPost({
|
||||
required PlaybackStopInfo? body,
|
||||
}) =>
|
||||
api.sessionsPlayingStoppedPost(body: body);
|
||||
}) {
|
||||
final positionTicks = body?.positionTicks;
|
||||
if (positionTicks != null) {
|
||||
ref
|
||||
.read(syncProvider.notifier)
|
||||
.updatePlaybackPosition(itemId: body?.itemId, position: Duration(milliseconds: positionTicks ~/ 10000));
|
||||
}
|
||||
return api.sessionsPlayingStoppedPost(body: body);
|
||||
}
|
||||
|
||||
Future<Response> sessionsPlayingProgressPost({required PlaybackProgressInfo? body}) async =>
|
||||
api.sessionsPlayingProgressPost(body: body);
|
||||
|
|
@ -533,25 +601,51 @@ class JellyService {
|
|||
bool? enableUserData,
|
||||
ShowsSeriesIdEpisodesGetSortBy? sortBy,
|
||||
}) async {
|
||||
return api.showsSeriesIdEpisodesGet(
|
||||
seriesId: seriesId,
|
||||
userId: account?.id,
|
||||
fields: [
|
||||
...?fields,
|
||||
ItemFields.parentid,
|
||||
],
|
||||
isMissing: isMissing,
|
||||
limit: limit,
|
||||
sortBy: sortBy,
|
||||
enableUserData: enableUserData,
|
||||
startIndex: startIndex,
|
||||
adjacentTo: adjacentTo,
|
||||
startItemId: startItemId,
|
||||
season: season,
|
||||
seasonId: seasonId,
|
||||
enableImages: enableImages,
|
||||
enableImageTypes: enableImageTypes,
|
||||
);
|
||||
try {
|
||||
var response = await api.showsSeriesIdEpisodesGet(
|
||||
seriesId: seriesId,
|
||||
userId: account?.id,
|
||||
fields: [
|
||||
...?fields,
|
||||
ItemFields.parentid,
|
||||
],
|
||||
isMissing: isMissing,
|
||||
limit: limit,
|
||||
sortBy: sortBy,
|
||||
enableUserData: enableUserData,
|
||||
startIndex: startIndex,
|
||||
adjacentTo: adjacentTo,
|
||||
startItemId: startItemId,
|
||||
season: season,
|
||||
seasonId: seasonId,
|
||||
enableImages: enableImages,
|
||||
enableImageTypes: enableImageTypes,
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
final seriesItem = await ref.read(syncProvider.notifier).getSyncedItem(seriesId);
|
||||
if (seriesItem != null) {
|
||||
final episodes = await ref.read(syncProvider.notifier).getNestedChildren(seriesItem)
|
||||
..where((e) => e.itemModel is EpisodeModel);
|
||||
return Response<BaseItemDtoQueryResult>(
|
||||
http.Response("", 200),
|
||||
BaseItemDtoQueryResult(
|
||||
items: episodes.map((e) => e.data).nonNulls.toList(),
|
||||
totalRecordCount: episodes.length,
|
||||
startIndex: 0,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Response<BaseItemDtoQueryResult>(
|
||||
http.Response("", 400),
|
||||
const BaseItemDtoQueryResult(
|
||||
items: [],
|
||||
totalRecordCount: 0,
|
||||
startIndex: 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<ItemBaseModel>> fetchEpisodeFromShow({
|
||||
|
|
@ -566,11 +660,22 @@ class JellyService {
|
|||
String? itemId,
|
||||
int? limit,
|
||||
}) async {
|
||||
return api.itemsItemIdSimilarGet(
|
||||
userId: account?.id,
|
||||
itemId: itemId,
|
||||
limit: limit,
|
||||
);
|
||||
try {
|
||||
return await api.itemsItemIdSimilarGet(userId: account?.id, itemId: itemId, limit: limit, fields: [
|
||||
ItemFields.parentid,
|
||||
ItemFields.candelete,
|
||||
ItemFields.candownload,
|
||||
]);
|
||||
} catch (e) {
|
||||
return Response<BaseItemDtoQueryResult>(
|
||||
http.Response("", 400),
|
||||
const BaseItemDtoQueryResult(
|
||||
items: [],
|
||||
totalRecordCount: 0,
|
||||
startIndex: 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response<BaseItemDtoQueryResult>> usersUserIdItemsGet({
|
||||
|
|
@ -692,8 +797,9 @@ class JellyService {
|
|||
bool? enableUserData,
|
||||
bool? isMissing,
|
||||
List<ItemFields>? fields,
|
||||
}) =>
|
||||
api.showsSeriesIdSeasonsGet(
|
||||
}) async {
|
||||
try {
|
||||
final response = await api.showsSeriesIdSeasonsGet(
|
||||
seriesId: seriesId,
|
||||
isMissing: isMissing,
|
||||
enableUserData: enableUserData,
|
||||
|
|
@ -702,6 +808,31 @@ class JellyService {
|
|||
ItemFields.parentid,
|
||||
],
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
final seriesItem = await ref.read(syncProvider.notifier).getSyncedItem(seriesId);
|
||||
if (seriesItem != null) {
|
||||
final seasons = await ref.read(syncProvider.notifier).getChildren(seriesItem.id);
|
||||
return Response<BaseItemDtoQueryResult>(
|
||||
http.Response("", 200),
|
||||
BaseItemDtoQueryResult(
|
||||
items: seasons.map((e) => e.data).nonNulls.toList(),
|
||||
totalRecordCount: seasons.length,
|
||||
startIndex: 0,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Response<BaseItemDtoQueryResult>(
|
||||
http.Response("", 400),
|
||||
const BaseItemDtoQueryResult(
|
||||
items: [],
|
||||
totalRecordCount: 0,
|
||||
startIndex: 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response<QueryFilters>> itemsFilters2Get({
|
||||
String? parentId,
|
||||
|
|
@ -817,33 +948,75 @@ class JellyService {
|
|||
|
||||
Future<Response<UserItemDataDto>> usersUserIdFavoriteItemsItemIdPost({
|
||||
required String? itemId,
|
||||
}) =>
|
||||
api.userFavoriteItemsItemIdPost(
|
||||
}) async {
|
||||
Response<UserItemDataDto>? response;
|
||||
try {
|
||||
response = await api.userFavoriteItemsItemIdPost(
|
||||
itemId: itemId,
|
||||
userId: account?.id,
|
||||
);
|
||||
} finally {
|
||||
await ref
|
||||
.read(syncProvider.notifier)
|
||||
.updateFavoriteItem(itemId, isFavorite: true, responseSuccessful: response?.isSuccessful ?? false);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<Response<UserItemDataDto>> usersUserIdFavoriteItemsItemIdDelete({
|
||||
required String? itemId,
|
||||
}) =>
|
||||
api.userFavoriteItemsItemIdDelete(
|
||||
}) async {
|
||||
Response<UserItemDataDto>? response;
|
||||
try {
|
||||
response = await api.userFavoriteItemsItemIdDelete(
|
||||
itemId: itemId,
|
||||
userId: account?.id,
|
||||
);
|
||||
} finally {
|
||||
await ref
|
||||
.read(syncProvider.notifier)
|
||||
.updateFavoriteItem(itemId, isFavorite: false, responseSuccessful: response?.isSuccessful ?? false);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<Response<UserItemDataDto>> usersUserIdPlayedItemsItemIdPost({
|
||||
required String? itemId,
|
||||
DateTime? datePlayed,
|
||||
}) =>
|
||||
api.userPlayedItemsItemIdPost(itemId: itemId, userId: account?.id, datePlayed: datePlayed);
|
||||
}) async {
|
||||
Response<UserItemDataDto>? response;
|
||||
try {
|
||||
response = await api.userPlayedItemsItemIdPost(itemId: itemId, userId: account?.id, datePlayed: datePlayed);
|
||||
} finally {
|
||||
await ref.read(syncProvider.notifier).updatePlayedItem(
|
||||
itemId,
|
||||
datePlayed: datePlayed,
|
||||
played: true,
|
||||
responseSuccessful: response?.isSuccessful ?? false,
|
||||
);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<Response<UserItemDataDto>> usersUserIdPlayedItemsItemIdDelete({
|
||||
required String? itemId,
|
||||
}) =>
|
||||
api.userPlayedItemsItemIdDelete(
|
||||
}) async {
|
||||
Response<UserItemDataDto>? response;
|
||||
try {
|
||||
response = await api.userPlayedItemsItemIdDelete(
|
||||
itemId: itemId,
|
||||
userId: account?.id,
|
||||
);
|
||||
} finally {
|
||||
await ref.read(syncProvider.notifier).updatePlayedItem(
|
||||
itemId,
|
||||
played: false,
|
||||
responseSuccessful: response?.isSuccessful ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<Response<MediaSegmentsModel>?> mediaSegmentsGet({
|
||||
required String id,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue