我正在尝试做一个允许用户使用相机(实际上使用相机依赖项)的应用程序,在相机屏幕中,我想显示一个必须与视频一起记录的计时器。然后当我想在图库中查看视频时,计时器必须在视频中。我无法使用相机依赖项来做到这一点,所以我尝试使用渲染依赖项,其想法是记录相机预览的小部件和屏幕中显示的计时器。
// ignore_for_file: use_build_context_synchronously, unnecessary_null_comparison, avoid_print
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:video_app/providers/countdown_provider.dart';
import 'package:video_app/providers/countup_provider.dart';
import 'package:video_app/providers/tabata_provider.dart';
import 'package:video_app/screens/timer_screen.dart';
import 'package:video_app/screens/video_screen.dart';
import 'package:render/render.dart';
class CameraPage extends StatefulWidget {
const CameraPage({super.key, required this.timerType});
final TimerType timerType;
@override
CameraPageState createState() => CameraPageState();
}
enum WidgetState {none, loading, loaded, error}
class CameraPageState extends State<CameraPage> {
bool _isRecording = false;
WidgetState widgetState = WidgetState.none;
late CameraController _cameraController;
Offset? _focusPoint;
bool _isFlashOn = false;
late MotionRecorder stopController;
final RenderController renderController = RenderController(logLevel: LogLevel.debug);
// final renderController = RenderController();
@override
void initState() {
_initCamera();
super.initState();
}
@override
void dispose() {
_cameraController.dispose();
super.dispose();
}
_initCamera() async {
widgetState = WidgetState.loading;
final cameras = await availableCameras();
final back = cameras.firstWhere((camera) => camera.lensDirection == CameraLensDirection.back);
_cameraController = CameraController(back, ResolutionPreset.max);
await _cameraController.initialize();
//setState(() => _isLoading = false);
if (_cameraController.value.hasError) {
widgetState = WidgetState.error;
if (mounted) setState(() {});
} else {
widgetState = WidgetState.loaded;
if (mounted) setState(() {});
}
}
_recordVideo() async {
if (_isRecording == false) {
stopController = renderController.recordMotion();
// await _cameraController.prepareForVideoRecording();
// await _cameraController.startVideoRecording();
setState(() => _isRecording = true);
} else {
RenderResult result = await stopController.stop();
setState(() => _isRecording = false);
File refile = result.output;
XFile file = XFile(refile.path);
// final file = await _cameraController.stopVideoRecording();
// setState(() => _isRecording = false);
final route = MaterialPageRoute(
fullscreenDialog: true,
builder: (_) => VideoPage(file: file),
);
Navigator.push(context, route);
}
}
@override
Widget build(BuildContext context) {
final countdownProvider = Provider.of<CountDownProvider>(context);
final countupProvider = Provider.of<CountupProvider>(context);
final tabataProvider = Provider.of<TabataProvider>(context);
switch (widgetState) {
case WidgetState.none:
case WidgetState.loading:
return buildScaffold(context, const Text('Iniciando camara...'));
case WidgetState.loaded:
return SafeArea(
child: Scaffold(
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Stack(
alignment: Alignment.bottomCenter,
children: [
//
Positioned.fill(
top: 50,
child: AspectRatio(
aspectRatio: _cameraController.value.aspectRatio,
child: GestureDetector(
onTapDown: (TapDownDetails details){
final Offset tapPosition = details.localPosition;
final Offset relativTapPosition = Offset(
tapPosition.dx / constraints.maxWidth,
tapPosition.dy / constraints.maxHeight,
);
_setFocusPoint(relativTapPosition);
},
child: Render(
controller: renderController,
child: CameraPreview(_cameraController,
child:
Positioned(
top: 0,
right: 10,
child: Container(
color: Colors.transparent,
child: Text( widget.timerType != TimerType.tabata
? (widget.timerType == TimerType.countdown
? context.select((CountDownProvider count) => count.timeLeftString)
: context.select((CountupProvider count) => count.timeLeftString))
: context.select((TabataProvider count) => count.timeLeftString),
style: const TextStyle(fontSize: 50, fontWeight: FontWeight.normal, color: Colors.white),),
),
),
),
),
),
),
),
Positioned(
bottom: 0,
child: Padding(
padding: const EdgeInsets.all(25),
child: IconButton(
icon: Icon(_isRecording ? Icons.stop : Icons.circle, color: _isRecording ? Colors.black : Colors.red), iconSize: 75,
onPressed: () {
switch (widget.timerType){
case TimerType.countdown:
countdownProvider.startStopTimer();
break;
case TimerType.countup:
countupProvider.startStopTimer();
break;
case TimerType.tabata:
tabataProvider.startStopTimer();
break;
}
_recordVideo();
},
),
),
),
],
);
},
),
),
);
case WidgetState.error:
return buildScaffold(context, const Text("¡Ooops! Error al cargar la cámara 😩. Reinicia la apliación."));
}}
Widget buildScaffold(BuildContext context, Text text) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text("Cámara"),
backgroundColor: Colors.transparent,
elevation: 0,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if(widgetState == WidgetState.loading)
const CircularProgressIndicator(),
text
],
),
),
);
}
}
我没有找到其他方法来做到这一点。我愿意接受想法。
如果您想要类似应用程序的参考:Google Play 上的“WeTime”或 Google Play 和应用商店中的“WODProof”
我能够解决它,问题是当我想将文件传递给 XFile 时
enderResult result = await stopController.stop();
setState(() => _isRecording = false);
File refile = result.output;
XFile file = XFile(refile.path);
删除最后两行并将 result.output 传递到 videoPage (并修改 videoPage 屏幕)
final route = MaterialPageRoute(
fullscreenDialog: true,
builder: (_) => VideoPage(file: result.output),
);
这有效,但视频后来看起来非常滞后,所以如果有人有更好的主意来实现应用程序的此功能,我将非常感激