Skip to content

LcpAuthenticating.retrievePassphrase() is never called when opening LCP-protected audiobook #733

@honeychawla

Description

@honeychawla

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions