I'm making an Audio Player app that will load some predefined audios from inside its raw resource folder.
I've sent the test APK for some people to test it, and it worked on 99% of the cases. But then, I started to get some crash reports on Crashlytics for a single device, the OnePlus A5000, running Android 8.1.0.
The stacktrace on crashlytics is as follows:
Fatal Exception: java.lang.IllegalStateException
at android.media.MediaPlayer._setDataSource(MediaPlayer.java)
at android.media.MediaPlayer.setDataSource(MediaPlayer.java:1270)
at com.example.home.MediaPlayerHolder.loadMedia(MediaPlayerHolder.kt:56)
at com.example.home.HomePresenter.playSound(HomePresenter.kt:26)
at com.example.home.HomeFragment.onPlaySelected(HomeFragment.kt:178)
at com.example.home.SoundItemAdapter$ViewHolder$bind$1.onClick(SoundItemAdapter.kt:30)
package names were changed for privacy reasons
This is happening almost every time he clicks on the sound item play button. It also happens on the first time he clicks on any sound item.
I've tried to reproduce this crash on some devices, all of then have API <= 25, but I had no success doing so. All of them ran without an issue. I'm clueless of the probable cause of this error, or even if it's an edge case problem, or if there's indeed something wrong with my code.
This is how I'm loading the audio on the MediaPlayer:
if (mediaPlayer?.isPlaying == true) {
mediaPlayer?.release()
mediaPlayer = null
}
if (mediaPlayer == null) {
mediaPlayer = MediaPlayer()
}
val fileDescriptor = context.resources.openRawResourceFd(resource)
mediaPlayer?.setDataSource(fileDescriptor.fileDescriptor, fileDescriptor.startOffset, fileDescriptor.length)
fileDescriptor.close()
mediaPlayer?.prepare()
mediaPlayer?.start()
The reason was you can't start services in the background anymore after API 26. So you have to start ForegroundService above API 26.
Related
My website plays a background song and some audio effects at the same time. Although the song loads fine (I can see its duration via Audio.duration property), it doesn't play, while the audio effects play (I have already clicked on the screen to activate audio playing). On Firefox in my PC it plays without problem. The audio file is a 10 MB mp3 (but my Android is at home wi-fi). Below is part of my code.
var music,clicked=false,downloaded=false;
function play() {
music.play();
}
function canPlay() {
downloaded = true;
if (clicked) {
play();
}
}
function touchStart(e) {
if (!clicked) {
clicked = true;
if (downloaded) {
play();
}
}
}
function load() {
music = new Audio('mp3/music.mp3');
music.addEventListener('canplaythrough',canPlay,false);
window.addEventListener('touchstart',touchStart,false);
}
The load function is called in the onload of body. The clicked and downloaded variables are required because I never know what happens first: the user click or the audio finish downloading.
On Google mobile-friendly test, it says the audio file couldn’t be loaded (Status: Other error). I've read this test doesn't wait for more than 3 seconds. However, reading the time of the song in seconds doesn't prove it has downloaded?
Why won't the song play on mobile?
EDIT
I created a timer interval that tells me the position (music.currentTime) of the song once every minute. On desktop, it gives the right time. On mobile, it always gives 0, meaning that the song, although loaded (?), isn't really being played.
EDIT 2
I investigated the promise returned by music.play(), and noticed it was giving the error: "(NotAllowedError: play() failed because the user didn't interact with the document first)". It seems that touchStart doesn't count as a "user interaction" the way that mouseDown does...
In the end, I removed both touchStart and touchEnd (each with his own preventDefault), leaving the song to be played after the resulting mouseDown. I've kept the touchMove, however, because it's different from the mouseMove, and required for my site.
I'm running through a slideshow in an infinite loop. Every 'x' amount of time, I switch the video playing. First I stop the video, and then I load the new video. My app is crashing when I set the video path, with an error that there is an illegal state. It only happens once every couple thousand uses, so I cannot replicate it. That is, if I leave the app open for 24 hours on 100 devices, it will happen once per day on one device.
runOnUiThread() {
if (mVideoView!!.isPlaying) {
mVideoView!!.stopPlayback()
}
mVideoView!!.setVideoPath(filePath)
mVideoView!!.setOnErrorListener { mp, what, extra ->
LOG.e("Media player error: $what extra: $extra")
// Something's wrong, try again
slideshowHandler.removeCallbacksAndMessages(null)
slideshowHandler.postDelayed(slideshowRunner, 0)
true
}
if (startTime > 0) {
mVideoView!!.seekTo(startTime)
}
mVideoView!!.start()
}
}
java.lang.IllegalStateException
at android.media.MediaPlayer.prepareAsync(Native Method)
at android.widget.VideoView.openVideo(VideoView.java:356)
at android.widget.VideoView.setVideoURI(VideoView.java:265)
at android.widget.VideoView.setVideoURI(VideoView.java:248)
at android.widget.VideoView.setVideoPath(VideoView.java:239)
Any thoughts on what this could be? What state is invalid? Am I doing things in the wrong order?
I can't find any similar issues on SO. The app is running lollipop 5.1 (has to).
Edit: I'm fixing this right now using a try/catch. Seems like there is some underlying bug in Android that is causing this.
for awhile now, I am working on a media playing app. In this app, I also programmed a little 5 Band EQ using mainly this code:
try
{
AppPreferencesClass ap = new AppPreferencesClass(ctx);
if (Activity_Player.eq != null)
{
Activity_Player.eq.Dispose();
}
Activity_Player.eq = new Android.Media.Audiofx.Equalizer(0, Activity_Player.mediaPlayerSessionId);
Activity_Player.eq.SetEnabled(true);
await Task.Run(() =>
{
if (Activity_Player.EqActive)
{
if (ap.getAwesomeSound())
{
Activity_Player.eq.SetBandLevel(0, Convert.ToInt16(Activity_Equalizer.awesomesound0));
Activity_Player.eq.SetBandLevel(1, Convert.ToInt16(Activity_Equalizer.awesomesound1));
Activity_Player.eq.SetBandLevel(2, Convert.ToInt16(Activity_Equalizer.awesomesound2));
Activity_Player.eq.SetBandLevel(3, Convert.ToInt16(Activity_Equalizer.awesomesound3));
Activity_Player.eq.SetBandLevel(4, Convert.ToInt16(Activity_Equalizer.awesomesound4));
}
else
{
Activity_Player.eq.SetBandLevel(0, Convert.ToInt16(ap.getEQ0()));
Activity_Player.eq.SetBandLevel(1, Convert.ToInt16(ap.getEQ1()));
Activity_Player.eq.SetBandLevel(2, Convert.ToInt16(ap.getEQ2()));
Activity_Player.eq.SetBandLevel(3, Convert.ToInt16(ap.getEQ3()));
Activity_Player.eq.SetBandLevel(4, Convert.ToInt16(ap.getEQ4()));
}
}
});
}
catch
{
}
For many days, this worked just fine but out of NO WHERE, the catch block sometimes gets activated. But only occasionally.On other times, try works fine but there are just no more changes to the audio being played. This is odd enough, since I never changed anything on this code after it starting working.
I then tried another phone (Samsung S4) on my code and the eq worked just perfectly.
So this got me googleing and I think I might have heard that there can only be as many audiosession IDs after you just would run out. I tested and the audio session ID used here is somewhere at 74,000.
So this could be an issue I thought but this would easialy be tested because I already had this very app running in the google play store just an older version of it. I am 100 percent positive, that in this version the EQ worked on my phone. Otherwise I would have not uploaded that version.
Anyway, I downloaded my old app from the play store and here we go:
It doesnt work anymore. The EQ in the old version also has simply NO effect on the audio. While ofcourse on my other phones this old version works perfectly.
Before I am going to reset my own personal phone I wanted to ask you guys if this could be infact the case.
Another thing is, that I am using many static variables in order to get the EQ to work right. Actually, the variable EQ itself is static. Do maybe static variables sort of leave a "trace" behind and maybe I have set the eq up just "too" many times? Although I am disposing of the object before intialising it again (see in my code).
Summing up:
1.) Can there maybe be a maxmium number of EQ or AudioSessionIDs and I have passed those?
2.) Can creating static variables over and over again in my code cause a memory leak so big, even deinstalling the app doesnt do anything?
Thank you!
This is the error log:
11-20 12:16:43.736 E/AudioEffect(16990): set(): AudioFlinger could not create effect, status: -38
11-20 12:16:43.736 E/AudioEffects-JNI(16990): Error setting AudioEffect
11-20 12:16:43.737 E/AudioEffect-JAVA(16990): Error code -19 when initializing AudioEffect.
Thread started: #311-20 12:16:43.745 V/MediaPlayerNative(16990): unrecognized message: (6, 0, 0)
After 2 days of googeling and trying evetything out, here is the issue:
NOT CALLING RELEASE() will have you eventually have to REBOOT your phone. It wont allow too many instances of an EQ to be set.
Solution:
if (eq != null)
{
eq.Release();
}
I am using cordova 6.4.0 with cordova-plugin-media for streaming radio-stations in an Android Application. Unfortunately there is a case, where the application is not responding properly anymore.
Let's say the user wants to stream a radiostation, but while the stream is loading, he wants to abort it (for example because the stream is down, or taking very long to load).
In this case I am not able to cancel the process!
media = new Media("http://direct.franceinfo.fr/live/franceinfo-midfi.mp3?ID=f9fbk29m84", mediaPlayerSuccess, mediaPlayerFail, mediaPlayerStatus);
media.play();
Now I want to cancel the process of buffering the stream, but I'm not able to. The functions:
media.pause();
media.stop();
are throwing error messages in the ADB-log and are calling the mediaPlayer-onError callback.
D/AudioPlayer( 3362): AudioPlayer Error: pausePlaying() called during invalid state: 1
...
D/AudioPlayer( 3362): AudioPlayer Error: stopPlaying() called during invalid state: 1
The media.release() command stops the loading of the stream! However just releasing the stream without stopping it, causes other, rather big problems:
Most of the times the system reacts just very slow and hangs a few seconds, if you call media.release() on a media-object. But if you do this often, the system completly freezes. Meaning it does not accetp remote-control commands anymore.
The Adb-log is still working, but does not show any errors in this case. Only the POWER-Button is still working (it locks and unlocks the screen). The only way to recover from this screwed-up state, is to reboot the device.
How am I supposed to cancel a Media-stream if it is not playing, yet? Is this a bug in the plugin?
Attached is the code-snippet, that I use to handle the media-streaming-logic. Like described above... it basically works, but it slows down or even freezes device, if you call it multiple times.
function radioControl(action, media_src){
//media_src is a webradio-streamurl.
if(action == 'play') {
// Initial Play
if(media === null){
mediaCreateObject(media_src);
}
// If we get PLAY but on antoher station
else if(media.src != media_src){
mediaReleaseRessources();
mediaCreateObject(media_src);
}
//interrupt_timer = false;
if(media === null){
mediaCreateObject(media_src);
}
media.play();
}
else if (action === 'pause') {
//If we get "pause", but it didn't even start yet
if(media._duration == -1){
mediaReleaseRessources();
}
else{
media.pause();
}
}
}
function mediaCreateObject(media_src){
media = new Media(media_src, mediaPlayerSuccess, mediaPlayerFail, mediaPlayerStatus);
}
function mediaReleaseRessources(){
media.release();
}
I found out, that this is not a cordova issue, but an 8 year-old (!) android-bug, that was never fixed. See here:
https://code.google.com/p/android/issues/detail?id=959
MediaPlayer "crash" (deadlocks the calling thread) when resetting or releasing an unused MediaPlayer
Basically the problem is: If you try to "release" a media-object that is not playing (yet), it will deadlock the calling thread, which causes the freezing that I have mentioned in the question. Unfortunately they never fixed this bug, but just marked it as "obsolete". In Android 5.1.1. the bug apparently is still there. Maybe they fixed it in later versions.
I have made a rather ugly workaround for this problem, but it is working. Basically what I did is:
We save every media-object in a javaScript-object. If the user stops it, while it plays, we can just stop and delete the object. But if it is not playing, we leave this media-object in this javaScript-object media_objects = {};
Also we save the currently active_media stream in a variable.
If cordova calls the mediaPlayerStatusChange-callback we loop through the media_objects and check if the status of one of the "pending"-objects has now changed to "running". - Cordova justs calls the media-status-change-callback without any indictation what media-object exactly just changed the state. That is unfortunate, so we have to check if one of the pending-"obsolete" objects now started playing. If so, we can stop and release it. (If the object is actually playing, stop and release works like intended - only if it's not playing, it causes the crash)
function mediaPlayerStatusChange(status){
mediaReleaseRessources();
// handle status change....
// ......
}
function mediaReleaseRessources(){
for(var key in media_objects) {
// We can only stop-and release an object, if it is playing
// If an object started playing, the "_duration"-value is != -1
if(key !== active_media && media_objects[key]._duration != -1) {
media_objects[key].stop();
media_objects[key].release();
delete media_objects[key];
}
}
}
This solution works for me, however I am still interested in a better and cleaner way to handle multiple media-streams in cordova.
$(document).on('click', '#bar img', function () {
alphaLetter = $(this).data('club-id');
audio_file_path = '/android_asset/www/audio/'+alphaLetter+'.mp3';
var my_media = null;
my_media = new Media(audio_file_path);
my_media.play();
});
Above is my code to play sound. It played sound, but when I restarted my project it has stopped working since then, and gives these errors;
E/MediaPlayer(2306): error (-19, 0)
E/MediaPlayer(2306): Attempt to call getDuration without a valid mediaplayer
E/MediaPlayer(2306): error (-38, 0)
E/MediaPlayer(2306): Error (-19,0)
This can be caused by you having too many media objects running at the same time. After the sound is finished you should release it.
The PhoneGap documentation says this about release:
Function media.release is a synchronous function that releases the
underlying operating systems audio resources. This function is
particularly important for Android as there are a finite amount of
OpenCore instances for media playback. Developers should call the
'release' function when they no longer need the Media resource.
I got the same error as well and using reset after finished sounds worked swell for me. Remember to call it after the sound has completed though, such as:
var my_media = null;
my_media = new Media(audio_file_path, function()
{
this.release();
});
my_media.play();
I've written more about the problem here if you are interested but the above solution should work well.
It seems as if this error pops up whenever you try to play the sound even though it is not fully loaded at the certain moment (Attempt to call getDuration).
I looked up the Phonegap documentation and couldn't find a method that may check for the availability of the sound. What you can do is:
Build a Timer that always waits for some time until the sound shall be played.
Use var media = new Media(src, mediaSuccess, [mediaError], [mediaStatus]); for some further handling when playing the sound fails.