Commit 9efff6aeb485f8e6c7536413c72215e1a39a3f7c

Authored by liangchengyou
1 parent b830911b

feat:视频跟读逻辑修改

ios/Runner/XSMessageMehtodChannel.swift
@@ -78,7 +78,7 @@ class XSMessageMehtodChannel: NSObject,SSOralEvaluatingManagerDelegate { @@ -78,7 +78,7 @@ class XSMessageMehtodChannel: NSObject,SSOralEvaluatingManagerDelegate {
78 } else { 78 } else {
79 config.oralType = .sentence 79 config.oralType = .sentence
80 } 80 }
81 - config.oralType = .kidSent 81 + config.oralType = .sentence
82 config.userId = userId 82 config.userId = userId
83 SSOralEvaluatingManager.share().startEvaluateOral(withWavPath: voicePath, config: config) 83 SSOralEvaluatingManager.share().startEvaluateOral(withWavPath: voicePath, config: config)
84 } 84 }
lib/pages/repeatafter/widgets/repeat_after_item.dart
@@ -20,12 +20,11 @@ class RepeatAfterItem extends StatelessWidget { @@ -20,12 +20,11 @@ class RepeatAfterItem extends StatelessWidget {
20 child: GestureDetector( 20 child: GestureDetector(
21 onTap: (){ 21 onTap: (){
22 ///todo 暂时注释调,测试用 22 ///todo 暂时注释调,测试用
23 - // if (entity != null) {  
24 - // if (!entity!.lock!) {  
25 - // tapEvent?.call();  
26 - // }  
27 - // }  
28 - tapEvent?.call(); 23 + if (entity != null) {
  24 + if (!entity!.lock!) {
  25 + tapEvent?.call();
  26 + }
  27 + }
29 }, 28 },
30 child: Stack( 29 child: Stack(
31 children: [ 30 children: [
lib/pages/repeataftercontent/bloc/repeat_after_content_bloc.dart
1 -import 'package:audioplayers/audioplayers.dart'; 1 +import 'dart:io';
  2 +import 'dart:async';
  3 +
  4 +import 'package:audio_session/audio_session.dart';
2 import 'package:flutter/cupertino.dart'; 5 import 'package:flutter/cupertino.dart';
3 import 'package:flutter/services.dart'; 6 import 'package:flutter/services.dart';
4 import 'package:flutter_bloc/flutter_bloc.dart'; 7 import 'package:flutter_bloc/flutter_bloc.dart';
  8 +import 'package:flutter_sound/flutter_sound.dart';
  9 +import 'package:path_provider/path_provider.dart';
  10 +import 'package:permission_handler/permission_handler.dart';
5 import 'package:wow_english/common/request/dao/listen_dao.dart'; 11 import 'package:wow_english/common/request/dao/listen_dao.dart';
6 import '../../../common/request/exception.dart'; 12 import '../../../common/request/exception.dart';
7 import '../../../models/read_content_entity.dart'; 13 import '../../../models/read_content_entity.dart';
@@ -23,47 +29,56 @@ enum VoiceRecordState { @@ -23,47 +29,56 @@ enum VoiceRecordState {
23 voiceRecordEnd 29 voiceRecordEnd
24 } 30 }
25 31
26 -enum VoicePlayState { 32 +///先声测评状态
  33 +enum XSVoiceCheckState {
27 ///未知 34 ///未知
28 unKnow, 35 unKnow,
29 - ///播放中  
30 - playing,  
31 - ///播放完成  
32 - completed,  
33 - ///播放终止  
34 - stop 36 + ///测评开始
  37 + start,
  38 + ///测评结束
  39 + stop,
35 } 40 }
36 41
37 class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterContentState> { 42 class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterContentState> {
38 43
39 final String courseLessonId; 44 final String courseLessonId;
40 45
41 - ///是否正在播放视频 46 + /// 是否正在播放视频
42 bool _videoPlaying = true; 47 bool _videoPlaying = true;
43 - ///是否需要录音 48 + bool get videoPlaying => _videoPlaying;
  49 + /// 是否正在录音
44 bool _isRecord = false; 50 bool _isRecord = false;
45 - 51 + bool get isRecord => _isRecord;
  52 + /// 先声评测状态
  53 + XSVoiceCheckState _xSCheckState = XSVoiceCheckState.unKnow;
  54 + XSVoiceCheckState get xSCheckState => _xSCheckState;
  55 + /// 评测结果
46 Map? _voiceTestResult; 56 Map? _voiceTestResult;
47 - 57 + Map? get voiceTestResult => _voiceTestResult;
  58 + /// 录音的次数
48 int _recordNumber = 0; 59 int _recordNumber = 0;
49 - 60 + /// 录音文件地址
  61 + String _path = '';
  62 + String get path => _path;
  63 + /// 当前播放的视频位置
  64 + int _currentPlayIndex = 0;
  65 + int get currentPlayIndex => _currentPlayIndex;
  66 +
  67 + /// 录音状态
50 VoiceRecordState _voiceRecordState = VoiceRecordState.voiceRecordUnkonw; 68 VoiceRecordState _voiceRecordState = VoiceRecordState.voiceRecordUnkonw;
51 -  
52 - bool get videoPlaying => _videoPlaying;  
53 -  
54 - bool get isRecord => _isRecord;  
55 -  
56 VoiceRecordState get voiceRecordState => _voiceRecordState; 69 VoiceRecordState get voiceRecordState => _voiceRecordState;
57 70
  71 + /// 跟读内容数字
58 List<ReadContentEntity?>? _entityList; 72 List<ReadContentEntity?>? _entityList;
59 -  
60 List<ReadContentEntity?>? get entityList => _entityList ; 73 List<ReadContentEntity?>? get entityList => _entityList ;
61 74
62 - Map? get voiceTestResult => _voiceTestResult;  
63 - 75 + /// 方法
64 late MethodChannel methodChannel; 76 late MethodChannel methodChannel;
65 77
66 - late AudioPlayer audioPlayer; 78 + ///录音
  79 + late FlutterSoundRecorder _soundRecorder;
  80 + late FlutterSoundPlayer _soundPlayer;
  81 + StreamSubscription? _soundPlayerListen;
67 82
68 RepeatAfterContentBloc(this.courseLessonId) : super(RepeatAfterContentInitial()) { 83 RepeatAfterContentBloc(this.courseLessonId) : super(RepeatAfterContentInitial()) {
69 on<VoiceRecordStateChangeEvent>(_voiceRecordStateChange); 84 on<VoiceRecordStateChangeEvent>(_voiceRecordStateChange);
@@ -71,60 +86,67 @@ class RepeatAfterContentBloc extends Bloc&lt;RepeatAfterContentEvent, RepeatAfterCo @@ -71,60 +86,67 @@ class RepeatAfterContentBloc extends Bloc&lt;RepeatAfterContentEvent, RepeatAfterCo
71 on<ChangeVideoPlayIndexEvent>(_changeVideoPlayIndex); 86 on<ChangeVideoPlayIndexEvent>(_changeVideoPlayIndex);
72 on<VideoPlayChangeEvent>(_videoPlayStateChange); 87 on<VideoPlayChangeEvent>(_videoPlayStateChange);
73 on<RecordeVoicePlayEvent>(_recordeVoicePlay); 88 on<RecordeVoicePlayEvent>(_recordeVoicePlay);
  89 + on<StarRecordVoiceEvent>(_starRecordVoice);
  90 + on<StopRecordVoiceEvent>(_stopRecordVoice);
74 on<XSVoiceResultEvent>(_voiceXsResult); 91 on<XSVoiceResultEvent>(_voiceXsResult);
75 on<XSVoiceInitEvent>(_initVoiceSdk); 92 on<XSVoiceInitEvent>(_initVoiceSdk);
76 on<RequestDataEvent>(_requestData); 93 on<RequestDataEvent>(_requestData);
77 on<XSVoiceTestEvent>(_voiceXsTest); 94 on<XSVoiceTestEvent>(_voiceXsTest);
78 on<XSVoiceStopEvent>(_voiceXsStop); 95 on<XSVoiceStopEvent>(_voiceXsStop);
79 on<VoiceRecordEvent>(_voiceRecord); 96 on<VoiceRecordEvent>(_voiceRecord);
80 - on<InitBlocEvent>((event, emit) {  
81 - //音频播放器  
82 - audioPlayer = AudioPlayer();  
83 - audioPlayer.onPlayerStateChanged.listen((event) async {  
84 - debugPrint('播放状态变化');  
85 - if (event == PlayerState.completed) {  
86 - debugPrint('播放完成');  
87 -  
88 - }  
89 - if (event == PlayerState.stopped) {  
90 - debugPrint('播放结束');  
91 -  
92 - }  
93 -  
94 - if (event == PlayerState.playing) {  
95 - debugPrint('正在播放中');  
96 -  
97 - }  
98 - if(isClosed) {  
99 - return;  
100 - }  
101 -  
102 - }); 97 + on<InitBlocEvent>(_initBlocData);
  98 + }
103 99
104 - methodChannel = const MethodChannel('wow_english/sing_sound_method_channel');  
105 - methodChannel.setMethodCallHandler((call) async {  
106 - if (call.method == 'voiceResult') {//评测结果  
107 - add(XSVoiceResultEvent(call.arguments));  
108 - add(PostFollowReadContentEvent());  
109 - return;  
110 - } 100 + @override
  101 + Future<void> close() {
  102 + _releaseFlauto();
  103 + return super.close();
  104 + }
111 105
112 - if (call.method == 'voiceStart') {//评测开始  
113 - debugPrint('评测开始');  
114 - return;  
115 - } 106 + ///初始化功能
  107 + void _initBlocData(InitBlocEvent event, Emitter<RepeatAfterContentState> emitter) {
  108 + methodChannel = const MethodChannel('wow_english/sing_sound_method_channel');
  109 + methodChannel.setMethodCallHandler((call) async {
  110 + if (call.method == 'voiceResult') {//评测结果
  111 + add(XSVoiceResultEvent(call.arguments));
  112 + add(PostFollowReadContentEvent());
  113 + return;
  114 + }
  115 + });
116 116
117 - if (call.method == 'voiceEnd') {//评测结束  
118 - debugPrint('评测结束');  
119 - return;  
120 - } 117 + //录音
  118 + _soundRecorder = FlutterSoundRecorder();
  119 + _init();
  120 + }
121 121
122 - if (call.method == 'voiceFail') {//评测失败  
123 - showToast('评测失败');  
124 - return;  
125 - }  
126 - });  
127 - }); 122 + void _init() async {
  123 + await _soundRecorder.openRecorder();
  124 + await _soundRecorder.setSubscriptionDuration(const Duration(milliseconds: 10));
  125 +
  126 + //音屏
  127 + _soundPlayer = FlutterSoundPlayer();
  128 + //设置音频
  129 + final session = await AudioSession.instance;
  130 + await session.configure(AudioSessionConfiguration(
  131 + avAudioSessionCategory: AVAudioSessionCategory.playAndRecord,
  132 + avAudioSessionCategoryOptions:
  133 + AVAudioSessionCategoryOptions.allowBluetooth |
  134 + AVAudioSessionCategoryOptions.defaultToSpeaker,
  135 + avAudioSessionMode: AVAudioSessionMode.spokenAudio,
  136 + avAudioSessionRouteSharingPolicy:
  137 + AVAudioSessionRouteSharingPolicy.defaultPolicy,
  138 + avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none,
  139 + androidAudioAttributes: const AndroidAudioAttributes(
  140 + contentType: AndroidAudioContentType.speech,
  141 + flags: AndroidAudioFlags.none,
  142 + usage: AndroidAudioUsage.voiceCommunication,
  143 + ),
  144 + androidAudioFocusGainType: AndroidAudioFocusGainType.gain,
  145 + androidWillPauseWhenDucked: true,
  146 + ));
  147 + await _soundPlayer.closePlayer();
  148 + await _soundPlayer.openPlayer();
  149 + await _soundPlayer.setSubscriptionDuration(const Duration(milliseconds: 10));
128 } 150 }
129 151
130 ///请求数据 152 ///请求数据
@@ -177,12 +199,17 @@ class RepeatAfterContentBloc extends Bloc&lt;RepeatAfterContentEvent, RepeatAfterCo @@ -177,12 +199,17 @@ class RepeatAfterContentBloc extends Bloc&lt;RepeatAfterContentEvent, RepeatAfterCo
177 199
178 ///先声测试 200 ///先声测试
179 void _voiceXsTest(XSVoiceTestEvent event,Emitter<RepeatAfterContentState> emitter) async { 201 void _voiceXsTest(XSVoiceTestEvent event,Emitter<RepeatAfterContentState> emitter) async {
180 - await audioPlayer.stop();  
181 methodChannel.invokeMethod( 202 methodChannel.invokeMethod(
182 - 'startVoice',  
183 - {'word':event.testWord,'type':event.type,'userId':event.userId.toString()} 203 + 'starLocalVoice',
  204 + {
  205 + 'type':event.type,
  206 + 'word':event.testWord,
  207 + 'voicePath':_path,
  208 + 'userId':event.userId.toString()
  209 + }
184 ); 210 );
185 _recordNumber++; 211 _recordNumber++;
  212 + _xSCheckState = XSVoiceCheckState.start;
186 emitter(XSVoiceTestState()); 213 emitter(XSVoiceTestState());
187 } 214 }
188 215
@@ -196,21 +223,116 @@ class RepeatAfterContentBloc extends Bloc&lt;RepeatAfterContentEvent, RepeatAfterCo @@ -196,21 +223,116 @@ class RepeatAfterContentBloc extends Bloc&lt;RepeatAfterContentEvent, RepeatAfterCo
196 final Map args = event.message as Map; 223 final Map args = event.message as Map;
197 final result = args['result'] as Map; 224 final result = args['result'] as Map;
198 final overall = result['overall'].toString(); 225 final overall = result['overall'].toString();
199 - final audioUrl = args['audioUrl'].toString();  
200 - _voiceTestResult = {'overall':overall,'audioUrl':audioUrl}; 226 + _voiceTestResult = {'overall':overall};
201 emitter(XSVoiceTestState()); 227 emitter(XSVoiceTestState());
202 } 228 }
203 229
204 ///播放声音 230 ///播放声音
205 void _recordeVoicePlay(RecordeVoicePlayEvent event,Emitter<RepeatAfterContentState> emitter) async { 231 void _recordeVoicePlay(RecordeVoicePlayEvent event,Emitter<RepeatAfterContentState> emitter) async {
206 - await audioPlayer.stop();  
207 - assert(event.audioUrl.isNotEmpty);  
208 - await audioPlayer.play(UrlSource(event.audioUrl));  
209 - } 232 + if (await _fileExists(_path)) {
  233 + if (_soundPlayer.isPlaying) {
  234 + _soundPlayer.stopPlayer();
  235 + }
210 236
  237 + await _soundPlayer.startPlayer(
  238 + fromURI: path,
  239 + codec: Codec.aacADTS,
  240 + sampleRate: 44000,
  241 + whenFinished: (){
  242 +
  243 + }
  244 + );
  245 + }
  246 + }
211 247
212 ///更改播放的视频 248 ///更改播放的视频
213 void _changeVideoPlayIndex(ChangeVideoPlayIndexEvent event,Emitter<RepeatAfterContentState> emitter) async { 249 void _changeVideoPlayIndex(ChangeVideoPlayIndexEvent event,Emitter<RepeatAfterContentState> emitter) async {
  250 + if (_entityList == null || _entityList!.isEmpty) {
  251 + return;
  252 + }
  253 + if (event.isNext) {
  254 + if (_currentPlayIndex < _entityList!.length-1) {
  255 + _currentPlayIndex++;
  256 + }
  257 + } else {
  258 + if (_currentPlayIndex >0) {
  259 + _currentPlayIndex--;
  260 + }
  261 + }
214 emitter(ChangeVideoPlayIndexState(event.isNext)); 262 emitter(ChangeVideoPlayIndexState(event.isNext));
215 } 263 }
  264 +
  265 + ///开始录音
  266 + void _starRecordVoice(StarRecordVoiceEvent event,Emitter<RepeatAfterContentState> emitter) async {
  267 + try {
  268 + await getPermissionStatus().then((value) async {
  269 + if (!value) {
  270 + debugPrint('失败$value');
  271 + return;
  272 + }
  273 + Directory tempDir = await getTemporaryDirectory();
  274 + var time = DateTime.now().millisecondsSinceEpoch;
  275 + String path = '${tempDir.path}/$time${ext[Codec.aacADTS.index]}';
  276 +
  277 + _path = path;
  278 + debugPrint('=====> 准备开始录音');
  279 + await _soundRecorder.startRecorder(
  280 + toFile: path,
  281 + codec: Codec.aacADTS,
  282 + bitRate: 8000,
  283 + numChannels: 1,
  284 + sampleRate: 8000,
  285 + );
  286 + debugPrint('=====> 开始录音');
  287 + _voiceRecordState = VoiceRecordState.voiceRecording;
  288 + emitter(VoiceRecordStateChange());
  289 + });
  290 + } catch (error) {
  291 + await _soundRecorder.stopRecorder();
  292 + }
  293 + }
  294 +
  295 + ///停止录音
  296 + void _stopRecordVoice(StopRecordVoiceEvent event,Emitter<RepeatAfterContentState> emitter) async {
  297 + debugPrint('=====> 停止录音');
  298 + await _soundRecorder.stopRecorder();
  299 + _voiceRecordState = VoiceRecordState.voiceRecordEnd;
  300 + emitter(VoiceRecordStateChange());
  301 + }
  302 +
  303 + /// 判断文件是否存在
  304 + Future<bool> _fileExists(String path) async {
  305 + return await File(path).exists();
  306 + }
  307 +
  308 + ///获取权限
  309 + Future<bool> getPermissionStatus() async {
  310 + Permission permission = Permission.microphone;
  311 + PermissionStatus status = await permission.status;
  312 + if (status.isGranted) {
  313 + return true;
  314 + } else if (status.isDenied) {
  315 + requestPermission(permission);
  316 + } else if (status.isPermanentlyDenied) {
  317 + openAppSettings();
  318 + } else if (status.isRestricted) {
  319 + requestPermission(permission);
  320 + } else {
  321 +
  322 + }
  323 + return false;
  324 + }
  325 +
  326 + /// 释放录音
  327 + Future<void> _releaseFlauto() async {
  328 + await _soundRecorder.closeRecorder();
  329 + }
  330 +
  331 + ///申请权限
  332 + void requestPermission(Permission permission) async {
  333 + PermissionStatus status = await permission.request();
  334 + if (status.isPermanentlyDenied) {
  335 + openAppSettings();
  336 + }
  337 + }
216 } 338 }
lib/pages/repeataftercontent/bloc/repeat_after_content_event.dart
@@ -6,9 +6,9 @@ abstract class RepeatAfterContentEvent {} @@ -6,9 +6,9 @@ abstract class RepeatAfterContentEvent {}
6 class InitBlocEvent extends RepeatAfterContentEvent {} 6 class InitBlocEvent extends RepeatAfterContentEvent {}
7 7
8 class VideoPlayChangeEvent extends RepeatAfterContentEvent {} 8 class VideoPlayChangeEvent extends RepeatAfterContentEvent {}
9 - 9 +///切换录音状态
10 class VoiceRecordEvent extends RepeatAfterContentEvent {} 10 class VoiceRecordEvent extends RepeatAfterContentEvent {}
11 - 11 +///请求数据
12 class RequestDataEvent extends RepeatAfterContentEvent {} 12 class RequestDataEvent extends RepeatAfterContentEvent {}
13 13
14 class VoiceRecordStateChangeEvent extends RepeatAfterContentEvent { 14 class VoiceRecordStateChangeEvent extends RepeatAfterContentEvent {
@@ -39,13 +39,18 @@ class XSVoiceResultEvent extends RepeatAfterContentEvent { @@ -39,13 +39,18 @@ class XSVoiceResultEvent extends RepeatAfterContentEvent {
39 XSVoiceResultEvent(this.message); 39 XSVoiceResultEvent(this.message);
40 } 40 }
41 41
42 -class RecordeVoicePlayEvent extends RepeatAfterContentEvent {  
43 - final String audioUrl;  
44 - RecordeVoicePlayEvent(this.audioUrl);  
45 -} 42 +///开始录音
  43 +class StarRecordVoiceEvent extends RepeatAfterContentEvent {}
  44 +
  45 +///停止录音
  46 +class StopRecordVoiceEvent extends RepeatAfterContentEvent {}
  47 +
  48 +///播放录音
  49 +class RecordeVoicePlayEvent extends RepeatAfterContentEvent {}
46 50
47 class PostFollowReadContentEvent extends RepeatAfterContentEvent {} 51 class PostFollowReadContentEvent extends RepeatAfterContentEvent {}
48 52
  53 +///切换视频播放
49 class ChangeVideoPlayIndexEvent extends RepeatAfterContentEvent { 54 class ChangeVideoPlayIndexEvent extends RepeatAfterContentEvent {
50 final bool isNext; 55 final bool isNext;
51 ChangeVideoPlayIndexEvent(this.isNext); 56 ChangeVideoPlayIndexEvent(this.isNext);
lib/pages/repeataftercontent/repeat_after_content_page.dart
@@ -7,6 +7,7 @@ import &#39;package:wow_english/route/route.dart&#39;; @@ -7,6 +7,7 @@ import &#39;package:wow_english/route/route.dart&#39;;
7 7
8 import '../../common/core/app_consts.dart'; 8 import '../../common/core/app_consts.dart';
9 import '../../common/core/user_util.dart'; 9 import '../../common/core/user_util.dart';
  10 +import '../../models/read_content_entity.dart';
10 import '../../utils/toast_util.dart'; 11 import '../../utils/toast_util.dart';
11 import 'widgets/repeat_video_widget.dart'; 12 import 'widgets/repeat_video_widget.dart';
12 13
@@ -39,7 +40,14 @@ class _RepeatAfterContentPage extends StatelessWidget { @@ -39,7 +40,14 @@ class _RepeatAfterContentPage extends StatelessWidget {
39 Widget build(BuildContext context) { 40 Widget build(BuildContext context) {
40 return BlocListener<RepeatAfterContentBloc,RepeatAfterContentState>( 41 return BlocListener<RepeatAfterContentBloc,RepeatAfterContentState>(
41 listener: (context,state){ 42 listener: (context,state){
42 - 43 + final bloc = BlocProvider.of<RepeatAfterContentBloc>(context);
  44 + if (state is VoiceRecordStateChange) {//录音状态回调
  45 + if (bloc.voiceRecordState == VoiceRecordState.voiceRecordEnd) {//声音录制结束
  46 + ReadContentEntity? readContentEntity = bloc.entityList?[bloc.currentPlayIndex];
  47 + bloc.add(XSVoiceTestEvent(readContentEntity?.word??'','0',UserUtil.getUser()!.id.toString()));
  48 + }
  49 + return;
  50 + }
43 }, 51 },
44 child: _repeatAfterContentView(), 52 child: _repeatAfterContentView(),
45 ); 53 );
@@ -248,7 +256,7 @@ class _RepeatAfterContentPage extends StatelessWidget { @@ -248,7 +256,7 @@ class _RepeatAfterContentPage extends StatelessWidget {
248 mainAxisAlignment: MainAxisAlignment.end, 256 mainAxisAlignment: MainAxisAlignment.end,
249 children: [ 257 children: [
250 Offstage( 258 Offstage(
251 - offstage: bloc.voiceRecordState != VoiceRecordState.voiceRecordEnd && voiceResult == null, 259 + offstage:!(bloc.voiceRecordState == VoiceRecordState.voiceRecordEnd && bloc.xSCheckState == XSVoiceCheckState.stop),
252 child: Column( 260 child: Column(
253 children: [ 261 children: [
254 Container( 262 Container(
@@ -270,9 +278,7 @@ class _RepeatAfterContentPage extends StatelessWidget { @@ -270,9 +278,7 @@ class _RepeatAfterContentPage extends StatelessWidget {
270 ), 278 ),
271 IconButton( 279 IconButton(
272 onPressed: (){ 280 onPressed: (){
273 - if(voiceResult != null) {  
274 - bloc.add(RecordeVoicePlayEvent(voiceResult['audioUrl']??''));  
275 - } 281 + bloc.add(RecordeVoicePlayEvent());
276 }, 282 },
277 icon: Image.asset( 283 icon: Image.asset(
278 'voice_record_play'.assetPng, 284 'voice_record_play'.assetPng,
@@ -291,19 +297,29 @@ class _RepeatAfterContentPage extends StatelessWidget { @@ -291,19 +297,29 @@ class _RepeatAfterContentPage extends StatelessWidget {
291 ], 297 ],
292 ), 298 ),
293 ), 299 ),
  300 + Offstage(
  301 + offstage: bloc.voiceRecordState == VoiceRecordState.voiceRecordUnkonw || bloc.xSCheckState != XSVoiceCheckState.unKnow,
  302 + child: Container(
  303 + color: Colors.grey,
  304 + padding: EdgeInsets.symmetric(
  305 + vertical: 50.h,
  306 + horizontal: 50.w
  307 + ),
  308 + child: Text(
  309 + bloc.voiceRecordState == VoiceRecordState.voiceRecording?'正在录音':'录音结束'
  310 + ),
  311 + ),
  312 + ),
  313 + 10.verticalSpace,
294 GestureDetector( 314 GestureDetector(
295 onTap: () => bloc.add(VoiceRecordEvent()), 315 onTap: () => bloc.add(VoiceRecordEvent()),
296 - onLongPress: () {  
297 - bloc.add(XSVoiceTestEvent(bloc.entityList?.first?.word??'', '0', UserUtil.getUser()!.id.toString()));  
298 - },  
299 onLongPressStart: (LongPressStartDetails details) { 316 onLongPressStart: (LongPressStartDetails details) {
300 - bloc.add(VoiceRecordStateChangeEvent(VoiceRecordState.voiceRecordStat)); 317 + ///开始录音
  318 + bloc.add(StarRecordVoiceEvent());
301 }, 319 },
302 onLongPressEnd: (LongPressEndDetails details) { 320 onLongPressEnd: (LongPressEndDetails details) {
303 - bloc.add(VoiceRecordStateChangeEvent(VoiceRecordState.voiceRecordEnd));  
304 - },  
305 - onLongPressUp: () {  
306 - 321 + ///结束录音
  322 + bloc.add(StopRecordVoiceEvent());
307 }, 323 },
308 child: Image.asset( 324 child: Image.asset(
309 'video_record'.assetPng, 325 'video_record'.assetPng,
pubspec.yaml
@@ -95,6 +95,8 @@ dependencies: @@ -95,6 +95,8 @@ dependencies:
95 audioplayers: ^4.1.0 95 audioplayers: ^4.1.0
96 # 语音录制 https://pub.dev/packages/flutter_sound 96 # 语音录制 https://pub.dev/packages/flutter_sound
97 flutter_sound: ^9.2.13 97 flutter_sound: ^9.2.13
  98 + # 音频播放 https://pub.dev/packages/audio_session
  99 + audio_session: ^0.1.16
98 # 文件管理 https://pub.dev/packages/path_provider 100 # 文件管理 https://pub.dev/packages/path_provider
99 path_provider: ^2.0.15 101 path_provider: ^2.0.15
100 # 阿里云oss https://pub.dev/packages/flutter_oss_aliyun 102 # 阿里云oss https://pub.dev/packages/flutter_oss_aliyun