mirror of
https://github.com/immich-app/immich.git
synced 2025-03-11 02:23:09 -05:00
separate video loader widget
This commit is contained in:
parent
338e5a8e5c
commit
7ed2c68c46
3 changed files with 79 additions and 47 deletions
|
@ -33,25 +33,28 @@ class NativeVideoLoader extends HookConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final log = Logger('NativeVideoLoader');
|
||||||
|
log.info('Building NativeVideoLoader');
|
||||||
// fast path for aspect ratio
|
// fast path for aspect ratio
|
||||||
final initAspectRatio = useMemoized(
|
// final initAspectRatio = useMemoized(
|
||||||
() {
|
// () {
|
||||||
if (asset.exifInfo == null) {
|
// if (asset.exifInfo == null) {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
|
|
||||||
final width = asset.orientatedWidth?.toDouble();
|
// final width = asset.orientatedWidth?.toDouble();
|
||||||
final height = asset.orientatedHeight?.toDouble();
|
// final height = asset.orientatedHeight?.toDouble();
|
||||||
return width != null && height != null && width > 0 && height > 0
|
// return width != null && height != null && width > 0 && height > 0
|
||||||
? width / height
|
// ? width / height
|
||||||
: null;
|
// : null;
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
|
|
||||||
final localEntity = useMemoized(
|
final localEntity = useMemoized(
|
||||||
() => asset.localId != null ? AssetEntity.fromId(asset.localId!) : null,
|
() => asset.isLocal ? AssetEntity.fromId(asset.localId!) : null,
|
||||||
);
|
);
|
||||||
Future<double> calculateAspectRatio() async {
|
Future<double> calculateAspectRatio() async {
|
||||||
|
log.info('Calculating aspect ratio');
|
||||||
late final double? orientatedWidth;
|
late final double? orientatedWidth;
|
||||||
late final double? orientatedHeight;
|
late final double? orientatedHeight;
|
||||||
|
|
||||||
|
@ -68,6 +71,7 @@ class NativeVideoLoader extends HookConsumerWidget {
|
||||||
orientatedHeight = entity.orientatedHeight?.toDouble();
|
orientatedHeight = entity.orientatedHeight?.toDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info('Calculated aspect ratio');
|
||||||
if (orientatedWidth != null &&
|
if (orientatedWidth != null &&
|
||||||
orientatedHeight != null &&
|
orientatedHeight != null &&
|
||||||
orientatedWidth > 0 &&
|
orientatedWidth > 0 &&
|
||||||
|
@ -78,12 +82,7 @@ class NativeVideoLoader extends HookConsumerWidget {
|
||||||
return 1.0;
|
return 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
final aspectRatioFuture = useMemoized(
|
final aspectRatioFuture = useMemoized(() => calculateAspectRatio());
|
||||||
() => initAspectRatio == null ? calculateAspectRatio() : null,
|
|
||||||
);
|
|
||||||
|
|
||||||
final log = Logger('NativeVideoLoader');
|
|
||||||
log.info('Building NativeVideoLoader');
|
|
||||||
|
|
||||||
Future<VideoSource> createLocalSource() async {
|
Future<VideoSource> createLocalSource() async {
|
||||||
log.info('Loading video from local storage');
|
log.info('Loading video from local storage');
|
||||||
|
@ -97,10 +96,12 @@ class NativeVideoLoader extends HookConsumerWidget {
|
||||||
throw Exception('No file found for the video');
|
throw Exception('No file found for the video');
|
||||||
}
|
}
|
||||||
|
|
||||||
return await VideoSource.init(
|
final source = await VideoSource.init(
|
||||||
path: file.path,
|
path: file.path,
|
||||||
type: VideoSourceType.file,
|
type: VideoSourceType.file,
|
||||||
);
|
);
|
||||||
|
log.info('Loaded video from local storage');
|
||||||
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<VideoSource> createRemoteSource() async {
|
Future<VideoSource> createRemoteSource() async {
|
||||||
|
@ -112,14 +113,16 @@ class NativeVideoLoader extends HookConsumerWidget {
|
||||||
? '$serverEndpoint/assets/${asset.livePhotoVideoId}/video/playback'
|
? '$serverEndpoint/assets/${asset.livePhotoVideoId}/video/playback'
|
||||||
: '$serverEndpoint/assets/${asset.remoteId}/video/playback';
|
: '$serverEndpoint/assets/${asset.remoteId}/video/playback';
|
||||||
|
|
||||||
return await VideoSource.init(
|
final source = await VideoSource.init(
|
||||||
path: videoUrl,
|
path: videoUrl,
|
||||||
type: VideoSourceType.network,
|
type: VideoSourceType.network,
|
||||||
headers: ApiService.getRequestHeaders(),
|
headers: ApiService.getRequestHeaders(),
|
||||||
);
|
);
|
||||||
|
log.info('Loaded video from server');
|
||||||
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<VideoSource> createSource(Asset asset) async {
|
Future<VideoSource> createSource() {
|
||||||
if (asset.isLocal && asset.livePhotoVideoId == null) {
|
if (asset.isLocal && asset.livePhotoVideoId == null) {
|
||||||
return createLocalSource();
|
return createLocalSource();
|
||||||
}
|
}
|
||||||
|
@ -127,6 +130,16 @@ class NativeVideoLoader extends HookConsumerWidget {
|
||||||
return createRemoteSource();
|
return createRemoteSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final createSourceFuture = useMemoized(() => createSource());
|
||||||
|
|
||||||
|
final combinedFuture = useMemoized(
|
||||||
|
() async {
|
||||||
|
final aspectRatio = await aspectRatioFuture;
|
||||||
|
final source = await createSourceFuture;
|
||||||
|
return (source, aspectRatio);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
final size = MediaQuery.sizeOf(context);
|
final size = MediaQuery.sizeOf(context);
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
|
@ -143,18 +156,17 @@ class NativeVideoLoader extends HookConsumerWidget {
|
||||||
width: size.width,
|
width: size.width,
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
key: ValueKey(asset.id),
|
key: ValueKey(asset.id),
|
||||||
future: aspectRatioFuture,
|
future: combinedFuture,
|
||||||
initialData: initAspectRatio,
|
// initialData: initAspectRatio,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return placeholder;
|
return placeholder;
|
||||||
}
|
}
|
||||||
|
|
||||||
return NativeVideoViewerPage(
|
return NativeVideoViewerPage(
|
||||||
key: ValueKey(asset.id),
|
videoSource: snapshot.data!.$1,
|
||||||
videoSource: createSource(asset),
|
|
||||||
duration: asset.duration,
|
duration: asset.duration,
|
||||||
aspectRatio: snapshot.data as double,
|
aspectRatio: snapshot.data!.$2,
|
||||||
isMotionVideo: isMotionVideo,
|
isMotionVideo: isMotionVideo,
|
||||||
hideControlsTimer: hideControlsTimer,
|
hideControlsTimer: hideControlsTimer,
|
||||||
loopVideo: loopVideo,
|
loopVideo: loopVideo,
|
||||||
|
|
|
@ -30,7 +30,7 @@ import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
// }
|
// }
|
||||||
|
|
||||||
class NativeVideoViewerPage extends HookConsumerWidget {
|
class NativeVideoViewerPage extends HookConsumerWidget {
|
||||||
final Future<VideoSource> videoSource;
|
final VideoSource videoSource;
|
||||||
final double aspectRatio;
|
final double aspectRatio;
|
||||||
final Duration duration;
|
final Duration duration;
|
||||||
final bool isMotionVideo;
|
final bool isMotionVideo;
|
||||||
|
@ -75,7 +75,7 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
// timer to mark videos as buffering if the position does not change
|
// timer to mark videos as buffering if the position does not change
|
||||||
useInterval(const Duration(seconds: 5), checkIfBuffering);
|
// useInterval(const Duration(seconds: 5), checkIfBuffering);
|
||||||
|
|
||||||
// When the volume changes, set the volume
|
// When the volume changes, set the volume
|
||||||
ref.listen(videoPlayerControlsProvider.select((value) => value.mute),
|
ref.listen(videoPlayerControlsProvider.select((value) => value.mute),
|
||||||
|
@ -186,12 +186,12 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
||||||
if (state == VideoPlaybackState.playing) {
|
if (state == VideoPlaybackState.playing) {
|
||||||
log.info('Syncing with the controls playing');
|
log.info('Syncing with the controls playing');
|
||||||
// Sync with the controls playing
|
// Sync with the controls playing
|
||||||
WakelockPlus.enable();
|
// WakelockPlus.enable();
|
||||||
log.info('Synced with the controls playing');
|
log.info('Synced with the controls playing');
|
||||||
} else {
|
} else {
|
||||||
log.info('Syncing with the controls pause');
|
log.info('Syncing with the controls pause');
|
||||||
// Sync with the controls pause
|
// Sync with the controls pause
|
||||||
WakelockPlus.disable();
|
// WakelockPlus.disable();
|
||||||
log.info('Synced with the controls pause');
|
log.info('Synced with the controls pause');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,7 +233,7 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
||||||
log.info('initController: added onPlaybackEnded listener');
|
log.info('initController: added onPlaybackEnded listener');
|
||||||
|
|
||||||
log.info('initController: loading video source');
|
log.info('initController: loading video source');
|
||||||
nc.loadVideoSource(await videoSource);
|
nc.loadVideoSource(videoSource);
|
||||||
log.info('initController: loaded video source');
|
log.info('initController: loaded video source');
|
||||||
|
|
||||||
log.info('initController: setting controller');
|
log.info('initController: setting controller');
|
||||||
|
@ -280,26 +280,24 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
||||||
playerController.onPlaybackEnded.removeListener(onPlaybackEnded);
|
playerController.onPlaybackEnded.removeListener(onPlaybackEnded);
|
||||||
log.info('Removed onPlaybackEnded listener');
|
log.info('Removed onPlaybackEnded listener');
|
||||||
|
|
||||||
Future.microtask(() async {
|
log.info('Stopping video');
|
||||||
log.info('Stopping video');
|
playerController.stop();
|
||||||
await playerController.stop();
|
log.info('Stopped video');
|
||||||
log.info('Stopped video');
|
|
||||||
|
|
||||||
log.info('Disabling wakelock');
|
|
||||||
await WakelockPlus.disable();
|
|
||||||
log.info('Disabled wakelock');
|
|
||||||
});
|
|
||||||
|
|
||||||
log.info('Disposing controller');
|
log.info('Disposing controller');
|
||||||
controller.value = null;
|
controller.value = null;
|
||||||
log.info('Disposed controller');
|
log.info('Disposed controller');
|
||||||
|
|
||||||
|
// log.info('Disabling Wakelock');
|
||||||
|
// WakelockPlus.disable();
|
||||||
|
// log.info('Disabled Wakelock');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.severe('Error during useEffect cleanup: $error');
|
log.severe('Error during useEffect cleanup: $error');
|
||||||
// Consume error from the controller
|
// Consume error from the controller
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[],
|
[videoSource],
|
||||||
);
|
);
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
|
|
|
@ -36,6 +36,10 @@ class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
|
if (state.position == 0 && !state.mute && !state.pause) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state = VideoPlaybackControls(
|
state = VideoPlaybackControls(
|
||||||
position: 0,
|
position: 0,
|
||||||
pause: false,
|
pause: false,
|
||||||
|
@ -47,6 +51,10 @@ class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
|
||||||
bool get mute => state.mute;
|
bool get mute => state.mute;
|
||||||
|
|
||||||
set position(double value) {
|
set position(double value) {
|
||||||
|
if (state.position == value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state = VideoPlaybackControls(
|
state = VideoPlaybackControls(
|
||||||
position: value,
|
position: value,
|
||||||
mute: state.mute,
|
mute: state.mute,
|
||||||
|
@ -55,6 +63,10 @@ class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
|
||||||
}
|
}
|
||||||
|
|
||||||
set mute(bool value) {
|
set mute(bool value) {
|
||||||
|
if (state.mute == value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state = VideoPlaybackControls(
|
state = VideoPlaybackControls(
|
||||||
position: state.position,
|
position: state.position,
|
||||||
mute: value,
|
mute: value,
|
||||||
|
@ -71,6 +83,10 @@ class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void pause() {
|
void pause() {
|
||||||
|
if (state.pause) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state = VideoPlaybackControls(
|
state = VideoPlaybackControls(
|
||||||
position: state.position,
|
position: state.position,
|
||||||
mute: state.mute,
|
mute: state.mute,
|
||||||
|
@ -79,6 +95,10 @@ class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void play() {
|
void play() {
|
||||||
|
if (!state.pause) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state = VideoPlaybackControls(
|
state = VideoPlaybackControls(
|
||||||
position: state.position,
|
position: state.position,
|
||||||
mute: state.mute,
|
mute: state.mute,
|
||||||
|
@ -95,11 +115,13 @@ class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void restart() {
|
void restart() {
|
||||||
state = VideoPlaybackControls(
|
if (state.position > 0 || !state.pause) {
|
||||||
position: 0,
|
state = VideoPlaybackControls(
|
||||||
mute: state.mute,
|
position: 0,
|
||||||
pause: true,
|
mute: state.mute,
|
||||||
);
|
pause: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
state = VideoPlaybackControls(
|
state = VideoPlaybackControls(
|
||||||
position: 0,
|
position: 0,
|
||||||
|
|
Loading…
Add table
Reference in a new issue