MediaPlayer.setDataSource(String) 无法处理本地文件
MediaPlayer.setDataSource(String) not working with local files
如果我使用静态方法 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/目录下。
我假设我设置的路径不正确,但我在网上找到的所有示例都使用 FileDescriptor 版本的 setDataPath 而不是 String 版本的 setDataPath。
编辑:如果我使用方法 MediaPlayer.setDataSource(FileDescriptor) 并将文件放在 Eclipse 的 /assets/ 目录中,我也可以播放本地 mp3。
编辑 #2:我接受了这是不可能的答案,但后来意识到我正在使用的库 (openFrameworks) 实际上确实使用 String 方法来加载文件。看这里:
如androiddocumentation所说
Arbitrary files to save in their raw form. To open these resources
with a raw InputStream, call Resources.openRawResource() with the
resource ID, which is R.raw.filename.
However, if you need access to original file names and file hierarchy,
you might consider saving some resources in the assets/ directory
(instead of res/raw/). Files in assets/ are not given a resource ID,
so you can read them only using AssetManager.
因此在将音频文件设置为媒体播放器之前,您需要使用 InputStream 读取音频文件。
建议你像你说的那样把音频文件放到assets文件夹下
:)
在处理原始资源时,您应该依赖以下构造函数:
public static MediaPlayer create (Context context, int resid)
Convenience method to create a MediaPlayer for a given resource id. On success, prepare() will already have been called and must not be called again.
代码看起来像
mediaPlayer = MediaPlayer.create(this, R.raw.test0);
mediaPlayer.start();
完成后别忘了调用 mediaPlayer.release()
。
(source)
下面的代码适合我,我认为这段代码会对你有所帮助
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();
}
来自 here,
When path refers to a local file, the file may actually be opened by a process other than the calling application. This implies that the pathname should be an absolute path (as any other process runs with unspecified current working directory), and that the pathname should reference a world-readable file. As an alternative, the application could first open the file for reading, and then use the file descriptor form setDataSource(FileDescriptor).
试试,
String filePath = "path/file.mp3";
File file = new File(filePath);
FileInputStream inputStream = new FileInputStream(file);
mediaPlayer.setDataSource(inputStream.getFD());
inputStream.close();
希望对您有所帮助!
备选方案 #1:使用 Resources.getIdentifier()
为什么不使用 getResources().getIdentifier() 来获取资源的 id 而像往常一样使用静态 MediaPlayer.create() 呢?
public int getIdentifier (String name, String defType, String defPackage)
getIdentifier() 获取您的资源名称 (test0)、资源类型 (raw)、您的包名称和 returns 实际资源 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"。如果不是这种情况,那么上述两种解决方案应该非常适合您,或者如果您试图避免上下文,恐怕使用带有签名 setDataSource(String) 调用的函数是不可能的。原因如下,
MediaPlayer setDataSource() 函数有以下选项,您只对 setDataSource(String) 感兴趣,
setDataSource(String) 内部调用 setDataSource(String path, String[] keys, String[] values) 函数。如果你能检查它的 source,
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 scheme不会为空(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 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) 如 Update#1.
参考完整的源代码以获得更多的理解: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 forum link openFrameworks 核心开发人员之一 arturo 说,
don't know exactly how the mediaPlayer works but everything in res/raw
or bin/data gets copied to /sdcard/cc.openframeworks.packagename
根据该评论,您可以尝试在 setDataSource() 中使用复制的路径。在 MediaPlayer 的 setDataSource(String) 上使用资源文件是不可能的,因为它不能接受资源文件路径。请注意,我说 "resource file path" 以方案 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:我知道它的答案有点冗长,但我希望这能详细解释。如果可以帮助其他人,请将替代解决方案留在顶部。
您必须使用 setDataSource(@NonNull Context context, @NonNull Uri uri)
而不是 setDataSource(String path)
既然要解析resource内部应用资源,就得提供Context
用来解析这个东东
此外,如果您深入了解这两种方法,您会发现它们使用不同的策略来查找结果资源。
如果我使用静态方法 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/目录下。
我假设我设置的路径不正确,但我在网上找到的所有示例都使用 FileDescriptor 版本的 setDataPath 而不是 String 版本的 setDataPath。
编辑:如果我使用方法 MediaPlayer.setDataSource(FileDescriptor) 并将文件放在 Eclipse 的 /assets/ 目录中,我也可以播放本地 mp3。
编辑 #2:我接受了这是不可能的答案,但后来意识到我正在使用的库 (openFrameworks) 实际上确实使用 String 方法来加载文件。看这里:
如androiddocumentation所说
Arbitrary files to save in their raw form. To open these resources with a raw InputStream, call Resources.openRawResource() with the resource ID, which is R.raw.filename.
However, if you need access to original file names and file hierarchy, you might consider saving some resources in the assets/ directory (instead of res/raw/). Files in assets/ are not given a resource ID, so you can read them only using AssetManager.
因此在将音频文件设置为媒体播放器之前,您需要使用 InputStream 读取音频文件。
建议你像你说的那样把音频文件放到assets文件夹下
:)
在处理原始资源时,您应该依赖以下构造函数:
public static MediaPlayer create (Context context, int resid)
Convenience method to create a MediaPlayer for a given resource id. On success, prepare() will already have been called and must not be called again.
代码看起来像
mediaPlayer = MediaPlayer.create(this, R.raw.test0);
mediaPlayer.start();
完成后别忘了调用 mediaPlayer.release()
。
(source)
下面的代码适合我,我认为这段代码会对你有所帮助
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();
}
来自 here,
When path refers to a local file, the file may actually be opened by a process other than the calling application. This implies that the pathname should be an absolute path (as any other process runs with unspecified current working directory), and that the pathname should reference a world-readable file. As an alternative, the application could first open the file for reading, and then use the file descriptor form setDataSource(FileDescriptor).
试试,
String filePath = "path/file.mp3";
File file = new File(filePath);
FileInputStream inputStream = new FileInputStream(file);
mediaPlayer.setDataSource(inputStream.getFD());
inputStream.close();
希望对您有所帮助!
备选方案 #1:使用 Resources.getIdentifier()
为什么不使用 getResources().getIdentifier() 来获取资源的 id 而像往常一样使用静态 MediaPlayer.create() 呢?
public int getIdentifier (String name, String defType, String defPackage)
getIdentifier() 获取您的资源名称 (test0)、资源类型 (raw)、您的包名称和 returns 实际资源 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"。如果不是这种情况,那么上述两种解决方案应该非常适合您,或者如果您试图避免上下文,恐怕使用带有签名 setDataSource(String) 调用的函数是不可能的。原因如下,
MediaPlayer setDataSource() 函数有以下选项,您只对 setDataSource(String) 感兴趣,
setDataSource(String) 内部调用 setDataSource(String path, String[] keys, String[] values) 函数。如果你能检查它的 source,
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 scheme不会为空(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 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) 如 Update#1.
参考完整的源代码以获得更多的理解: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 forum link openFrameworks 核心开发人员之一 arturo 说,
don't know exactly how the mediaPlayer works but everything in res/raw or bin/data gets copied to /sdcard/cc.openframeworks.packagename
根据该评论,您可以尝试在 setDataSource() 中使用复制的路径。在 MediaPlayer 的 setDataSource(String) 上使用资源文件是不可能的,因为它不能接受资源文件路径。请注意,我说 "resource file path" 以方案 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:我知道它的答案有点冗长,但我希望这能详细解释。如果可以帮助其他人,请将替代解决方案留在顶部。
您必须使用 setDataSource(@NonNull Context context, @NonNull Uri uri)
而不是 setDataSource(String path)
既然要解析resource内部应用资源,就得提供Context
用来解析这个东东
此外,如果您深入了解这两种方法,您会发现它们使用不同的策略来查找结果资源。