mirror of
https://github.com/immich-app/immich.git
synced 2025-02-11 01:18:24 -05:00
splitup the player
This commit is contained in:
parent
a8994ffb22
commit
a346a37743
6 changed files with 240 additions and 80 deletions
|
@ -12,6 +12,7 @@ import 'package:immich_mobile/constants/constants.dart';
|
|||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/pages/common/download_panel.dart';
|
||||
import 'package:immich_mobile/pages/common/native_video_viewer.page.dart';
|
||||
import 'package:immich_mobile/pages/common/video_viewer.page.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart';
|
||||
|
@ -62,7 +63,6 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||
final localPosition = useState<Offset?>(null);
|
||||
final currentIndex = useState(initialIndex);
|
||||
final currentAsset = loadAsset(currentIndex.value);
|
||||
|
||||
// Update is playing motion video
|
||||
ref.listen(videoPlaybackValueProvider.select((v) => v.state), (_, state) {
|
||||
isPlayingVideo.value = state == VideoPlaybackState.playing;
|
||||
|
@ -353,6 +353,9 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||
),
|
||||
);
|
||||
} else {
|
||||
final useNativePlayer =
|
||||
asset.isLocal && asset.livePhotoVideoId == null;
|
||||
|
||||
return PhotoViewGalleryPageOptions.customChild(
|
||||
onDragStart: (_, details, __) =>
|
||||
localPosition.value = details.localPosition,
|
||||
|
@ -367,19 +370,32 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||
maxScale: 1.0,
|
||||
minScale: 1.0,
|
||||
basePosition: Alignment.center,
|
||||
child: VideoViewerPage(
|
||||
key: ValueKey(a),
|
||||
asset: a,
|
||||
isMotionVideo: a.livePhotoVideoId != null,
|
||||
loopVideo: shouldLoopVideo.value,
|
||||
placeholder: Image(
|
||||
image: provider,
|
||||
fit: BoxFit.contain,
|
||||
height: context.height,
|
||||
width: context.width,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
),
|
||||
child: useNativePlayer
|
||||
? NativeVideoViewerPage(
|
||||
key: ValueKey(a),
|
||||
asset: a,
|
||||
// loopVideo: shouldLoopVideo.value,
|
||||
// placeholder: Image(
|
||||
// image: provider,
|
||||
// fit: BoxFit.contain,
|
||||
// height: context.height,
|
||||
// width: context.width,
|
||||
// alignment: Alignment.center,
|
||||
// ),
|
||||
)
|
||||
: VideoViewerPage(
|
||||
key: ValueKey(a),
|
||||
asset: a,
|
||||
isMotionVideo: a.livePhotoVideoId != null,
|
||||
loopVideo: shouldLoopVideo.value,
|
||||
placeholder: Image(
|
||||
image: provider,
|
||||
fit: BoxFit.contain,
|
||||
height: context.height,
|
||||
width: context.width,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
117
mobile/lib/pages/common/native_video_viewer.page.dart
Normal file
117
mobile/lib/pages/common/native_video_viewer.page.dart
Normal file
|
@ -0,0 +1,117 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/native_video_player_controller_provider.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/video_player_controller_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/widgets/asset_viewer/video_player.dart';
|
||||
import 'package:immich_mobile/widgets/common/delayed_loading_indicator.dart';
|
||||
import 'package:native_video_player/native_video_player.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
class NativeVideoViewerPage extends ConsumerStatefulWidget {
|
||||
final Asset asset;
|
||||
final Widget? placeholder;
|
||||
|
||||
const NativeVideoViewerPage({
|
||||
super.key,
|
||||
required this.asset,
|
||||
this.placeholder,
|
||||
});
|
||||
|
||||
@override
|
||||
NativeVideoViewerPageState createState() => NativeVideoViewerPageState();
|
||||
}
|
||||
|
||||
class NativeVideoViewerPageState extends ConsumerState<NativeVideoViewerPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
double videoWidth = size.width;
|
||||
double videoHeight = size.height;
|
||||
|
||||
NativeVideoPlayerController? controller;
|
||||
|
||||
void initController(NativeVideoPlayerController videoCtrl) {
|
||||
controller = videoCtrl;
|
||||
|
||||
controller?.onPlaybackReady.addListener(() {
|
||||
// Emitted when the video loaded successfully and it's ready to play.
|
||||
// At this point, videoInfo is available.
|
||||
final videoInfo = controller?.videoInfo;
|
||||
|
||||
setState(() {
|
||||
if (videoInfo != null) {
|
||||
videoWidth = videoInfo.width.toDouble();
|
||||
videoHeight = videoInfo.height.toDouble();
|
||||
|
||||
print(videoHeight);
|
||||
print(videoWidth);
|
||||
}
|
||||
});
|
||||
|
||||
final videoDuration = videoInfo?.duration;
|
||||
|
||||
controller?.play();
|
||||
});
|
||||
|
||||
controller?.onPlaybackStatusChanged.addListener(() {
|
||||
final playbackStatus = controller?.playbackInfo?.status;
|
||||
// playbackStatus can be playing, paused, or stopped.
|
||||
});
|
||||
|
||||
controller?.onPlaybackPositionChanged.addListener(() {
|
||||
final playbackPosition = controller?.playbackInfo?.position;
|
||||
});
|
||||
|
||||
controller?.onPlaybackEnded.addListener(() {
|
||||
// Emitted when the video has finished playing.
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
controller = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
return PopScope(
|
||||
onPopInvoked: (pop) {
|
||||
ref.read(videoPlaybackValueProvider.notifier).value =
|
||||
VideoPlaybackValue.uninitialized();
|
||||
},
|
||||
child: SizedBox(
|
||||
height: videoHeight,
|
||||
width: videoWidth,
|
||||
child: AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: NativeVideoPlayerView(
|
||||
onViewReady: (c) async {
|
||||
// Use a local file for the video player controller
|
||||
final file = await widget.asset.local!.file;
|
||||
if (file == null) {
|
||||
throw Exception('No file found for the video');
|
||||
}
|
||||
|
||||
final videoSource = await VideoSource.init(
|
||||
path: file.path,
|
||||
type: VideoSourceType.file,
|
||||
);
|
||||
|
||||
await c.loadVideoSource(videoSource);
|
||||
initController(c);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
// final Asset asset;
|
||||
// final Widget? placeholder;
|
||||
// final Duration hideControlsTimer;
|
||||
// final bool showControls;
|
||||
// final bool showDownloadingIndicator;
|
||||
// final bool loopVideo;
|
||||
}
|
|
@ -103,7 +103,7 @@ class VideoViewerPage extends HookConsumerWidget {
|
|||
// Done in a microtask to avoid setting the state while the is building
|
||||
if (!isMotionVideo) {
|
||||
Future.microtask(() {
|
||||
ref.read(showControlsProvider.notifier).show = true;
|
||||
ref.read(showControlsProvider.notifier).show = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -148,20 +148,16 @@ class VideoViewerPage extends HookConsumerWidget {
|
|||
),
|
||||
if (controller != null)
|
||||
SizedBox(
|
||||
height: 16 / 9 * size.width,
|
||||
height: size.height,
|
||||
width: size.width,
|
||||
child: AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: VideoPlayerViewer(
|
||||
controller: controller,
|
||||
isMotionVideo: isMotionVideo,
|
||||
placeholder: placeholder,
|
||||
hideControlsTimer: hideControlsTimer,
|
||||
showControls: showControls,
|
||||
showDownloadingIndicator: showDownloadingIndicator,
|
||||
loopVideo: loopVideo,
|
||||
asset: asset,
|
||||
),
|
||||
child: VideoPlayerViewer(
|
||||
controller: controller,
|
||||
isMotionVideo: isMotionVideo,
|
||||
placeholder: placeholder,
|
||||
hideControlsTimer: hideControlsTimer,
|
||||
showControls: showControls,
|
||||
showDownloadingIndicator: showDownloadingIndicator,
|
||||
loopVideo: loopVideo,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:native_video_player/native_video_player.dart';
|
||||
|
||||
final nativePlayerControllerProvider =
|
||||
StateProvider((ref) => NativeVideoPlayerController(0));
|
63
mobile/lib/widgets/asset_viewer/native_video_player.dart
Normal file
63
mobile/lib/widgets/asset_viewer/native_video_player.dart
Normal file
|
@ -0,0 +1,63 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:native_video_player/native_video_player.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class NativeVideoPlayer extends HookConsumerWidget {
|
||||
final VideoPlayerController controller;
|
||||
final bool isMotionVideo;
|
||||
final Widget? placeholder;
|
||||
final Duration hideControlsTimer;
|
||||
final bool showControls;
|
||||
final bool showDownloadingIndicator;
|
||||
final bool loopVideo;
|
||||
final Asset asset;
|
||||
|
||||
const NativeVideoPlayer({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.isMotionVideo,
|
||||
this.placeholder,
|
||||
required this.hideControlsTimer,
|
||||
required this.showControls,
|
||||
required this.showDownloadingIndicator,
|
||||
required this.loopVideo,
|
||||
required this.asset,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return NativeVideoPlayerView(
|
||||
onViewReady: (controller) async {
|
||||
try {
|
||||
String path = '';
|
||||
VideoSourceType type = VideoSourceType.file;
|
||||
if (asset.isLocal && asset.livePhotoVideoId == null) {
|
||||
// Use a local file for the video player controller
|
||||
final file = await asset.local!.file;
|
||||
if (file == null) {
|
||||
throw Exception('No file found for the video');
|
||||
}
|
||||
path = file.path;
|
||||
type = VideoSourceType.file;
|
||||
|
||||
final videoSource = await VideoSource.init(
|
||||
path: path,
|
||||
type: type,
|
||||
);
|
||||
|
||||
await controller.loadVideoSource(videoSource);
|
||||
await controller.play();
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 100), () async {
|
||||
await controller.setVolume(0.5);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error loading video: $e');
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,11 +1,8 @@
|
|||
import 'package:chewie/chewie.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/utils/hooks/chewiew_controller_hook.dart';
|
||||
import 'package:immich_mobile/widgets/asset_viewer/custom_video_player_controls.dart';
|
||||
import 'package:native_video_player/native_video_player.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class VideoPlayerViewer extends HookConsumerWidget {
|
||||
|
@ -16,7 +13,6 @@ class VideoPlayerViewer extends HookConsumerWidget {
|
|||
final bool showControls;
|
||||
final bool showDownloadingIndicator;
|
||||
final bool loopVideo;
|
||||
final Asset asset;
|
||||
|
||||
const VideoPlayerViewer({
|
||||
super.key,
|
||||
|
@ -27,59 +23,26 @@ class VideoPlayerViewer extends HookConsumerWidget {
|
|||
required this.showControls,
|
||||
required this.showDownloadingIndicator,
|
||||
required this.loopVideo,
|
||||
required this.asset,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
// final chewie = useChewieController(
|
||||
// controller: controller,
|
||||
// controlsSafeAreaMinimum: const EdgeInsets.only(
|
||||
// bottom: 100,
|
||||
// ),
|
||||
// placeholder: SizedBox.expand(child: placeholder),
|
||||
// customControls: CustomVideoPlayerControls(
|
||||
// hideTimerDuration: hideControlsTimer,
|
||||
// ),
|
||||
// showControls: showControls && !isMotionVideo,
|
||||
// hideControlsTimer: hideControlsTimer,
|
||||
// loopVideo: loopVideo,
|
||||
// );
|
||||
final chewie = useChewieController(
|
||||
controller: controller,
|
||||
controlsSafeAreaMinimum: const EdgeInsets.only(
|
||||
bottom: 100,
|
||||
),
|
||||
placeholder: SizedBox.expand(child: placeholder),
|
||||
customControls: CustomVideoPlayerControls(
|
||||
hideTimerDuration: hideControlsTimer,
|
||||
),
|
||||
showControls: showControls && !isMotionVideo,
|
||||
hideControlsTimer: hideControlsTimer,
|
||||
loopVideo: loopVideo,
|
||||
);
|
||||
|
||||
// return Chewie(
|
||||
// controller: chewie,
|
||||
// );
|
||||
|
||||
return NativeVideoPlayerView(
|
||||
onViewReady: (controller) async {
|
||||
try {
|
||||
String path = '';
|
||||
VideoSourceType type = VideoSourceType.file;
|
||||
if (asset.isLocal && asset.livePhotoVideoId == null) {
|
||||
// Use a local file for the video player controller
|
||||
final file = await asset.local!.file;
|
||||
if (file == null) {
|
||||
throw Exception('No file found for the video');
|
||||
}
|
||||
path = file.path;
|
||||
type = VideoSourceType.file;
|
||||
|
||||
final videoSource = await VideoSource.init(
|
||||
path: path,
|
||||
type: type,
|
||||
);
|
||||
|
||||
await controller.loadVideoSource(videoSource);
|
||||
await controller.play();
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 100), () async {
|
||||
await controller.setVolume(0.5);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error loading video: $e');
|
||||
}
|
||||
},
|
||||
return Chewie(
|
||||
controller: chewie,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue