Android - 如何用ExoPlayer播放AES/GCM/NoPadding加密视频?
Android - How to play AES/GCM/NoPadding encrypted Video with ExoPlayer?
我 AES/GCM/NoPadding 在内部应用程序存储中加密了视频,我想使用 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()
}
和我的自定义数据源尝试(上面的代码中没有使用):
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)
}
我 AES/GCM/NoPadding 在内部应用程序存储中加密了视频,我想使用 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()
}
和我的自定义数据源尝试(上面的代码中没有使用):
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)
}