I'm trying to include a folder browser element in my app, which upon clicking an audio file, locates that file in the MediaStore and returns it's _ID.
One of the issues I'm having is that for some devices, it seems the absolute file path works fine, but for other devices, I need the canonical paths. In some cases, it doesn't work at all!
String canonicalPath = file.getCanonicalPath();
String absolutePath = file.getAbsolutePath();
For what it's worth, the 'file' is created from the root "/", not from getExternalStorageDirectory() (because some Audio files may be located on the internal sd or elsewhere).
So the query looks like this:
Cursor cursor = getActivity().getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cols, MediaStore.MediaColumns.DATA + "= ? ", new String[]{absolutePath}, null);
If it fails, I do the same query on the canonicalPath, and see if that works. The issue is that it fails in both cases on a lot of different devices, depending on the file structure.
On my particular device (Galaxy S2), the difference between the absolute and canonical paths is usually as follows:
/extSdCard/Music/myFile.mp3
/storage/sdcard1/Music/myFile.mp3
Am I wrong to assume the file will always have an ID in the MediaStore? Is there a better way to find the 'actual' path to the file, or a better way to construct the query to get the id of the file? It's crashing all over the place on various devices, due to the query not returning and cursor items (in other words, it can't find anywhere where the MediaColumns.DATA is equal to the absolute or canoncial path passed in.
I guess I'm just looking for a way to have a more failproof query.. A better way to find the path or the id of the audio file. Any ideas would be appreciated.
Related
This question already has answers here:
Get path from recorded video
(1 answer)
Android Kotlin: Getting a FileNotFoundException with filename chosen from file picker?
(5 answers)
Closed 10 months ago.
I need to get the file path from my onActivityResult Uri in Kotlin.
When I google for that I get a ton of different ways on how to do it, most of them are not working and a lot seem uneccessary complex, some of them solutions are even 8 years old.
I also found some librarys like PickIt and Simplestorage.
What is currently the best practice (or one of the best practices) to get a file path from Uri?
I'm building a Sound Editor and I need the real file path of the selected Sound File for that.
Storing the sound file as a temp file in my local storage would also be a solution for me, whats the best practice there?
P.S I'm also using Jetpack Compose if that matters
Use MediaStore for the latest versions of Android, that's the recommended practice.
val projection = arrayOf(media-database-columns-to-retrieve)
val selection = sql-where-clause-with-placeholder-variables
val selectionArgs = values-of-placeholder-variables
val sortOrder = sql-order-by-clause
applicationContext.contentResolver.query(
MediaStore.media-type.Media.EXTERNAL_CONTENT_URI,
projection,
selection,
selectionArgs,
sortOrder
)?.use { cursor ->
while (cursor.moveToNext()) {
// Use an ID column from the projection to get
// a URI representing the media item itself.
}
}
This is the basic structure of the API usage, you an read full details here.
Basically, you create a Directory in one of the default Android folders (in your case, it may be the 'Music' Directory), and then you read and write to that specific directory which is designated to your app.
Columns have default values, like name, thumbnail etc. You can get full URIs as well, there are a couple of methods available for that within the API, I think. You can read files using FileDescriptors like so
// Open a specific media item using ParcelFileDescriptor.
val resolver = applicationContext.contentResolver
// "rw" for read-and-write;
// "rwt" for truncating or overwriting existing file contents.
val readOnlyMode = "r"
resolver.openFileDescriptor(content-uri, readOnlyMode).use { pfd ->
// Perform operations on "pfd".
}
Maybe have a look here - How to get the Uri from MediaStore via file path?
This question already has answers here:
Create a file from a photo URI on Android
(1 answer)
Android Kotlin: Getting a FileNotFoundException with filename chosen from file picker?
(5 answers)
Closed 2 years ago.
I developed an android-app for a customer. He can:
pick multiple files (images, videos, files). These files are added to a ListView (containing only filenames).
start an upload of these files to a server later on.
Yet, i save the file-paths internally and whenever the upload-button is clicked, i pick the files from the saved paths and transmit them to the server.
The problem is that all my methods for getting the path, use depcreated methods and deprecated media-columns like f.i.
MediaStore.Video.Media.DATA
So i got to get rid of these and, as API10 recommends, don't work with absolute paths anymore.
I' m getting the filename like this:
Cursor cursor = getApplicationContext().getContentResolver().query(uri, null, null, null, null);
int indexedname = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
cursor.moveToFirst();
String filename = cursor.getString(indexedname);
cursor.close();
QUESTION: So far so good. I got the file for the upload list. But how do i get the file only by name and without absolute path for the upload later on, whenever the user clicks on the upload-button?
I'm able to get a DocumentFile and the MIME from the ContentResolver and much more, but no File!
Thank you for your help in advance!
The main idea of the question is just same as the title - what is the difference between .getPath() vs cursor, when you get the real path of a file from uri in Android?
In case you don't get what I meant by using cursor, the example is here.
private String getRealPathFromURI(Uri contentURI) {
String result;
Cursor cursor = getContentResolver().query(contentURI, null, null, null, null);
if (cursor == null) { // Source is Dropbox or other similar local file path
result = contentURI.getPath();
} else {
cursor.moveToFirst();
int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
result = cursor.getString(idx);
cursor.close();
}
return result;
}
The two most frequent ways were these two, but it seems a bit too complicated using cursor, while you can get the same result with one simple method, .getPath(). So, I think there must be the reason I should use the cursor in some cases, but I can't get it.
Could you explain me what it would be?
what is the difference between .getPath() vs cursor, when you get the real path of a file from uri in Android?
A Uri is not a file. There is no "real path".
If the scheme of the Uri is file, then it represents a file on the filesystem that, in theory, your app should be able to access. Use getPath() to get the filesystem path.
If the scheme is anything else, it does not necessarily represent a file on the filesystem that your app can access. For example, if the scheme is http or https, the Uri represents something that would be downloaded from a Web server.
If the scheme is content, then it is backed by a ContentProvider. Use a ContentResolver and openInputStream() to get an InputStream on the content identified by the Uri.
If the scheme is content and you specifically obtained the Uri from the MediaStore, then perhaps your Cursor approach will give you a path. It also might give you null, and the path that you get may not be accessible to you (just because the system's MediaStore can index a file does not imply that your app has access to that same file). This is worse on Android 10, where you do not have read access to external storage by default. Hence, this technique is unreliable and should not be used.
Beyond that, though, you cannot make any assumptions about what data is used to support that content Uri. It could be:
A local file on external storage
A local file on internal storage for the other app (e.g., served by FileProvider)
A local file on removable storage
A local file that is encrypted and needs to be decrypted on the fly by the ContentProvider
A stream of bytes held in a BLOB column in a database that needs to be served by the ContentProvider
A piece of content that needs to be downloaded by the other app first (e.g., Dropbox)
...and so on
So, to recap: a Uri is not a file. There is no "real path".
I'm trying to fetch all the music files in my phone:
For this I'm using:
String[] STAR = {"*"};
Uri allExternalSongUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String selection = MediaStore.Audio.Media.IS_MUSIC + " != 0";
Cursor cursor = getContentResolver().query(allExternalSongUri, STAR, selection, null, null);
if(cursor != null){
if(cursor.moveToFirst()){
do {
String songName = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME));
Log.i("name", songName);
} while (cursor.moveToNext());
}
cursor.close();
}
But above code, apart from getting music files, is also fetching some additional unnecessary files like *sound_screen_on.mp3* (which is installed & used by some other app).
Issue is my native android music player does not list & plays these unnecessary files.
How can I filter files like these.
You can filter using MediaStore.Audio.AudioColumns class.That will return lots of value of Audio file and you can use it.
musiccursor = managedQuery(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
proj, MediaStore.Audio.Media.DURATION + ">= 60000", null, null);
Filter by Duration - The duration of the audio file, in ms
Filter by Type-Music -Non-zero if the audio file is music
Filter by Type-RingTone - Non-zero if the audio file may be a ringtone
Filter by Type-Alarm - Non-zero if the audio file may be an alarm
Check for more reference
Aside from the above methods suggested by others, in the case where column is not reliable from the db, you can always use MediaMetadataRetriever to get the information you want after the first filtering from the db.
http://developer.android.com/reference/android/media/MediaMetadataRetriever.html
This is of course slower than getting existing information from the media db, but it will give you author/album/duration etc other information and you can do as custom filter as you can get with the file list that you get from the ContentProvider.
Using your same code, i dont get any undesired file.
Usually that files are marked to be ignored, so probably the media scanner did something wrong or the index is not updated in your system.
Sounds, images and other media files that are meant to be used by one app or by the system, are marked to be ignored simply by adding a file called ".nomedia" in the same directory. As you can read in the docs of the MediaStore provider
public static final String MEDIA_IGNORE_FILENAME
Added in API level 9 Name of the file signaling the media scanner to
ignore media in the containing directory and its subdirectories.
Developers should use this to avoid application graphics showing up in
the Gallery and likewise prevent application sounds and music from
showing up in the Music app.
Constant Value: ".nomedia"
So, besides of the advices from chintan khetiya, that are great, probably you can consider that there are some files marked to be ignored, that are not actually being ignored, and check it by yourself.
Simply, when you iterating the cursor, for every file, just check if in the same directory there is a file called like the value of MediaStore.MEDIA_IGNORE_FILENAME or the hardcoded .nomedia
only get the music which the _data is more than a certain value, for example 300kb.
I've been working on a small media player app for Android. I'm having some trouble retrieving the meta data from the music files. I've been using the MediaMetadataRetriever, but it has proved to be quite troublesome. Does anyone know of a better way to go about this? If so how would one implement such method?
I've used JAudioTagger, which you can find here. It supports (basically) every version of ID3, so you're covered even for old files with outdated metadata. Pretty good (and easy) solution. Other options include mp3agic and entagged.
I was able to read the metadata from the Android database, using the code here. Just change the managedquery to something like:
String selection = MediaStore.Audio.Media.DATA + " == ?";
cursor = this.managedQuery(media,
projection,
selection,
new String[] {file.getCanonicalPath().toString()},
null);
This returns a cursor to the metadata of file. (Author, Title, duration etc.)