MediaPlayer.setDataSource(String) 不适用于本地文件

新手上路,请多包涵

如果我使用静态方法 MediaPlayer.create(context, id),我可以播放本地 mp3,但如果我使用非静态方法 MediaPlayer.setDataSource(String),它就无法播放。发生的事情是,当我调用 MediaPlayer.prepare() 时出现同步异常:

prepare exceptionjava.io.IOException: Prepare failed.: status=0x1

这是我的代码(省略日志记录):

 String filename = "android.resource://" + this.getPackageName() + "/raw/test0";

mp = new MediaPlayer();
try { mp.setDataSource(filename); } catch (Exception e) {}
try { mp.prepare(); } catch (Exception e) {}
mp.start();

请注意,我没有收到有关找不到文件或任何内容的错误。该文件的全名是 test0.mp3,我将其放在 Eclipse 的 /res/raw/ 目录中。

我假设我设置的路径不正确,但我在网上找到的所有示例都使用 setDataPath 的 FileDescriptor 版本,而不是 setDataPath 的 String 版本。

编辑:如果我使用方法 MediaPlayer.setDataSource(FileDescriptor) 并将文件放在 Eclipse 的 /assets/ 目录中,我也可以播放本地 mp3。

编辑 #2:我接受了这是不可能的答案,但后来意识到我正在使用的库 (openFrameworks) 实际上确实使用 String 方法加载文件。看这里:

https://github.com/openframeworks/openFrameworks/blob/master/addons/ofxAndroid/ofAndroidLib/src/cc/openframeworks/OFAndroidSoundPlayer.java

原文由 user755921 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1k
2 个回答

替代解决方案 #1:使用 Resources.getIdentifier()

为什么不使用 getResources().getIdentifier() 来获取资源的 id 并像往常一样使用静态 MediaPlayer.create() ?

 public int getIdentifier (String name, String defType, String defPackage)

getIdentifier() 获取您的资源名称 (test0)、资源类型 (raw)、您的包名称并返回实际的资源 ID。

  MediaPlayer mp;
 //String filename = "android.resource://" + this.getPackageName() + "/raw/test0";
 mp=MediaPlayer.create(getApplicationContext(), getResources().getIdentifier("test0","raw",getPackageName()));
 mp.start();

我已经测试了这段代码并且它有效。


更新#1:

替代解决方案 #2:使用 Uri.parse()

我也测试了这段代码,它也有效。将您的资源路径作为 URI 传递给 setDataSource()。我只是对您的代码进行了更改以使其正常工作。

 String filename = "android.resource://" + this.getPackageName() + "/raw/test0";

mp = new MediaPlayer();
try { mp.setDataSource(this,Uri.parse(filename)); } catch (Exception e) {}
try { mp.prepare(); } catch (Exception e) {}
mp.start();


更新#2:答案是否定的

关于 setDataSource(String) 调用

看到您的评论后,您似乎确实希望将 setDataSource(string) 用于您的目的。我不明白为什么。但是,我假设的是,出于某种原因,您试图避免使用“上下文”。如果不是这种情况,那么上述两种解决方案应该非常适合您,或者如果您试图避免上下文,恐怕使用带有签名 setDataSource(String) 调用的函数是不可能的。原因如下,

MediaPlayer setDataSource() 函数有以下选项,您只对 setDataSource(String) 感兴趣,

setDataSource 函数

setDataSource(String) 在内部调用 setDataSource(String path, String[] keys, String[] values) 函数。如果你能查到它的 来源

 public void setDataSource(String path)
            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
        setDataSource(path, null, null);
    }

如果您检查 setDataSource(String path, String[] keys, String[] values) 代码,您将看到以下条件根据其方案过滤路径,特别是如果它是“文件”方案,它调用 setDataSource(FileDescriptor) 或如果方案不是“文件”,它会调用本机 JNI 媒体函数。

 {
        final Uri uri = Uri.parse(path);
        final String scheme = uri.getScheme();
        if ("file".equals(scheme)) {
            path = uri.getPath();
        } else if (scheme != null) {
            // handle non-file sources
            nativeSetDataSource(
                MediaHTTPService.createHttpServiceBinderIfNecessary(path),
                path,
                keys,
                values);
            return;
        }
        final File file = new File(path);
        if (file.exists()) {
            FileInputStream is = new FileInputStream(file);
            FileDescriptor fd = is.getFD();
            setDataSource(fd);
            is.close();
        } else {
            throw new IOException("setDataSource failed.");
        }
}

在上面的代码中,您的资源文件 URI 方案不会为 null (android.resource://) 并且 setDataSource(String) 将尝试使用本机 JNI 函数 nativeSetDataSource() 认为您的路径是 http/https/rtsp 并且显然调用也会失败而不会抛出任何异常。这就是为什么您对 setDataSource(String) 的调用无一例外地转义并进入 prepare() 调用但出现以下异常。

 Prepare failed.: status=0x1

所以 setDataSource(String) override 无法处理你的资源文件。您需要为此选择另一个覆盖。

另一方面,检查 setDataSource(Context context, Uri uri) 使用的 setDataSource(Context context, Uri uri, Map headers),它使用上下文中的 AssetFileDescriptor、ContentResolver 和 openAssetFileDescriptor 打开 URI,它作为 openAssetFileDescriptor( ) 可以打开您的资源文件,最后生成的 fd 用于调用 setDataSource(FileDescriptor) 覆盖。

     AssetFileDescriptor fd = null;
    try {
        ContentResolver resolver = context.getContentResolver();
        fd = resolver.openAssetFileDescriptor(uri, "r");
        //  :
        //  :
        //  :
        if (fd.getDeclaredLength() < 0) {
                setDataSource(fd.getFileDescriptor());
            } else {
                setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getDeclaredLength());
            }

总而言之,您不能像使用资源 mp3 文件那样使用 setDataSource(String) 覆盖。相反,如果您想使用字符串来播放您的资源文件,您可以使用 MediaPlayer.create() 静态函数和上面给出的 getIdentifier() 或 setDataSource(context,uri) ,如更新#1 中给出的。

完整源码参考此处了解更多: Android MediaPlayer


更新#3:

openFrameworks setDataSource(字符串):

正如我在下面的评论中提到的,openFrameworks 使用 android MediaPlayer 代码 asis。如果您可以参考第 4 行,

 import android.media.MediaPlayer;

和第 26、27、28 和 218 行

        player = new MediaPlayer();       //26
        player.setDataSource(fileName);   //27
        player.prepare();                 //28

        private MediaPlayer player;       //218

因此,如果您尝试使用 openFrameworks 将 ardroid.resource//+ this.getPackageName() + “raw/test0” 传递给 setDataSource(),您仍然会遇到与我在 Update#2 中解释的相同的异常。话虽如此,我只是碰碰运气在谷歌上搜索以加倍确定我在说什么,并找到了这个 openFrameworks 论坛链接,其中一位 openFrameworks 核心开发人员 arturo 说,

不知道 mediaPlayer 是如何工作的,但是 res/raw 或 bin/data 中的所有内容都被复制到 /sdcard/cc.openframeworks.packagename

根据该评论,您可以尝试在 setDataSource() 中使用复制的路径。在 MediaPlayer 的 setDataSource(String) 上使用资源文件是不可能的,因为它不能接受资源文件路径。请注意,我说的“资源文件路径”以方案 android.resource// 开头,它实际上是一个 jar 位置(在您的 apk 中),而不是物理位置。本地文件将与以方案 file:// 开头的 setDataSource(String) 一起使用。

为了让您清楚地了解您要对资源文件做什么,请尝试执行下面的代码并在 logcat 中查看结果,

     try{
      Log.d("RESURI", this.getClass().getClassLoader().getResource("res/raw/test0").toURI().toString());
    }
    catch(Exception e) {

    }

你会得到这样的结果,

 jar:file:/data/app/<packagename>/<apkname>.apk!/res/raw/test0

这是向您展示您尝试访问的资源文件实际上不是物理路径中的文件,而是您无法使用 setDataSource(String) 方法访问的 jar 位置(在 apk 中)。 (尝试使用 7zip 解压缩您的 apk 文件,您将在其中看到 res/raw/test0)。

希望有所帮助。

PS:我知道它的答案有点冗长,但我希望这能详细解释。如果可以帮助其他人,请将替代解决方案留在顶部。

原文由 Naren Neelamegam 发布,翻译遵循 CC BY-SA 3.0 许可协议

下面的代码对我有用,我认为这段代码会对你有所帮助

    player = MediaPlayer.create(this,R.raw.test0);
    player.setLooping(true); // Set looping

    player.setVolume(100,100);
    player.start();

@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
player.stop();
player.release();
}

原文由 Ravi Vaghela 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题