|
|
@@ -6,11 +6,15 @@ import 'package:electronic_assistant/data/consts/error_code.dart';
|
|
|
import 'package:electronic_assistant/data/repositories/talk_repository.dart';
|
|
|
import 'package:electronic_assistant/dialog/alert_dialog.dart';
|
|
|
import 'package:electronic_assistant/module/record/constants.dart';
|
|
|
+import 'package:electronic_assistant/module/record/record_task.dart';
|
|
|
import 'package:electronic_assistant/module/talk/view.dart';
|
|
|
+import 'package:electronic_assistant/resource/string.gen.dart';
|
|
|
import 'package:electronic_assistant/router/app_pages.dart';
|
|
|
import 'package:electronic_assistant/utils/http_handler.dart';
|
|
|
import 'package:electronic_assistant/utils/mmkv_util.dart';
|
|
|
import 'package:electronic_assistant/utils/toast_util.dart';
|
|
|
+import 'package:flutter/cupertino.dart';
|
|
|
+import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
|
|
import 'package:get/get.dart';
|
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
import 'package:record/record.dart';
|
|
|
@@ -28,11 +32,12 @@ class RecordController extends BaseController {
|
|
|
final Rx<RecordStatus> currentStatus = RecordStatus.pending.obs;
|
|
|
final RxDouble currentDuration = 0.0.obs;
|
|
|
final AudioRecorder _record = AudioRecorder();
|
|
|
- final RecordConfig _recordConfig = const RecordConfig(
|
|
|
- encoder: AudioEncoder.pcm16bits,
|
|
|
- bitRate: 128000,
|
|
|
- sampleRate: 44100,
|
|
|
- numChannels: 2);
|
|
|
+ final RecordConfig _recordConfig = RecordConfig(
|
|
|
+ encoder: AudioEncoder.pcm16bits,
|
|
|
+ bitRate: 16000,
|
|
|
+ sampleRate: SampleRate.rate32k.value,
|
|
|
+ numChannels: Channel.mono.value,
|
|
|
+ );
|
|
|
late final String _lastRecordId;
|
|
|
|
|
|
@override
|
|
|
@@ -40,6 +45,7 @@ class RecordController extends BaseController {
|
|
|
super.onInit();
|
|
|
_initLastRecordId();
|
|
|
_initLastRecordStatus();
|
|
|
+ _initForegroundService();
|
|
|
}
|
|
|
|
|
|
@override
|
|
|
@@ -68,6 +74,31 @@ class RecordController extends BaseController {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ _initForegroundService() {
|
|
|
+ WidgetsBinding.instance
|
|
|
+ .addPostFrameCallback((_) => FlutterForegroundTask.init(
|
|
|
+ androidNotificationOptions: AndroidNotificationOptions(
|
|
|
+ channelId: StringName.recordNotificationChannelId,
|
|
|
+ channelName: StringName.recordNotificationChannelName,
|
|
|
+ channelDescription:
|
|
|
+ StringName.recordNotificationChannelDescription,
|
|
|
+ channelImportance: NotificationChannelImportance.LOW,
|
|
|
+ priority: NotificationPriority.LOW,
|
|
|
+ ),
|
|
|
+ iosNotificationOptions: const IOSNotificationOptions(
|
|
|
+ showNotification: false,
|
|
|
+ playSound: false,
|
|
|
+ ),
|
|
|
+ foregroundTaskOptions: ForegroundTaskOptions(
|
|
|
+ eventAction: ForegroundTaskEventAction.once(),
|
|
|
+ autoRunOnBoot: false,
|
|
|
+ autoRunOnMyPackageReplaced: true,
|
|
|
+ allowWakeLock: true,
|
|
|
+ allowWifiLock: false,
|
|
|
+ ),
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
void addShortcut() {}
|
|
|
|
|
|
void onBackClick() {
|
|
|
@@ -131,10 +162,21 @@ class RecordController extends BaseController {
|
|
|
_onRecordPermissionDenied();
|
|
|
return;
|
|
|
}
|
|
|
+
|
|
|
+ await _requestForegroundTaskPermission().catchError((error) {
|
|
|
+ debugPrint("requestForegroundTaskPermission error: $error");
|
|
|
+ });
|
|
|
+
|
|
|
File targetFile = await _getCurrentRecordFile();
|
|
|
Stream<Uint8List> recordStream = await _record.startStream(_recordConfig);
|
|
|
- _changeRecordStatus(RecordStatus.recording);
|
|
|
+ _startForegroundService();
|
|
|
recordStream.listen((data) async {
|
|
|
+ if (data.isEmpty) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (currentStatus.value != RecordStatus.recording) {
|
|
|
+ _changeRecordStatus(RecordStatus.recording);
|
|
|
+ }
|
|
|
targetFile.writeAsBytesSync(data, mode: FileMode.append);
|
|
|
currentDuration.value = currentDuration.value +
|
|
|
_getPcmDuration(data.length, _recordConfig.sampleRate, 16,
|
|
|
@@ -151,7 +193,8 @@ class RecordController extends BaseController {
|
|
|
Future<void> _stopRecord() {
|
|
|
return _record
|
|
|
.pause()
|
|
|
- .then((_) => _changeRecordStatus(RecordStatus.paused));
|
|
|
+ .then((_) => _changeRecordStatus(RecordStatus.paused))
|
|
|
+ .then((_) => FlutterForegroundTask.stopService());
|
|
|
}
|
|
|
|
|
|
Future<File> _getCurrentRecordFile() async {
|
|
|
@@ -211,9 +254,7 @@ class RecordController extends BaseController {
|
|
|
ToastUtil.showToast("${error.message}");
|
|
|
if (error.code == ErrorCode.errorCodeNoLogin) {
|
|
|
Get.toNamed(RoutePath.login)?.then((loginSuccess) {
|
|
|
- loginSuccess != null && loginSuccess
|
|
|
- ? _saveCurrentRecord()
|
|
|
- : null;
|
|
|
+ loginSuccess != null && loginSuccess ? _saveCurrentRecord() : null;
|
|
|
});
|
|
|
}
|
|
|
} else {
|
|
|
@@ -227,6 +268,36 @@ class RecordController extends BaseController {
|
|
|
status == RecordStatus.recording ? frameAnimationController.play() : null;
|
|
|
}
|
|
|
|
|
|
+ Future<void> _requestForegroundTaskPermission() async {
|
|
|
+ final NotificationPermission notificationPermission =
|
|
|
+ await FlutterForegroundTask.checkNotificationPermission();
|
|
|
+ if (notificationPermission != NotificationPermission.granted) {
|
|
|
+ await FlutterForegroundTask.requestNotificationPermission();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Platform.isAndroid) {
|
|
|
+ if (!await FlutterForegroundTask.isIgnoringBatteryOptimizations) {
|
|
|
+ // This function requires `android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` permission.
|
|
|
+ await FlutterForegroundTask.requestIgnoreBatteryOptimization();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Future<ServiceRequestResult> _startForegroundService() async {
|
|
|
+ if (await FlutterForegroundTask.isRunningService) {
|
|
|
+ return FlutterForegroundTask.restartService();
|
|
|
+ } else {
|
|
|
+ return FlutterForegroundTask.startService(
|
|
|
+ serviceId: 256,
|
|
|
+ notificationTitle: StringName.appName,
|
|
|
+ notificationText: StringName.recordStatusRecording,
|
|
|
+ notificationIcon: null,
|
|
|
+ notificationButtons: [],
|
|
|
+ callback: setRecordCallback,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/// 获取录音文件地址
|
|
|
static Future<File> getRecordFile(String talkId) async {
|
|
|
Directory documentDir = await getApplicationDocumentsDirectory();
|