mirror of
https://github.com/immich-app/immich.git
synced 2025-06-23 15:34:03 -04:00
splitup the player
This commit is contained in:
parent
e0ed1fcecc
commit
c5cc4930fb
@ -10,6 +10,7 @@ import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.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/pages/common/video_viewer.page.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart';
|
||||||
@ -61,7 +62,6 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
final localPosition = useState<Offset?>(null);
|
final localPosition = useState<Offset?>(null);
|
||||||
final currentIndex = useState(initialIndex);
|
final currentIndex = useState(initialIndex);
|
||||||
final currentAsset = loadAsset(currentIndex.value);
|
final currentAsset = loadAsset(currentIndex.value);
|
||||||
|
|
||||||
// Update is playing motion video
|
// Update is playing motion video
|
||||||
ref.listen(videoPlaybackValueProvider.select((v) => v.state), (_, state) {
|
ref.listen(videoPlaybackValueProvider.select((v) => v.state), (_, state) {
|
||||||
isPlayingVideo.value = state == VideoPlaybackState.playing;
|
isPlayingVideo.value = state == VideoPlaybackState.playing;
|
||||||
@ -352,6 +352,9 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
final useNativePlayer =
|
||||||
|
asset.isLocal && asset.livePhotoVideoId == null;
|
||||||
|
|
||||||
return PhotoViewGalleryPageOptions.customChild(
|
return PhotoViewGalleryPageOptions.customChild(
|
||||||
onDragStart: (_, details, __) =>
|
onDragStart: (_, details, __) =>
|
||||||
localPosition.value = details.localPosition,
|
localPosition.value = details.localPosition,
|
||||||
@ -366,19 +369,32 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
maxScale: 1.0,
|
maxScale: 1.0,
|
||||||
minScale: 1.0,
|
minScale: 1.0,
|
||||||
basePosition: Alignment.center,
|
basePosition: Alignment.center,
|
||||||
child: VideoViewerPage(
|
child: useNativePlayer
|
||||||
key: ValueKey(a),
|
? NativeVideoViewerPage(
|
||||||
asset: a,
|
key: ValueKey(a),
|
||||||
isMotionVideo: a.livePhotoVideoId != null,
|
asset: a,
|
||||||
loopVideo: shouldLoopVideo.value,
|
// loopVideo: shouldLoopVideo.value,
|
||||||
placeholder: Image(
|
// placeholder: Image(
|
||||||
image: provider,
|
// image: provider,
|
||||||
fit: BoxFit.contain,
|
// fit: BoxFit.contain,
|
||||||
height: context.height,
|
// height: context.height,
|
||||||
width: context.width,
|
// width: context.width,
|
||||||
alignment: Alignment.center,
|
// 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
|
// Done in a microtask to avoid setting the state while the is building
|
||||||
if (!isMotionVideo) {
|
if (!isMotionVideo) {
|
||||||
Future.microtask(() {
|
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)
|
if (controller != null)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 16 / 9 * size.width,
|
height: size.height,
|
||||||
width: size.width,
|
width: size.width,
|
||||||
child: AspectRatio(
|
child: VideoPlayerViewer(
|
||||||
aspectRatio: 16 / 9,
|
controller: controller,
|
||||||
child: VideoPlayerViewer(
|
isMotionVideo: isMotionVideo,
|
||||||
controller: controller,
|
placeholder: placeholder,
|
||||||
isMotionVideo: isMotionVideo,
|
hideControlsTimer: hideControlsTimer,
|
||||||
placeholder: placeholder,
|
showControls: showControls,
|
||||||
hideControlsTimer: hideControlsTimer,
|
showDownloadingIndicator: showDownloadingIndicator,
|
||||||
showControls: showControls,
|
loopVideo: loopVideo,
|
||||||
showDownloadingIndicator: showDownloadingIndicator,
|
|
||||||
loopVideo: loopVideo,
|
|
||||||
asset: asset,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -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:chewie/chewie.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/utils/hooks/chewiew_controller_hook.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/custom_video_player_controls.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';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
class VideoPlayerViewer extends HookConsumerWidget {
|
class VideoPlayerViewer extends HookConsumerWidget {
|
||||||
@ -16,7 +13,6 @@ class VideoPlayerViewer extends HookConsumerWidget {
|
|||||||
final bool showControls;
|
final bool showControls;
|
||||||
final bool showDownloadingIndicator;
|
final bool showDownloadingIndicator;
|
||||||
final bool loopVideo;
|
final bool loopVideo;
|
||||||
final Asset asset;
|
|
||||||
|
|
||||||
const VideoPlayerViewer({
|
const VideoPlayerViewer({
|
||||||
super.key,
|
super.key,
|
||||||
@ -27,59 +23,26 @@ class VideoPlayerViewer extends HookConsumerWidget {
|
|||||||
required this.showControls,
|
required this.showControls,
|
||||||
required this.showDownloadingIndicator,
|
required this.showDownloadingIndicator,
|
||||||
required this.loopVideo,
|
required this.loopVideo,
|
||||||
required this.asset,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
// final chewie = useChewieController(
|
final chewie = useChewieController(
|
||||||
// controller: controller,
|
controller: controller,
|
||||||
// controlsSafeAreaMinimum: const EdgeInsets.only(
|
controlsSafeAreaMinimum: const EdgeInsets.only(
|
||||||
// bottom: 100,
|
bottom: 100,
|
||||||
// ),
|
),
|
||||||
// placeholder: SizedBox.expand(child: placeholder),
|
placeholder: SizedBox.expand(child: placeholder),
|
||||||
// customControls: CustomVideoPlayerControls(
|
customControls: CustomVideoPlayerControls(
|
||||||
// hideTimerDuration: hideControlsTimer,
|
hideTimerDuration: hideControlsTimer,
|
||||||
// ),
|
),
|
||||||
// showControls: showControls && !isMotionVideo,
|
showControls: showControls && !isMotionVideo,
|
||||||
// hideControlsTimer: hideControlsTimer,
|
hideControlsTimer: hideControlsTimer,
|
||||||
// loopVideo: loopVideo,
|
loopVideo: loopVideo,
|
||||||
// );
|
);
|
||||||
|
|
||||||
// return Chewie(
|
return Chewie(
|
||||||
// controller: 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');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user