|
|
|
|
@ -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');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|