Commit 9efff6aeb485f8e6c7536413c72215e1a39a3f7c
1 parent
b830911b
feat:视频跟读逻辑修改
Showing
6 changed files
with
245 additions
and
101 deletions
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<RepeatAfterContentEvent, RepeatAfterCo | @@ -71,60 +86,67 @@ class RepeatAfterContentBloc extends Bloc<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<RepeatAfterContentEvent, RepeatAfterCo | @@ -177,12 +199,17 @@ class RepeatAfterContentBloc extends Bloc<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<RepeatAfterContentEvent, RepeatAfterCo | @@ -196,21 +223,116 @@ class RepeatAfterContentBloc extends Bloc<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 'package:wow_english/route/route.dart'; | @@ -7,6 +7,7 @@ import 'package:wow_english/route/route.dart'; | ||
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 |