Reproducción de vídeo cifrado con ExoPlayer

Estoy utilizando ExoPlayer , en Android, y estoy tratando de reproducir un video cifrado almacenado localmente.

La modularidad de ExoPlayer permite crear componentes personalizados que se pueden inyectar en ExoPlayer, y esto parece ser el caso. De hecho, después de algunas investigaciones me di cuenta de que para lograr esa tarea podría crear un DataSource personalizado y sobreescribir open() , read() y close() .

También he encontrado esta solución , pero en realidad aquí el archivo entero se descifra en un solo paso y se almacena en un inputtream claro. Esto puede ser bueno en muchas situaciones. Pero, ¿y si necesito reproducir archivos grandes?

Así que la pregunta es: ¿cómo puedo reproducir vídeo cifrado en ExoPlayer, descifrando contenido "on-fly" (sin descifrar todo el archivo)? ¿Es esto posible?

He intentado crear un DataSource personalizado que tiene el método 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; } 

Y este es el método 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; } } 

Si en vez de un archivo codificado paso un archivo claro, y apenas quito la pieza de CipherInputStream, entonces trabaja muy bien, en lugar con el archivo cifrado obtengo este error:

  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) 

EDIT :

El video cifrado se genera de esta manera:

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

Entonces el outputStream se guarda en un archivo.

Compruebe su proxy, dada la siguiente configuración.

 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 ? 

En la aplicación Demo de ExoPlayer – DashRenderBuilder.java tiene un método 'filterHdContent' esto siempre devuelve true si el dispositivo no es de nivel 1 (asumiendo aquí es L3). Esto hace que el reproductor no tenga en cuenta el HD AdaptionSet en el mpd mientras lo analiza.

Puede configurar el filtroHdContent para devolver siempre false si desea reproducir HD, sin embargo, es típico de los propietarios de contenido que requieran una implementación L1 Widevine para contenido HD.

Consulta este enlace para obtener más https://github.com/google/ExoPlayer/issues/1116 https://github.com/google/ExoPlayer/issues/1523

No creo que un DataSource personalizado, con abrir / leer / cerrar, es una solución a su necesidad. Para un descifrado 'on-the-fly' (valioso para archivos grandes pero no sólo), debe diseñar una arquitectura de streaming.

Ya hay publicaciones similares a las tuyas. Para encontrarlos, no busque 'exoplayer', sino 'videoview' o 'mediaplayer' en su lugar. Las respuestas deben ser compatibles.

Por ejemplo, reproducir archivos de vídeo cifrados mediante VideoView

Finalmente encontré la solución.

He utilizado un no-relleno para el algoritmo de cifrado, de esta manera:

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

De modo que el tamaño del archivo cifrado y el tamaño de archivo claro permanezcan iguales. Así que ahora he creado el flujo:

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

Esto se debe a que la documentación de Java dice acerca de ChiperInputStream.available() que

Este método debe ser anulado

Y en realidad creo que es más como un DEBE, porque los valores recuperados de ese método son a menudo muy extraño.

¡Y eso es todo! Ahora funciona perfectamente.

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.