Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 37 additions & 21 deletions mobile/lib/pages/common/native_video_viewer.page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import 'package:wakelock_plus/wakelock_plus.dart';

@RoutePage()
class NativeVideoViewerPage extends HookConsumerWidget {
static final log = Logger('NativeVideoViewer');
final Asset asset;
final bool showControls;
final int playbackDelayFactor;
Expand Down Expand Up @@ -59,8 +60,6 @@ class NativeVideoViewerPage extends HookConsumerWidget {
// Used to show the placeholder during hero animations for remote videos to avoid a stutter
final isVisible = useState(Platform.isIOS && asset.isLocal);

final log = Logger('NativeVideoViewerPage');

final isCasting = ref.watch(castProvider.select((c) => c.isCasting));

final isVideoReady = useState(false);
Expand Down Expand Up @@ -142,7 +141,7 @@ class NativeVideoViewerPage 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;
Expand All @@ -153,28 +152,14 @@ class NativeVideoViewerPage 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));
}
});

Expand Down Expand Up @@ -234,7 +219,7 @@ class NativeVideoViewerPage 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) {
Expand Down Expand Up @@ -391,4 +376,35 @@ class NativeVideoViewerPage 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();
}

if (!context.mounted) {
return;
}

try {
if (isPaused) {
await controller.pause();
} else {
await controller.play();
}
} catch (error) {
log.severe('Error pausing or playing video: $error');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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));
}
});

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider
class VideoPlaybackControls {
const VideoPlaybackControls({required this.position, required this.pause, this.restarted = false});

final double position;
final Duration position;
final bool pause;
final bool restarted;
}
Expand All @@ -13,7 +13,7 @@ final videoPlayerControlsProvider = StateNotifierProvider<VideoPlayerControls, V
return VideoPlayerControls(ref);
});

const videoPlayerControlsDefault = VideoPlaybackControls(position: 0, pause: false);
const videoPlayerControlsDefault = VideoPlaybackControls(position: Duration.zero, pause: false);

class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
VideoPlayerControls(this.ref) : super(videoPlayerControlsDefault);
Expand All @@ -30,10 +30,10 @@ class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
state = videoPlayerControlsDefault;
}

double get position => state.position;
Duration get position => state.position;
bool get paused => state.pause;

set position(double value) {
set position(Duration value) {
if (state.position == value) {
return;
}
Expand Down Expand Up @@ -62,7 +62,7 @@ class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
}

void restart() {
state = const VideoPlaybackControls(position: 0, pause: false, restarted: true);
state = const VideoPlaybackControls(position: Duration.zero, pause: false, restarted: true);
ref.read(videoPlaybackValueProvider.notifier).value = ref
.read(videoPlaybackValueProvider.notifier)
.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ class VideoPlaybackValue {
};

return VideoPlaybackValue(
position: Duration(seconds: playbackInfo.position),
duration: Duration(seconds: videoInfo.duration),
position: Duration(milliseconds: playbackInfo.position),
duration: Duration(milliseconds: videoInfo.duration),
state: status,
volume: playbackInfo.volume,
);
Expand Down
2 changes: 1 addition & 1 deletion mobile/lib/widgets/asset_viewer/video_position.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class VideoPosition extends HookConsumerWidget {
return;
}

ref.read(videoPlayerControlsProvider.notifier).position = seekToDuration.inSeconds.toDouble();
ref.read(videoPlayerControlsProvider.notifier).position = seekToDuration;

// This immediately updates the slider position without waiting for the video to update
ref.read(videoPlaybackValueProvider.notifier).position = seekToDuration;
Expand Down
4 changes: 2 additions & 2 deletions mobile/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1233,8 +1233,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "893894b"
resolved-ref: "893894b98b832be8a995a8d5d4c2289d0ad2d246"
ref: d921ae2
resolved-ref: d921ae210e294d2821954009ec2cc8aeae918725
url: "https://github.com/immich-app/native_video_player"
source: git
version: "1.3.1"
Expand Down
2 changes: 1 addition & 1 deletion mobile/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ dependencies:
native_video_player:
git:
url: https://github.com/immich-app/native_video_player
ref: '893894b'
ref: 'd921ae2'
network_info_plus: ^6.1.3
octo_image: ^2.1.0
openapi:
Expand Down
Loading