【问题标题】:Android MediaPlayer Fails on API 21 (but NOT API 19 or 22!)Android MediaPlayer 在 API 21 上失败(但不是 API 19 或 22!)
【发布时间】:2018-05-15 11:23:26
【问题描述】:

在我的应用程序中,我希望有一个简单的方法,它使用 MediaPlayer 类来播放声音资源一次(如果需要,可以再次播放)。

我已经成功地实现了这一点(见下面的代码)并在具有 API 23 的真实硬件设备上成功地对其进行了测试,并且还在 API 级别从 19 一直到 P...的模拟器上成功地测试了它... API 21 除外, 失败的地方。

这不是很奇怪吗?为什么 API 19... 和 23 可以工作,而 21 却不行?

那么我所说的失败是什么意思?好吧,在 API 21 上,如果你按下 play 按钮,没有声音播放,而且似乎什么也没有发生(尽管如果你偷看 logcat,你会看到所有的方式可怕的本地图书馆消息,包括许多“死亡”甚至“墓碑”或两个)。

如果你继续按 play 第二次,同样的事情会发生,在 logcat 中会生成更多内部错误消息......第三次,你最终会崩溃。

我能看到的唯一可能与我可以控制的任何事情有关的错误是 Log.i 中捕获的异常:

"com.example.boober.stackqmediaplayer I/SFX: 错误:java.io.IOException:准备失败。:status=0x64"

所有其他消息和最终堆栈跟踪均由内部原生库生成。

我搜索了这些错误,阅读了 logcat 和 MediaPlayer 文档。希望我只是在做一些根本上愚蠢的事情,有人可以向我指出。

我真的希望有人可以看看。

我已经精简了一个非常简单的问题示例,应该很容易在 Android Studio 中剪切/粘贴/重建,以便您可以重现该问题:

主活动:

public class MainActivity extends Activity {

    MediaPlayer ourPlayer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ourPlayer = MediaPlayer.create(this, R.raw.soundeffect);
    }

    public void play(View v) {
        try {
            ourPlayer.stop();
            ourPlayer.prepare();
            ourPlayer.start();
        } catch (IOException e) {
            Log.i("SFX", "error:" + e.toString());
        }
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:onClick="play"
        android:clickable="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="PLAY"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

Res 文件夹(使用任何音频文件):

Build.gradle:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.example.boober.stackqmediaplayer"
        minSdkVersion 19
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support.constraint:constraint-layout:1.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

【问题讨论】:

  • 请检查我的答案,如果有遗漏请告诉我。

标签: android audio crash buffer android-mediaplayer


【解决方案1】:

根据MediaPlayer's documentation

  1. ourPlayerinitialized 状态开始,而不是 idle 状态,因为您使用了MediaPlayer.create()

  2. 1234563播放器处于错误状态,而不是停止状态。
  3. 您对 prepare() 的调用处于错误状态,IOExceptionIllegalStateException 或可能会被抛出。文档没有说明前者何时被抛出,但它被您的try-catch 捕获。后者会使您的应用在您第三次单击播放按钮时崩溃。

解决方案是仅在播放器正在播放暂停时调用stop(),因为这些状态具有指向停止状态的直接箭头。

MediaPlayer 有一个isPlaying() 方法,但没有isPaused()。这个问题的一些解决方法可以在here

找到

以下是我提出的解决方案:

public class MainActivity extends Activity {
    private MediaPlayer ourPlayer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ourPlayer = MediaPlayer.create(this, R.raw.beer03);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ourPlayer.release(); // Free native resources
    }

    public void play(View ignored) {
        try {
            if (ourPlayer.isPlaying() || isPlaybackPaused()) {
                ourPlayer.stop();
                ourPlayer.prepare();
            }
            player.start();
        } catch (IOException e) {
            Log.i("SFX", "error:" + e.toString());
        }
    }

    private boolean isPlaybackPaused() {
        // TODO
    }
}

【讨论】:

  • 谢谢!!!我对此进行了测试(没有 isPaused() 检查)并且它有效!我认为我不需要 isPaused() 因为我从不暂停播放...您认为出于某种原因我需要它吗?我想知道为什么 API 21 是唯一受我的错误影响的 API。
  • 很高兴知道它有效!我刚刚提到 isPaused() 是为了完成,所以在你的情况下,忽略它是安全的。
  • 至于为什么 API 21 是唯一不工作的我只能猜测,但实际上我想知道为什么其他人工作,因为原始代码没有像它应该的那样通过状态图.在这种情况下,这种灵活的行为会产生误导,但幸运的是您花时间尝试了每个 API 级别
【解决方案2】:

如果有人和我有完全相同的用例......

您只需要在按下按钮(或触发事件)时从头开始播放声音...

我找到了另一个似乎可行的简单解决方案/解决方法:

@Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // ourPlayer = MediaPlayer.create(this, R.raw.soundeffect);

    }

public void play(View v) {

        if (ourPlayer != null) {
        ourPlayer.release(); }
        ourPlayer = MediaPlayer.create(this, R.raw.soundeffect);
        ourPlayer.start();

    }

如果出于某种原因这是一个坏主意,请告诉我!谢谢!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-14
    • 1970-01-01
    • 2019-11-11
    • 1970-01-01
    相关资源
    最近更新 更多