Flutter audioplayers使用小结

7,516 阅读2分钟

简介

audioplayers是一个可以支持同时播放多个音频文件的Flutter的库。用法也是相当的简单:

 AudioPlayer audioPlayer = new AudioPlayer();
 await audioPlayer.play(url);

 //or
 //await audioPlayer.play(localPath, isLocal: true);
 
 
 // 一些控制API
 await audioPlayer.pause();
 await audioPlayer.stop();
 await audioPlayer.resume();

如何使程序崩溃(Android)

AudioPlayer audioPlayer = new AudioPlayer();
await audioPlayer.play(localPath, isLocal: true);

//播放后,马上调用暂停,底层抛出`PlatformException`异常,程序崩溃
await audioPlayer.pause();

嗯,就是这么简单,还没写满10行代码就GG了。

分析原因

遇到了这个崩溃后,本人也是怀揣着好奇心第一时间翻看了audioplayers的代码,以及又看了一遍文档。原来,文档中介绍,为了实现先加载后播放的功能,我们可以调用audioPlayer.setUrl函数,之后需要播放时候调用resume进行播放即可。

嗯,当时我没看到这段,塞翁失马焉知非福,这样的实现也是同样存在问题的。 先来一张Android MediaPlayerState Diagram:

无论是audioPlayer.play还是andioPlayer.setUrl,他们最重要的工作就是调用Android原生的prepareAsync方法,让MediaPlayer对象处于Prepared状态。引用一下Android官方文档:

A MediaPlayer object must first enter the Prepared state before playback can be started.

...... or a call to prepareAsync() (asynchronous) which first transfers the object to the Preparing state after the call returns (which occurs almost right away) while the internal player engine continues working on the rest of preparation work until the preparation work completes. When the preparation completes or when prepare() call returns, the internal player engine then calls a user supplied callback method ......

概括起来就两点:

  1. 播放/暂停等操作之前,必须进入Prepared状态
  2. prepareAsync是一个异步操作,会使MediaPlayer进入preparing状态, 当底层的player engine完成preparation的工作时,将会调用用户提供的回调函数。

所以,上面崩溃的问题也比较明了了,audioPlayer.play调用之后底层的MediaPlayer其实是一个preparing状态,此时调用pause导致了PlatformException异常的抛出,程序直接崩溃。这其实是一个audioplayers没有处理好的状态,playsetUrl这类的Future不应该只在perparing状态就resolve,而应该等到prepared

因此...我反手去audioplayers github上提了个issue,深藏功与名。

尾声

audioplayersAndroid还支持SoundPool(new AudioPlayer(mode: PlayerMode.LOW_LATENCY)),也存在着同样的问题。

推测ios应该也会出现同样的问题。

写的比较杂乱简单,希望能够帮助到碰到同样问题的人。