Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
# Conflicts:
#	.idea/jarRepositories.xml
#	app/src/main/AndroidManifest.xml
#	lightcompressor/src/main/java/com/abedelazizshe/lightcompressorlibrary/VideoCompressor.kt
  • Loading branch information
Goooler committed Aug 16, 2020
2 parents 3c0088f + 343314d commit 9de8412
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 114 deletions.
221 changes: 147 additions & 74 deletions app/src/main/java/com/abedelazizshe/lightcompressor/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package com.abedelazizshe.lightcompressor
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ContentValues
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.provider.MediaStore
import android.text.format.DateUtils
import android.util.Log
import android.view.View
Expand All @@ -19,14 +22,18 @@ import com.abedelazizshe.lightcompressorlibrary.VideoCompressor
import com.abedelazizshe.lightcompressorlibrary.VideoQuality
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.content_main.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException

/**
* Created by AbedElaziz Shehadeh on 26 Jan, 2020
* [email protected]
*/

class MainActivity : AppCompatActivity() {

companion object {
Expand Down Expand Up @@ -73,79 +80,80 @@ class MainActivity : AppCompatActivity() {
if (requestCode == REQUEST_SELECT_VIDEO) {
if (data != null && data.data != null) {
val uri = data.data
path = getMediaPath(this, uri)
val file = File(path)

mainContents.visibility = View.VISIBLE
GlideApp.with(this).load(uri).into(videoImage)

val downloadsPath =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val desFile = File(downloadsPath, "${System.currentTimeMillis()}_${file.name}")
if (desFile.exists()) {
desFile.delete()
try {
desFile.createNewFile()
} catch (e: IOException) {
e.printStackTrace()
}

}

var time = 0L

VideoCompressor.start(
path,
desFile.path,
object : CompressionListener {
override fun onProgress(percent: Float) {
//Update UI
runOnUiThread {
progress.text = "${percent.toLong()}%"
progressBar.progress = percent.toInt()
}

}

override fun onStart() {
time = System.currentTimeMillis()
progress.visibility = View.VISIBLE
progressBar.visibility = View.VISIBLE
originalSize.text = "Original size: ${getFileSize(file.length())}"
}

override fun onSuccess() {
val newSizeValue = desFile.length()

newSize.text =
"Size after compression: ${getFileSize(newSizeValue)}"

time = System.currentTimeMillis() - time
timeTaken.text =
"Duration: ${DateUtils.formatElapsedTime(time / 1000)}"

path = desFile.path

Handler().postDelayed({
progress.visibility = View.GONE
progressBar.visibility = View.GONE
}, 50)
uri?.let {
mainContents.visibility = View.VISIBLE
GlideApp.with(applicationContext).load(uri).into(videoImage)

GlobalScope.launch {
// run in background as it can take a long time if the video is big,
// this implementation is not the best way to do it,
// todo(abed): improve threading
val job = async { getMediaPath(applicationContext, uri) }
path = job.await()

val desFile = saveVideoFile(path)

desFile?.let {
var time = 0L
VideoCompressor.start(
path,
desFile.path,
object : CompressionListener {
override fun onProgress(percent: Float) {
//Update UI
if (percent <= 100 && percent.toInt() % 5 == 0)
runOnUiThread {
progress.text = "${percent.toLong()}%"
progressBar.progress = percent.toInt()
}
}

override fun onStart() {
time = System.currentTimeMillis()
progress.visibility = View.VISIBLE
progressBar.visibility = View.VISIBLE
originalSize.text =
"Original size: ${getFileSize(File(path).length())}"
progress.text = ""
progressBar.progress = 0
}

override fun onSuccess() {
val newSizeValue = desFile.length()

newSize.text =
"Size after compression: ${getFileSize(newSizeValue)}"

time = System.currentTimeMillis() - time
timeTaken.text =
"Duration: ${DateUtils.formatElapsedTime(time / 1000)}"

path = desFile.path

Handler().postDelayed({
progress.visibility = View.GONE
progressBar.visibility = View.GONE
}, 50)
}

override fun onFailure(failureMessage: String) {
progress.text = failureMessage
Log.wtf("failureMessage", failureMessage)
}

override fun onCancelled() {
Log.wtf("TAG", "compression has been cancelled")
// make UI changes, cleanup, etc
}
},
VideoQuality.MEDIUM,
isMinBitRateEnabled = false,
keepOriginalResolution = false
)
}

override fun onFailure(failureMessage: String) {
progress.text = failureMessage
Log.wtf("failureMessage", failureMessage)
}

override fun onCancelled() {
Log.wtf("TAG", "compression has been cancelled")
// make UI changes, cleanup, etc
}
},
VideoQuality.MEDIUM,
isMinBitRateEnabled = false,
keepOriginalResolution = false
)
}
}
}
}

Expand All @@ -159,12 +167,11 @@ class MainActivity : AppCompatActivity() {
) != PackageManager.PERMISSION_GRANTED
) {

if (ActivityCompat.shouldShowRequestPermissionRationale(
if (!ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
) {
} else {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
Expand All @@ -173,4 +180,70 @@ class MainActivity : AppCompatActivity() {
}
}
}

@Suppress("DEPRECATION")
private fun saveVideoFile(filePath: String?): File? {
filePath?.let {
val videoFile = File(filePath)
val videoFileName = "${System.currentTimeMillis()}_${videoFile.name}"
val folderName = Environment.DIRECTORY_MOVIES
if (Build.VERSION.SDK_INT >= 29) {

val values = ContentValues().apply {

put(
MediaStore.Images.Media.DISPLAY_NAME,
videoFileName
)
put(MediaStore.Images.Media.MIME_TYPE, "video/mp4")
put(MediaStore.Images.Media.RELATIVE_PATH, folderName)
put(MediaStore.Images.Media.IS_PENDING, 1)
}

val collection =
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)

val fileUri = applicationContext.contentResolver.insert(collection, values)

fileUri?.let {
application.contentResolver.openFileDescriptor(fileUri, "w").use { descriptor ->
descriptor?.let {
FileOutputStream(descriptor.fileDescriptor).use { out ->
FileInputStream(videoFile).use { inputStream ->
val buf = ByteArray(4096)
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)
applicationContext.contentResolver.update(fileUri, values, null, null)

return File(getMediaPath(applicationContext, fileUri))
}
} else {
val downloadsPath =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val desFile = File(downloadsPath, videoFileName)

if (desFile.exists())
desFile.delete()

try {
desFile.createNewFile()
} catch (e: IOException) {
e.printStackTrace()
}

return desFile
}
}
return null
}
}
94 changes: 89 additions & 5 deletions app/src/main/java/com/abedelazizshe/lightcompressor/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,47 @@ import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.provider.MediaStore
import java.io.*
import java.text.DecimalFormat
import kotlin.math.log10
import kotlin.math.pow

fun getMediaPath(context: Context, uri: Uri): String {

fun getMediaPath(context: Context, uri: Uri?): String {
val resolver = context.contentResolver
val projection = arrayOf(MediaStore.Video.Media.DATA)
var cursor: Cursor? = null
try {
cursor = context.contentResolver.query(uri!!, projection, null, null, null)
cursor = resolver.query(uri, projection, null, null, null)
return if (cursor != null) {
val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
cursor.moveToFirst()
cursor.getString(columnIndex)

} else ""
}finally {

} catch (e: Exception) {
resolver.let {
val filePath = (context.applicationInfo.dataDir + File.separator
+ System.currentTimeMillis())
val file = File(filePath)

resolver.openInputStream(uri)?.use { inputStream ->
FileOutputStream(file).use { outputStream ->
val buf = ByteArray(4096)
var len: Int
while (inputStream.read(buf).also { len = it } > 0) outputStream.write(
buf,
0,
len
)
}
}
return file.absolutePath
}
} finally {
cursor?.close()
}

}

fun getFileSize(size: Long): String {
Expand All @@ -36,4 +57,67 @@ fun getFileSize(size: Long): String {
return DecimalFormat("#,##0.#").format(
size / 1024.0.pow(digitGroups.toDouble())
) + " " + units[digitGroups]
}
}

//The following methods can be alternative to [getMediaPath].
// todo(abed): remove [getPathFromUri], [getVideoExtension], and [copy]
fun getPathFromUri(context: Context, uri: Uri): String {
var file: File? = null
var inputStream: InputStream? = null
var outputStream: OutputStream? = null
var success = false
try {
val extension: String = getVideoExtension(uri)
inputStream = context.contentResolver.openInputStream(uri)
file = File.createTempFile("compressor", extension, context.cacheDir)
file.deleteOnExit()
outputStream = FileOutputStream(file)
if (inputStream != null) {
copy(inputStream, outputStream)
success = true
}
} catch (ignored: IOException) {
} finally {
try {
inputStream?.close()
} catch (ignored: IOException) {
}
try {
outputStream?.close()
} catch (ignored: IOException) {
// If closing the output stream fails, we cannot be sure that the
// target file was written in full. Flushing the stream merely moves
// the bytes into the OS, not necessarily to the file.
success = false
}
}
return if (success) file!!.path else ""
}

/** @return extension of video with dot, or default .mp4 if it none.
*/
private fun getVideoExtension(uriVideo: Uri): String {
var extension: String? = null
try {
val imagePath = uriVideo.path
if (imagePath != null && imagePath.lastIndexOf(".") != -1) {
extension = imagePath.substring(imagePath.lastIndexOf(".") + 1)
}
} catch (e: Exception) {
extension = null
}
if (extension == null || extension.isEmpty()) {
//default extension for matches the previous behavior of the plugin
extension = "mp4"
}
return ".$extension"
}

private fun copy(`in`: InputStream, out: OutputStream) {
val buffer = ByteArray(4 * 1024)
var bytesRead: Int
while (`in`.read(buffer).also { bytesRead = it } != -1) {
out.write(buffer, 0, bytesRead)
}
out.flush()
}
Loading

0 comments on commit 9de8412

Please sign in to comment.