Android 自定义加解密播放音视频(m3u8独立加密)
文章目录
- 背景
- 加密流程
- 音视频解密
- 音视频播放
- 结语
背景
- 当涉及App内部视频的时候,我们不希望被别人以抓包的形式来爬取我们的视频
- 大视频文件以文件方式整个加密的话需要完全下载后才能进行解密
- 当前m3u8格式虽然支持加密,但是ts格式的小视频可以独立播放的,也就是ts文件本身没有被加密,或者加密方法过于复杂
根据以上,我通过修改ExoPlayer的源代码实现以下功能,这里不讨论其他视频流加密解密的方法
- 大文件分段加密后应用分段解密(m3u8)
- 高度自定义,你可以实现任何你需要的加密方法,甚至每一个ts都有自己的解码方式
- ts加密,不允许独立播放
加密流程
PS:使用ffmpeg进行音视频分割后使用Java代码进行加密
- 音视频分割
代码就是通过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;}
- 音视频加密
这里的视频加密使用的是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里了,所以如果不使用纯源代码来修改的话,也会跟我的演示一样有删除线,但是无伤大雅
- 引入依赖,直接在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'
- 准备修改代码,我们需要修改的类如下
- 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文件进行加密处理,一切处理方法都可以实现,如果对您有帮助不妨点个赞
相关文章:
Android 自定义加解密播放音视频(m3u8独立加密)
文章目录 背景加密流程音视频解密音视频播放结语 背景 当涉及App内部视频的时候,我们不希望被别人以抓包的形式来爬取我们的视频大视频文件以文件方式整个加密的话需要完全下载后才能进行解密当前m3u8格式虽然支持加密,但是ts格式的小视频可以独立播放的…...

常见的文件格式
一、C:\fakepath\新建文本文档.txt [object String] 实现方式: <input onchange"test(this.value)" type"file"></input><script>function test(e){console.log(e,Object.prototype.toString.call(e))}</script> 二、…...
浏览器输入url后回车展开过程
当你在浏览器中输入一个URL并敲下回车后,浏览器会执行一系列步骤来访问并展示网页。下面是浏览器访问网页的一般流程: DNS解析:浏览器首先会提取URL中的主机名,然后向DNS服务器发送请求,将主机名解析为对应的IP地址。这…...
Docker 容器创建命令说明
使用命令如下: docker run -itd --name=cluster -v /home/ClusterApp/cluster:/home/ClusterApp/cluster --restart=always --privileged=true -p 12000:12000 python:3.8 命令说明: docker run 创建一个容器并运行 docker run --help 可以查看所有的参数: 命令中参数说明 -…...

西瓜书读书笔记整理(六)—— 第六章 支持向量机
第六章 支持向量机 6.1 间隔与支持向量6.1.1 什么是支持向量机6.1.2 支持向量与间隔6.1.3 支持向量机的求解过程 6.2 对偶问题(dual problem)6.2.1 什么是对偶问题6.2.2 如何求解支持向量机的对偶问题 6.3 核函数(kernel function)…...

蓝桥杯每日一题2023.9.23
4961. 整数删除 - AcWing题库 题目描述 分析 注:如果要进行大量的删除操作可以使用链表 动态求最小值使用堆,每次从堆中取出最小值的下标然后在链表中删除 注意long long 代码解释: while(k --){auto t q.top();q.pop();res t.first;i…...

C语言数组和指针笔试题(三)(一定要看)
目录 字符数组四例题1例题2例题3例题4例题5例题6例题7 结果字符数组五例题1例题2例题3例题4例题5例题6例题7结果字符数组六例题1例题2例题3例题4例题5例题6例题7 结果 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 🐒🐒🐒个…...
leetcode11 盛水最多的容器
题目 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 示例 输入:[1,8,6,2,5,4,8,…...

进入数据结构的世界
数据结构和算法的概述 一、什么是数据结构二、什么是算法三、如何去学习数据结构和算法四、算法的时间复杂度和空间复杂度4.1 算法效率4.2 大O的渐进表示法4.3 时间复杂度4.4 空间复杂度4.5 常见复杂度对比 一、什么是数据结构 数据结构是计算机存储、组织数据的方式。&#x…...

stm32之看门狗
STM32 有两个看门狗,独立看门狗和窗口看门狗,独立看门狗又称宠物狗,窗 口看门狗又称警犬。可用来检测和解决由软件错误引起的故障。两个看门狗的原理都是当计数器达到给定的超时值时,产生系统复位,对于窗口型看门狗同…...

纤维蛋白单体(FM)介绍
纤维蛋白单体(FM)是血栓形成的早期产物,是纤维蛋白原(Fibrinogen,Fbg)在凝血酶(Thrombin)的作用下,脱掉肽A(Fibrinopeptide A,Fp A)与…...

知识图谱实战导论:从什么是KG到LLM与KG/DB的结合实战
前言 本文侧重讲解: 什么是知识图谱LLM与langchain/数据库/知识图谱的结合应用 比如,虽说基于知识图谱的问答 早在2019年之前就有很多研究了,但谁会想到今年KBQA因为LLM如此突飞猛进呢 第一部分 知识图谱入门导论 //待更.. 第二部分 LLM与…...
第5章 会话与会话技术
第5章 会话与会话技术 一. 单选题(共5题,50分)二. 判断题(共5题,50分) 一. 单选题(共5题,50分) (单选题) 阅读下面代码: Book book BookDB.getBook(id)…...

IDEA2023新UI回退老UI
idea2023年发布了新UI,如下所示 但是用起来真心不好用,各种位置也是错乱,用下面方法可以回退老UI...

ElasticSearch(三)
1.数据聚合 聚合(aggregations)可以让我们极其方便的实现对数据的统计、分析、运算。例如: 什么品牌的手机最受欢迎? 这些手机的平均价格、最高价格、最低价格? 这些手机每月的销售情况如何? 实现这些…...
【LinkedHashMap】146. LRU 缓存
146. LRU 缓存 解题思路 与普通的 HashMap 不同,LinkedHashMap 会保持元素的有序性。这可以在某些情况下提供更可预测的迭代顺序直接获取元素 因为使用到该元素 将该元素重新放入队尾 表示最近使用该元素写入元素,首先如果该元素原来存在 那么需要将ke…...

Opencv-python去图标与水印方案实践
RGB色彩模式是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色ÿ…...

自己写过比较蠢的代码:从失败中学习的经验
文章目录 引言1. 代码没有注释2. 长函数和复杂逻辑3. 不恰当的变量名4. 重复的代码5. 不适当的异常处理6. 硬编码的敏感信息7. 没有单元测试结论 🎉 自己写过比较蠢的代码:从失败中学习的经验 ☆* o(≧▽≦)o *☆嗨~我是IT陈寒🍹✨博客主页&a…...
C语言 cortex-A7核 点LED灯 (附 汇编实现、使用C语言 循环实现、使用C语言 封装函数实现【重要、常用】)
1 汇编实现 text global _start start: ************** LED1点灯 ---> PE10 **************/ ************** RCC章节初始化 **************/ CC_INIT:1.使能GPIOE组控制器,通过RCC_MP_AHB4ENSETR寄存器设置GPIOE组使能0x50000A28[4] 1ldr r0,0x50000A28 准…...

LABVIEW 实战案例1--温度报警系统
图1 温度报警系统前面板 图2 温度报警系统后面板...

网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...