mirror of
https://github.com/immich-app/immich.git
synced 2026-03-11 04:13:44 -04:00
Consolidate video state into a single asset-scoped provider, and reduce dependency on global state generally. Overall this should fix a few timing issues and race conditions with videos specifically, and make future changes in this area easier.
111 lines
3.8 KiB
Dart
111 lines
3.8 KiB
Dart
import 'dart:math';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:immich_mobile/constants/colors.dart';
|
|
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
|
|
import 'package:immich_mobile/providers/cast.provider.dart';
|
|
import 'package:immich_mobile/widgets/asset_viewer/formatted_duration.dart';
|
|
|
|
class VideoPosition extends HookConsumerWidget {
|
|
final String videoPlayerName;
|
|
|
|
const VideoPosition({super.key, required this.videoPlayerName});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final isCasting = ref.watch(castProvider).isCasting;
|
|
|
|
final (position, duration) = isCasting
|
|
? ref.watch(castProvider.select((c) => (c.currentTime, c.duration)))
|
|
: ref.watch(videoPlayerProvider(videoPlayerName).select((v) => (v.position, v.duration)));
|
|
|
|
final wasPlaying = useRef<bool>(true);
|
|
return duration == Duration.zero
|
|
? const _VideoPositionPlaceholder()
|
|
: Column(
|
|
children: [
|
|
Padding(
|
|
// align with slider's inherent padding
|
|
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [FormattedDuration(position), FormattedDuration(duration)],
|
|
),
|
|
),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Slider(
|
|
value: min(position.inMicroseconds / duration.inMicroseconds * 100, 100),
|
|
min: 0,
|
|
max: 100,
|
|
thumbColor: Colors.white,
|
|
activeColor: Colors.white,
|
|
inactiveColor: whiteOpacity75,
|
|
onChangeStart: (value) {
|
|
final status = ref.read(videoPlayerProvider(videoPlayerName)).status;
|
|
wasPlaying.value = status != VideoPlaybackStatus.paused;
|
|
ref.read(videoPlayerProvider(videoPlayerName).notifier).pause();
|
|
},
|
|
onChangeEnd: (value) {
|
|
if (wasPlaying.value) {
|
|
ref.read(videoPlayerProvider(videoPlayerName).notifier).play();
|
|
}
|
|
},
|
|
onChanged: (value) {
|
|
final seekToDuration = (duration * (value / 100.0));
|
|
|
|
if (isCasting) {
|
|
ref.read(castProvider.notifier).seekTo(seekToDuration);
|
|
return;
|
|
}
|
|
|
|
ref.read(videoPlayerProvider(videoPlayerName).notifier).seekTo(seekToDuration);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _VideoPositionPlaceholder extends StatelessWidget {
|
|
const _VideoPositionPlaceholder();
|
|
|
|
static void _onChangedDummy(_) {}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const Column(
|
|
children: [
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [FormattedDuration(Duration.zero), FormattedDuration(Duration.zero)],
|
|
),
|
|
),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Slider(
|
|
value: 0.0,
|
|
min: 0,
|
|
max: 100,
|
|
thumbColor: Colors.white,
|
|
activeColor: Colors.white,
|
|
inactiveColor: whiteOpacity75,
|
|
onChanged: _onChangedDummy,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|