Compare commits

..

1 Commits

Author SHA1 Message Date
bo0tzz
bee1698bb1 chore: track full actions/cache version in comment 2025-09-24 16:51:51 +02:00
8 changed files with 55 additions and 83 deletions

View File

@@ -73,7 +73,7 @@ jobs:
- name: Restore Gradle Cache
id: cache-gradle-restore
uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: |
~/.gradle/caches
@@ -130,7 +130,7 @@ jobs:
- name: Save Gradle Cache
id: cache-gradle-save
uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
if: github.ref == 'refs/heads/main'
with:
path: |

View File

@@ -26,7 +26,6 @@ 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;
@@ -60,6 +59,8 @@ 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);
@@ -141,7 +142,7 @@ class NativeVideoViewerPage extends HookConsumerWidget {
interval: const Duration(milliseconds: 100),
maxWaitTime: const Duration(milliseconds: 200),
);
ref.listen(videoPlayerControlsProvider, (oldControls, newControls) {
ref.listen(videoPlayerControlsProvider, (oldControls, newControls) async {
final playerController = controller.value;
if (playerController == null) {
return;
@@ -152,14 +153,28 @@ class NativeVideoViewerPage extends HookConsumerWidget {
return;
}
final oldSeek = oldControls?.position.inMilliseconds;
final newSeek = newControls.position.inMilliseconds;
final oldSeek = (oldControls?.position ?? 0) ~/ 1;
final newSeek = newControls.position ~/ 1;
if (oldSeek != newSeek || newControls.restarted) {
seekDebouncer.run(() => playerController.seekTo(newSeek));
}
if (oldControls?.pause != newControls.pause || newControls.restarted) {
unawaited(_onPauseChange(context, playerController, seekDebouncer, newControls.pause));
// 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');
}
}
});
@@ -216,7 +231,7 @@ class NativeVideoViewerPage extends HookConsumerWidget {
return;
}
ref.read(videoPlaybackValueProvider.notifier).position = Duration(milliseconds: playbackInfo.position);
ref.read(videoPlaybackValueProvider.notifier).position = Duration(seconds: playbackInfo.position);
// Check if the video is buffering
if (playbackInfo.status == PlaybackStatus.playing) {
@@ -373,35 +388,4 @@ 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');
}
}
}

View File

@@ -45,7 +45,6 @@ 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,6 +78,8 @@ 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 {
@@ -159,7 +160,7 @@ class NativeVideoViewer extends HookConsumerWidget {
interval: const Duration(milliseconds: 100),
maxWaitTime: const Duration(milliseconds: 200),
);
ref.listen(videoPlayerControlsProvider, (oldControls, newControls) {
ref.listen(videoPlayerControlsProvider, (oldControls, newControls) async {
final playerController = controller.value;
if (playerController == null) {
return;
@@ -170,14 +171,28 @@ class NativeVideoViewer extends HookConsumerWidget {
return;
}
final oldSeek = oldControls?.position.inMilliseconds;
final newSeek = newControls.position.inMilliseconds;
final oldSeek = (oldControls?.position ?? 0) ~/ 1;
final newSeek = newControls.position ~/ 1;
if (oldSeek != newSeek || newControls.restarted) {
seekDebouncer.run(() => playerController.seekTo(newSeek));
}
if (oldControls?.pause != newControls.pause || newControls.restarted) {
unawaited(_onPauseChange(context, playerController, seekDebouncer, newControls.pause));
// 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');
}
}
});
@@ -236,7 +251,7 @@ class NativeVideoViewer extends HookConsumerWidget {
return;
}
ref.read(videoPlaybackValueProvider.notifier).position = Duration(milliseconds: playbackInfo.position);
ref.read(videoPlaybackValueProvider.notifier).position = Duration(seconds: playbackInfo.position);
// Check if the video is buffering
if (playbackInfo.status == PlaybackStatus.playing) {
@@ -392,31 +407,4 @@ 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');
}
}
}

View File

@@ -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 Duration position;
final double position;
final bool pause;
final bool restarted;
}
@@ -13,7 +13,7 @@ final videoPlayerControlsProvider = StateNotifierProvider<VideoPlayerControls, V
return VideoPlayerControls(ref);
});
const videoPlayerControlsDefault = VideoPlaybackControls(position: Duration.zero, pause: false);
const videoPlayerControlsDefault = VideoPlaybackControls(position: 0, pause: false);
class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
VideoPlayerControls(this.ref) : super(videoPlayerControlsDefault);
@@ -30,10 +30,10 @@ class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
state = videoPlayerControlsDefault;
}
Duration get position => state.position;
double get position => state.position;
bool get paused => state.pause;
set position(Duration value) {
set position(double value) {
if (state.position == value) {
return;
}
@@ -62,7 +62,7 @@ class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
}
void restart() {
state = const VideoPlaybackControls(position: Duration.zero, pause: false, restarted: true);
state = const VideoPlaybackControls(position: 0, pause: false, restarted: true);
ref.read(videoPlaybackValueProvider.notifier).value = ref
.read(videoPlaybackValueProvider.notifier)
.value

View File

@@ -33,8 +33,8 @@ class VideoPlaybackValue {
};
return VideoPlaybackValue(
position: Duration(milliseconds: playbackInfo.position),
duration: Duration(milliseconds: videoInfo.duration),
position: Duration(seconds: playbackInfo.position),
duration: Duration(seconds: videoInfo.duration),
state: status,
volume: playbackInfo.volume,
);

View File

@@ -61,7 +61,7 @@ class VideoPosition extends HookConsumerWidget {
return;
}
ref.read(videoPlayerControlsProvider.notifier).position = seekToDuration;
ref.read(videoPlayerControlsProvider.notifier).position = seekToDuration.inSeconds.toDouble();
// This immediately updates the slider position without waiting for the video to update
ref.read(videoPlaybackValueProvider.notifier).position = seekToDuration;

View File

@@ -1208,8 +1208,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: c4cdb27
resolved-ref: c4cdb277b02d956c4effb186905329bcb9771adc
ref: "893894b"
resolved-ref: "893894b98b832be8a995a8d5d4c2289d0ad2d246"
url: "https://github.com/immich-app/native_video_player"
source: git
version: "1.3.1"

View File

@@ -77,7 +77,7 @@ dependencies:
native_video_player:
git:
url: https://github.com/immich-app/native_video_player
ref: 'c4cdb27'
ref: '893894b'
openapi:
path: openapi
isar: