Skip to content
Closed
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
5 changes: 4 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ android {
compose = true
buildConfig = true
}
testOptions {
unitTests.isIncludeAndroidResources = true
}
dependenciesInfo {
includeInBundle = false
includeInApk = false
Expand Down Expand Up @@ -76,4 +79,4 @@ dependencies {
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1939,7 +1939,14 @@ class CompressorViewModel(application: Application) : AndroidViewModel(applicati
val sourceHeight = sourceInfo?.height ?: state.originalHeight
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,10 @@
package compressedge.joshattic.us.viewmodel

import android.app.Application
import android.content.Context
import android.media.MediaCodecList
import android.media.MediaExtractor
import android.net.Uri
import androidx.media3.common.MimeTypes
import androidx.test.core.app.ApplicationProvider
import compressedge.joshattic.us.model.CompressorUiState
Expand All @@ -10,6 +14,8 @@ import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mockConstruction
import org.mockito.Mockito.`when`
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

Expand Down Expand Up @@ -110,6 +116,46 @@ 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(
compressedge.joshattic.us.R.string.error_decoder_config_unsupported_details,
3840, 2160, 60f, "hevc"
),
error
)
}

private fun planWithNoAdvertisedCodecs(sourceMime: String): String {
// Model the failing phone: no decoder advertises Dolby Vision. A source
// probe with no tracks falls back to the metadata already in UI state.
mockConstruction(MediaExtractor::class.java).use {
mockConstruction(MediaCodecList::class.java) { codecList, _ ->
`when`(codecList.codecInfos).thenReturn(emptyArray())
}.use {
val method = CompressorViewModel::class.java.getDeclaredMethod(
"buildCompressionPlan", Context::class.java, CompressorUiState::class.java,
Uri::class.java, Int::class.javaPrimitiveType, Int::class.javaPrimitiveType
).apply { isAccessible = true }
val state = CompressorUiState(
originalVideoMime = sourceMime, originalWidth = 3840,
originalHeight = 2160, originalFps = 60f
)
val plan = method.invoke(viewModel, application, state, Uri.EMPTY, 2160, 0)
return plan.javaClass.getDeclaredMethod("getBlockingError")
.apply { isAccessible = true }.invoke(plan) as String
}
}
}

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