在flutter_foreground_task中使用flutter_sound时出现MissingPluginException

问题描述 投票:0回答:1

我必须实现一项功能,即通过

flutter_sound
包的
FlutterSoundRecorder
录制用户的麦克风,并立即通过适用于 Android 和 iOS 的
FlutterSoundPlayer
的扬声器进行回放。我已经完成了这部分任务。

录制和立即播放必须在单独的隔离中进行,并且即使在应用程序最小化/未聚焦以及智能手机进入睡眠模式时也应继续进行。

这个套餐

flutter_foreground_task
看起来非常有前途。我尝试运行它的示例,效果完美。

当我尝试将

flutter_sound
代码移至由
flutter_foreground_task
包创建的隔离区并开始运行应用程序时,问题就开始了:

MissingPluginException (MissingPluginException(No implementation found for method resetPlugin on channel xyz.canardoux.flutter_sound_player))

我尝试了

flutter clean
以及
flutter pub cache clean
并在添加所有依赖项后重建项目。我尝试了
WidgetsFlutterBinding.ensureInitialized()
DartPluginRegistrant.ensureInitialized()
,但没有成功。

我知道在另一个隔离区中运行代码以及在构建过程中仅通过

@pragma('vm:entry-point')
不被忽略的代码可能会在执行其中的本机代码或插件时造成麻烦。

如何解决这个异常?目前我只针对Android进行了测试,iOS会是稍后的问题。

要重现它,请生成一个新的空 flutter 项目,并根据

官方 flutter_foreground_task 示例
(我可以根据要求进一步缩短)将 main.dart 内容替换为以下代码。相应的
AndroidManifest.xml
位于代码下方。另外,将以下三个包添加到项目的
pubspec.yaml
中:
flutter_foreground_task
flutter_sound
permission_handler

import 'dart:async';
import 'dart:io';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:permission_handler/permission_handler.dart';

void main() {
  FlutterForegroundTask.initCommunicationPort();
  runApp(const ExampleApp());
}

@pragma('vm:entry-point')
void startCallback() {
  FlutterForegroundTask.setTaskHandler(MyTaskHandler());
}

class MyTaskHandler extends TaskHandler {
  static const String incrementCountCommand = 'incrementCount';

  int _count = 0;

  FlutterSoundRecorder _recorder = FlutterSoundRecorder();
  FlutterSoundPlayer _player = FlutterSoundPlayer();

  final StreamController<Uint8List> _streamController =
      StreamController<Uint8List>.broadcast();

  void _incrementCount() {
    _count++;

    FlutterForegroundTask.updateService(
      notificationTitle: 'Hello MyTaskHandler :)',
      notificationText: 'count: $_count',
    );

    FlutterForegroundTask.sendDataToMain(_count);
  }

  @override
  void onStart(DateTime timestamp) async {
    print('onStart');
    _incrementCount();
    await _player.openPlayer();
    await _player.setVolume(1.0);
    await _recorder.openRecorder();
    _streamController.stream.listen((Uint8List food) {
      if (_player.isPlaying) {
        _player.foodSink!.add(FoodData(food));
      }
    });
    await _player.startPlayerFromStream(
      sampleRate: 44100,
      whenFinished: _streamController.close,
    );
    await _recorder.startRecorder(
      toStream: _streamController.sink,
      codec: Codec.pcm16,
      sampleRate: 44100,
    );
  }

  @override
  void onRepeatEvent(DateTime timestamp) {
    _incrementCount();
  }

  @override
  void onDestroy(DateTime timestamp) async {
    print('onDestroy');
    await _player.stopPlayer();
    await _player.closePlayer();

    await _recorder.stopRecorder();
    await _recorder.closeRecorder();

    await _streamController.close();
  }

  @override
  void onReceiveData(Object data) {
    print('onReceiveData: $data');
    if (data == incrementCountCommand) {
      _incrementCount();
    }
  }

  @override
  void onNotificationButtonPressed(String id) {
    print('onNotificationButtonPressed: $id');
  }

  @override
  void onNotificationPressed() {
    FlutterForegroundTask.launchApp('/');
    print('onNotificationPressed');
  }

  @override
  void onNotificationDismissed() {
    print('onNotificationDismissed');
  }
}

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {
        '/': (context) => const ExamplePage(),
      },
      initialRoute: '/',
    );
  }
}

class ExamplePage extends StatefulWidget {
  const ExamplePage({super.key});

  @override
  State<StatefulWidget> createState() => _ExamplePageState();
}

class _ExamplePageState extends State<ExamplePage> {
  final ValueNotifier<Object?> _receivedTaskData = ValueNotifier(null);

  Future<void> _requestPermissions() async {
    final NotificationPermission notificationPermission =
        await FlutterForegroundTask.checkNotificationPermission();
    if (notificationPermission != NotificationPermission.granted) {
      await FlutterForegroundTask.requestNotificationPermission();
    }
    //microphone permission
    final PermissionStatus microphonePermission =
        await Permission.microphone.status;

    if (Platform.isAndroid) {
      if (!await FlutterForegroundTask.canDrawOverlays) {
        await FlutterForegroundTask.openSystemAlertWindowSettings();
      }

      if (!await FlutterForegroundTask.isIgnoringBatteryOptimizations) {
        await FlutterForegroundTask.requestIgnoreBatteryOptimization();
      }

      if (!await FlutterForegroundTask.canScheduleExactAlarms) {
        await FlutterForegroundTask.openAlarmsAndRemindersSettings();
      }
    }
  }

  void _initService() {
    FlutterForegroundTask.init(
      androidNotificationOptions: AndroidNotificationOptions(
        channelId: 'foreground_service',
        channelName: 'Foreground Service Notification',
        channelDescription:
            'This notification appears when the foreground service is running.',
        channelImportance: NotificationChannelImportance.LOW,
        priority: NotificationPriority.LOW,
      ),
      iosNotificationOptions: const IOSNotificationOptions(
        showNotification: false,
        playSound: false,
      ),
      foregroundTaskOptions: ForegroundTaskOptions(
        eventAction: ForegroundTaskEventAction.repeat(5000),
        autoRunOnBoot: true,
        autoRunOnMyPackageReplaced: true,
        allowWakeLock: true,
        allowWifiLock: true,
      ),
    );
  }

  Future<ServiceRequestResult> _startService() async {
    if (await FlutterForegroundTask.isRunningService) {
      return FlutterForegroundTask.restartService();
    } else {
      return FlutterForegroundTask.startService(
        serviceId: 256,
        notificationTitle: 'Foreground Service is running',
        notificationText: 'Tap to return to the app',
        notificationIcon: null,
        notificationButtons: [
          const NotificationButton(id: 'btn_hello', text: 'hello'),
        ],
        callback: startCallback,
      );
    }
  }

  Future<ServiceRequestResult> _stopService() async {
    return FlutterForegroundTask.stopService();
  }

  void _onReceiveTaskData(Object data) {
    print('onReceiveTaskData: $data');
    _receivedTaskData.value = data;
  }

  void _incrementCount() {
    FlutterForegroundTask.sendDataToTask(MyTaskHandler.incrementCountCommand);
  }

  @override
  void initState() {
    super.initState();
    FlutterForegroundTask.addTaskDataCallback(_onReceiveTaskData);

    WidgetsBinding.instance.addPostFrameCallback((_) {
      _requestPermissions();
      _initService();
    });
  }

  @override
  void dispose() {
    FlutterForegroundTask.removeTaskDataCallback(_onReceiveTaskData);
    _receivedTaskData.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return WithForegroundTask(
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Flutter Foreground Task'),
          centerTitle: true,
        ),
        body: _buildContentView(),
      ),
    );
  }

  Widget _buildContentView() {
    return Column(
      children: [
        Expanded(child: _buildCommunicationText()),
        _buildServiceControlButtons(),
      ],
    );
  }

  Widget _buildCommunicationText() {
    return ValueListenableBuilder(
      valueListenable: _receivedTaskData,
      builder: (context, data, _) {
        return Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              const Text('You received data from TaskHandler:'),
              Text('$data', style: Theme.of(context).textTheme.headlineMedium),
            ],
          ),
        );
      },
    );
  }

  Widget _buildServiceControlButtons() {
    buttonBuilder(String text, {VoidCallback? onPressed}) {
      return ElevatedButton(
        onPressed: onPressed,
        child: Text(text),
      );
    }

    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          buttonBuilder('start service', onPressed: _startService),
          buttonBuilder('stop service', onPressed: _stopService),
          buttonBuilder('increment count', onPressed: _incrementCount),
        ],
      ),
    );
  }
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
    <!-- required -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <!-- foregroundServiceType: dataSync -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
    <!-- foregroundServiceType: remoteMessaging -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
    <application
        tools:replace="android:icon, android:label"
        android:label="flutter_isolate_test"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <!-- Warning: Do not change service name. -->
        <service 
            android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
            android:foregroundServiceType="dataSync|remoteMessaging"
            android:exported="false" />
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:taskAffinity=""
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
    <!-- Required to query activities that can process text, see:
         https://developer.android.com/training/package-visibility and
         https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.

         In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
    <queries>
        <intent>
            <action android:name="android.intent.action.PROCESS_TEXT"/>
            <data android:mimeType="text/plain"/>
        </intent>
    </queries>
</manifest>
android flutter dart foreground-service isolate
1个回答
0
投票

你找到解决办法了吗?

© www.soinside.com 2019 - 2024. All rights reserved.