Audio (not) playing in Android
Android is incredibly popular now, it has a great IDE (thumbs up, Android Studio!) and a huge community. The number of different open source libraries is amazing, it’s all cool and fun.
But sometimes even such a big amount of information combined with Stack Overflow answers can’t help you handle specific situations quickly and efficiently. I encountered one of them, and I really want to share some experience with anyone who needs it.
The situation was like this: I needed multiple audio files to be played at once. Users could turn them on and off when needed, and these sounds had to loop. Nothing really sophisticated… only at the first glance, of course, because if it was simple you wouldn’t be reading this post :)
I spent quite a long time solving this problem and learned a lot on my way through, so let me guide you through some aspects of playing audio on Android, as I collected all this data from various sources across the Web and from personal experience while struggling for a solution. We’ll cover some of the undocumented features a.k.a. bugs that you may encounter.
First, let’s make a quick coverage of what Android has to handle audio playing.
- MediaPlayer. It’s the simplest and most used Android class that allows you to play audio and video. We won’t speak of the video specifics, as it’s beyond our topic, only about the audio part.
- SoundPool. An Android class I discovered when working on this problem. A very peculiar thing really, it can be used for small audio files. The purpose of it is to control the playing of multiple small sounds at the same time. And by saying “small” I TOTALLY mean it. Let me explain a bit: SoundPool takes your file (either from the raw folder or from storage) and decompresses it into PCM [from wikipedia.org: PCM, or Pulse-code modulation, is a method used to digitally represent sampled analog signals, details here]. The thing is, your files should be no more than 1 Mb each when decompressed, otherwise they won’t play. So SoundPool’s main purpose is described as playing very short sounds like game sounds or other similar stuff.
- AudioTrack. A class that allows playing an audio file, it’s a bit lower-level tool than the previous ones.
- ExoPlayer. A player from Google that is recommended for use instead of MediaPlayer. When I only started this article, the last release version was 1.5.11, and then Google released the 2.0.0 (the latest now is 2.0.4 as of Oct 31st 2016) — way different from the first one.
Let’s start with MediaPlayer, as it’s probably the most popular of all tools listed above. I just have to put one nice picture here — it would scare the sh*t out of anyone, trust me :)
This diagram shows the lifecycle of a MediaPlayer… And yes, you need to read it through and try to understand at least the general workflow to use it. Google has a nice guide on working with MediaPlayer, so check it out if you haven’t used this class before. I’ll go straight to the possible issues.
- Multiple instances of MediaPlayer will not play at the same time on Google Nexus 5 and Google Nexus 5X. I personally tested on these devices. Can’t be sure about Nexus 6P — if anyone has it please let me know what happens there.
MediaPlayer’s isPlaying() may return true (and may return false) even when the playing sound has completed. Let me illustrate it with a piece of code and some logs.
- MediaPlayer’s setVolume() may not work in Jelly Bean API 16 — I personally had this issue on an LG Optimus device, and that’s why I had to use the Android’s AudioManager to set volume for the whole STREAM_MUSIC streamId like this (here I’m decreasing volume by 1):
It’s not convenient at least — you are limited by integer values, and sometimes you have to change volume very smoothly. This piece of code actually changes the volume of the whole stream, which means that all the sounds playing in it (other MediaPlayers, other apps with their sounds) will have this volume as well. Not a good solution at all.
- MediaPlayer’s OnErrorListener interface has a method called onError(MediaPlayer mp, int what, int extra). The documentation mentions some values, but not all types you can receive. For example, you can get (-38, 0) — and it indicates that you have some trouble with the network. There are some other logs, though, so you can figure out what’s going on, but it’s strange that these codes mean nothing without supporting logs that may not appear. The whole situation in logs may look like this:
SoundPool. I didn’t get to use it personally because of the file size limitation I mentioned above, but I found a very interesting conversation where those who used it discuss the flaws it has, so check it out. The only thing I can say is this: if your files are longer than 30 seconds, it’s not what you need.
AudioTrack. A low-level implementation of audio playing where you have to write bytes to be played. Among the issues, I can mention the same thing with Nexus 5 and Nexus 5X as in MediaPlayer above (as taken from here). If you still want to take a look at AudioTrack, checkout out this tutorial.
ExoPlayer. [My personal winner].
Check out the official page that describes most of the things you’re going to need to work with ExoPlayer. It’s very customizable — you can change almost anything you need.
I have to say that I was going to describe the issues I encountered, but with the second version released they’re all gone :) Still, I will mention them for those who haven’t migrated to the 2.0.0 yet.
- Looping wasn’t available as of 1.5.11. You had to restart your player manually in onComplete and it caused a gap before starting the file again. Now in 2.+ we have a LoopingMediaSource and it’s amazing at looping — you will never find out where exactly the file ends and starts again. Gapless playback was also implemented, so enjoy using the ExoPlayer :)
- In 1st version we could not set volume to the ExoPlayer without other components, we had to send a message to its renderer like this:
Since 2.0.0 we have a setVolume for SimpleExoPlayer. It’s only a convenience issue, though, and it’s fixed, too :)
- Due to this issue, you may have problems with ExoPlayer on some devices with API 16 Jelly Bean. If it happens try a workaround mentioned in the discussion there.
So, I did all this investigation while working on one of our projects, Audiojoy (you can read more about it here).
As a result, I have one advice for you guys: use ExoPlayer. Seriously. It’s constantly updated and fixed, it’s open-source, it’s customizable as no other Android audio playing library. A bit harder to get started with, but then it will repay 10 times the time it took to start using it.
Have you ever had similar issues? Or maybe you know any best Android audio practices?
Please share in comments if you do :)
And thank you for reading!
Also you can check out this blog for some more info about ExoPlayer.