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

Use MonotonicTimelineInterpolator in video transcoder #4257

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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 @@ -184,13 +184,17 @@
}

private suspend fun processVideo(uri: Uri, mimeType: String?, shouldBeCompressed: Boolean): MediaUploadInfo {
val resultFile = videoCompressor.compress(uri, shouldBeCompressed)
.onEach {
// TODO handle progress
}
.filterIsInstance<VideoTranscodingEvent.Completed>()
.first()
.file
val resultFile = runCatching {
videoCompressor.compress(uri, shouldBeCompressed)
.onEach {

Check warning on line 189 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt#L188-L189

Added lines #L188 - L189 were not covered by tests
// TODO handle progress
}
.filterIsInstance<VideoTranscodingEvent.Completed>()
.first()
.file

Check warning on line 194 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt#L192-L194

Added lines #L192 - L194 were not covered by tests
}
// If the video could not be compressed, just copy use the original one by copying to a temporary file
.getOrNull() ?: copyToTmpFile(uri)
val thumbnailInfo = thumbnailFactory.createVideoThumbnail(resultFile)
val videoInfo = extractVideoMetadata(resultFile, mimeType, thumbnailInfo)
return MediaUploadInfo.Video(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,67 +8,115 @@
package io.element.android.libraries.mediaupload.impl

import android.content.Context
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.webkit.MimeTypeMap
import com.otaliastudios.transcoder.Transcoder
import com.otaliastudios.transcoder.TranscoderListener
import com.otaliastudios.transcoder.internal.media.MediaFormatConstants
import com.otaliastudios.transcoder.resize.AtMostResizer
import com.otaliastudios.transcoder.strategy.DefaultVideoStrategy
import io.element.android.libraries.androidutils.file.createTmpFile
import io.element.android.libraries.androidutils.file.getMimeType
import io.element.android.libraries.androidutils.file.safeDelete
import io.element.android.libraries.di.ApplicationContext
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import timber.log.Timber
import java.io.File
import javax.inject.Inject

class VideoCompressor @Inject constructor(
@ApplicationContext private val context: Context,
) {
companion object {
private const val MP4_EXTENSION = "mp4"
}

fun compress(uri: Uri, shouldBeCompressed: Boolean) = callbackFlow {
val tmpFile = context.createTmpFile(extension = "mp4")
val future = Transcoder.into(tmpFile.path)
.setVideoTrackStrategy(
DefaultVideoStrategy.Builder()
.addResizer(
AtMostResizer(
if (shouldBeCompressed) {
720
} else {
1080
}
)
)
.build()
)
.addDataSource(context, uri)
.setListener(object : TranscoderListener {
override fun onTranscodeProgress(progress: Double) {
trySend(VideoTranscodingEvent.Progress(progress.toFloat()))
}
val (width, height) = getVideoDimensions(uri) ?: (Int.MAX_VALUE to Int.MAX_VALUE)

override fun onTranscodeCompleted(successCode: Int) {
trySend(VideoTranscodingEvent.Completed(tmpFile))
close()
}
val resizer = when {

Check warning on line 39 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L39

Added line #L39 was not covered by tests
shouldBeCompressed && (width > 720 || height > 720) -> AtMostResizer(720)
width > 1080 || height > 1080 -> AtMostResizer(1080)
else -> null

Check warning on line 42 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L42

Added line #L42 was not covered by tests
}

override fun onTranscodeCanceled() {
tmpFile.safeDelete()
close()
}
val expectedExtension = MimeTypeMap.getSingleton().getExtensionFromMimeType(context.getMimeType(uri))

Check warning on line 45 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L45

Added line #L45 was not covered by tests

val tmpFile = context.createTmpFile(extension = MP4_EXTENSION)

Check warning on line 47 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L47

Added line #L47 was not covered by tests

override fun onTranscodeFailed(exception: Throwable) {
tmpFile.safeDelete()
close(exception)
// If there's no transcoding needed for the video file, just copy it to the tmp file and return it
val future = if (expectedExtension == MP4_EXTENSION && resizer == null) {
context.contentResolver.openInputStream(uri)?.use { input ->
tmpFile.outputStream().use { output ->
input.copyTo(output)

Check warning on line 53 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L52-L53

Added lines #L52 - L53 were not covered by tests
}
})
.transcode()
}
trySend(VideoTranscodingEvent.Completed(tmpFile))
close()
null

Check warning on line 58 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L56-L58

Added lines #L56 - L58 were not covered by tests
} else {
Transcoder.into(tmpFile.path)
.setVideoTrackStrategy(
DefaultVideoStrategy.Builder()
.apply {

Check warning on line 63 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L60-L63

Added lines #L60 - L63 were not covered by tests
resizer?.let { addResizer(it) }
}
.mimeType(MediaFormatConstants.MIMETYPE_VIDEO_AVC)
.build()

Check warning on line 67 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L65-L67

Added lines #L65 - L67 were not covered by tests
)
.addDataSource(context, uri)
.setListener(object : TranscoderListener {

Check warning on line 70 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L69-L70

Added lines #L69 - L70 were not covered by tests
override fun onTranscodeProgress(progress: Double) {
trySend(VideoTranscodingEvent.Progress(progress.toFloat()))

Check warning on line 72 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L72

Added line #L72 was not covered by tests
}

override fun onTranscodeCompleted(successCode: Int) {
trySend(VideoTranscodingEvent.Completed(tmpFile))
close()

Check warning on line 77 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L76-L77

Added lines #L76 - L77 were not covered by tests
}

override fun onTranscodeCanceled() {
tmpFile.safeDelete()
close()

Check warning on line 82 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L81-L82

Added lines #L81 - L82 were not covered by tests
}

override fun onTranscodeFailed(exception: Throwable) {
tmpFile.safeDelete()
close(exception)

Check warning on line 87 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L86-L87

Added lines #L86 - L87 were not covered by tests
}
})
.transcode()

Check warning on line 90 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L90

Added line #L90 was not covered by tests
}

awaitClose {
if (!future.isDone) {
if (future?.isDone == false) {
future.cancel(true)
}
}
}

private fun getVideoDimensions(uri: Uri): Pair<Int, Int>? {
return runCatching {
MediaMetadataRetriever().use {
it.setDataSource(context, uri)

Check warning on line 103 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L101-L103

Added lines #L101 - L103 were not covered by tests

val width = it.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toIntOrNull() ?: -1
val height = it.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toIntOrNull() ?: -1

if (width == -1 || height == -1) {
// Try getting the first frame instead
val bitmap = it.getFrameAtTime(0) ?: return null
bitmap.width to bitmap.height

Check warning on line 111 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L111

Added line #L111 was not covered by tests
} else {
width to height

Check warning on line 113 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L113

Added line #L113 was not covered by tests
}
}

Check warning on line 115 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L115

Added line #L115 was not covered by tests
}.onFailure {
Timber.e(it, "Failed to get video dimensions")

Check warning on line 117 in libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt

View check run for this annotation

Codecov / codecov/patch

libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt#L117

Added line #L117 was not covered by tests
}.getOrNull()
}
}

sealed interface VideoTranscodingEvent {
Expand Down
Loading