赞
踩
根据以上,我通过修改ExoPlayer的源代码实现以下功能,这里不讨论其他视频流加密解密的方法
PS:使用ffmpeg进行音视频分割后使用Java代码进行加密
private static String encryptVideoWithFFmpeg(String videoFilePath, String outputDirPath) { File outputDir = new File(outputDirPath); if (!outputDir.exists()) { outputDir.mkdirs(); } String outputFileName = "output"; // 输出文件名,这里可以根据需要自定义 String tsOutputPath = outputDirPath + File.separator + outputFileName + ".ts"; String m3u8OutputPath = outputDirPath + File.separator + outputFileName + ".m3u8"; try { ProcessBuilder processBuilder = new ProcessBuilder("ffmpeg", "-i", videoFilePath, "-c:v", "libx264", "-c:a", "aac", "-f", "hls", "-hls_time", "5", "-hls_list_size", "0", "-hls_segment_filename", outputDirPath + File.separator + "output%03d.ts", m3u8OutputPath); // 设置工作目录,可以防止某些情况下找不到 ffmpeg 命令的问题 Process process = processBuilder.start(); // 获取 ffmpeg 命令执行的输出信息(可选,如果需要查看 ffmpeg 执行日志) BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream())); String line; while ((line = reader.readLine()) != null) { System.out.println(line); } int exitCode = process.waitFor(); if (exitCode == 0) { System.out.println("FFmpeg command executed successfully."); } else { System.err.println("Error executing FFmpeg command. Exit code: " + exitCode); } } catch (IOException | InterruptedException e) { e.printStackTrace(); } return tsOutputPath; } private static String splitAudioWithFFmpeg(String audioFilePath, String outputDirPath) { File outputDir = new File(outputDirPath); if (!outputDir.exists()) { outputDir.mkdirs(); } String outputFileName = "output"; // 输出文件名,这里可以根据需要自定义 String tsOutputPath = outputDirPath + File.separator + outputFileName + ".ts"; String m3u8OutputPath = outputDirPath + File.separator + outputFileName + ".m3u8"; try { ProcessBuilder processBuilder = new ProcessBuilder("ffmpeg", "-i", audioFilePath, "-c:a", "aac", "-f", "hls", "-hls_time", "10", "-hls_list_size", "0", "-hls_segment_filename", outputDirPath + File.separator + "output%03d.ts", m3u8OutputPath); Process process = processBuilder.start(); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream())); String line; while ((line = reader.readLine()) != null) { System.out.println(line); } int exitCode = process.waitFor(); if (exitCode == 0) { System.out.println("FFmpeg command executed successfully."); } else { System.err.println("Error executing FFmpeg command. Exit code: " + exitCode); } } catch (IOException | InterruptedException e) { e.printStackTrace(); } return tsOutputPath; }
private static void encryptTSSegmentsWithAES(String outputDirPath, String aesKey) { File outputDir = new File(outputDirPath); File[] tsFiles = outputDir.listFiles((dir, name) -> name.endsWith(".ts")); if (tsFiles != null) { try { byte[] keyBytes = aesKey.getBytes(); Key aesKeySpec = new SecretKeySpec(keyBytes, AES_ALGORITHM); Cipher cipher = Cipher.getInstance(AES_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, aesKeySpec); for (File tsFile : tsFiles) { byte[] tsData = Files.readAllBytes(Paths.get(tsFile.getPath())); byte[] encryptedData = cipher.doFinal(tsData); Files.write(Paths.get(tsFile.getPath()), encryptedData); } } catch (Exception e) { e.printStackTrace(); } } } public static void decryptTSSegmentsWithAES(String outputDirPath, String aesKey) { File outputDir = new File(outputDirPath); File[] tsFiles = outputDir.listFiles((dir, name) -> name.endsWith(".ts")); if (tsFiles != null) { try { byte[] keyBytes = aesKey.getBytes(); Key aesKeySpec = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance(AES_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, aesKeySpec); for (File tsFile : tsFiles) { byte[] tsData = Files.readAllBytes(Paths.get(tsFile.getPath())); byte[] encryptedData = cipher.doFinal(tsData); Files.write(Paths.get(tsFile.getPath()), encryptedData); } } catch (Exception e) { e.printStackTrace(); } } }
加密完成之后将m3u8放在服务器上,并且分割的文件也要在同一目录,或者切片的时候手动设置,保证切片后的视频可以正常播放即可
这里使用的是修改ExoPlayer的源代码来实现的,因为在Android手机上面播放视频的选择有很多,大家也可以根据我的方法修改其他播放器,本次按照ExoPlayer进行演示教学
PS:因为Google把ExoPlayer整合到MediaPlayer3里了,所以如果不使用纯源代码来修改的话,也会跟我的演示一样有删除线,但是无伤大雅
implementation 'com.google.android.exoplayer:exoplayer-core:2.19.0'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.19.0'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.19.0'
implementation 'com.google.android.exoplayer:exoplayer-hls:2.19.0'
我们只需要复制其源码然后进行修改后,使用ExoPlayer播放视频的时候,使用我们自己的类即可,如果你不想这样,那么可以直接下载ExoPlayer2的源代码进行修改,这样的话还能去除废弃的表示,没有那么多删除线,接下来我们正式开始修改
修改类“DefaultHttpDataSource”
我将以注释的方式来讲解代码,注意这里只是演示一个简单的自定义加解密的切入方式,所以按照文件名末尾为ts的文件进行暴力判断,精细化的处理方式可以有很多拓展,比如仅加密视频的中间部分作为会员视频,这样只需要单一视频流就可以解决试看的问题,而且不怕应用内部修改VIP标志位(对于修改源码等暴力破解的方法无效,毕竟源码都给你扒出来了)
//定义解密流,主要使用此流来进行解密 private CipherInputStream cipherInputStream; //修改open方法代码,最后的try代码块中增加如下内容用来解密流 @Override public long open(DataSpec dataSpec) throws HttpDataSourceException { .... try { inputStream = connection.getInputStream(); if (isCompressed) { inputStream = new GZIPInputStream(inputStream); } //新增代码块,这里的解密方法可以按照自己的需求编写---------------------------------- if (dataSpec.uri.getPath().endsWith(".ts")) { Cipher cipher; try { cipher = Cipher.getInstance("AES"); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { throw new RuntimeException(e); } Key aesKeySpec = new SecretKeySpec("1234567890abcdef".getBytes(), "AES"); try { cipher.init(Cipher.DECRYPT_MODE, aesKeySpec); } catch (InvalidKeyException e) { throw new RuntimeException(e); } cipherInputStream = new CipherInputStream(inputStream, cipher); } //新增代码块结束------------------------------ } catch (IOException e) { closeConnectionQuietly(); throw new HttpDataSourceException( e, dataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, HttpDataSourceException.TYPE_OPEN); } .... } //修改read方法如下,如果判断是需要解密的文件则走cipherInputStream @Override public final int read(byte[] buffer, int offset, int length) throws IOException { if (dataSpec.uri.getPath().endsWith(".ts")) { Assertions.checkNotNull(cipherInputStream); int bytesRead = cipherInputStream.read(buffer, offset, length); if (bytesRead < 0) { return C.RESULT_END_OF_INPUT; } return bytesRead; } else { try { return readInternal(buffer, offset, length); } catch (IOException e) { throw HttpDataSourceException.createForIOException( e, castNonNull(dataSpec), HttpDataSourceException.TYPE_READ); } } } //最后释放资源 @Override public void close() throws HttpDataSourceException { try { @Nullable InputStream inputStream = this.inputStream; if (inputStream != null) { long bytesRemaining = bytesToRead == C.LENGTH_UNSET ? C.LENGTH_UNSET : bytesToRead - bytesRead; maybeTerminateInputStream(connection, bytesRemaining); try { inputStream.close(); } catch (IOException e) { throw new HttpDataSourceException( e, castNonNull(dataSpec), PlaybackException.ERROR_CODE_IO_UNSPECIFIED, HttpDataSourceException.TYPE_CLOSE); } } if (cipherInputStream != null) { cipherInputStream.close(); } } catch (IOException e) { throw new HttpDataSourceException( e, castNonNull(dataSpec), PlaybackException.ERROR_CODE_IO_UNSPECIFIED, HttpDataSourceException.TYPE_CLOSE); } finally { inputStream = null; cipherInputStream = null; closeConnectionQuietly(); if (opened) { opened = false; transferEnded(); } } }
修改类“DefaultDataSourceFactory”
此类只需要修改一点,那就是将DefaultDataSource的create过程引导到我们自己写的DefaultDataSource,也就是删除原来的ExoPlayer2的依赖引入,引入刚刚讲到的DefaultHttpDataSource,不需要修改代码,只需要切换依赖即可
public DefaultDataSourceFactory(
Context context, @Nullable String userAgent, @Nullable TransferListener listener) {
this(context, listener, new DefaultHttpDataSource.Factory().setUserAgent(userAgent));
}
因为ExoPlayer2同时支持音频和视频的播放,所以均可使用下列方式完成
public class PlayerActivity extends AppCompatActivity { private PlayerView playerView; private SimpleExoPlayer player; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_player); // Initialize PlayerView playerView = findViewById(R.id.player); // Create a DefaultTrackSelector to enable tracks DefaultTrackSelector trackSelector = new DefaultTrackSelector(this); // Create an instance of ExoPlayer player = new SimpleExoPlayer.Builder(this) .setTrackSelector(trackSelector) .build(); // Attach the player to the PlayerView playerView.setPlayer(player); String userAgent = Util.getUserAgent(this, "ExoPlayerDemo"); DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(this, userAgent); String videoUrl = "http://zhangzhiao.top/missit/aa/output.m3u8"; // Create an HlsMediaSource HlsMediaSource mediaSource = new HlsMediaSource.Factory(dataSourceFactory) .createMediaSource(MediaItem.fromUri(Uri.parse(videoUrl))); // Prepare the player with the media source player.prepare(mediaSource); } @Override protected void onDestroy() { super.onDestroy(); // Release the player when the activity is destroyed player.release(); } }
代码里给大家提供了一个小视频,如果按照流程编写应该是可以顺利播放的,如果需要还可以把m3u8文件进行加密处理,一切处理方法都可以实现,如果对您有帮助不妨点个赞
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。