Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to write data to a File using a Uri #114

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,22 @@
*/
package com.linkedin.android.litr.exception;

import android.net.Uri;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;

public class MediaTargetException extends MediaTransformationException {

private static final String INVALID_PARAMS_TEXT = "Invalid parameters";
private static final String IO_FAILURE_TEXT = "Failed to open the media target for write.";
private static final String UNSUPPORTED_URI_TYPE_TEXT = "URI type not supported at API level below 26";

private final Error error;
private final String outputFilePath;
private final int outputFormat;

public MediaTargetException(@NonNull Error error, @NonNull String outputFilePath, @IntRange(from=0, to=2) int outputFormat) {
this(error, outputFilePath, outputFormat, new Throwable());
public MediaTargetException(@NonNull Error error, @NonNull Uri outputFileUri, @IntRange(from=0, to=2) int outputFormat, @NonNull Throwable cause) {
this(error, outputFileUri.toString(), outputFormat, cause);
}

public MediaTargetException(@NonNull Error error, @NonNull String outputFilePath, @IntRange(from=0, to=2) int outputFormat, @NonNull Throwable cause) {
Expand All @@ -32,7 +34,8 @@ public MediaTargetException(@NonNull Error error, @NonNull String outputFilePath

public enum Error {
INVALID_PARAMS(INVALID_PARAMS_TEXT),
IO_FAILUE(IO_FAILURE_TEXT);
IO_FAILUE(IO_FAILURE_TEXT),
UNSUPPORTED_URI_TYPE(UNSUPPORTED_URI_TYPE_TEXT);

private final String text;
Error(String text) {
Expand All @@ -50,7 +53,7 @@ public Error getError() {
public String toString() {
return super.toString() + '\n'
+ error.text + '\n'
+ "Output file path: " + outputFilePath + '\n'
+ "Output file path or Uri encoded string: " + outputFilePath + '\n'
+ "MediaMuxer output format: " + outputFormat;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,25 @@
*/
package com.linkedin.android.litr.io;

import android.content.Context;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.linkedin.android.litr.exception.MediaTargetException;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.LinkedList;

import static com.linkedin.android.litr.exception.MediaTargetException.Error.INVALID_PARAMS;
import static com.linkedin.android.litr.exception.MediaTargetException.Error.IO_FAILUE;
import static com.linkedin.android.litr.exception.MediaTargetException.Error.UNSUPPORTED_URI_TYPE;

/**
* An implementation of MediaTarget, which wraps Android's {@link MediaMuxer}
Expand All @@ -40,29 +44,66 @@ public class MediaMuxerMediaTarget implements MediaTarget {

private MediaFormat[] mediaFormatsToAdd;

private ParcelFileDescriptor parcelFileDescriptor;
private String outputFilePath;
private int numberOfTracksToAdd;
private int trackCount;

public MediaMuxerMediaTarget(@NonNull String outputFilePath, @IntRange(from = 1) int trackCount, int orientationHint, int outputFormat) throws MediaTargetException {
this.outputFilePath = outputFilePath;
this.trackCount = trackCount;

/**
* Create an instance using input URI. On Android Oreo (API level 26) and above it can be any URI writeable by the
* provided context, otherwise it must be a "file://" URI. This constructor is especially useful when working with
* FileProvider created URI to comply with scoped storage enforcement on Android 10+
*/
public MediaMuxerMediaTarget(@NonNull Context context, @NonNull Uri outputFileUri,
izzytwosheds marked this conversation as resolved.
Show resolved Hide resolved
@IntRange(from = 1) int trackCount, int orientationHint, int outputFormat) throws MediaTargetException {
try {
mediaMuxer = new MediaMuxer(outputFilePath, outputFormat);
mediaMuxer.setOrientationHint(orientationHint);
MediaMuxer mediaMuxer;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
parcelFileDescriptor = context.getContentResolver().openFileDescriptor(outputFileUri, "rwt");
if (parcelFileDescriptor != null) {
mediaMuxer = new MediaMuxer(parcelFileDescriptor.getFileDescriptor(), outputFormat);
} else {
throw new IOException();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe MediaTargetException here as well? Use IO_FAILURE enum value

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@izzytwosheds, already the MediaTargetException with IO_FAILURE will be thrown after throwing the IOException because there is an IOException catch case with the try, else if you prefer to directly throw the MediaTargetException here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, I see it now. Should we add a helpful message to IOException? Something like "Inaccessible URI" + outputFileUri ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think yes, and it's fixed now

}
} else if ("file".equalsIgnoreCase(outputFileUri.getScheme()) && outputFileUri.getPath() != null) {
mediaMuxer = new MediaMuxer(outputFileUri.getPath(), outputFormat);
} else {
throw new MediaTargetException(UNSUPPORTED_URI_TYPE, outputFileUri, outputFormat, new Throwable());
}
init(mediaMuxer, trackCount, orientationHint);
} catch (IllegalArgumentException illegalArgumentException) {
throw new MediaTargetException(INVALID_PARAMS, outputFileUri, outputFormat, illegalArgumentException);
} catch (IOException ioException) {
releaseFileDescriptor();
throw new MediaTargetException(IO_FAILUE, outputFileUri, outputFormat, ioException);
}
}

numberOfTracksToAdd = 0;
isStarted = false;
queue = new LinkedList<>();
mediaFormatsToAdd = new MediaFormat[trackCount];
public MediaMuxerMediaTarget(@NonNull String outputFilePath, @IntRange(from = 1) int trackCount,
int orientationHint, int outputFormat) throws MediaTargetException {
this.outputFilePath = outputFilePath;
try {
MediaMuxer mediaMuxer = new MediaMuxer(outputFilePath, outputFormat);
init(mediaMuxer, trackCount, orientationHint);
} catch (IllegalArgumentException illegalArgumentException) {
throw new MediaTargetException(INVALID_PARAMS, outputFilePath, outputFormat, illegalArgumentException);
} catch (IOException ioException) {
throw new MediaTargetException(IO_FAILUE, outputFilePath, outputFormat, ioException);
}
}

private void init(@NonNull MediaMuxer mediaMuxer, @IntRange(from = 1) int trackCount, int orientationHint) throws IllegalArgumentException {
this.trackCount = trackCount;

this.mediaMuxer = mediaMuxer;
this.mediaMuxer.setOrientationHint(orientationHint);

numberOfTracksToAdd = 0;
isStarted = false;
queue = new LinkedList<>();
mediaFormatsToAdd = new MediaFormat[trackCount];
}

@Override
public int addTrack(@NonNull MediaFormat mediaFormat, @IntRange(from = 0) int targetTrack) {
mediaFormatsToAdd[targetTrack] = mediaFormat;
Expand Down Expand Up @@ -106,12 +147,23 @@ public void writeSampleData(int targetTrack, @NonNull ByteBuffer buffer, @NonNul
@Override
public void release() {
mediaMuxer.release();
releaseFileDescriptor();
}

@Override
@NonNull
public String getOutputFilePath() {
return outputFilePath;
return outputFilePath != null ? outputFilePath : "";
}

private void releaseFileDescriptor() {
try {
if (parcelFileDescriptor != null) {
parcelFileDescriptor.close();
parcelFileDescriptor = null;
}
} catch (IOException ignored) {
}
}

private class MediaSample {
Expand Down