diff --git a/mobile/lib/pages/common/native_video_viewer.page.dart b/mobile/lib/pages/common/native_video_viewer.page.dart index 3a8d22d9a30dc..4077cb7cd246b 100644 --- a/mobile/lib/pages/common/native_video_viewer.page.dart +++ b/mobile/lib/pages/common/native_video_viewer.page.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -33,6 +35,28 @@ class NativeVideoViewerPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final controller = useState(null); + final lastVideoPosition = useRef(-1); + final isBuffering = useRef(false); + + void checkIfBuffering([Timer? timer]) { + if (!context.mounted) { + timer?.cancel(); + return; + } + + final videoPlayback = ref.read(videoPlaybackValueProvider); + if ((isBuffering.value || + videoPlayback.state == VideoPlaybackState.initializing) && + videoPlayback.state != VideoPlaybackState.buffering) { + ref.read(videoPlaybackValueProvider.notifier).value = + videoPlayback.copyWith(state: VideoPlaybackState.buffering); + } + } + + // timer to mark videos as buffering if the position does not change + final bufferingTimer = useRef( + Timer.periodic(const Duration(seconds: 5), checkIfBuffering), + ); Future createSource(Asset asset) async { if (asset.isLocal && asset.livePhotoVideoId == null) { @@ -100,6 +124,15 @@ class NativeVideoViewerPage extends HookConsumerWidget { final videoPlayback = VideoPlaybackValue.fromNativeController(controller.value!); ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback; + // Check if the video is buffering + if (videoPlayback.state == VideoPlaybackState.playing) { + isBuffering.value = + lastVideoPosition.value == videoPlayback.position.inSeconds; + lastVideoPosition.value = videoPlayback.position.inSeconds; + } else { + isBuffering.value = false; + lastVideoPosition.value = -1; + } final state = videoPlayback.state; // Enable the WakeLock while the video is playing @@ -142,6 +175,8 @@ class NativeVideoViewerPage extends HookConsumerWidget { final videoSource = await createSource(asset); controller.value?.loadVideoSource(videoSource); + + Timer(const Duration(milliseconds: 200), checkIfBuffering); } useEffect( @@ -158,6 +193,7 @@ class NativeVideoViewerPage extends HookConsumerWidget { } return () { + bufferingTimer.value.cancel(); controller.value?.onPlaybackPositionChanged .removeListener(onPlaybackPositionChanged); controller.value?.onPlaybackStatusChanged @@ -169,9 +205,6 @@ class NativeVideoViewerPage extends HookConsumerWidget { [], ); - void updatePlayback(VideoPlaybackValue value) => - ref.read(videoPlaybackValueProvider.notifier).value = value; - final size = MediaQuery.sizeOf(context); return SizedBox( @@ -180,8 +213,9 @@ class NativeVideoViewerPage extends HookConsumerWidget { child: GestureDetector( behavior: HitTestBehavior.deferToChild, child: PopScope( - onPopInvokedWithResult: (didPop, _) => - updatePlayback(VideoPlaybackValue.uninitialized()), + onPopInvokedWithResult: (didPop, _) => ref + .read(videoPlaybackValueProvider.notifier) + .value = VideoPlaybackValue.uninitialized(), child: SizedBox( height: size.height, width: size.width, diff --git a/mobile/lib/providers/asset_viewer/video_player_value_provider.dart b/mobile/lib/providers/asset_viewer/video_player_value_provider.dart index 82b971ee0c600..dad46593925cb 100644 --- a/mobile/lib/providers/asset_viewer/video_player_value_provider.dart +++ b/mobile/lib/providers/asset_viewer/video_player_value_provider.dart @@ -87,6 +87,20 @@ class VideoPlaybackValue { volume: 0.0, ); } + + VideoPlaybackValue copyWith({ + Duration? position, + Duration? duration, + VideoPlaybackState? state, + double? volume, + }) { + return VideoPlaybackValue( + position: position ?? this.position, + duration: duration ?? this.duration, + state: state ?? this.state, + volume: volume ?? this.volume, + ); + } } final videoPlaybackValueProvider =