mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	* initial cast framework complete and mocked cast dialog working * wip casting * casting works! just need to add session key check and remote video controls * cleanup of classes * add session expiration checks * cast dialog now shows connected device at top of list with a list header. Discovered devices are also cached for app session. * cast video player finalized * show fullsize assets on casting * translation already happens on the text element * remove prints * fix lintings * code review changes from @shenlong-tanwen * fix connect method override * fix alphabetization * remove important * filter chromecast audio devices * fix some disconnect command ordering issues and unawaited futures * remove prints * only disconnect if we are connected * don't try to reconnect if its the current device * add cast button to top bar * format sessions api * more formatting issues fixed * add snack bar to tell user that we cannot cast an asset that is not uploaded to server * make casting icon change to primary color when casting is active * only show casting snackbar if we are casting * dont show cast button if asset is remote and we are not casting * stop playing media if we seek to an asset that is not remote * remove https check since it works with local http IP addresses * remove unneeded imports * fix recasting when socket closes * fix info plist formatting * only show cast button if there is an active websocket connection (ie the server is accessible) * add device capability bitmask checks * small comment about bitmask
		
			
				
	
	
		
			132 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			132 lines
		
	
	
		
			4.4 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_controls_provider.dart';
 | 
						|
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
 | 
						|
import 'package:immich_mobile/providers/cast.provider.dart';
 | 
						|
import 'package:immich_mobile/widgets/asset_viewer/formatted_duration.dart';
 | 
						|
 | 
						|
class VideoPosition extends HookConsumerWidget {
 | 
						|
  const VideoPosition({super.key});
 | 
						|
 | 
						|
  @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(
 | 
						|
            videoPlaybackValueProvider.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 state =
 | 
						|
                            ref.read(videoPlaybackValueProvider).state;
 | 
						|
                        wasPlaying.value = state != VideoPlaybackState.paused;
 | 
						|
                        ref.read(videoPlayerControlsProvider.notifier).pause();
 | 
						|
                      },
 | 
						|
                      onChangeEnd: (value) {
 | 
						|
                        if (wasPlaying.value) {
 | 
						|
                          ref.read(videoPlayerControlsProvider.notifier).play();
 | 
						|
                        }
 | 
						|
                      },
 | 
						|
                      onChanged: (value) {
 | 
						|
                        final seekToDuration = (duration * (value / 100.0));
 | 
						|
 | 
						|
                        if (isCasting) {
 | 
						|
                          ref
 | 
						|
                              .read(castProvider.notifier)
 | 
						|
                              .seekTo(seekToDuration);
 | 
						|
                          return;
 | 
						|
                        }
 | 
						|
 | 
						|
                        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;
 | 
						|
                      },
 | 
						|
                    ),
 | 
						|
                  ),
 | 
						|
                ],
 | 
						|
              ),
 | 
						|
            ],
 | 
						|
          );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
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,
 | 
						|
              ),
 | 
						|
            ),
 | 
						|
          ],
 | 
						|
        ),
 | 
						|
      ],
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |