From d5d754a25c54bdfe168cc6338488c49cbfb48005 Mon Sep 17 00:00:00 2001 From: igor-bv Date: Tue, 10 Feb 2026 20:44:08 +0200 Subject: [PATCH 1/3] Add Android enableDrmLicenseRenewRetry TweakConfig flag. --- CHANGELOG.md | 4 ++++ .../player/reactnative/converter/JsonConverter.kt | 1 + src/tweaksConfig.ts | 8 ++++++++ 3 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65f18d25..942212a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Update Bitmovin's native Android SDK version to `3.141.0+jason` +### Added + +- Android: TweaksConfig option `enableDrmLicenseRenewRetry` to retry renewing a DRM license when the first renewal attempt fails + ## [1.8.0] - 2026-02-03 ### Changed diff --git a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt index 77e59eea..ae163384 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt @@ -202,6 +202,7 @@ fun Map.toTweaksConfig(): TweaksConfig = TweaksConfig().apply { withBoolean("useDrmSessionForClearPeriods") { useDrmSessionForClearPeriods = it } withBoolean("useDrmSessionForClearSources") { useDrmSessionForClearSources = it } withBoolean("useFiletypeExtractorFallbackForHls") { useFiletypeExtractorFallbackForHls = it } + withBoolean("enableDrmLicenseRenewRetry") { enableDrmLicenseRenewRetry = it } withStringArray("forceReuseVideoCodecReasons") { forceReuseVideoCodecReasons = it .filterNotNull() diff --git a/src/tweaksConfig.ts b/src/tweaksConfig.ts index 23655068..dced5eaa 100644 --- a/src/tweaksConfig.ts +++ b/src/tweaksConfig.ts @@ -166,6 +166,14 @@ export interface TweaksConfig { * @platform Android */ useFiletypeExtractorFallbackForHls?: boolean; + /** + * If enabled, the player will retry renewing a DRM license in case the first renewal attempt fails. + * This can help maintain playback for long-running sessions where the license might expire. + * + * @defaultValue `false` + * @platform Android + */ + enableDrmLicenseRenewRetry?: boolean; /** * Determines whether `AVKit` should update Now Playing information automatically when using System UI. * From 61b45094d60edeb618e4f80e53daf38f1e876308 Mon Sep 17 00:00:00 2001 From: igor-bv Date: Tue, 10 Feb 2026 20:44:08 +0200 Subject: [PATCH 2/3] Add Android enableDrmLicenseRenewRetry TweakConfig flag. --- CHANGELOG.md | 4 ++ .../reactnative/converter/JsonConverter.kt | 60 +++++++++++-------- src/tweaksConfig.ts | 8 +++ 3 files changed, 47 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65f18d25..942212a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Update Bitmovin's native Android SDK version to `3.141.0+jason` +### Added + +- Android: TweaksConfig option `enableDrmLicenseRenewRetry` to retry renewing a DRM license when the first renewal attempt fails + ## [1.8.0] - 2026-02-03 ### Changed diff --git a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt index 77e59eea..92da2e2e 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt @@ -181,32 +181,42 @@ private fun String.toForceReuseVideoCodecReason(): ForceReuseVideoCodecReason? = else -> null } -fun Map.toTweaksConfig(): TweaksConfig = TweaksConfig().apply { - withDouble("timeChangedInterval") { timeChangedInterval = it } - withInt("bandwidthEstimateWeightLimit") { - bandwidthMeterType = BandwidthMeterType.Default( - bandwidthEstimateWeightLimit = it, - ) +fun Map.toTweaksConfig(): TweaksConfig { + val renewRetry = this["enableDrmLicenseRenewRetry"] as? Boolean + val cfg = if (renewRetry != null) { + // enableDrmLicenseRenewRetry is a val in the SDK, so it must be set via constructor + TweaksConfig(enableDrmLicenseRenewRetry = renewRetry) + } else { + TweaksConfig() } - withMap("devicesThatRequireSurfaceWorkaround") { devices -> - val deviceNames = devices.withStringArray("deviceNames") { - it.filterNotNull().map(::DeviceName) - } ?: emptyList() - val modelNames = devices.withStringArray("modelNames") { - it.filterNotNull().map(::DeviceName) - } ?: emptyList() - devicesThatRequireSurfaceWorkaround = deviceNames + modelNames - } - withBoolean("languagePropertyNormalization") { languagePropertyNormalization = it } - withDouble("localDynamicDashWindowUpdateInterval") { localDynamicDashWindowUpdateInterval = it } - withBoolean("useDrmSessionForClearPeriods") { useDrmSessionForClearPeriods = it } - withBoolean("useDrmSessionForClearSources") { useDrmSessionForClearSources = it } - withBoolean("useFiletypeExtractorFallbackForHls") { useFiletypeExtractorFallbackForHls = it } - withStringArray("forceReuseVideoCodecReasons") { - forceReuseVideoCodecReasons = it - .filterNotNull() - .mapNotNull(String::toForceReuseVideoCodecReason) - .toSet() + + return cfg.apply { + withDouble("timeChangedInterval") { timeChangedInterval = it } + withInt("bandwidthEstimateWeightLimit") { + bandwidthMeterType = BandwidthMeterType.Default( + bandwidthEstimateWeightLimit = it, + ) + } + withMap("devicesThatRequireSurfaceWorkaround") { devices -> + val deviceNames = devices.withStringArray("deviceNames") { + it.filterNotNull().map(::DeviceName) + } ?: emptyList() + val modelNames = devices.withStringArray("modelNames") { + it.filterNotNull().map(::DeviceName) + } ?: emptyList() + devicesThatRequireSurfaceWorkaround = deviceNames + modelNames + } + withBoolean("languagePropertyNormalization") { languagePropertyNormalization = it } + withDouble("localDynamicDashWindowUpdateInterval") { localDynamicDashWindowUpdateInterval = it } + withBoolean("useDrmSessionForClearPeriods") { useDrmSessionForClearPeriods = it } + withBoolean("useDrmSessionForClearSources") { useDrmSessionForClearSources = it } + withBoolean("useFiletypeExtractorFallbackForHls") { useFiletypeExtractorFallbackForHls = it } + withStringArray("forceReuseVideoCodecReasons") { + forceReuseVideoCodecReasons = it + .filterNotNull() + .mapNotNull(String::toForceReuseVideoCodecReason) + .toSet() + } } } diff --git a/src/tweaksConfig.ts b/src/tweaksConfig.ts index 23655068..dced5eaa 100644 --- a/src/tweaksConfig.ts +++ b/src/tweaksConfig.ts @@ -166,6 +166,14 @@ export interface TweaksConfig { * @platform Android */ useFiletypeExtractorFallbackForHls?: boolean; + /** + * If enabled, the player will retry renewing a DRM license in case the first renewal attempt fails. + * This can help maintain playback for long-running sessions where the license might expire. + * + * @defaultValue `false` + * @platform Android + */ + enableDrmLicenseRenewRetry?: boolean; /** * Determines whether `AVKit` should update Now Playing information automatically when using System UI. * From 8125ffb1259686c9218c6eb538e8984a1bda9229 Mon Sep 17 00:00:00 2001 From: "igor.bocharnikov@3ss.tv" Date: Wed, 18 Feb 2026 19:33:18 +0200 Subject: [PATCH 3/3] Code review fix: Change TweaksConfig init to receiver pattern --- .../reactnative/converter/JsonConverter.kt | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt index c21e7eef..78ee58cd 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt @@ -181,20 +181,15 @@ private fun String.toForceReuseVideoCodecReason(): ForceReuseVideoCodecReason? = else -> null } -fun Map.toTweaksConfig(): TweaksConfig { - val renewRetry = this["enableDrmLicenseRenewRetry"] as? Boolean - val cfg = if (renewRetry != null) { - // enableDrmLicenseRenewRetry is a val in the SDK, so it must be set via constructor - TweaksConfig(enableDrmLicenseRenewRetry = renewRetry) - } else { - TweaksConfig() - } - - return cfg.apply { - withDouble("timeChangedInterval") { timeChangedInterval = it } +fun Map.toTweaksConfig(): TweaksConfig = + TweaksConfig.Builder().apply { + withBoolean("enableDrmLicenseRenewRetry") { setEnableDrmLicenseRenewRetry(it) } + withDouble("timeChangedInterval") { setTimeChangedInterval(it) } withInt("bandwidthEstimateWeightLimit") { - bandwidthMeterType = BandwidthMeterType.Default( - bandwidthEstimateWeightLimit = it, + setBandwidthMeterType( + BandwidthMeterType.Default( + bandwidthEstimateWeightLimit = it, + ) ) } withMap("devicesThatRequireSurfaceWorkaround") { devices -> @@ -204,21 +199,22 @@ fun Map.toTweaksConfig(): TweaksConfig { val modelNames = devices.withStringArray("modelNames") { it.filterNotNull().map(::DeviceName) } ?: emptyList() - devicesThatRequireSurfaceWorkaround = deviceNames + modelNames + setDevicesThatRequireSurfaceWorkaround(deviceNames + modelNames) } - withBoolean("languagePropertyNormalization") { languagePropertyNormalization = it } - withDouble("localDynamicDashWindowUpdateInterval") { localDynamicDashWindowUpdateInterval = it } - withBoolean("useDrmSessionForClearPeriods") { useDrmSessionForClearPeriods = it } - withBoolean("useDrmSessionForClearSources") { useDrmSessionForClearSources = it } - withBoolean("useFiletypeExtractorFallbackForHls") { useFiletypeExtractorFallbackForHls = it } + withBoolean("languagePropertyNormalization") { setLanguagePropertyNormalization(it) } + withDouble("localDynamicDashWindowUpdateInterval") { setLocalDynamicDashWindowUpdateInterval(it) } + withBoolean("useDrmSessionForClearPeriods") { setUseDrmSessionForClearPeriods(it) } + withBoolean("useDrmSessionForClearSources") { setUseDrmSessionForClearSources(it) } + withBoolean("useFiletypeExtractorFallbackForHls") { setUseFiletypeExtractorFallbackForHls(it) } withStringArray("forceReuseVideoCodecReasons") { - forceReuseVideoCodecReasons = it - .filterNotNull() - .mapNotNull(String::toForceReuseVideoCodecReason) - .toSet() + setForceReuseVideoCodecReasons( + it + .filterNotNull() + .mapNotNull(String::toForceReuseVideoCodecReason) + .toSet() + ) } - } -} + }.build() fun Map.toAdvertisingConfig(): AdvertisingConfig? { val schedule = getArray("schedule")?.toMapList()?.mapNotNull { it?.toAdItem() } ?: emptyList()