Skip to content
Open
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
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ android {
}
// Built-in Kotlin derives the Kotlin jvmTarget from compileOptions, so the
// deprecated kotlinOptions block is no longer needed.
testOptions {
unitTests.isIncludeAndroidResources = true
}
buildFeatures {
compose = true
buildConfig = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1636,7 +1636,14 @@ class CompressorViewModel(application: Application) : AndroidViewModel(applicati
val sourceHeight = sourceInfo?.height ?: 0
val sourceFps = if ((sourceInfo?.frameRate ?: 0f) > 0f) sourceInfo!!.frameRate else state.originalFps

if (!sourceMime.isNullOrBlank() && sourceWidth > 0 && sourceHeight > 0) {
// Dolby Vision may have an HEVC-compatible base layer even when the device
// advertises no video/dolby-vision decoder. Let Media3 inspect the actual
// profile and select a compatible decoder; an exact MIME check here would
// reject the file before DefaultDecoderFactory can try that fallback.
// Keep the original format intact so Media3 can validate compatibility.
if (!sourceMime.isNullOrBlank() && sourceWidth > 0 && sourceHeight > 0 &&
sourceMime != MimeTypes.VIDEO_DOLBY_VISION
) {
val decoderSupported = isCodecConfigurationSupported(
mimeType = sourceMime,
width = sourceWidth,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package compress.joshattic.us.viewmodel

import android.app.Application
import android.media.MediaCodecList
import androidx.media3.common.MimeTypes
import androidx.test.core.app.ApplicationProvider
import compress.joshattic.us.model.CompressorUiState
Expand Down Expand Up @@ -110,6 +111,53 @@ class CompressorViewModelTest {
assertEquals(QualityPreset.CUSTOM, viewModel.uiState.value.activePreset)
}

@Test
fun dolbyVisionWithoutAdvertisedDecoderReachesOutputPlanning() {
val error = planWithNoAdvertisedCodecs(MimeTypes.VIDEO_DOLBY_VISION)
assertTrue(error.startsWith("This device can't encode"))
}

@Test
fun unsupportedHevcStillReportsDecoderFailure() {
val error = planWithNoAdvertisedCodecs(MimeTypes.VIDEO_H265)
assertEquals(
application.getString(
compress.joshattic.us.R.string.error_decoder_config_unsupported_details,
3840, 2160, 60f, "hevc"
),
error
)
}

private fun planWithNoAdvertisedCodecs(sourceMime: String): String {
// With no advertised decoders, Dolby Vision should still reach output
// planning so Media3 can later validate the profile and try HEVC fallback.
assertTrue(MediaCodecList(MediaCodecList.ALL_CODECS).codecInfos.isEmpty())
val videoInfoClass = CompressorViewModel::class.java.declaredClasses
.single { it.simpleName == "VideoTrackInfo" }
val videoInfo = videoInfoClass.getDeclaredConstructor(
String::class.java, Int::class.javaPrimitiveType,
Int::class.javaPrimitiveType, Float::class.javaPrimitiveType
).apply { isAccessible = true }.newInstance(sourceMime, 3840, 2160, 60f)
val probeClass = CompressorViewModel::class.java.declaredClasses
.single { it.simpleName == "TrackProbe" }
val probe = probeClass.getDeclaredConstructor(
videoInfoClass, String::class.java,
Int::class.javaPrimitiveType, Int::class.javaPrimitiveType
).apply { isAccessible = true }.newInstance(videoInfo, null, 0, -1)
val method = CompressorViewModel::class.java.getDeclaredMethod(
"buildCompressionPlan", probeClass, CompressorUiState::class.java
).apply { isAccessible = true }
val state = CompressorUiState(
originalVideoMime = sourceMime, originalWidth = 3840,
originalHeight = 2160, originalFps = 60f,
targetResolutionHeight = 2160, targetFps = 0
)
val plan = method.invoke(viewModel, probe, state)
return plan.javaClass.getDeclaredMethod("getBlockingError")
.apply { isAccessible = true }.invoke(plan) as String
}

@Test
fun testCompressedOutputFileName() {
val state = CompressorUiState(
Expand Down
Loading