MediaPlayer.setDataSource(String)无法使用本地文件

38
如果我使用静态方法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


1
我不知道这是否有帮助,所以请看一下这篇文章:播放mp3文件 - Lama Sonmez
@LamaSonmez 那段代码使用了MediaPlayer.create(context, id)方法。我正在尝试使使用字符串文件路径的版本能够运行:http://developer.android.com/reference/android/media/MediaPlayer.html#setDataSource(java.lang.String) - user755921
1
设置一个倒计时器并调用setDataSource,可能需要5秒钟,不确定资源是否已经加载完成,所以让它冷静一下,然后再尝试访问资源...您还可以添加onerrorlistener,并尝试重试播放4次...只是一个尝试。 - Ahmad
也许这个答案可以帮助那些在这里寻找答案的人:https://dev59.com/hqzka4cB1Zd3GeqP4zNP#53390067 - Andy Weinstein
7个回答

46

替代方案 #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) 来实现您的目的。我不明白为什么。但是,我认为您是因为某种原因想要避免使用 "context"。如果不是这种情况,那么上述两个解决方案应该完全适合您,或者如果您正在尝试避免 context,则恐怕无法使用具有签名 setDataSource(String) 的函数。原因如下:

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

setDataSource Functions

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)代码,您将看到以下条件根据其方案过滤路径,特别是如果它是“file”方案,则调用setDataSource(FileDescriptor),如果方案不是“file”,则调用本机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)重载不能处理您的资源文件。您需要选择另一个重载。

另一方面,请检查setDataSource(Context context, Uri uri, Map headers),该方法由setDataSource(Context context, Uri uri)使用,它使用来自上下文的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());
            }

总之,你不能直接使用setDataSource(String)来播放你的资源mp3文件。如果你想要使用字符串来播放你的资源文件,你可以使用上面提到的MediaPlayer.create()静态函数和getIdentifier(),或者使用Update#1中提到的setDataSource(context,uri)。

请参考完整的源代码以便更好地理解:Android MediaPlayer


更新 #3:

openFrameworks setDataSource(String):

如我在下面的评论中所述,openFrameworks使用android MediaPlayer代码,就是原样使用。如果您能参考第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中使用setDataSource()传递ardroid.resource//+ this.getPackageName() + "raw/test0",您仍将得到与我在更新#2中解释的相同异常。话虽如此,我刚刚尝试了一下通过谷歌搜索来确保我的说法,并找到了这个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

这是为了向您展示,您尝试访问的资源文件实际上不是物理路径中的文件,而是一个jar位置(在apk内部),您无法使用setDataSource(String)方法来访问它。(尝试使用7zip提取您的apk文件,您将在其中看到res/raw/test0)。

希望有所帮助。

PS: 我知道这是一个有点冗长的答案,但我希望这样可以详细解释。如果这能帮助其他人,就把替代方案放在前面。


我可能表达不清楚 -- 我特别是在询问 MediaPlayer.setDataSource(String) 这个方法。我并不关心其他版本的该方法。 - user755921
@racarate,你是说更新#1中的解决方案对你也不起作用吗?那个解决方案使用了setDataSource(context,uri)。正如我在答案中提到的,我只是将你的代码从mp.setDataSource(filename)更改为mp.setDataSource(this, Uri.parse(filename)),并且它可以完美地与你的资源文件配合使用。 - Naren Neelamegam
请检查更新后的答案(更新#2)。setDataSource(String)覆盖不能直接使用。您可以使用setDataSource(context,uri)代替,并使用您的字符串资源路径或使用MediaPlayer.create()中的getIdentifier()。 - Naren Neelamegam
好的,答案是否定的。感谢您提供的细节,如果您删除答案的前两部分并编辑#2成为官方的“不可能”的答案,那么这可能会更有帮助。我必须说,官方文档关于这一点并不清楚。 - user755921
好的,我现在明白区别了。谢谢你的回复! - user755921
显示剩余5条评论

4

就像Android 文档所说的那样:

任意文件以其原始形式保存。要使用原始InputStream打开这些资源,请调用Resources.openRawResource()并传入资源ID,即R.raw.filename。

但是,如果您需要访问原始文件名和文件层次结构,则可以考虑将某些资源保存在assets/目录中(而不是res/raw/)。assets/中的文件不会被分配资源ID,因此只能使用AssetManager读取它们。

所以您需要使用InputStream在将音频文件设置到媒体播放器之前先读取它。

我建议您按照自己的想法将音频文件放置在assets文件夹中。

:)


我对InputStream的作用感到困惑?我正在尝试让接受文件路径字符串版本的代码工作。 - user755921
也许这个答案可以帮到你 :) https://dev59.com/FW7Xa4cB1Zd3GeqPvvWO - DeKoServidoni

4
以下代码对我有效,我认为这段代码会帮助您。
    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();
}

这个例子使用了MediaPlayer.create(context, id)方法,我的问题是关于MediaPlayer.setDataSource(String)方法。 - user755921
1
请注意,您不能使用MediaPlayer.setDataSource(String)来播放本地文件夹中的文件。建议使用最佳实践。 - Ravi Vaghela
@RaviVGHL,你评论中的链接并没有说setDataSource(String)不能用于本地文件。它说MediaPlayer.create()在内部使用setDataSource()和Prepare()方法。虽然你的答案是可行的代码,但你评论的陈述是不正确的,racarate提出的问题也不是关于使用R.raw.test0。racarate想要使用字符串作为参数(而不是ID)来播放声音以达到他的目的。 - Naren Neelamegam

2
处理原始资源时,您应该依赖以下构造函数:

public static MediaPlayer create (Context context, int resid)

创建给定资源ID的MediaPlayer的方便方法。 成功后,prepare()已经被调用,不必再次调用。

代码如下:
mediaPlayer = MediaPlayer.create(this, R.raw.test0);
mediaPlayer.start();

在使用完mediaPlayer后,不要忘记调用mediaPlayer.release()

(来源)


是的,这个方法对我有效,但我正试图让那个接受字符串的版本起作用:http://developer.android.com/reference/android/media/MediaPlayer.html#setDataSource(java.lang.String) - user755921

1

这里开始,

当路径指向本地文件时,该文件可能被调用应用程序以外的进程打开。这意味着路径名应为绝对路径(因为任何其他进程都具有未指定的当前工作目录),并且路径名应引用一个可供全球读取的文件。作为替代方案,应用程序可以首先打开要读取的文件,然后使用文件描述符形式的setDataSource(FileDescriptor)。

尝试一下,

String filePath = "path/file.mp3";
File file = new File(filePath);
FileInputStream inputStream = new FileInputStream(file);
mediaPlayer.setDataSource(inputStream.getFD());
inputStream.close();

希望有所帮助!

是的,这个方法对我有用,但我特别询问的是MediaPlayer.setDataSource(String)方法。 - user755921

0

在编程中,你需要使用setDataSource(@NonNull Context context, @NonNull Uri uri)而不是setDataSource(String path)

因为你想解决应用内资源,所以必须提供Context来解决这些问题。

此外,如果你仔细查看这两种方法,你会发现它们使用不同的策略来查找结果资源。


0

试试这个:

  if(mediaPlayer != null ){
    
                        if (mediaPlayer.isPlaying()){mediaPlayer.stop();}
    
                        mediaPlayer.reset(); //this line is important!
    
                        String path = File.separator + "sdcard" + File.separator + utilsFields.repoDirRoot + File.separator + media.mp4;
    
    
                        try {
                            mediaPlayer.setDataSource(path);
                        }catch (Exception ignored){}
    
    
                        try {
                            mediaPlayer.prepare();
                        }catch (Exception ignored){}
    
                        mediaPlayer.start();
    
                    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接