Android - 如何用ExoPlayer播放AES/GCM/NoPadding加密视频?

Android - How to play AES/GCM/NoPadding encrypted Video with ExoPlayer?

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

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

还有各种版本。

我是不是漏掉了什么?


下面是部分代码:

我的播放器设置

    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)
}