Flutter audioplayers使用小結
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 MediaPlayer
的 State 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 ......
概括起來就兩點:
- 播放/暫停等操作之前,必須進入
Prepared
狀態 -
prepareAsync
是一個非同步操作,會使MediaPlayer
進入preparing
狀態, 當底層的player engine
完成preparation
的工作時,將會呼叫使用者提供的回撥函式。
所以,上面崩潰的問題也比較明瞭了, audioPlayer.play
呼叫之後底層的 MediaPlayer
其實是一個 preparing
狀態,此時呼叫 pause
導致了 PlatformException
異常的丟擲,程式直接崩潰。這其實是一個 audioplayers
沒有處理好的狀態, play
和 setUrl
這類的 Future
不應該只在 perparing
狀態就 resolve
,而應該等到 prepared
。
因此...我反手去 audioplayers github 上提了個issue,深藏功與名。
尾聲
audioplayers
在 Android
還支援 SoundPool
( new AudioPlayer(mode: PlayerMode.LOW_LATENCY)
),也存在著同樣的問題。
推測 ios
應該也會出現同樣的問題。
寫的比較雜亂簡單,希望能夠幫助到碰到同樣問題的人。