MediaPlayer.setDataSource(String) 不能处理本地文件

2022-09-01 05:16:03

如果我使用静态方法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的字符串版本。

编辑:如果我使用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

替代解决方案#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(字符串) 用于您的目的。我不明白为什么。但是,我的假设是,出于某种原因,你试图避免使用“上下文”。如果不是这种情况,那么上述两个解决方案应该非常适合您,或者如果您试图避免上下文,恐怕使用带有签名集DataSource(String)调用的函数是不可能的。原因如下,

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

setDataSource Functions

setDataSource(String) 在内部调用 setDataSource(String path, String[] 键, String[] 值) 函数。如果您可以检查其来源

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

如果您检查setDataSource(String path,String[]键,String[]值)代码,您将看到以下条件根据其方案过滤路径,特别是如果它是“文件”方案,它调用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方案不会为空(android.resource://),setDataSource(String)将尝试使用本机JNI函数nativeSetDataSource()认为你的路径是http/https/rtsp,显然该调用也会失败而不会引发任何异常。这就是为什么你对 setDataSource(String) 的调用无异常地转义,并得到 prepare() 调用,并出现以下异常。

Prepare failed.: status=0x1

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

另一方面,检查 setDataSource(Context context, Uri uri) 使用的 setDataSource(Context context, Uri headers),它使用 AssetFileDescriptor,ContentResolver from your context 和 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代码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 中解释的相同异常。话虽如此,我只是尝试运气搜索Google,以加倍确定我所说的话,并找到了这个openFrameworks论坛链接,其中一位openFrameworks核心开发人员arturo说:

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

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

为了让您清楚地了解您尝试使用资源文件执行的操作,请尝试执行以下代码并在 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:我知道它的答案有点冗长,但我希望这能详细解释它。如果可以帮助其他人,请将替代解决方案留在顶部。


答案 2

就像机器人文档说的那样

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

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

因此,您需要先使用 InputStream 读取音频文件,然后再将其设置为媒体播放器。

我建议您将音频文件放在assets文件夹中,就像您说的那样播放

:)


推荐