2022-11-11 06:53:31 +00:00
|
|
|
import 'dart:async';
|
2023-08-24 16:56:24 +00:00
|
|
|
import 'dart:io';
|
2021-10-30 05:43:56 +00:00
|
|
|
|
2020-06-19 23:03:26 +00:00
|
|
|
import 'package:chewie/chewie.dart';
|
2020-08-14 00:01:37 +00:00
|
|
|
import 'package:flutter/cupertino.dart';
|
2020-06-20 23:51:10 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2020-07-07 21:54:11 +00:00
|
|
|
import 'package:logging/logging.dart';
|
2022-09-26 07:48:18 +00:00
|
|
|
import 'package:photos/core/configuration.dart';
|
2022-03-14 11:44:18 +00:00
|
|
|
import 'package:photos/core/constants.dart';
|
2023-04-07 05:57:56 +00:00
|
|
|
import "package:photos/generated/l10n.dart";
|
2023-08-25 04:39:30 +00:00
|
|
|
import 'package:photos/models/file/file.dart';
|
2023-07-03 10:02:55 +00:00
|
|
|
import "package:photos/services/feature_flag_service.dart";
|
2022-09-26 07:36:56 +00:00
|
|
|
import 'package:photos/services/files_service.dart';
|
2022-07-01 14:18:05 +00:00
|
|
|
import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
|
|
|
|
import 'package:photos/ui/viewer/file/video_controls.dart';
|
2023-06-06 11:35:36 +00:00
|
|
|
import "package:photos/utils/dialog_util.dart";
|
2020-08-13 20:03:29 +00:00
|
|
|
import 'package:photos/utils/file_util.dart';
|
2020-07-07 21:54:11 +00:00
|
|
|
import 'package:photos/utils/toast_util.dart';
|
2020-06-19 23:03:26 +00:00
|
|
|
import 'package:video_player/video_player.dart';
|
2020-07-29 19:17:03 +00:00
|
|
|
import 'package:visibility_detector/visibility_detector.dart';
|
2023-08-16 05:17:50 +00:00
|
|
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
2020-06-19 23:03:26 +00:00
|
|
|
|
|
|
|
class VideoWidget extends StatefulWidget {
|
2023-08-24 16:56:24 +00:00
|
|
|
final EnteFile file;
|
2022-12-30 12:10:17 +00:00
|
|
|
final bool? autoPlay;
|
|
|
|
final String? tagPrefix;
|
|
|
|
final Function(bool)? playbackCallback;
|
2021-07-06 23:24:19 +00:00
|
|
|
|
2022-07-04 08:43:01 +00:00
|
|
|
const VideoWidget(
|
2020-07-13 21:33:43 +00:00
|
|
|
this.file, {
|
|
|
|
this.autoPlay = false,
|
|
|
|
this.tagPrefix,
|
2021-07-06 23:24:19 +00:00
|
|
|
this.playbackCallback,
|
2022-12-30 12:10:17 +00:00
|
|
|
Key? key,
|
2020-07-13 21:33:43 +00:00
|
|
|
}) : super(key: key);
|
2020-06-19 23:03:26 +00:00
|
|
|
|
|
|
|
@override
|
2022-07-03 09:45:00 +00:00
|
|
|
State<VideoWidget> createState() => _VideoWidgetState();
|
2020-06-19 23:03:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class _VideoWidgetState extends State<VideoWidget> {
|
2021-10-06 10:48:38 +00:00
|
|
|
final _logger = Logger("VideoWidget");
|
2022-12-30 12:10:17 +00:00
|
|
|
VideoPlayerController? _videoPlayerController;
|
|
|
|
ChewieController? _chewieController;
|
2023-08-04 11:39:50 +00:00
|
|
|
final _progressNotifier = ValueNotifier<double?>(null);
|
2022-12-30 12:10:17 +00:00
|
|
|
bool _isPlaying = false;
|
2022-11-11 06:53:31 +00:00
|
|
|
bool _wakeLockEnabledHere = false;
|
2020-06-19 23:03:26 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
2022-09-23 01:48:25 +00:00
|
|
|
if (widget.file.isRemoteFile) {
|
2020-11-17 06:02:14 +00:00
|
|
|
_loadNetworkVideo();
|
2022-09-26 07:36:56 +00:00
|
|
|
_setFileSizeIfNull();
|
2022-09-23 01:48:25 +00:00
|
|
|
} else if (widget.file.isSharedMediaToAppSandbox) {
|
2023-08-24 16:56:24 +00:00
|
|
|
final localFile = File(getSharedMediaFilePath(widget.file));
|
2021-09-20 12:04:51 +00:00
|
|
|
if (localFile.existsSync()) {
|
2021-09-20 12:27:12 +00:00
|
|
|
_logger.fine("loading from app cache");
|
2021-09-20 12:04:51 +00:00
|
|
|
_setVideoPlayerController(file: localFile);
|
|
|
|
} else if (widget.file.uploadedFileID != null) {
|
|
|
|
_loadNetworkVideo();
|
|
|
|
}
|
2020-11-17 06:02:14 +00:00
|
|
|
} else {
|
2022-09-23 01:48:25 +00:00
|
|
|
widget.file.getAsset.then((asset) async {
|
2020-11-17 06:02:14 +00:00
|
|
|
if (asset == null || !(await asset.exists)) {
|
|
|
|
if (widget.file.uploadedFileID != null) {
|
|
|
|
_loadNetworkVideo();
|
2020-08-13 20:03:29 +00:00
|
|
|
}
|
2020-11-17 06:02:14 +00:00
|
|
|
} else {
|
|
|
|
asset.getMediaUrl().then((url) {
|
|
|
|
_setVideoPlayerController(url: url);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-26 07:36:56 +00:00
|
|
|
void _setFileSizeIfNull() {
|
2022-09-26 07:48:18 +00:00
|
|
|
if (widget.file.fileSize == null &&
|
|
|
|
widget.file.ownerID == Configuration.instance.getUserID()) {
|
2022-09-26 07:36:56 +00:00
|
|
|
FilesService.instance
|
2022-12-30 12:10:17 +00:00
|
|
|
.getFileSize(widget.file.uploadedFileID!)
|
2022-09-26 07:36:56 +00:00
|
|
|
.then((value) {
|
|
|
|
widget.file.fileSize = value;
|
2022-10-21 08:05:16 +00:00
|
|
|
if (mounted) {
|
|
|
|
setState(() {});
|
|
|
|
}
|
2022-09-26 07:36:56 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-17 06:02:14 +00:00
|
|
|
void _loadNetworkVideo() {
|
2021-02-08 09:05:41 +00:00
|
|
|
getFileFromServer(
|
|
|
|
widget.file,
|
|
|
|
progressCallback: (count, total) {
|
2023-08-04 11:39:50 +00:00
|
|
|
_progressNotifier.value = count / (widget.file.fileSize ?? total);
|
|
|
|
if (_progressNotifier.value == 1) {
|
|
|
|
if (mounted) {
|
|
|
|
showShortToast(context, S.of(context).decryptingVideo);
|
|
|
|
}
|
2021-09-20 12:04:51 +00:00
|
|
|
}
|
2021-02-08 09:05:41 +00:00
|
|
|
},
|
|
|
|
).then((file) {
|
2021-04-27 16:36:19 +00:00
|
|
|
if (file != null) {
|
|
|
|
_setVideoPlayerController(file: file);
|
|
|
|
}
|
2023-08-04 12:18:01 +00:00
|
|
|
}).onError((error, stackTrace) {
|
2023-08-07 10:15:14 +00:00
|
|
|
showErrorDialog(context, "Error", S.of(context).failedToDownloadVideo);
|
2021-02-08 09:05:41 +00:00
|
|
|
});
|
2020-06-19 23:03:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
2023-07-03 14:37:09 +00:00
|
|
|
_videoPlayerController?.dispose();
|
|
|
|
_chewieController?.dispose();
|
2022-11-11 06:53:31 +00:00
|
|
|
if (_wakeLockEnabledHere) {
|
|
|
|
unawaited(
|
2023-08-16 05:17:50 +00:00
|
|
|
WakelockPlus.enabled.then((isEnabled) {
|
|
|
|
isEnabled ? WakelockPlus.disable() : null;
|
2022-11-11 06:53:31 +00:00
|
|
|
}),
|
|
|
|
);
|
|
|
|
}
|
2020-06-19 23:03:26 +00:00
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
2023-08-21 17:15:27 +00:00
|
|
|
void _setVideoPlayerController({
|
2022-12-30 15:42:03 +00:00
|
|
|
String? url,
|
2023-08-24 16:56:24 +00:00
|
|
|
File? file,
|
2022-12-30 15:42:03 +00:00
|
|
|
}) {
|
2023-08-21 17:15:27 +00:00
|
|
|
if (!mounted) {
|
|
|
|
// Note: Do not initiale video player if widget is not mounted.
|
|
|
|
// On Android, if multiple instance of ExoPlayer is created, it will start
|
|
|
|
// resulting in playback errors for videos. See https://github.com/google/ExoPlayer/issues/6168
|
|
|
|
return;
|
|
|
|
}
|
2021-10-06 10:48:38 +00:00
|
|
|
VideoPlayerController videoPlayerController;
|
2020-09-17 14:30:39 +00:00
|
|
|
if (url != null) {
|
|
|
|
videoPlayerController = VideoPlayerController.network(url);
|
|
|
|
} else {
|
2022-12-30 12:10:17 +00:00
|
|
|
videoPlayerController = VideoPlayerController.file(file!);
|
2020-09-17 14:30:39 +00:00
|
|
|
}
|
2023-06-06 11:35:36 +00:00
|
|
|
|
|
|
|
debugPrint("videoPlayerController: $videoPlayerController");
|
|
|
|
_videoPlayerController = videoPlayerController
|
2020-06-23 14:59:51 +00:00
|
|
|
..initialize().whenComplete(() {
|
2020-08-13 22:00:19 +00:00
|
|
|
if (mounted) {
|
|
|
|
setState(() {});
|
|
|
|
}
|
2023-06-06 11:35:36 +00:00
|
|
|
}).onError(
|
|
|
|
(error, stackTrace) {
|
2023-07-03 10:02:55 +00:00
|
|
|
if (mounted &&
|
|
|
|
FeatureFlagService.instance.isInternalUserOrDebugBuild()) {
|
2023-06-06 11:35:36 +00:00
|
|
|
if (error is Exception) {
|
|
|
|
showErrorDialogForException(
|
|
|
|
context: context,
|
|
|
|
exception: error,
|
|
|
|
message: "Failed to play video\n ${error.toString()}",
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
showToast(context, "Failed to play video");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
2020-06-23 14:59:51 +00:00
|
|
|
}
|
|
|
|
|
2020-06-19 23:03:26 +00:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2022-07-03 09:49:33 +00:00
|
|
|
final content = _videoPlayerController != null &&
|
2022-12-30 12:10:17 +00:00
|
|
|
_videoPlayerController!.value.isInitialized
|
2020-06-23 14:59:51 +00:00
|
|
|
? _getVideoPlayer()
|
|
|
|
: _getLoadingWidget();
|
2022-03-14 11:44:18 +00:00
|
|
|
final contentWithDetector = GestureDetector(
|
|
|
|
child: content,
|
|
|
|
onVerticalDragUpdate: (d) => {
|
2023-08-19 11:39:56 +00:00
|
|
|
if (d.delta.dy > dragSensitivity) {Navigator.of(context).pop()},
|
2022-03-14 11:44:18 +00:00
|
|
|
},
|
|
|
|
);
|
2020-07-29 19:17:03 +00:00
|
|
|
return VisibilityDetector(
|
2022-09-23 01:48:25 +00:00
|
|
|
key: Key(widget.file.tag),
|
2020-07-29 19:17:03 +00:00
|
|
|
onVisibilityChanged: (info) {
|
|
|
|
if (info.visibleFraction < 1) {
|
2020-09-17 14:30:39 +00:00
|
|
|
if (mounted && _chewieController != null) {
|
2022-12-30 12:10:17 +00:00
|
|
|
_chewieController!.pause();
|
2020-08-14 00:04:12 +00:00
|
|
|
}
|
2020-07-29 19:17:03 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
child: Hero(
|
2022-12-30 12:10:17 +00:00
|
|
|
tag: widget.tagPrefix! + widget.file.tag,
|
2022-03-14 11:44:18 +00:00
|
|
|
child: contentWithDetector,
|
2020-07-29 19:17:03 +00:00
|
|
|
),
|
2020-06-23 15:05:48 +00:00
|
|
|
);
|
2020-06-23 14:59:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Widget _getLoadingWidget() {
|
2022-06-11 08:23:52 +00:00
|
|
|
return Stack(
|
|
|
|
children: [
|
|
|
|
_getThumbnail(),
|
|
|
|
Container(
|
|
|
|
color: Colors.black12,
|
2022-07-04 06:02:17 +00:00
|
|
|
constraints: const BoxConstraints.expand(),
|
2022-06-11 08:23:52 +00:00
|
|
|
),
|
|
|
|
Center(
|
|
|
|
child: SizedBox.fromSize(
|
2023-06-21 06:10:35 +00:00
|
|
|
size: const Size.square(20),
|
2023-08-04 11:39:50 +00:00
|
|
|
child: ValueListenableBuilder(
|
|
|
|
valueListenable: _progressNotifier,
|
|
|
|
builder: (BuildContext context, double? progress, _) {
|
|
|
|
return progress == null || progress == 1
|
|
|
|
? const CupertinoActivityIndicator(
|
|
|
|
color: Colors.white,
|
|
|
|
)
|
|
|
|
: CircularProgressIndicator(
|
|
|
|
backgroundColor: Colors.black,
|
|
|
|
value: progress,
|
|
|
|
valueColor: const AlwaysStoppedAnimation<Color>(
|
|
|
|
Color.fromRGBO(45, 194, 98, 1.0),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
},
|
|
|
|
),
|
2022-06-11 08:23:52 +00:00
|
|
|
),
|
2020-08-14 00:01:37 +00:00
|
|
|
),
|
2022-06-11 08:23:52 +00:00
|
|
|
],
|
|
|
|
);
|
2020-06-23 14:59:51 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 19:14:18 +00:00
|
|
|
Widget _getThumbnail() {
|
|
|
|
return Container(
|
2022-06-09 15:03:08 +00:00
|
|
|
color: Colors.black,
|
2022-07-04 06:02:17 +00:00
|
|
|
constraints: const BoxConstraints.expand(),
|
2020-08-13 20:03:29 +00:00
|
|
|
child: ThumbnailWidget(
|
|
|
|
widget.file,
|
|
|
|
fit: BoxFit.contain,
|
|
|
|
),
|
2020-06-23 19:14:18 +00:00
|
|
|
);
|
2020-06-19 23:03:26 +00:00
|
|
|
}
|
|
|
|
|
2022-11-11 06:53:31 +00:00
|
|
|
Future<void> _keepScreenAliveOnPlaying(bool isPlaying) async {
|
|
|
|
if (isPlaying) {
|
2023-08-16 05:17:50 +00:00
|
|
|
return WakelockPlus.enabled.then((value) {
|
2022-11-11 06:53:31 +00:00
|
|
|
if (value == false) {
|
2023-08-16 05:17:50 +00:00
|
|
|
WakelockPlus.enable();
|
2022-11-11 06:53:31 +00:00
|
|
|
//wakeLockEnabledHere will not be set to true if wakeLock is already enabled from settings on iOS.
|
|
|
|
//We shouldn't disable when video is not playing if it was enabled manually by the user from ente settings by user.
|
|
|
|
_wakeLockEnabledHere = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (_wakeLockEnabledHere && !isPlaying) {
|
2023-08-16 05:17:50 +00:00
|
|
|
return WakelockPlus.disable();
|
2022-11-11 06:53:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-23 14:59:51 +00:00
|
|
|
Widget _getVideoPlayer() {
|
2022-12-30 12:10:17 +00:00
|
|
|
_videoPlayerController!.addListener(() {
|
|
|
|
if (_isPlaying != _videoPlayerController!.value.isPlaying) {
|
|
|
|
_isPlaying = _videoPlayerController!.value.isPlaying;
|
2021-07-06 23:24:19 +00:00
|
|
|
if (widget.playbackCallback != null) {
|
2022-12-30 12:10:17 +00:00
|
|
|
widget.playbackCallback!(_isPlaying);
|
2021-07-06 23:24:19 +00:00
|
|
|
}
|
2022-12-30 15:42:03 +00:00
|
|
|
unawaited(_keepScreenAliveOnPlaying(_isPlaying));
|
2021-07-06 23:24:19 +00:00
|
|
|
}
|
|
|
|
});
|
2020-06-20 22:47:09 +00:00
|
|
|
_chewieController = ChewieController(
|
2022-12-30 12:10:17 +00:00
|
|
|
videoPlayerController: _videoPlayerController!,
|
|
|
|
aspectRatio: _videoPlayerController!.value.aspectRatio,
|
|
|
|
autoPlay: widget.autoPlay!,
|
2020-06-20 22:47:09 +00:00
|
|
|
autoInitialize: true,
|
|
|
|
looping: true,
|
2022-06-09 15:03:08 +00:00
|
|
|
allowMuting: true,
|
2020-06-20 22:47:09 +00:00
|
|
|
allowFullScreen: false,
|
2022-07-04 06:02:17 +00:00
|
|
|
customControls: const VideoControls(),
|
2020-06-20 22:47:09 +00:00
|
|
|
);
|
2022-06-09 15:03:08 +00:00
|
|
|
return Container(
|
|
|
|
color: Colors.black,
|
2022-12-30 12:10:17 +00:00
|
|
|
child: Chewie(controller: _chewieController!),
|
2022-06-09 15:03:08 +00:00
|
|
|
);
|
2020-06-19 23:03:26 +00:00
|
|
|
}
|
|
|
|
}
|