当前位置: 首页 > news >正文

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文件进行加密处理,一切处理方法都可以实现,如果对您有帮助不妨点个赞

相关文章:

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

文章目录 背景加密流程音视频解密音视频播放结语 背景 当涉及App内部视频的时候&#xff0c;我们不希望被别人以抓包的形式来爬取我们的视频大视频文件以文件方式整个加密的话需要完全下载后才能进行解密当前m3u8格式虽然支持加密&#xff0c;但是ts格式的小视频可以独立播放的…...

常见的文件格式

一、C:\fakepath\新建文本文档.txt [object String] 实现方式&#xff1a; <input onchange"test(this.value)" type"file"></input><script>function test(e){console.log(e,Object.prototype.toString.call(e))}</script> 二、…...

浏览器输入url后回车展开过程

当你在浏览器中输入一个URL并敲下回车后&#xff0c;浏览器会执行一系列步骤来访问并展示网页。下面是浏览器访问网页的一般流程&#xff1a; DNS解析&#xff1a;浏览器首先会提取URL中的主机名&#xff0c;然后向DNS服务器发送请求&#xff0c;将主机名解析为对应的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 对偶问题&#xff08;dual problem&#xff09;6.2.1 什么是对偶问题6.2.2 如何求解支持向量机的对偶问题 6.3 核函数&#xff08;kernel function&#xff09…...

蓝桥杯每日一题2023.9.23

4961. 整数删除 - AcWing题库 题目描述 分析 注&#xff1a;如果要进行大量的删除操作可以使用链表 动态求最小值使用堆&#xff0c;每次从堆中取出最小值的下标然后在链表中删除 注意long long 代码解释&#xff1a; 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 结果 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 &#x1f412;&#x1f412;&#x1f412;个…...

leetcode11 盛水最多的容器

题目 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 示例 输入&#xff1a;[1,8,6,2,5,4,8,…...

进入数据结构的世界

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

stm32之看门狗

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

纤维蛋白单体(FM)介绍

纤维蛋白单体&#xff08;FM&#xff09;是血栓形成的早期产物&#xff0c;是纤维蛋白原&#xff08;Fibrinogen&#xff0c;Fbg&#xff09;在凝血酶&#xff08;Thrombin&#xff09;的作用下&#xff0c;脱掉肽A&#xff08;Fibrinopeptide A&#xff0c;Fp A&#xff09;与…...

知识图谱实战导论:从什么是KG到LLM与KG/DB的结合实战

前言 本文侧重讲解&#xff1a; 什么是知识图谱LLM与langchain/数据库/知识图谱的结合应用 比如&#xff0c;虽说基于知识图谱的问答 早在2019年之前就有很多研究了&#xff0c;但谁会想到今年KBQA因为LLM如此突飞猛进呢 第一部分 知识图谱入门导论 //待更.. 第二部分 LLM与…...

第5章 会话与会话技术

第5章 会话与会话技术 一. 单选题&#xff08;共5题&#xff0c;50分&#xff09;二. 判断题&#xff08;共5题&#xff0c;50分&#xff09; 一. 单选题&#xff08;共5题&#xff0c;50分&#xff09; (单选题) 阅读下面代码&#xff1a; Book book BookDB.getBook(id)…...

IDEA2023新UI回退老UI

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

ElasticSearch(三)

1.数据聚合 聚合&#xff08;aggregations&#xff09;可以让我们极其方便的实现对数据的统计、分析、运算。例如&#xff1a; 什么品牌的手机最受欢迎&#xff1f; 这些手机的平均价格、最高价格、最低价格&#xff1f; 这些手机每月的销售情况如何&#xff1f; 实现这些…...

【LinkedHashMap】146. LRU 缓存

146. LRU 缓存 解题思路 与普通的 HashMap 不同&#xff0c;LinkedHashMap 会保持元素的有序性。这可以在某些情况下提供更可预测的迭代顺序直接获取元素 因为使用到该元素 将该元素重新放入队尾 表示最近使用该元素写入元素&#xff0c;首先如果该元素原来存在 那么需要将ke…...

Opencv-python去图标与水印方案实践

RGB色彩模式是工业界的一种颜色标准&#xff0c;是通过对红&#xff08;R&#xff09;、绿&#xff08;G&#xff09;、蓝&#xff08;B&#xff09;三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的&#xff0c;RGB即是代表红、绿、蓝三个通道的颜色&#xff…...

自己写过比较蠢的代码:从失败中学习的经验

文章目录 引言1. 代码没有注释2. 长函数和复杂逻辑3. 不恰当的变量名4. 重复的代码5. 不适当的异常处理6. 硬编码的敏感信息7. 没有单元测试结论 &#x1f389; 自己写过比较蠢的代码&#xff1a;从失败中学习的经验 ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨博客主页&a…...

C语言 cortex-A7核 点LED灯 (附 汇编实现、使用C语言 循环实现、使用C语言 封装函数实现【重要、常用】)

1 汇编实现 text global _start start: ************** LED1点灯 ---> PE10 **************/ ************** RCC章节初始化 **************/ CC_INIT:1.使能GPIOE组控制器&#xff0c;通过RCC_MP_AHB4ENSETR寄存器设置GPIOE组使能0x50000A28[4] 1ldr r0,0x50000A28 准…...

LABVIEW 实战案例1--温度报警系统

图1 温度报警系统前面板 图2 温度报警系统后面板...

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

腾讯云V3签名

想要接入腾讯云的Api&#xff0c;必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口&#xff0c;但总是卡在签名这一步&#xff0c;最后放弃选择SDK&#xff0c;这次终于自己代码实现。 可能腾讯云翻新了接口文档&#xff0c;现在阅读起来&#xff0c;清晰了很多&…...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...

【C++进阶篇】智能指针

C内存管理终极指南&#xff1a;智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...

解读《网络安全法》最新修订,把握网络安全新趋势

《网络安全法》自2017年施行以来&#xff0c;在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂&#xff0c;网络攻击、数据泄露等事件频发&#xff0c;现行法律已难以完全适应新的风险挑战。 2025年3月28日&#xff0c;国家网信办会同相关部门起草了《网络安全…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践

作者&#xff1a;吴岐诗&#xff0c;杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言&#xff1a;融合数据湖与数仓的创新之路 在数字金融时代&#xff0c;数据已成为金融机构的核心竞争力。杭银消费金…...

一些实用的chrome扩展0x01

简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序&#xff0c;无论是测试应用程序、搜寻漏洞还是收集情报&#xff0c;它们都能提升工作流程。 FoxyProxy 代理管理工具&#xff0c;此扩展简化了使用代理&#xff08;如 Burp…...

echarts使用graphic强行给图增加一个边框(边框根据自己的图形大小设置)- 适用于无法使用dom的样式

pdf-lib https://blog.csdn.net/Shi_haoliu/article/details/148157624?spm1001.2014.3001.5501 为了完成在pdf中导出echarts图&#xff0c;如果边框加在dom上面&#xff0c;pdf-lib导出svg的时候并不会导出边框&#xff0c;所以只能在echarts图上面加边框 grid的边框是在图里…...