mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
feat(mobile): high precision seeking (#22346)
* millisecond precision video playback * wrap in unawaited * update commit
This commit is contained in:
parent
78fb815cdb
commit
c73e3dacea
7 changed files with 81 additions and 53 deletions
|
|
@ -46,6 +46,7 @@ bool _isCurrentAsset(BaseAsset asset, BaseAsset? currentAsset) {
|
|||
}
|
||||
|
||||
class NativeVideoViewer extends HookConsumerWidget {
|
||||
static final log = Logger('NativeVideoViewer');
|
||||
final BaseAsset asset;
|
||||
final bool showControls;
|
||||
final int playbackDelayFactor;
|
||||
|
|
@ -79,8 +80,6 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||
// Used to show the placeholder during hero animations for remote videos to avoid a stutter
|
||||
final isVisible = useState(Platform.isIOS && asset.hasLocal);
|
||||
|
||||
final log = Logger('NativeVideoViewerPage');
|
||||
|
||||
final isCasting = ref.watch(castProvider.select((c) => c.isCasting));
|
||||
|
||||
Future<VideoSource?> createSource() async {
|
||||
|
|
@ -169,7 +168,7 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||
interval: const Duration(milliseconds: 100),
|
||||
maxWaitTime: const Duration(milliseconds: 200),
|
||||
);
|
||||
ref.listen(videoPlayerControlsProvider, (oldControls, newControls) async {
|
||||
ref.listen(videoPlayerControlsProvider, (oldControls, newControls) {
|
||||
final playerController = controller.value;
|
||||
if (playerController == null) {
|
||||
return;
|
||||
|
|
@ -180,28 +179,14 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||
return;
|
||||
}
|
||||
|
||||
final oldSeek = (oldControls?.position ?? 0) ~/ 1;
|
||||
final newSeek = newControls.position ~/ 1;
|
||||
final oldSeek = oldControls?.position.inMilliseconds;
|
||||
final newSeek = newControls.position.inMilliseconds;
|
||||
if (oldSeek != newSeek || newControls.restarted) {
|
||||
seekDebouncer.run(() => playerController.seekTo(newSeek));
|
||||
}
|
||||
|
||||
if (oldControls?.pause != newControls.pause || newControls.restarted) {
|
||||
// Make sure the last seek is complete before pausing or playing
|
||||
// Otherwise, `onPlaybackPositionChanged` can receive outdated events
|
||||
if (seekDebouncer.isActive) {
|
||||
await seekDebouncer.drain();
|
||||
}
|
||||
|
||||
try {
|
||||
if (newControls.pause) {
|
||||
await playerController.pause();
|
||||
} else {
|
||||
await playerController.play();
|
||||
}
|
||||
} catch (error) {
|
||||
log.severe('Error pausing or playing video: $error');
|
||||
}
|
||||
unawaited(_onPauseChange(context, playerController, seekDebouncer, newControls.pause));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -263,7 +248,7 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||
return;
|
||||
}
|
||||
|
||||
ref.read(videoPlaybackValueProvider.notifier).position = Duration(seconds: playbackInfo.position);
|
||||
ref.read(videoPlaybackValueProvider.notifier).position = Duration(milliseconds: playbackInfo.position);
|
||||
|
||||
// Check if the video is buffering
|
||||
if (playbackInfo.status == PlaybackStatus.playing) {
|
||||
|
|
@ -422,4 +407,31 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onPauseChange(
|
||||
BuildContext context,
|
||||
NativeVideoPlayerController controller,
|
||||
Debouncer seekDebouncer,
|
||||
bool isPaused,
|
||||
) async {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the last seek is complete before pausing or playing
|
||||
// Otherwise, `onPlaybackPositionChanged` can receive outdated events
|
||||
if (seekDebouncer.isActive) {
|
||||
await seekDebouncer.drain();
|
||||
}
|
||||
|
||||
try {
|
||||
if (isPaused) {
|
||||
await controller.pause();
|
||||
} else {
|
||||
await controller.play();
|
||||
}
|
||||
} catch (error) {
|
||||
log.severe('Error pausing or playing video: $error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue