inway / flutter_ringtone_player

Simple Flutter plugin to play ringtone, alarm & notification sounds
MIT License
89 stars 57 forks source link

Can't stop the alarm with FlutterRingtonePlayer.stop() after killing the app. #7

Closed nhimrmh closed 1 year ago

nhimrmh commented 4 years ago

I'm currently using flutter_ringtone_player package to implement an Alarm application on mobile. I have AlarmManager to help schedule the time to fire FlutterRingtonePlayer.play() when time comes. If i play and stop the alarm without closing my app then everything works fine, but after closing (killing) my app, the command FlutterRingtonePlayer.stop() on the previous scheduled alarm no longer works when the app is reopened. How can i resolve this? Thank you.

JohnPang1204 commented 4 years ago

I also facing this issues. Is there any workaround?

LaxmikanthMadhyastha commented 3 years ago

Yes, even I am facing this issue, I'm using this package to play the ringtone whenever I receive a firebase push notification even when the app is in the background, and when the app is resumed I'm calling FlutterRingtonePlayer.stop() which is not stopping the ringtone from playing.

Bharavi26 commented 3 years ago

@LaxmikanthMadhyastha sir, have you found solutiontion for this ? if yes then can you please share solution ? i also want to play ringtone when app is in background..thank you

nhimrmh commented 3 years ago

Unfortunately, i haven't found any solutions around for this. I think we can only wait for FlutterRingtonePlayer package to be updated as they fix this.

Bharavi26 commented 3 years ago

@nhimrmh sir, i am using FlutterRingtonePlayer.playRingtone() in BackgroundMessageHandler of notification whenever i receive notificaiton then ringtone generate MissingPluginException.

Unhandled Exception: MissingPluginException(No implementation found for method play on channel flutter_ringtone_player) have you idea about this error..can you please guide me ?

ringtoneplayer

nhimrmh commented 3 years ago

@Bharavi26 sorry but I can't help you with this because I stopped using this package for a long time since there's no other ways to work around the bug of not capable to stop the ringtone in background, and I haven't been through your kind of exception yet.

benyamin218118 commented 3 years ago

in your flutter_project/android/app/src/main/your.package.name/Application.java | MainActivity.java add this import :

import io.inway.ringtone.player.FlutterRingtonePlayerPlugin;

then add this line in the overrided registerWith function: FlutterRingtonePlayerPlugin.registerWith(registry.registrarFor("io.inway.ringtone.player.FlutterRingtonePlayerPlugin"));

your problem should be solved

liquidiert commented 2 years ago

Hey @Bharavi26, @nhimrmh I used port communication between the isolates:

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

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

And then whenever I want to stop it:

IsolateNameServer.lookupPortByName(portName)?.send("stop");

@Bharavi26 regarding the exception are you sure flutter is intialized correctly and your backgroundhandler is a top-level function?

maheshmnj commented 2 years ago

@liquidiert thank you so much that worked like a charm :)

For anyone who would stumble upon this issue, Here's the complete code sample. Where I am trying to stop the audio on tapping notification (The audio was played while the app was terminated).

code sample ```dart // Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // @dart=2.9 import 'dart:async'; import 'dart:convert'; import 'dart:isolate'; import 'dart:ui'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_ringtone_player/flutter_ringtone_player.dart'; import 'package:http/http.dart' as http; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'message.dart'; import 'message_list.dart'; import 'permissions.dart'; import 'token_monitor.dart'; AndroidNotificationChannel channel; FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; const String isolateName = 'isolate'; /// Define a top-level named handler which background/terminated messages will /// call. /// /// To verify things are working, check out the native platform logs. Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { // If you're going to use other Firebase services in the background, such as Firestore, // make sure you call `initializeApp` before using other Firebase services. ReceivePort receiver = ReceivePort(); IsolateNameServer.registerPortWithName(receiver.sendPort, isolateName); receiver.listen((message) async { if (message == "stop") { await FlutterRingtonePlayer.stop(); } }); playAudio(); await Firebase.initializeApp(); print('Handling a background message ${message.messageId}'); } Future main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); // Set the background messaging handler early on, as a named top-level function FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); if (!kIsWeb) { /// Create a [AndroidNotificationChannel] for heads up notifications channel = AndroidNotificationChannel( 'high_importance_channel', // id 'High Importance Notifications', // title description: 'This channel is used for important notifications.', // description importance: Importance.high, ); /// Initialize the [FlutterLocalNotificationsPlugin] package. flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); /// Create an Android Notification Channel. /// /// We use this channel in the `AndroidManifest.xml` file to override the /// default FCM channel to enable heads up notifications. await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(channel); /// Update the iOS foreground notification presentation options to allow /// heads up notifications. await FirebaseMessaging.instance .setForegroundNotificationPresentationOptions( alert: true, badge: true, sound: true, ); } runApp(MessagingExampleApp()); } /// Entry point for the example application. class MessagingExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Messaging Example App', theme: ThemeData.dark(), routes: { '/': (context) => Application(), '/message': (context) => MessageView(), }, ); } } // Crude counter to make messages unique int _messageCount = 0; /// The API endpoint here accepts a raw FCM payload for demonstration purposes. String constructFCMPayload(String token) { _messageCount++; return jsonEncode({ 'to': token, 'data': { 'via': 'FlutterFire Cloud Messaging!!!', 'count': _messageCount.toString(), }, 'notification': { 'title': 'Hello FlutterFire!', 'body': 'This notification (#$_messageCount) was created via FCM!', }, }); } void playAudio() { FlutterRingtonePlayer.play( android: AndroidSounds.ringtone, ios: const IosSound(1023), looping: false, volume: 0.8, ); } Future stopAudio() async { IsolateNameServer.lookupPortByName(isolateName)?.send("stop"); await FlutterRingtonePlayer.stop(); } /// Renders the example application. class Application extends StatefulWidget { @override State createState() => _Application(); } class _Application extends State { String _token; @override void initState() { super.initState(); FirebaseMessaging.instance .getInitialMessage() .then((RemoteMessage message) { if (message != null) { print("Got initial message $message"); stopAudio(); // Navigator.pushNamed(context, '/message', // arguments: MessageArguments(message, true)); } }); FirebaseMessaging.onMessage.listen((RemoteMessage message) { RemoteNotification notification = message.notification; AndroidNotification android = message.notification?.android; print('A new onMessage event was published!'); if (notification != null && android != null && !kIsWeb) { flutterLocalNotificationsPlugin.show( notification.hashCode, notification.title, notification.body, NotificationDetails( android: AndroidNotificationDetails( channel.id, channel.name, channelDescription: channel.description, // TODO add a proper drawable resource to android, for now using // one that already exists in example app. icon: 'launch_background', ), )); } }); FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { print('App launched!'); stopAudio(); }); } Future sendPushMessage() async { if (_token == null) { print('Unable to send FCM message, no token exists.'); return; } try { var resp = json.decode((await http.post( Uri.parse('https://fcm.googleapis.com/fcm/send'), headers: { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': 'key=DTo77u8SzdrrwPHy-qF1bxBgpfdd' }, body: constructFCMPayload(_token), )) .body); print(resp); if (resp['failure'] == 1) { print( "Failed to send to $_token due to ${resp['results'][0]['error']}"); } } catch (e) { print(e); } } Future onActionSelected(String value) async { switch (value) { case 'subscribe': { print( 'FlutterFire Messaging Example: Subscribing to topic "fcm_test".'); await FirebaseMessaging.instance.subscribeToTopic('fcm_test'); print( 'FlutterFire Messaging Example: Subscribing to topic "fcm_test" successful.'); } break; case 'unsubscribe': { print( 'FlutterFire Messaging Example: Unsubscribing from topic "fcm_test".'); await FirebaseMessaging.instance.unsubscribeFromTopic('fcm_test'); print( 'FlutterFire Messaging Example: Unsubscribing from topic "fcm_test" successful.'); } break; case 'get_apns_token': { if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) { print('FlutterFire Messaging Example: Getting APNs token...'); String token = await FirebaseMessaging.instance.getAPNSToken(); print('FlutterFire Messaging Example: Got APNs token: $token'); } else { print( 'FlutterFire Messaging Example: Getting an APNs token is only supported on iOS and macOS platforms.'); } } break; default: break; } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Cloud Messaging'), actions: [ PopupMenuButton( onSelected: onActionSelected, itemBuilder: (BuildContext context) { return [ const PopupMenuItem( value: 'subscribe', child: Text('Subscribe to topic'), ), const PopupMenuItem( value: 'unsubscribe', child: Text('Unsubscribe to topic'), ), const PopupMenuItem( value: 'get_apns_token', child: Text('Get APNs token (Apple only)'), ), ]; }, ), ], ), floatingActionButton: Builder( builder: (context) => FloatingActionButton( onPressed: () { playAudio(); // FlutterRingtonePlayer.stop(); // sendPushMessage, }, backgroundColor: Colors.white, child: const Icon(Icons.send), ), ), body: SingleChildScrollView( child: Column( children: [ MetaCard('Permissions', Permissions()), MetaCard('FCM Token', TokenMonitor((token) { print('Token monitor set $_token'); _token = token; return Column( children: [ token == null ? const CircularProgressIndicator() : Text(token, style: const TextStyle(fontSize: 12)), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( onPressed: () async { var before = _token; print('Before: $_token'); var token; if (kIsWeb) { token = await FirebaseMessaging.instance.getToken( vapidKey: "BHOPmb4Gz7mSLtue-BSzZGOzIO1LTiHThiHl1_ZbUtRaj5PhHJhR2Isr9hGBH1gfw4jhcKJVTyDPneau8kdLyVw"); } else { token = await FirebaseMessaging.instance.getToken(); } _token = token; print('After: $_token'); if (_token == null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: const Text("No token!"))); return; } if (before == _token) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: const Text("Current token is valid!"))); } else { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: const Text("Got new token!"))); } }, icon: const Icon(Icons.refresh), tooltip: "Get token", ), if (_token != null) IconButton( onPressed: () { Clipboard.setData(ClipboardData(text: _token)); ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: const Text("Copied token to clipboard!"))); }, icon: const Icon(Icons.copy), tooltip: "Copy token to clipboard", ), if (_token != null) IconButton( onPressed: () async { await FirebaseMessaging.instance.deleteToken(); print('Deleted token $_token'); var token = await FirebaseMessaging.instance.getToken(); print("New token $token"); ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: const Text("Deleted token!"))); }, icon: const Icon(Icons.delete_forever), tooltip: "Delete token", ), ], ), ], ); })), MetaCard('Message Stream', MessageList()), ], ), ), ); } } /// UI Widget for displaying metadata. class MetaCard extends StatelessWidget { final String _title; final Widget _children; // ignore: public_member_api_docs MetaCard(this._title, this._children); @override Widget build(BuildContext context) { return Container( width: double.infinity, margin: const EdgeInsets.only(left: 8, right: 8, top: 8), child: Card( child: Padding( padding: const EdgeInsets.all(16), child: Column(children: [ Container( margin: const EdgeInsets.only(bottom: 16), child: Text(_title, style: const TextStyle(fontSize: 18))), _children, ])))); } } ```
github-actions[bot] commented 1 year ago

This issue has stalled.

github-actions[bot] commented 1 year ago

This issue has been closed due to inactivity.

pranavo72bex commented 1 year ago

Any update regarding this issue?

KawindaWAD commented 1 year ago

Hello,

I was facing the same issue with the ringtone not stopping when expected. After some debugging, I found that the issue was with the way the ringtone instance was being managed in the FlutterRingtonePlayerPlugin class. Here's the root cause and the solution:

Root Cause: When the play method is called, the plugin checks if a ringtone instance exists. If it does, it stops that instance and creates a new ringtone instance. However, when the stop method is called, the plugin stops the ringtone instance, but it does not nullify it. As a result, there is no persistent reference to the ringtone that's currently playing. So, when FlutterRingtonePlayer.stop() is called from the Flutter code, the plugin tries to stop the current ringtone. But if ringtone is null, nothing is stopped, and the ringtone continues to play.

Solution: To fix this, I made the ringtone instance static so it's shared across all instances of FlutterRingtonePlayerPlugin. I also added code to nullify the ringtone instance after stopping it in the stop method. This ensures that the correct ringtone is stopped when FlutterRingtonePlayer.stop() is called from the Flutter code.

Here's a link to my forked repository with the fix: Forked Repository

You can use this repository in your Flutter application by replacing the flutter_ringtone_player dependency in your pubspec.yaml file as follows:

dependencies:
  flutter:
    sdk: flutter

  flutter_ringtone_player:
    git:
      url: https://github.com/KawindaWAD/flutter_ringtone_player.git
SPodjasek commented 5 months ago

@maheshmnj It appears that you have an active Firebase secret included in code sample from your comment, please revoke it....

maheshmnj commented 5 months ago

@maheshmnj It appears that you have an active Firebase secret included in code sample from your comment, please revoke it....

Thanks @SPodjasek, However that secret was for a sample project and I removed it.

sunil-singh-chaudhary commented 2 months ago

https://stackoverflow.com/questions/78378655/setting-custom-ringtone-when-in-foreground-working-fine-but-in-background-not-wo/78389716#78389716. check this