123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583 |
- import 'dart:io';
- import 'package:auto_route/auto_route.dart';
- import 'package:bot_toast/bot_toast.dart';
- import 'package:eitc_erm_dental_flutter/app_router.gr.dart';
- import 'package:eitc_erm_dental_flutter/db_util.dart';
- import 'package:eitc_erm_dental_flutter/entity/db/local_patient_info.dart';
- import 'package:eitc_erm_dental_flutter/exts.dart';
- import 'package:eitc_erm_dental_flutter/global.dart';
- import 'package:eitc_erm_dental_flutter/pages/view/widget/video_patient_info_bar.dart';
- import 'package:eitc_erm_dental_flutter/sp_util.dart';
- import 'package:eitc_erm_dental_flutter/vm/global_view_model.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import 'package:flutter_screenutil/flutter_screenutil.dart';
- import 'package:just_audio/just_audio.dart';
- import 'package:wakelock_plus/wakelock_plus.dart';
- import '../../funcs.dart';
- import '../../generated/assets.dart';
- import 'widget/video_operation_view.dart';
- ///视频查看页面
- @RoutePage(name: "videoViewRoute")
- class VideoViewPage extends ConsumerStatefulWidget {
- const VideoViewPage({super.key});
- @override
- ConsumerState createState() => _VideoViewPageState();
- }
- class _VideoViewPageState extends ConsumerState<VideoViewPage>
- with AutomaticKeepAliveClientMixin, WidgetsBindingObserver {
- ///纹理ID
- int _textureId = -1;
- ///视频控制界面控制器
- final VideoOperationViewController _operationViewController =
- VideoOperationViewController();
- ///上一次拍照的路径
- String _lastTakePhotoPath = "";
- ///音频播放器
- final AudioPlayer _takePhotoAudioPlayer = AudioPlayer();
- ///是否已弹出
- bool _hasPoped = false;
- ///已选择的咨询人信息
- LocalPatientInfo? _patientInfo;
- ///wifi名称
- String _wifiName = "";
- ///设备型号
- String _deviceModel = "";
- ///当前牙齿区域
- String _currentToothArea = "";
- ///是否可以设备拍照
- bool _canDeviceTakePhoto = true;
- ///是否正在选择牙齿区域
- bool _isSelectingToothArea = false;
- @override
- void initState() {
- super.initState();
- WidgetsBinding.instance.addObserver(this);
- //保持屏幕常亮
- WakelockPlus.enable();
- //开启屏幕旋转
- screenEnableRotate();
- //设置MethodChannel回调
- videoChannel.setMethodCallHandler(_methodCallHandler);
- //音频播放器加载资源
- _takePhotoAudioPlayer.setAsset(Assets.audiosTakePhoto);
- //开始视频
- _startVideo();
- //监听连接状态变化
- ref.listenManual(deviceConnectStatusProvider(videoChannel), (_, bo) {
- if (!bo) {
- logd("连接丢失");
- _popSelf();
- }
- });
- //初始化数据
- _initData();
- WidgetsBinding.instance.addPostFrameCallback((_) {
- //选择牙齿区域
- _selectToothArea();
- });
- }
- ///初始化数据
- void _initData() async {
- //初始化咨询人信息,设备型号
- await Future.wait([_initPatientInfo(), _initDeviceModel()]);
- setState(() {});
- }
- ///初始化咨询人信息
- Future _initPatientInfo() async {
- if (selectedPatientId < 0) {
- return;
- }
- LocalPatientInfo? info =
- await DbUtil.instance.getLocalPatientById(selectedPatientId);
- if (info != null) {
- _patientInfo = info;
- }
- }
- ///初始化设备型号
- Future _initDeviceModel() async {
- String? name = await getWifiName();
- if (name.isNullOrEmpty) {
- return;
- }
- _wifiName = name!;
- _deviceModel = await getDeviceModel() ?? "";
- logd("初始化设备型号,wifiName=$_wifiName,deviceModel=$_deviceModel");
- }
- @override
- void dispose() {
- WidgetsBinding.instance.removeObserver(this);
- //关闭屏幕常亮
- WakelockPlus.disable();
- if (!_hasPoped) {
- _stopRecord();
- }
- _stopVideo();
- videoChannel.setMethodCallHandler(null);
- //关闭屏幕旋转
- screenDisableRotate();
- _operationViewController.dispose();
- super.dispose();
- }
- @override
- void didChangeAppLifecycleState(AppLifecycleState state) {
- if (!(state == AppLifecycleState.inactive ||
- state == AppLifecycleState.resumed)) {
- _popSelf();
- }
- }
- @override
- bool get wantKeepAlive => true;
- @override
- Widget build(BuildContext context) {
- super.build(context);
- return Scaffold(
- body: SafeArea(
- child: Container(
- color: Colors.black,
- child: Stack(
- children: [
- Center(
- child: _getVideo(),
- ),
- VideoOperationView(
- controller: _operationViewController,
- onTakePhoto: _takePhoto,
- onStartRecord: _startRecord,
- onStopRecord: () => _stopRecord(toPreview: true),
- onStopVideo: _stopVideo,
- area: _currentToothArea,
- ),
- Align(
- alignment: Alignment.topLeft,
- child: _getVideoViewToolBar(),
- )
- ],
- ),
- )),
- );
- }
- void _startVideo() async {
- if (!mounted) {
- return;
- }
- try {
- int result = await videoChannel.invokeMethod("startVideo");
- setState(() {
- _textureId = result;
- });
- } catch (e) {
- showToast(text: getS().showVideoError);
- loge("开始视频失败", error: e);
- }
- }
- void _stopVideo() async {
- logd("停止视频");
- if (_textureId < 0) {
- return;
- }
- _textureId = -1;
- try {
- await videoChannel.invokeMethod("stopVideo");
- } catch (e) {
- loge("停止视频失败", error: e);
- }
- }
- Future<dynamic> _methodCallHandler(MethodCall call) {
- logd(call);
- if (call.method == "onDeviceTakePhoto") {
- _onDeviceTakePhoto(call.arguments);
- }
- return Future.value("");
- }
- ///获取视频Widget
- Widget _getVideo() {
- return OrientationBuilder(builder: (ctx, orientation) {
- if (Platform.isIOS) {
- Size size = MediaQuery.of(context).size;
- double width = size.width;
- double height = size.height;
- videoChannel.invokeMethod(
- orientation == Orientation.landscape
- ? "getLandscapeView"
- : "getPortraitView",
- [width, height]);
- logd("ios,width=$width,height=$height");
- if (orientation == Orientation.landscape) {
- return AspectRatio(
- aspectRatio: 16.0 / 9.0,
- child: Stack(
- children: [
- _getVideoView(true),
- Align(
- alignment: Alignment.bottomCenter,
- child: _getPatientInfoBar(orientation),
- )
- ],
- ),
- );
- } else {
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- AspectRatio(
- aspectRatio: 16.0 / 9.0,
- child: _getVideoView(true),
- ),
- _getPatientInfoBar(orientation),
- ],
- );
- }
- } else {
- if (orientation == Orientation.landscape) {
- return AspectRatio(
- aspectRatio: 16.0 / 9.0,
- child: Stack(
- children: [
- _getVideoView(false),
- Align(
- alignment: Alignment.bottomCenter,
- child: _getPatientInfoBar(orientation),
- )
- ],
- ),
- );
- } else {
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- AspectRatio(
- aspectRatio: 16.0 / 9.0,
- child: _getVideoView(false),
- ),
- _getPatientInfoBar(orientation)
- ],
- );
- }
- }
- });
- }
- Widget _getVideoView(bool isIos) {
- if (isIos) {
- return UiKitView(
- viewType: "VideoView",
- onPlatformViewCreated: (id) => logd("IOS PlatformView创建,id=$id"),
- );
- } else {
- return _textureId >= 0
- ? Texture(textureId: _textureId)
- : const SizedBox();
- }
- }
- ///获取患者信息条
- Widget _getPatientInfoBar(Orientation orientation) {
- return _operationViewController.isRecording
- ? SizedBox()
- : VideoPatientInfoBar(
- info: _patientInfo,
- orientation: orientation,
- area: _currentToothArea,
- deviceModel: _deviceModel);
- }
- ///获取工具栏
- Widget _getVideoViewToolBar() {
- return Row(
- children: [
- IconButton(
- onPressed: () => _popSelf(true),
- icon: Icon(
- Platform.isAndroid ? Icons.arrow_back : Icons.arrow_back_ios_new,
- color: Colors.white,
- )),
- const Spacer(),
- IconButton(
- onPressed: _gotoSettings,
- icon: const Icon(
- Icons.settings_outlined,
- color: Colors.white,
- )),
- ],
- );
- }
- ///拍照
- void _takePhoto() async {
- if (_operationViewController.isRecording) {
- return;
- }
- try {
- String path = await videoChannel.invokeMethod("takePhoto");
- _showTakePhotoSuccess(path);
- } catch (e) {
- loge("拍照失败", error: e);
- showToast(text: getS().takePhotoFailed);
- }
- }
- ///当设备拍照
- ///IOS平台设备和手机拍照都会调用
- void _onDeviceTakePhoto(dynamic arguments) async {
- if (!_canDeviceTakePhoto || !isCurrentPage) {
- logd("设备拍照,但是不可拍照,返回");
- return;
- }
- _takePhoto();
- }
- void _showTakePhotoSuccess(String newPath) async {
- if (newPath == _lastTakePhotoPath) {
- return;
- }
- logd("拍照成功,path=$newPath");
- _lastTakePhotoPath = newPath;
- showToast(text: getS().takePhotoSuccess);
- _playTakePhotoAudio();
- String area = _currentToothArea;
- if (mounted) {
- setState(() {
- _currentToothArea = "";
- _stopVideo();
- });
- await context.pushRoute(PhotoPreviewRoute(
- info: _patientInfo!,
- path: newPath,
- area: area,
- time: DateTime.now(),
- mobile: "",
- deviceModel: _deviceModel,
- wifi: _wifiName));
- _selectToothArea();
- _startVideo();
- }
- }
- ///开始录像
- void _startRecord() async {
- if (_operationViewController.isRecording) {
- return;
- }
- _canDeviceTakePhoto = false;
- try {
- await videoChannel.invokeMethod(
- "startRecord",
- makeFilePrefix(
- name: _patientInfo?.name ?? "",
- idCard: _patientInfo?.idCardDecrypt ?? "",
- mobile: "",
- area: _currentToothArea,
- wifi: _wifiName,
- time: DateTime.now().millisecondsSinceEpoch,
- userId: await SpUtil.getUserId()));
- _operationViewController.updateRecordingState(true);
- showToast(text: getS().hasStartRecord);
- setState(() {});
- } catch (e) {
- _canDeviceTakePhoto = true;
- _operationViewController.updateRecordingState(false);
- loge("开始录像失败", error: e);
- showToast(text: getS().startRecordFailed);
- }
- }
- ///停止录像
- void _stopRecord({bool toPreview = false}) async {
- if (!_operationViewController.isRecording) {
- return;
- }
- logd("停止录像");
- _canDeviceTakePhoto = true;
- try {
- String path = await videoChannel.invokeMethod("stopRecord");
- if (toPreview) {
- //显示loading延迟停止视频,因为停止录像是异步的,如果在没执行完就停止视频
- //会导致录像文件丢失尾帧信息,播放时没有总时长
- var cancel = BotToast.showLoading(
- crossPage: false,
- clickClose: false,
- backButtonBehavior: BackButtonBehavior.ignore);
- await Future.delayed(Duration(seconds: 1, milliseconds: 500));
- cancel();
- showToast(text: getS().hasStopRecord);
- setState(() {
- _operationViewController.updateRecordingState(false);
- _stopVideo();
- });
- if (mounted) {
- await context.pushRoute(VideoPreviewRoute(
- info: _patientInfo!,
- path: path,
- area: _currentToothArea,
- time: DateTime.now(),
- mobile: "",
- deviceModel: _deviceModel,
- wifi: _wifiName));
- _selectToothArea();
- _startVideo();
- }
- } else {
- _operationViewController.updateRecordingState(false);
- showToast(text: getS().hasStopRecord);
- }
- } catch (e) {
- _operationViewController.updateRecordingState(false);
- loge("停止录像失败", error: e);
- showToast(text: getS().stopRecordFailed);
- }
- }
- ///播放拍照音效
- void _playTakePhotoAudio() async {
- await _takePhotoAudioPlayer.seek(Duration.zero);
- await _takePhotoAudioPlayer.play();
- }
- ///前往设置
- void _gotoSettings() async {
- await context.pushRoute(const DelayShotSettingsRoute());
- if (_hasPoped) {
- return;
- }
- screenEnableRotate();
- }
- ///弹出自身页面
- void _popSelf([bool force = false]) {
- if (force) {
- Navigator.pop(context);
- return;
- }
- if (_hasPoped) {
- return;
- }
- _hasPoped = true;
- _stopRecord();
- //如果是锁屏导致的,dispose会在解锁后才会调用,导致在锁屏期间视频还是播放的,所以这里提前停止
- _stopVideo();
- //如果当前页面是最上层的就是pop,否则remove,否则会导致把后来打开的页面pop但是当前页面还存在的问题
- if (isCurrentPage) {
- logd("视频查看页面pop自身");
- //如果正在选择牙齿区域,先把弹窗pop掉
- if (_isSelectingToothArea) {
- Navigator.pop(context);
- }
- Navigator.pop(context);
- } else {
- logd("视频查看页面remove自身");
- AutoRouter.of(context).removeRoute(context.routeData);
- }
- }
- ///是否是当前页面
- bool get isCurrentPage =>
- AutoRouter.of(context).current.name == VideoViewRoute.name;
- ///选择延迟区域
- void _selectToothArea() async {
- if (!mounted) {
- return;
- }
- _canDeviceTakePhoto = false;
- _isSelectingToothArea = true;
- String? area = await showDialog(
- context: context,
- barrierDismissible: false,
- builder: (ctx) {
- return _getToothSelect(ctx, (String area) {
- showToast(text: getS().hasSelectXx(toothAreaTranslate(area)));
- Navigator.pop(ctx, area);
- });
- });
- _canDeviceTakePhoto = true;
- _isSelectingToothArea = false;
- if (area == null) {
- return;
- }
- setState(() {
- _currentToothArea = area;
- });
- }
- Widget _getToothSelect(
- BuildContext context, void Function(String area) onSelect) {
- return PopScope(
- canPop: false,
- child: FittedBox(
- child: Container(
- margin: EdgeInsets.all(80.r),
- padding: EdgeInsets.symmetric(horizontal: 100.w, vertical: 50.h),
- decoration: BoxDecoration(
- color: Colors.white30,
- borderRadius: BorderRadius.circular(100.r)),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- GestureDetector(
- child: Image.asset(Assets.imagesToothRt),
- onTap: () => onSelect(toothAreaRightTop),
- ),
- GestureDetector(
- child: Image.asset(Assets.imagesToothRb),
- onTap: () => onSelect(toothAreaRightBottom),
- ),
- ],
- ),
- Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- GestureDetector(
- child: Image.asset(Assets.imagesToothLt),
- onTap: () => onSelect(toothAreaLeftTop),
- ),
- GestureDetector(
- child: Image.asset(Assets.imagesToothLb),
- onTap: () => onSelect(toothAreaLeftBottom),
- ),
- ],
- ),
- ],
- ),
- ),
- ));
- }
- }
|