Flutter - 如何从后台任务播放铃声并让用户能够停止它?

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

我想在我的 flutter 应用程序在后台时播放铃声,并让用户停止它。

我想在用户选择的特定时间间隔(例如3分钟或58分钟)后播放铃声,然后用户应该能够停止铃声。当 flutter 应用程序位于前台时,此功能有效,但当应用程序位于后台时(例如,当屏幕关闭时)则无效。

我目前正在使用 Flutter Workmanager 插件的回调Dispatcher,它会创建一个新的 Isolate。我们称之为 isolate B,以区别于 flutter 启动我的应用程序时创建的 isolate A

这是回调调度程序:

void callbackDispatcher() {
  print('callbackDispatcher');

    FlutterRingtonePlayer.play(
      android: AndroidSounds.ringtone,
      ios: IosSounds.glass,
      looping: false,
      // Android only - API >= 28
      volume: 1,
      // Android only - API >= 28
      asAlarm: true, // Android only - all APIs
    );

    return Future.value(true);
  });
}

问题是我可以开始播放隔离 B 中的铃声,但我不知道如何让用户停止它。

到目前为止我已经尝试过:

  • 单击通知时从隔离 A 调用 FlutterRingtonePlayer.stop(),但这不会停止隔离 B 中正在播放的铃声。
  • 从隔离区 B 开始通知,然后单击后停止隔离区 B 中的铃声。但由于某种原因,在隔离 B 中创建通知时不会弹出通知。
  • 从隔离区 A 调用 FlutterRingtonePlayer.stop(),但这不会停止隔离区 B 中正在播放的铃声。
  • 使用播放和停止铃声的方法创建一个全局类,并在隔离B中使用它来播放铃声,并在隔离A中使用它来停止铃声,但这也不起作用...

我不知道如何与 Workmanager 创建的 Isolate B 进行通信,而且他们的文档似乎没有指定如何与创建的新 Isolate 进行通信...

我还能尝试什么?我唯一的目标是在后台模式下播放铃声并让用户停止它。

flutter background
4个回答
3
投票

我有同样的问题(但是,使用 android 警报管理器包而不是 workmanager - 但是,我相信这个概念仍然是相同的)。我可以按如下方式解决。

隔离就像线程,但不共享内存 - 从这个意义上说,它们类似于 Linux/Unix 进程。因此,FlutterRingtonePlayer 内部的全局/静态在两个隔离中并不相同 - 它们是两个不同的实例。因此,我们需要使用消息来在隔离体之间进行通信。

我们可以使用 ReceivePort 和 SendPort 在两个隔离区之间进行通信。我们需要在后台isolate中运行的回调中定义一个ReceivePort(dart:isolate包)。我们需要在 IsolateNameServer (dart:ui) 中注册与该接收端口 (.sendPort) 相对应的发送端口。然后,我们需要在后台隔离中监听该 ReceivePort。从前台隔离(当用户单击示例中的通知时),我们可以使用 IsolateNameServer.lookup 查找 SendPort 并向后台隔离发送消息。当该消息在后台隔离中处理时,我们可以执行 FlutterRingtonePlayer.stop()。


3
投票

我能够通过使用端口通信在隔离区之间发送消息来解决此问题。

监听端口消息可能采用

main
方法

final String portName = 'myUniquePortName';

ReceivePort receiver = ReceivePort();
  IsolateNameServer.registerPortWithName(receiver.sendPort, portName);

  receiver.listen((message) async {
    if (message == "stop") {
      await FlutterRingtonePlayer.stop() 
    }
  });

使用

stopAudio
方法向 Isolate 发送停止消息。

Future<void> stopAudio() async {
  IsolateNameServer.lookupPortByName(isolateName)?.send("stop");
  await FlutterRingtonePlayer.stop();
}

最初在这里回答:https://github.com/inway/flutter_ringtone_player/issues/7#issuecomment-1012971949


0
投票

我也是,我也遇到了 Android 警报管理器和音频播放器的问题,但我像这样修复了它:

    stopTheSound() async {
    await AndroidAlarmManager.oneShot(Duration(seconds: 0), 4, stopSound,
        exact: true, wakeup: true, alarmClock: true, allowWhileIdle: true);
  }
  
  static stopSound() async {
    if (MyApp.isPlaying) {
      var res = await audioPlugin.stop();
      if (res == 1) {
        MyApp.isPlaying = false;
      }
    }
  }

我使用警报管理器来调用声音,所以我再次使用它来停止它。


0
投票

要停止声音,请使用以下代码

import 'dart:developer' as developer;
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
import 'package:flutter_ringtone_player/flutter_ringtone_player.dart';
import 'package:flutter/material.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(const AlarmManagerExampleApp());
}

class AlarmManagerExampleApp extends StatelessWidget {
  const AlarmManagerExampleApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        useMaterial3: true,
        colorSchemeSeed: const Color(0x9f4376f8),
      ),
      home: const _AlarmHomePage(),
    );
  }
}

class _AlarmHomePage extends StatefulWidget {
  const _AlarmHomePage({Key? key}) : super(key: key);

  @override
  _AlarmHomePageState createState() => _AlarmHomePageState();
}

class _AlarmHomePageState extends State<_AlarmHomePage> {
  int alarmId = 1;

  Future<void> scheduleAlarm() async {
    await AndroidAlarmManager.oneShot(
      const Duration(microseconds: 0),
      alarmId,
      callback,
      rescheduleOnReboot: true,
      exact: true,
      wakeup: true,
    );
  }

  Future<void> scheduleCancelAlarm() async {
    await AndroidAlarmManager.oneShot(
      const Duration(microseconds: 0),
      alarmId,
      stopcallback,
      rescheduleOnReboot: true,
      exact: true,
      wakeup: true,
    );
  }

  @pragma('vm:entry-point')
  static Future<void> callback() async {
    developer.log('Alarm fired!');
    FlutterRingtonePlayer.playAlarm(
      looping: true,
      asAlarm: false,
      volume: 1.0,
    );
  }

  @pragma('vm:entry-point')
  static Future<void> stopcallback() async {
    developer.log('Alarm stop!');
    AndroidAlarmManager.cancel(1);
    FlutterRingtonePlayer.stop();
  }

  @override
  Widget build(BuildContext context) {
    final textStyle = Theme.of(context).textTheme.bodySmall;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Android alarm manager plus example'),
        elevation: 4,
        actions: [
          IconButton(
            onPressed: () {
              scheduleCancelAlarm();
            },
            icon: const Icon(Icons.stop),
          ),
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Alarm fired  times',
              style: textStyle,
            ),
            const SizedBox(height: 24),
            ElevatedButton(
              onPressed: () async {
                await scheduleAlarm();
              },
              child: const Text('Schedule OneShot Alarm'),
            ),
          ],
        ),
      ),
    );
  }
}

这是完整代码

使用2插件

 android_alarm_manager_plus: ^3.0.1
 flutter_ringtone_player: ^3.2.0
© www.soinside.com 2019 - 2024. All rights reserved.