9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
1
2
3
4
|
import 'dart:io';
import 'dart:async';
import 'package:audio_session/audio_session.dart';
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
5
6
7
|
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
8
9
10
|
import 'package:flutter_sound/flutter_sound.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
11
|
import 'package:wow_english/common/request/dao/listen_dao.dart';
|
934e2b47
liangchengyou
feat:权限调整+课程进度接口对接
|
12
13
|
import 'package:wow_english/route/route.dart';
import '../../../common/dialogs/show_dialog.dart';
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
import '../../../common/request/exception.dart';
import '../../../models/read_content_entity.dart';
import '../../../utils/loading.dart';
import '../../../utils/toast_util.dart';
part 'repeat_after_content_event.dart';
part 'repeat_after_content_state.dart';
enum VoiceRecordState {
///未知
voiceRecordUnkonw,
///开始录音
voiceRecordStat,
///正在录音
voiceRecording,
///录音结束
voiceRecordEnd
}
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
34
35
|
///先声测评状态
enum XSVoiceCheckState {
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
36
37
|
///未知
unKnow,
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
38
39
|
///测评开始
start,
|
31d42f06
liangchengyou
feat:视频跟读录音修改
|
40
41
|
///评测结果
result,
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
42
43
|
///测评结束
stop,
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
44
45
46
47
48
49
|
}
class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterContentState> {
final String courseLessonId;
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
50
|
/// 是否正在播放视频
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
51
|
bool _videoPlaying = true;
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
52
53
|
bool get videoPlaying => _videoPlaying;
/// 是否正在录音
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
54
|
bool _isRecord = false;
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
55
56
57
58
59
|
bool get isRecord => _isRecord;
/// 先声评测状态
XSVoiceCheckState _xSCheckState = XSVoiceCheckState.unKnow;
XSVoiceCheckState get xSCheckState => _xSCheckState;
/// 评测结果
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
60
|
Map? _voiceTestResult;
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
61
62
|
Map? get voiceTestResult => _voiceTestResult;
/// 录音的次数
|
cc3b183a
liangchengyou
feat:ios启动图/logo
|
63
|
int _recordNumber = 0;
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
64
65
66
67
68
69
70
71
|
/// 录音文件地址
String _path = '';
String get path => _path;
/// 当前播放的视频位置
int _currentPlayIndex = 0;
int get currentPlayIndex => _currentPlayIndex;
/// 录音状态
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
72
|
VoiceRecordState _voiceRecordState = VoiceRecordState.voiceRecordUnkonw;
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
73
74
|
VoiceRecordState get voiceRecordState => _voiceRecordState;
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
75
|
/// 跟读内容数字
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
76
|
List<ReadContentEntity?>? _entityList;
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
77
78
|
List<ReadContentEntity?>? get entityList => _entityList ;
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
79
|
/// 方法
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
80
81
|
late MethodChannel methodChannel;
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
82
83
84
|
///录音
late FlutterSoundRecorder _soundRecorder;
late FlutterSoundPlayer _soundPlayer;
|
934e2b47
liangchengyou
feat:权限调整+课程进度接口对接
|
85
|
// StreamSubscription? _soundPlayerListen;
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
86
87
88
|
RepeatAfterContentBloc(this.courseLessonId) : super(RepeatAfterContentInitial()) {
on<VoiceRecordStateChangeEvent>(_voiceRecordStateChange);
|
cc3b183a
liangchengyou
feat:ios启动图/logo
|
89
|
on<PostFollowReadContentEvent>(_postFollowReadContent);
|
7d6a6ddc
liangchengyou
feat:1.0.1优化
|
90
|
on<ChangeVideoPlayIndexEvent>(_changeVideoPlayIndex);
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
91
92
|
on<VideoPlayChangeEvent>(_videoPlayStateChange);
on<RecordeVoicePlayEvent>(_recordeVoicePlay);
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
93
94
|
on<StarRecordVoiceEvent>(_starRecordVoice);
on<StopRecordVoiceEvent>(_stopRecordVoice);
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
95
96
97
98
99
100
|
on<XSVoiceResultEvent>(_voiceXsResult);
on<XSVoiceInitEvent>(_initVoiceSdk);
on<RequestDataEvent>(_requestData);
on<XSVoiceTestEvent>(_voiceXsTest);
on<XSVoiceStopEvent>(_voiceXsStop);
on<VoiceRecordEvent>(_voiceRecord);
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
101
102
|
on<InitBlocEvent>(_initBlocData);
}
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
103
|
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
104
105
106
|
@override
Future<void> close() {
_releaseFlauto();
|
6b0947ca
吴启风
feat:绘本增加initVoic...
|
107
|
_voiceXsCancel();
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
108
109
|
return super.close();
}
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
110
|
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
111
|
///初始化功能
|
cacfac41
liangchengyou
feat:修复视频跟读页无法录音问题
|
112
|
void _initBlocData(InitBlocEvent event, Emitter<RepeatAfterContentState> emitter) async {
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
113
114
115
116
117
|
methodChannel = const MethodChannel('wow_english/sing_sound_method_channel');
methodChannel.setMethodCallHandler((call) async {
if (call.method == 'voiceResult') {//评测结果
add(XSVoiceResultEvent(call.arguments));
add(PostFollowReadContentEvent());
|
31d42f06
liangchengyou
feat:视频跟读录音修改
|
118
119
120
121
122
|
return;
}
if (call.method == 'voiceEnd') {
return;
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
123
124
|
}
});
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
125
|
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
126
127
|
//录音
_soundRecorder = FlutterSoundRecorder();
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
128
129
|
//音屏
_soundPlayer = FlutterSoundPlayer();
|
934e2b47
liangchengyou
feat:权限调整+课程进度接口对接
|
130
131
132
133
134
135
|
_init();
}
void _init() async {
await _soundRecorder.openRecorder();
await _soundRecorder.setSubscriptionDuration(const Duration(milliseconds: 10));
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
//设置音频
final session = await AudioSession.instance;
await session.configure(AudioSessionConfiguration(
avAudioSessionCategory: AVAudioSessionCategory.playAndRecord,
avAudioSessionCategoryOptions:
AVAudioSessionCategoryOptions.allowBluetooth |
AVAudioSessionCategoryOptions.defaultToSpeaker,
avAudioSessionMode: AVAudioSessionMode.spokenAudio,
avAudioSessionRouteSharingPolicy:
AVAudioSessionRouteSharingPolicy.defaultPolicy,
avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none,
androidAudioAttributes: const AndroidAudioAttributes(
contentType: AndroidAudioContentType.speech,
flags: AndroidAudioFlags.none,
usage: AndroidAudioUsage.voiceCommunication,
),
androidAudioFocusGainType: AndroidAudioFocusGainType.gain,
androidWillPauseWhenDucked: true,
));
await _soundPlayer.closePlayer();
await _soundPlayer.openPlayer();
|
934e2b47
liangchengyou
feat:权限调整+课程进度接口对接
|
157
|
await _soundPlayer.setSubscriptionDuration(const Duration(milliseconds: 10));
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
|
}
///请求数据
void _requestData(RequestDataEvent event,Emitter<RepeatAfterContentState> emitter) async {
try {
await loading(() async {
_entityList = await ListenDao.readContent(courseLessonId);
emitter(RequestDataState());
});
} catch (e) {
if (e is ApiException) {
showToast(e.message??'请求失败,请检查网络连接');
}
}
}
|
68dd7ba8
liangchengyou
feat:首页主题颜色+已知问题修改
|
174
|
///提交跟读结果
|
cc3b183a
liangchengyou
feat:ios启动图/logo
|
175
176
|
void _postFollowReadContent(PostFollowReadContentEvent event,Emitter<RepeatAfterContentState> emitter) async {
try {
|
68dd7ba8
liangchengyou
feat:首页主题颜色+已知问题修改
|
177
178
|
ReadContentEntity entity = _entityList![_currentPlayIndex]!;
await ListenDao.followResult(_recordNumber.toString(),entity.id);
|
cc3b183a
liangchengyou
feat:ios启动图/logo
|
179
180
181
182
183
184
185
|
} catch (e) {
if (e is ApiException) {
}
}
}
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
|
void _videoPlayStateChange(VideoPlayChangeEvent event,Emitter<RepeatAfterContentState> emitter) async {
_videoPlaying = !_videoPlaying;
emitter(VideoPlayChangeState());
}
void _voiceRecord(VoiceRecordEvent event,Emitter<RepeatAfterContentState> emitter) async {
_isRecord = !_isRecord;
emitter(VoiceRecordChangeState());
}
void _voiceRecordStateChange(VoiceRecordStateChangeEvent event,Emitter<RepeatAfterContentState> emitter) async {
_voiceRecordState = event.voiceRecordState;
emitter(VoiceRecordStateChange());
}
_initVoiceSdk(XSVoiceInitEvent event,Emitter<RepeatAfterContentState> emitter) async {
methodChannel.invokeMethod('initVoiceSdk',event.data);
}
///先声测试
void _voiceXsTest(XSVoiceTestEvent event,Emitter<RepeatAfterContentState> emitter) async {
|
934e2b47
liangchengyou
feat:权限调整+课程进度接口对接
|
208
209
210
|
_recordNumber += 1;
_xSCheckState = XSVoiceCheckState.start;
emitter(XSVoiceTestState());
|
cacfac41
liangchengyou
feat:修复视频跟读页无法录音问题
|
211
|
await methodChannel.invokeMethod(
|
dae7f2fd
吴启风
feat:增加android端先声...
|
212
|
'startLocalVoice',
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
213
214
215
216
217
218
|
{
'type':event.type,
'word':event.testWord,
'voicePath':_path,
'userId':event.userId.toString()
}
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
219
|
);
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
220
221
222
223
224
225
226
|
}
///终止评测
void _voiceXsStop(XSVoiceStopEvent event,Emitter<RepeatAfterContentState> emitter) async {
methodChannel.invokeMethod('stopVoice');
}
|
6b0947ca
吴启风
feat:绘本增加initVoic...
|
227
228
229
230
231
|
///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作)
void _voiceXsCancel() {
methodChannel.invokeMethod('cancelVoice');
}
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
232
233
234
235
|
///先声评测结果
void _voiceXsResult(XSVoiceResultEvent event,Emitter<RepeatAfterContentState> emitter) async {
final Map args = event.message as Map;
final result = args['result'] as Map;
|
37b78a15
liangchengyou
feat:添加录音插件
|
236
|
final overall = result['overall'].toString();
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
237
|
_voiceTestResult = {'overall':overall};
|
31d42f06
liangchengyou
feat:视频跟读录音修改
|
238
|
_xSCheckState = XSVoiceCheckState.result;
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
239
240
241
242
243
|
emitter(XSVoiceTestState());
}
///播放声音
void _recordeVoicePlay(RecordeVoicePlayEvent event,Emitter<RepeatAfterContentState> emitter) async {
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
244
245
246
247
|
if (await _fileExists(_path)) {
if (_soundPlayer.isPlaying) {
_soundPlayer.stopPlayer();
}
|
7d6a6ddc
liangchengyou
feat:1.0.1优化
|
248
|
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
249
250
|
await _soundPlayer.startPlayer(
fromURI: path,
|
934e2b47
liangchengyou
feat:权限调整+课程进度接口对接
|
251
|
codec: Codec.pcm16WAV,
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
252
253
254
255
256
257
|
whenFinished: (){
}
);
}
}
|
7d6a6ddc
liangchengyou
feat:1.0.1优化
|
258
259
260
|
///更改播放的视频
void _changeVideoPlayIndex(ChangeVideoPlayIndexEvent event,Emitter<RepeatAfterContentState> emitter) async {
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
261
262
263
264
265
266
267
268
269
270
271
272
|
if (_entityList == null || _entityList!.isEmpty) {
return;
}
if (event.isNext) {
if (_currentPlayIndex < _entityList!.length-1) {
_currentPlayIndex++;
}
} else {
if (_currentPlayIndex >0) {
_currentPlayIndex--;
}
}
|
7d6a6ddc
liangchengyou
feat:1.0.1优化
|
273
274
|
emitter(ChangeVideoPlayIndexState(event.isNext));
}
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
275
276
277
278
279
280
281
282
283
284
285
|
///开始录音
void _starRecordVoice(StarRecordVoiceEvent event,Emitter<RepeatAfterContentState> emitter) async {
try {
await getPermissionStatus().then((value) async {
if (!value) {
debugPrint('失败$value');
return;
}
Directory tempDir = await getTemporaryDirectory();
var time = DateTime.now().millisecondsSinceEpoch;
|
31d42f06
liangchengyou
feat:视频跟读录音修改
|
286
|
String path = '${tempDir.path}/$time${ext[Codec.pcm16WAV.index]}';
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
287
288
289
290
291
|
_path = path;
debugPrint('=====> 准备开始录音');
await _soundRecorder.startRecorder(
toFile: path,
|
31d42f06
liangchengyou
feat:视频跟读录音修改
|
292
|
codec: Codec.pcm16WAV,
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
293
294
295
|
);
debugPrint('=====> 开始录音');
_voiceRecordState = VoiceRecordState.voiceRecording;
|
934e2b47
liangchengyou
feat:权限调整+课程进度接口对接
|
296
|
_xSCheckState = XSVoiceCheckState.unKnow;
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
|
emitter(VoiceRecordStateChange());
});
} catch (error) {
await _soundRecorder.stopRecorder();
}
}
///停止录音
void _stopRecordVoice(StopRecordVoiceEvent event,Emitter<RepeatAfterContentState> emitter) async {
debugPrint('=====> 停止录音');
await _soundRecorder.stopRecorder();
_voiceRecordState = VoiceRecordState.voiceRecordEnd;
emitter(VoiceRecordStateChange());
}
/// 判断文件是否存在
Future<bool> _fileExists(String path) async {
return await File(path).exists();
}
///获取权限
Future<bool> getPermissionStatus() async {
Permission permission = Permission.microphone;
PermissionStatus status = await permission.status;
if (status.isGranted) {
return true;
} else if (status.isDenied) {
requestPermission(permission);
} else if (status.isPermanentlyDenied) {
|
934e2b47
liangchengyou
feat:权限调整+课程进度接口对接
|
326
|
showDialog();
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
|
} else if (status.isRestricted) {
requestPermission(permission);
} else {
}
return false;
}
/// 释放录音
Future<void> _releaseFlauto() async {
await _soundRecorder.closeRecorder();
}
///申请权限
void requestPermission(Permission permission) async {
PermissionStatus status = await permission.request();
if (status.isPermanentlyDenied) {
|
934e2b47
liangchengyou
feat:权限调整+课程进度接口对接
|
344
|
showDialog();
|
9efff6ae
liangchengyou
feat:视频跟读逻辑修改
|
345
346
|
}
}
|
934e2b47
liangchengyou
feat:权限调整+课程进度接口对接
|
347
348
|
void showDialog() async {
|
3840b7fe
liangchengyou
feat:更新设置页面
|
349
|
showTwoActionDialog('提示', '取消', '去设置', '请进入设置页打开麦克风权限', leftTap: (){
|
934e2b47
liangchengyou
feat:权限调整+课程进度接口对接
|
350
|
popPage();
|
3840b7fe
liangchengyou
feat:更新设置页面
|
351
|
},rightTap: (){
|
934e2b47
liangchengyou
feat:权限调整+课程进度接口对接
|
352
353
354
355
|
popPage();
openAppSettings();
});
}
|
cb38bc90
liangchengyou
feat:视频跟读逻辑处理
|
356
|
}
|