mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 20:25:32 -04:00
optimized seeking, cleanup
This commit is contained in:
parent
b461318641
commit
284f8c035e
@ -47,7 +47,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
this.initialIndex = 0,
|
this.initialIndex = 0,
|
||||||
this.heroOffset = 0,
|
this.heroOffset = 0,
|
||||||
this.showStack = false,
|
this.showStack = false,
|
||||||
}) : controller = PageController(initialPage: initialIndex, keepPage: false);
|
}) : controller = PageController(initialPage: initialIndex);
|
||||||
|
|
||||||
final PageController controller;
|
final PageController controller;
|
||||||
|
|
||||||
@ -328,7 +328,9 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
final ImageProvider provider =
|
final ImageProvider provider =
|
||||||
ImmichImage.imageProvider(asset: a);
|
ImmichImage.imageProvider(asset: a);
|
||||||
|
|
||||||
|
ref.read(videoPlaybackValueProvider.notifier).reset();
|
||||||
if (a.isImage && !isPlayingVideo.value) {
|
if (a.isImage && !isPlayingVideo.value) {
|
||||||
|
ref.read(showControlsProvider.notifier).show = false;
|
||||||
return PhotoViewGalleryPageOptions(
|
return PhotoViewGalleryPageOptions(
|
||||||
onDragStart: (_, details, __) {
|
onDragStart: (_, details, __) {
|
||||||
log.info('Drag start');
|
log.info('Drag start');
|
||||||
@ -363,13 +365,11 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
log.info('Loading asset ${a.id} (index $index) as video');
|
log.info('Loading asset ${a.id} (index $index) as video');
|
||||||
ref.read(videoPlaybackValueProvider.notifier).value =
|
|
||||||
VideoPlaybackValue.uninitialized();
|
|
||||||
return PhotoViewGalleryPageOptions.customChild(
|
return PhotoViewGalleryPageOptions.customChild(
|
||||||
// onDragStart: (_, details, __) =>
|
onDragStart: (_, details, __) =>
|
||||||
// localPosition.value = details.localPosition,
|
localPosition.value = details.localPosition,
|
||||||
// onDragUpdate: (_, details, __) =>
|
onDragUpdate: (_, details, __) =>
|
||||||
// handleSwipeUpDown(details),
|
handleSwipeUpDown(details),
|
||||||
// heroAttributes: PhotoViewHeroAttributes(
|
// heroAttributes: PhotoViewHeroAttributes(
|
||||||
// tag: isFromDto
|
// tag: isFromDto
|
||||||
// ? '${currentAsset.remoteId}-$heroOffset'
|
// ? '${currentAsset.remoteId}-$heroOffset'
|
||||||
|
@ -173,9 +173,8 @@ class NativeVideoLoader extends HookConsumerWidget {
|
|||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
behavior: HitTestBehavior.deferToChild,
|
behavior: HitTestBehavior.deferToChild,
|
||||||
child: PopScope(
|
child: PopScope(
|
||||||
onPopInvokedWithResult: (didPop, _) => ref
|
onPopInvokedWithResult: (didPop, _) =>
|
||||||
.read(videoPlaybackValueProvider.notifier)
|
ref.read(videoPlaybackValueProvider.notifier).reset(),
|
||||||
.value = VideoPlaybackValue.uninitialized(),
|
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: size.height,
|
height: size.height,
|
||||||
width: size.width,
|
width: size.width,
|
||||||
|
@ -12,23 +12,6 @@ import 'package:logging/logging.dart';
|
|||||||
import 'package:native_video_player/native_video_player.dart';
|
import 'package:native_video_player/native_video_player.dart';
|
||||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
|
|
||||||
// @override
|
|
||||||
// void dispose() {
|
|
||||||
// bufferingTimer.value.cancel();
|
|
||||||
// try {
|
|
||||||
// controller.value?.onPlaybackPositionChanged
|
|
||||||
// .removeListener(onPlaybackPositionChanged);
|
|
||||||
// controller.value?.onPlaybackStatusChanged
|
|
||||||
// .removeListener(onPlaybackPositionChanged);
|
|
||||||
// controller.value?.onPlaybackReady.removeListener(onPlaybackReady);
|
|
||||||
// controller.value?.onPlaybackEnded.removeListener(onPlaybackEnded);
|
|
||||||
// controller.value?.stop();
|
|
||||||
// } catch (_) {
|
|
||||||
// // Consume error from the controller
|
|
||||||
// }
|
|
||||||
// super.dispose();
|
|
||||||
// }
|
|
||||||
|
|
||||||
class NativeVideoViewerPage extends HookConsumerWidget {
|
class NativeVideoViewerPage extends HookConsumerWidget {
|
||||||
final VideoSource videoSource;
|
final VideoSource videoSource;
|
||||||
final double aspectRatio;
|
final double aspectRatio;
|
||||||
@ -75,7 +58,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),
|
||||||
@ -86,19 +69,22 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final playbackInfo = playerController.playbackInfo;
|
||||||
|
if (playbackInfo == null) {
|
||||||
|
log.info('No playback info to update');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (mute) {
|
if (mute && playbackInfo.volume != 0.0) {
|
||||||
log.info('Muting video');
|
log.info('Muting video');
|
||||||
playerController.setVolume(0.0);
|
playerController.setVolume(0.0);
|
||||||
log.info('Muted video');
|
} else if (!mute && playbackInfo.volume != 0.7) {
|
||||||
} else {
|
|
||||||
log.info('Unmuting video');
|
log.info('Unmuting video');
|
||||||
playerController.setVolume(0.7);
|
playerController.setVolume(0.7);
|
||||||
log.info('Unmuted video');
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.severe('Error setting volume: $error');
|
log.severe('Error setting volume: $error');
|
||||||
// Consume error from the controller
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -108,20 +94,28 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
|||||||
final playerController = controller.value;
|
final playerController = controller.value;
|
||||||
if (playerController == null) {
|
if (playerController == null) {
|
||||||
log.info('No controller to seek to');
|
log.info('No controller to seek to');
|
||||||
// No seeeking if there is no video
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final playbackInfo = playerController.playbackInfo;
|
||||||
|
if (playbackInfo == null) {
|
||||||
|
log.info('No playback info to update');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the position to seek to
|
// Find the position to seek to
|
||||||
final Duration seek = duration * (position / 100.0);
|
final int seek = (duration * (position / 100.0)).inSeconds;
|
||||||
try {
|
if (seek != playbackInfo.position) {
|
||||||
log.info('Seeking to position: ${seek.inSeconds}');
|
log.info('Seeking to position: $seek from ${playbackInfo.position}');
|
||||||
playerController.seekTo(seek.inSeconds);
|
try {
|
||||||
log.info('Seeked to position: ${seek.inSeconds}');
|
playerController.seekTo(seek);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.severe('Error seeking to position $position: $error');
|
log.severe('Error seeking to position $position: $error');
|
||||||
// Consume error from the controller
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ref.read(videoPlaybackValueProvider.notifier).position =
|
||||||
|
Duration(seconds: seek);
|
||||||
});
|
});
|
||||||
|
|
||||||
// // When the custom video controls pause or play
|
// // When the custom video controls pause or play
|
||||||
@ -131,15 +125,14 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
|||||||
if (pause) {
|
if (pause) {
|
||||||
log.info('Pausing video');
|
log.info('Pausing video');
|
||||||
controller.value?.pause();
|
controller.value?.pause();
|
||||||
log.info('Paused video');
|
WakelockPlus.disable();
|
||||||
} else {
|
} else {
|
||||||
log.info('Playing video');
|
log.info('Playing video');
|
||||||
controller.value?.play();
|
controller.value?.play();
|
||||||
log.info('Played video');
|
WakelockPlus.enable();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.severe('Error pausing or playing video: $error');
|
log.severe('Error pausing or playing video: $error');
|
||||||
// Consume error from the controller
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -148,69 +141,77 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
|||||||
log.info('onPlaybackReady: Playing video');
|
log.info('onPlaybackReady: Playing video');
|
||||||
controller.value?.play();
|
controller.value?.play();
|
||||||
controller.value?.setVolume(0.9);
|
controller.value?.setVolume(0.9);
|
||||||
log.info('onPlaybackReady: Played video');
|
WakelockPlus.enable();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.severe('Error playing video: $error');
|
log.severe('Error playing video: $error');
|
||||||
// Consume error from the controller
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onPlaybackPositionChanged() {
|
void onPlaybackStatusChanged() {
|
||||||
if (controller.value == null || !context.mounted) {
|
final videoController = controller.value;
|
||||||
|
if (videoController == null || !context.mounted) {
|
||||||
log.info('No controller to update');
|
log.info('No controller to update');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info('Updating video playback');
|
|
||||||
final videoPlayback =
|
final videoPlayback =
|
||||||
VideoPlaybackValue.fromNativeController(controller.value!);
|
VideoPlaybackValue.fromNativeController(controller.value!);
|
||||||
ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback;
|
ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback;
|
||||||
log.info('Updated video playback');
|
|
||||||
|
if (videoPlayback.state == VideoPlaybackState.playing) {
|
||||||
|
// Sync with the controls playing
|
||||||
|
WakelockPlus.enable();
|
||||||
|
log.info('Video is playing; enabled wakelock');
|
||||||
|
} else {
|
||||||
|
// Sync with the controls pause
|
||||||
|
WakelockPlus.disable();
|
||||||
|
log.info('Video is not playing; disabled wakelock');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onPlaybackPositionChanged() {
|
||||||
|
final videoController = controller.value;
|
||||||
|
if (videoController == null || !context.mounted) {
|
||||||
|
log.info('No controller to update');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final playbackInfo = videoController.playbackInfo;
|
||||||
|
if (playbackInfo == null) {
|
||||||
|
log.info('No playback info to update');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref.read(videoPlaybackValueProvider.notifier).position =
|
||||||
|
Duration(seconds: playbackInfo.position);
|
||||||
|
|
||||||
// Check if the video is buffering
|
// Check if the video is buffering
|
||||||
if (videoPlayback.state == VideoPlaybackState.playing) {
|
if (playbackInfo.status == PlaybackStatus.playing) {
|
||||||
log.info('Updating video: checking if playing video is buffering');
|
|
||||||
isBuffering.value =
|
|
||||||
lastVideoPosition.value == videoPlayback.position.inSeconds;
|
|
||||||
lastVideoPosition.value = videoPlayback.position.inSeconds;
|
|
||||||
log.info('Updating playing video position');
|
log.info('Updating playing video position');
|
||||||
|
isBuffering.value = lastVideoPosition.value == playbackInfo.position;
|
||||||
|
lastVideoPosition.value = playbackInfo.position;
|
||||||
} else {
|
} else {
|
||||||
log.info('Updating video: video is not playing');
|
log.info('Updating non-playing video position');
|
||||||
isBuffering.value = false;
|
isBuffering.value = false;
|
||||||
lastVideoPosition.value = -1;
|
lastVideoPosition.value = -1;
|
||||||
log.info('Updated non-playing video position');
|
|
||||||
}
|
|
||||||
final state = videoPlayback.state;
|
|
||||||
|
|
||||||
// Enable the WakeLock while the video is playing
|
|
||||||
if (state == VideoPlaybackState.playing) {
|
|
||||||
log.info('Syncing with the controls playing');
|
|
||||||
// Sync with the controls playing
|
|
||||||
// WakelockPlus.enable();
|
|
||||||
log.info('Synced with the controls playing');
|
|
||||||
} else {
|
|
||||||
log.info('Syncing with the controls pause');
|
|
||||||
// Sync with the controls pause
|
|
||||||
// WakelockPlus.disable();
|
|
||||||
log.info('Synced with the controls pause');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onPlaybackEnded() {
|
void onPlaybackEnded() {
|
||||||
try {
|
log.info('onPlaybackEnded: Video ended');
|
||||||
log.info('onPlaybackEnded: Video ended');
|
if (loopVideo) {
|
||||||
if (loopVideo) {
|
log.info('onPlaybackEnded: Looping video');
|
||||||
log.info('onPlaybackEnded: Looping video');
|
try {
|
||||||
controller.value?.play();
|
controller.value?.play();
|
||||||
log.info('onPlaybackEnded: Looped video');
|
} catch (error) {
|
||||||
|
log.severe('Error looping video: $error');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} else {
|
||||||
log.severe('Error looping video: $error');
|
WakelockPlus.disable();
|
||||||
// Consume error from the controller
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initController(NativeVideoPlayerController nc) async {
|
void initController(NativeVideoPlayerController nc) {
|
||||||
if (controller.value != null) {
|
if (controller.value != null) {
|
||||||
log.info('initController: Controller already initialized');
|
log.info('initController: Controller already initialized');
|
||||||
return;
|
return;
|
||||||
@ -218,27 +219,21 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
log.info('initController: adding onPlaybackPositionChanged listener');
|
log.info('initController: adding onPlaybackPositionChanged listener');
|
||||||
nc.onPlaybackPositionChanged.addListener(onPlaybackPositionChanged);
|
nc.onPlaybackPositionChanged.addListener(onPlaybackPositionChanged);
|
||||||
log.info('initController: added onPlaybackPositionChanged listener');
|
|
||||||
|
|
||||||
log.info('initController: adding onPlaybackStatusChanged listener');
|
log.info('initController: adding onPlaybackStatusChanged listener');
|
||||||
nc.onPlaybackStatusChanged.addListener(onPlaybackPositionChanged);
|
nc.onPlaybackStatusChanged.addListener(onPlaybackStatusChanged);
|
||||||
log.info('initController: added onPlaybackStatusChanged listener');
|
|
||||||
|
|
||||||
log.info('initController: adding onPlaybackReady listener');
|
log.info('initController: adding onPlaybackReady listener');
|
||||||
nc.onPlaybackReady.addListener(onPlaybackReady);
|
nc.onPlaybackReady.addListener(onPlaybackReady);
|
||||||
log.info('initController: added onPlaybackReady listener');
|
|
||||||
|
|
||||||
log.info('initController: adding onPlaybackEnded listener');
|
log.info('initController: adding onPlaybackEnded listener');
|
||||||
nc.onPlaybackEnded.addListener(onPlaybackEnded);
|
nc.onPlaybackEnded.addListener(onPlaybackEnded);
|
||||||
log.info('initController: added onPlaybackEnded listener');
|
|
||||||
|
|
||||||
log.info('initController: loading video source');
|
log.info('initController: loading video source');
|
||||||
nc.loadVideoSource(videoSource);
|
nc.loadVideoSource(videoSource);
|
||||||
log.info('initController: loaded video source');
|
|
||||||
|
|
||||||
log.info('initController: setting controller');
|
log.info('initController: setting controller');
|
||||||
controller.value = nc;
|
controller.value = nc;
|
||||||
log.info('initController: set controller');
|
|
||||||
Timer(const Duration(milliseconds: 200), checkIfBuffering);
|
Timer(const Duration(milliseconds: 200), checkIfBuffering);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,55 +241,45 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
|||||||
() {
|
() {
|
||||||
log.info('useEffect: resetting video player controls');
|
log.info('useEffect: resetting video player controls');
|
||||||
ref.read(videoPlayerControlsProvider.notifier).reset();
|
ref.read(videoPlayerControlsProvider.notifier).reset();
|
||||||
log.info('useEffect: resetting video player controls');
|
|
||||||
|
|
||||||
if (isMotionVideo) {
|
if (isMotionVideo) {
|
||||||
// ignore: prefer-extracting-callbacks
|
// ignore: prefer-extracting-callbacks
|
||||||
log.info('useEffect: disabled showing video player controls');
|
log.info('useEffect: disabling showing video player controls');
|
||||||
ref.read(showControlsProvider.notifier).show = false;
|
ref.read(showControlsProvider.notifier).show = false;
|
||||||
log.info('useEffect: disabled showing video player controls');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return () {
|
return () {
|
||||||
|
final playerController = controller.value;
|
||||||
|
if (playerController == null) {
|
||||||
|
log.info('No controller to dispose');
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
final playerController = controller.value;
|
log.info('Stopping video');
|
||||||
if (playerController == null) {
|
playerController.stop();
|
||||||
log.info('No controller to dispose');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log.info('Removing onPlaybackPositionChanged listener');
|
log.info('Removing onPlaybackPositionChanged listener');
|
||||||
playerController.onPlaybackPositionChanged
|
playerController.onPlaybackPositionChanged
|
||||||
.removeListener(onPlaybackPositionChanged);
|
.removeListener(onPlaybackPositionChanged);
|
||||||
log.info('Removed onPlaybackPositionChanged listener');
|
|
||||||
|
|
||||||
log.info('Removing onPlaybackStatusChanged listener');
|
log.info('Removing onPlaybackStatusChanged listener');
|
||||||
playerController.onPlaybackStatusChanged
|
playerController.onPlaybackStatusChanged
|
||||||
.removeListener(onPlaybackPositionChanged);
|
.removeListener(onPlaybackStatusChanged);
|
||||||
log.info('Removed onPlaybackStatusChanged listener');
|
|
||||||
|
|
||||||
log.info('Removing onPlaybackReady listener');
|
log.info('Removing onPlaybackReady listener');
|
||||||
playerController.onPlaybackReady.removeListener(onPlaybackReady);
|
playerController.onPlaybackReady.removeListener(onPlaybackReady);
|
||||||
log.info('Removed onPlaybackReady listener');
|
|
||||||
|
|
||||||
log.info('Removing onPlaybackEnded listener');
|
log.info('Removing onPlaybackEnded listener');
|
||||||
playerController.onPlaybackEnded.removeListener(onPlaybackEnded);
|
playerController.onPlaybackEnded.removeListener(onPlaybackEnded);
|
||||||
log.info('Removed onPlaybackEnded listener');
|
|
||||||
|
|
||||||
log.info('Stopping video');
|
|
||||||
playerController.stop();
|
|
||||||
log.info('Stopped video');
|
|
||||||
|
|
||||||
log.info('Disposing controller');
|
|
||||||
controller.value = null;
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info('Disposing controller');
|
||||||
|
controller.value = null;
|
||||||
|
|
||||||
|
log.info('Disabling Wakelock');
|
||||||
|
WakelockPlus.disable();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[videoSource],
|
[videoSource],
|
||||||
|
@ -124,8 +124,7 @@ class VideoViewerPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
return PopScope(
|
return PopScope(
|
||||||
onPopInvokedWithResult: (didPop, _) {
|
onPopInvokedWithResult: (didPop, _) {
|
||||||
ref.read(videoPlaybackValueProvider.notifier).value =
|
ref.read(videoPlaybackValueProvider.notifier).reset();
|
||||||
VideoPlaybackValue.uninitialized();
|
|
||||||
},
|
},
|
||||||
child: AnimatedSwitcher(
|
child: AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 400),
|
duration: const Duration(milliseconds: 400),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
class VideoPlaybackControls {
|
class VideoPlaybackControls {
|
||||||
VideoPlaybackControls({
|
const VideoPlaybackControls({
|
||||||
required this.position,
|
required this.position,
|
||||||
required this.mute,
|
required this.mute,
|
||||||
required this.pause,
|
required this.pause,
|
||||||
@ -17,15 +17,14 @@ final videoPlayerControlsProvider =
|
|||||||
return VideoPlayerControls(ref);
|
return VideoPlayerControls(ref);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const videoPlayerControlsDefault = VideoPlaybackControls(
|
||||||
|
position: 0,
|
||||||
|
pause: false,
|
||||||
|
mute: false,
|
||||||
|
);
|
||||||
|
|
||||||
class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
|
class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
|
||||||
VideoPlayerControls(this.ref)
|
VideoPlayerControls(this.ref) : super(videoPlayerControlsDefault);
|
||||||
: super(
|
|
||||||
VideoPlaybackControls(
|
|
||||||
position: 0,
|
|
||||||
pause: false,
|
|
||||||
mute: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final Ref ref;
|
final Ref ref;
|
||||||
|
|
||||||
@ -36,15 +35,7 @@ class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
if (state.position == 0 && !state.mute && !state.pause) {
|
state = videoPlayerControlsDefault;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
state = VideoPlaybackControls(
|
|
||||||
position: 0,
|
|
||||||
pause: false,
|
|
||||||
mute: false,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double get position => state.position;
|
double get position => state.position;
|
||||||
@ -115,14 +106,6 @@ class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void restart() {
|
void restart() {
|
||||||
if (state.position > 0 || !state.pause) {
|
|
||||||
state = VideoPlaybackControls(
|
|
||||||
position: 0,
|
|
||||||
mute: state.mute,
|
|
||||||
pause: false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
state = VideoPlaybackControls(
|
state = VideoPlaybackControls(
|
||||||
position: 0,
|
position: 0,
|
||||||
mute: state.mute,
|
mute: state.mute,
|
||||||
|
@ -23,7 +23,7 @@ class VideoPlaybackValue {
|
|||||||
/// The volume of the video
|
/// The volume of the video
|
||||||
final double volume;
|
final double volume;
|
||||||
|
|
||||||
VideoPlaybackValue({
|
const VideoPlaybackValue({
|
||||||
required this.position,
|
required this.position,
|
||||||
required this.duration,
|
required this.duration,
|
||||||
required this.state,
|
required this.state,
|
||||||
@ -33,32 +33,31 @@ class VideoPlaybackValue {
|
|||||||
factory VideoPlaybackValue.fromNativeController(
|
factory VideoPlaybackValue.fromNativeController(
|
||||||
NativeVideoPlayerController controller,
|
NativeVideoPlayerController controller,
|
||||||
) {
|
) {
|
||||||
PlaybackInfo? playbackInfo;
|
final playbackInfo = controller.playbackInfo;
|
||||||
VideoInfo? videoInfo;
|
final videoInfo = controller.videoInfo;
|
||||||
try {
|
|
||||||
playbackInfo = controller.playbackInfo;
|
if (playbackInfo == null || videoInfo == null) {
|
||||||
videoInfo = controller.videoInfo;
|
return videoPlaybackValueDefault;
|
||||||
} catch (_) {
|
|
||||||
// Consume error from the controller
|
|
||||||
}
|
}
|
||||||
late VideoPlaybackState s;
|
|
||||||
if (playbackInfo?.status == null) {
|
late final VideoPlaybackState status;
|
||||||
s = VideoPlaybackState.initializing;
|
switch (playbackInfo.status) {
|
||||||
} else if (playbackInfo?.status == PlaybackStatus.stopped &&
|
case PlaybackStatus.playing:
|
||||||
(playbackInfo?.positionFraction == 1 ||
|
status = VideoPlaybackState.playing;
|
||||||
playbackInfo?.positionFraction == 0)) {
|
break;
|
||||||
s = VideoPlaybackState.completed;
|
case PlaybackStatus.paused:
|
||||||
} else if (playbackInfo?.status == PlaybackStatus.playing) {
|
status = VideoPlaybackState.paused;
|
||||||
s = VideoPlaybackState.playing;
|
break;
|
||||||
} else {
|
case PlaybackStatus.stopped:
|
||||||
s = VideoPlaybackState.paused;
|
status = VideoPlaybackState.completed;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return VideoPlaybackValue(
|
return VideoPlaybackValue(
|
||||||
position: Duration(seconds: playbackInfo?.position ?? 0),
|
position: Duration(seconds: playbackInfo.position),
|
||||||
duration: Duration(seconds: videoInfo?.duration ?? 0),
|
duration: Duration(seconds: videoInfo.duration),
|
||||||
state: s,
|
state: status,
|
||||||
volume: playbackInfo?.volume ?? 0.0,
|
volume: playbackInfo.volume,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,15 +84,6 @@ class VideoPlaybackValue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory VideoPlaybackValue.uninitialized() {
|
|
||||||
return VideoPlaybackValue(
|
|
||||||
position: Duration.zero,
|
|
||||||
duration: Duration.zero,
|
|
||||||
state: VideoPlaybackState.initializing,
|
|
||||||
volume: 0.0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
VideoPlaybackValue copyWith({
|
VideoPlaybackValue copyWith({
|
||||||
Duration? position,
|
Duration? position,
|
||||||
Duration? duration,
|
Duration? duration,
|
||||||
@ -109,16 +99,20 @@ class VideoPlaybackValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VideoPlaybackValue videoPlaybackValueDefault = VideoPlaybackValue(
|
||||||
|
position: Duration.zero,
|
||||||
|
duration: Duration.zero,
|
||||||
|
state: VideoPlaybackState.initializing,
|
||||||
|
volume: 0.0,
|
||||||
|
);
|
||||||
|
|
||||||
final videoPlaybackValueProvider =
|
final videoPlaybackValueProvider =
|
||||||
StateNotifierProvider<VideoPlaybackValueState, VideoPlaybackValue>((ref) {
|
StateNotifierProvider<VideoPlaybackValueState, VideoPlaybackValue>((ref) {
|
||||||
return VideoPlaybackValueState(ref);
|
return VideoPlaybackValueState(ref);
|
||||||
});
|
});
|
||||||
|
|
||||||
class VideoPlaybackValueState extends StateNotifier<VideoPlaybackValue> {
|
class VideoPlaybackValueState extends StateNotifier<VideoPlaybackValue> {
|
||||||
VideoPlaybackValueState(this.ref)
|
VideoPlaybackValueState(this.ref) : super(videoPlaybackValueDefault);
|
||||||
: super(
|
|
||||||
VideoPlaybackValue.uninitialized(),
|
|
||||||
);
|
|
||||||
|
|
||||||
final Ref ref;
|
final Ref ref;
|
||||||
|
|
||||||
@ -129,6 +123,7 @@ class VideoPlaybackValueState extends StateNotifier<VideoPlaybackValue> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set position(Duration value) {
|
set position(Duration value) {
|
||||||
|
if (state.position == value) return;
|
||||||
state = VideoPlaybackValue(
|
state = VideoPlaybackValue(
|
||||||
position: value,
|
position: value,
|
||||||
duration: state.duration,
|
duration: state.duration,
|
||||||
@ -136,4 +131,8 @@ class VideoPlaybackValueState extends StateNotifier<VideoPlaybackValue> {
|
|||||||
volume: state.volume,
|
volume: state.volume,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
state = videoPlaybackValueDefault;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart';
|
||||||
@ -29,10 +28,9 @@ class CustomVideoPlayerControls extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final showBuffering = useState(false);
|
|
||||||
final VideoPlaybackState state =
|
final VideoPlaybackState state =
|
||||||
ref.watch(videoPlaybackValueProvider).state;
|
ref.watch(videoPlaybackValueProvider.select((value) => value.state));
|
||||||
|
final showBuffering = state == VideoPlaybackState.buffering;
|
||||||
|
|
||||||
/// Shows the controls and starts the timer to hide them
|
/// Shows the controls and starts the timer to hide them
|
||||||
void showControlsAndStartHideTimer() {
|
void showControlsAndStartHideTimer() {
|
||||||
@ -52,16 +50,9 @@ class CustomVideoPlayerControls extends HookConsumerWidget {
|
|||||||
showControlsAndStartHideTimer();
|
showControlsAndStartHideTimer();
|
||||||
});
|
});
|
||||||
|
|
||||||
ref.listen(videoPlaybackValueProvider.select((value) => value.state),
|
|
||||||
(_, state) {
|
|
||||||
// Show buffering
|
|
||||||
showBuffering.value = state == VideoPlaybackState.buffering;
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Toggles between playing and pausing depending on the state of the video
|
/// Toggles between playing and pausing depending on the state of the video
|
||||||
void togglePlay() {
|
void togglePlay() {
|
||||||
showControlsAndStartHideTimer();
|
showControlsAndStartHideTimer();
|
||||||
final state = ref.read(videoPlaybackValueProvider).state;
|
|
||||||
if (state == VideoPlaybackState.playing) {
|
if (state == VideoPlaybackState.playing) {
|
||||||
ref.read(videoPlayerControlsProvider.notifier).pause();
|
ref.read(videoPlayerControlsProvider.notifier).pause();
|
||||||
} else if (state == VideoPlaybackState.completed) {
|
} else if (state == VideoPlaybackState.completed) {
|
||||||
@ -78,7 +69,7 @@ class CustomVideoPlayerControls extends HookConsumerWidget {
|
|||||||
absorbing: !ref.watch(showControlsProvider),
|
absorbing: !ref.watch(showControlsProvider),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
if (showBuffering.value)
|
if (showBuffering)
|
||||||
const Center(
|
const Center(
|
||||||
child: DelayedLoadingIndicator(
|
child: DelayedLoadingIndicator(
|
||||||
fadeInDuration: Duration(milliseconds: 400),
|
fadeInDuration: Duration(milliseconds: 400),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user