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 | 78 | } else { |
| 79 | 79 | config.oralType = .sentence |
| 80 | 80 | } |
| 81 | - config.oralType = .kidSent | |
| 81 | + config.oralType = .sentence | |
| 82 | 82 | config.userId = userId |
| 83 | 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 | 20 | child: GestureDetector( |
| 21 | 21 | onTap: (){ |
| 22 | 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 | 29 | child: Stack( |
| 31 | 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 | 5 | import 'package:flutter/cupertino.dart'; |
| 3 | 6 | import 'package:flutter/services.dart'; |
| 4 | 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 | 11 | import 'package:wow_english/common/request/dao/listen_dao.dart'; |
| 6 | 12 | import '../../../common/request/exception.dart'; |
| 7 | 13 | import '../../../models/read_content_entity.dart'; |
| ... | ... | @@ -23,47 +29,56 @@ enum VoiceRecordState { |
| 23 | 29 | voiceRecordEnd |
| 24 | 30 | } |
| 25 | 31 | |
| 26 | -enum VoicePlayState { | |
| 32 | +///先声测评状态 | |
| 33 | +enum XSVoiceCheckState { | |
| 27 | 34 | ///未知 |
| 28 | 35 | unKnow, |
| 29 | - ///播放中 | |
| 30 | - playing, | |
| 31 | - ///播放完成 | |
| 32 | - completed, | |
| 33 | - ///播放终止 | |
| 34 | - stop | |
| 36 | + ///测评开始 | |
| 37 | + start, | |
| 38 | + ///测评结束 | |
| 39 | + stop, | |
| 35 | 40 | } |
| 36 | 41 | |
| 37 | 42 | class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterContentState> { |
| 38 | 43 | |
| 39 | 44 | final String courseLessonId; |
| 40 | 45 | |
| 41 | - ///是否正在播放视频 | |
| 46 | + /// 是否正在播放视频 | |
| 42 | 47 | bool _videoPlaying = true; |
| 43 | - ///是否需要录音 | |
| 48 | + bool get videoPlaying => _videoPlaying; | |
| 49 | + /// 是否正在录音 | |
| 44 | 50 | bool _isRecord = false; |
| 45 | - | |
| 51 | + bool get isRecord => _isRecord; | |
| 52 | + /// 先声评测状态 | |
| 53 | + XSVoiceCheckState _xSCheckState = XSVoiceCheckState.unKnow; | |
| 54 | + XSVoiceCheckState get xSCheckState => _xSCheckState; | |
| 55 | + /// 评测结果 | |
| 46 | 56 | Map? _voiceTestResult; |
| 47 | - | |
| 57 | + Map? get voiceTestResult => _voiceTestResult; | |
| 58 | + /// 录音的次数 | |
| 48 | 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 | 68 | VoiceRecordState _voiceRecordState = VoiceRecordState.voiceRecordUnkonw; |
| 51 | - | |
| 52 | - bool get videoPlaying => _videoPlaying; | |
| 53 | - | |
| 54 | - bool get isRecord => _isRecord; | |
| 55 | - | |
| 56 | 69 | VoiceRecordState get voiceRecordState => _voiceRecordState; |
| 57 | 70 | |
| 71 | + /// 跟读内容数字 | |
| 58 | 72 | List<ReadContentEntity?>? _entityList; |
| 59 | - | |
| 60 | 73 | List<ReadContentEntity?>? get entityList => _entityList ; |
| 61 | 74 | |
| 62 | - Map? get voiceTestResult => _voiceTestResult; | |
| 63 | - | |
| 75 | + /// 方法 | |
| 64 | 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 | 83 | RepeatAfterContentBloc(this.courseLessonId) : super(RepeatAfterContentInitial()) { |
| 69 | 84 | on<VoiceRecordStateChangeEvent>(_voiceRecordStateChange); |
| ... | ... | @@ -71,60 +86,67 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo |
| 71 | 86 | on<ChangeVideoPlayIndexEvent>(_changeVideoPlayIndex); |
| 72 | 87 | on<VideoPlayChangeEvent>(_videoPlayStateChange); |
| 73 | 88 | on<RecordeVoicePlayEvent>(_recordeVoicePlay); |
| 89 | + on<StarRecordVoiceEvent>(_starRecordVoice); | |
| 90 | + on<StopRecordVoiceEvent>(_stopRecordVoice); | |
| 74 | 91 | on<XSVoiceResultEvent>(_voiceXsResult); |
| 75 | 92 | on<XSVoiceInitEvent>(_initVoiceSdk); |
| 76 | 93 | on<RequestDataEvent>(_requestData); |
| 77 | 94 | on<XSVoiceTestEvent>(_voiceXsTest); |
| 78 | 95 | on<XSVoiceStopEvent>(_voiceXsStop); |
| 79 | 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 | 199 | |
| 178 | 200 | ///先声测试 |
| 179 | 201 | void _voiceXsTest(XSVoiceTestEvent event,Emitter<RepeatAfterContentState> emitter) async { |
| 180 | - await audioPlayer.stop(); | |
| 181 | 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 | 211 | _recordNumber++; |
| 212 | + _xSCheckState = XSVoiceCheckState.start; | |
| 186 | 213 | emitter(XSVoiceTestState()); |
| 187 | 214 | } |
| 188 | 215 | |
| ... | ... | @@ -196,21 +223,116 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo |
| 196 | 223 | final Map args = event.message as Map; |
| 197 | 224 | final result = args['result'] as Map; |
| 198 | 225 | final overall = result['overall'].toString(); |
| 199 | - final audioUrl = args['audioUrl'].toString(); | |
| 200 | - _voiceTestResult = {'overall':overall,'audioUrl':audioUrl}; | |
| 226 | + _voiceTestResult = {'overall':overall}; | |
| 201 | 227 | emitter(XSVoiceTestState()); |
| 202 | 228 | } |
| 203 | 229 | |
| 204 | 230 | ///播放声音 |
| 205 | 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 | 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 | 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 | 6 | class InitBlocEvent extends RepeatAfterContentEvent {} |
| 7 | 7 | |
| 8 | 8 | class VideoPlayChangeEvent extends RepeatAfterContentEvent {} |
| 9 | - | |
| 9 | +///切换录音状态 | |
| 10 | 10 | class VoiceRecordEvent extends RepeatAfterContentEvent {} |
| 11 | - | |
| 11 | +///请求数据 | |
| 12 | 12 | class RequestDataEvent extends RepeatAfterContentEvent {} |
| 13 | 13 | |
| 14 | 14 | class VoiceRecordStateChangeEvent extends RepeatAfterContentEvent { |
| ... | ... | @@ -39,13 +39,18 @@ class XSVoiceResultEvent extends RepeatAfterContentEvent { |
| 39 | 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 | 51 | class PostFollowReadContentEvent extends RepeatAfterContentEvent {} |
| 48 | 52 | |
| 53 | +///切换视频播放 | |
| 49 | 54 | class ChangeVideoPlayIndexEvent extends RepeatAfterContentEvent { |
| 50 | 55 | final bool isNext; |
| 51 | 56 | ChangeVideoPlayIndexEvent(this.isNext); | ... | ... |
lib/pages/repeataftercontent/repeat_after_content_page.dart
| ... | ... | @@ -7,6 +7,7 @@ import 'package:wow_english/route/route.dart'; |
| 7 | 7 | |
| 8 | 8 | import '../../common/core/app_consts.dart'; |
| 9 | 9 | import '../../common/core/user_util.dart'; |
| 10 | +import '../../models/read_content_entity.dart'; | |
| 10 | 11 | import '../../utils/toast_util.dart'; |
| 11 | 12 | import 'widgets/repeat_video_widget.dart'; |
| 12 | 13 | |
| ... | ... | @@ -39,7 +40,14 @@ class _RepeatAfterContentPage extends StatelessWidget { |
| 39 | 40 | Widget build(BuildContext context) { |
| 40 | 41 | return BlocListener<RepeatAfterContentBloc,RepeatAfterContentState>( |
| 41 | 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 | 52 | child: _repeatAfterContentView(), |
| 45 | 53 | ); |
| ... | ... | @@ -248,7 +256,7 @@ class _RepeatAfterContentPage extends StatelessWidget { |
| 248 | 256 | mainAxisAlignment: MainAxisAlignment.end, |
| 249 | 257 | children: [ |
| 250 | 258 | Offstage( |
| 251 | - offstage: bloc.voiceRecordState != VoiceRecordState.voiceRecordEnd && voiceResult == null, | |
| 259 | + offstage:!(bloc.voiceRecordState == VoiceRecordState.voiceRecordEnd && bloc.xSCheckState == XSVoiceCheckState.stop), | |
| 252 | 260 | child: Column( |
| 253 | 261 | children: [ |
| 254 | 262 | Container( |
| ... | ... | @@ -270,9 +278,7 @@ class _RepeatAfterContentPage extends StatelessWidget { |
| 270 | 278 | ), |
| 271 | 279 | IconButton( |
| 272 | 280 | onPressed: (){ |
| 273 | - if(voiceResult != null) { | |
| 274 | - bloc.add(RecordeVoicePlayEvent(voiceResult['audioUrl']??'')); | |
| 275 | - } | |
| 281 | + bloc.add(RecordeVoicePlayEvent()); | |
| 276 | 282 | }, |
| 277 | 283 | icon: Image.asset( |
| 278 | 284 | 'voice_record_play'.assetPng, |
| ... | ... | @@ -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 | 314 | GestureDetector( |
| 295 | 315 | onTap: () => bloc.add(VoiceRecordEvent()), |
| 296 | - onLongPress: () { | |
| 297 | - bloc.add(XSVoiceTestEvent(bloc.entityList?.first?.word??'', '0', UserUtil.getUser()!.id.toString())); | |
| 298 | - }, | |
| 299 | 316 | onLongPressStart: (LongPressStartDetails details) { |
| 300 | - bloc.add(VoiceRecordStateChangeEvent(VoiceRecordState.voiceRecordStat)); | |
| 317 | + ///开始录音 | |
| 318 | + bloc.add(StarRecordVoiceEvent()); | |
| 301 | 319 | }, |
| 302 | 320 | onLongPressEnd: (LongPressEndDetails details) { |
| 303 | - bloc.add(VoiceRecordStateChangeEvent(VoiceRecordState.voiceRecordEnd)); | |
| 304 | - }, | |
| 305 | - onLongPressUp: () { | |
| 306 | - | |
| 321 | + ///结束录音 | |
| 322 | + bloc.add(StopRecordVoiceEvent()); | |
| 307 | 323 | }, |
| 308 | 324 | child: Image.asset( |
| 309 | 325 | 'video_record'.assetPng, | ... | ... |
pubspec.yaml
| ... | ... | @@ -95,6 +95,8 @@ dependencies: |
| 95 | 95 | audioplayers: ^4.1.0 |
| 96 | 96 | # 语音录制 https://pub.dev/packages/flutter_sound |
| 97 | 97 | flutter_sound: ^9.2.13 |
| 98 | + # 音频播放 https://pub.dev/packages/audio_session | |
| 99 | + audio_session: ^0.1.16 | |
| 98 | 100 | # 文件管理 https://pub.dev/packages/path_provider |
| 99 | 101 | path_provider: ^2.0.15 |
| 100 | 102 | # 阿里云oss https://pub.dev/packages/flutter_oss_aliyun | ... | ... |