mirror of
https://github.com/ente-io/ente.git
synced 2025-08-09 07:48:52 +00:00
feat: use media kit directly for preview, instead of video_player proxy
This commit is contained in:
parent
c37deecb96
commit
c5dab37dfa
@ -52,7 +52,6 @@ import "package:photos/utils/email_util.dart";
|
|||||||
import 'package:photos/utils/file_uploader.dart';
|
import 'package:photos/utils/file_uploader.dart';
|
||||||
import "package:photos/utils/lock_screen_settings.dart";
|
import "package:photos/utils/lock_screen_settings.dart";
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import "package:video_player_media_kit/video_player_media_kit.dart";
|
|
||||||
|
|
||||||
final _logger = Logger("main");
|
final _logger = Logger("main");
|
||||||
|
|
||||||
@ -238,10 +237,6 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
|
|||||||
ServiceLocator.instance
|
ServiceLocator.instance
|
||||||
.init(preferences, NetworkClient.instance.enteDio, packageInfo);
|
.init(preferences, NetworkClient.instance.enteDio, packageInfo);
|
||||||
|
|
||||||
if (!isBackground) {
|
|
||||||
VideoPlayerMediaKit.ensureInitialized(iOS: true, android: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.info("UserService init $tlog");
|
_logger.info("UserService init $tlog");
|
||||||
await UserService.instance.init();
|
await UserService.instance.init();
|
||||||
_logger.info("UserService init done $tlog");
|
_logger.info("UserService init done $tlog");
|
||||||
|
@ -1,266 +1,266 @@
|
|||||||
import 'dart:async';
|
// import 'dart:async';
|
||||||
import "dart:io";
|
// import "dart:io";
|
||||||
|
|
||||||
import 'package:chewie/chewie.dart';
|
// import 'package:chewie/chewie.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
// import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
// import 'package:flutter/material.dart';
|
||||||
import "package:fluttertoast/fluttertoast.dart";
|
// import "package:fluttertoast/fluttertoast.dart";
|
||||||
import "package:logging/logging.dart";
|
// import "package:logging/logging.dart";
|
||||||
import 'package:photos/core/constants.dart';
|
// import 'package:photos/core/constants.dart';
|
||||||
import "package:photos/core/event_bus.dart";
|
// import "package:photos/core/event_bus.dart";
|
||||||
import "package:photos/events/guest_view_event.dart";
|
// import "package:photos/events/guest_view_event.dart";
|
||||||
import 'package:photos/models/file/file.dart';
|
// import 'package:photos/models/file/file.dart';
|
||||||
import "package:photos/service_locator.dart";
|
// import "package:photos/service_locator.dart";
|
||||||
import "package:photos/services/filedata/filedata_service.dart";
|
// import "package:photos/services/filedata/filedata_service.dart";
|
||||||
import "package:photos/services/preview_video_store.dart";
|
// import "package:photos/services/preview_video_store.dart";
|
||||||
import "package:photos/ui/actions/file/file_actions.dart";
|
// import "package:photos/ui/actions/file/file_actions.dart";
|
||||||
import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
|
// import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
|
||||||
import "package:photos/ui/viewer/file/video_control.dart";
|
// import "package:photos/ui/viewer/file/video_control.dart";
|
||||||
import "package:photos/utils/data_util.dart";
|
// import "package:photos/utils/data_util.dart";
|
||||||
// import 'package:photos/ui/viewer/file/video_controls.dart';
|
// // import 'package:photos/ui/viewer/file/video_controls.dart';
|
||||||
import "package:photos/utils/dialog_util.dart";
|
// import "package:photos/utils/dialog_util.dart";
|
||||||
import 'package:photos/utils/file_util.dart';
|
// import 'package:photos/utils/file_util.dart';
|
||||||
import 'package:photos/utils/toast_util.dart';
|
// import 'package:photos/utils/toast_util.dart';
|
||||||
import "package:photos/utils/wakelock_util.dart";
|
// import "package:photos/utils/wakelock_util.dart";
|
||||||
import 'package:video_player/video_player.dart';
|
// import 'package:video_player/video_player.dart';
|
||||||
import 'package:visibility_detector/visibility_detector.dart';
|
// import 'package:visibility_detector/visibility_detector.dart';
|
||||||
|
|
||||||
class PreviewVideoWidget extends StatefulWidget {
|
// class PreviewVideoWidget extends StatefulWidget {
|
||||||
final EnteFile file;
|
// final EnteFile file;
|
||||||
final bool? autoPlay;
|
// final bool? autoPlay;
|
||||||
final String? tagPrefix;
|
// final String? tagPrefix;
|
||||||
final Function(bool)? playbackCallback;
|
// final Function(bool)? playbackCallback;
|
||||||
final void Function()? onStreamChange;
|
// final void Function()? onStreamChange;
|
||||||
|
|
||||||
const PreviewVideoWidget(
|
// const PreviewVideoWidget(
|
||||||
this.file, {
|
// this.file, {
|
||||||
this.autoPlay = true,
|
// this.autoPlay = true,
|
||||||
this.tagPrefix,
|
// this.tagPrefix,
|
||||||
this.playbackCallback,
|
// this.playbackCallback,
|
||||||
this.onStreamChange,
|
// this.onStreamChange,
|
||||||
super.key,
|
// super.key,
|
||||||
});
|
// });
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
State<PreviewVideoWidget> createState() => _PreviewVideoWidgetState();
|
// State<PreviewVideoWidget> createState() => _PreviewVideoWidgetState();
|
||||||
}
|
// }
|
||||||
|
|
||||||
class _PreviewVideoWidgetState extends State<PreviewVideoWidget> {
|
// class _PreviewVideoWidgetState extends State<PreviewVideoWidget> {
|
||||||
final _logger = Logger("PreviewVideoWidget");
|
// final _logger = Logger("PreviewVideoWidget");
|
||||||
VideoPlayerController? _videoPlayerController;
|
// VideoPlayerController? _videoPlayerController;
|
||||||
ChewieController? _chewieController;
|
// ChewieController? _chewieController;
|
||||||
final _progressNotifier = ValueNotifier<double?>(null);
|
// final _progressNotifier = ValueNotifier<double?>(null);
|
||||||
bool _isPlaying = false;
|
// bool _isPlaying = false;
|
||||||
final EnteWakeLock _wakeLock = EnteWakeLock();
|
// final EnteWakeLock _wakeLock = EnteWakeLock();
|
||||||
bool _isFileSwipeLocked = false;
|
// bool _isFileSwipeLocked = false;
|
||||||
late final StreamSubscription<GuestViewEvent> _fileSwipeLockEventSubscription;
|
// late final StreamSubscription<GuestViewEvent> _fileSwipeLockEventSubscription;
|
||||||
File? previewFile;
|
// File? previewFile;
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
void initState() {
|
// void initState() {
|
||||||
super.initState();
|
// super.initState();
|
||||||
|
|
||||||
_checkForPreview();
|
// _checkForPreview();
|
||||||
_fileSwipeLockEventSubscription =
|
// _fileSwipeLockEventSubscription =
|
||||||
Bus.instance.on<GuestViewEvent>().listen((event) {
|
// Bus.instance.on<GuestViewEvent>().listen((event) {
|
||||||
setState(() {
|
// setState(() {
|
||||||
_isFileSwipeLocked = event.swipeLocked;
|
// _isFileSwipeLocked = event.swipeLocked;
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
void dispose() {
|
// void dispose() {
|
||||||
_fileSwipeLockEventSubscription.cancel();
|
// _fileSwipeLockEventSubscription.cancel();
|
||||||
removeCallBack(widget.file);
|
// removeCallBack(widget.file);
|
||||||
_videoPlayerController?.dispose();
|
// _videoPlayerController?.dispose();
|
||||||
_chewieController?.dispose();
|
// _chewieController?.dispose();
|
||||||
_progressNotifier.dispose();
|
// _progressNotifier.dispose();
|
||||||
_wakeLock.dispose();
|
// _wakeLock.dispose();
|
||||||
super.dispose();
|
// super.dispose();
|
||||||
}
|
// }
|
||||||
|
|
||||||
Future<void> _checkForPreview() async {
|
// Future<void> _checkForPreview() async {
|
||||||
final data = await PreviewVideoStore.instance
|
// final data = await PreviewVideoStore.instance
|
||||||
.getPlaylist(widget.file)
|
// .getPlaylist(widget.file)
|
||||||
.onError((error, stackTrace) {
|
// .onError((error, stackTrace) {
|
||||||
if (!mounted) return;
|
// if (!mounted) return;
|
||||||
_logger.warning("Failed to download preview video", error, stackTrace);
|
// _logger.warning("Failed to download preview video", error, stackTrace);
|
||||||
Fluttertoast.showToast(msg: "Failed to download preview!");
|
// Fluttertoast.showToast(msg: "Failed to download preview!");
|
||||||
return null;
|
// return null;
|
||||||
});
|
// });
|
||||||
if (!mounted) return;
|
// if (!mounted) return;
|
||||||
if (data != null) {
|
// if (data != null) {
|
||||||
if (flagService.internalUser) {
|
// if (flagService.internalUser) {
|
||||||
final d =
|
// final d =
|
||||||
FileDataService.instance.previewIds?[widget.file.uploadedFileID!];
|
// FileDataService.instance.previewIds?[widget.file.uploadedFileID!];
|
||||||
if (d != null && widget.file.fileSize != null) {
|
// if (d != null && widget.file.fileSize != null) {
|
||||||
// show toast with human readable size
|
// // show toast with human readable size
|
||||||
final size = formatBytes(widget.file.fileSize!);
|
// final size = formatBytes(widget.file.fileSize!);
|
||||||
showToast(
|
// showToast(
|
||||||
context,
|
// context,
|
||||||
"Preview OG Size ($size), previewSize: ${formatBytes(d.objectSize)}",
|
// "Preview OG Size ($size), previewSize: ${formatBytes(d.objectSize)}",
|
||||||
);
|
// );
|
||||||
} else {
|
// } else {
|
||||||
showShortToast(context, "Playing preview");
|
// showShortToast(context, "Playing preview");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
previewFile = data.preview;
|
// previewFile = data.preview;
|
||||||
_setVideoPlayerController();
|
// _setVideoPlayerController();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
void _setVideoPlayerController() {
|
// void _setVideoPlayerController() {
|
||||||
if (!mounted) {
|
// if (!mounted) {
|
||||||
// Note: Do not initiale video player if widget is not mounted.
|
// // Note: Do not initiale video player if widget is not mounted.
|
||||||
// On Android, if multiple instance of ExoPlayer is created, it will start
|
// // 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
|
// // resulting in playback errors for videos. See https://github.com/google/ExoPlayer/issues/6168
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
VideoPlayerController videoPlayerController;
|
// VideoPlayerController videoPlayerController;
|
||||||
videoPlayerController = VideoPlayerController.file(previewFile!);
|
// videoPlayerController = VideoPlayerController.file(previewFile!);
|
||||||
|
|
||||||
debugPrint("videoPlayerController: $videoPlayerController");
|
// debugPrint("videoPlayerController: $videoPlayerController");
|
||||||
_videoPlayerController = videoPlayerController
|
// _videoPlayerController = videoPlayerController
|
||||||
..initialize().whenComplete(() {
|
// ..initialize().whenComplete(() {
|
||||||
if (mounted) {
|
// if (mounted) {
|
||||||
setState(() {});
|
// setState(() {});
|
||||||
}
|
// }
|
||||||
}).onError(
|
// }).onError(
|
||||||
(error, stackTrace) {
|
// (error, stackTrace) {
|
||||||
if (mounted && flagService.internalUser) {
|
// if (mounted && flagService.internalUser) {
|
||||||
if (error is Exception) {
|
// if (error is Exception) {
|
||||||
showErrorDialogForException(
|
// showErrorDialogForException(
|
||||||
context: context,
|
// context: context,
|
||||||
exception: error,
|
// exception: error,
|
||||||
message: "Failed to play video\n ${error.toString()}",
|
// message: "Failed to play video\n ${error.toString()}",
|
||||||
);
|
// );
|
||||||
} else {
|
// } else {
|
||||||
showToast(context, "Failed to play video");
|
// showToast(context, "Failed to play video");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
Widget build(BuildContext context) {
|
// Widget build(BuildContext context) {
|
||||||
final content = _videoPlayerController != null &&
|
// final content = _videoPlayerController != null &&
|
||||||
_videoPlayerController!.value.isInitialized
|
// _videoPlayerController!.value.isInitialized
|
||||||
? _getVideoPlayer()
|
// ? _getVideoPlayer()
|
||||||
: _getLoadingWidget();
|
// : _getLoadingWidget();
|
||||||
final contentWithDetector = GestureDetector(
|
// final contentWithDetector = GestureDetector(
|
||||||
onVerticalDragUpdate: _isFileSwipeLocked
|
// onVerticalDragUpdate: _isFileSwipeLocked
|
||||||
? null
|
// ? null
|
||||||
: (d) => {
|
// : (d) => {
|
||||||
if (d.delta.dy > dragSensitivity)
|
// if (d.delta.dy > dragSensitivity)
|
||||||
{
|
// {
|
||||||
Navigator.of(context).pop(),
|
// Navigator.of(context).pop(),
|
||||||
}
|
// }
|
||||||
else if (d.delta.dy < (dragSensitivity * -1))
|
// else if (d.delta.dy < (dragSensitivity * -1))
|
||||||
{
|
// {
|
||||||
showDetailsSheet(context, widget.file),
|
// showDetailsSheet(context, widget.file),
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
child: content,
|
// child: content,
|
||||||
);
|
// );
|
||||||
return VisibilityDetector(
|
// return VisibilityDetector(
|
||||||
key: Key(widget.file.tag),
|
// key: Key(widget.file.tag),
|
||||||
onVisibilityChanged: (info) {
|
// onVisibilityChanged: (info) {
|
||||||
if (info.visibleFraction < 1) {
|
// if (info.visibleFraction < 1) {
|
||||||
if (mounted && _chewieController != null) {
|
// if (mounted && _chewieController != null) {
|
||||||
_chewieController!.pause();
|
// _chewieController!.pause();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
child: Hero(
|
// child: Hero(
|
||||||
tag: widget.tagPrefix! + widget.file.tag,
|
// tag: widget.tagPrefix! + widget.file.tag,
|
||||||
child: contentWithDetector,
|
// child: contentWithDetector,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
Widget _getLoadingWidget() {
|
// Widget _getLoadingWidget() {
|
||||||
return Stack(
|
// return Stack(
|
||||||
children: [
|
// children: [
|
||||||
_getThumbnail(),
|
// _getThumbnail(),
|
||||||
Container(
|
// Container(
|
||||||
color: Colors.black12,
|
// color: Colors.black12,
|
||||||
constraints: const BoxConstraints.expand(),
|
// constraints: const BoxConstraints.expand(),
|
||||||
),
|
// ),
|
||||||
Center(
|
// Center(
|
||||||
child: SizedBox.fromSize(
|
// child: SizedBox.fromSize(
|
||||||
size: const Size.square(20),
|
// size: const Size.square(20),
|
||||||
child: ValueListenableBuilder(
|
// child: ValueListenableBuilder(
|
||||||
valueListenable: _progressNotifier,
|
// valueListenable: _progressNotifier,
|
||||||
builder: (BuildContext context, double? progress, _) {
|
// builder: (BuildContext context, double? progress, _) {
|
||||||
return progress == null || progress == 1
|
// return progress == null || progress == 1
|
||||||
? const CupertinoActivityIndicator(
|
// ? const CupertinoActivityIndicator(
|
||||||
color: Colors.white,
|
// color: Colors.white,
|
||||||
)
|
// )
|
||||||
: CircularProgressIndicator(
|
// : CircularProgressIndicator(
|
||||||
backgroundColor: Colors.black,
|
// backgroundColor: Colors.black,
|
||||||
value: progress,
|
// value: progress,
|
||||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
// valueColor: const AlwaysStoppedAnimation<Color>(
|
||||||
Color.fromRGBO(45, 194, 98, 1.0),
|
// Color.fromRGBO(45, 194, 98, 1.0),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
Widget _getThumbnail() {
|
// Widget _getThumbnail() {
|
||||||
return Container(
|
// return Container(
|
||||||
color: Colors.black,
|
// color: Colors.black,
|
||||||
constraints: const BoxConstraints.expand(),
|
// constraints: const BoxConstraints.expand(),
|
||||||
child: ThumbnailWidget(
|
// child: ThumbnailWidget(
|
||||||
widget.file,
|
// widget.file,
|
||||||
fit: BoxFit.contain,
|
// fit: BoxFit.contain,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
Future<void> _keepScreenAliveOnPlaying(bool isPlaying) async {
|
// Future<void> _keepScreenAliveOnPlaying(bool isPlaying) async {
|
||||||
if (isPlaying) {
|
// if (isPlaying) {
|
||||||
_wakeLock.enable();
|
// _wakeLock.enable();
|
||||||
}
|
// }
|
||||||
if (!isPlaying) {
|
// if (!isPlaying) {
|
||||||
_wakeLock.disable();
|
// _wakeLock.disable();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
Widget _getVideoPlayer() {
|
// Widget _getVideoPlayer() {
|
||||||
_videoPlayerController!.addListener(() {
|
// _videoPlayerController!.addListener(() {
|
||||||
if (_isPlaying != _videoPlayerController!.value.isPlaying) {
|
// if (_isPlaying != _videoPlayerController!.value.isPlaying) {
|
||||||
_isPlaying = _videoPlayerController!.value.isPlaying;
|
// _isPlaying = _videoPlayerController!.value.isPlaying;
|
||||||
if (widget.playbackCallback != null) {
|
// if (widget.playbackCallback != null) {
|
||||||
widget.playbackCallback!(_isPlaying);
|
// widget.playbackCallback!(_isPlaying);
|
||||||
}
|
// }
|
||||||
unawaited(_keepScreenAliveOnPlaying(_isPlaying));
|
// unawaited(_keepScreenAliveOnPlaying(_isPlaying));
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
_chewieController = ChewieController(
|
// _chewieController = ChewieController(
|
||||||
progressIndicatorDelay: const Duration(milliseconds: 200),
|
// progressIndicatorDelay: const Duration(milliseconds: 200),
|
||||||
videoPlayerController: _videoPlayerController!,
|
// videoPlayerController: _videoPlayerController!,
|
||||||
aspectRatio: _videoPlayerController!.value.aspectRatio,
|
// aspectRatio: _videoPlayerController!.value.aspectRatio,
|
||||||
autoPlay: widget.autoPlay!,
|
// autoPlay: widget.autoPlay!,
|
||||||
autoInitialize: true,
|
// autoInitialize: true,
|
||||||
looping: true,
|
// looping: true,
|
||||||
allowMuting: true,
|
// allowMuting: true,
|
||||||
allowFullScreen: false,
|
// allowFullScreen: false,
|
||||||
customControls: VideoControls(
|
// customControls: VideoControls(
|
||||||
file: widget.file,
|
// file: widget.file,
|
||||||
onStreamChange: widget.onStreamChange,
|
// onStreamChange: widget.onStreamChange,
|
||||||
playbackCallback: widget.playbackCallback,
|
// playbackCallback: widget.playbackCallback,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
return Container(
|
// return Container(
|
||||||
color: Colors.black,
|
// color: Colors.black,
|
||||||
child: Chewie(controller: _chewieController!),
|
// child: Chewie(controller: _chewieController!),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
@ -1,449 +1,449 @@
|
|||||||
// ignore_for_file: implementation_imports
|
// // ignore_for_file: implementation_imports
|
||||||
|
|
||||||
import 'dart:async';
|
// import 'dart:async';
|
||||||
|
|
||||||
import "package:chewie/chewie.dart";
|
// import "package:chewie/chewie.dart";
|
||||||
import "package:chewie/src/notifiers/index.dart";
|
// import "package:chewie/src/notifiers/index.dart";
|
||||||
import 'package:flutter/material.dart';
|
// import 'package:flutter/material.dart';
|
||||||
import "package:photos/models/file/file.dart";
|
// import "package:photos/models/file/file.dart";
|
||||||
import "package:photos/theme/colors.dart";
|
// import "package:photos/theme/colors.dart";
|
||||||
import "package:photos/theme/ente_theme.dart";
|
// import "package:photos/theme/ente_theme.dart";
|
||||||
import "package:photos/ui/common/loading_widget.dart";
|
// import "package:photos/ui/common/loading_widget.dart";
|
||||||
import "package:photos/ui/viewer/file/preview_status_widget.dart";
|
// import "package:photos/ui/viewer/file/preview_status_widget.dart";
|
||||||
import "package:photos/ui/viewer/file/video_control/custom_progress_bar.dart";
|
// import "package:photos/ui/viewer/file/video_control/custom_progress_bar.dart";
|
||||||
import "package:photos/utils/seconds_to_duration.dart";
|
// import "package:photos/utils/seconds_to_duration.dart";
|
||||||
import 'package:provider/provider.dart';
|
// import 'package:provider/provider.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
// import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
class VideoControls extends StatefulWidget {
|
// class VideoControls extends StatefulWidget {
|
||||||
const VideoControls({
|
// const VideoControls({
|
||||||
super.key,
|
// super.key,
|
||||||
required this.file,
|
// required this.file,
|
||||||
required this.onStreamChange,
|
// required this.onStreamChange,
|
||||||
required this.playbackCallback,
|
// required this.playbackCallback,
|
||||||
});
|
// });
|
||||||
final EnteFile file;
|
// final EnteFile file;
|
||||||
final void Function()? onStreamChange;
|
// final void Function()? onStreamChange;
|
||||||
final void Function(bool)? playbackCallback;
|
// final void Function(bool)? playbackCallback;
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
State<StatefulWidget> createState() {
|
// State<StatefulWidget> createState() {
|
||||||
return _VideoControlsState();
|
// return _VideoControlsState();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
class _VideoControlsState extends State<VideoControls>
|
// class _VideoControlsState extends State<VideoControls>
|
||||||
with SingleTickerProviderStateMixin {
|
// with SingleTickerProviderStateMixin {
|
||||||
late PlayerNotifier notifier;
|
// late PlayerNotifier notifier;
|
||||||
late VideoPlayerValue _latestValue;
|
// late VideoPlayerValue _latestValue;
|
||||||
Timer? _hideTimer;
|
// Timer? _hideTimer;
|
||||||
Timer? _initTimer;
|
// Timer? _initTimer;
|
||||||
Timer? _showAfterExpandCollapseTimer;
|
// Timer? _showAfterExpandCollapseTimer;
|
||||||
bool _dragging = false;
|
// bool _dragging = false;
|
||||||
bool _displayTapped = false;
|
// bool _displayTapped = false;
|
||||||
Timer? _bufferingDisplayTimer;
|
// Timer? _bufferingDisplayTimer;
|
||||||
bool _displayBufferingIndicator = false;
|
// bool _displayBufferingIndicator = false;
|
||||||
|
|
||||||
final barHeight = 48.0 * 1.5;
|
// final barHeight = 48.0 * 1.5;
|
||||||
final marginSize = 5.0;
|
// final marginSize = 5.0;
|
||||||
|
|
||||||
late VideoPlayerController controller;
|
// late VideoPlayerController controller;
|
||||||
ChewieController? _chewieController;
|
// ChewieController? _chewieController;
|
||||||
|
|
||||||
// We know that _chewieController is set in didChangeDependencies
|
// // We know that _chewieController is set in didChangeDependencies
|
||||||
ChewieController get chewieController => _chewieController!;
|
// ChewieController get chewieController => _chewieController!;
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
void initState() {
|
// void initState() {
|
||||||
super.initState();
|
// super.initState();
|
||||||
notifier = Provider.of<PlayerNotifier>(context, listen: false);
|
// notifier = Provider.of<PlayerNotifier>(context, listen: false);
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
Widget build(BuildContext context) {
|
// Widget build(BuildContext context) {
|
||||||
if (_latestValue.hasError) {
|
// if (_latestValue.hasError) {
|
||||||
return chewieController.errorBuilder?.call(
|
// return chewieController.errorBuilder?.call(
|
||||||
context,
|
// context,
|
||||||
chewieController.videoPlayerController.value.errorDescription!,
|
// chewieController.videoPlayerController.value.errorDescription!,
|
||||||
) ??
|
// ) ??
|
||||||
Center(
|
// Center(
|
||||||
child: Icon(
|
// child: Icon(
|
||||||
Icons.error,
|
// Icons.error,
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
// color: Theme.of(context).colorScheme.onSurface,
|
||||||
size: 42,
|
// size: 42,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
return MouseRegion(
|
// return MouseRegion(
|
||||||
onHover: (_) {
|
// onHover: (_) {
|
||||||
_cancelAndRestartTimer();
|
// _cancelAndRestartTimer();
|
||||||
},
|
// },
|
||||||
child: GestureDetector(
|
// child: GestureDetector(
|
||||||
onTap: () => _cancelAndRestartTimer(),
|
// onTap: () => _cancelAndRestartTimer(),
|
||||||
child: AbsorbPointer(
|
// child: AbsorbPointer(
|
||||||
absorbing: notifier.hideStuff,
|
// absorbing: notifier.hideStuff,
|
||||||
child: Stack(
|
// child: Stack(
|
||||||
children: [
|
// children: [
|
||||||
if (_displayBufferingIndicator)
|
// if (_displayBufferingIndicator)
|
||||||
_chewieController?.bufferingBuilder?.call(context) ??
|
// _chewieController?.bufferingBuilder?.call(context) ??
|
||||||
const Center(
|
// const Center(
|
||||||
child: EnteLoadingWidget(
|
// child: EnteLoadingWidget(
|
||||||
size: 32,
|
// size: 32,
|
||||||
color: fillBaseDark,
|
// color: fillBaseDark,
|
||||||
padding: 0,
|
// padding: 0,
|
||||||
),
|
// ),
|
||||||
)
|
// )
|
||||||
else
|
// else
|
||||||
_buildHitArea(),
|
// _buildHitArea(),
|
||||||
SafeArea(
|
// SafeArea(
|
||||||
top: false,
|
// top: false,
|
||||||
left: false,
|
// left: false,
|
||||||
right: false,
|
// right: false,
|
||||||
child: Column(
|
// child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
// mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: <Widget>[
|
// children: <Widget>[
|
||||||
PreviewStatusWidget(
|
// PreviewStatusWidget(
|
||||||
showControls: !notifier.hideStuff,
|
// showControls: !notifier.hideStuff,
|
||||||
file: widget.file,
|
// file: widget.file,
|
||||||
isPreviewPlayer: true,
|
// isPreviewPlayer: true,
|
||||||
onStreamChange: widget.onStreamChange,
|
// onStreamChange: widget.onStreamChange,
|
||||||
),
|
// ),
|
||||||
if (!chewieController.isLive) _buildBottomBar(context),
|
// if (!chewieController.isLive) _buildBottomBar(context),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
void dispose() {
|
// void dispose() {
|
||||||
_dispose();
|
// _dispose();
|
||||||
super.dispose();
|
// super.dispose();
|
||||||
}
|
// }
|
||||||
|
|
||||||
void _dispose() {
|
// void _dispose() {
|
||||||
controller.removeListener(_updateState);
|
// controller.removeListener(_updateState);
|
||||||
_hideTimer?.cancel();
|
// _hideTimer?.cancel();
|
||||||
_initTimer?.cancel();
|
// _initTimer?.cancel();
|
||||||
_showAfterExpandCollapseTimer?.cancel();
|
// _showAfterExpandCollapseTimer?.cancel();
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
void didChangeDependencies() {
|
// void didChangeDependencies() {
|
||||||
final oldController = _chewieController;
|
// final oldController = _chewieController;
|
||||||
_chewieController = ChewieController.of(context);
|
// _chewieController = ChewieController.of(context);
|
||||||
controller = chewieController.videoPlayerController;
|
// controller = chewieController.videoPlayerController;
|
||||||
|
|
||||||
if (oldController != chewieController) {
|
// if (oldController != chewieController) {
|
||||||
_dispose();
|
// _dispose();
|
||||||
_initialize();
|
// _initialize();
|
||||||
}
|
// }
|
||||||
|
|
||||||
super.didChangeDependencies();
|
// super.didChangeDependencies();
|
||||||
}
|
// }
|
||||||
|
|
||||||
AnimatedOpacity _buildBottomBar(
|
// AnimatedOpacity _buildBottomBar(
|
||||||
BuildContext context,
|
// BuildContext context,
|
||||||
) {
|
// ) {
|
||||||
return AnimatedOpacity(
|
// return AnimatedOpacity(
|
||||||
opacity: notifier.hideStuff ? 0.0 : 1.0,
|
// opacity: notifier.hideStuff ? 0.0 : 1.0,
|
||||||
duration: const Duration(milliseconds: 300),
|
// duration: const Duration(milliseconds: 300),
|
||||||
child: Container(
|
// child: Container(
|
||||||
height: 40,
|
// height: 40,
|
||||||
margin: const EdgeInsets.only(bottom: 60, left: 8, right: 8),
|
// margin: const EdgeInsets.only(bottom: 60, left: 8, right: 8),
|
||||||
child: Container(
|
// child: Container(
|
||||||
padding: const EdgeInsets.fromLTRB(
|
// padding: const EdgeInsets.fromLTRB(
|
||||||
16,
|
// 16,
|
||||||
4,
|
// 4,
|
||||||
16,
|
// 16,
|
||||||
4,
|
// 4,
|
||||||
),
|
// ),
|
||||||
decoration: BoxDecoration(
|
// decoration: BoxDecoration(
|
||||||
color: Colors.black.withOpacity(0.3),
|
// color: Colors.black.withOpacity(0.3),
|
||||||
borderRadius: const BorderRadius.all(
|
// borderRadius: const BorderRadius.all(
|
||||||
Radius.circular(8),
|
// Radius.circular(8),
|
||||||
),
|
// ),
|
||||||
border: Border.all(
|
// border: Border.all(
|
||||||
color: strokeFaintDark,
|
// color: strokeFaintDark,
|
||||||
width: 1,
|
// width: 1,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
child: Row(
|
// child: Row(
|
||||||
children: [
|
// children: [
|
||||||
Text(
|
// Text(
|
||||||
secondsToDuration(_latestValue.position.inSeconds),
|
// secondsToDuration(_latestValue.position.inSeconds),
|
||||||
style: getEnteTextTheme(
|
// style: getEnteTextTheme(
|
||||||
context,
|
// context,
|
||||||
).mini.copyWith(
|
// ).mini.copyWith(
|
||||||
color: textBaseDark,
|
// color: textBaseDark,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 16),
|
// const SizedBox(width: 16),
|
||||||
Expanded(
|
// Expanded(
|
||||||
child: _buildProgressBar(),
|
// child: _buildProgressBar(),
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 16),
|
// const SizedBox(width: 16),
|
||||||
Text(
|
// Text(
|
||||||
secondsToDuration(_latestValue.duration.inSeconds),
|
// secondsToDuration(_latestValue.duration.inSeconds),
|
||||||
style: getEnteTextTheme(
|
// style: getEnteTextTheme(
|
||||||
context,
|
// context,
|
||||||
).mini.copyWith(
|
// ).mini.copyWith(
|
||||||
color: textBaseDark,
|
// color: textBaseDark,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
Widget _buildHitArea() {
|
// Widget _buildHitArea() {
|
||||||
final bool isFinished = (_latestValue.position >= _latestValue.duration) &&
|
// final bool isFinished = (_latestValue.position >= _latestValue.duration) &&
|
||||||
_latestValue.duration.inSeconds > 0;
|
// _latestValue.duration.inSeconds > 0;
|
||||||
final bool showPlayButton = true && !_dragging && !notifier.hideStuff;
|
// final bool showPlayButton = true && !_dragging && !notifier.hideStuff;
|
||||||
|
|
||||||
return GestureDetector(
|
// return GestureDetector(
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
if (_latestValue.isPlaying) {
|
// if (_latestValue.isPlaying) {
|
||||||
if (_displayTapped) {
|
// if (_displayTapped) {
|
||||||
setState(() {
|
// setState(() {
|
||||||
notifier.hideStuff = true;
|
// notifier.hideStuff = true;
|
||||||
});
|
// });
|
||||||
} else {
|
// } else {
|
||||||
_cancelAndRestartTimer();
|
// _cancelAndRestartTimer();
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
setState(() {
|
// setState(() {
|
||||||
notifier.hideStuff = true;
|
// notifier.hideStuff = true;
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
widget.playbackCallback?.call(notifier.hideStuff);
|
// widget.playbackCallback?.call(notifier.hideStuff);
|
||||||
},
|
// },
|
||||||
child: Container(
|
// child: Container(
|
||||||
alignment: Alignment.center,
|
// alignment: Alignment.center,
|
||||||
color: Colors.transparent,
|
// color: Colors.transparent,
|
||||||
child: Row(
|
// child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
// mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
// children: [
|
||||||
Container(
|
// Container(
|
||||||
margin: EdgeInsets.symmetric(
|
// margin: EdgeInsets.symmetric(
|
||||||
horizontal: marginSize,
|
// horizontal: marginSize,
|
||||||
),
|
// ),
|
||||||
child: CenterPlayButton(
|
// child: CenterPlayButton(
|
||||||
backgroundColor: Colors.black54,
|
// backgroundColor: Colors.black54,
|
||||||
iconColor: Colors.white,
|
// iconColor: Colors.white,
|
||||||
isFinished: isFinished,
|
// isFinished: isFinished,
|
||||||
isPlaying: controller.value.isPlaying,
|
// isPlaying: controller.value.isPlaying,
|
||||||
show: showPlayButton,
|
// show: showPlayButton,
|
||||||
onPressed: _playPause,
|
// onPressed: _playPause,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
void _cancelAndRestartTimer() {
|
// void _cancelAndRestartTimer() {
|
||||||
_hideTimer?.cancel();
|
// _hideTimer?.cancel();
|
||||||
_startHideTimer();
|
// _startHideTimer();
|
||||||
|
|
||||||
setState(() {
|
// setState(() {
|
||||||
notifier.hideStuff = false;
|
// notifier.hideStuff = false;
|
||||||
_displayTapped = true;
|
// _displayTapped = true;
|
||||||
});
|
// });
|
||||||
widget.playbackCallback?.call(notifier.hideStuff);
|
// widget.playbackCallback?.call(notifier.hideStuff);
|
||||||
}
|
// }
|
||||||
|
|
||||||
Future<void> _initialize() async {
|
// Future<void> _initialize() async {
|
||||||
controller.addListener(_updateState);
|
// controller.addListener(_updateState);
|
||||||
|
|
||||||
_updateState();
|
// _updateState();
|
||||||
|
|
||||||
if (controller.value.isPlaying || chewieController.autoPlay) {
|
// if (controller.value.isPlaying || chewieController.autoPlay) {
|
||||||
_startHideTimer();
|
// _startHideTimer();
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (chewieController.showControlsOnInitialize) {
|
// if (chewieController.showControlsOnInitialize) {
|
||||||
_initTimer = Timer(const Duration(milliseconds: 200), () {
|
// _initTimer = Timer(const Duration(milliseconds: 200), () {
|
||||||
setState(() {
|
// setState(() {
|
||||||
notifier.hideStuff = false;
|
// notifier.hideStuff = false;
|
||||||
});
|
// });
|
||||||
widget.playbackCallback?.call(notifier.hideStuff);
|
// widget.playbackCallback?.call(notifier.hideStuff);
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
void _playPause() {
|
// void _playPause() {
|
||||||
final bool isFinished = (_latestValue.position >= _latestValue.duration) &&
|
// final bool isFinished = (_latestValue.position >= _latestValue.duration) &&
|
||||||
_latestValue.duration.inSeconds > 0;
|
// _latestValue.duration.inSeconds > 0;
|
||||||
|
|
||||||
setState(() {
|
// setState(() {
|
||||||
if (controller.value.isPlaying) {
|
// if (controller.value.isPlaying) {
|
||||||
notifier.hideStuff = false;
|
// notifier.hideStuff = false;
|
||||||
_hideTimer?.cancel();
|
// _hideTimer?.cancel();
|
||||||
controller.pause();
|
// controller.pause();
|
||||||
} else {
|
// } else {
|
||||||
_cancelAndRestartTimer();
|
// _cancelAndRestartTimer();
|
||||||
|
|
||||||
if (!controller.value.isInitialized) {
|
// if (!controller.value.isInitialized) {
|
||||||
controller.initialize().then((_) {
|
// controller.initialize().then((_) {
|
||||||
controller.play();
|
// controller.play();
|
||||||
});
|
// });
|
||||||
} else {
|
// } else {
|
||||||
if (isFinished) {
|
// if (isFinished) {
|
||||||
controller.seekTo(Duration.zero);
|
// controller.seekTo(Duration.zero);
|
||||||
}
|
// }
|
||||||
controller.play();
|
// controller.play();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
widget.playbackCallback?.call(notifier.hideStuff);
|
// widget.playbackCallback?.call(notifier.hideStuff);
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
void _startHideTimer() {
|
// void _startHideTimer() {
|
||||||
final hideControlsTimer = chewieController.hideControlsTimer.isNegative
|
// final hideControlsTimer = chewieController.hideControlsTimer.isNegative
|
||||||
? ChewieController.defaultHideControlsTimer
|
// ? ChewieController.defaultHideControlsTimer
|
||||||
: chewieController.hideControlsTimer;
|
// : chewieController.hideControlsTimer;
|
||||||
_hideTimer = Timer(hideControlsTimer, () {
|
// _hideTimer = Timer(hideControlsTimer, () {
|
||||||
setState(() {
|
// setState(() {
|
||||||
notifier.hideStuff = true;
|
// notifier.hideStuff = true;
|
||||||
});
|
// });
|
||||||
widget.playbackCallback?.call(notifier.hideStuff);
|
// widget.playbackCallback?.call(notifier.hideStuff);
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
void _bufferingTimerTimeout() {
|
// void _bufferingTimerTimeout() {
|
||||||
_displayBufferingIndicator = true;
|
// _displayBufferingIndicator = true;
|
||||||
if (mounted) {
|
// if (mounted) {
|
||||||
setState(() {});
|
// setState(() {});
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
void _updateState() {
|
// void _updateState() {
|
||||||
if (!mounted) return;
|
// if (!mounted) return;
|
||||||
|
|
||||||
// display the progress bar indicator only after the buffering delay if it has been set
|
// // display the progress bar indicator only after the buffering delay if it has been set
|
||||||
if (chewieController.progressIndicatorDelay != null) {
|
// if (chewieController.progressIndicatorDelay != null) {
|
||||||
if (controller.value.isBuffering) {
|
// if (controller.value.isBuffering) {
|
||||||
_bufferingDisplayTimer ??= Timer(
|
// _bufferingDisplayTimer ??= Timer(
|
||||||
chewieController.progressIndicatorDelay!,
|
// chewieController.progressIndicatorDelay!,
|
||||||
_bufferingTimerTimeout,
|
// _bufferingTimerTimeout,
|
||||||
);
|
// );
|
||||||
} else {
|
// } else {
|
||||||
_bufferingDisplayTimer?.cancel();
|
// _bufferingDisplayTimer?.cancel();
|
||||||
_bufferingDisplayTimer = null;
|
// _bufferingDisplayTimer = null;
|
||||||
_displayBufferingIndicator = false;
|
// _displayBufferingIndicator = false;
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
_displayBufferingIndicator = controller.value.isBuffering;
|
// _displayBufferingIndicator = controller.value.isBuffering;
|
||||||
}
|
// }
|
||||||
|
|
||||||
setState(() {
|
// setState(() {
|
||||||
_latestValue = controller.value;
|
// _latestValue = controller.value;
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
Widget _buildProgressBar() {
|
// Widget _buildProgressBar() {
|
||||||
final colorScheme = getEnteColorScheme(context);
|
// final colorScheme = getEnteColorScheme(context);
|
||||||
return Expanded(
|
// return Expanded(
|
||||||
child: CustomProgressBar(
|
// child: CustomProgressBar(
|
||||||
controller,
|
// controller,
|
||||||
onDragStart: () {
|
// onDragStart: () {
|
||||||
setState(() {
|
// setState(() {
|
||||||
_dragging = true;
|
// _dragging = true;
|
||||||
});
|
// });
|
||||||
|
|
||||||
_hideTimer?.cancel();
|
// _hideTimer?.cancel();
|
||||||
},
|
// },
|
||||||
onDragUpdate: () {
|
// onDragUpdate: () {
|
||||||
_hideTimer?.cancel();
|
// _hideTimer?.cancel();
|
||||||
},
|
// },
|
||||||
onDragEnd: () {
|
// onDragEnd: () {
|
||||||
setState(() {
|
// setState(() {
|
||||||
_dragging = false;
|
// _dragging = false;
|
||||||
});
|
// });
|
||||||
|
|
||||||
_startHideTimer();
|
// _startHideTimer();
|
||||||
},
|
// },
|
||||||
colors: ChewieProgressColors(
|
// colors: ChewieProgressColors(
|
||||||
playedColor: colorScheme.primary300,
|
// playedColor: colorScheme.primary300,
|
||||||
handleColor: backgroundElevatedLight,
|
// handleColor: backgroundElevatedLight,
|
||||||
bufferedColor: backgroundElevatedLight.withOpacity(0.5),
|
// bufferedColor: backgroundElevatedLight.withOpacity(0.5),
|
||||||
backgroundColor: fillMutedDark,
|
// backgroundColor: fillMutedDark,
|
||||||
),
|
// ),
|
||||||
draggableProgressBar: chewieController.draggableProgressBar,
|
// draggableProgressBar: chewieController.draggableProgressBar,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
class CenterPlayButton extends StatelessWidget {
|
// class CenterPlayButton extends StatelessWidget {
|
||||||
const CenterPlayButton({
|
// const CenterPlayButton({
|
||||||
super.key,
|
// super.key,
|
||||||
required this.backgroundColor,
|
// required this.backgroundColor,
|
||||||
this.iconColor,
|
// this.iconColor,
|
||||||
required this.show,
|
// required this.show,
|
||||||
required this.isPlaying,
|
// required this.isPlaying,
|
||||||
required this.isFinished,
|
// required this.isFinished,
|
||||||
this.onPressed,
|
// this.onPressed,
|
||||||
});
|
// });
|
||||||
|
|
||||||
final Color backgroundColor;
|
// final Color backgroundColor;
|
||||||
final Color? iconColor;
|
// final Color? iconColor;
|
||||||
final bool show;
|
// final bool show;
|
||||||
final bool isPlaying;
|
// final bool isPlaying;
|
||||||
final bool isFinished;
|
// final bool isFinished;
|
||||||
final VoidCallback? onPressed;
|
// final VoidCallback? onPressed;
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
Widget build(BuildContext context) {
|
// Widget build(BuildContext context) {
|
||||||
return AnimatedOpacity(
|
// return AnimatedOpacity(
|
||||||
opacity: show ? 1.0 : 0.0,
|
// opacity: show ? 1.0 : 0.0,
|
||||||
duration: const Duration(milliseconds: 300),
|
// duration: const Duration(milliseconds: 300),
|
||||||
child: Container(
|
// child: Container(
|
||||||
width: 54,
|
// width: 54,
|
||||||
height: 54,
|
// height: 54,
|
||||||
decoration: BoxDecoration(
|
// decoration: BoxDecoration(
|
||||||
color: Colors.black.withOpacity(0.3),
|
// color: Colors.black.withOpacity(0.3),
|
||||||
shape: BoxShape.circle,
|
// shape: BoxShape.circle,
|
||||||
border: Border.all(
|
// border: Border.all(
|
||||||
color: strokeFaintDark,
|
// color: strokeFaintDark,
|
||||||
width: 1,
|
// width: 1,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
child: GestureDetector(
|
// child: GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
// behavior: HitTestBehavior.opaque,
|
||||||
onTap: onPressed,
|
// onTap: onPressed,
|
||||||
child: AnimatedSwitcher(
|
// child: AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 250),
|
// duration: const Duration(milliseconds: 250),
|
||||||
transitionBuilder: (Widget child, Animation<double> animation) {
|
// transitionBuilder: (Widget child, Animation<double> animation) {
|
||||||
return ScaleTransition(scale: animation, child: child);
|
// return ScaleTransition(scale: animation, child: child);
|
||||||
},
|
// },
|
||||||
switchInCurve: Curves.easeInOutQuart,
|
// switchInCurve: Curves.easeInOutQuart,
|
||||||
switchOutCurve: Curves.easeInOutQuart,
|
// switchOutCurve: Curves.easeInOutQuart,
|
||||||
child: isPlaying
|
// child: isPlaying
|
||||||
? const Icon(
|
// ? const Icon(
|
||||||
Icons.pause,
|
// Icons.pause,
|
||||||
size: 32,
|
// size: 32,
|
||||||
key: ValueKey("pause"),
|
// key: ValueKey("pause"),
|
||||||
color: Colors.white,
|
// color: Colors.white,
|
||||||
)
|
// )
|
||||||
: const Icon(
|
// : const Icon(
|
||||||
Icons.play_arrow,
|
// Icons.play_arrow,
|
||||||
size: 36,
|
// size: 36,
|
||||||
key: ValueKey("play"),
|
// key: ValueKey("play"),
|
||||||
color: Colors.white,
|
// color: Colors.white,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
@ -7,8 +7,8 @@ import "package:photos/events/use_media_kit_for_video.dart";
|
|||||||
import "package:photos/models/file/file.dart";
|
import "package:photos/models/file/file.dart";
|
||||||
import "package:photos/services/filedata/filedata_service.dart";
|
import "package:photos/services/filedata/filedata_service.dart";
|
||||||
import "package:photos/services/preview_video_store.dart";
|
import "package:photos/services/preview_video_store.dart";
|
||||||
import "package:photos/ui/viewer/file/preview_video_widget.dart";
|
|
||||||
import "package:photos/ui/viewer/file/video_widget_media_kit_new.dart";
|
import "package:photos/ui/viewer/file/video_widget_media_kit_new.dart";
|
||||||
|
import "package:photos/ui/viewer/file/video_widget_media_kit_preview.dart";
|
||||||
import "package:photos/ui/viewer/file/video_widget_native.dart";
|
import "package:photos/ui/viewer/file/video_widget_native.dart";
|
||||||
|
|
||||||
class VideoWidget extends StatefulWidget {
|
class VideoWidget extends StatefulWidget {
|
||||||
@ -60,7 +60,7 @@ class _VideoWidgetState extends State<VideoWidget> {
|
|||||||
?.containsKey(widget.file.uploadedFileID!) ??
|
?.containsKey(widget.file.uploadedFileID!) ??
|
||||||
false);
|
false);
|
||||||
if (isPreviewVideoPlayable && selectPreviewForPlay) {
|
if (isPreviewVideoPlayable && selectPreviewForPlay) {
|
||||||
return PreviewVideoWidget(
|
return VideoWidgetMediaKitPreview(
|
||||||
widget.file,
|
widget.file,
|
||||||
tagPrefix: widget.tagPrefix,
|
tagPrefix: widget.tagPrefix,
|
||||||
playbackCallback: widget.playbackCallback,
|
playbackCallback: widget.playbackCallback,
|
||||||
|
458
mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart
Normal file
458
mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart
Normal file
@ -0,0 +1,458 @@
|
|||||||
|
import "dart:async";
|
||||||
|
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:media_kit_video/media_kit_video.dart";
|
||||||
|
import "package:photos/models/file/file.dart";
|
||||||
|
import "package:photos/theme/colors.dart";
|
||||||
|
import "package:photos/theme/ente_theme.dart";
|
||||||
|
import "package:photos/ui/viewer/file/preview_status_widget.dart";
|
||||||
|
import "package:photos/utils/debouncer.dart";
|
||||||
|
import "package:photos/utils/seconds_to_duration.dart";
|
||||||
|
|
||||||
|
class VideoWidget extends StatefulWidget {
|
||||||
|
final EnteFile file;
|
||||||
|
final VideoController controller;
|
||||||
|
final Function(bool)? playbackCallback;
|
||||||
|
final bool isFromMemories;
|
||||||
|
final void Function() onStreamChange;
|
||||||
|
|
||||||
|
const VideoWidget(
|
||||||
|
this.file,
|
||||||
|
this.controller,
|
||||||
|
this.playbackCallback, {
|
||||||
|
super.key,
|
||||||
|
required this.isFromMemories,
|
||||||
|
// ignore: unused_element
|
||||||
|
required this.onStreamChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<VideoWidget> createState() => _VideoWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VideoWidgetState extends State<VideoWidget> {
|
||||||
|
final showControlsNotifier = ValueNotifier<bool>(true);
|
||||||
|
static const verticalMargin = 72.0;
|
||||||
|
final _hideControlsDebouncer = Debouncer(
|
||||||
|
const Duration(milliseconds: 2000),
|
||||||
|
);
|
||||||
|
final _isSeekingNotifier = ValueNotifier<bool>(false);
|
||||||
|
late final StreamSubscription<bool> _isPlayingStreamSubscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_isPlayingStreamSubscription =
|
||||||
|
widget.controller.player.stream.playing.listen((isPlaying) {
|
||||||
|
if (isPlaying && !_isSeekingNotifier.value) {
|
||||||
|
_hideControlsDebouncer.run(() async {
|
||||||
|
showControlsNotifier.value = false;
|
||||||
|
widget.playbackCallback?.call(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_isSeekingNotifier.addListener(isSeekingListener);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
showControlsNotifier.dispose();
|
||||||
|
_isPlayingStreamSubscription.cancel();
|
||||||
|
_hideControlsDebouncer.cancelDebounceTimer();
|
||||||
|
_isSeekingNotifier.removeListener(isSeekingListener);
|
||||||
|
_isSeekingNotifier.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void isSeekingListener() {
|
||||||
|
if (_isSeekingNotifier.value) {
|
||||||
|
_hideControlsDebouncer.cancelDebounceTimer();
|
||||||
|
} else {
|
||||||
|
if (widget.controller.player.state.playing) {
|
||||||
|
_hideControlsDebouncer.run(() async {
|
||||||
|
showControlsNotifier.value = false;
|
||||||
|
widget.playbackCallback?.call(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Video(
|
||||||
|
controller: widget.controller,
|
||||||
|
controls: (state) {
|
||||||
|
return ValueListenableBuilder(
|
||||||
|
valueListenable: showControlsNotifier,
|
||||||
|
builder: (context, value, _) {
|
||||||
|
return AnimatedOpacity(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
opacity: value ? 1 : 0,
|
||||||
|
curve: Curves.easeInOutQuad,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
showControlsNotifier.value = !showControlsNotifier.value;
|
||||||
|
if (widget.playbackCallback != null) {
|
||||||
|
widget.playbackCallback!(
|
||||||
|
!showControlsNotifier.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
constraints: const BoxConstraints.expand(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IgnorePointer(
|
||||||
|
ignoring: !value,
|
||||||
|
child: PlayPauseButtonMediaKit(widget.controller),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: verticalMargin,
|
||||||
|
right: 0,
|
||||||
|
left: 0,
|
||||||
|
child: IgnorePointer(
|
||||||
|
ignoring: !value,
|
||||||
|
child: SafeArea(
|
||||||
|
top: false,
|
||||||
|
left: false,
|
||||||
|
right: false,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: widget.isFromMemories ? 32 : 0,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
widget.file.caption != null
|
||||||
|
? Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(
|
||||||
|
16,
|
||||||
|
12,
|
||||||
|
16,
|
||||||
|
8,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
widget.file.caption!,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
style: getEnteTextTheme(context)
|
||||||
|
.mini
|
||||||
|
.copyWith(
|
||||||
|
color: textBaseDark,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
PreviewStatusWidget(
|
||||||
|
showControls: value,
|
||||||
|
file: widget.file,
|
||||||
|
isPreviewPlayer: true,
|
||||||
|
onStreamChange: widget.onStreamChange,
|
||||||
|
),
|
||||||
|
SeekBarAndDuration(
|
||||||
|
controller: widget.controller,
|
||||||
|
isSeekingNotifier: _isSeekingNotifier,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlayPauseButtonMediaKit extends StatefulWidget {
|
||||||
|
final VideoController? controller;
|
||||||
|
const PlayPauseButtonMediaKit(
|
||||||
|
this.controller, {
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PlayPauseButtonMediaKit> createState() => _PlayPauseButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlayPauseButtonState extends State<PlayPauseButtonMediaKit> {
|
||||||
|
bool _isPlaying = true;
|
||||||
|
late final StreamSubscription<bool>? isPlayingStreamSubscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
isPlayingStreamSubscription =
|
||||||
|
widget.controller?.player.stream.playing.listen((isPlaying) {
|
||||||
|
setState(() {
|
||||||
|
_isPlaying = isPlaying;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
isPlayingStreamSubscription?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
if (widget.controller?.player.state.playing ?? false) {
|
||||||
|
widget.controller?.player.pause();
|
||||||
|
} else {
|
||||||
|
widget.controller?.player.play();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: 54,
|
||||||
|
height: 54,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withOpacity(0.3),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: strokeFaintDark,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||||
|
return ScaleTransition(scale: animation, child: child);
|
||||||
|
},
|
||||||
|
switchInCurve: Curves.easeInOutQuart,
|
||||||
|
switchOutCurve: Curves.easeInOutQuart,
|
||||||
|
child: _isPlaying
|
||||||
|
? const Icon(
|
||||||
|
Icons.pause,
|
||||||
|
size: 32,
|
||||||
|
key: ValueKey("pause"),
|
||||||
|
color: Colors.white,
|
||||||
|
)
|
||||||
|
: const Icon(
|
||||||
|
Icons.play_arrow,
|
||||||
|
size: 36,
|
||||||
|
key: ValueKey("play"),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SeekBarAndDuration extends StatelessWidget {
|
||||||
|
final VideoController? controller;
|
||||||
|
final ValueNotifier<bool> isSeekingNotifier;
|
||||||
|
|
||||||
|
const SeekBarAndDuration({
|
||||||
|
super.key,
|
||||||
|
required this.controller,
|
||||||
|
required this.isSeekingNotifier,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.fromLTRB(
|
||||||
|
16,
|
||||||
|
4,
|
||||||
|
16,
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withOpacity(0.3),
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(8),
|
||||||
|
),
|
||||||
|
border: Border.all(
|
||||||
|
color: strokeFaintDark,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
StreamBuilder(
|
||||||
|
stream: controller?.player.stream.position,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return Text(
|
||||||
|
"0:00",
|
||||||
|
style: getEnteTextTheme(
|
||||||
|
context,
|
||||||
|
).mini.copyWith(
|
||||||
|
color: textBaseDark,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Text(
|
||||||
|
secondsToDuration(snapshot.data!.inSeconds),
|
||||||
|
style: getEnteTextTheme(
|
||||||
|
context,
|
||||||
|
).mini.copyWith(
|
||||||
|
color: textBaseDark,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: SeekBar(
|
||||||
|
controller!,
|
||||||
|
isSeekingNotifier,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
_secondsToDuration(
|
||||||
|
controller!.player.state.duration.inSeconds,
|
||||||
|
),
|
||||||
|
style: getEnteTextTheme(
|
||||||
|
context,
|
||||||
|
).mini.copyWith(
|
||||||
|
color: textBaseDark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the duration in the format "h:mm:ss" or "m:ss".
|
||||||
|
String _secondsToDuration(int totalSeconds) {
|
||||||
|
final hours = totalSeconds ~/ 3600;
|
||||||
|
final minutes = (totalSeconds % 3600) ~/ 60;
|
||||||
|
final seconds = totalSeconds % 60;
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return '${hours.toString().padLeft(1, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
||||||
|
} else {
|
||||||
|
return '${minutes.toString().padLeft(1, '0')}:${seconds.toString().padLeft(2, '0')}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SeekBar extends StatefulWidget {
|
||||||
|
final VideoController controller;
|
||||||
|
final ValueNotifier<bool> isSeekingNotifier;
|
||||||
|
const SeekBar(
|
||||||
|
this.controller,
|
||||||
|
this.isSeekingNotifier, {
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SeekBar> createState() => _SeekBarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SeekBarState extends State<SeekBar> {
|
||||||
|
double _sliderValue = 0.0;
|
||||||
|
late final StreamSubscription<Duration> _positionStreamSubscription;
|
||||||
|
final _debouncer = Debouncer(
|
||||||
|
const Duration(milliseconds: 300),
|
||||||
|
executionInterval: const Duration(milliseconds: 300),
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_positionStreamSubscription =
|
||||||
|
widget.controller.player.stream.position.listen((event) {
|
||||||
|
if (widget.isSeekingNotifier.value) return;
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_sliderValue = event.inMilliseconds /
|
||||||
|
widget.controller.player.state.duration.inMilliseconds;
|
||||||
|
if (_sliderValue.isNaN) {
|
||||||
|
_sliderValue = 0.0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_positionStreamSubscription.cancel();
|
||||||
|
_debouncer.cancelDebounceTimer();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colorScheme = getEnteColorScheme(context);
|
||||||
|
return SliderTheme(
|
||||||
|
data: SliderTheme.of(context).copyWith(
|
||||||
|
trackHeight: 1.0,
|
||||||
|
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8.0),
|
||||||
|
overlayShape: const RoundSliderOverlayShape(overlayRadius: 14.0),
|
||||||
|
activeTrackColor: colorScheme.primary300,
|
||||||
|
inactiveTrackColor: fillMutedDark,
|
||||||
|
thumbColor: backgroundElevatedLight,
|
||||||
|
overlayColor: fillMutedDark,
|
||||||
|
),
|
||||||
|
child: Slider(
|
||||||
|
min: 0.0,
|
||||||
|
max: 1.0,
|
||||||
|
value: _sliderValue,
|
||||||
|
onChangeStart: (value) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
widget.isSeekingNotifier.value = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChanged: (value) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_sliderValue = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_debouncer.run(() async {
|
||||||
|
await widget.controller.player.seek(
|
||||||
|
Duration(
|
||||||
|
milliseconds: (value *
|
||||||
|
widget.controller.player.state.duration.inMilliseconds)
|
||||||
|
.round(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
divisions: 4500,
|
||||||
|
onChangeEnd: (value) async {
|
||||||
|
await widget.controller.player.seek(
|
||||||
|
Duration(
|
||||||
|
milliseconds: (value *
|
||||||
|
widget.controller.player.state.duration.inMilliseconds)
|
||||||
|
.round(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
widget.isSeekingNotifier.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
allowedInteraction: SliderInteraction.tapAndSlide,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -14,13 +14,12 @@ import "package:photos/models/file/extensions/file_props.dart";
|
|||||||
import "package:photos/models/file/file.dart";
|
import "package:photos/models/file/file.dart";
|
||||||
import "package:photos/services/files_service.dart";
|
import "package:photos/services/files_service.dart";
|
||||||
import "package:photos/theme/colors.dart";
|
import "package:photos/theme/colors.dart";
|
||||||
import "package:photos/theme/ente_theme.dart";
|
|
||||||
import "package:photos/ui/actions/file/file_actions.dart";
|
import "package:photos/ui/actions/file/file_actions.dart";
|
||||||
import "package:photos/ui/viewer/file/preview_status_widget.dart";
|
import "package:photos/ui/common/loading_widget.dart";
|
||||||
import "package:photos/utils/debouncer.dart";
|
import "package:photos/ui/viewer/file/video_widget_media_kit_common.dart"
|
||||||
|
as common;
|
||||||
import "package:photos/utils/dialog_util.dart";
|
import "package:photos/utils/dialog_util.dart";
|
||||||
import "package:photos/utils/file_util.dart";
|
import "package:photos/utils/file_util.dart";
|
||||||
import "package:photos/utils/seconds_to_duration.dart";
|
|
||||||
import "package:photos/utils/toast_util.dart";
|
import "package:photos/utils/toast_util.dart";
|
||||||
|
|
||||||
class VideoWidgetMediaKitNew extends StatefulWidget {
|
class VideoWidgetMediaKitNew extends StatefulWidget {
|
||||||
@ -28,14 +27,14 @@ class VideoWidgetMediaKitNew extends StatefulWidget {
|
|||||||
final String? tagPrefix;
|
final String? tagPrefix;
|
||||||
final Function(bool)? playbackCallback;
|
final Function(bool)? playbackCallback;
|
||||||
final bool isFromMemories;
|
final bool isFromMemories;
|
||||||
final void Function()? onStreamChange;
|
final void Function() onStreamChange;
|
||||||
|
|
||||||
const VideoWidgetMediaKitNew(
|
const VideoWidgetMediaKitNew(
|
||||||
this.file, {
|
this.file, {
|
||||||
this.tagPrefix,
|
this.tagPrefix,
|
||||||
this.playbackCallback,
|
this.playbackCallback,
|
||||||
this.isFromMemories = false,
|
this.isFromMemories = false,
|
||||||
this.onStreamChange,
|
required this.onStreamChange,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -138,44 +137,20 @@ class _VideoWidgetMediaKitNewState extends State<VideoWidgetMediaKitNew>
|
|||||||
},
|
},
|
||||||
child: Center(
|
child: Center(
|
||||||
child: controller != null
|
child: controller != null
|
||||||
? _VideoWidget(
|
? common.VideoWidget(
|
||||||
widget.file,
|
widget.file,
|
||||||
controller!,
|
controller!,
|
||||||
widget.playbackCallback,
|
widget.playbackCallback,
|
||||||
isFromMemories: widget.isFromMemories,
|
isFromMemories: widget.isFromMemories,
|
||||||
|
onStreamChange: widget.onStreamChange,
|
||||||
)
|
)
|
||||||
// : Stack(
|
: const Center(
|
||||||
// children: [
|
child: EnteLoadingWidget(
|
||||||
// _getThumbnail(),
|
size: 32,
|
||||||
// Container(
|
color: fillBaseDark,
|
||||||
// color: Colors.black12,
|
padding: 0,
|
||||||
// constraints: const BoxConstraints.expand(),
|
),
|
||||||
// ),
|
),
|
||||||
// Center(
|
|
||||||
// child: SizedBox.fromSize(
|
|
||||||
// size: const Size.square(20),
|
|
||||||
// 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),
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -230,452 +205,3 @@ class _VideoWidgetMediaKitNewState extends State<VideoWidgetMediaKitNew>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _VideoWidget extends StatefulWidget {
|
|
||||||
final EnteFile file;
|
|
||||||
final VideoController controller;
|
|
||||||
final Function(bool)? playbackCallback;
|
|
||||||
final bool isFromMemories;
|
|
||||||
final void Function()? onStreamChange;
|
|
||||||
|
|
||||||
const _VideoWidget(
|
|
||||||
this.file,
|
|
||||||
this.controller,
|
|
||||||
this.playbackCallback, {
|
|
||||||
required this.isFromMemories,
|
|
||||||
// ignore: unused_element
|
|
||||||
this.onStreamChange,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<_VideoWidget> createState() => __VideoWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class __VideoWidgetState extends State<_VideoWidget> {
|
|
||||||
final showControlsNotifier = ValueNotifier<bool>(true);
|
|
||||||
static const verticalMargin = 72.0;
|
|
||||||
final _hideControlsDebouncer = Debouncer(
|
|
||||||
const Duration(milliseconds: 2000),
|
|
||||||
);
|
|
||||||
final _isSeekingNotifier = ValueNotifier<bool>(false);
|
|
||||||
late final StreamSubscription<bool> _isPlayingStreamSubscription;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
_isPlayingStreamSubscription =
|
|
||||||
widget.controller.player.stream.playing.listen((isPlaying) {
|
|
||||||
if (isPlaying && !_isSeekingNotifier.value) {
|
|
||||||
_hideControlsDebouncer.run(() async {
|
|
||||||
showControlsNotifier.value = false;
|
|
||||||
widget.playbackCallback?.call(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_isSeekingNotifier.addListener(isSeekingListener);
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
showControlsNotifier.dispose();
|
|
||||||
_isPlayingStreamSubscription.cancel();
|
|
||||||
_hideControlsDebouncer.cancelDebounceTimer();
|
|
||||||
_isSeekingNotifier.removeListener(isSeekingListener);
|
|
||||||
_isSeekingNotifier.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void isSeekingListener() {
|
|
||||||
if (_isSeekingNotifier.value) {
|
|
||||||
_hideControlsDebouncer.cancelDebounceTimer();
|
|
||||||
} else {
|
|
||||||
if (widget.controller.player.state.playing) {
|
|
||||||
_hideControlsDebouncer.run(() async {
|
|
||||||
showControlsNotifier.value = false;
|
|
||||||
widget.playbackCallback?.call(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Video(
|
|
||||||
controller: widget.controller,
|
|
||||||
controls: (state) {
|
|
||||||
return ValueListenableBuilder(
|
|
||||||
valueListenable: showControlsNotifier,
|
|
||||||
builder: (context, value, _) {
|
|
||||||
return AnimatedOpacity(
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
opacity: value ? 1 : 0,
|
|
||||||
curve: Curves.easeInOutQuad,
|
|
||||||
child: Stack(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
children: [
|
|
||||||
GestureDetector(
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
onTap: () {
|
|
||||||
showControlsNotifier.value = !showControlsNotifier.value;
|
|
||||||
if (widget.playbackCallback != null) {
|
|
||||||
widget.playbackCallback!(
|
|
||||||
!showControlsNotifier.value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
constraints: const BoxConstraints.expand(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IgnorePointer(
|
|
||||||
ignoring: !value,
|
|
||||||
child: PlayPauseButtonMediaKit(widget.controller),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
bottom: verticalMargin,
|
|
||||||
right: 0,
|
|
||||||
left: 0,
|
|
||||||
child: IgnorePointer(
|
|
||||||
ignoring: !value,
|
|
||||||
child: SafeArea(
|
|
||||||
top: false,
|
|
||||||
left: false,
|
|
||||||
right: false,
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
bottom: widget.isFromMemories ? 32 : 0,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
widget.file.caption != null
|
|
||||||
? Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(
|
|
||||||
16,
|
|
||||||
12,
|
|
||||||
16,
|
|
||||||
8,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
widget.file.caption!,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
style: getEnteTextTheme(context)
|
|
||||||
.mini
|
|
||||||
.copyWith(
|
|
||||||
color: textBaseDark,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
ValueListenableBuilder(
|
|
||||||
valueListenable: showControlsNotifier,
|
|
||||||
builder: (context, value, _) {
|
|
||||||
return PreviewStatusWidget(
|
|
||||||
showControls: value,
|
|
||||||
file: widget.file,
|
|
||||||
onStreamChange: widget.onStreamChange,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_SeekBarAndDuration(
|
|
||||||
controller: widget.controller,
|
|
||||||
isSeekingNotifier: _isSeekingNotifier,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PlayPauseButtonMediaKit extends StatefulWidget {
|
|
||||||
final VideoController? controller;
|
|
||||||
const PlayPauseButtonMediaKit(
|
|
||||||
this.controller, {
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<PlayPauseButtonMediaKit> createState() => _PlayPauseButtonState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PlayPauseButtonState extends State<PlayPauseButtonMediaKit> {
|
|
||||||
bool _isPlaying = true;
|
|
||||||
late final StreamSubscription<bool>? isPlayingStreamSubscription;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
isPlayingStreamSubscription =
|
|
||||||
widget.controller?.player.stream.playing.listen((isPlaying) {
|
|
||||||
setState(() {
|
|
||||||
_isPlaying = isPlaying;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
isPlayingStreamSubscription?.cancel();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
onTap: () {
|
|
||||||
if (widget.controller?.player.state.playing ?? false) {
|
|
||||||
widget.controller?.player.pause();
|
|
||||||
} else {
|
|
||||||
widget.controller?.player.play();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
width: 54,
|
|
||||||
height: 54,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black.withOpacity(0.3),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(
|
|
||||||
color: strokeFaintDark,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 250),
|
|
||||||
transitionBuilder: (Widget child, Animation<double> animation) {
|
|
||||||
return ScaleTransition(scale: animation, child: child);
|
|
||||||
},
|
|
||||||
switchInCurve: Curves.easeInOutQuart,
|
|
||||||
switchOutCurve: Curves.easeInOutQuart,
|
|
||||||
child: _isPlaying
|
|
||||||
? const Icon(
|
|
||||||
Icons.pause,
|
|
||||||
size: 32,
|
|
||||||
key: ValueKey("pause"),
|
|
||||||
color: Colors.white,
|
|
||||||
)
|
|
||||||
: const Icon(
|
|
||||||
Icons.play_arrow,
|
|
||||||
size: 36,
|
|
||||||
key: ValueKey("play"),
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SeekBarAndDuration extends StatelessWidget {
|
|
||||||
final VideoController? controller;
|
|
||||||
final ValueNotifier<bool> isSeekingNotifier;
|
|
||||||
|
|
||||||
const _SeekBarAndDuration({
|
|
||||||
required this.controller,
|
|
||||||
required this.isSeekingNotifier,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 8,
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.fromLTRB(
|
|
||||||
16,
|
|
||||||
4,
|
|
||||||
16,
|
|
||||||
4,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black.withOpacity(0.3),
|
|
||||||
borderRadius: const BorderRadius.all(
|
|
||||||
Radius.circular(8),
|
|
||||||
),
|
|
||||||
border: Border.all(
|
|
||||||
color: strokeFaintDark,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
StreamBuilder(
|
|
||||||
stream: controller?.player.stream.position,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.data == null) {
|
|
||||||
return Text(
|
|
||||||
"0:00",
|
|
||||||
style: getEnteTextTheme(
|
|
||||||
context,
|
|
||||||
).mini.copyWith(
|
|
||||||
color: textBaseDark,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Text(
|
|
||||||
secondsToDuration(snapshot.data!.inSeconds),
|
|
||||||
style: getEnteTextTheme(
|
|
||||||
context,
|
|
||||||
).mini.copyWith(
|
|
||||||
color: textBaseDark,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _SeekBar(
|
|
||||||
controller!,
|
|
||||||
isSeekingNotifier,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
_secondsToDuration(
|
|
||||||
controller!.player.state.duration.inSeconds,
|
|
||||||
),
|
|
||||||
style: getEnteTextTheme(
|
|
||||||
context,
|
|
||||||
).mini.copyWith(
|
|
||||||
color: textBaseDark,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the duration in the format "h:mm:ss" or "m:ss".
|
|
||||||
String _secondsToDuration(int totalSeconds) {
|
|
||||||
final hours = totalSeconds ~/ 3600;
|
|
||||||
final minutes = (totalSeconds % 3600) ~/ 60;
|
|
||||||
final seconds = totalSeconds % 60;
|
|
||||||
|
|
||||||
if (hours > 0) {
|
|
||||||
return '${hours.toString().padLeft(1, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
|
||||||
} else {
|
|
||||||
return '${minutes.toString().padLeft(1, '0')}:${seconds.toString().padLeft(2, '0')}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SeekBar extends StatefulWidget {
|
|
||||||
final VideoController controller;
|
|
||||||
final ValueNotifier<bool> isSeekingNotifier;
|
|
||||||
const _SeekBar(
|
|
||||||
this.controller,
|
|
||||||
this.isSeekingNotifier,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<_SeekBar> createState() => _SeekBarState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SeekBarState extends State<_SeekBar> {
|
|
||||||
double _sliderValue = 0.0;
|
|
||||||
late final StreamSubscription<Duration> _positionStreamSubscription;
|
|
||||||
final _debouncer = Debouncer(
|
|
||||||
const Duration(milliseconds: 300),
|
|
||||||
executionInterval: const Duration(milliseconds: 300),
|
|
||||||
);
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_positionStreamSubscription =
|
|
||||||
widget.controller.player.stream.position.listen((event) {
|
|
||||||
if (widget.isSeekingNotifier.value) return;
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_sliderValue = event.inMilliseconds /
|
|
||||||
widget.controller.player.state.duration.inMilliseconds;
|
|
||||||
if (_sliderValue.isNaN) {
|
|
||||||
_sliderValue = 0.0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_positionStreamSubscription.cancel();
|
|
||||||
_debouncer.cancelDebounceTimer();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final colorScheme = getEnteColorScheme(context);
|
|
||||||
return SliderTheme(
|
|
||||||
data: SliderTheme.of(context).copyWith(
|
|
||||||
trackHeight: 1.0,
|
|
||||||
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8.0),
|
|
||||||
overlayShape: const RoundSliderOverlayShape(overlayRadius: 14.0),
|
|
||||||
activeTrackColor: colorScheme.primary300,
|
|
||||||
inactiveTrackColor: fillMutedDark,
|
|
||||||
thumbColor: backgroundElevatedLight,
|
|
||||||
overlayColor: fillMutedDark,
|
|
||||||
),
|
|
||||||
child: Slider(
|
|
||||||
min: 0.0,
|
|
||||||
max: 1.0,
|
|
||||||
value: _sliderValue,
|
|
||||||
onChangeStart: (value) {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
widget.isSeekingNotifier.value = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onChanged: (value) {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_sliderValue = value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_debouncer.run(() async {
|
|
||||||
await widget.controller.player.seek(
|
|
||||||
Duration(
|
|
||||||
milliseconds: (value *
|
|
||||||
widget.controller.player.state.duration.inMilliseconds)
|
|
||||||
.round(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
divisions: 4500,
|
|
||||||
onChangeEnd: (value) async {
|
|
||||||
await widget.controller.player.seek(
|
|
||||||
Duration(
|
|
||||||
milliseconds: (value *
|
|
||||||
widget.controller.player.state.duration.inMilliseconds)
|
|
||||||
.round(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
widget.isSeekingNotifier.value = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
allowedInteraction: SliderInteraction.tapAndSlide,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
172
mobile/lib/ui/viewer/file/video_widget_media_kit_preview.dart
Normal file
172
mobile/lib/ui/viewer/file/video_widget_media_kit_preview.dart
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
import "dart:async";
|
||||||
|
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:fluttertoast/fluttertoast.dart";
|
||||||
|
import "package:logging/logging.dart";
|
||||||
|
import "package:media_kit/media_kit.dart";
|
||||||
|
import "package:media_kit_video/media_kit_video.dart";
|
||||||
|
import "package:photos/core/constants.dart";
|
||||||
|
import "package:photos/core/event_bus.dart";
|
||||||
|
import "package:photos/events/guest_view_event.dart";
|
||||||
|
import "package:photos/events/pause_video_event.dart";
|
||||||
|
import "package:photos/models/file/file.dart";
|
||||||
|
import "package:photos/service_locator.dart";
|
||||||
|
import "package:photos/services/filedata/filedata_service.dart";
|
||||||
|
import "package:photos/services/preview_video_store.dart";
|
||||||
|
import "package:photos/theme/colors.dart";
|
||||||
|
import "package:photos/ui/actions/file/file_actions.dart";
|
||||||
|
import "package:photos/ui/common/loading_widget.dart";
|
||||||
|
import "package:photos/ui/viewer/file/video_widget_media_kit_common.dart"
|
||||||
|
as common;
|
||||||
|
import "package:photos/utils/data_util.dart";
|
||||||
|
import "package:photos/utils/file_util.dart";
|
||||||
|
import "package:photos/utils/toast_util.dart";
|
||||||
|
|
||||||
|
class VideoWidgetMediaKitPreview extends StatefulWidget {
|
||||||
|
final EnteFile file;
|
||||||
|
final String? tagPrefix;
|
||||||
|
final Function(bool)? playbackCallback;
|
||||||
|
final bool isFromMemories;
|
||||||
|
final void Function() onStreamChange;
|
||||||
|
|
||||||
|
const VideoWidgetMediaKitPreview(
|
||||||
|
this.file, {
|
||||||
|
this.tagPrefix,
|
||||||
|
this.playbackCallback,
|
||||||
|
this.isFromMemories = false,
|
||||||
|
required this.onStreamChange,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<VideoWidgetMediaKitPreview> createState() =>
|
||||||
|
_VideoWidgetMediaKitPreviewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VideoWidgetMediaKitPreviewState extends State<VideoWidgetMediaKitPreview>
|
||||||
|
with WidgetsBindingObserver {
|
||||||
|
final Logger _logger = Logger("VideoWidgetMediaKitNew");
|
||||||
|
late final player = Player();
|
||||||
|
VideoController? controller;
|
||||||
|
final _progressNotifier = ValueNotifier<double?>(null);
|
||||||
|
bool _isAppInFG = true;
|
||||||
|
late StreamSubscription<PauseVideoEvent> pauseVideoSubscription;
|
||||||
|
bool isGuestView = false;
|
||||||
|
late final StreamSubscription<GuestViewEvent> _guestViewEventSubscription;
|
||||||
|
bool _isGuestView = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_logger.info(
|
||||||
|
'initState for ${widget.file.generatedID} with tag ${widget.file.tag} and name ${widget.file.displayName}',
|
||||||
|
);
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
_checkForPreview();
|
||||||
|
|
||||||
|
pauseVideoSubscription = Bus.instance.on<PauseVideoEvent>().listen((event) {
|
||||||
|
player.pause();
|
||||||
|
});
|
||||||
|
_guestViewEventSubscription =
|
||||||
|
Bus.instance.on<GuestViewEvent>().listen((event) {
|
||||||
|
setState(() {
|
||||||
|
_isGuestView = event.isGuestView;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _checkForPreview() async {
|
||||||
|
widget.playbackCallback?.call(false);
|
||||||
|
final data = await PreviewVideoStore.instance
|
||||||
|
.getPlaylist(widget.file)
|
||||||
|
.onError((error, stackTrace) {
|
||||||
|
if (!mounted) return;
|
||||||
|
_logger.warning("Failed to download preview video", error, stackTrace);
|
||||||
|
Fluttertoast.showToast(msg: "Failed to download preview!");
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
if (!mounted) return;
|
||||||
|
if (data != null) {
|
||||||
|
if (flagService.internalUser) {
|
||||||
|
final d =
|
||||||
|
FileDataService.instance.previewIds?[widget.file.uploadedFileID!];
|
||||||
|
if (d != null && widget.file.fileSize != null) {
|
||||||
|
// show toast with human readable size
|
||||||
|
final size = formatBytes(widget.file.fileSize!);
|
||||||
|
showToast(
|
||||||
|
context,
|
||||||
|
"[i] Preview OG Size ($size), previewSize: ${formatBytes(d.objectSize)}",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
showShortToast(context, "Playing preview");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_setVideoController(data.preview.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
if (state == AppLifecycleState.resumed) {
|
||||||
|
_isAppInFG = true;
|
||||||
|
} else {
|
||||||
|
_isAppInFG = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_guestViewEventSubscription.cancel();
|
||||||
|
pauseVideoSubscription.cancel();
|
||||||
|
removeCallBack(widget.file);
|
||||||
|
_progressNotifier.dispose();
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
player.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onVerticalDragUpdate: _isGuestView
|
||||||
|
? null
|
||||||
|
: (d) => {
|
||||||
|
if (d.delta.dy > dragSensitivity)
|
||||||
|
{
|
||||||
|
Navigator.of(context).pop(),
|
||||||
|
}
|
||||||
|
else if (d.delta.dy < (dragSensitivity * -1))
|
||||||
|
{
|
||||||
|
showDetailsSheet(context, widget.file),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: controller != null
|
||||||
|
? common.VideoWidget(
|
||||||
|
widget.file,
|
||||||
|
controller!,
|
||||||
|
widget.playbackCallback,
|
||||||
|
isFromMemories: widget.isFromMemories,
|
||||||
|
onStreamChange: widget.onStreamChange,
|
||||||
|
)
|
||||||
|
: const Center(
|
||||||
|
child: EnteLoadingWidget(
|
||||||
|
size: 32,
|
||||||
|
color: fillBaseDark,
|
||||||
|
padding: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setVideoController(String url) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
player.setPlaylistMode(PlaylistMode.single);
|
||||||
|
controller = VideoController(player);
|
||||||
|
player.open(Media(url), play: _isAppInFG);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1587,7 +1587,7 @@ packages:
|
|||||||
source: git
|
source: git
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
media_kit:
|
media_kit:
|
||||||
dependency: "direct overridden"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: media_kit
|
path: media_kit
|
||||||
ref: HEAD
|
ref: HEAD
|
||||||
@ -1604,7 +1604,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.6"
|
version: "1.3.6"
|
||||||
media_kit_libs_ios_video:
|
media_kit_libs_ios_video:
|
||||||
dependency: "direct overridden"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "libs/ios/media_kit_libs_ios_video"
|
path: "libs/ios/media_kit_libs_ios_video"
|
||||||
ref: HEAD
|
ref: HEAD
|
||||||
@ -1629,7 +1629,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.4"
|
version: "1.1.4"
|
||||||
media_kit_libs_video:
|
media_kit_libs_video:
|
||||||
dependency: "direct overridden"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "libs/universal/media_kit_libs_video"
|
path: "libs/universal/media_kit_libs_video"
|
||||||
ref: HEAD
|
ref: HEAD
|
||||||
@ -1646,7 +1646,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.10"
|
version: "1.0.10"
|
||||||
media_kit_video:
|
media_kit_video:
|
||||||
dependency: "direct overridden"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: media_kit_video
|
path: media_kit_video
|
||||||
ref: HEAD
|
ref: HEAD
|
||||||
@ -2802,7 +2802,7 @@ packages:
|
|||||||
source: git
|
source: git
|
||||||
version: "3.0.0"
|
version: "3.0.0"
|
||||||
video_player:
|
video_player:
|
||||||
dependency: "direct overridden"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "packages/video_player/video_player"
|
path: "packages/video_player/video_player"
|
||||||
ref: android_video_roation_fix
|
ref: android_video_roation_fix
|
||||||
@ -2826,15 +2826,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.6.2"
|
version: "2.6.2"
|
||||||
video_player_media_kit:
|
|
||||||
dependency: "direct overridden"
|
|
||||||
description:
|
|
||||||
path: video_player_media_kit
|
|
||||||
ref: HEAD
|
|
||||||
resolved-ref: "3c4ff28c43d20e68f8d587956b2f525292c25a80"
|
|
||||||
url: "https://github.com/media-kit/media-kit"
|
|
||||||
source: git
|
|
||||||
version: "1.0.5"
|
|
||||||
video_player_platform_interface:
|
video_player_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -120,6 +120,22 @@ dependencies:
|
|||||||
git:
|
git:
|
||||||
url: "https://github.com/ente-io/media_extension.git"
|
url: "https://github.com/ente-io/media_extension.git"
|
||||||
ref: deeplink_fixes
|
ref: deeplink_fixes
|
||||||
|
media_kit:
|
||||||
|
git:
|
||||||
|
url: https://github.com/media-kit/media-kit
|
||||||
|
path: media_kit
|
||||||
|
media_kit_libs_ios_video:
|
||||||
|
git:
|
||||||
|
url: https://github.com/media-kit/media-kit
|
||||||
|
path: libs/ios/media_kit_libs_ios_video
|
||||||
|
media_kit_libs_video:
|
||||||
|
git:
|
||||||
|
url: https://github.com/media-kit/media-kit
|
||||||
|
path: libs/universal/media_kit_libs_video
|
||||||
|
media_kit_video:
|
||||||
|
git:
|
||||||
|
url: https://github.com/media-kit/media-kit
|
||||||
|
path: media_kit_video
|
||||||
ml_linalg: ^13.11.31
|
ml_linalg: ^13.11.31
|
||||||
modal_bottom_sheet: ^3.0.0-pre
|
modal_bottom_sheet: ^3.0.0-pre
|
||||||
motion_photos:
|
motion_photos:
|
||||||
@ -186,6 +202,11 @@ dependencies:
|
|||||||
video_editor:
|
video_editor:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/prateekmedia/video_editor.git
|
url: https://github.com/prateekmedia/video_editor.git
|
||||||
|
video_player:
|
||||||
|
git:
|
||||||
|
url: https://github.com/ente-io/packages.git
|
||||||
|
ref: android_video_roation_fix
|
||||||
|
path: packages/video_player/video_player/
|
||||||
video_thumbnail: ^0.5.3
|
video_thumbnail: ^0.5.3
|
||||||
visibility_detector: ^0.3.3
|
visibility_detector: ^0.3.3
|
||||||
wakelock_plus: ^1.1.1
|
wakelock_plus: ^1.1.1
|
||||||
@ -200,32 +221,28 @@ dependency_overrides:
|
|||||||
# Newer flutter packages depends on ffi > 2.0.0 while flutter_sodium depends on ffi < 2.0.0
|
# Newer flutter packages depends on ffi > 2.0.0 while flutter_sodium depends on ffi < 2.0.0
|
||||||
ffi: 2.1.0
|
ffi: 2.1.0
|
||||||
intl: 0.18.1
|
intl: 0.18.1
|
||||||
|
js: ^0.6.7
|
||||||
media_kit:
|
media_kit:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/media-kit/media-kit
|
url: https://github.com/media-kit/media-kit
|
||||||
path: media_kit
|
path: media_kit
|
||||||
media_kit_video:
|
|
||||||
git:
|
|
||||||
url: https://github.com/media-kit/media-kit
|
|
||||||
path: media_kit_video
|
|
||||||
media_kit_libs_video:
|
|
||||||
git:
|
|
||||||
url: https://github.com/media-kit/media-kit
|
|
||||||
path: libs/universal/media_kit_libs_video
|
|
||||||
media_kit_libs_ios_video:
|
media_kit_libs_ios_video:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/media-kit/media-kit
|
url: https://github.com/media-kit/media-kit
|
||||||
path: libs/ios/media_kit_libs_ios_video
|
path: libs/ios/media_kit_libs_ios_video
|
||||||
|
media_kit_libs_video:
|
||||||
|
git:
|
||||||
|
url: https://github.com/media-kit/media-kit
|
||||||
|
path: libs/universal/media_kit_libs_video
|
||||||
|
media_kit_video:
|
||||||
|
git:
|
||||||
|
url: https://github.com/media-kit/media-kit
|
||||||
|
path: media_kit_video
|
||||||
video_player:
|
video_player:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/ente-io/packages.git
|
url: https://github.com/ente-io/packages.git
|
||||||
ref: android_video_roation_fix
|
ref: android_video_roation_fix
|
||||||
path: packages/video_player/video_player/
|
path: packages/video_player/video_player/
|
||||||
video_player_media_kit:
|
|
||||||
git:
|
|
||||||
url: https://github.com/media-kit/media-kit
|
|
||||||
path: video_player_media_kit
|
|
||||||
js: ^0.6.7
|
|
||||||
watcher: ^1.1.0
|
watcher: ^1.1.0
|
||||||
win32: ^5.5.4
|
win32: ^5.5.4
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user