-
Notifications
You must be signed in to change notification settings - Fork 120
Description
Describe the bug
LcpAuthenticating.retrievePassphrase() is never called when opening LCP-protected audiobook (.lcpa) with custom authentication implementation, causing playback to fail with "Cannot decipher content because the publication is locked" error.
How to reproduce?
Setup:
// 1. Create custom authentication
class LCPPassphraseAuthentication(
private val passphrase: String
) : LcpAuthenticating {
override suspend fun retrievePassphrase(
license: LcpAuthenticating.AuthenticatedLicense,
reason: LcpAuthenticating.AuthenticationReason,
allowUserInteraction: Boolean
): String {
Log.d("LCP", "retrievePassphrase called") // NEVER LOGGED
return passphrase
}
}
// 2. Initialize services
val httpClient = DefaultHttpClient()
val assetRetriever = AssetRetriever(context.contentResolver, httpClient)
val lcpService = LcpService(context, assetRetriever)
// 3. Create PublicationOpener
val authentication = LCPPassphraseAuthentication("correct_passphrase")
val publicationParser = DefaultPublicationParser(context, httpClient, assetRetriever, null)
val publicationOpener = PublicationOpener(
publicationParser = publicationParser,
contentProtections = listOf(lcpService.contentProtection(authentication))
)
// 4. Retrieve and open .lcpa file
val formatHints = FormatHints(
mediaTypes = listOf(MediaType.LCP_PROTECTED_AUDIOBOOK),
fileExtensions = listOf(FileExtension("lcpa"))
)
val asset = assetRetriever.retrieve(lcpaFile, formatHints).getOrNull()!!
val publication = publicationOpener.open(asset, allowUserInteraction = true).getOrNull()!!
// 5. Create AudioNavigator
val engineProvider = ExoPlayerEngineProvider(application)
val factory = AudioNavigatorFactory(publication, engineProvider)!!
val navigator = factory.createNavigator(
initialLocator = publication.readingOrder[0].let { publication.locatorFromLink(it) },
initialPreferences = ExoPlayerPreferences(),
readingOrder = publication.readingOrder
).getOrNull()!!
// 6. Start playback
navigator.play() // FAILS: "Cannot decipher content because the publication is locked"
Observed behavior:
- retrievePassphrase() is NEVER called (confirmed with logging and LcpDialogAuthentication)
- Publication opens successfully without error
- Asset format correctly detected as application/audiobook+lcp
- AudioNavigator creates successfully
- Playback fails with org.readium.r2.shared.util.ErrorException: Cannot decipher content because the publication is locked
- Manual publication.get(link) CAN decrypt resources successfully
Expected behavior:
- retrievePassphrase() should be called during publicationOpener.open() to unlock the LCP license
- Publication should be unlocked for AudioNavigator resource access
- Playback should work
Readium version
- Readium Kotlin Toolkit: 3.1.2 - Kotlin: 2.0.21 - Android Gradle Plugin: 8.3.2
Android API version
Target SDK 34, Min SDK 21, Test device API 36 (Android emulator)
Additional context
Additional context:
- Same code pattern works successfully on iOS with Readium Swift 3.3.0
- Tested with both custom LcpAuthenticating implementation and built-in LcpDialogAuthentication - neither gets called
- Cleared app data to remove any cached passphrases - no change
- Suspect Readium may be using a different authentication path for .lcpa files or has a cached passphrase that blocks callback