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 cancel or skip AudioManager.instance functions or remove background activity #50

Closed
md186 opened this issue Oct 31, 2020 · 11 comments

Comments

@md186
Copy link

md186 commented Oct 31, 2020

Hey Jerome,

Im facing an issue for both android and ios where Im not able to move to the next item in my pageview as long as the AudioManager.instance.next function is part of the function tree. None of the AudioManager functions are skipable. They basically block the Pageview controlls.

As you will see in my demo, if I slide to the next element, its getting stuck completely, it doesnt let me override the previous action. Also If I press the close button with AudioManager.instance.stop function. It will either get stuck or it wont stop and song will be played on different screen.

This is another or combining issue. If you call the AudioManager.instance.stop(); function and move to a different screen, the song doesnt stop, it continues playing on a different screen. On iOS you can clearly see that this behaviour happens if you slide threw the items and close the screen. It doesnt get recognized anymore. If I put a print in a dispose tree, it will get printed but it doesnt dispose the Audio if you move to a different screen.

In the following im providing you a fully working demo with audio sources from my firebase project. You can test and reproduce the error there. Im also providing 2 videos, one with android from stackoverflow user, where it just looks fine and one from me with iOS, where you can clearly see that it starts playing audio even if we call AudioManager.instance.stop();

Videos:
Android: https://streamable.com/sepgb1
iOS: https://streamable.com/e/ycxwob

import 'package:audio_manager/audio_manager.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();
}

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 audioIndex = 0;

class _ScreenBState extends State<ScreenB> {

  var _controller = PageController();
  PlayMode playMode = AudioManager.instance.playMode;

  var globalIndex =0;

  @override
  void initState() {
    // TODO: implement initState
    
    List<AudioInfo> _list = [];
    for (var i = 0; i < audioFiles.length; i++) {
      _list.add(AudioInfo(audioFiles[i], coverUrl: '', desc: '', title: ''));
    }

    print(_list);

    AudioManager.instance.audioList = _list;
    AudioManager.instance.intercepter = true;
    AudioManager.instance.play(auto: true);

    super.initState();
  }


@override
void dispose() {
    // TODO: implement dispose
    AudioManager.instance.release();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: Icon(Icons.close),
            onPressed: () {
              AudioManager.instance.stop();
              widget.func(0);
            },
          ),
        ],
      ),
      body: PageView.custom(
          dragStartBehavior: DragStartBehavior.start,
          controller: _controller,
          physics: NeverScrollableScrollPhysics(),
          scrollDirection: Axis.horizontal,
          childrenDelegate: SliverChildBuilderDelegate((ctx, pageIndex) =>
              GestureDetector(
                  onPanUpdate: (details) {
                    if (details.delta.dx < 0) {
                      _controller.nextPage(
                          duration: Duration(milliseconds: 200),
                          curve: Curves.easeInOut);

                      setState(() {
                        //audioIndex = pageIndex;
                        AudioManager.instance.next();
                      });
                      
                    }
                  },
                  child: Center(
                      child: Container(
                          width: 200,
                          height: 200,
                          color: Colors.red,
                          child: Text(audioFiles[audioIndex])))))),
    );
  }
}

@jeromexiong
Copy link
Owner

jeromexiong commented Nov 1, 2020

If you want to play the whole app, using provider is a good choice. If you just want to play on a page, then calling the release method in the dispose method can clear all the playback information.

@md186
Copy link
Author

md186 commented Nov 1, 2020

Im using your plugin in the whole app, I just minifed code here so its not too complicated. The main problem still is that if Im using any of the functions inside Pageview or Iconbutton, it gets stuck and Pageview doesnt work smooth.

It seems like the functions are "more important" than the Pageview because it blocks the sliding. It has something to do with the functions, I tried different music plugins from pubdev for example audioplayers plugin to have a reference. This works fine. If I just slide threw Pageview or stop and close the screen, functions do what expected and they dont block the Pageview.

@jeromexiong
Copy link
Owner

setStatemethod called too many times in a short time

onPanUpdate: (details) {
  if (details.delta.dx < 0) {
    _controller.nextPage(
        duration: Duration(milliseconds: 200),
        curve: Curves.easeInOut);

    setState(() {
      //audioIndex = pageIndex;
      AudioManager.instance.next();
    });
    
  }
}

@md186
Copy link
Author

md186 commented Nov 1, 2020

Oh alright, you got my demo, could you show me a fix where it will be called only once?

@jeromexiong
Copy link
Owner

Add a timer to execute the last method

Timer _timer;
  _startTimer() {
    _cancelTimer();
    _timer = Timer.periodic(Duration(milliseconds: 200), (timer) {
      _cancelTimer();
      AudioManager.instance.next();
      setState(() {});
    });
  }

  _cancelTimer() {
    _timer?.cancel();
  }

then invoke that

_controller.nextPage(
                          duration: Duration(milliseconds: 200),
                          curve: Curves.easeInOut);

                      print("$pageIndex, ${AudioManager.instance.curIndex}");
                      _startTimer();

@md186
Copy link
Author

md186 commented Nov 2, 2020

Hey @jeromexiong great that you took a look to my code. It seems like this is fixed now, still need to test some cases but it works smoother now. THANK YOU FOR THIS!

EDIT::
It works fine but on real iOS device its not working... Could you check it yourself please?

@jeromexiong
Copy link
Owner

you can debug code step by step

@md186
Copy link
Author

md186 commented Nov 5, 2020

I managed to get a version where setState and the function is only called once, so we also don't need the timer anymore. But we still have the same problem with AudioManager.instance.next() and AudioManager.instance.stop. Its getting stuck and/or blocking the PageView sliding in the given direction. Even with one setState(). If remove the AudioManager.instance.next() or stop function, everything works smooth. Please check my updated code:

import 'package:audio_manager/audio_manager.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();
}

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 audioIndex = 0;

class _ScreenBState extends State<ScreenB> {

  //var _controller = PageController();
  PlayMode playMode = AudioManager.instance.playMode;

  var globalIndex =0;

  @override
  void initState() {
    // TODO: implement initState
    
    List<AudioInfo> _list = [];
    for (var i = 0; i < audioFiles.length; i++) {
      _list.add(AudioInfo(audioFiles[i], coverUrl: '', desc: '', title: ''));
    }

    print(_list);

    AudioManager.instance.audioList = _list;
    //AudioManager.instance.intercepter = true;
    AudioManager.instance.play(auto: true);

    super.initState();
  }


@override
void dispose() {
    // TODO: implement dispose
   // AudioManager.instance.release();
    super.dispose();
  }

  var lastPage =0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: Icon(Icons.close),
            onPressed: () {
              AudioManager.instance.stop();
              widget.func(0);
            },
          ),
        ],
      ),
      body: PageView.custom(
          onPageChanged: (page){
            if(page != lastPage){
              if(lastPage < page){
                print('swipe right');
                lastPage = page;
                setState(() {
                //audioIndex = page;
                AudioManager.instance.next();
                });
              } else{
                print('swipe left');
                lastPage = page;
              }
            }
          },
          //controller: _controller,
          physics: PageScrollPhysics(),
          scrollDirection: Axis.horizontal,
          childrenDelegate: SliverChildBuilderDelegate((ctx, pageIndex) =>
              // GestureDetector(
              //     onPanUpdate: (details) {
              //       if (details.delta.dx < 0) {
              //         _controller.nextPage(
              //             duration: Duration(milliseconds: 200),
              //             curve: Curves.easeInOut);

              //         setState(() {
              //           //audioIndex = pageIndex;
              //           AudioManager.instance.next();
              //         });
                      
              //       }
              //     },
                  //child: 
                  Center(
                      child: Container(
                          width: 200,
                          height: 200,
                          color: Colors.red,
                          child: Text(audioFiles[audioIndex]))))),
    );
  }
}

@md186
Copy link
Author

md186 commented Nov 5, 2020

Oh I got something for you. It has to do with the audio. My audio provided in the code example is from my firebase project. if we change the audio to this (audio files from internet with.mp3 ending):

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"
  ];

it works perfectly fine and we are able to swipe and stop the music without any blocking. I hope this helps you fixing the issue.

@jeromexiong
Copy link
Owner

yeah,the network in each region is different, users can replace it with their own URL as needed

@md186
Copy link
Author

md186 commented Nov 16, 2020

@jeromexiong what did you mean with users can replace it with their own url?

The files without mp3 ending are from my firebase project, this is handled in a different way in your Plugin. It’s not able to execute any AudioManager function in a correct way with this url type as you can see in the Demo.

But in my case music gets uploaded and you can play it from the url. This is a case which many people do have in their projects. That’s why we should fix that in your plugin making it working the same good way as with the mp3 files

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants