当前位置: 首页 > 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 温度报警系统后面板...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)

题目 做法 启动靶机&#xff0c;点进去 点进去 查看URL&#xff0c;有 ?fileflag.php说明存在文件包含&#xff0c;原理是php://filter 协议 当它与包含函数结合时&#xff0c;php://filter流会被当作php文件执行。 用php://filter加编码&#xff0c;能让PHP把文件内容…...

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

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

适应性Java用于现代 API:REST、GraphQL 和事件驱动

在快速发展的软件开发领域&#xff0c;REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名&#xff0c;不断适应这些现代范式的需求。随着不断发展的生态系统&#xff0c;Java 在现代 API 方…...

uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)

UniApp 集成腾讯云 IM 富媒体消息全攻略&#xff08;地理位置/文件&#xff09; 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型&#xff0c;核心实现方式&#xff1a; 标准消息类型&#xff1a;直接使用 SDK 内置类型&#xff08;文件、图片等&#xff09;自…...