Android - How To Upload Video In Chunks Using Okhttp?
please see my below code which I use to upload a video to a server. However, for large enough videos, I'm getting an OutOfMemory exception. InputStream stream = getContent
Solution 1:
You can create a custom RequestBody
which streams the data. You will have to take some care: it may be reused multiple times because OkHttp may decide to retry the request. Make sure you can re-open the InputStream
from the start each time.
ContentResolvercontentResolver= context.getContentResolver();
finalStringcontentType= contentResolver.getType(videoUri);
finalAssetFileDescriptorfd= contentResolver.openAssetFileDescriptor(videoUri, "r");
if (fd == null) {
thrownewFileNotFoundException("could not open file descriptor");
}
RequestBodyvideoFile=newRequestBody() {
@OverridepubliclongcontentLength() { return fd.getDeclaredLength(); }
@Overridepublic MediaType contentType() { return MediaType.parse(contentType); }
@OverridepublicvoidwriteTo(BufferedSink sink)throws IOException {
try (InputStreamis= fd.createInputStream()) {
sink.writeAll(Okio.buffer(Okio.source(is)));
}
}
};
RequestBodyrequestBody=newMultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", "fname", videoFile)
.build();
Requestrequest=newRequest.Builder()
.url(uploadURL)
.post(requestBody)
.build();
client.newCall(request).enqueue(newCallback() {
@OverridepublicvoidonFailure(Call call, IOException e) {
try {
fd.close();
} catch (IOException ex) {
e.addSuppressed(ex);
}
Log.e(TAG, "failed", e);
}
@OverridepublicvoidonResponse(Call call, Response response)throws IOException {
fd.close();
}
});
Solution 2:
- Tries to convert
Uri
toInputStream
using one ofAssetFileDescriptor
/ContentResolver
/ParcelFileDescriptor
- Creates
okio.Source
fromInputStream
(about source/sink) - Write
Source
intoSink
, then closeInputStream
If you know what server expects as contentType
, hardcode it. Because server might only accept application/octet-stream
even if file is video/mp4
. Uri to contentType
Check for Uri to contentLength, if contentLength is not found, Content-Length: X
header will not be appended when uploading.
/** It supports file/content/mediaStore/asset URIs. asset not tested */funcreateAssetFileDescriptor() = try {
contentResolver.openAssetFileDescriptor(this, "r")
} catch (e: FileNotFoundException) {
null
}
/** It supports file/content/mediaStore URIs. Will not work with providers that return sub-sections of files */funcreateParcelFileDescriptor() = try {
contentResolver.openFileDescriptor(this, "r")
} catch (e: FileNotFoundException) {
null
}
/** - It supports file/content/mediaStore/asset URIs. asset not tested
* - When file URI is used, may get contentLength error (expected x but got y) error when uploading if contentLength header is filled from assetFileDescriptor.length */funcreateInputStreamFromContentResolver() = try {
contentResolver.openInputStream(this)
} catch (e: FileNotFoundException) {
null
}
fun Uri.asRequestBody(contentResolver: ContentResolver,
contentType: MediaType? = null,
contentLength: Long = -1L)
: RequestBody {
returnobject : RequestBody() {
/** If null is given, it is binary for Streams */overridefuncontentType() = contentType
/** 'chunked' transfer encoding will be used for big files when length not specified */overridefuncontentLength() = contentLength
/** This may get called twice if HttpLoggingInterceptor is used */overridefunwriteTo(sink: BufferedSink) {
val assetFileDescriptor = createAssetFileDescriptor()
if (assetFileDescriptor != null) {
// when InputStream is closed, it auto closes AssetFileDescriptor
AssetFileDescriptor.AutoCloseInputStream(assetFileDescriptor)
.source()
.use { source -> sink.writeAll(source) }
} else {
val inputStream = createInputStreamFromContentResolver()
if (inputStream != null) {
inputStream
.source()
.use { source -> sink.writeAll(source) }
} else {
val parcelFileDescriptor = createParcelFileDescriptor()
if (parcelFileDescriptor != null) {
// when InputStream is closed, it auto closes ParcelFileDescriptor
ParcelFileDescriptor.AutoCloseInputStream(parcelFileDescriptor)
.source()
.use { source -> sink.writeAll(source) }
} else {
throw IOException()
}
}
}
}
}
}
Usage:
valrequest= uri.asRequestBody(
contentResolver = context.contentResolver,
contentType = "application/octet-stream".toMediaTypeOrNull(),
contentLength = uri.length(context.contentResolver)
)
Post a Comment for "Android - How To Upload Video In Chunks Using Okhttp?"