Mediaplayer Setdatasource, Better to Use Path or Filedescriptor

MediaPlayer setDataSource, better to use path or FileDescriptor?

Actually, it turns out that there IS a difference in certain situations.

mediaPlayer.setDataSource(String path) will fail when you call mediaPlayer.prepare(), if you are trying to load a file from getApplicationContext().getFilesDir(), depending on how the file was saved. For example, if I write a file using new RandomAccessFile(filePath, "rw"), the file is not actually readable by the mediaplayer if you use mediaPlayer.setDataSource(String path). The prepare() will immediately trigger error(1, -2147483648) from the mediaplayer; essentially a file permission error. SDK 9 introduced file.setReadable (boolean readable, boolean ownerOnly) which would presumably allow you to resolve this issue by setting ownerOnly to false...but that doesn't help you if you need to support older SDKs.

HOWEVER, mediaPlayer.setDataSource(FileDescriptor fd) does NOT have this problem and mediaplayer will successfully prepare the same exact file without a permission problem.

MediaPlayer.setDataSource(String) not working with local files

Alternative Solution #1: Using Resources.getIdentifier()

Why not use getResources().getIdentifier() to get id of the resource and use the static MediaPlayer.create() as usual?

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

getIdentifier() takes your resource name (test0), resource type(raw), your package name and returns the actual resource id.

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

I've tested this code and it works.


Update #1:

Alternative Solution #2: Using Uri.parse()

I've tested this code as well and it works too. Pass your resource path as URI to setDataSource(). I just made that change to your code to get it work.

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();


Update #2: Answer is NO

About setDataSource(String) call

After seeing your comment, it looks like you exactly want setDataSource(string) to be used for your purpose. I don't understand why. But, what I assume is, for some reason you are trying to avoid using "context". If that is not the case then the above two solutions should work perfectly for you or if you are trying to avoid context, I'm afraid that is not possible with the function with signature setDataSource(String) call. The reason is as below,

MediaPlayer setDataSource() function has these below options out of which you are only interested in setDataSource(String),

setDataSource Functions

setDataSource(String) internally calls setDataSource(String path, String[] keys, String[] values) function. If you can check its source,

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

and if you check setDataSource(String path, String[] keys, String[] values) code, you will see the below condition filtering the path based on its scheme, particularly if it is "file" scheme it calls setDataSource(FileDescriptor) or if scheme is non "file", it calls native JNI media function.

{
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.");
}
}

In the above code, your resource file URI scheme will not be null (android.resource://) and setDataSource(String) will try to use native JNI function nativeSetDataSource() thinking that your path is http/https/rtsp and obviously that call will fail as well without throwing any exception. Thats why your call to setDataSource(String) escapes without an exception and gets to prepare() call with the following exception.

Prepare failed.: status=0x1

So setDataSource(String) override cannot handle your resource file. You need to choose another override for that.

On the other side, check setDataSource(Context context, Uri uri, Map headers) which is used by setDataSource(Context context, Uri uri), it uses AssetFileDescriptor, ContentResolver from your context and openAssetFileDescriptor to open the URI which gets success as openAssetFileDescriptor() can open your resource file and finally the resultant fd is used to call setDataSource(FileDescriptor) override.

    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());
}

To conclude, you cannot use setDataSource(String) override as is to use your resource mp3 file. Instead, if you want use string to play your resource file you can use either MediaPlayer.create() static function with getIdentifier() as given above or setDataSource(context,uri) as given in Update#1.

Refer to the complete source code for more understanding here: Android MediaPlayer


Update #3:

openFrameworks setDataSource(String):

As I have mentioned in the comments below, openFrameworks uses android MediaPlayer code asis. If you can refer to Line no: 4,

import android.media.MediaPlayer;

and Line no: 26, 27, 28 and 218

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

private MediaPlayer player; //218

So, if you try to pass ardroid.resource//+ this.getPackageName() + "raw/test0" to setDataSource() using openFrameworks, you will still get the same exception as I explained in Update#2. Having said that, I just tried my luck searching Google to double sure what I am saying and found this openFrameworks forum link where one of the openFrameworks core developer arturo says,

don't know exactly how the mediaPlayer works but everything in res/raw
or bin/data gets copied to /sdcard/cc.openframeworks.packagename

Based on that comment, you may try using the copied path in setDataSource(). Using resource file on setDataSource(String) of MediaPlayer is not possible as it cannot accept resource file path. Please note that, I said "resource file path" starts with the scheme android.resource// which is actually a jar location (within your apk), not a physical location. Local file will work with setDataSource(String) which starts with the scheme file://.

To make you clearly understand what you are trying to do with a resource file, try executing this below code and see the result in logcat,

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

}

You'll get the result as,

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

that is to show you that the resource file you are trying to access is not actually a file in physical path but a jar location (within apk) which you cannot access using setDataSource(String) method. (Try using 7zip to extract your apk file and you will see the res/raw/test0 in it).

Hope that helps.

PS: I know its bit lengthy answer, but I hope this explains it in detail. Leaving the alternative solutions in the top if that can help others.

MediaPlayer setDataSource need best practice advice

There aren't any real benefits to the various ways of calling create or setDataSource. The static create methods don't do much more than call setDataSource and prepare. The various setDataSource methods call each other internally. Eventually they boil down to two possible native calls, one with a string describing a remote URI and another with a local file descriptor. There may be a very slight performance advantage to creating the file descriptor yourself, but it will not be appreciable in context.

For local file playback, as you are demonstrating in your code, simply calling prepare (or the static create methods) isn't a bad practice at all. The underlying player should have no problem determining the relevant metadata and returning quickly no matter the size of the file. The prepareAsync method is more useful for network streams, where any number of situations might cause some unexpected delay. If you are designing a general purpose player, then using the prepareAsync method would be the way to go, but if you are simply playing raw assets it shouldn't make any difference. The variety of methods provided is simply a matter of convenience (take note of the javadoc for create).

Android, VideoView, Internal Storage

Found the solution. Every way I've tested worked probably, because all errors I received came from "wrong downloaded media files"...

while((curRead = in.read(buf)) {
out.write(buf);
}

Was simply missing a ",0 ,curRead" - all working now. It's obviously because read() can always return less than BUFFER_SIZE and then writes 0-bytes into the rest or something...

Thanks!

SetDataSource in media player android

Example:

try {
FileDescriptor fd = null;

if (isInInternalMemory(audioFilename)) {
int audioResourceId = mContext.getResources().getIdentifier(audioFilename, "raw", "com.ampirik.audio");
AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(audioResourceId);
fd = afd.getFileDescriptor();
} else if (isInSdCard(audioFilename)) {
File baseDir = Environment.getExternalStorageDirectory();
String audioPath = baseDir.getAbsolutePath() + audioFilename + ".mp3";
FileInputStream fis = new FileInputStream(audioPath);
fd = fis.getFD();
}

if (fd != null) {
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(fd);
mediaPlayer.prepare();
mediaPlayer.start();
}
} catch (Exception e) {
e.printStackTrace();
}

This example is from another question with the same issue.

file not playing from Internal storage in Android

This is how I solve it.

 String musicUrl = "";
if (songSavedInDB()) {
musicUrl = "here is any file path(Internal or external)"
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(musicUrl);
mPlayer.setDataSource(fileInputStream.getFD());
fileInputStream.close();
mPlayer.prepare();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

} else {
musicUrl = "here is url";
try {
mPlayer.setDataSource(getContext(), Uri.parse(musicUrl));
mPlayer.prepareAsync();
} catch (IOException e) {
e.printStackTrace();
}
}


Related Topics



Leave a reply



Submit