Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
nhimrmh opened this issue Mar 11, 2020 · 16 comments
Labels
help wanted Extra attention is needed stale

Comments

@nhimrmh
Copy link

nhimrmh commented Mar 11, 2020

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
Copy link

I also facing this issues. Is there any workaround?

@LaxmikanthMadhyastha
Copy link

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
Copy link

@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
Copy link
Author

nhimrmh commented Jan 1, 2021

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
Copy link

@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
Copy link
Author

nhimrmh commented Jan 2, 2021

@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
Copy link

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

@SPodjasek SPodjasek added the help wanted Extra attention is needed label Mar 25, 2021
@liquidiert
Copy link

liquidiert commented Jan 14, 2022

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
Copy link

maheshmnj commented Mar 26, 2022

@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
// 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<void> _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<void> 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<void> stopAudio() async {
  IsolateNameServer.lookupPortByName(isolateName)?.send("stop");
  await FlutterRingtonePlayer.stop();
}

/// Renders the example application.
class Application extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _Application();
}

class _Application extends State<Application> {
  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<void> 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: <String, String>{
          '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<void> 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: <Widget>[
          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
Copy link

github-actions bot commented Nov 8, 2022

This issue has stalled.

@github-actions github-actions bot added the stale label Nov 8, 2022
@github-actions
Copy link

github-actions bot commented Dec 9, 2022

This issue has been closed due to inactivity.

@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Dec 9, 2022
@pranavo72bex
Copy link

Any update regarding this issue?

@KawindaWAD
Copy link

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
Copy link
Member

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

@maheshmnj
Copy link

maheshmnj commented Jan 23, 2024

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed stale
Projects
None yet
Development

No branches or pull requests