mirror of
https://github.com/immich-app/immich.git
synced 2024-12-29 15:11:58 +00:00
Videos play in memories now
This commit is contained in:
parent
b0ff859cd6
commit
f14295e107
3 changed files with 108 additions and 55 deletions
|
@ -21,40 +21,45 @@ class VideoViewerPage extends HookConsumerWidget {
|
||||||
final Asset asset;
|
final Asset asset;
|
||||||
final bool isMotionVideo;
|
final bool isMotionVideo;
|
||||||
final Widget? placeholder;
|
final Widget? placeholder;
|
||||||
final VoidCallback onVideoEnded;
|
final VoidCallback? onVideoEnded;
|
||||||
final VoidCallback? onPlaying;
|
final VoidCallback? onPlaying;
|
||||||
final VoidCallback? onPaused;
|
final VoidCallback? onPaused;
|
||||||
|
final Duration hideControlsTimer;
|
||||||
|
final bool showControls;
|
||||||
|
final bool showDownloadingIndicator;
|
||||||
|
|
||||||
const VideoViewerPage({
|
const VideoViewerPage({
|
||||||
super.key,
|
super.key,
|
||||||
required this.asset,
|
required this.asset,
|
||||||
required this.isMotionVideo,
|
this.isMotionVideo = false,
|
||||||
required this.onVideoEnded,
|
this.onVideoEnded,
|
||||||
this.onPlaying,
|
this.onPlaying,
|
||||||
this.onPaused,
|
this.onPaused,
|
||||||
this.placeholder,
|
this.placeholder,
|
||||||
|
this.showControls = true,
|
||||||
|
this.hideControlsTimer = const Duration(seconds: 5),
|
||||||
|
this.showDownloadingIndicator = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
if (asset.isLocal && asset.livePhotoVideoId == null) {
|
if (asset.isLocal && asset.livePhotoVideoId == null) {
|
||||||
final AsyncValue<File> videoFile = ref.watch(_fileFamily(asset.local!));
|
final AsyncValue<File> videoFile = ref.watch(_fileFamily(asset.local!));
|
||||||
return videoFile.when(
|
return AnimatedSwitcher(
|
||||||
data: (data) => VideoPlayer(
|
duration: const Duration(milliseconds: 200),
|
||||||
file: data,
|
child: videoFile.when(
|
||||||
isMotionVideo: false,
|
data: (data) => VideoPlayer(
|
||||||
onVideoEnded: () {},
|
file: data,
|
||||||
),
|
isMotionVideo: false,
|
||||||
error: (error, stackTrace) => Icon(
|
onVideoEnded: () {},
|
||||||
Icons.image_not_supported_outlined,
|
|
||||||
color: context.primaryColor,
|
|
||||||
),
|
|
||||||
loading: () => const Center(
|
|
||||||
child: SizedBox(
|
|
||||||
width: 75,
|
|
||||||
height: 75,
|
|
||||||
child: CircularProgressIndicator.adaptive(),
|
|
||||||
),
|
),
|
||||||
|
error: (error, stackTrace) => Icon(
|
||||||
|
Icons.image_not_supported_outlined,
|
||||||
|
color: context.primaryColor,
|
||||||
|
),
|
||||||
|
loading: () => showDownloadingIndicator
|
||||||
|
? const Center(child: ImmichLoadingIndicator())
|
||||||
|
: Container(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -74,15 +79,24 @@ class VideoViewerPage extends HookConsumerWidget {
|
||||||
onPaused: onPaused,
|
onPaused: onPaused,
|
||||||
onPlaying: onPlaying,
|
onPlaying: onPlaying,
|
||||||
placeholder: placeholder,
|
placeholder: placeholder,
|
||||||
|
hideControlsTimer: hideControlsTimer,
|
||||||
|
showControls: showControls,
|
||||||
|
showDownloadingIndicator: showDownloadingIndicator,
|
||||||
),
|
),
|
||||||
if (downloadAssetStatus == DownloadAssetStatus.loading)
|
AnimatedOpacity(
|
||||||
SizedBox(
|
duration: const Duration(milliseconds: 400),
|
||||||
|
opacity: (downloadAssetStatus == DownloadAssetStatus.loading &&
|
||||||
|
showDownloadingIndicator)
|
||||||
|
? 1.0
|
||||||
|
: 0.0,
|
||||||
|
child: SizedBox(
|
||||||
height: context.height,
|
height: context.height,
|
||||||
width: context.width,
|
width: context.width,
|
||||||
child: const Center(
|
child: const Center(
|
||||||
child: ImmichLoadingIndicator(),
|
child: ImmichLoadingIndicator(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -102,7 +116,9 @@ class VideoPlayer extends StatefulWidget {
|
||||||
final String? jwtToken;
|
final String? jwtToken;
|
||||||
final File? file;
|
final File? file;
|
||||||
final bool isMotionVideo;
|
final bool isMotionVideo;
|
||||||
final VoidCallback onVideoEnded;
|
final VoidCallback? onVideoEnded;
|
||||||
|
final Duration hideControlsTimer;
|
||||||
|
final bool showControls;
|
||||||
|
|
||||||
final Function()? onPlaying;
|
final Function()? onPlaying;
|
||||||
final Function()? onPaused;
|
final Function()? onPaused;
|
||||||
|
@ -111,16 +127,23 @@ class VideoPlayer extends StatefulWidget {
|
||||||
/// usually, a thumbnail of the video
|
/// usually, a thumbnail of the video
|
||||||
final Widget? placeholder;
|
final Widget? placeholder;
|
||||||
|
|
||||||
|
final bool showDownloadingIndicator;
|
||||||
|
|
||||||
const VideoPlayer({
|
const VideoPlayer({
|
||||||
super.key,
|
super.key,
|
||||||
this.url,
|
this.url,
|
||||||
this.jwtToken,
|
this.jwtToken,
|
||||||
this.file,
|
this.file,
|
||||||
required this.onVideoEnded,
|
this.onVideoEnded,
|
||||||
required this.isMotionVideo,
|
required this.isMotionVideo,
|
||||||
this.onPlaying,
|
this.onPlaying,
|
||||||
this.onPaused,
|
this.onPaused,
|
||||||
this.placeholder,
|
this.placeholder,
|
||||||
|
this.hideControlsTimer = const Duration(
|
||||||
|
seconds: 5,
|
||||||
|
),
|
||||||
|
this.showControls = true,
|
||||||
|
this.showDownloadingIndicator = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -149,7 +172,7 @@ class _VideoPlayerState extends State<VideoPlayer> {
|
||||||
if (videoPlayerController.value.position ==
|
if (videoPlayerController.value.position ==
|
||||||
videoPlayerController.value.duration) {
|
videoPlayerController.value.duration) {
|
||||||
WakelockPlus.disable();
|
WakelockPlus.disable();
|
||||||
widget.onVideoEnded();
|
widget.onVideoEnded?.call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -184,9 +207,9 @@ class _VideoPlayerState extends State<VideoPlayer> {
|
||||||
autoInitialize: true,
|
autoInitialize: true,
|
||||||
allowFullScreen: false,
|
allowFullScreen: false,
|
||||||
allowedScreenSleep: false,
|
allowedScreenSleep: false,
|
||||||
showControls: !widget.isMotionVideo,
|
showControls: widget.showControls && !widget.isMotionVideo,
|
||||||
customControls: const VideoPlayerControls(),
|
customControls: const VideoPlayerControls(),
|
||||||
hideControlsTimer: const Duration(seconds: 5),
|
hideControlsTimer: widget.hideControlsTimer,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,9 +237,10 @@ class _VideoPlayerState extends State<VideoPlayer> {
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
if (widget.placeholder != null) widget.placeholder!,
|
if (widget.placeholder != null) widget.placeholder!,
|
||||||
const Center(
|
if (widget.showDownloadingIndicator)
|
||||||
child: ImmichLoadingIndicator(),
|
const Center(
|
||||||
),
|
child: ImmichLoadingIndicator(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:ui';
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/ui/immich_image.dart';
|
import 'package:immich_mobile/shared/ui/immich_image.dart';
|
||||||
|
@ -11,15 +12,15 @@ import 'package:openapi/api.dart';
|
||||||
|
|
||||||
class MemoryCard extends StatelessWidget {
|
class MemoryCard extends StatelessWidget {
|
||||||
final Asset asset;
|
final Asset asset;
|
||||||
final void Function() onTap;
|
|
||||||
final String title;
|
final String title;
|
||||||
final bool showTitle;
|
final bool showTitle;
|
||||||
|
final Function()? onVideoEnded;
|
||||||
|
|
||||||
const MemoryCard({
|
const MemoryCard({
|
||||||
required this.asset,
|
required this.asset,
|
||||||
required this.onTap,
|
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.showTitle,
|
required this.showTitle,
|
||||||
|
this.onVideoEnded,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -59,24 +60,23 @@ class MemoryCard extends StatelessWidget {
|
||||||
child: Container(color: Colors.black.withOpacity(0.2)),
|
child: Container(color: Colors.black.withOpacity(0.2)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
GestureDetector(
|
LayoutBuilder(
|
||||||
onTap: onTap,
|
builder: (context, constraints) {
|
||||||
child: LayoutBuilder(
|
// Determine the fit using the aspect ratio
|
||||||
builder: (context, constraints) {
|
BoxFit fit = BoxFit.fitWidth;
|
||||||
// Determine the fit using the aspect ratio
|
if (asset.width != null && asset.height != null) {
|
||||||
BoxFit fit = BoxFit.fitWidth;
|
final aspectRatio = asset.height! / asset.width!;
|
||||||
if (asset.width != null && asset.height != null) {
|
final phoneAspectRatio =
|
||||||
final aspectRatio = asset.width! / asset.height!;
|
constraints.maxWidth / constraints.maxHeight;
|
||||||
final phoneAspectRatio =
|
// Look for a 25% difference in either direction
|
||||||
constraints.maxWidth / constraints.maxHeight;
|
if (phoneAspectRatio * .75 < aspectRatio &&
|
||||||
// Look for a 25% difference in either direction
|
phoneAspectRatio * 1.25 > aspectRatio) {
|
||||||
if (phoneAspectRatio * .75 < aspectRatio &&
|
// Cover to look nice if we have nearly the same aspect ratio
|
||||||
phoneAspectRatio * 1.25 > aspectRatio) {
|
fit = BoxFit.cover;
|
||||||
// Cover to look nice if we have nearly the same aspect ratio
|
|
||||||
fit = BoxFit.cover;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asset.isImage) {
|
||||||
return Hero(
|
return Hero(
|
||||||
tag: 'memory-${asset.id}',
|
tag: 'memory-${asset.id}',
|
||||||
child: ImmichImage(
|
child: ImmichImage(
|
||||||
|
@ -88,8 +88,25 @@ class MemoryCard extends StatelessWidget {
|
||||||
preferredLocalAssetSize: 2048,
|
preferredLocalAssetSize: 2048,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
} else {
|
||||||
),
|
return Hero(
|
||||||
|
tag: 'memory-${asset.id}',
|
||||||
|
child: VideoViewerPage(
|
||||||
|
asset: asset,
|
||||||
|
showDownloadingIndicator: false,
|
||||||
|
placeholder: ImmichImage(
|
||||||
|
asset,
|
||||||
|
fit: fit,
|
||||||
|
type: ThumbnailFormat.JPEG,
|
||||||
|
preferredLocalAssetSize: 2048,
|
||||||
|
),
|
||||||
|
hideControlsTimer: const Duration(seconds: 2),
|
||||||
|
onVideoEnded: onVideoEnded,
|
||||||
|
showControls: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
if (showTitle)
|
if (showTitle)
|
||||||
Positioned(
|
Positioned(
|
||||||
|
|
|
@ -245,13 +245,25 @@ class MemoryPage extends HookConsumerWidget {
|
||||||
itemCount: memories[mIndex].assets.length,
|
itemCount: memories[mIndex].assets.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final asset = memories[mIndex].assets[index];
|
final asset = memories[mIndex].assets[index];
|
||||||
return Container(
|
return GestureDetector(
|
||||||
color: Colors.black,
|
behavior: HitTestBehavior.translucent,
|
||||||
child: MemoryCard(
|
onTap: () {
|
||||||
asset: asset,
|
toNextAsset(index);
|
||||||
onTap: () => toNextAsset(index),
|
},
|
||||||
title: memories[mIndex].title,
|
child: Container(
|
||||||
showTitle: index == 0,
|
color: Colors.black,
|
||||||
|
child: MemoryCard(
|
||||||
|
asset: asset,
|
||||||
|
title: memories[mIndex].title,
|
||||||
|
showTitle: index == 0,
|
||||||
|
onVideoEnded: () {
|
||||||
|
// If this is a live photo, don't go to
|
||||||
|
// next asset
|
||||||
|
if (asset.livePhotoVideoId == null) {
|
||||||
|
toNextAsset(index);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue