Android 自定义加解密播放音视频(m3u8独立加密)

文章目录

    • 背景
    • 加密流程
    • 音视频解密
    • 音视频播放
    • 结语

背景

  1. 当涉及App内部视频的时候,我们不希望被别人以抓包的形式来爬取我们的视频
  2. 大视频文件以文件方式整个加密的话需要完全下载后才能进行解密
  3. 当前m3u8格式虽然支持加密,但是ts格式的小视频可以独立播放的,也就是ts文件本身没有被加密,或者加密方法过于复杂

根据以上,我通过修改ExoPlayer的源代码实现以下功能,这里不讨论其他视频流加密解密的方法

  1. 大文件分段加密后应用分段解密(m3u8)
  2. 高度自定义,你可以实现任何你需要的加密方法,甚至每一个ts都有自己的解码方式
  3. ts加密,不允许独立播放

加密流程

PS:使用ffmpeg进行音视频分割后使用Java代码进行加密

  1. 音视频分割
    代码就是通过java执行ffmpeg的命令即可,请确保环境变量中安装了ffmpeg,内部的代码可以自己通过需求来修改,其中音频与视频的分割方式差不多
 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;}
  1. 音视频加密
    这里的视频加密使用的是AES加密,是将ts结尾的所有文件进行加密,后面的方法是解密,一般用不到
 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里了,所以如果不使用纯源代码来修改的话,也会跟我的演示一样有删除线,但是无伤大雅

  1. 引入依赖,直接在App层的Build.gradle引入ExoPlayer2的依赖,其中我们要使用的视频流为hls格式,所以需要引入hls模块
	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'
  1. 准备修改代码,我们需要修改的类如下
  • DefaultDataSource
  • DefaultDataSourceFactory
  • DefaultHttpDataSource
  • HttpDataSource

我们只需要复制其源码然后进行修改后,使用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@Overridepublic 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);}}}
//最后释放资源@Overridepublic 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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_player);// Initialize PlayerViewplayerView = findViewById(R.id.player);// Create a DefaultTrackSelector to enable tracksDefaultTrackSelector trackSelector = new DefaultTrackSelector(this);// Create an instance of ExoPlayerplayer = new SimpleExoPlayer.Builder(this).setTrackSelector(trackSelector).build();// Attach the player to the PlayerViewplayerView.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 HlsMediaSourceHlsMediaSource mediaSource = new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(Uri.parse(videoUrl)));// Prepare the player with the media sourceplayer.prepare(mediaSource);}@Overrideprotected void onDestroy() {super.onDestroy();// Release the player when the activity is destroyedplayer.release();}
}

源码下载

结语

代码里给大家提供了一个小视频,如果按照流程编写应该是可以顺利播放的,如果需要还可以把m3u8文件进行加密处理,一切处理方法都可以实现,如果对您有帮助不妨点个赞

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/85939.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python协程(asyncio)(三)异步应用对象

流是用于处理网络连接的支持 async/await 的高层级原语。 流允许发送和接收数据&#xff0c;而不需要使用回调或低级协议和传输。 asyncio 函数可以用来创建和处理异步网络流。 import asyncioasync def tcp_echo_client(message):reader, writer await asyncio.open_connect…

智能生活从这里开始:数字孪生驱动的社区

数字孪生技术&#xff0c;这个近年来备受瞩目的名词&#xff0c;正迅速渗透到社区发展领域&#xff0c;改变着我们居住的方式、管理的方式以及与周围环境互动的方式。它不仅仅是一种概念&#xff0c;更是一种变革&#xff0c;下面我们将探讨数字孪生技术如何推动社区智能化发展…

Linux:network:socket:ip_unprivileged_port_start CAP_NET_BIND_SERVICE

ip_unprivileged_port_start - INTEGER 这个参数定义了,从哪一个port开始是非特权可以使用的port。而特权的port,需要root用户使用,或者需要权限:CAP_NET_BIND_SERVICE 。如果设置为0,就是没有特权port。 This is a per-namespace sysctl. It defines the first unprivile…

React 全栈体系(八)

第四章 React ajax 三、案例 – github 用户搜索 2. 代码实现 2.3 axios 发送请求 Search /* src/components/Search/index.jsx */ import React, { Component } from "react"; import axios from axiosexport default class Search extends Component {search …

基于微服务的第二课堂管理系统(素质拓展学分管理平台)SpringCloud、SpringBoot 分布式,微服务

基于微服务的第二课堂管理系统 一款真正的企业级开发项目&#xff0c;采用标准的企业规范开发&#xff0c;有项目介绍视频和源码&#xff0c;需要学习的同学可以拿去学习&#xff0c;这是一款真正可以写在简历上的校招项目&#xff0c;能够真正学到东西的一个项目&#xff0c;话…

基于springboot高校场馆预订系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

【zookeeper】zk集群安装与启动踩坑点

zk安装我也踩了一些坑。特别是第一点&#xff0c;zk官网好像都没什么说明&#xff0c;导致直接下错了&#xff0c;搞了好几个小时。 踩坑点如下&#xff1a; 1&#xff0c;在zk官网下载包时&#xff0c;注意3.5以后的版本&#xff0c;要下载带-bin的&#xff0c;3.5之后&…

重新认识架构—不只是软件设计

前言 什么是架构&#xff1f; 通常情况下&#xff0c;人们对架构的认知仅限于在软件工程中的定义&#xff1a;架构主要指软件系统的结构设计&#xff0c;比如常见的SOLID准则、DDD架构。一个良好的软件架构可以帮助团队更有效地进行软件开发&#xff0c;降低维护成本&#xff0…

Leetcode171. Excel 表列序号

给你一个字符串 columnTitle &#xff0c;表示 Excel 表格中的列名称。返回 该列名称对应的列序号 。 例如&#xff1a; A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> 28 ... 题解&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱…

mysqld_exporter监控MySQL服务

一、MySQL授权 1、登录MySQL服务器对监控使用的账号授权 CREATE USER exporterlocalhost IDENTIFIED BY 123456 WITH MAX_USER_CONNECTIONS 3; GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO exporterlocalhost; flush privileges;2、上传mysqld_exporter安装包&#…

Spring boot原理

起步依赖 Maven的传递依赖 自动配置 Springboot的自动配置就是当spring容器启动后&#xff0c;一些配置类、bean对象就自动存入到IOC容器中&#xff0c;不需要我们手动去声明&#xff0c;从而简化了开发&#xff0c;省去了繁琐的配置操作。 自动配置原理&#xff1a; 方案一…

druid在springboot中如何整合配置!

在Spring Boot中配置Druid作为数据源非常简单。Druid是一个高性能的数据库连接池&#xff0c;它提供了丰富的监控和统计功能&#xff0c;适用于各种数据库。以下是在Spring Boot中配置Druid数据源的步骤&#xff1a; 1. 添加Druid依赖&#xff1a; 首先&#xff0c;您需要在项…

MongoDB(一) windows 和 linux 之 Ubuntu 安装

数据库分类 一、关系型数据库&#xff08;RDBMS&#xff09; mysql 、Oracle、DB2、SQL Server 关系数据库中全都是表 二、非关系型数据库&#xff08;NO SQL&#xff09; MongoDB、Redis 键值对数据库 文档数据库MongoDB 下载 mongoDB https://www.mongodb.com/try/downloa…

获取文件最后修改时间

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl Java源码 public void testGetFileTime() {try {String string "E://test.txt";File file new File(string);Path path file.toPath();BasicFileAttributes ba…

【eslint】屏蔽语言提醒

在 JavaScript 中&#xff0c;ESLint 是一种常用的静态代码分析工具&#xff0c;它用于检测和提醒代码中的潜在问题和风格问题。有时候&#xff0c;在某些特定情况下&#xff0c;你可能希望临时屏蔽或禁用某些 ESLint 的提醒信息&#xff0c;以便消除不必要的警告或避免不符合项…

【视觉SLAM入门】8. 回环检测,词袋模型,字典,感知,召回,机器学习

"见人细过 掩匿盖覆” 1. 意义2. 做法2.1 词袋模型和字典2.1.2 感知偏差和感知变异2.1.2 词袋2.1.3 字典 2.2 匹配(相似度)计算 3. 提升 前言&#xff1a; 前端提取数据&#xff0c;后端优化数据&#xff0c;但误差会累计&#xff0c;需要回环检测构建全局一致的地图&…

Go 语言学习总结(9)—— Go 与 Java 全面对比总结

基本语法格式 Golang: 编码风格相对统一&#xff0c;简单&#xff0c;没有太多的语法糖等&#xff0c;Java层次清晰&#xff0c;全面面向对象。 变量相关 变量的声明及使用 在Java或者PHP、Python中&#xff0c;声明了变量&#xff0c;可以不使用&#xff0c;也不报错。 p…

函数形状的定义方式

在TypeScript中&#xff0c;函数形状有三种定义方式&#xff1a;函数声明、函数表达式和箭头函数。 1.函数声明&#xff1a; function add(x: number, y: number): number {return x y; }2.函数表达式&#xff1a; const subtract function(x: number, y: number): number {r…

JavaWeb开发-06-SpringBootWeb-MySQL

一.MySQL概述 1.安装、配置 官网下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/ 2.数据模型 3.SQL简介 二.数据库设计-DDL 1.数据库 官网&#xff1a;http:// https://www.jetbrains.com/zh-cn/datagrip/ 2.表&#xff08;创建、查询、修改、删除&#xff09; #…

【完全二叉树魔法:顺序结构实现堆的奇象】

本章重点 二叉树的顺序结构堆的概念及结构堆的实现堆的调整算法堆的创建堆排序TOP-K问题 1.二叉树的顺序结构 普通的二叉树是不适合用数组来存储的&#xff0c;因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构…