I'm a beginner in Android programming.
I'm writing an application to list all video file in a folder and display information of all videos in the folder. But when i try to get the video duration it return null and i can't find the way to get it.
Any one can help me?
Below is my code:
Uri uri = Uri.parse("content://media/external/video/media/9");
Cursor cursor = MediaStore.Video.query(res, data.getData(), new String[]{MediaStore.Video.VideoColumns.DURATION});
if(cursor.moveToFirst()) {
String duration = cursor.getString(0);
System.out.println("Duration: " + duration);
}
Use MediaMetadataRetriever to retrieve media specific data:
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
//use one of overloaded setDataSource() functions to set your data source
retriever.setDataSource(context, Uri.fromFile(videoFile));
String time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
long timeInMillisec = Long.parseLong(time );
retriever.release()
I think the easiest way is:
MediaPlayer mp = MediaPlayer.create(this, Uri.parse(uriOfFile));
int duration = mp.getDuration();
mp.release();
/*convert millis to appropriate time*/
return String.format("%d min, %d sec",
TimeUnit.MILLISECONDS.toMinutes(duration),
TimeUnit.MILLISECONDS.toSeconds(duration) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(duration))
);
Kotlin Extension Solution
Here is the way to fetch media file duration in Kotlin
fun File.getMediaDuration(context: Context): Long {
if (!exists()) return 0
val retriever = MediaMetadataRetriever()
retriever.setDataSource(context, Uri.parse(absolutePath))
val duration = retriever.extractMetadata(METADATA_KEY_DURATION)
retriever.release()
return duration.toLongOrNull() ?: 0
}
If you want to make it safer (Uri.parse could throw exception), use this combination. The others are generally just useful as well :)
fun String?.asUri(): Uri? {
try {
return Uri.parse(this)
} catch (e: Exception) {
}
return null
}
val File.uri get() = this.absolutePath.asUri()
fun File.getMediaDuration(context: Context): Long {
if (!exists()) return 0
val retriever = MediaMetadataRetriever()
retriever.setDataSource(context, uri)
val duration = retriever.extractMetadata(METADATA_KEY_DURATION)
retriever.release()
return duration.toLongOrNull() ?: 0
}
Not necessary here, but generally helpful additional Uri extensions
val Uri?.exists get() = if (this == null) false else asFile().exists()
fun Uri.asFile(): File = File(toString())
I don't think you post your URI into the mediastore video query
Uri uri = Uri.parse("content://media/external/video/media/9");
Cursor cursor = MediaStore.Video.query(res, data.getData(), new String[]{MediaStore.Video.VideoColumns.DURATION});
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(uriOfFile);
long duration = Long.parseLong(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION))
int width = Integer.valueOf(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
int height = Integer.valueOf(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
retriever.release();
public static long getDurationOfSound(Context context, Object soundFile)
{
int millis = 0;
MediaPlayer mp = new MediaPlayer();
try
{
Class<? extends Object> currentArgClass = soundFile.getClass();
if(currentArgClass == Integer.class)
{
AssetFileDescriptor afd = context.getResources().openRawResourceFd((Integer)soundFile);
mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
afd.close();
}
else if(currentArgClass == String.class)
{
mp.setDataSource((String)soundFile);
}
else if(currentArgClass == File.class)
{
mp.setDataSource(((File)soundFile).getAbsolutePath());
}
mp.prepare();
millis = mp.getDuration();
}
catch(Exception e)
{
// Logger.e(e.toString());
}
finally
{
mp.release();
mp = null;
}
return millis;
}
MediaPlayer mpl = MediaPlayer.create(this,R.raw.videoFile);
int si = mpl.getDuration();
This will give the duration of the video file
object MediaHelper {
const val PICK_IMAGE_FROM_DEVICE = 100
const val PICK_MEDIA_FROM_DEVICE = 101
fun openPhoneGalleryForImage(fragment: Fragment){
Intent(Intent.ACTION_GET_CONTENT).run {
this.type = "image/*"
fragment.startActivityForResult(this, PICK_IMAGE_FROM_DEVICE)
}
}
fun openPhoneGalleryForMedia(fragment: Fragment){
Intent(Intent.ACTION_GET_CONTENT).run {
this.type = "*/*"
fragment.startActivityForResult(this, PICK_MEDIA_FROM_DEVICE)
}
}
fun getMediaType(context: Context, source: Uri): MediaType? {
val mediaTypeRaw = context.contentResolver.getType(source)
if (mediaTypeRaw?.startsWith("image") == true)
return MediaType.Image
if (mediaTypeRaw?.startsWith("video") == true)
return MediaType.Video
return null
}
fun getVideoDuration(context: Context, source: Uri): Long? {
if (getMediaType(context, source) != MediaType.Video)
return null
val retriever = MediaMetadataRetriever()
retriever.setDataSource(context, source)
val duration = retriever.extractMetadata(METADATA_KEY_DURATION)
retriever.release()
return duration?.toLongOrNull()
}
}
enum class MediaType {
Image {
override val mimeType: String = "image/jpeg"
override val fileExtension: String = ".jpg"
},
Video {
override val mimeType: String = "video/mp4"
override val fileExtension: String = ".mp4"
};
abstract val mimeType: String
abstract val fileExtension: String
}
fun File.getVideoDuration(context: Context): Long {
context.contentResolver.query(toUri(), arrayOf(MediaStore.Video.Media.DURATION), null, null, null)?.use {
val durationColumn = it.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION)
it.moveToFirst()
return it.getLong(durationColumn)
}
return 0
Related
I need a method to access the images stored in external storage by my app.
I want to access those images without requesting READ_EXTERNAL_STORAGE or READ_MEDIA_IMAGES from the user
I'm using ACTION_GET_CONTENT to get the image from the user.
val launcher = rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()){
val uri : String = it.data?.data?.toString()?:"null"
if (uri != "null"){
val mimeType = context.contentResolver.getType(uri.toUri()).toString()
it.data?.data?.let {
returnUri ->
context.contentResolver.query(returnUri, null, null, null, null)
}?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
cursor.moveToFirst()
val name = cursor.getString(nameIndex)
val size = cursor.getLong(sizeIndex)
image = image.copy(
path = uri, mimeType = mimeType.replace("image/",""), size = size,
name = name, uri = it.data?.data
)
}
}else{
image = image.copy(path = uri)
}
}
Calling the launcher for result
launcher.launch(Intent(Intent.ACTION_GET_CONTENT).setType("image/*"))
After performing the required actions on the image, I save the image using the following method.
fun saveFile(context : Context, file: File) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
try {
val values = ContentValues()
val path = Environment.DIRECTORY_PICTURES + "/FolderName"
values.put(MediaStore.MediaColumns.DISPLAY_NAME, file.name)
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/*")
values.put(MediaStore.MediaColumns.RELATIVE_PATH, path)
val savedFile = context.contentResolver.insert(Media.EXTERNAL_CONTENT_URI, values)
val outputStream = savedFile?.let {
context.contentResolver.openOutputStream(it)
}
val fis = FileInputStream(file)
var length : Int
val buffer = ByteArray(8192)
while (fis.read(buffer).also { length = it } > 0)
outputStream?.write(buffer, 0, length)
Toast.makeText(context, "Picture saved to $path", Toast.LENGTH_SHORT).show()
println("Picture : $path / $savedFile")
}catch (e : IOException){
Toast.makeText(
context,
"An error occured while saving the file",
Toast.LENGTH_SHORT
).show()
e.printStackTrace()
}catch (e : Exception) { e.printStackTrace() }
}else{
try {
val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).absolutePath + "/FolderName"
val image = File(dir, file.name)
val fis = FileInputStream(file)
val fos = FileOutputStream(image)
var length : Int
val buffer = ByteArray(8192)
while (fis.read(buffer).also { length = it } > 0) {
fos.write(buffer, 0, length)
}
}catch (e : IOException){
Toast.makeText(
context,
"An Error occurred while saving the file",
Toast.LENGTH_SHORT
).show()
e.printStackTrace()
}catch (e : Exception) { e.printStackTrace() }
}
}
psst ~ All of these actions are performed without requesting any permissions.
When I'm trying to access images from contentResolver, it always returns 0.
private fun loadImages() : List<Image> {
val photos = mutableListOf<Image>()
val collection = sdk29andUp {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
} ?: MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATE_ADDED
)
contentResolver.query(
collection, projection, null, null, null
)?.use { cursor ->
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val displayNameColumn =
cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
val dateAddedColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_ADDED)
cursor.moveToFirst()
while (cursor.moveToNext()) {
val id = cursor.getLong(idColumn)
val name = cursor.getString(displayNameColumn)
val date = cursor.getLong(dateAddedColumn)
val contentUri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
id
)
photos.add(
Image(
id = id, name = name, entryDate = date, contentUri = contentUri
))
}
}
return photos.toList()
}
If someone can help me with this, I would really appreciate that.
Edit : The app crashes on API 28 and Before, so I'll have to request permissions for those API levels, but it means there is a solution for api's after 28
I was able to fix it with a very straight forward implementation. Posting the code in hopes that it will help someone.
This implementation covers all API levels from 21 to 33
Saving the image
/** import MediaStore.Images.Media to remove Redundant typing of MediaStore.Images.Media **/
private fun saveImage(file: File) {
sdk29andUp {
val collection = Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val path = Environment.DIRECTORY_PICTURES + "/" + getString(R.string.app_name)
val values = ContentValues().apply {
put(Media.DISPLAY_NAME, file.name)
put(Media.SIZE, file.length())
put(Media.RELATIVE_PATH, path)
}
contentResolver.insert(collection, values)?.also { uri ->
contentResolver.openOutputStream(uri)?.use { outputStream ->
val fis = FileInputStream(file)
var length : Int
val buffer = ByteArray(8192)
while (fis.read(buffer).also { length = it } > 0)
outputStream.write(buffer, 0, length)
}
println(uri)
} ?: throw IOException("Error creating entry in mediaStore")
} ?: saveImageBefore29(file)
}
private fun saveImageBefore29(file: File) {
val resolver = contentResolver
val collection = Media.EXTERNAL_CONTENT_URI
val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).absolutePath + "/${getString(R.string.app_name)}"
val directory = File(dir)
if (!directory.exists())
directory.mkdirs()
val image = File(dir, file.name).also { println(it) }
val values = ContentValues().apply {
put(Media.DISPLAY_NAME, image.name)
put(Media.SIZE, image.length())
put(Media.MIME_TYPE, "image/png")
put(Media.DATA, image.path)
}
resolver.insert(collection, values)?.also { uri ->
contentResolver.openOutputStream(uri)?.use { outputStream ->
val fis = FileInputStream(file)
var length : Int
val buffer = ByteArray(8192)
while (fis.read(buffer).also { length = it } > 0)
outputStream.write(buffer, 0, length)
}
}
}
sdk29andUp function ->
inline fun <T> sdk29andUp(onSdk29: () -> T) : T? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
onSdk29()
else null
Some things I want to point out are:
API 29 and above:
Creating the folder you want to save the file in before this function would result in IllegalArgumentException as your app doesn't own that folder. Just insert the folder path in RELATIVE_PATH and resolver will create the folder in which your app can write freely.
API 28 and below:
You will need to create the folder yourself in these API levels while also requesting the READ_EXTERNAL_STORAGE & WRITE_EXTERNAL_STORAGE from the user.
Loading The Images
private fun loadImages() : List<Image> {
return sdk29andUp {
val imagesList = mutableListOf<Image>()
val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATE_MODIFIED
)
contentResolver.query(
collection,
projection,
null, null,
null
)?.use { cursor ->
val idColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID)
val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
val dateColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATE_MODIFIED)
while (cursor.moveToNext()){
val id = cursor.getLong(idColumn)
val name = cursor.getString(nameColumn)
val date = cursor.getLong(dateColumn)
val contentUri = ContentUris.withAppendedId(collection, id).also { println("URI $it") }
imagesList += Image(
id = id, name = name, path = contentUri.path.toString(),
contentUri = contentUri, entryDate = date
)
}
imagesList
}
} ?: loadImagesBefore29()
}
private fun loadImagesBefore29() : List<Image> {
val images = mutableListOf<Image>()
val collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media.DATA
)
val selection = "${MediaStore.Images.Media.DATA} like ? "
val selectionArgs = arrayOf("%${getString(R.string.app_name)}%")
contentResolver.query(
collection,
projection,
selection,
selectionArgs,
null
)?.use { cursor ->
val idColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID)
val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
val dateColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATE_TAKEN)
val dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA)
while (cursor.moveToNext()){
val id = cursor.getLong(idColumn)
val name = cursor.getString(nameColumn)
val date = cursor.getLong(dateColumn)
val data = cursor.getString(dataColumn)
val contentUri = ContentUris.withAppendedId(
collection, id
).also { println("Uri $it") }
images += Image(
id = id, name = name, path = data,
contentUri = contentUri, entryDate = date
)
}
}
return images
}
When loading the images on API level 29 and above, if your app doesn't have READ_EXTERNAL_STORAGE or READ_MEDIA_AUDIO permission granted, ContentResolver will only query the MediaStore for images/files your app created.
On API level 28 and below, I'm querying a specific folder as the app holds READ_EXTERNAL_STORAGE so contentResolver will return all images in MediaStore if no selectionArgs are specified.
Hope I was able to explain this and help someone with this.
I am trying to save video, created from custom application, to the specific folder inside DCIM folder, side by side to original camera folder.
private fun recordVideo() {
val intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
takeVideo.launch(intent)
}
private val takeVideo = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
Log.i(
"VIDEO_RECORD_TAG ", "Video is available at ${
it.data?.data?.let { it1 -> getRealPathFromURI(it1) }
}"
)
saveMediaFile(
it.data?.data?.let { it1 -> getRealPathFromURI(it1) }, "MyVideoName"
)
}
}
private fun saveMediaFile(filePath: String?, fileName: String) {
filePath?.let {
val values = ContentValues().apply {
put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
val mExtension = MimeTypeMap.getFileExtensionFromUrl(filePath)
put(MediaStore.Video.Media.MIME_TYPE, MimeTypeMap.getSingleton().getMimeTypeFromExtension(mExtension))
put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_MOVIES + "/${getString(R.string.app_name)}/")
put(MediaStore.Video.Media.IS_PENDING, 1)
}
val fileUri = contentResolver.insert(
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY),
values
)
fileUri?.let {
contentResolver.openFileDescriptor(fileUri, "w").use { descriptor ->
descriptor?.let {
FileOutputStream(descriptor.fileDescriptor).use { out ->
val videoFile = File(filePath)
FileInputStream(videoFile).use { inputStream ->
val buf = ByteArray(8192)
while (true) {
val sz = inputStream.read(buf)
if (sz <= 0) break
out.write(buf, 0, sz)
}
}
}
}
}
values.clear()
values.put(MediaStore.Video.Media.IS_PENDING, 0)
contentResolver.update(fileUri, values, null, null)
}
}
}
private fun getRealPathFromURI(contentUri: Uri): String? {
var cursor: Cursor? = null
return try {
val proj = arrayOf(MediaStore.Images.Media.DATA)
cursor = contentResolver.query(contentUri, proj, null, null, null)
val columnIndex = cursor?.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
cursor?.moveToFirst()
columnIndex?.let { cursor?.getString(it) }
} catch (e: Exception) {
Log.e("TAG", "getRealPathFromURI Exception : $e")
""
} finally {
cursor?.close()
}
}
Using this code, folder is created, video is stored as well, but in camera folder I have the same video with original name.
I would like to have only custom created folder with stored videos.
Any suggestion ?
Thanks
I want to send a video to the telegram app via Intent but when I send it, it sends as a file, (same happens with images). Is there any trick to specify how you want to send it? (Because from the gallery it sends the same video playable)
function for sending via Intent
private fun shareItem(fileName: String) {
Log.i(TAG, "shareItem: ")
val videoContentUri = FileProvider.getUriForFile(requireContext(),
"${activity?.applicationContext?.packageName}.provider",
File(LocalFilesRepository.getFullVideoPath(fileName, requireContext()))
)
Log.i("TAG", "shareItem: $videoContentUri")
val shareIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
putExtra(Intent.EXTRA_STREAM, videoContentUri)
type = "video/mp4"
}
startActivity(Intent.createChooser(shareIntent, resources.getText(R.string.send_to)))
}
...
fun getFullVideoPath(fileName: String, context: Context) =
context.getExternalFilesDir(Environment.DIRECTORY_MOVIES).toString() + File.separatorChar + fileName
videoContentUri looks like this content://com.dmytroa.appname.provider/external_files/emulated/0/Android/data/com.dmytroa.appname/files/Movies/1625360538459.mp4
If someone is interested I solved it by just saving a file temporarily at the public storage and then returning Uri from Mediastore.
fun createTemporaryCopyInPublicStorage(file: File, context: Context): Uri? {
val fileName = "tmp"
return if(Build.VERSION.SDK_INT >= 29) {
val uri = findCreatedTemporaryUri(context, fileName, TEMPORARY_DIR_Q)
copyVideoQAndAbove(context, file, uri, fileName, TEMPORARY_DIR_Q)
} else {
val uri = findCreatedTemporaryUri(context, fileName, TEMPORARY_DIR_BELOWQ)
copyVideoBelowQ(context, file, uri, fileName, TEMPORARY_DIR_BELOWQ)
}
}
private fun findCreatedTemporaryUri(context: Context, fileName: String, path: String): Uri? {
val collection = if(Build.VERSION.SDK_INT >= 29) {
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
} else {
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
}
val selection = if (Build.VERSION.SDK_INT >= 29) {
"${MediaStore.Video.Media.TITLE} = ? " +
"AND " +
"${MediaStore.Video.Media.RELATIVE_PATH} = ? "
} else {
"${MediaStore.Video.Media.TITLE} = ? " +
"AND " +
"${MediaStore.Video.Media.DATA} = ? "
}
val args = if (Build.VERSION.SDK_INT >= 29) {
arrayOf(fileName, path)
} else {
arrayOf(fileName, File(path, fileName).absolutePath)
}
context.contentResolver.query(
collection,
arrayOf(MediaStore.Video.Media._ID
),
selection,
args,
null
).use { cursor ->
return if (cursor != null && cursor.moveToFirst()) {
val columnIndexID = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
val id = cursor.getLong(columnIndexID)
Log.i(TAG, "findCreatedTemporaryUri: " +
"contentUri was already added $id $path $fileName")
Uri.withAppendedPath(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "$id")
} else {
null
}
}
}
#RequiresApi(Build.VERSION_CODES.Q)
private fun copyVideoQAndAbove(context: Context,
fileToCopy: File,
uri: Uri?,
fileName: String,
relPath: String): Uri? {
val contentDetails = ContentValues().apply {
put(MediaStore.Video.Media.IS_PENDING, 1)
}
val contentUri = if (uri != null) {
context.contentResolver.update(uri, contentDetails, null, null)
uri
} else {
Log.i(TAG, "saveVideoQAndAbove: contentUri insert")
contentDetails.apply {
put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")
put(MediaStore.Video.Media.RELATIVE_PATH, relPath)
}
val collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
context.contentResolver.insert(collection, contentDetails)
}
return contentUri?.let { createdUri ->
try {
context.contentResolver.openFileDescriptor(createdUri, "w").use { pfd ->
ParcelFileDescriptor.AutoCloseOutputStream(pfd).write(fileToCopy.readBytes())
}
} catch (e: IOException) {
e.printStackTrace()
}
contentDetails.clear()
contentDetails.put(MediaStore.Video.Media.IS_PENDING, 0)
context.contentResolver.update(createdUri, contentDetails, null, null)
createdUri
}
}
private fun copyVideoBelowQ(context: Context,
fileToCopy: File,
uri: Uri?,
fileName: String,
dstParentPath: String): Uri? {
val dstDir = File(dstParentPath)
if (!dstDir.exists())
dstDir.mkdirs()
val fileToCreate = File(dstDir, fileName)
fileToCreate.delete()
fileToCreate.createNewFile()
Log.i(TAG, "saveVideo: created ${fileToCreate.name}")
try {
fileToCopy.inputStream().use { input ->
fileToCreate.outputStream().use { output ->
input.copyTo(output, 2 * 1024)
Log.i(TAG, "saveVideo: finished ${fileToCreate.path}")
}
}
return uri ?: let {
val values = ContentValues().apply {
put(MediaStore.Video.Media.TITLE, fileToCreate.name)
put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")
put(MediaStore.Video.Media.DATA, fileToCreate.path)
}
context.contentResolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values)
}
} catch (e: IOException) {
e.printStackTrace()
}
return null
}
private val TEMPORARY_DIR_Q = Environment.DIRECTORY_MOVIES + File.separator +
"APPNAME" + File.separator +
"temporary" + File.separator
private val TEMPORARY_DIR_BELOWQ = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MOVIES).absolutePath + File.separator +
"APPNAME" + File.separator +
"temporary" + File.separator
I use MediaCodec.inputSurface and MediaMixuer for recording of my android view. Everything is good on the most of devices, but not on Huawei. For some reason it produces a video which cannot be played on this devices. But for some unknown reason 1 out of 10 times it generates a good video. Here's link to the broken video and to the normal video. It is also weird that both videos can be played on my mac laptop.
Our users reported the issue from multiple huawei models and I can confirm it from my phone: HUAWEI P8 lite 2017, android 7.0. Also it happens on new phones too with any android version.
Here's code of how I manage the recording:
/**
* Stages:
* 1. Draw canvas to bitmap
* 2. Take bitmap pixels and convert them to YUV
* 3. Write bitmap pixels as a frame to MediaCodec
* 4. Take mediaCodec and write to mediaMuxer to receive file
*/
class VideoEncoder(
val width: Int,
val height: Int,
val frameRate: Int,
val file: File,
val durationUs: Long,
val handler: Handler,
val videoRecordingFinished: () -> Unit,
val onError: (MediaCodec.CodecException) -> Unit
) : KoinComponent {
var mediaMuxer: MediaMuxer? = null
var videoCodec: MediaCodec? = null
var videoTrackIndex = 0
var surface: Surface? = null
val videoBufferInfo by lazy { MediaCodec.BufferInfo() }
var writingVideoFinished = false
private var currentFrame = 0
var audioEncoder: AudioEncoder? = null
var writingAudioFinished: Boolean
get() = audioEncoder?.writingAudioFinished ?: true
set(value) {
audioEncoder?.writingAudioFinished = value
}
var videoFormatInited: Boolean = false
val allFormatsInited: Boolean
get() = videoFormatInited && (audioEncoder?.audioFormatInited != false)
private val pendingVEncoderInfos = LinkedList<MediaCodec.BufferInfo>()
private val pendingVEncoderIndices = LinkedList<Int>()
val logger: KLogger by inject {
parametersOf("video-encoder")
}
private fun createVideoFormat(mimeType: String, desiredColorFormat: Int): MediaFormat {
val mediaFormat =
MediaFormat.createVideoFormat(mimeType, width, height)
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, ENCODING_VIDEO_BITRATE)
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, this.frameRate)
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, desiredColorFormat)
return mediaFormat
}
private fun findCorrectVideoFormat(): MediaFormat {
val mimeType = POSSIBLE_MIME_TYPES[0]
val desiredColorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
val mediaFormat = createVideoFormat(mimeType, desiredColorFormat)
val encoderForFormat =
MediaCodecList(MediaCodecList.REGULAR_CODECS).findEncoderForFormat(mediaFormat)
if (encoderForFormat == null) {
logger.info { "encoderForFormatIsNull!!! width = $width, height = $height" }
videoCodec = MediaCodec.createEncoderByType(mimeType)
} else {
videoCodec = MediaCodec.createByCodecName(encoderForFormat)
}
val codecInfo = videoCodec!!.codecInfo
if (codecInfo.isEncoder && codecInfo.supportedTypes.contains(mimeType) &&
codecInfo.getCapabilitiesForType(mimeType).colorFormats
.contains(desiredColorFormat)
) {
} else {
throw IllegalStateException("MediaCodec is wrong = ${codecInfo}")
}
val errorMessage = checkIsColorFormatSupported(mediaFormat, desiredColorFormat, mimeType)
if (errorMessage != null)
throw IllegalStateException(errorMessage)
return mediaFormat
}
//return error message if false
fun checkIsColorFormatSupported(
mediaFormat: MediaFormat,
desiredColorFormat: Int,
mimeType: String
): String? {
var colorFormats = videoCodec!!.codecInfo.getCapabilitiesForType(mimeType).colorFormats
var colorFormatSize = colorFormats.size
var counterColorFormat = 0
val colorFormatCorrect: Boolean
while (true) {
if (counterColorFormat >= colorFormatSize) {
colorFormatCorrect = false
break
}
if (colorFormats[counterColorFormat] == desiredColorFormat) {
colorFormatCorrect = true
break
}
++counterColorFormat
}
if (!colorFormatCorrect) {
var message = "NO COLOR FORMAT COMPATIBLE\\n$mediaFormat"
colorFormats = videoCodec!!.codecInfo.getCapabilitiesForType(mimeType).colorFormats
colorFormatSize = colorFormats.size
counterColorFormat = 0
while (counterColorFormat < colorFormatSize) {
val sb = StringBuilder()
sb.append(message)
sb.append("\\n")
sb.append(colorFormats[counterColorFormat])
message = sb.toString()
logger.debug { message }
++counterColorFormat
}
return message
}
return null
}
private fun printVideoCodecInfo() {
logger.debug {
val json = JSONObject()
json.put("codec_name", videoCodec!!.name)
json.put("codec_info_name", videoCodec!!.codecInfo.name)
json.put("codec_supported_types", videoCodec!!.codecInfo.supportedTypes)
json.put("output_width", width)
json.put("output_height", height)
json.toString()
}
}
#Throws(Exception::class)
fun initialize(videoAsyncEncoder: Boolean) {
val filePath = file.canonicalPath
val mediaFormat = findCorrectVideoFormat()
printVideoCodecInfo()
if (videoAsyncEncoder) {
videoCodec!!.setCallback(object : MediaCodec.Callback() {
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
}
override fun onOutputBufferAvailable(
codec: MediaCodec,
index: Int,
info: MediaCodec.BufferInfo
) {
pendingVEncoderIndices.add(index)
pendingVEncoderInfos.add(info)
if (allFormatsInited)
checkVideoOutputAvailable()
}
override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
writingVideoFinished = true
e.printDebug()
onError.invoke(e)
}
override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
onVideoFormatChanged(format)
}
}, handler)
}
videoCodec!!.configure(
mediaFormat, null, null,
MediaCodec.CONFIGURE_FLAG_ENCODE
)
surface = videoCodec!!.createInputSurface()
mediaMuxer = MediaMuxer(filePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
}
fun initAudio(
path: String,
startTimeUs: Long,
volume: Int,
audioRecordingFinished: () -> Unit
) {
audioEncoder = AudioEncoder(
mediaMuxer!!,
handler,
durationUs,
::checkFormatsInited,
audioRecordingFinished
)
audioEncoder!!.initAudio(path, startTimeUs, volume)
audioEncoder!!.startAudioCodec()
}
fun canWriteAudio() {
audioEncoder?.canWriteAudio()
}
fun getCurrentAudioTime() = audioEncoder?.getCurrentAudioTime()
private fun onVideoFormatChanged(format: MediaFormat) {
videoTrackIndex =
mediaMuxer!!.addTrack(format)
videoFormatInited = true
checkFormatsInited()
}
fun checkFormatsInited() {
if (allFormatsInited) {
mediaMuxer!!.start()
checkVideoOutputAvailable()
}
}
#Throws(IllegalStateException::class)
fun writeToMuxerSyncMode(currentFrame: Int = -1): Boolean {
var success = false
while (videoCodec != null && mediaMuxer != null) {
val outputBufferIndex = videoCodec!!.dequeueOutputBuffer(videoBufferInfo, 0L)
logger.info {
"writeToMuxer, outputBufferIndex = ${outputBufferIndex}, bufferFlag = ${videoBufferInfo.flags}," +
" presentationTime = ${((currentFrame * 1000000L) / frameRate)}," +
" bufferInfo.size ${videoBufferInfo.size}, bufferInfo.offset ${videoBufferInfo.offset}"
}
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
onVideoFormatChanged(videoCodec!!.outputFormat)
} else {
if (outputBufferIndex < 0) {
return success
}
success = true
val bufferInfo = videoBufferInfo
if (bufferInfo.offset >= 0 && bufferInfo.size > 0) {
val outputBuffer = videoCodec!!.getOutputBuffer(outputBufferIndex)!!
outputBuffer.position(this.videoBufferInfo.offset)
outputBuffer.limit(bufferInfo.offset + bufferInfo.size)
if (currentFrame != -1) {
if (videoBufferInfo.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
success = false
else
bufferInfo.presentationTimeUs = (currentFrame * 1000000L) / frameRate
}
mediaMuxer!!.writeSampleData(
videoTrackIndex,
outputBuffer,
this.videoBufferInfo
)
}
videoCodec!!.releaseOutputBuffer(outputBufferIndex, false)
if (bufferInfo.flags.and(MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
return success
}
}
}
return success
}
private fun onVideoWritingFinished() {
writingVideoFinished = true
videoRecordingFinished.invoke()
}
private fun checkVideoOutputAvailable() {
while (pendingVEncoderIndices.size > 0 &&
pendingVEncoderInfos.size > 0 && videoCodec != null
) {
val index = pendingVEncoderIndices.removeFirst()
val info = pendingVEncoderInfos.removeFirst()
onVideoOutputAvailable(videoCodec!!, index, info)
}
}
private fun onVideoOutputAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {
if (videoCodec == null)
return
if (info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
codec.releaseOutputBuffer(index, false)
onVideoWritingFinished()
} else {
val outputBuffer = codec.getOutputBuffer(index)!!
outputBuffer.position(info.offset)
outputBuffer.limit(info.offset + info.size)
info.presentationTimeUs = (currentFrame * 1000000L) / frameRate
if (info.flags != MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
currentFrame++
}
logger.info {
"videoOutputAvailable time ${info.presentationTimeUs}, flags ${info.flags}," +
" size ${info.size}, offset ${info.offset}"
}
mediaMuxer!!.writeSampleData(
videoTrackIndex,
outputBuffer, info
)
codec.releaseOutputBuffer(index, false)
}
}
fun startVideoCodec() {
videoCodec?.start()
}
fun stop() {
audioEncoder?.stop()
pendingVEncoderInfos.clear()
pendingVEncoderIndices.clear()
surface?.release()
surface = null
if (videoCodec != null) {
try {
videoCodec?.stop()
} catch (e: IllegalStateException) {
} finally {
videoCodec?.release()
videoCodec = null
}
}
if (mediaMuxer != null) {
try {
mediaMuxer?.release()
} catch (e: IllegalStateException) {
logger.error(e)
} finally {
mediaMuxer = null
}
}
}
fun sendEndOfStreamSurface() {
videoCodec?.signalEndOfInputStream()
if (!ThreadRecord.VIDEO_CODEC_ASYNC) {
onVideoWritingFinished()
}
}
companion object {
const val ENCODING_VIDEO_BITRATE = 12000000
val POSSIBLE_MIME_TYPES = arrayOf("video/avc", "video/hevc", "video/x-vnd.on2.vp8")
}
}
I am newbie in Android. I have one requirement in my application. I need to download audio files, .mp3 files from server using Downloadmanager and save it in specific folder in my device. Please help me to complete this task
Thanks in advance
The Android DownloadManager introduced in Android 2.3. (API 9) is a system service which allows to handle long-running HTTP downloads in the background and notify the triggering application via a broadcast receiver once the download is finished.
Courtesy:
http://www.vogella.com/blog/2011/06/14/android-downloadmanager-example/
Please see the below code
object DownloadHelper {
private var downloadReference: Long = 0
private lateinit var downloadManager: DownloadManager
var completeListener:DownloadCompleteListener?= null
interface DownloadCompleteListener{
fun ondownloadComplete(name:String,path:String?,uri:Uri?)
fun ondownloadFailled(error:String)
}
private val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
if (action == DownloadManager.ACTION_DOWNLOAD_COMPLETE) {
val downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
if (downloadId != downloadReference) {
context.unregisterReceiver(this)
return
}
val query = DownloadManager.Query()
query.setFilterById(downloadReference)
val cursor = downloadManager.query(query)
if(cursor != null){
cursor?.let {
if (cursor.moveToFirst()) {
val columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
if (DownloadManager.STATUS_SUCCESSFUL == cursor.getInt(columnIndex)) {
var localFile = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
var localName = localFile.substring(localFile.lastIndexOf("/")+1)//cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME))
completeListener!!.ondownloadComplete(localName,localFile,null)
} else if (DownloadManager.STATUS_FAILED == cursor.getInt(columnIndex)) {
completeListener!!.ondownloadFailled(cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_REASON)))
}
}else{
completeListener!!.ondownloadFailled("Download cancelled or failled")
}
cursor.close()
}
}else{
completeListener!!.ondownloadFailled("Download cancelled or failled")
}
context.unregisterReceiver(this)
}
}
}
fun getAudiofilePath(name:String):String{
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)
.getAbsolutePath().toString() + "/" + name
}
fun downloadFile(context: Context,title: String,url: String?,uristring: Uri?, mimeType: String? = null,completeListener: DownloadCompleteListener) {
this.completeListener = completeListener
val guessFileName = URLUtil.guessFileName(url, null, mimeType)
if(url == null || url.isEmpty()){
completeListener!!.ondownloadComplete(title,url,uristring)
return
}
var audiofilepath = getAudiofilePath(guessFileName);
var applictionFile = File(audiofilepath);
if(isAllreadyAvailabel(context,applictionFile)){
completeListener!!.ondownloadComplete(applictionFile.name,applictionFile.absolutePath,null)
return
}
downloadManager = context!!.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downloadUri = Uri.parse(url)
val request = DownloadManager.Request(downloadUri)
request.apply {
setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE or DownloadManager.Request.NETWORK_WIFI)
//setAllowedOverRoaming(true)
// setTitle(guessFileName)
// setDescription(guessFileName)
// setVisibleInDownloadsUi(true)
allowScanningByMediaScanner()
// setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
//request.setDestinationUri(Uri.fromFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)))
setDestinationInExternalPublicDir(Environment.DIRECTORY_MUSIC, guessFileName)
context.registerReceiver(receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
downloadReference = downloadManager.enqueue(this)
}
}
fun getAllreadyDownloadAudioPath(context: Context,url:String,mimeType: String? = null):String {
val guessFileName = URLUtil.guessFileName(url, null, mimeType)
var audiofilepath = getAudiofilePath(guessFileName);
var applictionFile = File(audiofilepath);
if(isAllreadyAvailabel(context,applictionFile)){
return applictionFile.absolutePath
}
return ""
}
fun downloadVideo(context: Context,title: String, urlFromServer: String?, mimeType: String? = null,completeListener: DownloadCompleteListener) {
this.completeListener = completeListener
val guessFileName = URLUtil.guessFileName(urlFromServer, null, mimeType)
if(urlFromServer == null || urlFromServer.isEmpty()){
completeListener!!.ondownloadComplete(title,urlFromServer, Uri.EMPTY)
return
}
val directory = File(Environment.getExternalStorageDirectory().toString() + File.separator + "zingsharetemp")
directory.mkdirs()
// var audiofilepath = getAudiofilePath(guessFileName);
var applicationFile = File(directory!!.path+'/'+guessFileName);
if(isAllreadyAvailabel(context,applicationFile)){
completeListener!!.ondownloadComplete(applicationFile.name,applicationFile.absolutePath,null)
return
}
downloadManager = context!!.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downloadUri = Uri.parse(urlFromServer)
val request = DownloadManager.Request(downloadUri)
request.apply {
setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE or DownloadManager.Request.NETWORK_WIFI)
allowScanningByMediaScanner()
setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, guessFileName)
context.registerReceiver(receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
downloadReference = downloadManager.enqueue(this)
}
}
fun isAllreadyAvailabel(context: Context,applictionFile:File):Boolean{
if(applictionFile.canRead() && applictionFile.length()>0) {
return true
}else{
deleteFiles(applictionFile.absolutePath)
}
return false
}
}
fun download(){
DownloadHelper.downloadFile(context,
title,
url,
uristring,
"audio/*",
object : DownloadHelper.DownloadCompleteListener {
override fun ondownloadComplete(name: String, path: String?, uristring: Uri?) {
}
override fun ondownloadFailled(error: String) {
}
})
}
hope it will help you