1

I'm recording video with the aid of android.media.MediaRecorder class, which accepts path string for output file (MediaRecorder.setOutputFile(String)), though there is a version of the method which accepts FileDescriptor.

I need to store huge video files, so I want to use SD card. In order to get path to relevant directory I use Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM). It turns out that resulting path is for the “emulated” storage (/sdcard/…) instead of real SD card (/sdcard1/ on my Xperia Z3 Compact, Android 5.1.1).

I tried to hardcode /sdcard1/ (as well as /storage/sdcard1) but get an IOException talking about permission deny. Of course, I have

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

I heard about huge changes in access to SD card after 4.4 (aka Storage Access Framework), but couldn't find simple and clear enough explanation on how to get things done in such a case. Any help on short and concise solution for this?

PS Solution with hardcoded path would be OK for me as I'm going to use this app only with my phone.

Artem Pelenitsyn
  • 2,508
  • 22
  • 38
  • Possible duplicate of [differences between /sdcard/emulated/0 and /sdcard](http://stackoverflow.com/questions/20017468/differences-between-sdcard-emulated-0-and-sdcard) – Dhaval Parmar Feb 23 '16 at 10:26

2 Answers2

1

So I had to go “full circle” with above mentioned Storage Access Framework.

Step 1: Request for file creation

Note: fileName below is literally name of a file itself, without full (desired) path to it. E.g. myvideo.mp4.

private void createFile(String fileName) {
    String mimeType = "video/mp4";
    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);

    intent.addCategory(Intent.CATEGORY_OPENABLE);

    intent.setType(mimeType);
    intent.putExtra(Intent.EXTRA_TITLE, fileName);
    startActivityForResult(intent, WRITE_REQUEST_CODE /* is some random int constant*/);
}

Step 2: Callback for handling directory choice

After calling createFile (cf. step 1) a user will be supplied with a system dialog for choosing a directory where file will be stored. After they makes their choice the handler will be fired with desired URI for the new file. We can turn it into FileDescriptor and use corresponding version of MediaRecorder.setOutputFile.

@Override
public void onActivityResult(int requestCode, int resultCode,
                             Intent resultData) {

    if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        if (resultData != null) {
            URI outputFileUri = resultData.getData();
            FileDesriptor outputFileDescriptor = getContentResolver().openFileDescriptor(outputFileUri, "w").getFileDescriptor()
            mMediaRecorder.setOutputFile(outputFileDescriptor);
            mMediaRecorder.start();
        }
    }
}

This method should be implemented inside some Activity-class.

Room for improvement

So a user has to click Save in system menu each time they starts new video. It would be nice to restore pre-KitKat state when my application could decide the directory on its own. Considering my experience if using “professional” applications this indeed is possible. E.g. “ES Explorer” asks user to explicitly give permission for operations on SD card only once (using the alike system dialog).

Artem Pelenitsyn
  • 2,508
  • 22
  • 38
  • I followed your steps, but my prepareRecording() still throws IllegalStateException. This method works for any internal and external storages on Android 4.3 or older. It still works for internal storage except external storage on Android 5+ – Dante May 26 '16 at 12:38
  • @Dante The method described worked for me on Android 5 and 6. The sources are here: https://github.com/ulysses4ever/Background-VideoRecorder – Artem Pelenitsyn May 26 '16 at 18:05
  • I just tested it again, and it still threw an IllegalStateException. I used the new Storage Access Framework API to pick the folder. How did you pick your folder? Thanks – Dante Jun 07 '16 at 19:36
  • @Dante a directory is chosen by a user: they gets pop-up window for making a choice as a result of `startActivityForResult` from Step 1. Btw, didn't you try to look trough the code I linked above? – Artem Pelenitsyn Jun 07 '16 at 20:38
  • 1
    Sorry, it was an error with my recorder.prepareMethod(). Your answer works actually. Thank you for your kindness to help out the others. – Dante Jun 08 '16 at 15:36
1

This code should solve your problem:

 public String createVideoFilePath() {
        String time = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        File[] pathsArray = getExternalFilesDirs(Environment.DIRECTORY_DCIM);

/Usually, pathArray[1] contains path to necessary folder on removable SD card. So we can generally use just pathArray[1] instead of searching necessary File in pathArray with for statement/

        for (File f : pathsArray) {
            if ((f != null) && (Environment.isExternalStorageRemovable(f))) {
                return f.getPath() + File.separator +"video_"+ time + ".mp4";
            }
        }

        return pathsArray[0].getPath() + File.separator +"video_"+ time + ".mp4";

    }

This method returns location of file on removable SD card or in emulated storage, if removable SD doesn't exist

Just use this method as a paramter of MediaRecorder.setOutputFile(String) Update: It is very important to say, that all files, which locate in folders, gotten with Context. getExternalFilesDirs(String typr) will be removed after uninstalling your app.

  • It works, thank you! Though the path is a bit ugly (`/storage/sdcard1/Android/data/com.myapp/files`), so I probably stick to SAF solution. – Artem Pelenitsyn Feb 23 '16 at 19:19
  • I just can't see how this answer would work for sd card on lollipop or newer because it requires SAF. – Dante May 26 '16 at 12:12