当我停止使用功能播放歌曲并更改导航栏索引时,颤动的音乐继续播放

Posted

技术标签:

【中文标题】当我停止使用功能播放歌曲并更改导航栏索引时,颤动的音乐继续播放【英文标题】:flutter music keeps playing when I stop playing song with function and change navigation bar index 【发布时间】:2021-02-07 06:47:33 【问题描述】:

我有一个包含每个项目的音乐的列表。如果我关闭屏幕,我会更改当前位于的BottomNavigationPageindex 并调用停止函数来停止音频,但这有时不起作用。如果歌曲正在加载过程中或用户速度非常快,歌曲将继续在不同的页面上播放。

我正在使用https://pub.dev/packages/audioplayers 插件。

这是我的缩小代码示例完整工作演示: 编辑::

所以我跟进了@Ringil 的提示,提供了一个完整的工作示例以及我在这里面临的实际问题。这是我的代码:

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])))))),
    );
  

还有一段短视频展示了这个问题。如果我们转到轮播屏幕,在加载之前浏览已播放的歌曲并关闭屏幕,它们将在我不想要的不同屏幕上播放。我也不想以某种方式“阻止”用户滑动扔旋转木马,直到加载歌曲。 视频:https://streamable.com/e/ycxwob

基本上,我将我的代码压缩到尽可能少的程度。我拿了 Ringil 的代码,因为在他的演示中一切都很好。然后我添加了越来越多的代码,直到我再次注意到错误。现在我们到了。基本上,来自 hin 和 mine 的示例音频文件有所不同。从文件大小方面来看,我的文件并不大,但它们似乎更长,这给了我们一直期待的延迟。似乎如果在关闭或更改索引之前未加载歌曲,则会发生错误。

【问题讨论】:

我认为总会有一些(轻微的)延迟。您希望音频关闭多快? 【参考方案1】:

听起来您正在尝试构建一个轮播滑块。我建议不要尝试构建它,而是使用 carousel_slider 包之类的东西。您应该可以在 onPageChanged 的回调中调用 play

但是,回到您的具体问题,我认为问题在于您可能使页面索引和 globalSongIndex 不同步。

您似乎在全球范围内有这样的事情:

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

具有这样的播放功能:

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

然后,为了确保您的手势和您的 pageView 控制器上的 pageView 同步,您需要确保当您在 PageController 上调用 nextPage 函数时,您还需要确保您的状态变量也更新为 nextPage价值。我不太确定 Provider.of&lt;Songs&gt; 的具体细节是如何工作的,但您可能需要将其强制为特定值。

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])))

完整的工作示例:

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

【讨论】:

你好@Ringil,非常感谢你的代码。你完全正确,我的代码看起来几乎就像你描述的那样。现在我的问题,正如您在我的代码中看到的,用户可以随时使用 stop() 调用 IconButton;功能。现在,当我在加载歌曲之前执行此操作时,它并没有像我们预期的那样停止。结果是用户“关闭”或更改了页面,歌曲没有停止,只是将在不同的屏幕上播放,这不应该发生。我错过了什么?如果您能帮助我,将不胜感激。 @MarcelDz 不清楚navigationBarIndex.currentIndex = 0 应该做什么。 0 是什么意思,音频小部件是否使用不同的索引? 我怀疑另一件事是你可能没有await stop 函数。 感谢您的回答。使用 navigationBarIndex.currentIndex = 0 我只是导航到第一个 BottomNavigationBar 项。我们也可以只使用 Navigator.of(context).pushnamed('page') 代替。如果我将 Iconbutton 更改为 await stop 和 Navigator 并将其设置为异步,则没有任何区别。即使我们等待停止功能,音乐也会在不同的屏幕上播放,这不应该发生。 (我可以确认我使用与示例中完全相同的结构,我也使用相同的功能,仍然是同样的问题) 如果您在示例中放置一个图标按钮,该按钮将具有停止功能并导航到不同的屏幕,如果您在轮播中滑动然后按关闭它,您应该能够实际重现错误图标按钮【参考方案2】:

添加:“与 WidgetsBindingObserver”

class Screen extends StatefulWidget with WidgetsBindingObserver

您将可以访问应用生命周期状态:

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

【讨论】:

您好,谢谢您的回答。如果我在我的应用程序中移动到不同的导航点,应用程序生命周期状态不会暂停,正如我在文档中阅读的那样。如果您最小化应用程序,将触发暂停。在我的情况下,我在屏幕 A 中,然后我通过单击下一个底部导航点移动到屏幕 B,即使我在移动到其他屏幕之前调用了 player.pause(),音乐仍然在屏幕 B 上播放。但我不会在那里最小化应用程序。

以上是关于当我停止使用功能播放歌曲并更改导航栏索引时,颤动的音乐继续播放的主要内容,如果未能解决你的问题,请参考以下文章

颤动底部导航,其中一个页面有选项卡

移动到其他页面时无法保持底部导航栏颤动

颤动底部导航,其中一页有标签

颤动中的不同导航栏

颤动中的底部导航栏不会在点击选项卡时切换屏幕

如何在颤动中推动替换删除整个导航堆栈?