Воспроизведение зашифрованного видео с использованием ExoPlayer

я используюExoPlayerв Android, и я пытаюсь воспроизвести зашифрованное видео, хранящееся локально.

Модульность ExoPlayer позволяет создавать пользовательские компоненты, которые могут быть внедрены в ExoPlayer, и это, похоже, так. Действительно, после некоторых исследований я понял, что для достижения этой задачи я мог бы создать собственный источник данных и переопределитьopen(), read() а такжеclose().

Я также нашелэто решение, но на самом деле здесь весь файл расшифровывается за один шаг и сохраняется в чистом потоке ввода. Это может быть хорошо во многих ситуациях. Но что, если мне нужно воспроизвести большой файл?

Итак, вопрос: как я могу воспроизвести зашифрованное видео в ExoPlayer, расшифровывая контент «на лету» (не расшифровывая весь файл)? Это возможно?

Я попытался создать собственный источник данных, который имеет метод open ():

@Override
    public long open(DataSpec dataSpec) throws FileDataSourceException {
        try {
            File file = new File(dataSpec.uri.getPath());

            clearInputStream = new CipherInputStream(new FileInputStream(file), mCipher);

            long skipped = clearInputStream.skip(dataSpec.position);
            if (skipped < dataSpec.position) {
                throw new EOFException();
            }
            if (dataSpec.length != C.LENGTH_UNBOUNDED) {
                bytesRemaining = dataSpec.length;
            } else {
                bytesRemaining = clearInputStream.available();
                if (bytesRemaining == 0) {
                    bytesRemaining = C.LENGTH_UNBOUNDED;
                }
            }
        } catch (EOFException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        opened = true;
        if (listener != null) {
            listener.onTransferStart();
        }

        return bytesRemaining;
    }

И это метод read ():

@Override
public int read(byte[] buffer, int offset, int readLength) throws FileDataSourceException {
        if (bytesRemaining == 0) {
            return -1;
        } else {
            int bytesRead = 0;

                int bytesToRead = bytesRemaining == C.LENGTH_UNBOUNDED ? readLength
                        : (int) Math.min(bytesRemaining, readLength);
            try {
                bytesRead = clearInputStream.read(buffer, offset, bytesToRead);
            } catch (IOException e) {
                e.printStackTrace();
            }

            if (bytesRead > 0) {
                if (bytesRemaining != C.LENGTH_UNBOUNDED) {
                    bytesRemaining -= bytesRead;
                }
                if (listener != null) {
                    listener.onBytesTransferred(bytesRead);
                }
            }

            return bytesRead;
        }
    }

Если вместо закодированного файла я пропускаю чистый файл и просто удаляю часть CipherInputStream, тогда она работает нормально, вместо этого с зашифрованным файлом я получаю эту ошибку:

    Unexpected exception loading stream
java.lang.IllegalStateException: Top bit not zero: -1195853062
at com.google.android.exoplayer.util.ParsableByteArray.readUnsignedIntToInt(ParsableByteArray.java:240)
at com.google.android.exoplayer.extractor.mp4.Mp4Extractor.readSample(Mp4Extractor.java:331)
at com.google.android.exoplayer.extractor.mp4.Mp4Extractor.read(Mp4Extractor.java:122)
at com.google.android.exoplayer.extractor.ExtractorSampleSource$ExtractingLoadable.load(ExtractorSampleSource.java:745)
at com.google.android.exoplayer.upstream.Loader$LoadTask.run(Loader.java:209)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)

РЕДАКТИРОВАТЬ:

зашифрованное видео создается следующим образом:

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec("0123456789012345".getBytes(), "AES");
IvParameterSpec ivSpec = new IvParameterSpec("0123459876543210".getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

outputStream = new CipherOutputStream(output_stream, cipher);

Затем outputStream сохраняется в файл.

 GVillani8205 авг. 2016 г., 09:18
С DataInputStream у меня такая же проблема
 GVillani8204 авг. 2016 г., 22:43
Я отредактировал свой вопрос
 sergej shafarenka05 авг. 2016 г., 08:33
Я бы попробовал завернутьCipherInputStream сDataInputStream и использоватьreadFully() вместоread().
 sergej shafarenka04 авг. 2016 г., 22:40
У вас есть зашифрованное видео? Каким шифрованием он шифруется?

Ответы на вопрос(4)

Решение Вопроса

Я использовал no-padding для алгоритма шифрования следующим образом:

cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");

так что размер зашифрованного файла и размер чистого файла остаются неизменными. Итак, теперь я создал поток:

cipherInputStream = new CipherInputStream(inputStream, cipher) {
    @Override
    public int available() throws IOException {
         return in.available();
    }
};

Это потому, что в документации Java говорится оChiperInputStream.available() тот

Этот методдолжен быть переопределенным

и на самом деле я думаю, что это больше похоже на ОБЯЗАТЕЛЬНО, потому что значения, полученные из этого метода, часто очень странные.

И это все! Теперь это работает отлично.

надеюсь, это кому-нибудь поможет. Я использую Kotlin здесь

import android.net.Uri
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DataSourceInputStream
import com.google.android.exoplayer2.upstream.DataSpec
import com.google.android.exoplayer2.util.Assertions
import java.io.IOException
import javax.crypto.CipherInputStream

class EncryptedDataSource(upstream: DataSource) : DataSource {

    private var upstream: DataSource? = upstream
    private var cipherInputStream: CipherInputStream? = null

    override fun open(dataSpec: DataSpec?): Long {
        val cipher = getCipherInitDecrypt()
        val inputStream = DataSourceInputStream(upstream, dataSpec)
        cipherInputStream = CipherInputStream(inputStream, cipher)
        inputStream.open()
        return C.LENGTH_UNSET.toLong()

    }

    override fun read(buffer: ByteArray?, offset: Int, readLength: Int): Int {
        Assertions.checkNotNull<Any>(cipherInputStream)
        val bytesRead = cipherInputStream!!.read(buffer, offset, readLength)
        return if (bytesRead < 0) {
            C.RESULT_END_OF_INPUT
        } else bytesRead
    }

    override fun getUri(): Uri {
        return upstream!!.uri
    }

    @Throws(IOException::class)
    override fun close() {
        if (cipherInputStream != null) {
            cipherInputStream = null
            upstream!!.close()
        }
    }
}

В приведенной выше функции вам нужно получить шифр, который использовался для шифрования иначать это: что-то вроде этого

fun getCipherInitDecrypt(): Cipher {
    val cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
    val iv = IvParameterSpec(initVector.toByteArray(charset("UTF-8")))
    val skeySpec = SecretKeySpec(key, TYPE_RSA)
    cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv)
    return cipher
}

Следующим шагом является созданиеDataSource.Factory заDataSource мы реализовали ранее

import com.google.android.exoplayer2.upstream.DataSource

class EncryptedFileDataSourceFactory(var dataSource: DataSource) : DataSource.Factory {

    override fun createDataSource(): DataSource {
        return EncryptedDataSource(dataSource)
    }
}

И последний шаг - инициализация игроков.

    private fun prepareExoPlayerFromFileUri(uri: Uri) {
        val player = ExoPlayerFactory.newSimpleInstance(
                    DefaultRenderersFactory(this),
                    DefaultTrackSelector(),
                    DefaultLoadControl())

        val playerView = findViewById<PlayerView>(R.id.player_view)
        playerView.player = player

        val dsf = DefaultDataSourceFactory(this, Util.getUserAgent(this, "ExoPlayerInfo"))
        //This line do the thing
        val mediaSource = ExtractorMediaSource.Factory(EncryptedFileDataSourceFactory(dsf.createDataSource())).createMediaSource(uri)
        player.prepare(mediaSource)
    }

что пользовательский источник данных с open / read / close - это решение для ваших нужд. Для дешифрования «на лету» (полезно для больших файлов, но не только) необходимо разработать потоковую архитектуру.

Уже есть сообщения, похожие на ваши. Чтобы найти их, ищите не «exoplayer», а «videoview» или «mediaplayer». Ответы должны быть совместимы.

Например,Воспроизведение зашифрованных видеофайлов с помощью VideoView

 GVillani8212 авг. 2016 г., 21:49
Конечно, вы можете использовать источник данных. ExoPlayer разработан модульно, так что DataSource может быть любым, что вы хотите. Вы можете создать свою собственную реализацию для извлечения данных из сети, из контент-провайдера, из ресурса, из файла (зашифрованного или нет) и т. Д. Так что это прекрасно. Посмотрите на мой ответ для деталей.

учитывая следующую конфигурацию.

ALLOWED_TRACK_TYPES = "SD_HD"
content_key_specs = [{ "track_type": "HD",
                       "security_level": 1,
                       "required_output_protection": {"hdcp": "HDCP_NONE" }
                     },
                     { "track_type": "SD",
                       "security_level": 1,
                       "required_output_protection": {"cgms_flags": "COPY_FREE" }
                     },
                     { "track_type": "AUDIO"}]
request = json.dumps({"payload": payload,
                      "content_id": content_id,
                      "provider": self.provider,
                      "allowed_track_types": ALLOWED_TRACK_TYPES,
                      "use_policy_overrides_exclusively": True,
                      "policy_overrides": policy_overrides,
                      "content_key_specs": content_key_specs
                     ?

В демонстрационном приложении ExoPlayer - DashRenderBuilder.java имеет метод filterHdContent, который всегда возвращает true, если устройство не уровня 1 (при условии, что здесь L3). Это приводит к тому, что проигрыватель игнорирует HD AdaptionSet в mpd во время его анализа.

Вы можете настроить filterHdContent так, чтобы он всегда возвращал значение false, если вы хотите воспроизводить HD, однако для владельцев контента обычно требуется реализация L1 Widevine для контента HD.

проверьте эту ссылку для болееhttps://github.com/google/ExoPlayer/issues/1116 https://github.com/google/ExoPlayer/issues/1523

 GVillani8212 авг. 2016 г., 21:50
Я действительно не понял вашего ответа. Кажется, это не связано с моей проблемой, поскольку это была просто проблема при создании источника данных надлежащим образом.

Ваш ответ на вопрос