I try to save a downloaded file with the ContentResolver to maintain metadata like artist, album and title.
First question:
Do i need a file with ID3 Tags or is it enough to specify the metadata via the MediaStore?
The MP3 file which gets downloaded doesn't have ID3 Tags.
Second question:
I use the following code to save the mp3 file to the MediaStore:
ContentValues values = new ContentValues(7);
values.put(MediaStore.Audio.Media.TITLE, audiodata.getTitle());
values.put(MediaStore.Audio.Media.ARTIST, audiodata.getArtist());
values.put(MediaStore.Audio.Media.ALBUM, audiodata.getAlbum());
values.put(MediaStore.Audio.Media.DATE_ADDED, (int) (System.currentTimeMillis() / 1000));
values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/mp3");
values.put(MediaStore.Audio.Media.DISPLAY_NAME, audiodata.getTitle() + ".mp3");
values.put(MediaStore.Audio.Media.RELATIVE_PATH, "Music/AudioConverter/");
Uri audiouri = getContentResolver().insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
ParcelFileDescriptor file = null;
try {
file = getContentResolver().openFileDescriptor(audiouri, "w");
boolean writtenToDisk = writeResponseBodyToDisk(response.body(), file);
btnSelectFile.setText("Datei wählen...");
pbMain.setVisibility(View.GONE);
} catch (FileNotFoundException e) {
e.printStackTrace();
pbMain.setVisibility(View.GONE);
Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
}
If I remove the extension ".mp3" from the display name, the Musicplayer shows me the right artist, album and so on. But if I leave the extension as shown above then no metadata gets saved except the display name.
Is there anything wrong? Do i miss something?
Related
I want to store my mp3 files recorded with my app to the Music folder with MediaStore for the new "Scoped Storage" of Android 10.
This method works good, but the files are named with timestamp (e.g. 1576253519449), without .mp3 extension.
If I add the extension manually with file manager, the file is recorded correctly.
How i can use fileName + ".mp3" to name the files?
String fileName; //Obtained by intent
MediaRecorder audioRecorder;
Uri audiouri;
ParcelFileDescriptor file;
private void startRecording() throws IOException {
ContentValues values = new ContentValues(4);
values.put(MediaStore.Audio.Media.TITLE, fileName);
values.put(MediaStore.Audio.Media.DATE_ADDED, (int) (System.currentTimeMillis() / 1000));
values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/mp3");
values.put(MediaStore.Audio.Media.RELATIVE_PATH, "Music/Recordings/");
audiouri = getContentResolver().insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
file = getContentResolver().openFileDescriptor(audiouri, "w");
if (file != null) {
audioRecorder = new MediaRecorder();
audioRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
audioRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
audioRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
audioRecorder.setOutputFile(file.getFileDescriptor());
audioRecorder.setAudioChannels(1);
audioRecorder.prepare();
audioRecorder.start();
}
}
And another question: MediaStore.Audio.Media.RELATIVE_PATH is only for Android 10.
How to maintain retrocompatibility with older releases?
EDIT:
For retrocompatibility, you can just remove values.put(MediaStore.Audio.Media.RELATIVE_PATH, "Music/Recordings/");: the files will be saved directly in Music directory.
Or use if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) to add the subdirectory only on Android 10.
As #blackapps suggested, can be used the DISPLAY_NAME property.
While this code successfully creates an image that is also present in the phone's gallery, the extension is '.jpg' instead of '.gif'.
File gifFile; // gif file stored in Context.getFilesDir()
final ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "Image" + System.currentTimeMillis());
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/gif");
// Create a new gif image using MediaStore
final Uri gifContentUri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
// Open a writable stream pointing to the new file created by MediaStore
OutputStream outputStream = context.getContentResolver().openOutputStream(gifContentUri, "w");
// Copy the original file from the app private data folder to the file created by MediaStore
IOUtils.copyFile(new FileInputStream(gifFile), outputStream);
Output file is created inside Pictures folder by MediaStore. If I manually change the output file's extension to gif, the gif animation is playing inside Android gallery.
I feel I'm missing a small detail for this to work
Removed the DISPLAY_NAME line.
Add contentValues.put(MediaStore.MediaColumns.DATA, "/storage/emulated/0/Pictures/Image." + System.currentTimeMillis() + ".gif");
It goes to a subdir of the Pictures directory if the subdir exists contentValues.put(MediaStore.MediaColumns.DATA, "/storage/emulated/0/Pictures/Mine/Image." + System.currentTimeMillis() + ".gif");.
For Android Q the DATA column is useless.
String displayName = "Image." + System.currentTimeMillis() + ".gif";
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName);
will do it there.
I'm trying to set the android default ringtone or notification tone via content provider from my assets folder.
Surprisingly, it works like this, but is it a legitimate way?
Uri audiouri = Uri.parse("content://"+BuildConfig.APPLICATION_ID+"/"+soundname+".mp3");
RingtoneManager.setActualDefaultRingtoneUri(a, TYPE_NOTIFICATION, audiouri );
Unfortunately, the sound name isn't shown in Android settings.
Strangely the sound name is actually shown when I go to 'Other sounds'
I also tried this:
Uri audiouri = Uri.parse("content://"+BuildConfig.APPLICATION_ID+"/"+soundname+".mp3");
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.TITLE, soundname);
Uri ringtoneuri = a.getContentResolver().insert(audiouri, contentValues);
RingtoneManager.setActualDefaultRingtoneUri(a, TYPE_NOTIFICATION, ringtoneuri);
resulting in a null sound (no sound is set)
third option I tried is:
Uri audiouri = MediaStore.Audio.Media.getContentUriForPath("content://"+BuildConfig.APPLICATION_ID+"/"+soundname+".mp3");
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DATA, "content://"+BuildConfig.APPLICATION_ID+"/"+soundname+".mp3");
contentValues.put(MediaStore.MediaColumns.TITLE, soundname);
Uri ringtoneuri = a.getContentResolver().insert(audiouri, contentValues);
RingtoneManager.setActualDefaultRingtoneUri(a, TYPE_NOTIFICATION, ringtoneuri);
Now the sound name is shown correctly, but no sound is actually played.
I get error on logcat:
java.io.FileNotFoundException: Can't access /content:/com.mydomain.myapp/test.mp3
So it seems it's taking the value from MediaColumns.DATA which does not support Content provider paths but only real paths. Right?
Final question: How to set tone AND name in android settings? Preferably without copying the file to external storage.
So, unfortunately I did not find out how to set asset as ringtone directly,
but this is a nice workaround:
When copying asset to internal app storage or cache dir (no permissions needed for that!) I was able to set the ringtone without WRITE_EXTERNAL_STORAGE permisson.
static void settone(int type, Sound sound, Activity a)
{
lastsound = sound; //global remember sound and type (alarm/ringtone/notification)
lasttype = type; // if we have to get permissions first, then call this from onActivityResult
if (canwritesystem(a))
{
RingtoneManager.setActualDefaultRingtoneUri(a, type, getringtoneuri(sound, a));
Toast.makeText(a, a.getString(R.string.settonesuccess), Toast.LENGTH_LONG).show();
}
else a.startActivityForResult(new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS).setData(Uri.parse("package:" + a.getPackageName())),CONTEXT_SET_TONE);
}
static Uri getringtoneuri(Sound sound, Activity a)
{
File tonefile = new File(sound.getpath); // path could be like: /Android/data/com.company.yourapp
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DATA, tonefile.getAbsolutePath());
contentValues.put(MediaStore.MediaColumns.TITLE, sound.getDisplayName());
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3");
contentValues.put(MediaStore.MediaColumns.SIZE, tonefile.length());
contentValues.put(MediaStore.Audio.Media.IS_RINGTONE, true);
contentValues.put(MediaStore.Audio.Media.IS_NOTIFICATION, true);
contentValues.put(MediaStore.Audio.Media.IS_ALARM, true);
contentValues.put(MediaStore.Audio.Media.IS_MUSIC, false);
Uri generalaudiouri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI;
a.getContentResolver().delete(generalaudiouri, MediaStore.MediaColumns.DATA + "='" + tonefile.getAbsolutePath() + "'", null);
return a.getContentResolver().insert(generalaudiouri, contentValues);
}
i am new to android, i copied files to /storage/sdcard1 from host pc using adb push.
But unable to view the file from gallery application.It is showing through ls command and when i rebooted the device , gallery application showing files properly.But immediately it is not updating in gallery , so can any one help me out for this?
Thanks in advance
You'll have to notify the media scanner to capture metadata of the newly created files. Apps like Gallery work on the metadata database and not directly on the filesystem.
Programmatically you'd use MediaScannerConnection.
Since you're working with adb, you can send a broadcast to invoke media scanner.
Media scanner runs as part of the boot sequence so that's why it works after reboot.
Because your gallery DB isn't updated.
You can run media scanner manually
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
or Use
MediaStore.Images.Media.insertImage();
You can also insert gallery(media) DB by hand.
private Uri insertMediaStore(String dirPath, String filename, byte[] jpegByteArray) {
String filePath = dirPath + "/" + filename;
try {
ContentValues values = new ContentValues();
values.put(Images.Media.DATE_TAKEN, new Date().getTime());
values.put(Images.Media.ORIENTATION, "0");
String title = filename.replace(".jpg", "");
values.put(Images.Media.TITLE, title);
values.put(Images.Media.DISPLAY_NAME, filename);
values.put(Images.Media.MIME_TYPE, "image/jpeg");
values.put(Images.Media.SIZE, jpegByteArray.length);
values.put("_data", filePath);
Uri uri = getContentResolver().insert(Images.Media.EXTERNAL_CONTENT_URI, values);
OutputStream os = getContentResolver().openOutputStream(uri);
os.write(jpegByteArray);
os.close();
Logger.info("MediaStore Inserted URI:" + uri.toString());
return uri;
} catch(Exception ex) {
Logger.error(ex, "Failed to save the Bitmap file. FilePath: %s", filePath);
}
return null;
}
code reference: http://helloworld.naver.com/helloworld/1819
So our app has the option to take either a picture or a video. If the user takes a picture, we can use the MediaStore.Images.Media.insertImage function to add the new image (via a filepath) to the phone's gallery and generate a content:// style URI. Is there a similar process for a captured video, given that we only have it's filepath?
Here is an easy 'single file based solution':
Whenever you add a file, let MediaStore Content Provider knows about it using
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(imageAdded)));
Main advantage: work with any mime type supported by MediaStore
Whenever you delete a file, let MediaStore Content Provider knows about it using
getContentResolver().delete(uri, null, null)
I'm also interested, could you find a solution?
Edit: solution is RTFM. Based on the "Content Providers" chapter here is my code that worked:
// Save the name and description of a video in a ContentValues map.
ContentValues values = new ContentValues(2);
values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
// values.put(MediaStore.Video.Media.DATA, f.getAbsolutePath());
// Add a new record (identified by uri) without the video, but with the values just set.
Uri uri = getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
// Now get a handle to the file for that record, and save the data into it.
try {
InputStream is = new FileInputStream(f);
OutputStream os = getContentResolver().openOutputStream(uri);
byte[] buffer = new byte[4096]; // tweaking this number may increase performance
int len;
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0, len);
}
os.flush();
is.close();
os.close();
} catch (Exception e) {
Log.e(TAG, "exception while writing video: ", e);
}
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
If your app is generating a new video and you simply want to give the MediaStore some metadata for it, you can build on this function:
public Uri addVideo(File videoFile) {
ContentValues values = new ContentValues(3);
values.put(MediaStore.Video.Media.TITLE, "My video title");
values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
values.put(MediaStore.Video.Media.DATA, videoFile.getAbsolutePath());
return getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
}
EDIT: As of Android 4.4 (KitKat), this method no longer works.
I was unable to get the Intent.ACTION_MEDIA_SCANNER_SCAN_FILE broadcast to work for me under API 21 (Lollipop), but the MediaScannerConnection does work, e.g.:
MediaScannerConnection.scanFile(
context, new String[] { path }, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
Log.d(TAG, "Finished scanning " + path + " New row: " + uri);
}
} );
Try this code. It seems working for me.
filePath = myfile.getAbsolutePath();
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DATA, filePath);
return context.getContentResolver().insert(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
Example of filePath -
/storage/emulated/0/DCIM/Camera/VID_20140313_114321.mp4