mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-10 07:50:28 -07:00
feat: Android TV support (#503)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
parent
7ab8c015b9
commit
c299492d6d
168 changed files with 12019 additions and 3073 deletions
|
|
@ -37,10 +37,14 @@ class JellyRequest implements Interceptor {
|
|||
FutureOr<Response<BodyType>> intercept<BodyType>(Chain<BodyType> chain) async {
|
||||
final connectivityNotifier = ref.read(connectivityStatusProvider.notifier);
|
||||
try {
|
||||
final serverUrl = Uri.parse(ref.read(userProvider)?.server ?? ref.read(authProvider).tempCredentials.server);
|
||||
final serverUrl = Uri.parse(
|
||||
ref.read(userProvider)?.server ?? ref.read(authProvider).serverLoginModel?.tempCredentials.server ?? "");
|
||||
|
||||
//Use current logged in user otherwise use the authprovider
|
||||
var loginModel = ref.read(userProvider)?.credentials ?? ref.read(authProvider).tempCredentials;
|
||||
var loginModel = ref.read(userProvider)?.credentials ?? ref.read(authProvider).serverLoginModel?.tempCredentials;
|
||||
|
||||
if (loginModel == null) throw UnimplementedError();
|
||||
|
||||
var headers = loginModel.header(ref);
|
||||
final Response<BodyType> response = await chain.proceed(
|
||||
applyHeaders(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:chopper/chopper.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
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/login_screen_model.dart';
|
||||
|
|
@ -13,44 +16,133 @@ import 'package:fladder/providers/service_provider.dart';
|
|||
import 'package:fladder/providers/shared_provider.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/providers/views_provider.dart';
|
||||
import 'package:fladder/screens/login/lock_screen.dart';
|
||||
import 'package:fladder/screens/shared/fladder_snackbar.dart';
|
||||
import 'package:fladder/util/fladder_config.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
|
||||
final authProvider = StateNotifierProvider<AuthNotifier, LoginScreenModel>((ref) {
|
||||
return AuthNotifier(ref);
|
||||
});
|
||||
|
||||
class AuthNotifier extends StateNotifier<LoginScreenModel> {
|
||||
AuthNotifier(this.ref)
|
||||
: super(
|
||||
LoginScreenModel(
|
||||
accounts: [],
|
||||
tempCredentials: CredentialsModel.createNewCredentials(),
|
||||
loading: false,
|
||||
),
|
||||
);
|
||||
AuthNotifier(this.ref) : super(LoginScreenModel());
|
||||
|
||||
final Ref ref;
|
||||
|
||||
late final JellyService api = ref.read(jellyApiProvider);
|
||||
|
||||
Future<Response<List<AccountModel>>?> getPublicUsers() async {
|
||||
try {
|
||||
var response = await api.usersPublicGet(state.tempCredentials);
|
||||
if (response.isSuccessful && response.body != null) {
|
||||
var models = response.body ?? [];
|
||||
BuildContext? context;
|
||||
|
||||
return response.copyWith(body: models.toList());
|
||||
Future<void> initModel(BuildContext newContext) async {
|
||||
context ??= newContext;
|
||||
ref.read(userProvider.notifier).clear();
|
||||
final currentAccounts = ref.read(authProvider.notifier).getSavedAccounts();
|
||||
ref.read(lockScreenActiveProvider.notifier).update((state) => true);
|
||||
if (FladderConfig.baseUrl != null) {
|
||||
final url = FladderConfig.baseUrl;
|
||||
state = state.copyWith(
|
||||
hasBaseUrl: true,
|
||||
);
|
||||
if (url != null) {
|
||||
await setServer(url);
|
||||
}
|
||||
return response.copyWith(body: []);
|
||||
}
|
||||
state = state.copyWith(
|
||||
accounts: currentAccounts,
|
||||
screen: currentAccounts.isEmpty ? LoginScreenType.login : LoginScreenType.users,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _fetchServerInfo(String url) async {
|
||||
try {
|
||||
final newCredentials = CredentialsModel.createNewCredentials().copyWith(server: url.rtrim('/'));
|
||||
final newLoginModel = ServerLoginModel(tempCredentials: newCredentials);
|
||||
state = state.copyWith(
|
||||
serverLoginModel: newLoginModel,
|
||||
loading: true,
|
||||
);
|
||||
final publicUsers = (await getPublicUsers())?.body ?? [];
|
||||
final quickConnectStatus = (await api.quickConnectEnabled()).body ?? false;
|
||||
final branding = await api.getBranding();
|
||||
final serverResponse = await api.systemInfoPublicGet();
|
||||
state = state.copyWith(
|
||||
screen: quickConnectStatus ? LoginScreenType.code : LoginScreenType.login,
|
||||
serverLoginModel: newLoginModel.copyWith(
|
||||
tempCredentials: newCredentials.copyWith(
|
||||
serverName: serverResponse.body?.serverName,
|
||||
serverId: serverResponse.body?.id,
|
||||
),
|
||||
accounts: publicUsers,
|
||||
hasQuickConnect: quickConnectStatus,
|
||||
serverMessage: branding.body?.loginDisclaimer,
|
||||
),
|
||||
loading: false,
|
||||
);
|
||||
} catch (e) {
|
||||
return null;
|
||||
state = state.copyWith(
|
||||
errorMessage: context?.localized.invalidUrl,
|
||||
loading: false,
|
||||
);
|
||||
if (context != null) {
|
||||
fladderSnackbar(context!, title: context!.localized.unableToConnectHost);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String? _parseUrl(String url) {
|
||||
if (url.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
if (!Uri.parse(url).isAbsolute) {
|
||||
return context?.localized.invalidUrl;
|
||||
}
|
||||
|
||||
if (!url.startsWith('https://') && !url.startsWith('http://')) {
|
||||
return context?.localized.invalidUrlDesc;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Response<List<AccountModel>>?> getPublicUsers() async {
|
||||
try {
|
||||
state = state.copyWith(loading: true);
|
||||
final credentials = state.serverLoginModel?.tempCredentials;
|
||||
if (credentials == null) return null;
|
||||
var response = await api.usersPublicGet(credentials);
|
||||
if (response.isSuccessful && response.body != null) {
|
||||
var models = response.body ?? [];
|
||||
return response.copyWith(body: models.toList());
|
||||
}
|
||||
state = state.copyWith(
|
||||
serverLoginModel: state.serverLoginModel?.copyWith(
|
||||
accounts: response.body ?? [],
|
||||
),
|
||||
);
|
||||
return response.copyWith(body: []);
|
||||
} catch (e) {
|
||||
return null;
|
||||
} finally {
|
||||
state = state.copyWith(loading: false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response<AccountModel>?> authenticateUsingSecret(String secret) async {
|
||||
clearAllProviders();
|
||||
var response = await api.quickConnectAuthenticate(secret);
|
||||
return _createAccountModel(response);
|
||||
}
|
||||
|
||||
Future<Response<AccountModel>?> authenticateByName(String userName, String password) async {
|
||||
state = state.copyWith(loading: true);
|
||||
clearAllProviders();
|
||||
var response = await api.usersAuthenticateByNamePost(userName: userName, password: password);
|
||||
CredentialsModel credentials = state.tempCredentials;
|
||||
return _createAccountModel(response);
|
||||
}
|
||||
|
||||
Future<Response<AccountModel>?> _createAccountModel(Response<AuthenticationResult> response) async {
|
||||
CredentialsModel? credentials = state.serverLoginModel?.tempCredentials;
|
||||
if (credentials == null) return null;
|
||||
if (response.isSuccessful && (response.body?.accessToken?.isNotEmpty ?? false)) {
|
||||
var serverResponse = await api.systemInfoPublicGet();
|
||||
credentials = credentials.copyWith(
|
||||
|
|
@ -68,16 +160,21 @@ class AuthNotifier extends StateNotifier<LoginScreenModel> {
|
|||
);
|
||||
ref.read(sharedUtilityProvider).addAccount(newUser);
|
||||
ref.read(userProvider.notifier).userState = newUser;
|
||||
state = state.copyWith(loading: false);
|
||||
final currentAccounts = ref.read(authProvider.notifier).getSavedAccounts();
|
||||
|
||||
state = state.copyWith(
|
||||
serverLoginModel: null,
|
||||
accounts: currentAccounts,
|
||||
);
|
||||
|
||||
return Response(response.base, newUser);
|
||||
}
|
||||
state = state.copyWith(loading: false);
|
||||
return Response(response.base, null);
|
||||
}
|
||||
|
||||
Future<Response?> logOutUser() async {
|
||||
final currentUser = ref.read(userProvider);
|
||||
state = state.copyWith(tempCredentials: CredentialsModel.createNewCredentials());
|
||||
state = state.copyWith(serverLoginModel: null);
|
||||
await ref.read(sharedUtilityProvider).removeAccount(currentUser);
|
||||
clearAllProviders();
|
||||
return null;
|
||||
|
|
@ -95,10 +192,17 @@ class AuthNotifier extends StateNotifier<LoginScreenModel> {
|
|||
ref.read(libraryScreenProvider.notifier).clear();
|
||||
}
|
||||
|
||||
void setServer(String server) {
|
||||
Future<void> setServer(String server) async {
|
||||
final url = (state.hasBaseUrl ? FladderConfig.baseUrl : server);
|
||||
if (url == null || server.isEmpty) return;
|
||||
final isUrlValid = _parseUrl(url);
|
||||
state = state.copyWith(
|
||||
tempCredentials: state.tempCredentials.copyWith(server: server),
|
||||
errorMessage: isUrlValid,
|
||||
serverLoginModel: null,
|
||||
);
|
||||
if (isUrlValid == null) {
|
||||
await _fetchServerInfo(url);
|
||||
}
|
||||
}
|
||||
|
||||
List<AccountModel> getSavedAccounts() {
|
||||
|
|
@ -113,4 +217,27 @@ class AuthNotifier extends StateNotifier<LoginScreenModel> {
|
|||
accounts.insert(newIndex, original);
|
||||
ref.read(sharedUtilityProvider).saveAccounts(accounts);
|
||||
}
|
||||
|
||||
void addNewUser() {
|
||||
state = state.copyWith(
|
||||
screen: LoginScreenType.login,
|
||||
);
|
||||
}
|
||||
|
||||
void goUserSelect() {
|
||||
state = state.copyWith(
|
||||
serverLoginModel: state.hasBaseUrl ? state.serverLoginModel : null,
|
||||
screen: LoginScreenType.users,
|
||||
);
|
||||
}
|
||||
|
||||
void tryParseUrl(String server) {
|
||||
if (server.isNotEmpty && state.errorMessage != null) {
|
||||
final url = server;
|
||||
final isUrlValid = _parseUrl(url);
|
||||
state = state.copyWith(
|
||||
errorMessage: isUrlValid,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,9 +26,16 @@ class DashboardNotifier extends StateNotifier<HomeModel> {
|
|||
final viewTypes =
|
||||
ref.read(viewsProvider.select((value) => value.dashboardViews)).map((e) => e.collectionType).toSet().toList();
|
||||
|
||||
final imagesToFetch = {
|
||||
ImageType.logo,
|
||||
ImageType.primary,
|
||||
ImageType.backdrop,
|
||||
ImageType.banner,
|
||||
}.toList();
|
||||
|
||||
if (viewTypes.containsAny([CollectionType.movies, CollectionType.tvshows])) {
|
||||
final resumeVideoResponse = await api.usersUserIdItemsResumeGet(
|
||||
limit: 16,
|
||||
enableImageTypes: imagesToFetch,
|
||||
fields: [
|
||||
ItemFields.parentid,
|
||||
ItemFields.mediastreams,
|
||||
|
|
@ -36,6 +43,8 @@ class DashboardNotifier extends StateNotifier<HomeModel> {
|
|||
ItemFields.candelete,
|
||||
ItemFields.candownload,
|
||||
ItemFields.primaryimageaspectratio,
|
||||
ItemFields.overview,
|
||||
ItemFields.genres,
|
||||
],
|
||||
mediaTypes: [MediaType.video],
|
||||
enableTotalRecordCount: false,
|
||||
|
|
@ -48,7 +57,7 @@ class DashboardNotifier extends StateNotifier<HomeModel> {
|
|||
|
||||
if (viewTypes.contains(CollectionType.music)) {
|
||||
final resumeAudioResponse = await api.usersUserIdItemsResumeGet(
|
||||
limit: 16,
|
||||
enableImageTypes: imagesToFetch,
|
||||
fields: [
|
||||
ItemFields.parentid,
|
||||
ItemFields.mediastreams,
|
||||
|
|
@ -56,6 +65,8 @@ class DashboardNotifier extends StateNotifier<HomeModel> {
|
|||
ItemFields.candelete,
|
||||
ItemFields.candownload,
|
||||
ItemFields.primaryimageaspectratio,
|
||||
ItemFields.overview,
|
||||
ItemFields.genres,
|
||||
],
|
||||
mediaTypes: [MediaType.audio],
|
||||
enableTotalRecordCount: false,
|
||||
|
|
@ -68,7 +79,7 @@ class DashboardNotifier extends StateNotifier<HomeModel> {
|
|||
|
||||
if (viewTypes.contains(CollectionType.books)) {
|
||||
final resumeBookResponse = await api.usersUserIdItemsResumeGet(
|
||||
limit: 16,
|
||||
enableImageTypes: imagesToFetch,
|
||||
fields: [
|
||||
ItemFields.parentid,
|
||||
ItemFields.mediastreams,
|
||||
|
|
@ -76,6 +87,8 @@ class DashboardNotifier extends StateNotifier<HomeModel> {
|
|||
ItemFields.candelete,
|
||||
ItemFields.candownload,
|
||||
ItemFields.primaryimageaspectratio,
|
||||
ItemFields.overview,
|
||||
ItemFields.genres,
|
||||
],
|
||||
mediaTypes: [MediaType.book],
|
||||
enableTotalRecordCount: false,
|
||||
|
|
@ -87,7 +100,6 @@ class DashboardNotifier extends StateNotifier<HomeModel> {
|
|||
}
|
||||
|
||||
final nextResponse = await api.showsNextUpGet(
|
||||
limit: 16,
|
||||
nextUpDateCutoff: DateTime.now().subtract(
|
||||
ref.read(clientSettingsProvider.select((value) => value.nextUpDateCutoff ?? const Duration(days: 28)))),
|
||||
fields: [
|
||||
|
|
@ -97,6 +109,8 @@ class DashboardNotifier extends StateNotifier<HomeModel> {
|
|||
ItemFields.candelete,
|
||||
ItemFields.candownload,
|
||||
ItemFields.primaryimageaspectratio,
|
||||
ItemFields.overview,
|
||||
ItemFields.genres,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import 'package:fladder/providers/user_provider.dart';
|
|||
|
||||
const _defaultHeight = 576;
|
||||
const _defaultWidth = 384;
|
||||
const _defaultQuality = 96;
|
||||
const _defaultQuality = 90;
|
||||
|
||||
final imageUtilityProvider = Provider<ImageNotifier>((ref) {
|
||||
return ImageNotifier(ref: ref);
|
||||
|
|
@ -19,7 +19,7 @@ class ImageNotifier {
|
|||
});
|
||||
|
||||
String get currentServerUrl {
|
||||
return ref.read(userProvider)?.server ?? ref.read(authProvider).tempCredentials.server;
|
||||
return ref.read(userProvider)?.server ?? ref.read(authProvider).serverLoginModel?.tempCredentials.server ?? "";
|
||||
}
|
||||
|
||||
String getUserImageUrl(String id) {
|
||||
|
|
|
|||
0
lib/providers/lock_screen_provider.dart
Normal file
0
lib/providers/lock_screen_provider.dart
Normal file
|
|
@ -77,7 +77,7 @@ class JellyService {
|
|||
final JellyfinOpenApi _api;
|
||||
|
||||
JellyfinOpenApi get api {
|
||||
var authServer = ref.read(authProvider).tempCredentials.server;
|
||||
var authServer = ref.read(authProvider).serverLoginModel?.tempCredentials.server ?? "";
|
||||
var currentServer = ref.read(userProvider)?.credentials.server;
|
||||
if ((authServer.isNotEmpty ? authServer : currentServer) == FakeHelper.fakeTestServerUrl) {
|
||||
return FakeJellyfinOpenApi();
|
||||
|
|
@ -1126,6 +1126,8 @@ class JellyService {
|
|||
|
||||
Future<Response<bool>> quickConnectEnabled() async => api.quickConnectEnabledGet();
|
||||
|
||||
Future<Response<BrandingOptions>> getBranding() async => api.brandingConfigurationGet();
|
||||
|
||||
Future<Response<dynamic>> deleteItem(String itemId) => api.itemsItemIdDelete(itemId: itemId);
|
||||
|
||||
Future<UserConfiguration?> _updateUserConfiguration(UserConfiguration newUserConfiguration) async {
|
||||
|
|
@ -1161,6 +1163,22 @@ class JellyService {
|
|||
);
|
||||
return _updateUserConfiguration(updated);
|
||||
}
|
||||
|
||||
Future<Response<QuickConnectResult>> quickConnectInitiate() async {
|
||||
return api.quickConnectInitiatePost();
|
||||
}
|
||||
|
||||
Future<Response<QuickConnectResult>> quickConnectConnectGet({
|
||||
String? secret,
|
||||
}) async {
|
||||
return api.quickConnectConnectGet(secret: secret);
|
||||
}
|
||||
|
||||
Future<Response<AuthenticationResult>> quickConnectAuthenticate(String secret) async {
|
||||
return api.usersAuthenticateWithQuickConnectPost(
|
||||
body: QuickConnectDto(secret: secret),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension ParsedMap on Map<String, dynamic> {
|
||||
|
|
|
|||
|
|
@ -5,10 +5,13 @@ import 'package:collection/collection.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:screen_brightness/screen_brightness.dart';
|
||||
|
||||
import 'package:fladder/models/items/media_segments_model.dart';
|
||||
import 'package:fladder/models/settings/key_combinations.dart';
|
||||
import 'package:fladder/models/settings/video_player_settings.dart';
|
||||
import 'package:fladder/providers/shared_provider.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/providers/video_player_provider.dart';
|
||||
import 'package:fladder/src/player_settings_helper.g.dart' as pigeon;
|
||||
|
||||
final videoPlayerSettingsProvider =
|
||||
StateNotifierProvider<VideoPlayerSettingsProviderNotifier, VideoPlayerSettingsModel>((ref) {
|
||||
|
|
@ -30,6 +33,30 @@ class VideoPlayerSettingsProviderNotifier extends StateNotifier<VideoPlayerSetti
|
|||
if (!oldState.playerSame(value)) {
|
||||
ref.read(videoPlayerProvider.notifier).init();
|
||||
}
|
||||
final userData = ref.read(userProvider);
|
||||
pigeon.PlayerSettingsPigeon().sendPlayerSettings(
|
||||
pigeon.PlayerSettings(
|
||||
skipTypes: value.segmentSkipSettings.map(
|
||||
(key, value) => MapEntry(
|
||||
switch (key) {
|
||||
MediaSegmentType.unknown => pigeon.SegmentType.intro,
|
||||
MediaSegmentType.commercial => pigeon.SegmentType.commercial,
|
||||
MediaSegmentType.preview => pigeon.SegmentType.preview,
|
||||
MediaSegmentType.recap => pigeon.SegmentType.recap,
|
||||
MediaSegmentType.outro => pigeon.SegmentType.outro,
|
||||
MediaSegmentType.intro => pigeon.SegmentType.intro,
|
||||
},
|
||||
switch (value) {
|
||||
SegmentSkip.none => pigeon.SegmentSkip.none,
|
||||
SegmentSkip.askToSkip => pigeon.SegmentSkip.ask,
|
||||
SegmentSkip.skip => pigeon.SegmentSkip.skip,
|
||||
},
|
||||
),
|
||||
),
|
||||
skipBackward: (userData?.userSettings?.skipBackDuration ?? const Duration(seconds: 15)).inMilliseconds,
|
||||
skipForward: (userData?.userSettings?.skipForwardDuration ?? const Duration(seconds: 30)).inMilliseconds,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void setScreenBrightness(double? value) async {
|
||||
|
|
|
|||
|
|
@ -55,10 +55,30 @@ class SharedUtility {
|
|||
}
|
||||
|
||||
Future<bool?> addAccount(AccountModel account) async {
|
||||
return await saveAccounts(getAccounts()
|
||||
..add(account.copyWith(
|
||||
lastUsed: DateTime.now(),
|
||||
)));
|
||||
final newAccount = account.copyWith(
|
||||
lastUsed: DateTime.now(),
|
||||
);
|
||||
|
||||
List<AccountModel> accounts = getAccounts().toList();
|
||||
if (accounts.any((element) => element.sameIdentity(newAccount))) {
|
||||
accounts = accounts
|
||||
.map(
|
||||
(e) => e.sameIdentity(newAccount)
|
||||
? e.copyWith(
|
||||
credentials: newAccount.credentials,
|
||||
lastUsed: newAccount.lastUsed,
|
||||
)
|
||||
: e,
|
||||
)
|
||||
.toList();
|
||||
} else {
|
||||
accounts = [
|
||||
...accounts,
|
||||
newAccount,
|
||||
];
|
||||
}
|
||||
|
||||
return await saveAccounts(accounts);
|
||||
}
|
||||
|
||||
Future<bool?> removeAccount(AccountModel? account) async {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/media_playback_model.dart';
|
||||
|
|
@ -77,6 +79,8 @@ class VideoPlayerNotifier extends StateNotifier<MediaControlsWrapper> {
|
|||
mediaState.update(
|
||||
(state) => state.playing == event ? state : state.copyWith(playing: event),
|
||||
);
|
||||
final currentState = playbackState;
|
||||
ref.read(playBackModel)?.updatePlaybackPosition(currentState.position, playbackState.playing, ref);
|
||||
}
|
||||
|
||||
Future<void> updatePosition(Duration event) async {
|
||||
|
|
@ -105,7 +109,7 @@ class VideoPlayerNotifier extends StateNotifier<MediaControlsWrapper> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<bool> loadPlaybackItem(PlaybackModel model, {Duration? startPosition}) async {
|
||||
Future<bool> loadPlaybackItem(PlaybackModel model, Duration startPosition) async {
|
||||
await state.stop();
|
||||
mediaState
|
||||
.update((state) => state.copyWith(state: VideoPlayerState.fullScreen, buffering: true, errorPlaying: false));
|
||||
|
|
@ -114,13 +118,13 @@ class VideoPlayerNotifier extends StateNotifier<MediaControlsWrapper> {
|
|||
PlaybackModel? newPlaybackModel = model;
|
||||
|
||||
if (media != null) {
|
||||
await state.open(media.url, false);
|
||||
await state.loadVideo(model, startPosition, false);
|
||||
await state.setVolume(ref.read(videoPlayerSettingsProvider).volume);
|
||||
state.stateStream?.takeWhile((event) => event.buffering == true).listen(
|
||||
null,
|
||||
onDone: () async {
|
||||
final start = startPosition ?? await model.startDuration();
|
||||
if (start != null) {
|
||||
final start = startPosition;
|
||||
if (start != Duration.zero) {
|
||||
await state.seek(start);
|
||||
}
|
||||
await state.setAudioTrack(null, model);
|
||||
|
|
@ -138,4 +142,6 @@ class VideoPlayerNotifier extends StateNotifier<MediaControlsWrapper> {
|
|||
mediaState.update((state) => state.copyWith(errorPlaying: true));
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> openPlayer(BuildContext context) async => state.openPlayer(context);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ class ViewsNotifier extends StateNotifier<ViewsModel> {
|
|||
ItemFields.candelete,
|
||||
ItemFields.candownload,
|
||||
ItemFields.primaryimageaspectratio,
|
||||
ItemFields.overview,
|
||||
],
|
||||
);
|
||||
return e.copyWith(recentlyAdded: recents.body?.map((e) => ItemBaseModel.fromBaseDto(e, ref)).toList());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue