第五阶段:网络传输协议
本文摘要: 文章深入解析视频传输协议的核心技术,重点对比HTTP、RTMP、HLS和WebRTC在短视频、直播等场景的应用差异。HTTP协议通过Range请求和流式传输实现视频秒开播放;RTMP专为低延迟直播推流设计;HLS支持大规模直播分发;WebRTC则适用于超低延迟视频通话。文章通过技术对比表格和Kotlin代码示例,详细说明如何基于HTTP协议实现智能分片下载、断点续传等视频流播放功能,帮
文章目录
理解视频在网络中的传输方式 📡
掌握直播和点播的核心传输技术

🤔 为什么要学习网络传输协议?
🎯 核心价值:网络传输协议是短视频和直播应用的生命线!
📊 不同协议解决不同问题
| 应用场景 | 核心需求 | 最佳协议 | 为什么选择它? |
|---|---|---|---|
| 📱 短视频播放 | 快速加载、流畅播放 | HTTP + 缓存 | 简单可靠,支持CDN加速 |
| 📺 直播推流 | 实时上传、稳定传输 | RTMP | 专为流媒体设计,延迟低 |
| 👥 直播观看 | 大规模分发、兼容性 | HLS | 支持自适应码率,兼容性强 |
| 💬 视频通话 | 超低延迟、双向通信 | WebRTC | 点对点直连,延迟最低 |
🚀 学会这些协议你能做什么?
📱 短视频应用:
- ✅ 实现秒开播放(HTTP优化)
- ✅ 支持离线缓存(断点续传)
- ✅ 自适应码率播放(根据网络调整清晰度)
📺 直播应用:
- ✅ 手机推流直播(RTMP推流)
- ✅ 万人在线观看(HLS分发)
- ✅ 实时互动连麦(WebRTC通信)
💡 实际价值:
- 🎯 技术选型:知道什么场景用什么协议
- 🛠️ 性能优化:理解协议特点,优化用户体验
- 🔧 问题排查:遇到网络问题能快速定位原因
- 📈 架构设计:设计高并发、低延迟的视频系统
🎯 本阶段学习目标
学完这个阶段,你将能够:
- 🌐 理解HTTP协议:掌握基础网络传输
- 📡 掌握RTMP推流:实现直播推流功能
- 📺 理解HLS协议:掌握分片播放机制
- ⚡ 了解WebRTC:实现超低延迟通信
📖 第一部分:HTTP协议基础
🌐 什么是HTTP协议?
生活比喻:HTTP就像是智能快递系统
🚚 快递车 = HTTP请求(可以按需运输)
📦 包裹 = 视频数据(可以分批次运送)
🏠 收货人 = 客户端(你的应用)
🏢 发货仓库 = 服务器(视频服务器)
💡 关键特点:
• 📋 可以指定要哪部分包裹(Range请求)
• 📦 不需要等所有包裹到齐就能开始使用(流式播放)
• 📍 可以随时要求从特定位置开始发货(跳转播放)
• 🔄 支持分批次持续配送(边下载边播放)
🎯 为什么HTTP适合视频流播放?
与传统的"一次性下载整个文件"不同,HTTP协议支持:
-
📋 Range请求:可以请求文件的特定部分
GET /video.mp4 HTTP/1.1 Range: bytes=0-1048576 // 只要前1MB数据 -
🌊 流式传输:边下载边播放,无需等待完整文件
下载进度: [████████░░] 80% 播放进度: [██████░░░░] 60% ← 可以同时进行! -
🎯 随机访问:可以跳转到视频任意位置
Range: bytes=5242880- // 从第5MB开始播放(跳转功能) -
🔄 断点续传:网络中断后可以从断点继续
Range: bytes=2097152- // 从已下载的2MB位置继续
💡 这就是为什么HTTP更像"智能快递"而不是"传统邮政":
- 传统邮政:必须等所有信件都到齐才能阅读 ❌
- 智能快递:可以分批配送,先到的先使用 ✅
📡 HTTP视频播放器实现
📋 HTTP流式播放核心逻辑说明:
HTTP视频播放器的核心思想是边下载边播放,不需要等待完整文件下载完成。实现流程如下:
-
🔍 服务器能力检测
- 发送HEAD请求检查服务器是否支持Range请求
- 获取视频文件总大小,用于进度计算和分片下载
-
📦 智能分片策略
- 优先下载视频头部(前1MB),包含元数据信息
- 让播放器快速获得视频格式、分辨率等关键信息
- 后台继续下载剩余内容,实现无缝播放
-
🎯 播放器协调
- MediaPlayer异步准备,不阻塞UI线程
- 建立播放进度监控,实时更新UI
- 支持用户交互(暂停、跳转、拖拽进度条)
-
🛡️ 错误处理与恢复
- 网络异常时自动重试机制
- 资源清理,避免内存泄漏
- 缓存文件管理,优化存储空间
💡 关键技术点:
- Range请求:
Range: bytes=0-1048576只请求需要的数据片段 - 异步处理:下载和播放并行进行,提升用户体验
- 缓存策略:临时文件缓存,支持快进和重播
- 状态管理:实时监控播放状态,提供准确的进度信息
/**
* 📡 HTTP视频流播放器
*
* 专门用于HTTP协议流式播放视频的播放器
* 支持边下载边播放、Range请求、拖拽快进等功能
* 适用于短视频播放、点播视频、在线视频等场景
*/
class HttpVideoStreamPlayer(private val context: Context) {
// 🌐 HTTP客户端配置,针对视频流播放进行优化
private val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS) // 连接超时:10秒(流播放需要快速响应)
.readTimeout(30, TimeUnit.SECONDS) // 读取超时:30秒(允许网络波动,视频数据较大)
.retryOnConnectionFailure(true) // 连接失败时自动重试(提高稳定性)
.build()
// 📱 播放器核心组件
private var mediaPlayer: MediaPlayer? = null // Android原生媒体播放器
private var isPlaying = false // 当前播放状态标志
private var videoUrl: String? = null // 当前播放的视频URL
private var contentLength = 0L // 视频文件总大小(字节)
private var supportRange = false // 服务器是否支持Range请求
/**
* 🎬 开始流式播放视频
*
* 不需要等待完整文件下载,边下载边播放
* 支持快速启播和流畅观看体验
*
* @param url 视频文件的网络地址
* @param surfaceView 视频显示的Surface视图
* @param progressCallback 播放进度回调函数
* @return Boolean 播放启动是否成功
*/
suspend fun startStreaming(
url: String,
surfaceView: SurfaceView,
progressCallback: (progress: Int, duration: Int) -> Unit
): Boolean = withContext(Dispatchers.IO) {
try {
this@HttpVideoStreamPlayer.videoUrl = url
// 第一步:检查服务器Range支持
supportRange = checkRangeSupport(url)
contentLength = getContentLength(url)
println("📊 视频信息: ${contentLength / 1024 / 1024}MB, Range支持: $supportRange")
// 第二步:初始化MediaPlayer
mediaPlayer = MediaPlayer().apply {
setOnPreparedListener { player ->
println("✅ 播放器准备完成,开始播放")
player.start()
isPlaying = true
// 开始进度监控
startProgressMonitoring(progressCallback)
}
setOnErrorListener { _, what, extra ->
println("❌ 播放错误: what=$what, extra=$extra")
false
}
setOnCompletionListener {
println("✅ 播放完成")
isPlaying = false
}
// 设置显示Surface
setDisplay(surfaceView.holder)
}
// 第三步:开始流式播放
if (supportRange) {
// 支持Range请求,可以实现更好的播放体验
startRangeStreaming(url)
} else {
// 不支持Range,使用渐进式下载播放
startProgressiveStreaming(url)
}
true
} catch (e: Exception) {
println("❌ 播放启动失败: ${e.message}")
cleanup()
false
}
}
/**
* 🎯 Range请求流式播放
*
* 利用HTTP Range请求,按需下载视频片段
* 支持快进、拖拽等高级功能
*/
private suspend fun startRangeStreaming(url: String) = withContext(Dispatchers.IO) {
// 创建临时文件用于缓存
val cacheFile = File(context.cacheDir, "streaming_${System.currentTimeMillis()}.mp4")
try {
// 先下载视频头部信息(前1MB),让播放器能够快速开始播放
val headerSize = minOf(1024 * 1024, contentLength) // 1MB或文件大小
downloadRange(url, 0, headerSize - 1, cacheFile, false)
// 设置数据源并准备播放
mediaPlayer?.apply {
setDataSource(cacheFile.absolutePath)
prepareAsync() // 异步准备,不阻塞UI线程
}
// 在后台继续下载剩余内容
if (contentLength > headerSize) {
downloadRange(url, headerSize, contentLength - 1, cacheFile, true)
}
} catch (e: Exception) {
println("❌ Range流播放失败: ${e.message}")
cacheFile.delete()
throw e
}
}
/**
* 🌊 渐进式流播放
*
* 传统的边下载边播放方式
* 虽然不支持Range,但仍能实现流式播放
*/
private suspend fun startProgressiveStreaming(url: String) = withContext(Dispatchers.IO) {
// 直接设置网络URL为数据源
// MediaPlayer会自动处理网络流
mediaPlayer?.apply {
setDataSource(url)
prepareAsync() // 异步准备,MediaPlayer会边下载边准备
}
}
/**
* 📊 检查服务器Range支持
*/
private suspend fun checkRangeSupport(url: String): Boolean = withContext(Dispatchers.IO) {
try {
val request = Request.Builder()
.url(url)
.head() // 发送HEAD请求,只获取响应头
.build()
val response = client.newCall(request).execute()
val acceptRanges = response.header("Accept-Ranges")
response.close()
acceptRanges == "bytes"
} catch (e: Exception) {
println("❌ 检查Range支持失败: ${e.message}")
false
}
}
/**
* 📏 获取内容长度
*/
private suspend fun getContentLength(url: String): Long = withContext(Dispatchers.IO) {
try {
val request = Request.Builder()
.url(url)
.head()
.build()
val response = client.newCall(request).execute()
val length = response.header("Content-Length")?.toLongOrNull() ?: 0L
response.close()
length
} catch (e: Exception) {
println("❌ 获取内容长度失败: ${e.message}")
0L
}
}
/**
* 📥 下载指定范围的数据
*/
private suspend fun downloadRange(
url: String,
start: Long,
end: Long,
outputFile: File,
append: Boolean
) = withContext(Dispatchers.IO) {
val request = Request.Builder()
.url(url)
.addHeader("Range", "bytes=$start-$end")
.build()
val response = client.newCall(request).execute()
response.body?.byteStream()?.use { input ->
FileOutputStream(outputFile, append).use { output ->
val buffer = ByteArray(8192)
var bytesRead: Int
while (input.read(buffer).also { bytesRead = it } != -1) {
output.write(buffer, 0, bytesRead)
}
output.flush()
}
}
response.close()
println("📥 下载范围完成: $start-$end bytes")
}
/**
* 📊 开始进度监控
*/
private fun startProgressMonitoring(callback: (progress: Int, duration: Int) -> Unit) {
Thread {
while (isPlaying) {
try {
mediaPlayer?.let { player ->
val currentPosition = player.currentPosition
val duration = player.duration
if (duration > 0) {
val progress = (currentPosition * 100 / duration)
callback(progress, duration)
}
}
Thread.sleep(1000) // 每秒更新一次进度
} catch (e: Exception) {
println("❌ 进度监控异常: ${e.message}")
break
}
}
}.start()
}
/**
* ⏸️ 暂停播放
*/
fun pause() {
mediaPlayer?.pause()
isPlaying = false
println("⏸️ 播放已暂停")
}
/**
* ▶️ 恢复播放
*/
fun resume() {
mediaPlayer?.start()
isPlaying = true
println("▶️ 播放已恢复")
}
/**
* ⏹️ 停止播放
*/
fun stop() {
mediaPlayer?.stop()
isPlaying = false
println("⏹️ 播放已停止")
}
/**
* 🎯 跳转到指定位置(毫秒)
*/
fun seekTo(position: Int) {
mediaPlayer?.seekTo(position)
println("🎯 跳转到: ${position / 1000}秒")
}
/**
* 🧹 清理资源
*/
private fun cleanup() {
try {
mediaPlayer?.release()
mediaPlayer = null
isPlaying = false
// 清理缓存文件
context.cacheDir.listFiles()?.forEach { file ->
if (file.name.startsWith("streaming_")) {
file.delete()
}
}
println("🧹 播放器资源清理完成")
} catch (e: Exception) {
println("❌ 资源清理失败: ${e.message}")
}
}
/**
* 📊 获取播放状态
*/
fun getPlaybackStatus(): PlaybackStatus {
return PlaybackStatus(
isPlaying = isPlaying,
currentPosition = mediaPlayer?.currentPosition ?: 0,
duration = mediaPlayer?.duration ?: 0,
url = videoUrl
)
}
data class PlaybackStatus(
val isPlaying: Boolean,
val currentPosition: Int,
val duration: Int,
val url: String?
)
}
🎯 HTTP流播放器使用示例
📱 完整的短视频播放Activity:
/**
* 📱 短视频播放Activity
*
* 展示如何使用HTTP协议进行视频流播放
* 实现边下载边播放,快速启播体验
*/
class VideoPlayerActivity : AppCompatActivity() {
private lateinit var httpPlayer: HttpVideoStreamPlayer
private lateinit var surfaceView: SurfaceView
private lateinit var btnPlay: Button
private lateinit var btnPause: Button
private lateinit var seekBar: SeekBar
private lateinit var tvProgress: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_video_player)
initViews()
initPlayer()
setupClickListeners()
}
/**
* 🎬 初始化UI组件
*/
private fun initViews() {
surfaceView = findViewById(R.id.surface_view)
btnPlay = findViewById(R.id.btn_play)
btnPause = findViewById(R.id.btn_pause)
seekBar = findViewById(R.id.seek_bar)
tvProgress = findViewById(R.id.tv_progress)
}
/**
* 🔧 初始化播放器
*/
private fun initPlayer() {
httpPlayer = HttpVideoStreamPlayer(this)
}
/**
* 🎯 设置点击事件
*/
private fun setupClickListeners() {
btnPlay.setOnClickListener {
startVideoPlayback()
}
btnPause.setOnClickListener {
httpPlayer.pause()
}
// 拖拽进度条
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser) {
val status = httpPlayer.getPlaybackStatus()
val seekPosition = (progress * status.duration / 100)
httpPlayer.seekTo(seekPosition)
}
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
}
/**
* 🚀 开始视频播放
*/
private fun startVideoPlayback() {
lifecycleScope.launch {
try {
// 视频URL - 可以是任何HTTP视频链接
val videoUrl = "https://sample-videos.com/zip/10/mp4/SampleVideo_1280x720_1mb.mp4"
// 开始流式播放
val success = httpPlayer.startStreaming(
url = videoUrl,
surfaceView = surfaceView
) { progress, duration ->
// 更新播放进度
runOnUiThread {
seekBar.progress = progress
tvProgress.text = "${formatTime(progress * duration / 100)} / ${formatTime(duration)}"
}
}
if (success) {
println("✅ 视频播放启动成功")
btnPlay.isEnabled = false
btnPause.isEnabled = true
} else {
println("❌ 视频播放启动失败")
}
} catch (e: Exception) {
println("❌ 播放异常: ${e.message}")
}
}
}
/**
* 🕒 格式化时间显示
*/
private fun formatTime(milliseconds: Int): String {
val seconds = milliseconds / 1000
val minutes = seconds / 60
val remainingSeconds = seconds % 60
return String.format("%02d:%02d", minutes, remainingSeconds)
}
override fun onDestroy() {
super.onDestroy()
httpPlayer.stop()
}
}
💡 HTTP协议播放的核心优势
✅ 为什么HTTP适合视频播放?
-
🚀 快速启播:
- 不需要等待完整文件下载
- 下载几秒钟内容就能开始播放
- 用户体验接近实时播放
-
🎯 灵活控制:
- 支持Range请求,可以跳转到任意位置
- 支持暂停、快进、倒退等操作
- 可以实现拖拽进度条功能
-
🌐 广泛支持:
- 所有服务器都支持HTTP协议
- 可以利用CDN进行全球加速
- 兼容性最好,无需特殊配置
-
📊 智能缓存:
- 可以预加载下一段内容
- 支持本地缓存,减少重复下载
- 网络中断后可以断点续传
📖 第二部分:流媒体协议概述
📺 什么是流媒体?
生活比喻:流媒体就像是自来水
🚰 自来水 = 打开水龙头就有水流出(不需要等水桶装满)
📺 流媒体 = 打开播放器就能看视频(不需要等文件下载完)
📊 各协议播放特点对比
🎯 不同协议的播放方式和特点:
| 协议类型 | 播放方式 | 缓冲策略 | 启播延迟 | 直播延迟 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|---|---|---|
| HTTP | 边下载边播放 | 预缓冲几秒 | 2-5秒 | ❌ 不适用 | ✅ 简单可靠 ✅ 支持CDN加速 ✅ 支持断点续传 |
❌ 仅适合点播 ❌ 无法实时直播 |
📱 短视频 🎬 点播视频 |
| RTMP | 实时流播放 | 极小缓冲 | 3-5秒 | 1-3秒 | ✅ 低延迟 ✅ 稳定推流 ✅ 实时性好 |
❌ 复杂度高 ❌ 需要Flash支持 |
📺 直播推流 🎮 游戏直播 |
| HLS | 分片流播放 | 分片缓冲 | 5-10秒 | 10-30秒 | ✅ 自适应码率 ✅ 兼容性强 ✅ 大规模分发 |
❌ 直播延迟高 ❌ 分片开销 |
👥 直播观看 📺 大规模直播 |
| WebRTC | 实时P2P播放 | 无缓冲 | <1秒 | <1秒 | ✅ 超低延迟 ✅ 点对点直连 ✅ 双向通信 |
❌ 网络要求高 ❌ 不适合大规模 |
💬 视频通话 🎮 实时互动 |
🤔 延迟概念澄清
重要区别:我们需要区分两种不同的"延迟"概念!
📊 启播延迟 vs 直播延迟
| 延迟类型 | 定义 | HTTP | HLS | 影响因素 |
|---|---|---|---|---|
| 🚀 启播延迟 | 点击播放到看到画面的时间 | 2-5秒 | 5-10秒 | 缓冲策略、网络速度 |
| 📺 直播延迟 | 实际事件到观众看到的时间 | 不适用 | 10-30秒 | 编码、传输、分片处理 |
💡 为什么HTTP启播快,但不适合直播?
🎯 HTTP协议的特点:
- ✅ 点播优势:文件已经存在服务器上,可以立即开始传输
- ✅ Range请求:可以从任意位置开始下载,启播快
- ❌ 直播劣势:无法处理实时生成的内容
📺 HLS协议的特点:
- ❌ 启播较慢:需要等待分片生成和下载
- ✅ 直播支持:专门为流媒体设计,支持实时内容
- ⚡ 延迟来源:分片生成(3-6秒) + 传输(2-5秒) + 缓冲(5-20秒)
🔍 具体场景分析
📱 短视频播放(HTTP):
用户点击 → 立即请求已存在的文件 → 2-3秒开始播放
时间轴: [点击] --2秒--> [开始播放]
📺 直播观看(HLS):
主播说话 → 编码 → 分片 → 传输 → 观众播放器 → 观众听到
时间轴: [主播] --3秒--> [编码] --2秒--> [分片] --5秒--> [传输] --10秒--> [观众]
总延迟: 20秒
🎮 游戏直播(RTMP拉流):
主播操作 → RTMP推流 → 服务器转发 → 观众播放器 → 观众看到
时间轴: [主播] --0.5秒--> [推流] --0.5秒--> [转发] --1秒--> [观众]
总延迟: 2秒
🎮 实际应用场景对比
📱 短视频应用(抖音、快手):
- 协议选择:HTTP + CDN
- 播放特点:下载2-3秒后立即播放,边下载边播放
- 用户体验:快速启播,支持拖拽和缓存
📺 直播观看(斗鱼、B站直播):
- 协议选择:HLS(网页端)+ RTMP(移动端)
- 播放特点:实时流播放,自适应码率
- 用户体验:延迟10-30秒,画质自动调整
🎮 游戏直播推流(OBS):
- 协议选择:RTMP推流
- 播放特点:实时上传,低延迟传输
- 用户体验:延迟1-3秒,稳定可靠
💬 视频通话(微信、钉钉):
- 协议选择:WebRTC
- 播放特点:实时双向通信,无缓冲
- 用户体验:延迟<1秒,实时交互
🔄 推流 vs 拉流
🎯 核心概念对比:推流和拉流是直播系统的两个关键环节!
📊 推流 VS 拉流 对比表
| 对比维度 | 📤推流 (Push) | 📥拉流 (Pull) |
|---|---|---|
| 🎯 定义 | 主播向服务器推送视频流 | 观众从服务器拉取视频流 |
| 🚀 方向 | 主播设备 → 流媒体服务器 |
流媒体服务器 → 观众设备 |
| 👥 角色 | 内容生产者(主播) | 内容消费者(观众) |
| 🔧 主要协议 | RTMP、SRT、WebRTC | HLS、DASH、FLV、RTMP |
| ⚡ 延迟要求 | 低延迟(1-3秒) | 可接受延迟(3-30秒) |
| 📊 并发量 | 1个推流(主播) | N个拉流(观众) |
| 🎮 典型场景 | 手机直播、游戏推流 | 观看直播、点播视频 |
🏭 直播系统架构图解
📱 推流端 (主播) 🌐 流媒体服务器 📺 拉流端 (观众)
| | |
| 📤 推流 (RTMP) | | 📥 拉流 (HLS)
| 上传音视频数据 | | 下载音视频数据
| | |
v v v
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 📹 相机采集 │ ──推流协议──→ │ 🔄 转码分发 │ ──拉流协议──→ │ 📺 播放器 │
│ 🎤 音频采集 │ (RTMP) │ 📡 CDN加速 │ (HLS) │ 🔊 音频输出 │
│ 🎬 编码压缩 │ │ 🗄️ 存储录制 │ │ 🖥️ 视频显示 │
└─────────────┘ └─────────────┘ └─────────────┘
📤 推流特点: 🌐 服务器处理: 📥 拉流特点:
• 实时上传 • 接收推流 • 实时下载
• 编码压缩 • 转码适配 • 解码播放
• 网络适应 • 多路分发 • 缓冲优化
• 断线重连 • CDN分布 • 清晰度切换
🎮 实际应用场景对比
📤 推流场景:
// 📱 手机直播推流
class LiveStreamingActivity {
fun startLiveStreaming() {
// 1. 📹 启动相机采集
cameraManager.startCapture()
// 2. 🎤 启动音频采集
audioManager.startRecord()
// 3. 📤 开始RTMP推流
rtmpPusher.startPush("rtmp://live.server.com/live/room123")
// 主播在这里产生内容,推送给服务器
}
}
📥 拉流场景:
// 📺 观众观看直播
class LiveWatchActivity {
fun startWatching() {
// 1. 📥 从服务器拉取HLS流
val hlsUrl = "https://live.server.com/live/room123.m3u8"
// 2. 📺 播放器开始播放
exoPlayer.setMediaItem(MediaItem.fromUri(hlsUrl))
exoPlayer.prepare()
exoPlayer.play()
// 观众在这里消费内容,从服务器拉取数据
}
}
💡 协议选择策略
📊 协议选择对比表
| 使用场景 | 推荐协议 | 延迟 | 兼容性 | 复杂度 | 适用人群 | 典型应用 |
|---|---|---|---|---|---|---|
| 📱 手机直播 | RTMP | 1-3秒 | ⭐⭐⭐⭐ | ⭐⭐⭐ | 个人主播 | 抖音、快手直播 |
| 🎮 游戏直播 | RTMP/SRT | 1-2秒 | ⭐⭐⭐ | ⭐⭐⭐⭐ | 游戏玩家 | 斗鱼、虎牙 |
| 🏢 专业直播 | SRT | 2-5秒 | ⭐⭐ | ⭐⭐⭐⭐⭐ | 专业团队 | 电视台、大型活动 |
| 💬 视频通话 | WebRTC | <1秒 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 所有用户 | 微信、钉钉视频 |
| 🌐 网页观看 | HLS | 10-30秒 | ⭐⭐⭐⭐⭐ | ⭐⭐ | 所有用户 | B站、爱奇艺 |
| 📱 移动观看 | HLS/DASH | 5-15秒 | ⭐⭐⭐⭐ | ⭐⭐⭐ | 移动用户 | 各大视频APP |
| 🎬 点播视频 | HTTP | 2-5秒 | ⭐⭐⭐⭐⭐ | ⭐ | 所有用户 | YouTube、优酷 |
🔄 完整直播链路架构图
📱 主播端 🌐 服务器端 📺 观众端
┌─────────────┐ ┌─────────────────┐ ┌─────────────┐
│ 📹 相机采集 │ │ │ │ │
│ 🎤 音频采集 │ RTMP │ 🔄 转码服务器 │ HLS │ 📺 播放器 │
│ 🔧 编码压缩 │ ──────→ │ 📊 CDN分发 │ ──────→ │ 🎵 解码播放 │
│ 📤 RTMP推流 │ │ 📈 负载均衡 │ │ 🖥️ 渲染显示 │
└─────────────┘ └─────────────────┘ └─────────────┘
⬇️ ⬇️ ⬇️
稳定上传 智能分发 流畅观看
低延迟 高并发 自适应
🎯 快速选择指南
📤 我要推流,选什么协议?
🤔 问自己几个问题:
1️⃣ 我用什么设备?
📱 手机 → RTMP(最简单)
💻 电脑 → RTMP/SRT(更稳定)
🎥 专业设备 → SRT(最可靠)
2️⃣ 我对延迟要求高吗?
⚡ 超低延迟(<1秒)→ WebRTC
🚀 低延迟(1-3秒)→ RTMP/SRT
😊 可接受延迟 → RTMP
3️⃣ 我的网络稳定吗?
📶 稳定网络 → RTMP
📡 不稳定网络 → SRT(抗丢包)
🌐 移动网络 → RTMP(兼容性好)
📥 我要观看,选什么协议?
🤔 问自己几个问题:
1️⃣ 我用什么平台?
🌐 网页浏览器 → HLS(兼容性最好)
📱 手机APP → HLS/DASH(自适应)
💻 桌面软件 → 任意协议
2️⃣ 我对延迟要求高吗?
⚡ 实时互动 → WebRTC
🚀 低延迟观看 → FLV/WebRTC
😊 正常观看 → HLS
3️⃣ 我的网络带宽如何?
📶 高带宽 → 任意协议
📡 低带宽 → HLS(自适应码率)
🌐 移动流量 → HLS(省流量)
🏆 最佳实践组合
| 应用类型 | 推流协议 | 拉流协议 | 延迟 | 优势 | 典型案例 |
|---|---|---|---|---|---|
| 📱 娱乐直播 | RTMP | HLS | 10-30秒 | 稳定、大规模 | 抖音、快手 |
| 🎮 游戏直播 | RTMP | HLS+FLV | 3-10秒 | 画质好、延迟可控 | 斗鱼、虎牙 |
| 💼 企业直播 | SRT | HLS | 5-15秒 | 可靠、专业 | 腾讯会议直播 |
| 💬 视频会议 | WebRTC | WebRTC | <1秒 | 实时互动 | 钉钉、腾讯会议 |
| 📺 电视直播 | SRT | HLS | 10-20秒 | 高质量、稳定 | IPTV、OTT |
📖 第三部分:RTMP推流原理
📡 什么是RTMP?
生活比喻:RTMP就像是专门的快递公司
📦 视频数据 = 包裹
🚚 RTMP = 专业快递公司(专门传输音视频)
🏢 流媒体服务器 = 收货仓库
📡 RTMP推流实现
📋 RTMP推流核心逻辑说明:
RTMP推流器的核心任务是将实时采集的音视频数据编码压缩后,通过网络实时传输到流媒体服务器。整个过程需要保证低延迟和高稳定性。
🔄 完整推流链路:
📹 相机采集 → 🎬 H.264编码 → 📦 RTMP封装 → 🌐 网络传输 → 🏢 流媒体服务器
🎤 音频采集 → 🎵 AAC编码 → 📦 RTMP封装 → 🌐 网络传输 → 🏢 流媒体服务器
⚡ 关键技术挑战:
- 实时性要求:音视频数据必须在采集后立即编码和传输
- 网络适应:根据网络状况动态调整码率和质量
- 同步问题:确保音视频时间戳同步,避免音画不同步
- 错误恢复:网络中断时快速重连,丢帧时智能补偿
🎯 实现流程详解:
-
🔧 编码器准备阶段
- 配置H.264视频编码器:分辨率、码率、帧率、关键帧间隔
- 配置AAC音频编码器:采样率、码率、声道数、编码格式
- 优化编码参数:平衡画质、文件大小和编码速度
-
🔗 RTMP连接建立
- TCP连接:建立可靠的网络传输通道
- RTMP握手:验证协议版本,协商传输参数
- 应用连接:连接到指定的直播应用(如"live")
- 流发布:开始发布指定名称的直播流
-
📡 实时数据传输
- 音视频数据采集:从相机和麦克风获取原始数据
- 编码处理:将原始数据压缩为H.264和AAC格式
- RTMP封装:将编码数据打包为RTMP协议格式
- 网络发送:通过TCP连接发送到流媒体服务器
-
📊 质量监控与优化
- 实时码率监控:确保传输速度符合预期
- 丢帧检测:监控编码器和网络丢帧情况
- 网络状态评估:根据延迟和丢包率调整策略
- 自适应调整:动态调整编码参数适应网络变化
/**
* 📡 RTMP推流管理器
*
* 专业的RTMP推流实现,支持直播推流的完整功能
* 包括视频编码、音频编码、网络传输、状态监控等
* 适用于手机直播、游戏推流、专业直播等场景
*/
class RTMPStreamer {
private var isStreaming = false // 推流状态标志
private var rtmpUrl: String? = null // RTMP推流地址
private var videoEncoder: MediaCodec? = null // H.264视频编码器
private var audioEncoder: MediaCodec? = null // AAC音频编码器
private var rtmpConnection: RTMPConnection? = null // RTMP连接对象
// 推流统计信息
private var framesSent = 0L // 已发送帧数
private var bytesSent = 0L // 已发送字节数
private var droppedFrames = 0L // 丢弃帧数
/**
* 🚀 开始RTMP推流
*
* 这是推流的主入口方法,完成所有初始化工作
*
* @param rtmpUrl RTMP推流地址,格式:rtmp://server/app/streamkey
* @param videoWidth 视频宽度(像素)
* @param videoHeight 视频高度(像素)
* @param videoBitrate 视频码率(bps),影响视频质量和文件大小
* @param audioSampleRate 音频采样率(Hz),常用44100或48000
* @param audioBitrate 音频码率(bps),影响音频质量
* @return Boolean 推流启动是否成功
*/
fun startStreaming(
rtmpUrl: String,
videoWidth: Int = 1280,
videoHeight: Int = 720,
videoBitrate: Int = 2000000, // 2Mbps,适合720p直播
audioSampleRate: Int = 44100,
audioBitrate: Int = 128000 // 128kbps,CD音质
): Boolean {
return try {
this.rtmpUrl = rtmpUrl
println("🎯 开始初始化RTMP推流器...")
// 第一步:配置视频编码参数
val videoConfig = VideoConfig(
width = videoWidth,
height = videoHeight,
bitrate = videoBitrate,
fps = 30, // 30帧每秒,流畅度和带宽的平衡
keyFrameInterval = 2, // 2秒一个关键帧,利于网络传输
profile = "baseline", // H.264 Baseline Profile,兼容性最好
level = "3.1" // H.264 Level 3.1,支持720p@30fps
)
// 第二步:配置音频编码参数
val audioConfig = AudioConfig(
sampleRate = audioSampleRate,
bitrate = audioBitrate,
channels = 2, // 立体声,提供更好的音频体验
profile = "LC" // AAC-LC Profile,最常用的AAC格式
)
// 第三步:初始化编码器和推流器
if (!initializeStreamer(videoConfig, audioConfig)) {
throw Exception("编码器初始化失败")
}
// 第四步:建立RTMP连接
if (!connectToServer(rtmpUrl)) {
throw Exception("RTMP服务器连接失败")
}
// 第五步:开始推流数据传输
startDataTransmission()
isStreaming = true
println("🚀 RTMP推流已启动: $rtmpUrl")
println("📊 视频配置: ${videoWidth}x${videoHeight}@${videoBitrate}bps")
println("🎵 音频配置: ${audioSampleRate}Hz@${audioBitrate}bps")
true
} catch (e: Exception) {
println("❌ 推流启动失败: ${e.message}")
// 清理资源
cleanup()
false
}
}
/**
* ⏹️ 停止推流
*/
fun stopStreaming() {
if (isStreaming) {
try {
disconnectFromServer()
releaseResources()
isStreaming = false
println("⏹️ RTMP推流已停止")
} catch (e: Exception) {
println("❌ 停止推流失败: ${e.message}")
}
}
}
/**
* 📊 推流状态监控
*/
fun getStreamingStatus(): StreamingStatus {
return StreamingStatus(
isStreaming = isStreaming,
url = rtmpUrl,
bitrate = getCurrentBitrate(),
fps = getCurrentFPS(),
droppedFrames = getDroppedFrames()
)
}
/**
* 🔧 初始化编码器和推流器
*
* 创建H.264视频编码器和AAC音频编码器
* 配置编码参数,准备数据传输通道
*/
private fun initializeStreamer(videoConfig: VideoConfig, audioConfig: AudioConfig): Boolean {
return try {
// 初始化H.264视频编码器
println("🎬 初始化H.264视频编码器...")
videoEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
val videoFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, videoConfig.width, videoConfig.height).apply {
setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
setInteger(MediaFormat.KEY_BIT_RATE, videoConfig.bitrate)
setInteger(MediaFormat.KEY_FRAME_RATE, videoConfig.fps)
setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, videoConfig.keyFrameInterval)
}
videoEncoder?.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
// 初始化AAC音频编码器
println("🎵 初始化AAC音频编码器...")
audioEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC)
val audioFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, audioConfig.sampleRate, audioConfig.channels).apply {
setInteger(MediaFormat.KEY_BIT_RATE, audioConfig.bitrate)
setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)
}
audioEncoder?.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
println("✅ 编码器初始化完成")
true
} catch (e: Exception) {
println("❌ 编码器初始化失败: ${e.message}")
false
}
}
/**
* 🔗 连接RTMP服务器
*
* 建立TCP连接,完成RTMP握手协议
* 验证推流权限,准备数据传输
*/
private fun connectToServer(url: String): Boolean {
return try {
println("🔗 正在连接RTMP服务器: $url")
// 解析RTMP URL
val rtmpUrl = parseRTMPUrl(url)
println("📋 服务器: ${rtmpUrl.host}:${rtmpUrl.port}")
println("📋 应用名: ${rtmpUrl.app}")
println("📋 流名称: ${rtmpUrl.streamKey}")
// 创建RTMP连接
rtmpConnection = RTMPConnection().apply {
// 第一步:建立TCP连接
connect(rtmpUrl.host, rtmpUrl.port)
// 第二步:RTMP握手
performHandshake()
// 第三步:连接应用
connectApp(rtmpUrl.app)
// 第四步:发布流
publishStream(rtmpUrl.streamKey)
}
println("✅ RTMP服务器连接成功")
true
} catch (e: Exception) {
println("❌ RTMP服务器连接失败: ${e.message}")
false
}
}
/**
* 🚀 开始数据传输
*
* 启动编码器,开始采集和推送音视频数据
*/
private fun startDataTransmission() {
println("🚀 开始音视频数据传输...")
// 启动视频编码器
videoEncoder?.start()
// 启动音频编码器
audioEncoder?.start()
// 开始数据采集和推送循环
startEncodingLoop()
}
/**
* 🔄 编码推送循环
*
* 持续采集音视频数据,编码后推送到RTMP服务器
*/
private fun startEncodingLoop() {
Thread {
while (isStreaming) {
try {
// 处理视频帧
processVideoFrame()
// 处理音频帧
processAudioFrame()
// 更新统计信息
updateStatistics()
// 短暂休眠,避免CPU占用过高
Thread.sleep(1)
} catch (e: Exception) {
println("❌ 编码推送异常: ${e.message}")
break
}
}
}.start()
}
/**
* 🎬 处理视频帧
*/
private fun processVideoFrame() {
// 从相机获取视频帧数据
// 送入H.264编码器进行编码
// 获取编码后的数据包
// 通过RTMP连接发送到服务器
framesSent++
}
/**
* 🎵 处理音频帧
*/
private fun processAudioFrame() {
// 从麦克风获取音频数据
// 送入AAC编码器进行编码
// 获取编码后的数据包
// 通过RTMP连接发送到服务器
}
/**
* 📊 更新统计信息
*/
private fun updateStatistics() {
// 计算实时码率
// 统计帧率
// 检测丢帧情况
// 监控网络状态
}
/**
* 🔌 断开服务器连接
*/
private fun disconnectFromServer() {
try {
println("🔌 正在断开RTMP连接...")
rtmpConnection?.disconnect()
rtmpConnection = null
println("✅ RTMP连接已断开")
} catch (e: Exception) {
println("❌ 断开连接失败: ${e.message}")
}
}
/**
* 🧹 释放所有资源
*/
private fun releaseResources() {
try {
println("🧹 正在释放推流资源...")
// 停止并释放编码器
videoEncoder?.stop()
videoEncoder?.release()
videoEncoder = null
audioEncoder?.stop()
audioEncoder?.release()
audioEncoder = null
// 重置统计信息
framesSent = 0
bytesSent = 0
droppedFrames = 0
println("✅ 推流资源释放完成")
} catch (e: Exception) {
println("❌ 资源释放失败: ${e.message}")
}
}
/**
* 🧹 清理所有资源(异常情况使用)
*/
private fun cleanup() {
isStreaming = false
disconnectFromServer()
releaseResources()
}
/**
* 📋 解析RTMP URL
*/
private fun parseRTMPUrl(url: String): RTMPUrl {
// 解析 rtmp://server:port/app/streamkey 格式的URL
// 返回解析后的各个组件
return RTMPUrl("localhost", 1935, "live", "stream123")
}
// 获取当前推流状态的辅助方法
private fun getCurrentBitrate(): Int = if (isStreaming) (bytesSent * 8 / 1000).toInt() else 0
private fun getCurrentFPS(): Int = if (isStreaming) 30 else 0
private fun getDroppedFrames(): Long = droppedFrames
// 数据类定义
data class VideoConfig(
val width: Int,
val height: Int,
val bitrate: Int,
val fps: Int,
val keyFrameInterval: Int,
val profile: String = "baseline",
val level: String = "3.1"
)
data class AudioConfig(
val sampleRate: Int,
val bitrate: Int,
val channels: Int,
val profile: String = "LC"
)
data class StreamingStatus(
val isStreaming: Boolean,
val url: String?,
val bitrate: Int,
val fps: Int,
val droppedFrames: Long
)
data class RTMPUrl(
val host: String,
val port: Int,
val app: String,
val streamKey: String
)
// RTMP连接类(简化实现)
class RTMPConnection {
fun connect(host: String, port: Int) {
// TCP连接实现
}
fun performHandshake() {
// RTMP握手实现
}
fun connectApp(app: String) {
// 连接应用实现
}
fun publishStream(streamKey: String) {
// 发布流实现
}
fun disconnect() {
// 断开连接实现
}
}
}
🎯 RTMP推流实际使用示例
📱 完整的直播推流Activity:
/**
* 📱 直播推流Activity
*
* 集成相机采集和RTMP推流的完整实现
* 展示如何在实际应用中使用RTMP推流器
*/
class LiveStreamActivity : AppCompatActivity() {
private lateinit var rtmpStreamer: RTMPStreamer
private lateinit var cameraManager: CameraManager
private lateinit var audioManager: AudioManager
// UI组件
private lateinit var previewView: PreviewView
private lateinit var btnStartLive: Button
private lateinit var btnStopLive: Button
private lateinit var tvStatus: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_live_stream)
initViews()
initManagers()
setupClickListeners()
}
/**
* 🎬 初始化UI组件
*/
private fun initViews() {
previewView = findViewById(R.id.preview_view)
btnStartLive = findViewById(R.id.btn_start_live)
btnStopLive = findViewById(R.id.btn_stop_live)
tvStatus = findViewById(R.id.tv_status)
}
/**
* 🔧 初始化管理器
*/
private fun initManagers() {
// 初始化RTMP推流器
rtmpStreamer = RTMPStreamer()
// 初始化相机管理器
cameraManager = CameraManager(this)
// 初始化音频管理器
audioManager = AudioManager(this)
}
/**
* 🎯 设置按钮点击事件
*/
private fun setupClickListeners() {
btnStartLive.setOnClickListener {
startLiveStreaming()
}
btnStopLive.setOnClickListener {
stopLiveStreaming()
}
}
/**
* 🚀 开始直播推流
*/
private fun startLiveStreaming() {
lifecycleScope.launch {
try {
updateStatus("正在启动直播...")
// 第一步:启动相机预览
if (!cameraManager.startCamera(previewView)) {
throw Exception("相机启动失败")
}
// 第二步:启动音频采集
if (!audioManager.startRecording()) {
throw Exception("音频采集启动失败")
}
// 第三步:开始RTMP推流
val rtmpUrl = "rtmp://your-server.com/live/your-stream-key"
val success = rtmpStreamer.startStreaming(
rtmpUrl = rtmpUrl,
videoWidth = 1280,
videoHeight = 720,
videoBitrate = 2000000, // 2Mbps
audioSampleRate = 44100,
audioBitrate = 128000 // 128kbps
)
if (success) {
updateStatus("直播推流已启动")
btnStartLive.isEnabled = false
btnStopLive.isEnabled = true
// 开始监控推流状态
startStatusMonitoring()
} else {
throw Exception("RTMP推流启动失败")
}
} catch (e: Exception) {
updateStatus("启动失败: ${e.message}")
// 清理资源
cleanup()
}
}
}
/**
* ⏹️ 停止直播推流
*/
private fun stopLiveStreaming() {
lifecycleScope.launch {
updateStatus("正在停止直播...")
// 停止推流
rtmpStreamer.stopStreaming()
// 停止相机
cameraManager.stopCamera()
// 停止音频采集
audioManager.stopRecording()
// 停止状态监控
stopStatusMonitoring()
updateStatus("直播已停止")
btnStartLive.isEnabled = true
btnStopLive.isEnabled = false
}
}
/**
* 📊 开始状态监控
*/
private fun startStatusMonitoring() {
// 每秒更新一次推流状态
Timer().scheduleAtFixedRate(object : TimerTask() {
override fun run() {
val status = rtmpStreamer.getStreamingStatus()
runOnUiThread {
updateStatus("""
直播中...
码率: ${status.bitrate}kbps
帧率: ${status.fps}fps
丢帧: ${status.droppedFrames}
""".trimIndent())
}
}
}, 0, 1000)
}
/**
* ⏹️ 停止状态监控
*/
private fun stopStatusMonitoring() {
// 停止定时器
}
/**
* 📱 更新状态显示
*/
private fun updateStatus(message: String) {
runOnUiThread {
tvStatus.text = message
println("📱 状态更新: $message")
}
}
/**
* 🧹 清理资源
*/
private fun cleanup() {
cameraManager.stopCamera()
audioManager.stopRecording()
rtmpStreamer.stopStreaming()
}
override fun onDestroy() {
super.onDestroy()
cleanup()
}
}
📖 第四部分:HLS播放协议
📺 什么是HLS?
生活比喻:HLS就像是连续剧的分集播放
📺 完整电影 = 完整视频文件
📋 分集列表 = M3U8播放列表
🎬 每一集 = 视频片段(通常10秒)
📺 HLS播放器实现
📋 HLS播放核心逻辑说明:
HLS(HTTP Live Streaming)的核心思想是将长视频切分成小片段,通过HTTP协议分发,实现自适应码率和大规模并发播放。
🎬 HLS播放原理:
📺 完整视频 → 🔪 切片工具 → 📋 M3U8播放列表 + 🎞️ TS视频片段
↓
📱 播放器 ← 📥 HTTP下载 ← 🌐 CDN分发 ← 🏢 流媒体服务器
💡 关键技术特点:
-
📋 M3U8播放列表
- 包含所有视频片段的URL和时长信息
- 支持多码率版本,实现自适应播放
- 动态更新,支持直播场景
-
🎞️ TS视频片段
- 通常每个片段5-10秒
- 独立解码,支持随机访问
- 通过HTTP协议分发,利用CDN加速
-
🔄 自适应码率
- 根据网络状况自动切换清晰度
- 保证流畅播放体验
- 智能预测和缓冲管理
-
📊 播放策略
- 预加载多个片段,减少卡顿
- 智能缓冲管理,平衡延迟和流畅度
- 错误恢复机制,处理网络异常
🎯 实现流程:
- 解析M3U8 → 获取片段列表和播放参数
- 创建媒体源 → 配置ExoPlayer的HLS媒体源
- 自适应轨道 → 设置码率选择策略
- 开始播放 → 自动下载和播放片段
/**
* 📺 HLS播放器管理器
*/
class HLSPlayerManager(private val context: Context) {
private var exoPlayer: ExoPlayer? = null
private var currentPlaylist: HLSPlaylist? = null
/**
* 🎬 播放HLS流
*/
fun playHLS(m3u8Url: String, playerView: PlayerView) {
try {
// 创建ExoPlayer实例
exoPlayer = ExoPlayer.Builder(context)
.setTrackSelector(createAdaptiveTrackSelector())
.build()
// 绑定到PlayerView
playerView.player = exoPlayer
// 创建HLS媒体源
val mediaSource = createHLSMediaSource(m3u8Url)
// 设置媒体源并开始播放
exoPlayer?.apply {
setMediaSource(mediaSource)
playWhenReady = true
prepare()
}
println("🎬 开始播放HLS: $m3u8Url")
} catch (e: Exception) {
println("❌ HLS播放失败: ${e.message}")
}
}
/**
* 🎯 创建自适应轨道选择器
*/
private fun createAdaptiveTrackSelector(): DefaultTrackSelector {
return DefaultTrackSelector(context).apply {
setParameters(
buildUponParameters()
.setMaxVideoSizeSd() // 默认SD质量
.setPreferredVideoMimeType(MimeTypes.VIDEO_H264)
.build()
)
}
}
/**
* 📡 创建HLS媒体源
*/
private fun createHLSMediaSource(m3u8Url: String): MediaSource {
val dataSourceFactory = DefaultHttpDataSource.Factory()
.setUserAgent(Util.getUserAgent(context, "HLSPlayer"))
return HlsMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(m3u8Url))
}
/**
* 📋 解析M3U8播放列表
*/
suspend fun parseM3U8(m3u8Url: String): HLSPlaylist = withContext(Dispatchers.IO) {
try {
val client = OkHttpClient()
val request = Request.Builder().url(m3u8Url).build()
val response = client.newCall(request).execute()
if (!response.isSuccessful) {
throw Exception("获取M3U8失败: ${response.code}")
}
val content = response.body?.string() ?: throw Exception("M3U8内容为空")
parseM3U8Content(content, m3u8Url)
} catch (e: Exception) {
println("❌ 解析M3U8失败: ${e.message}")
throw e
}
}
/**
* 📝 解析M3U8内容
*/
private fun parseM3U8Content(content: String, baseUrl: String): HLSPlaylist {
val segments = mutableListOf<HLSSegment>()
val lines = content.split("\n")
var duration = 0.0
var segmentIndex = 0
for (line in lines) {
when {
line.startsWith("#EXTINF:") -> {
// 解析片段时长
duration = line.substringAfter(":").substringBefore(",").toDoubleOrNull() ?: 0.0
}
line.isNotEmpty() && !line.startsWith("#") -> {
// 片段URL
val segmentUrl = if (line.startsWith("http")) {
line
} else {
// 相对URL,需要拼接
"${baseUrl.substringBeforeLast("/")}/$line"
}
segments.add(
HLSSegment(
index = segmentIndex++,
url = segmentUrl,
duration = duration
)
)
}
}
}
return HLSPlaylist(
url = baseUrl,
segments = segments,
totalDuration = segments.sumOf { it.duration }
).also { currentPlaylist = it }
}
/**
* 🧹 释放播放器
*/
fun release() {
exoPlayer?.release()
exoPlayer = null
currentPlaylist = null
}
data class HLSPlaylist(
val url: String,
val segments: List<HLSSegment>,
val totalDuration: Double
)
data class HLSSegment(
val index: Int,
val url: String,
val duration: Double
)
}
📖 第五部分:WebRTC实时通信
⚡ 什么是WebRTC?
生活比喻:WebRTC就像是直接通话
📞 传统电话 = 通过电话交换机中转
⚡ WebRTC = 两个人直接对讲(点对点通信)
⚡ WebRTC基础实现
📋 WebRTC核心逻辑说明:
WebRTC(Web Real-Time Communication)的核心思想是实现点对点(P2P)实时通信,绕过服务器中转,达到超低延迟的音视频通话效果。
🔄 WebRTC通信流程:
📱 用户A ←→ 🌐 信令服务器 ←→ 📱 用户B
↓ ↓ ↓
🔍 ICE候选 📋 SDP交换 🔍 ICE候选
↓ ↓ ↓
📱 用户A ←─────── 直接P2P连接 ─────→ 📱 用户B
💡 关键技术组件:
-
📡 信令服务器(Signaling Server)
- 交换SDP(会话描述协议)信息
- 传递ICE候选信息
- 协调连接建立过程
- 注意:不传输音视频数据
-
🧊 ICE(Interactive Connectivity Establishment)
- 发现可用的网络路径
- 处理NAT穿透问题
- 选择最优连接路径
- 包含STUN/TURN服务器支持
-
📋 SDP(Session Description Protocol)
- 描述媒体能力和参数
- 包含音视频编码格式
- 网络地址和端口信息
- Offer/Answer模式协商
-
🎬 媒体流处理
- 实时音视频采集和编码
- 网络传输优化
- 自适应码率调整
- 丢包恢复和错误纠正
⚡ 超低延迟的秘密:
- 直接连接:绕过服务器,点对点传输
- UDP协议:牺牲可靠性换取速度
- 硬件加速:利用设备编解码器
- 智能缓冲:最小化缓冲延迟
🎯 实现挑战:
- NAT穿透:大部分设备在NAT后面
- 网络适应:动态调整传输参数
- 设备兼容:不同设备能力差异
- 连接稳定:处理网络切换和中断
/**
* ⚡ WebRTC通信管理器
*/
class WebRTCManager(private val context: Context) {
private var peerConnection: PeerConnection? = null
private var localVideoTrack: VideoTrack? = null
private var remoteVideoTrack: VideoTrack? = null
/**
* 🚀 初始化WebRTC
*/
fun initialize() {
// 初始化PeerConnectionFactory
val initializationOptions = PeerConnectionFactory.InitializationOptions.builder(context)
.setEnableInternalTracer(true)
.createInitializationOptions()
PeerConnectionFactory.initialize(initializationOptions)
// 创建PeerConnectionFactory
val options = PeerConnectionFactory.Options()
val peerConnectionFactory = PeerConnectionFactory.builder()
.setOptions(options)
.createPeerConnectionFactory()
// 创建PeerConnection
val rtcConfig = PeerConnection.RTCConfiguration(
listOf(
PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer()
)
)
peerConnection = peerConnectionFactory.createPeerConnection(
rtcConfig,
object : PeerConnection.Observer {
override fun onSignalingChange(signalingState: PeerConnection.SignalingState?) {
println("📡 信令状态变化: $signalingState")
}
override fun onIceConnectionChange(iceConnectionState: PeerConnection.IceConnectionState?) {
println("🧊 ICE连接状态: $iceConnectionState")
}
override fun onIceCandidate(iceCandidate: IceCandidate?) {
println("🧊 发现ICE候选: ${iceCandidate?.sdp}")
// 发送给对方
}
override fun onAddStream(mediaStream: MediaStream?) {
println("📺 接收到远程流")
mediaStream?.videoTracks?.firstOrNull()?.let { videoTrack ->
remoteVideoTrack = videoTrack
}
}
override fun onRemoveStream(mediaStream: MediaStream?) {
println("📺 远程流已移除")
}
// 其他回调方法...
override fun onDataChannel(dataChannel: DataChannel?) {}
override fun onIceGatheringChange(iceGatheringState: PeerConnection.IceGatheringState?) {}
override fun onRenegotiationNeeded() {}
override fun onIceCandidatesRemoved(iceCandidates: Array<out IceCandidate>?) {}
}
)
println("⚡ WebRTC初始化完成")
}
/**
* 📹 添加本地视频流
*/
fun addLocalVideoStream(surfaceView: SurfaceViewRenderer) {
// 这里应该集成相机采集
// 简化示例,实际需要使用Camera2或CameraX
println("📹 添加本地视频流")
}
/**
* 📞 创建通话邀请
*/
fun createOffer(callback: (sdp: String) -> Unit) {
peerConnection?.createOffer(object : SdpObserver {
override fun onCreateSuccess(sessionDescription: SessionDescription?) {
sessionDescription?.let { sdp ->
peerConnection?.setLocalDescription(object : SdpObserver {
override fun onSetSuccess() {
callback(sdp.description)
}
override fun onSetFailure(error: String?) {
println("❌ 设置本地描述失败: $error")
}
override fun onCreateSuccess(p0: SessionDescription?) {}
override fun onCreateFailure(p0: String?) {}
}, sdp)
}
}
override fun onCreateFailure(error: String?) {
println("❌ 创建Offer失败: $error")
}
override fun onSetSuccess() {}
override fun onSetFailure(error: String?) {}
}, MediaConstraints())
}
/**
* 📞 响应通话邀请
*/
fun createAnswer(offerSdp: String, callback: (sdp: String) -> Unit) {
val remoteDescription = SessionDescription(SessionDescription.Type.OFFER, offerSdp)
peerConnection?.setRemoteDescription(object : SdpObserver {
override fun onSetSuccess() {
peerConnection?.createAnswer(object : SdpObserver {
override fun onCreateSuccess(sessionDescription: SessionDescription?) {
sessionDescription?.let { sdp ->
peerConnection?.setLocalDescription(object : SdpObserver {
override fun onSetSuccess() {
callback(sdp.description)
}
override fun onSetFailure(error: String?) {}
override fun onCreateSuccess(p0: SessionDescription?) {}
override fun onCreateFailure(p0: String?) {}
}, sdp)
}
}
override fun onCreateFailure(error: String?) {}
override fun onSetSuccess() {}
override fun onSetFailure(error: String?) {}
}, MediaConstraints())
}
override fun onSetFailure(error: String?) {
println("❌ 设置远程描述失败: $error")
}
override fun onCreateSuccess(sessionDescription: SessionDescription?) {}
override fun onCreateFailure(error: String?) {}
}, remoteDescription)
}
/**
* 🧹 释放资源
*/
fun release() {
localVideoTrack?.dispose()
remoteVideoTrack?.dispose()
peerConnection?.close()
peerConnection = null
println("🧹 WebRTC资源已释放")
}
}
🎓 第五阶段总结
✅ 你已经掌握的技能
🌐 HTTP协议基础:
- 理解了HTTP请求响应机制
- 实现了视频文件下载功能
- 掌握了断点续传技术
📡 RTMP推流原理:
- 理解了推流和拉流的概念
- 掌握了RTMP协议的特点
- 实现了基础的推流功能
📺 HLS播放协议:
- 理解了HLS分片播放机制
- 掌握了M3U8播放列表解析
- 实现了自适应码率播放
⚡ WebRTC实时通信:
- 理解了P2P通信原理
- 掌握了WebRTC的基础概念
- 了解了信令服务器的作用
💡 学习建议:
网络协议比较抽象,建议结合实际抓包工具观察数据传输。
重点理解各协议的适用场景和优缺点! 🌐
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)