mirror of
https://github.com/ente-io/ente.git
synced 2025-08-08 07:28:26 +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/lock_screen_settings.dart";
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import "package:video_player_media_kit/video_player_media_kit.dart";
|
||||
|
||||
final _logger = Logger("main");
|
||||
|
||||
@ -238,10 +237,6 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
|
||||
ServiceLocator.instance
|
||||
.init(preferences, NetworkClient.instance.enteDio, packageInfo);
|
||||
|
||||
if (!isBackground) {
|
||||
VideoPlayerMediaKit.ensureInitialized(iOS: true, android: true);
|
||||
}
|
||||
|
||||
_logger.info("UserService init $tlog");
|
||||
await UserService.instance.init();
|
||||
_logger.info("UserService init done $tlog");
|
||||
|
@ -1,266 +1,266 @@
|
||||
import 'dart:async';
|
||||
import "dart:io";
|
||||
// import 'dart:async';
|
||||
// import "dart:io";
|
||||
|
||||
import 'package:chewie/chewie.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:fluttertoast/fluttertoast.dart";
|
||||
import "package:logging/logging.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/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/ui/actions/file/file_actions.dart";
|
||||
import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
|
||||
import "package:photos/ui/viewer/file/video_control.dart";
|
||||
import "package:photos/utils/data_util.dart";
|
||||
// import 'package:photos/ui/viewer/file/video_controls.dart';
|
||||
import "package:photos/utils/dialog_util.dart";
|
||||
import 'package:photos/utils/file_util.dart';
|
||||
import 'package:photos/utils/toast_util.dart';
|
||||
import "package:photos/utils/wakelock_util.dart";
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
// import 'package:chewie/chewie.dart';
|
||||
// import 'package:flutter/cupertino.dart';
|
||||
// import 'package:flutter/material.dart';
|
||||
// import "package:fluttertoast/fluttertoast.dart";
|
||||
// import "package:logging/logging.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/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/ui/actions/file/file_actions.dart";
|
||||
// import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
|
||||
// import "package:photos/ui/viewer/file/video_control.dart";
|
||||
// import "package:photos/utils/data_util.dart";
|
||||
// // import 'package:photos/ui/viewer/file/video_controls.dart';
|
||||
// import "package:photos/utils/dialog_util.dart";
|
||||
// import 'package:photos/utils/file_util.dart';
|
||||
// import 'package:photos/utils/toast_util.dart';
|
||||
// import "package:photos/utils/wakelock_util.dart";
|
||||
// import 'package:video_player/video_player.dart';
|
||||
// import 'package:visibility_detector/visibility_detector.dart';
|
||||
|
||||
class PreviewVideoWidget extends StatefulWidget {
|
||||
final EnteFile file;
|
||||
final bool? autoPlay;
|
||||
final String? tagPrefix;
|
||||
final Function(bool)? playbackCallback;
|
||||
final void Function()? onStreamChange;
|
||||
// class PreviewVideoWidget extends StatefulWidget {
|
||||
// final EnteFile file;
|
||||
// final bool? autoPlay;
|
||||
// final String? tagPrefix;
|
||||
// final Function(bool)? playbackCallback;
|
||||
// final void Function()? onStreamChange;
|
||||
|
||||
const PreviewVideoWidget(
|
||||
this.file, {
|
||||
this.autoPlay = true,
|
||||
this.tagPrefix,
|
||||
this.playbackCallback,
|
||||
this.onStreamChange,
|
||||
super.key,
|
||||
});
|
||||
// const PreviewVideoWidget(
|
||||
// this.file, {
|
||||
// this.autoPlay = true,
|
||||
// this.tagPrefix,
|
||||
// this.playbackCallback,
|
||||
// this.onStreamChange,
|
||||
// super.key,
|
||||
// });
|
||||
|
||||
@override
|
||||
State<PreviewVideoWidget> createState() => _PreviewVideoWidgetState();
|
||||
}
|
||||
// @override
|
||||
// State<PreviewVideoWidget> createState() => _PreviewVideoWidgetState();
|
||||
// }
|
||||
|
||||
class _PreviewVideoWidgetState extends State<PreviewVideoWidget> {
|
||||
final _logger = Logger("PreviewVideoWidget");
|
||||
VideoPlayerController? _videoPlayerController;
|
||||
ChewieController? _chewieController;
|
||||
final _progressNotifier = ValueNotifier<double?>(null);
|
||||
bool _isPlaying = false;
|
||||
final EnteWakeLock _wakeLock = EnteWakeLock();
|
||||
bool _isFileSwipeLocked = false;
|
||||
late final StreamSubscription<GuestViewEvent> _fileSwipeLockEventSubscription;
|
||||
File? previewFile;
|
||||
// class _PreviewVideoWidgetState extends State<PreviewVideoWidget> {
|
||||
// final _logger = Logger("PreviewVideoWidget");
|
||||
// VideoPlayerController? _videoPlayerController;
|
||||
// ChewieController? _chewieController;
|
||||
// final _progressNotifier = ValueNotifier<double?>(null);
|
||||
// bool _isPlaying = false;
|
||||
// final EnteWakeLock _wakeLock = EnteWakeLock();
|
||||
// bool _isFileSwipeLocked = false;
|
||||
// late final StreamSubscription<GuestViewEvent> _fileSwipeLockEventSubscription;
|
||||
// File? previewFile;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
|
||||
_checkForPreview();
|
||||
_fileSwipeLockEventSubscription =
|
||||
Bus.instance.on<GuestViewEvent>().listen((event) {
|
||||
setState(() {
|
||||
_isFileSwipeLocked = event.swipeLocked;
|
||||
});
|
||||
});
|
||||
}
|
||||
// _checkForPreview();
|
||||
// _fileSwipeLockEventSubscription =
|
||||
// Bus.instance.on<GuestViewEvent>().listen((event) {
|
||||
// setState(() {
|
||||
// _isFileSwipeLocked = event.swipeLocked;
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_fileSwipeLockEventSubscription.cancel();
|
||||
removeCallBack(widget.file);
|
||||
_videoPlayerController?.dispose();
|
||||
_chewieController?.dispose();
|
||||
_progressNotifier.dispose();
|
||||
_wakeLock.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
// @override
|
||||
// void dispose() {
|
||||
// _fileSwipeLockEventSubscription.cancel();
|
||||
// removeCallBack(widget.file);
|
||||
// _videoPlayerController?.dispose();
|
||||
// _chewieController?.dispose();
|
||||
// _progressNotifier.dispose();
|
||||
// _wakeLock.dispose();
|
||||
// super.dispose();
|
||||
// }
|
||||
|
||||
Future<void> _checkForPreview() async {
|
||||
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,
|
||||
"Preview OG Size ($size), previewSize: ${formatBytes(d.objectSize)}",
|
||||
);
|
||||
} else {
|
||||
showShortToast(context, "Playing preview");
|
||||
}
|
||||
}
|
||||
previewFile = data.preview;
|
||||
_setVideoPlayerController();
|
||||
}
|
||||
}
|
||||
// Future<void> _checkForPreview() async {
|
||||
// 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,
|
||||
// "Preview OG Size ($size), previewSize: ${formatBytes(d.objectSize)}",
|
||||
// );
|
||||
// } else {
|
||||
// showShortToast(context, "Playing preview");
|
||||
// }
|
||||
// }
|
||||
// previewFile = data.preview;
|
||||
// _setVideoPlayerController();
|
||||
// }
|
||||
// }
|
||||
|
||||
void _setVideoPlayerController() {
|
||||
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;
|
||||
}
|
||||
VideoPlayerController videoPlayerController;
|
||||
videoPlayerController = VideoPlayerController.file(previewFile!);
|
||||
// void _setVideoPlayerController() {
|
||||
// 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;
|
||||
// }
|
||||
// VideoPlayerController videoPlayerController;
|
||||
// videoPlayerController = VideoPlayerController.file(previewFile!);
|
||||
|
||||
debugPrint("videoPlayerController: $videoPlayerController");
|
||||
_videoPlayerController = videoPlayerController
|
||||
..initialize().whenComplete(() {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}).onError(
|
||||
(error, stackTrace) {
|
||||
if (mounted && flagService.internalUser) {
|
||||
if (error is Exception) {
|
||||
showErrorDialogForException(
|
||||
context: context,
|
||||
exception: error,
|
||||
message: "Failed to play video\n ${error.toString()}",
|
||||
);
|
||||
} else {
|
||||
showToast(context, "Failed to play video");
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
// debugPrint("videoPlayerController: $videoPlayerController");
|
||||
// _videoPlayerController = videoPlayerController
|
||||
// ..initialize().whenComplete(() {
|
||||
// if (mounted) {
|
||||
// setState(() {});
|
||||
// }
|
||||
// }).onError(
|
||||
// (error, stackTrace) {
|
||||
// if (mounted && flagService.internalUser) {
|
||||
// if (error is Exception) {
|
||||
// showErrorDialogForException(
|
||||
// context: context,
|
||||
// exception: error,
|
||||
// message: "Failed to play video\n ${error.toString()}",
|
||||
// );
|
||||
// } else {
|
||||
// showToast(context, "Failed to play video");
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final content = _videoPlayerController != null &&
|
||||
_videoPlayerController!.value.isInitialized
|
||||
? _getVideoPlayer()
|
||||
: _getLoadingWidget();
|
||||
final contentWithDetector = GestureDetector(
|
||||
onVerticalDragUpdate: _isFileSwipeLocked
|
||||
? null
|
||||
: (d) => {
|
||||
if (d.delta.dy > dragSensitivity)
|
||||
{
|
||||
Navigator.of(context).pop(),
|
||||
}
|
||||
else if (d.delta.dy < (dragSensitivity * -1))
|
||||
{
|
||||
showDetailsSheet(context, widget.file),
|
||||
},
|
||||
},
|
||||
child: content,
|
||||
);
|
||||
return VisibilityDetector(
|
||||
key: Key(widget.file.tag),
|
||||
onVisibilityChanged: (info) {
|
||||
if (info.visibleFraction < 1) {
|
||||
if (mounted && _chewieController != null) {
|
||||
_chewieController!.pause();
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Hero(
|
||||
tag: widget.tagPrefix! + widget.file.tag,
|
||||
child: contentWithDetector,
|
||||
),
|
||||
);
|
||||
}
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// final content = _videoPlayerController != null &&
|
||||
// _videoPlayerController!.value.isInitialized
|
||||
// ? _getVideoPlayer()
|
||||
// : _getLoadingWidget();
|
||||
// final contentWithDetector = GestureDetector(
|
||||
// onVerticalDragUpdate: _isFileSwipeLocked
|
||||
// ? null
|
||||
// : (d) => {
|
||||
// if (d.delta.dy > dragSensitivity)
|
||||
// {
|
||||
// Navigator.of(context).pop(),
|
||||
// }
|
||||
// else if (d.delta.dy < (dragSensitivity * -1))
|
||||
// {
|
||||
// showDetailsSheet(context, widget.file),
|
||||
// },
|
||||
// },
|
||||
// child: content,
|
||||
// );
|
||||
// return VisibilityDetector(
|
||||
// key: Key(widget.file.tag),
|
||||
// onVisibilityChanged: (info) {
|
||||
// if (info.visibleFraction < 1) {
|
||||
// if (mounted && _chewieController != null) {
|
||||
// _chewieController!.pause();
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// child: Hero(
|
||||
// tag: widget.tagPrefix! + widget.file.tag,
|
||||
// child: contentWithDetector,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
Widget _getLoadingWidget() {
|
||||
return Stack(
|
||||
children: [
|
||||
_getThumbnail(),
|
||||
Container(
|
||||
color: Colors.black12,
|
||||
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),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
// Widget _getLoadingWidget() {
|
||||
// return Stack(
|
||||
// children: [
|
||||
// _getThumbnail(),
|
||||
// Container(
|
||||
// color: Colors.black12,
|
||||
// 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),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
|
||||
Widget _getThumbnail() {
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
constraints: const BoxConstraints.expand(),
|
||||
child: ThumbnailWidget(
|
||||
widget.file,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
);
|
||||
}
|
||||
// Widget _getThumbnail() {
|
||||
// return Container(
|
||||
// color: Colors.black,
|
||||
// constraints: const BoxConstraints.expand(),
|
||||
// child: ThumbnailWidget(
|
||||
// widget.file,
|
||||
// fit: BoxFit.contain,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
Future<void> _keepScreenAliveOnPlaying(bool isPlaying) async {
|
||||
if (isPlaying) {
|
||||
_wakeLock.enable();
|
||||
}
|
||||
if (!isPlaying) {
|
||||
_wakeLock.disable();
|
||||
}
|
||||
}
|
||||
// Future<void> _keepScreenAliveOnPlaying(bool isPlaying) async {
|
||||
// if (isPlaying) {
|
||||
// _wakeLock.enable();
|
||||
// }
|
||||
// if (!isPlaying) {
|
||||
// _wakeLock.disable();
|
||||
// }
|
||||
// }
|
||||
|
||||
Widget _getVideoPlayer() {
|
||||
_videoPlayerController!.addListener(() {
|
||||
if (_isPlaying != _videoPlayerController!.value.isPlaying) {
|
||||
_isPlaying = _videoPlayerController!.value.isPlaying;
|
||||
if (widget.playbackCallback != null) {
|
||||
widget.playbackCallback!(_isPlaying);
|
||||
}
|
||||
unawaited(_keepScreenAliveOnPlaying(_isPlaying));
|
||||
}
|
||||
});
|
||||
_chewieController = ChewieController(
|
||||
progressIndicatorDelay: const Duration(milliseconds: 200),
|
||||
videoPlayerController: _videoPlayerController!,
|
||||
aspectRatio: _videoPlayerController!.value.aspectRatio,
|
||||
autoPlay: widget.autoPlay!,
|
||||
autoInitialize: true,
|
||||
looping: true,
|
||||
allowMuting: true,
|
||||
allowFullScreen: false,
|
||||
customControls: VideoControls(
|
||||
file: widget.file,
|
||||
onStreamChange: widget.onStreamChange,
|
||||
playbackCallback: widget.playbackCallback,
|
||||
),
|
||||
);
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
child: Chewie(controller: _chewieController!),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Widget _getVideoPlayer() {
|
||||
// _videoPlayerController!.addListener(() {
|
||||
// if (_isPlaying != _videoPlayerController!.value.isPlaying) {
|
||||
// _isPlaying = _videoPlayerController!.value.isPlaying;
|
||||
// if (widget.playbackCallback != null) {
|
||||
// widget.playbackCallback!(_isPlaying);
|
||||
// }
|
||||
// unawaited(_keepScreenAliveOnPlaying(_isPlaying));
|
||||
// }
|
||||
// });
|
||||
// _chewieController = ChewieController(
|
||||
// progressIndicatorDelay: const Duration(milliseconds: 200),
|
||||
// videoPlayerController: _videoPlayerController!,
|
||||
// aspectRatio: _videoPlayerController!.value.aspectRatio,
|
||||
// autoPlay: widget.autoPlay!,
|
||||
// autoInitialize: true,
|
||||
// looping: true,
|
||||
// allowMuting: true,
|
||||
// allowFullScreen: false,
|
||||
// customControls: VideoControls(
|
||||
// file: widget.file,
|
||||
// onStreamChange: widget.onStreamChange,
|
||||
// playbackCallback: widget.playbackCallback,
|
||||
// ),
|
||||
// );
|
||||
// return Container(
|
||||
// color: Colors.black,
|
||||
// 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/src/notifiers/index.dart";
|
||||
import 'package:flutter/material.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/common/loading_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/utils/seconds_to_duration.dart";
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
// import "package:chewie/chewie.dart";
|
||||
// import "package:chewie/src/notifiers/index.dart";
|
||||
// import 'package:flutter/material.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/common/loading_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/utils/seconds_to_duration.dart";
|
||||
// import 'package:provider/provider.dart';
|
||||
// import 'package:video_player/video_player.dart';
|
||||
|
||||
class VideoControls extends StatefulWidget {
|
||||
const VideoControls({
|
||||
super.key,
|
||||
required this.file,
|
||||
required this.onStreamChange,
|
||||
required this.playbackCallback,
|
||||
});
|
||||
final EnteFile file;
|
||||
final void Function()? onStreamChange;
|
||||
final void Function(bool)? playbackCallback;
|
||||
// class VideoControls extends StatefulWidget {
|
||||
// const VideoControls({
|
||||
// super.key,
|
||||
// required this.file,
|
||||
// required this.onStreamChange,
|
||||
// required this.playbackCallback,
|
||||
// });
|
||||
// final EnteFile file;
|
||||
// final void Function()? onStreamChange;
|
||||
// final void Function(bool)? playbackCallback;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _VideoControlsState();
|
||||
}
|
||||
}
|
||||
// @override
|
||||
// State<StatefulWidget> createState() {
|
||||
// return _VideoControlsState();
|
||||
// }
|
||||
// }
|
||||
|
||||
class _VideoControlsState extends State<VideoControls>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late PlayerNotifier notifier;
|
||||
late VideoPlayerValue _latestValue;
|
||||
Timer? _hideTimer;
|
||||
Timer? _initTimer;
|
||||
Timer? _showAfterExpandCollapseTimer;
|
||||
bool _dragging = false;
|
||||
bool _displayTapped = false;
|
||||
Timer? _bufferingDisplayTimer;
|
||||
bool _displayBufferingIndicator = false;
|
||||
// class _VideoControlsState extends State<VideoControls>
|
||||
// with SingleTickerProviderStateMixin {
|
||||
// late PlayerNotifier notifier;
|
||||
// late VideoPlayerValue _latestValue;
|
||||
// Timer? _hideTimer;
|
||||
// Timer? _initTimer;
|
||||
// Timer? _showAfterExpandCollapseTimer;
|
||||
// bool _dragging = false;
|
||||
// bool _displayTapped = false;
|
||||
// Timer? _bufferingDisplayTimer;
|
||||
// bool _displayBufferingIndicator = false;
|
||||
|
||||
final barHeight = 48.0 * 1.5;
|
||||
final marginSize = 5.0;
|
||||
// final barHeight = 48.0 * 1.5;
|
||||
// final marginSize = 5.0;
|
||||
|
||||
late VideoPlayerController controller;
|
||||
ChewieController? _chewieController;
|
||||
// late VideoPlayerController controller;
|
||||
// ChewieController? _chewieController;
|
||||
|
||||
// We know that _chewieController is set in didChangeDependencies
|
||||
ChewieController get chewieController => _chewieController!;
|
||||
// // We know that _chewieController is set in didChangeDependencies
|
||||
// ChewieController get chewieController => _chewieController!;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
notifier = Provider.of<PlayerNotifier>(context, listen: false);
|
||||
}
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// notifier = Provider.of<PlayerNotifier>(context, listen: false);
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_latestValue.hasError) {
|
||||
return chewieController.errorBuilder?.call(
|
||||
context,
|
||||
chewieController.videoPlayerController.value.errorDescription!,
|
||||
) ??
|
||||
Center(
|
||||
child: Icon(
|
||||
Icons.error,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
size: 42,
|
||||
),
|
||||
);
|
||||
}
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// if (_latestValue.hasError) {
|
||||
// return chewieController.errorBuilder?.call(
|
||||
// context,
|
||||
// chewieController.videoPlayerController.value.errorDescription!,
|
||||
// ) ??
|
||||
// Center(
|
||||
// child: Icon(
|
||||
// Icons.error,
|
||||
// color: Theme.of(context).colorScheme.onSurface,
|
||||
// size: 42,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
return MouseRegion(
|
||||
onHover: (_) {
|
||||
_cancelAndRestartTimer();
|
||||
},
|
||||
child: GestureDetector(
|
||||
onTap: () => _cancelAndRestartTimer(),
|
||||
child: AbsorbPointer(
|
||||
absorbing: notifier.hideStuff,
|
||||
child: Stack(
|
||||
children: [
|
||||
if (_displayBufferingIndicator)
|
||||
_chewieController?.bufferingBuilder?.call(context) ??
|
||||
const Center(
|
||||
child: EnteLoadingWidget(
|
||||
size: 32,
|
||||
color: fillBaseDark,
|
||||
padding: 0,
|
||||
),
|
||||
)
|
||||
else
|
||||
_buildHitArea(),
|
||||
SafeArea(
|
||||
top: false,
|
||||
left: false,
|
||||
right: false,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
PreviewStatusWidget(
|
||||
showControls: !notifier.hideStuff,
|
||||
file: widget.file,
|
||||
isPreviewPlayer: true,
|
||||
onStreamChange: widget.onStreamChange,
|
||||
),
|
||||
if (!chewieController.isLive) _buildBottomBar(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
// return MouseRegion(
|
||||
// onHover: (_) {
|
||||
// _cancelAndRestartTimer();
|
||||
// },
|
||||
// child: GestureDetector(
|
||||
// onTap: () => _cancelAndRestartTimer(),
|
||||
// child: AbsorbPointer(
|
||||
// absorbing: notifier.hideStuff,
|
||||
// child: Stack(
|
||||
// children: [
|
||||
// if (_displayBufferingIndicator)
|
||||
// _chewieController?.bufferingBuilder?.call(context) ??
|
||||
// const Center(
|
||||
// child: EnteLoadingWidget(
|
||||
// size: 32,
|
||||
// color: fillBaseDark,
|
||||
// padding: 0,
|
||||
// ),
|
||||
// )
|
||||
// else
|
||||
// _buildHitArea(),
|
||||
// SafeArea(
|
||||
// top: false,
|
||||
// left: false,
|
||||
// right: false,
|
||||
// child: Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.end,
|
||||
// children: <Widget>[
|
||||
// PreviewStatusWidget(
|
||||
// showControls: !notifier.hideStuff,
|
||||
// file: widget.file,
|
||||
// isPreviewPlayer: true,
|
||||
// onStreamChange: widget.onStreamChange,
|
||||
// ),
|
||||
// if (!chewieController.isLive) _buildBottomBar(context),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_dispose();
|
||||
super.dispose();
|
||||
}
|
||||
// @override
|
||||
// void dispose() {
|
||||
// _dispose();
|
||||
// super.dispose();
|
||||
// }
|
||||
|
||||
void _dispose() {
|
||||
controller.removeListener(_updateState);
|
||||
_hideTimer?.cancel();
|
||||
_initTimer?.cancel();
|
||||
_showAfterExpandCollapseTimer?.cancel();
|
||||
}
|
||||
// void _dispose() {
|
||||
// controller.removeListener(_updateState);
|
||||
// _hideTimer?.cancel();
|
||||
// _initTimer?.cancel();
|
||||
// _showAfterExpandCollapseTimer?.cancel();
|
||||
// }
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
final oldController = _chewieController;
|
||||
_chewieController = ChewieController.of(context);
|
||||
controller = chewieController.videoPlayerController;
|
||||
// @override
|
||||
// void didChangeDependencies() {
|
||||
// final oldController = _chewieController;
|
||||
// _chewieController = ChewieController.of(context);
|
||||
// controller = chewieController.videoPlayerController;
|
||||
|
||||
if (oldController != chewieController) {
|
||||
_dispose();
|
||||
_initialize();
|
||||
}
|
||||
// if (oldController != chewieController) {
|
||||
// _dispose();
|
||||
// _initialize();
|
||||
// }
|
||||
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
// super.didChangeDependencies();
|
||||
// }
|
||||
|
||||
AnimatedOpacity _buildBottomBar(
|
||||
BuildContext context,
|
||||
) {
|
||||
return AnimatedOpacity(
|
||||
opacity: notifier.hideStuff ? 0.0 : 1.0,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Container(
|
||||
height: 40,
|
||||
margin: const EdgeInsets.only(bottom: 60, left: 8, right: 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: [
|
||||
Text(
|
||||
secondsToDuration(_latestValue.position.inSeconds),
|
||||
style: getEnteTextTheme(
|
||||
context,
|
||||
).mini.copyWith(
|
||||
color: textBaseDark,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: _buildProgressBar(),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
secondsToDuration(_latestValue.duration.inSeconds),
|
||||
style: getEnteTextTheme(
|
||||
context,
|
||||
).mini.copyWith(
|
||||
color: textBaseDark,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
// AnimatedOpacity _buildBottomBar(
|
||||
// BuildContext context,
|
||||
// ) {
|
||||
// return AnimatedOpacity(
|
||||
// opacity: notifier.hideStuff ? 0.0 : 1.0,
|
||||
// duration: const Duration(milliseconds: 300),
|
||||
// child: Container(
|
||||
// height: 40,
|
||||
// margin: const EdgeInsets.only(bottom: 60, left: 8, right: 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: [
|
||||
// Text(
|
||||
// secondsToDuration(_latestValue.position.inSeconds),
|
||||
// style: getEnteTextTheme(
|
||||
// context,
|
||||
// ).mini.copyWith(
|
||||
// color: textBaseDark,
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(width: 16),
|
||||
// Expanded(
|
||||
// child: _buildProgressBar(),
|
||||
// ),
|
||||
// const SizedBox(width: 16),
|
||||
// Text(
|
||||
// secondsToDuration(_latestValue.duration.inSeconds),
|
||||
// style: getEnteTextTheme(
|
||||
// context,
|
||||
// ).mini.copyWith(
|
||||
// color: textBaseDark,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
Widget _buildHitArea() {
|
||||
final bool isFinished = (_latestValue.position >= _latestValue.duration) &&
|
||||
_latestValue.duration.inSeconds > 0;
|
||||
final bool showPlayButton = true && !_dragging && !notifier.hideStuff;
|
||||
// Widget _buildHitArea() {
|
||||
// final bool isFinished = (_latestValue.position >= _latestValue.duration) &&
|
||||
// _latestValue.duration.inSeconds > 0;
|
||||
// final bool showPlayButton = true && !_dragging && !notifier.hideStuff;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (_latestValue.isPlaying) {
|
||||
if (_displayTapped) {
|
||||
setState(() {
|
||||
notifier.hideStuff = true;
|
||||
});
|
||||
} else {
|
||||
_cancelAndRestartTimer();
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
notifier.hideStuff = true;
|
||||
});
|
||||
}
|
||||
widget.playbackCallback?.call(notifier.hideStuff);
|
||||
},
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: marginSize,
|
||||
),
|
||||
child: CenterPlayButton(
|
||||
backgroundColor: Colors.black54,
|
||||
iconColor: Colors.white,
|
||||
isFinished: isFinished,
|
||||
isPlaying: controller.value.isPlaying,
|
||||
show: showPlayButton,
|
||||
onPressed: _playPause,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
// return GestureDetector(
|
||||
// onTap: () {
|
||||
// if (_latestValue.isPlaying) {
|
||||
// if (_displayTapped) {
|
||||
// setState(() {
|
||||
// notifier.hideStuff = true;
|
||||
// });
|
||||
// } else {
|
||||
// _cancelAndRestartTimer();
|
||||
// }
|
||||
// } else {
|
||||
// setState(() {
|
||||
// notifier.hideStuff = true;
|
||||
// });
|
||||
// }
|
||||
// widget.playbackCallback?.call(notifier.hideStuff);
|
||||
// },
|
||||
// child: Container(
|
||||
// alignment: Alignment.center,
|
||||
// color: Colors.transparent,
|
||||
// child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// Container(
|
||||
// margin: EdgeInsets.symmetric(
|
||||
// horizontal: marginSize,
|
||||
// ),
|
||||
// child: CenterPlayButton(
|
||||
// backgroundColor: Colors.black54,
|
||||
// iconColor: Colors.white,
|
||||
// isFinished: isFinished,
|
||||
// isPlaying: controller.value.isPlaying,
|
||||
// show: showPlayButton,
|
||||
// onPressed: _playPause,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
void _cancelAndRestartTimer() {
|
||||
_hideTimer?.cancel();
|
||||
_startHideTimer();
|
||||
// void _cancelAndRestartTimer() {
|
||||
// _hideTimer?.cancel();
|
||||
// _startHideTimer();
|
||||
|
||||
setState(() {
|
||||
notifier.hideStuff = false;
|
||||
_displayTapped = true;
|
||||
});
|
||||
widget.playbackCallback?.call(notifier.hideStuff);
|
||||
}
|
||||
// setState(() {
|
||||
// notifier.hideStuff = false;
|
||||
// _displayTapped = true;
|
||||
// });
|
||||
// widget.playbackCallback?.call(notifier.hideStuff);
|
||||
// }
|
||||
|
||||
Future<void> _initialize() async {
|
||||
controller.addListener(_updateState);
|
||||
// Future<void> _initialize() async {
|
||||
// controller.addListener(_updateState);
|
||||
|
||||
_updateState();
|
||||
// _updateState();
|
||||
|
||||
if (controller.value.isPlaying || chewieController.autoPlay) {
|
||||
_startHideTimer();
|
||||
}
|
||||
// if (controller.value.isPlaying || chewieController.autoPlay) {
|
||||
// _startHideTimer();
|
||||
// }
|
||||
|
||||
if (chewieController.showControlsOnInitialize) {
|
||||
_initTimer = Timer(const Duration(milliseconds: 200), () {
|
||||
setState(() {
|
||||
notifier.hideStuff = false;
|
||||
});
|
||||
widget.playbackCallback?.call(notifier.hideStuff);
|
||||
});
|
||||
}
|
||||
}
|
||||
// if (chewieController.showControlsOnInitialize) {
|
||||
// _initTimer = Timer(const Duration(milliseconds: 200), () {
|
||||
// setState(() {
|
||||
// notifier.hideStuff = false;
|
||||
// });
|
||||
// widget.playbackCallback?.call(notifier.hideStuff);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
void _playPause() {
|
||||
final bool isFinished = (_latestValue.position >= _latestValue.duration) &&
|
||||
_latestValue.duration.inSeconds > 0;
|
||||
// void _playPause() {
|
||||
// final bool isFinished = (_latestValue.position >= _latestValue.duration) &&
|
||||
// _latestValue.duration.inSeconds > 0;
|
||||
|
||||
setState(() {
|
||||
if (controller.value.isPlaying) {
|
||||
notifier.hideStuff = false;
|
||||
_hideTimer?.cancel();
|
||||
controller.pause();
|
||||
} else {
|
||||
_cancelAndRestartTimer();
|
||||
// setState(() {
|
||||
// if (controller.value.isPlaying) {
|
||||
// notifier.hideStuff = false;
|
||||
// _hideTimer?.cancel();
|
||||
// controller.pause();
|
||||
// } else {
|
||||
// _cancelAndRestartTimer();
|
||||
|
||||
if (!controller.value.isInitialized) {
|
||||
controller.initialize().then((_) {
|
||||
controller.play();
|
||||
});
|
||||
} else {
|
||||
if (isFinished) {
|
||||
controller.seekTo(Duration.zero);
|
||||
}
|
||||
controller.play();
|
||||
}
|
||||
}
|
||||
widget.playbackCallback?.call(notifier.hideStuff);
|
||||
});
|
||||
}
|
||||
// if (!controller.value.isInitialized) {
|
||||
// controller.initialize().then((_) {
|
||||
// controller.play();
|
||||
// });
|
||||
// } else {
|
||||
// if (isFinished) {
|
||||
// controller.seekTo(Duration.zero);
|
||||
// }
|
||||
// controller.play();
|
||||
// }
|
||||
// }
|
||||
// widget.playbackCallback?.call(notifier.hideStuff);
|
||||
// });
|
||||
// }
|
||||
|
||||
void _startHideTimer() {
|
||||
final hideControlsTimer = chewieController.hideControlsTimer.isNegative
|
||||
? ChewieController.defaultHideControlsTimer
|
||||
: chewieController.hideControlsTimer;
|
||||
_hideTimer = Timer(hideControlsTimer, () {
|
||||
setState(() {
|
||||
notifier.hideStuff = true;
|
||||
});
|
||||
widget.playbackCallback?.call(notifier.hideStuff);
|
||||
});
|
||||
}
|
||||
// void _startHideTimer() {
|
||||
// final hideControlsTimer = chewieController.hideControlsTimer.isNegative
|
||||
// ? ChewieController.defaultHideControlsTimer
|
||||
// : chewieController.hideControlsTimer;
|
||||
// _hideTimer = Timer(hideControlsTimer, () {
|
||||
// setState(() {
|
||||
// notifier.hideStuff = true;
|
||||
// });
|
||||
// widget.playbackCallback?.call(notifier.hideStuff);
|
||||
// });
|
||||
// }
|
||||
|
||||
void _bufferingTimerTimeout() {
|
||||
_displayBufferingIndicator = true;
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
// void _bufferingTimerTimeout() {
|
||||
// _displayBufferingIndicator = true;
|
||||
// if (mounted) {
|
||||
// setState(() {});
|
||||
// }
|
||||
// }
|
||||
|
||||
void _updateState() {
|
||||
if (!mounted) return;
|
||||
// void _updateState() {
|
||||
// if (!mounted) return;
|
||||
|
||||
// display the progress bar indicator only after the buffering delay if it has been set
|
||||
if (chewieController.progressIndicatorDelay != null) {
|
||||
if (controller.value.isBuffering) {
|
||||
_bufferingDisplayTimer ??= Timer(
|
||||
chewieController.progressIndicatorDelay!,
|
||||
_bufferingTimerTimeout,
|
||||
);
|
||||
} else {
|
||||
_bufferingDisplayTimer?.cancel();
|
||||
_bufferingDisplayTimer = null;
|
||||
_displayBufferingIndicator = false;
|
||||
}
|
||||
} else {
|
||||
_displayBufferingIndicator = controller.value.isBuffering;
|
||||
}
|
||||
// // display the progress bar indicator only after the buffering delay if it has been set
|
||||
// if (chewieController.progressIndicatorDelay != null) {
|
||||
// if (controller.value.isBuffering) {
|
||||
// _bufferingDisplayTimer ??= Timer(
|
||||
// chewieController.progressIndicatorDelay!,
|
||||
// _bufferingTimerTimeout,
|
||||
// );
|
||||
// } else {
|
||||
// _bufferingDisplayTimer?.cancel();
|
||||
// _bufferingDisplayTimer = null;
|
||||
// _displayBufferingIndicator = false;
|
||||
// }
|
||||
// } else {
|
||||
// _displayBufferingIndicator = controller.value.isBuffering;
|
||||
// }
|
||||
|
||||
setState(() {
|
||||
_latestValue = controller.value;
|
||||
});
|
||||
}
|
||||
// setState(() {
|
||||
// _latestValue = controller.value;
|
||||
// });
|
||||
// }
|
||||
|
||||
Widget _buildProgressBar() {
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
return Expanded(
|
||||
child: CustomProgressBar(
|
||||
controller,
|
||||
onDragStart: () {
|
||||
setState(() {
|
||||
_dragging = true;
|
||||
});
|
||||
// Widget _buildProgressBar() {
|
||||
// final colorScheme = getEnteColorScheme(context);
|
||||
// return Expanded(
|
||||
// child: CustomProgressBar(
|
||||
// controller,
|
||||
// onDragStart: () {
|
||||
// setState(() {
|
||||
// _dragging = true;
|
||||
// });
|
||||
|
||||
_hideTimer?.cancel();
|
||||
},
|
||||
onDragUpdate: () {
|
||||
_hideTimer?.cancel();
|
||||
},
|
||||
onDragEnd: () {
|
||||
setState(() {
|
||||
_dragging = false;
|
||||
});
|
||||
// _hideTimer?.cancel();
|
||||
// },
|
||||
// onDragUpdate: () {
|
||||
// _hideTimer?.cancel();
|
||||
// },
|
||||
// onDragEnd: () {
|
||||
// setState(() {
|
||||
// _dragging = false;
|
||||
// });
|
||||
|
||||
_startHideTimer();
|
||||
},
|
||||
colors: ChewieProgressColors(
|
||||
playedColor: colorScheme.primary300,
|
||||
handleColor: backgroundElevatedLight,
|
||||
bufferedColor: backgroundElevatedLight.withOpacity(0.5),
|
||||
backgroundColor: fillMutedDark,
|
||||
),
|
||||
draggableProgressBar: chewieController.draggableProgressBar,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// _startHideTimer();
|
||||
// },
|
||||
// colors: ChewieProgressColors(
|
||||
// playedColor: colorScheme.primary300,
|
||||
// handleColor: backgroundElevatedLight,
|
||||
// bufferedColor: backgroundElevatedLight.withOpacity(0.5),
|
||||
// backgroundColor: fillMutedDark,
|
||||
// ),
|
||||
// draggableProgressBar: chewieController.draggableProgressBar,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
class CenterPlayButton extends StatelessWidget {
|
||||
const CenterPlayButton({
|
||||
super.key,
|
||||
required this.backgroundColor,
|
||||
this.iconColor,
|
||||
required this.show,
|
||||
required this.isPlaying,
|
||||
required this.isFinished,
|
||||
this.onPressed,
|
||||
});
|
||||
// class CenterPlayButton extends StatelessWidget {
|
||||
// const CenterPlayButton({
|
||||
// super.key,
|
||||
// required this.backgroundColor,
|
||||
// this.iconColor,
|
||||
// required this.show,
|
||||
// required this.isPlaying,
|
||||
// required this.isFinished,
|
||||
// this.onPressed,
|
||||
// });
|
||||
|
||||
final Color backgroundColor;
|
||||
final Color? iconColor;
|
||||
final bool show;
|
||||
final bool isPlaying;
|
||||
final bool isFinished;
|
||||
final VoidCallback? onPressed;
|
||||
// final Color backgroundColor;
|
||||
// final Color? iconColor;
|
||||
// final bool show;
|
||||
// final bool isPlaying;
|
||||
// final bool isFinished;
|
||||
// final VoidCallback? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedOpacity(
|
||||
opacity: show ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
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: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onPressed,
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return AnimatedOpacity(
|
||||
// opacity: show ? 1.0 : 0.0,
|
||||
// duration: const Duration(milliseconds: 300),
|
||||
// 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: GestureDetector(
|
||||
// behavior: HitTestBehavior.opaque,
|
||||
// onTap: onPressed,
|
||||
// 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,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
@ -7,8 +7,8 @@ import "package:photos/events/use_media_kit_for_video.dart";
|
||||
import "package:photos/models/file/file.dart";
|
||||
import "package:photos/services/filedata/filedata_service.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_preview.dart";
|
||||
import "package:photos/ui/viewer/file/video_widget_native.dart";
|
||||
|
||||
class VideoWidget extends StatefulWidget {
|
||||
@ -60,7 +60,7 @@ class _VideoWidgetState extends State<VideoWidget> {
|
||||
?.containsKey(widget.file.uploadedFileID!) ??
|
||||
false);
|
||||
if (isPreviewVideoPlayable && selectPreviewForPlay) {
|
||||
return PreviewVideoWidget(
|
||||
return VideoWidgetMediaKitPreview(
|
||||
widget.file,
|
||||
tagPrefix: widget.tagPrefix,
|
||||
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/services/files_service.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/viewer/file/preview_status_widget.dart";
|
||||
import "package:photos/utils/debouncer.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/dialog_util.dart";
|
||||
import "package:photos/utils/file_util.dart";
|
||||
import "package:photos/utils/seconds_to_duration.dart";
|
||||
import "package:photos/utils/toast_util.dart";
|
||||
|
||||
class VideoWidgetMediaKitNew extends StatefulWidget {
|
||||
@ -28,14 +27,14 @@ class VideoWidgetMediaKitNew extends StatefulWidget {
|
||||
final String? tagPrefix;
|
||||
final Function(bool)? playbackCallback;
|
||||
final bool isFromMemories;
|
||||
final void Function()? onStreamChange;
|
||||
final void Function() onStreamChange;
|
||||
|
||||
const VideoWidgetMediaKitNew(
|
||||
this.file, {
|
||||
this.tagPrefix,
|
||||
this.playbackCallback,
|
||||
this.isFromMemories = false,
|
||||
this.onStreamChange,
|
||||
required this.onStreamChange,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@ -138,44 +137,20 @@ class _VideoWidgetMediaKitNewState extends State<VideoWidgetMediaKitNew>
|
||||
},
|
||||
child: Center(
|
||||
child: controller != null
|
||||
? _VideoWidget(
|
||||
? common.VideoWidget(
|
||||
widget.file,
|
||||
controller!,
|
||||
widget.playbackCallback,
|
||||
isFromMemories: widget.isFromMemories,
|
||||
onStreamChange: widget.onStreamChange,
|
||||
)
|
||||
// : Stack(
|
||||
// children: [
|
||||
// _getThumbnail(),
|
||||
// Container(
|
||||
// color: Colors.black12,
|
||||
// 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(),
|
||||
: const Center(
|
||||
child: EnteLoadingWidget(
|
||||
size: 32,
|
||||
color: fillBaseDark,
|
||||
padding: 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -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
|
||||
version: "1.0.2"
|
||||
media_kit:
|
||||
dependency: "direct overridden"
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: media_kit
|
||||
ref: HEAD
|
||||
@ -1604,7 +1604,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.3.6"
|
||||
media_kit_libs_ios_video:
|
||||
dependency: "direct overridden"
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "libs/ios/media_kit_libs_ios_video"
|
||||
ref: HEAD
|
||||
@ -1629,7 +1629,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.1.4"
|
||||
media_kit_libs_video:
|
||||
dependency: "direct overridden"
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "libs/universal/media_kit_libs_video"
|
||||
ref: HEAD
|
||||
@ -1646,7 +1646,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.0.10"
|
||||
media_kit_video:
|
||||
dependency: "direct overridden"
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: media_kit_video
|
||||
ref: HEAD
|
||||
@ -2802,7 +2802,7 @@ packages:
|
||||
source: git
|
||||
version: "3.0.0"
|
||||
video_player:
|
||||
dependency: "direct overridden"
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "packages/video_player/video_player"
|
||||
ref: android_video_roation_fix
|
||||
@ -2826,15 +2826,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -120,6 +120,22 @@ dependencies:
|
||||
git:
|
||||
url: "https://github.com/ente-io/media_extension.git"
|
||||
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
|
||||
modal_bottom_sheet: ^3.0.0-pre
|
||||
motion_photos:
|
||||
@ -186,6 +202,11 @@ dependencies:
|
||||
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
|
||||
visibility_detector: ^0.3.3
|
||||
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
|
||||
ffi: 2.1.0
|
||||
intl: 0.18.1
|
||||
js: ^0.6.7
|
||||
media_kit:
|
||||
git:
|
||||
url: https://github.com/media-kit/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:
|
||||
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
|
||||
video_player:
|
||||
git:
|
||||
url: https://github.com/ente-io/packages.git
|
||||
ref: android_video_roation_fix
|
||||
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
|
||||
win32: ^5.5.4
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user