diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index 5f93e37958..875b766353 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ createState() => _VideoThumbnailPlayerState(); -// } - -// class _VideoThumbnailPlayerState extends State { -// late VideoPlayerController videoPlayerController; -// ChewieController? chewieController; - -// @override -// void initState() { -// super.initState(); -// initializePlayer(); -// } - -// Future initializePlayer() async { -// videoPlayerController = -// VideoPlayerController.network('https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4'); - -// await Future.wait([ -// videoPlayerController.initialize(), -// ]); -// _createChewieController(); -// setState(() {}); -// } - -// _createChewieController() { -// chewieController = ChewieController( -// showControlsOnInitialize: false, -// videoPlayerController: videoPlayerController, -// autoPlay: true, -// looping: true, -// ); -// } - -// @override -// Widget build(BuildContext context) { -// return chewieController != null && chewieController!.videoPlayerController.value.isInitialized -// ? SizedBox( -// height: 300, -// width: 300, -// child: Chewie( -// controller: chewieController!, -// ), -// ) -// : const Text("Loading Video"); -// } -// } diff --git a/mobile/lib/modules/home/ui/thumbnail_image.dart b/mobile/lib/modules/home/ui/thumbnail_image.dart index 1225fd888d..227d1c2d42 100644 --- a/mobile/lib/modules/home/ui/thumbnail_image.dart +++ b/mobile/lib/modules/home/ui/thumbnail_image.dart @@ -21,14 +21,24 @@ class ThumbnailImage extends HookWidget { '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true'; return GestureDetector( onTap: () { - AutoRouter.of(context).push( - ImageViewerRoute( - imageUrl: - '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=false', - heroTag: asset.id, - thumbnailUrl: thumbnailRequestUrl, - ), - ); + if (asset.type == 'IMAGE') { + AutoRouter.of(context).push( + ImageViewerRoute( + imageUrl: + '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=false', + heroTag: asset.id, + thumbnailUrl: thumbnailRequestUrl, + ), + ); + } else { + debugPrint("Navigate to video player"); + + AutoRouter.of(context).push( + VideoViewerRoute( + videoUrl: '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}', + ), + ); + } }, onLongPress: () {}, child: Hero( @@ -37,7 +47,7 @@ class ThumbnailImage extends HookWidget { cacheKey: "${asset.id}-${cacheKey.value}", width: 300, height: 300, - memCacheHeight: 250, + memCacheHeight: asset.type == 'IMAGE' ? 250 : 500, fit: BoxFit.cover, imageUrl: thumbnailRequestUrl, httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"}, diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index 797117d914..367352a27a 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -5,6 +5,7 @@ import 'package:immich_mobile/modules/home/views/home_page.dart'; import 'package:immich_mobile/routing/auth_guard.dart'; import 'package:immich_mobile/shared/views/backup_controller_page.dart'; import 'package:immich_mobile/shared/views/image_viewer_page.dart'; +import 'package:immich_mobile/shared/views/video_viewer_page.dart'; part 'router.gr.dart'; @@ -15,6 +16,7 @@ part 'router.gr.dart'; AutoRoute(page: HomePage, guards: [AuthGuard]), AutoRoute(page: BackupControllerPage, guards: [AuthGuard]), AutoRoute(page: ImageViewerPage, guards: [AuthGuard]), + AutoRoute(page: VideoViewerPage, guards: [AuthGuard]), ], ) class AppRouter extends _$AppRouter { diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index cc76a25b74..ce8ceb5d61 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -42,6 +42,12 @@ class _$AppRouter extends RootStackRouter { imageUrl: args.imageUrl, heroTag: args.heroTag, thumbnailUrl: args.thumbnailUrl)); + }, + VideoViewerRoute.name: (routeData) { + final args = routeData.argsAs(); + return MaterialPageX( + routeData: routeData, + child: VideoViewerPage(key: args.key, videoUrl: args.videoUrl)); } }; @@ -52,7 +58,9 @@ class _$AppRouter extends RootStackRouter { RouteConfig(BackupControllerRoute.name, path: '/backup-controller-page', guards: [authGuard]), RouteConfig(ImageViewerRoute.name, - path: '/image-viewer-page', guards: [authGuard]) + path: '/image-viewer-page', guards: [authGuard]), + RouteConfig(VideoViewerRoute.name, + path: '/video-viewer-page', guards: [authGuard]) ]; } @@ -120,3 +128,27 @@ class ImageViewerRouteArgs { return 'ImageViewerRouteArgs{key: $key, imageUrl: $imageUrl, heroTag: $heroTag, thumbnailUrl: $thumbnailUrl}'; } } + +/// generated route for +/// [VideoViewerPage] +class VideoViewerRoute extends PageRouteInfo { + VideoViewerRoute({Key? key, required String videoUrl}) + : super(VideoViewerRoute.name, + path: '/video-viewer-page', + args: VideoViewerRouteArgs(key: key, videoUrl: videoUrl)); + + static const String name = 'VideoViewerRoute'; +} + +class VideoViewerRouteArgs { + const VideoViewerRouteArgs({this.key, required this.videoUrl}); + + final Key? key; + + final String videoUrl; + + @override + String toString() { + return 'VideoViewerRouteArgs{key: $key, videoUrl: $videoUrl}'; + } +} diff --git a/mobile/lib/shared/views/video_viewer_page.dart b/mobile/lib/shared/views/video_viewer_page.dart new file mode 100644 index 0000000000..6b006c06e8 --- /dev/null +++ b/mobile/lib/shared/views/video_viewer_page.dart @@ -0,0 +1,101 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:immich_mobile/constants/hive_box.dart'; +import 'package:chewie/chewie.dart'; +import 'package:video_player/video_player.dart'; + +class VideoViewerPage extends StatelessWidget { + final String videoUrl; + + const VideoViewerPage({Key? key, required this.videoUrl}) : super(key: key); + + @override + Widget build(BuildContext context) { + String jwtToken = Hive.box(userInfoBox).get(accessTokenKey); + + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + backgroundColor: Colors.black, + leading: IconButton( + onPressed: () { + AutoRouter.of(context).pop(); + }, + icon: const Icon(Icons.arrow_back_ios)), + ), + body: Center( + child: VideoThumbnailPlayer( + url: videoUrl, + jwtToken: jwtToken, + ), + ), + ); + } +} + +class VideoThumbnailPlayer extends StatefulWidget { + final String url; + final String? jwtToken; + + const VideoThumbnailPlayer({Key? key, required this.url, this.jwtToken}) : super(key: key); + + @override + State createState() => _VideoThumbnailPlayerState(); +} + +class _VideoThumbnailPlayerState extends State { + late VideoPlayerController videoPlayerController; + ChewieController? chewieController; + + @override + void initState() { + super.initState(); + initializePlayer(); + } + + Future initializePlayer() async { + videoPlayerController = + VideoPlayerController.network(widget.url, httpHeaders: {"Authorization": "Bearer ${widget.jwtToken}"}); + + await Future.wait([ + videoPlayerController.initialize(), + ]); + _createChewieController(); + setState(() {}); + } + + _createChewieController() { + chewieController = ChewieController( + showOptions: true, + showControlsOnInitialize: false, + videoPlayerController: videoPlayerController, + autoPlay: true, + autoInitialize: false, + ); + } + + @override + void dispose() { + super.dispose(); + videoPlayerController.pause(); + videoPlayerController.dispose(); + chewieController!.dispose(); + } + + @override + Widget build(BuildContext context) { + return chewieController != null && chewieController!.videoPlayerController.value.isInitialized + ? SizedBox( + child: Chewie( + controller: chewieController!, + ), + ) + : const SizedBox( + width: 75, + height: 75, + child: CircularProgressIndicator.adaptive( + strokeWidth: 2, + )); + } +}