I'm saving an image in DCIM directory, but in cases, I need to delete it.
Previously, I called just image.delete(), where image is file. But now this image is saved in another way:
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM" + File.separator + IMAGES_FOLDER_NAME);
Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
OutputStream fos = resolver.openOutputStream(imageUri);
boolean saved = bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
I tried to make a query with its name and call contentResolver.delete(...), but it doesn't work.
I have permission to write external storage, but I don't want to use SAF.
How can I delete such file?
You need to use the delete method of ContentResolver using the Uri you got when you called insert.
Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
OutputStream fos = resolver.openOutputStream(imageUri);
boolean saved = bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
......
int result = resolver.delete(imageUri, null, null);
if (result > 0) {
Log.d("Tag", "File deleted");
}
If you didn't store the Uri you need to perform a query(), retrieve the content and then call delete.
intent.addFlags before invoke the method startActivityForResult.
like this
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Because it check permission in ContentProvider#enforceWritePermissionInner.
The real implement is in ActivityManagerService#checkPermission method.
File file = new File(delPath);
if (file.exists()) {
try {
Uri imageUri = FileProvider.getUriForFile(Context,
getApplicationContext()
.getPackageName() + ".provider", file);
ContentResolver contentResolver = getContentResolver();
int deletefile = contentResolver.delete(imageUri, null, null);
} catch (Exception e) {
e.printStackTrace();
}
}
Here delete path delPath is the file path of Image you want to delete from storage.
The complete solution should include also handling the potential errors which will occure on Andorid Q when trying to delete using ContentResolver. In that case, you should wrap your code in the try/catch block.
Below solution:
try {
// 1
getApplication<Application>().contentResolver.delete(
imageUri,"${MediaStore.Images.Media._ID} = ?",
arrayOf(imageId)
)
}
// 2
catch (securityException: SecurityException) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val recoverableSecurityException =
securityException as? RecoverableSecurityException
?: throw securityException
val intentSender = recoverableSecurityException.userAction.actionIntent.intentSender
startIntentSenderForResult(
intentSender,
DELETE_PERMISSION_REQUEST,
null,
0,
0,
0,
null
)
} else {
throw securityException
}
}
Here, you call contentResolver.delete() inside a try block since this method can throw a SecurityException at runtime. The method requires the ContentUri of the image you want to delete. In the where parameter, you specify that you want to delete an image based on its _ID. In the final parameter, you pass the string version of the _ID in an array.
In Android 10 and above, it isn’t possible to delete or modify items from MediaStore directly. You need permission for these actions. The correct approach is to first catch RecoverableSecurityException, which contains an intentSender that can prompt the user to grant permission. You then startIntentSenderForResult with the intentSender extracted from RecoverableSecurityException to grant the additional permission to delete file on Android Q.
Source: https://www.raywenderlich.com/9577211-scoped-storage-in-android-10-getting-started
Related
I am writing a new Application on Android 11 (SDK Version 30) and I simply cannot find an example on how to save a file to the external storage.
I read their documentation and now know that they basicly ignore Manifest Permissions (READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE). They also ignore the android:requestLegacyExternalStorage="true" in the manifest.xml application tag.
In their documentation https://developer.android.com/about/versions/11/privacy/storage they write you need to enable the DEFAULT_SCOPED_STORAGE and FORCE_ENABLE_SCOPED_STORAGE flags to enable scoped storage in your app.
Where do I have to enable those?
And when I've done that how and when do I get the actual permission to write to the external storage? Can someone provide working code? I want to save .gif, .png and .mp3 files. So I don't want to write to the gallery.
Thanks in advance.
Corresponding To All Api, included Api 30, Android 11 :
public static File commonDocumentDirPath(String FolderName)
{
File dir = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
{
dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/" + FolderName);
}
else
{
dir = new File(Environment.getExternalStorageDirectory() + "/" + FolderName);
}
// Make sure the path directory exists.
if (!dir.exists())
{
// Make it, if it doesn't exit
boolean success = dir.mkdirs();
if (!success)
{
dir = null;
}
}
return dir;
}
Now, use this commonDocumentDirPath for saving file.
A side note from comments, getExternalStoragePublicDirectory with certain scopes are now working with Api 30, Android 11. Cheers! Thanks to CommonsWare hints.
You can save files to the public directories on external storage.
Like Documents, Download, DCIM, Pictures and so on.
In the usual way like before version 10.
**Simplest Answer and Tested ( Java ) **
private void createFile(String title) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/html");
intent.putExtra(Intent.EXTRA_TITLE, title);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.parse("/Documents"));
}
createInvoiceActivityResultLauncher.launch(intent);
}
private void createInvoice(Uri uri) {
try {
ParcelFileDescriptor pfd = getContentResolver().
openFileDescriptor(uri, "w");
if (pfd != null) {
FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
fileOutputStream.write(invoice_html.getBytes());
fileOutputStream.close();
pfd.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/////////////////////////////////////////////////////
// You can do the assignment inside onAttach or onCreate, i.e, before the activity is displayed
String invoice_html;
ActivityResultLauncher<Intent> createInvoiceActivityResultLauncher;
#Override
protected void onCreate(Bundle savedInstanceState) {
invoice_html = "<h1>Just for testing received...</h1>";
createInvoiceActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
// There are no request codes
Uri uri = null;
if (result.getData() != null) {
uri = result.getData().getData();
createInvoice(uri);
// Perform operations on the document using its URI.
}
}
});
I'm using this method and it really worked for me
I hope I can help you. Feel free to ask me if something is not clear to you
Bitmap imageBitmap;
OutputStream outputStream ;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
{
ContentResolver resolver = context.getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME,"Image_"+".jpg");
contentValues.put(MediaStore.MediaColumns.MIME_TYPE,"image/jpeg");
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH,Environment.DIRECTORY_PICTURES + File.separator+"TestFolder");
Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,contentValues);
try {
outputStream = resolver.openOutputStream(Objects.requireNonNull(imageUri) );
imageBitmap.compress(Bitmap.CompressFormat.JPEG,100,outputStream);
Objects.requireNonNull(outputStream);
Toast.makeText(context, "Image Saved", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Toast.makeText(context, "Image Not Not Saved: \n "+e, Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
manifest file (Add Permission)
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
My code runs perfect on any Android lower then Android 11.
On Android 11 (specifically on Pixel2 & Pixel3 emulator and real devices) the file descriptor fail to find the file after inserting it to the media store.
If I change the relative path on the second line to be only the Environment.DIRECTORY_MOVIES path - it works
String relativeLocation = Environment.DIRECTORY_MOVIES;
This is my code:
String mimeType = "video/mp4";
String relativeLocation = Environment.DIRECTORY_MOVIES + File.separator + SUB_DIRECTORY_NAME ;
shortFileName = getDateStamp() + selectedExtension;
videoContentValues = new ContentValues();
videoContentValues.put(MediaStore.Video.Media.TITLE, shortFileName);
videoContentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, shortFileName);
videoContentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
videoContentValues.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
videoContentValues.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
videoContentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, relativeLocation);
videoContentValues.put(MediaStore.MediaColumns.IS_PENDING, 1);
videResolver = mContext.getContentResolver();
Uri contentUri = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
videoUri = videResolver.insert(contentUri, videoContentValues);
if (videoUri == null) {
throw new IOException("Failed to create new MediaStore record.");
}
pfd = mContext.getContentResolver().openFileDescriptor(videoUri, "w");
mMediaRecorder.setOutputFile(pfd.getFileDescriptor());
The code runs OK until it gets to the openFileDescriptor which return the following error:
java.io.FileNotFoundException: java.io.FileNotFoundException: No item at content://media/external_primary/video/media/47
What am I doing wrong?
Update 1:
More info:
I am still requesting WRITE_EXTERNAL_STORAGE and making sure it is allowed before start recording
The following code works in the same camera app on Android 11 Pixel 2 emulator to save pics:
String mimeType = "image/jpeg";
final String relativeLocation = Environment.DIRECTORY_PICTURES + File.separator + SUB_DIRECTORY_NAME;
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, createImageFileNameQ());
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, relativeLocation);
ContentResolver resolver = mContext.getContentResolver();
OutputStream stream = null;
Uri uri = null;
try {
final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
uri = resolver.insert(contentUri, contentValues);
try {
OutputStream fos = resolver.openOutputStream(uri);
fos.write(data);
fos.flush();
fos.close();
}
catch (Exception ex) {
Log.e(TAG, ex.toString());
}
Update 2:
If I write on the second line "String relativeLocation = Environment.DIRECTORY_PICTURES + File.separator + SUB_DIRECTORY_NAME;" and not DIRECTORY_MOVIES :
It works!#!!
Video files are saved on Videos\Sub_Directory
Update 3:
Now on the emulators:
no trick helps to make relative path.
And also the pics cannot be saved to relative path
String mimeType = "image/jpeg"; String relativeLocation = Environment.DIRECTORY_PICTURES + File.separator + SUB_DIRECTORY_NAME;
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, createImageFileNameQ());
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, relativeLocation);
ContentResolver resolver = mContext.getContentResolver();
OutputStream stream = null;
Uri uri = null;
try {
final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
uri = resolver.insert(contentUri, contentValues);
try {
OutputStream fos = resolver.openOutputStream(uri);
fos.write(data);
fos.flush();
fos.close();
} catch (Exception ex) {
Log.e(TAG, ex.toString());
}
} catch (Exception e) {}
I have opened a ticket to Google/Android with full recording of the emulator debug messages like they asked. they said: "interesting, they will look into it". they did not answer - but they have fixed it for the latest Android 11 release. Security patch Nov. 2020. So - problem was solved.
I have left a bug fix in my code in case somebody did not get the last android 11 update. In case of this error - I set IsAndroid11SaveOnSubDirectoryEnabled variable to false
String relativeLocation = Environment.DIRECTORY_MOVIES + File.separator + SUB_DIRECTORY_NAME;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q && IsAndroid11SaveOnSubDirectoryEnabled == false)
{
relativeLocation = Environment.DIRECTORY_MOVIES + File.separator + "$" + SUB_DIRECTORY_NAME;
Log.e(TAG, "Recording on $SubDirectory");
}
Edit II:
I got an answer from Google/Android that maybe I have a ".nomedia" file in the directory, and so - the Media store cannot insert/index new files. (.nomedia is a file that tells the gallery not to read the medias on this folder, it applies until Android 10, on Android 11 - this file cannot be created or deleted in the movies folder because it is not media). But it was not correct - the log files and recording that I have sent to Google/Android were from an Android 11 Emulator which did not have this file.
Edit III - Since Dilijeet asked, IsAndroid11SaveOnSubDirectoryEnabled is a boolean that I save to the SharedPreference. here is the code - whenever the recording crash on IOExcpetion & Android 11 - I assume it is the subdirectory problem, and set this boolean for further recordings.
catch (IOException ex)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && IsAndroid11SaveOnSubDirectoryEnabled == true) {
mSharedPreferences.edit().putBoolean("chkSaveOnSubDirectory", false).apply();
IsAndroid11SaveOnSubDirectoryEnabled = false;
}
I have a chat application. If a user sends an image i save that image to internal storage of the app under
data/data/package_name/....
So if the user clicks on that image i send an intent to the system to open it
val uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID, File(it.localUri))
val intent = Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
setDataAndType(uri, "image/*")
}
startActivity(intent)
The problem is that in the image viewer there is no choice to save the file in the public storage of the phone, in that case in the gallery.
Is there any way to do that without changing the internal storage default save location of the images in my app?
If this is something you are interested in, you can use MediaStore to programmatically save in the gallery a picture from your internal storage.
Code
You could use the following code, which I successfully use in my app.
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "MyPicture.jpg");
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
}
ContentResolver resolver = myContext.getContentResolver();
Bitmap bitmap;
Uri uri;
try {
// Requires permission WRITE_EXTERNAL_STORAGE
bitmap = BitmapFactory.decodeFile(currentPhotoPath);
uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
} catch (Exception e) {
Log.e(LOG_TAG, "Error inserting picture in MediaStore: " + e.getMessage());
return;
}
try (OutputStream stream = resolver.openOutputStream(uri)) {
if (!bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream) {
throw new IOException("Error compressing the picture.");
}
} catch (Exception e) {
if (uri != null) {
resolver.delete(uri, null, null);
}
Log.e(LOG_TAG, "Error adding picture to gallery: " + e.getMessage());
}
Credits to this answer for the conversion of File to Bitmap and to this answer for the usage of MediaStore.
Notes
BitmapFactory.decodeFile() requires the runtime permission WRITE_EXTERNAL_STORAGE
With the introduction of scoped memory in Android 10 (which will become mandatory in Android 11, see this article) MediaStore is probably the only reliable way to save pictures to the gallery
A consequence of scoped storage is that, yes, you should keep using the internal storage for the cache copy of your picture.
Tests
I have tested this code with with targetSdkVersion 29 on the following devices / OS combinations
Samsung Galaxy S10 / API 29
Samsung Galaxy S9 / API 29
Huawei Nexus 6P / API 27
Hello so I write a small game where in the end you can share your result. The result is written on an image using canvas. The problem is when sharing i get the error "Error, could not locate the file". The error is seen on screen only and not reflected in logcat. I've already spent countless hours trying to solve it, but nothing seems to work. I get no errors what so ever but the file still appears to be impossible to share. Does anyone has a suggestion on why it does not work?
Quick recap: Load bitmap, make it a canvas, paint it, check for permissions to save, save it, get the URI of the saved file, use the URI inside of the share intent. I really don't see what is missing.
The canvas painting part was tested separately and I was able to share the bitmap to Facebook using fb library. Unfortunately android native share does not allow to share bitmaps without saving them.
In manifest I have WRITE and READ permissions for both internal and external storage. I would really appreciate any help.
Share button on click listener:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.Myimage);
mutableBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
Canvas canvas = new Canvas(mutableBitmap);
Paint paint = new Paint();
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
paint.setColor(Color.BLACK);
paint.setTextSize(170);
int top_margin = 1000;
int left_margin = 1700;
canvas.drawText("You got a ton of points", left_margin, top_margin, paint);
ActivityCompat.requestPermissions(test_process.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
Permission result:
#Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case 1: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
sharethis(mutableBitmap);
} else {
Toast.makeText(test_process.this, "Permission denied to read your External storage", Toast.LENGTH_SHORT).show();
}
return;
}
}
}
Share method:
public void sharethis(Bitmap bitmap){
File file_path = getFilesDir();
File file = new File(file_path, "resultImg.jpg");
FileOutputStream fOut;
try {
fOut = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fOut);
fOut.flush();
fOut.close();
} catch (Exception e) {
e.printStackTrace();
Log.i("file saving problem", String.valueOf(e));
}
Uri uri = Uri.fromFile(file);
Uri uriContent = getImageContentUri(this, file);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.setType("image/jpeg");
Log.i("Uri", String.valueOf(uri));
Log.i("UriContent", String.valueOf(uriContent));
intent.putExtra(Intent.EXTRA_STREAM, uriContent);
startActivity(Intent.createChooser(intent, "Share Cover Image"));
}
And URI convertor:
public static Uri getImageContentUri(Context context, File imageFile) {
String filePath = imageFile.getAbsolutePath();
Cursor cursor = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Images.Media._ID },
MediaStore.Images.Media.DATA + "=? ",
new String[] { filePath }, null);
if (cursor != null && cursor.moveToFirst()) {
int id = cursor.getInt(cursor
.getColumnIndex(MediaStore.MediaColumns._ID));
Uri baseUri = Uri.parse("content://media/external/images/media");
return Uri.withAppendedPath(baseUri, "" + id);
} else {
if (imageFile.exists()) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATA, filePath);
return context.getContentResolver().insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} else {
return null;
}
}
}
getImageContentUri() is not going to work. Your file is on internal storage; third-party apps, including the MediaStore, have no access to it.
Get rid of getImageContentUri(). Set up FileProvider to serve files from getFilesDir(). Then, use FileProvider.getUriForFile() to get a Uri that you can use in your ACTION_SEND Intent.
Also:
You will need to add FLAG_GRANT_READ_URI_PERMISSION to the ACTION_SEND Intent
You do not need READ_EXTERNAL_STORAGE for any of this
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