flutter music keeps playing when I stop playing song with function and change navigation bar index

1,798

Solution 1

add: "with WidgetsBindingObserver"

class Screen extends StatefulWidget with WidgetsBindingObserver

and you'll get access to the App Lifecycle State:

void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.paused) {
      player.pause();
    }
  }

Solution 2

It sounds like you're trying to build a carousel slider. I would suggest instead of trying to build it you use something like the carousel_slider package. You should be able to just call play in the callback for onPageChanged.

However, getting back to your specific issue, I think the issue is that you're likely getting the page index and the globalSongIndex out of sync.

It seems like you have something like this globally:

  var audioIndex = 0;
  var audioFiles = [
    "1.mp3",
    "2.mp3",
    "3.mp3"
  ];

with a play function like this:

  Future<void> play() async {
    int result = await audioPlayer.play(audioFiles[audioIndex]);
  }

Then to ensure your gesture and your pageView on your pageView controller are in sync you need to make sure when you call the nextPage function on the PageController you also ensure your state variable is also updated to the nextPage value. I'm not exactly sure on how the specifics of Provider.of<Songs> works, but you likely need to force it to a specific value.

SliverChildBuilderDelegate((ctx, pageIndex) => GestureDetector(
    onPanUpdate: (details) async {
      if (details.delta.dx < 0) {
        _controller.nextPage(
            duration: Duration(milliseconds: 200),
            curve: Curves.easeInOut);
        await stop();

        setState(() {
          audioIndex = pageIndex;
          play();
        });
      }
    },
    child: Center(child: Text(audioFiles[audioIndex])))

Full working example:

import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  var audioPlayer = AudioPlayer(playerId: 'player');
  var audioIndex = 0;
  var audioFiles = [
    "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
    "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3",
    "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3"
  ];
  var _controller = PageController();

  @override
  void initState() {
    super.initState();

// starting player
    startPlayer();
  }

  startPlayer() {
    play();
  }

// Player play class set globalindex
  Future<void> play() async {
    int result = await audioPlayer.play(audioFiles[audioIndex]);
    if (result == 1) {
      // success
    }
  }

  // Stop song instance of player
  Future<void> stop() async {
    int result = await audioPlayer.stop();
    if (result == 1) {
      // success
    }
  }

  // disposing listener if not needed or users navigates away from
  @override
  void dispose() {
    super.dispose();
    audioPlayer.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: PageView.custom(
            dragStartBehavior: DragStartBehavior.start,
            controller: _controller,
            physics: NeverScrollableScrollPhysics(),
            scrollDirection: Axis.horizontal,
            childrenDelegate:
                SliverChildBuilderDelegate((ctx, pageIndex) => GestureDetector(
                    onPanUpdate: (details) async {
                      if (details.delta.dx < 0) {
                        _controller.nextPage(
                            duration: Duration(milliseconds: 200),
                            curve: Curves.easeInOut);
                        await stop();

                        setState(() {
                          audioIndex = pageIndex;
                          play();
                        });
                      }
                    },
                    child: Center(child: Text(audioFiles[audioIndex]))))));
  }
}
Share:
1,798
Marcel Dz
Author by

Marcel Dz

Updated on December 25, 2022

Comments

  • Marcel Dz
    Marcel Dz over 1 year

    I have a list containing music on each item. If I close the screen im changing the index of the currently located BottomNavigationPage and calling the stop function for stopping the audio, but exactly this doesnt work sometimes. If the song is in loading process or user is really fast, song will continue beeing played on different pages.

    Im using https://pub.dev/packages/audioplayers plugin.

    This is my minified code example full working demo: EDIT::

    So i followed up the hint of @Ringil providing a full working example with the actual issue im facing in here. This is my code:

    import 'package:audioplayers/audioplayers.dart';
    import 'package:flutter/gestures.dart';
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
    
      int currentIndex =0;
    
      @override
      Widget build(BuildContext context) {
    
        // Page selector for tab list
      void _selectPage(int index) {
        print('page index: $index');
        setState(() {
          currentIndex = index;
        });
      }
    
      // Routes list for tab navigation Android
      final List<Widget> _pages = [
        ScreenA(),
        ScreenB(func: _selectPage),
      ];
    
        return Scaffold(
          appBar: AppBar(),
          body: _pages[currentIndex],
          bottomNavigationBar: SafeArea(
            child: BottomNavigationBar(
              onTap: _selectPage,
              iconSize: 22,
              currentIndex: currentIndex,
              type: BottomNavigationBarType.fixed,
              items: [
                BottomNavigationBarItem(
                  backgroundColor: Theme.of(context).primaryColor,
                  icon: Icon(Icons.description),
                  label: 'ScreenA',
                ),
                BottomNavigationBarItem(
                    backgroundColor: Theme.of(context).primaryColor,
                    icon: Icon(Icons.ac_unit_outlined),
                    label: 'ScreenB'),
              ],
            ),
          ),
        );
      }
    }
    
    class ScreenA extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          child: Text('HOME'),
        );
      }
    }
    
    class ScreenB extends StatefulWidget {
      Function func;
      ScreenB({Key key, @required this.func}) : super(key: key);
      @override
      _ScreenBState createState() => _ScreenBState();
    }
    
    class _ScreenBState extends State<ScreenB> {
      var audioPlayer = AudioPlayer(playerId: 'player');
      var audioIndex = 0;
      var audioFiles = [
        "https://docs.google.com/uc?export=open&id=1SaJWqfQuHnFtL7uqrzfYG31hzOnqDM3r",
        "https://docs.google.com/uc?export=open&id=1FZkFMjQyWguAl0RMAsYDEZ07c_Qf7gjz",
        "https://docs.google.com/uc?export=open&id=1GqrwQ3eRuiil0p-Na_R1tMAvggp9YrbH",
      ];
      var _controller = PageController();
    
      @override
      void initState() {
        super.initState();
    
    // starting player
        startPlayer();
    
      }
    
      startPlayer() {
        play();
      }
    
    // Player play class set globalindex
      Future<void> play() async {
        int result = await audioPlayer.play(audioFiles[audioIndex]);
        if (result == 1) {
          // success
        }
      }
    
      // Stop song instance of player
      Future<void> stop() async {
        int result = await audioPlayer.stop();
        if (result == 1) {
          // success
        }
      }
    
      // disposing listener if not needed or users navigates away from
      @override
      void dispose() {
        super.dispose();
        audioPlayer.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            actions: [
              IconButton(
                icon: Icon(Icons.access_alarm_sharp),
                onPressed: () async {
                  await stop();
                  widget.func(0);
                },
              ),
            ],
          ),
          body: PageView.custom(
              dragStartBehavior: DragStartBehavior.start,
              controller: _controller,
              physics: NeverScrollableScrollPhysics(),
              scrollDirection: Axis.horizontal,
              childrenDelegate: SliverChildBuilderDelegate((ctx, pageIndex) =>
                  GestureDetector(
                      onPanUpdate: (details) async {
                        if (details.delta.dx < 0) {
                          _controller.nextPage(
                              duration: Duration(milliseconds: 200),
                              curve: Curves.easeInOut);
                          await stop();
    
                          setState(() {
                            audioIndex = pageIndex;
                            play();
                          });
                        }
                      },
                      child: Center(
                          child: Container(
                              width: 200,
                              height: 200,
                              color: Colors.red,
                              child: Text(audioFiles[audioIndex])))))),
        );
      }
    }
    

    And a short video showing the issue. If we go to the carousel screen, navigate threw songs and closing the screen before they have been loaded, they will be played on a different screen which I dont want. I also dont want to somehow "block" the user sliding threw the carousel until the song is loaded. Video: https://streamable.com/e/ycxwob

    Basically I shrinked my code up to the minimum possible. I took the code of Ringil because in his demo everything works perfectly fine. Then I added more and more code from me, until I noticed the error again. Now here we are. Basically there is a difference in the example audio files from hin and mine. My files arent big from filesize aspect but they seem to be longer, which gives us the delay Im expecting all the time. It seems like if the song isnt loaded before closing or changing the index, the error occurs.

  • Marcel Dz
    Marcel Dz over 3 years
    hello, thank you for your answer. if i move to a different navigation point in my app, the app lifecycle state wont be paused, as i read in the docs. paused is going to be triggered if you minimize the app. In my case Im in Screen A, then I move to Screen B by clicking on next bottom navigation point and there the music keeps playing on Screen B even though I called player.pause() before moving to different screen. But i wont minimize the app there.
  • Marcel Dz
    Marcel Dz over 3 years
    hello @Ringil and thank you very much for your code. Youre completely right my code looks nearly like you described. Now my problem, as you can see in my code user is able to call the IconButton any time with stop(); function. Now when I do this before the song is loaded, its not stopping as we would expect it. The result then is that the users "closed" or changed the page and the song didnt stop and is just going to be played on a different screen which shouldnt happen. What am I missing? Would appreciate if you can help me out.
  • Ringil
    Ringil over 3 years
    @MarcelDz It's unclear what navigationBarIndex.currentIndex = 0 is supposed to be doing. What does the 0 mean and does the audio widget use a different index?
  • Ringil
    Ringil over 3 years
    I suspect the other thing is you perhaps don't await the stop function.
  • Marcel Dz
    Marcel Dz over 3 years
    thank you for your answer. With navigationBarIndex.currentIndex = 0 im just navigating to the first BottomNavigationBar Item. We could also just use Navigator.of(context).pushnamed('page') instead. If I change the Iconbutton to await stop and Navigator and set it to async, it doesnt make any difference. Music will be played on different screen too which shouldnt happen, even if we await the stop function. (I can confirm im using exactly the same structure you have in the example, also im using same functions, still the same problem)
  • Marcel Dz
    Marcel Dz over 3 years
    If you place an Iconbutton in your example which will have the stop function and navigate to a different screen, you should be able to reproduce the error actually if you swipe in your carousel and then close it by pressing the Iconbutton
  • Marcel Dz
    Marcel Dz over 3 years
    I also tried to take your code 1:1 still having the same issue if we quit the Screen with Iconbutton awaiting the stop function and Navigating to different screen. And I placed a print inside dispose. It will be called if we close the screen with Iconbutton, but it doesnt work actually.
  • Ringil
    Ringil over 3 years
    When you say "different" screen, do you mean the first BottomNavigationBar item and this carousel audio player is on say the second item?
  • Marcel Dz
    Marcel Dz over 3 years
    Lets say we have Screen Home and Screen Carousel, now different screen is Screen Home then for example
  • Marcel Dz
    Marcel Dz over 3 years
  • Marcel Dz
    Marcel Dz over 3 years
    Good morning Ringil, check my message in our discussion
  • Marcel Dz
    Marcel Dz over 3 years
    the issue is because of my sound files. they are different from yours. If I take yours which end with.mp3 it works perfectly fine. If I use mine which are also mp3 but the path is something different (idk this is a stream or seomthing) it doesnt work. Can we "listen to the stream" and quit it somehow?
  • Ringil
    Ringil over 3 years
    As I mentioned in the chat, I have absolutely no issues with your given stream files and I provided video evidence of it. Are you sure it isn't an iOS only issue?