安卓-如何使用ExoPlayer播放AES/GCM/NoPadding加密视频



I AES/GCM/NoPPadding在内部应用程序存储中加密视频,我想使用ExoPlayer播放它们。

没有什么对我有用。我试过了:

  • 带有FileDataSource的AesCipherDataSource->没有提取器可以读取流
  • 自定义数据源->NAL长度无效

还有各种版本的。

我是不是错过了什么?


以下是一些代码:

我的播放器设置

fun setupPlayer(photoId: Int) = viewModelScope.launch(Dispatchers.IO) {
val photo = photoRepository.get(photoId)
player = SimpleExoPlayer.Builder(app)
.setMediaSourceFactory(createMediaSourceFactory())
.build()
player!!.apply {
onMain {
setMediaItem(createMediaItem(photo))
prepare()
playWhenReady = true
}
}
}
private fun createMediaSourceFactory(): MediaSourceFactory {
val aesDataSource = AesCipherDataSource(encryptionManager.encodedKey, FileDataSource())
val factory = DataSource.Factory {
aesDataSource
}
return ProgressiveMediaSource.Factory(factory)
}
private fun createMediaItem(photo: Photo): MediaItem {
val uri = Uri.fromFile(app.getFileStreamPath(photo.internalFileName).canonicalFile)
return MediaItem.Builder()
.setMimeType(photo.type.mimeType)
.setUri(uri)
.build()
}

我的自定义DataSource尝试(上面的代码中没有使用(:

class AesGCMDataSource(
private val upstream: DataSource,
private val encryptionManager: EncryptionManager
) : DataSource {
private var cipherInputStream: CipherInputStream? = null
override fun addTransferListener(transferListener: TransferListener) {
upstream.addTransferListener(transferListener)
}
override fun open(dataSpec: DataSpec): Long {
val inputStream = DataSourceInputStream(upstream, dataSpec)
cipherInputStream = encryptionManager.createCipherInputStream(inputStream)
inputStream.open()
return C.LENGTH_UNSET.toLong()
}
override fun read(target: ByteArray, offset: Int, length: Int): Int {
Assertions.checkNotNull<Any>(cipherInputStream)
val read = cipherInputStream!!.read(target, offset, length)
return if (read < 0) {
C.RESULT_END_OF_INPUT
} else {
read
}
}
override fun getResponseHeaders(): MutableMap<String, MutableList<String>> {
return upstream.responseHeaders
}
override fun getUri(): Uri? = upstream.uri
override fun close() = upstream.close()
}

我找到了一个解决方案:

创建此数据源:

class AesDataSource(
private val cipher: Cipher
) : DataSource {
private var inputStream: CipherInputStream? = null
private lateinit var uri: Uri
override fun open(dataSpec: DataSpec): Long {
uri = dataSpec.uri
uri.path ?: return 0
val file = File(uri.path!!).canonicalFile
inputStream = CipherInputStream(file.inputStream(), cipher)
if (dataSpec.position != 0L) {
inputStream?.forceSkip(dataSpec.position) // Needed for skipping
}
return dataSpec.length
}
@Throws(IOException::class)
override fun read(target: ByteArray, offset: Int, length: Int): Int =
if (length == 0) {
0
} else {
inputStream?.read(target, offset, length) ?: 0
}
override fun addTransferListener(transferListener: TransferListener) {}
override fun getUri(): Uri = uri
override fun close() {
inputStream?.close()
}
}

DataSource使用以下扩展函数:

/**
* Skip bytes by reading them to a specific point.
* This is needed in GCM because the Authorisation Tag wont match when bytes are really skipped.
*/
fun CipherInputStream.forceSkip(bytesToSkip: Long): Long {
var processedBytes = 0L
while (processedBytes < bytesToSkip) {
read()
processedBytes++
}
return processedBytes
}

这样使用:

fun setupPlayer(file: Uri) {
player = SimpleExoPlayer.Builder(app)
.setMediaSourceFactory(createMediaSourceFactory())
.build()
.apply {
setMediaItem(createMediaItem(file))
prepare()
playWhenReady = true
}
}
private fun createMediaSourceFactory(): MediaSourceFactory {
val aesDataSource = AesDataSource(yourCipher) // Use your Cipher instance
val factory = DataSource.Factory {
aesDataSource
}
return ProgressiveMediaSource.Factory(factory)
}
private fun createMediaItem(file: Uri): MediaItem {
return MediaItem.fromUri(uri)
}

最新更新