mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -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
		
			
				
	
	
		
			126 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			126 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'package:flutter/material.dart';
 | 
						|
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
						|
import 'package:immich_mobile/models/cast/cast_manager_state.dart';
 | 
						|
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
 | 
						|
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.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/utils/hooks/timer_hook.dart';
 | 
						|
import 'package:immich_mobile/widgets/asset_viewer/center_play_button.dart';
 | 
						|
import 'package:immich_mobile/widgets/common/delayed_loading_indicator.dart';
 | 
						|
 | 
						|
class CustomVideoPlayerControls extends HookConsumerWidget {
 | 
						|
  final Duration hideTimerDuration;
 | 
						|
 | 
						|
  const CustomVideoPlayerControls({
 | 
						|
    super.key,
 | 
						|
    this.hideTimerDuration = const Duration(seconds: 5),
 | 
						|
  });
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context, WidgetRef ref) {
 | 
						|
    final assetIsVideo = ref.watch(
 | 
						|
      currentAssetProvider.select((asset) => asset != null && asset.isVideo),
 | 
						|
    );
 | 
						|
    final showControls = ref.watch(showControlsProvider);
 | 
						|
    final VideoPlaybackState state =
 | 
						|
        ref.watch(videoPlaybackValueProvider.select((value) => value.state));
 | 
						|
 | 
						|
    final cast = ref.watch(castProvider);
 | 
						|
 | 
						|
    // A timer to hide the controls
 | 
						|
    final hideTimer = useTimer(
 | 
						|
      hideTimerDuration,
 | 
						|
      () {
 | 
						|
        if (!context.mounted) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        final state = ref.read(videoPlaybackValueProvider).state;
 | 
						|
 | 
						|
        // Do not hide on paused
 | 
						|
        if (state != VideoPlaybackState.paused &&
 | 
						|
            state != VideoPlaybackState.completed &&
 | 
						|
            assetIsVideo) {
 | 
						|
          ref.read(showControlsProvider.notifier).show = false;
 | 
						|
        }
 | 
						|
      },
 | 
						|
    );
 | 
						|
    final showBuffering =
 | 
						|
        state == VideoPlaybackState.buffering && !cast.isCasting;
 | 
						|
 | 
						|
    /// Shows the controls and starts the timer to hide them
 | 
						|
    void showControlsAndStartHideTimer() {
 | 
						|
      hideTimer.reset();
 | 
						|
      ref.read(showControlsProvider.notifier).show = true;
 | 
						|
    }
 | 
						|
 | 
						|
    // When we change position, show or hide timer
 | 
						|
    ref.listen(videoPlayerControlsProvider.select((v) => v.position),
 | 
						|
        (previous, next) {
 | 
						|
      showControlsAndStartHideTimer();
 | 
						|
    });
 | 
						|
 | 
						|
    /// Toggles between playing and pausing depending on the state of the video
 | 
						|
    void togglePlay() {
 | 
						|
      showControlsAndStartHideTimer();
 | 
						|
 | 
						|
      if (cast.isCasting) {
 | 
						|
        if (cast.castState == CastState.playing) {
 | 
						|
          ref.read(castProvider.notifier).pause();
 | 
						|
        } else if (cast.castState == CastState.paused) {
 | 
						|
          ref.read(castProvider.notifier).play();
 | 
						|
        } else if (cast.castState == CastState.idle) {
 | 
						|
          // resend the play command since its finished
 | 
						|
          final asset = ref.read(currentAssetProvider);
 | 
						|
          if (asset == null) {
 | 
						|
            return;
 | 
						|
          }
 | 
						|
          ref.read(castProvider.notifier).loadMedia(asset, true);
 | 
						|
        }
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      if (state == VideoPlaybackState.playing) {
 | 
						|
        ref.read(videoPlayerControlsProvider.notifier).pause();
 | 
						|
      } else if (state == VideoPlaybackState.completed) {
 | 
						|
        ref.read(videoPlayerControlsProvider.notifier).restart();
 | 
						|
      } else {
 | 
						|
        ref.read(videoPlayerControlsProvider.notifier).play();
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    return GestureDetector(
 | 
						|
      behavior: HitTestBehavior.opaque,
 | 
						|
      onTap: showControlsAndStartHideTimer,
 | 
						|
      child: AbsorbPointer(
 | 
						|
        absorbing: !showControls,
 | 
						|
        child: Stack(
 | 
						|
          children: [
 | 
						|
            if (showBuffering)
 | 
						|
              const Center(
 | 
						|
                child: DelayedLoadingIndicator(
 | 
						|
                  fadeInDuration: Duration(milliseconds: 400),
 | 
						|
                ),
 | 
						|
              )
 | 
						|
            else
 | 
						|
              GestureDetector(
 | 
						|
                onTap: () =>
 | 
						|
                    ref.read(showControlsProvider.notifier).show = false,
 | 
						|
                child: CenterPlayButton(
 | 
						|
                  backgroundColor: Colors.black54,
 | 
						|
                  iconColor: Colors.white,
 | 
						|
                  isFinished: state == VideoPlaybackState.completed,
 | 
						|
                  isPlaying: state == VideoPlaybackState.playing ||
 | 
						|
                      (cast.isCasting && cast.castState == CastState.playing),
 | 
						|
                  show: assetIsVideo && showControls,
 | 
						|
                  onPressed: togglePlay,
 | 
						|
                ),
 | 
						|
              ),
 | 
						|
          ],
 | 
						|
        ),
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |