理解视频在网络中的传输方式 📡
掌握直播和点播的核心传输技术

在这里插入图片描述

🤔 为什么要学习网络传输协议?

🎯 核心价值:网络传输协议是短视频和直播应用的生命线

📊 不同协议解决不同问题

应用场景 核心需求 最佳协议 为什么选择它?
📱 短视频播放 快速加载、流畅播放 HTTP + 缓存 简单可靠,支持CDN加速
📺 直播推流 实时上传、稳定传输 RTMP 专为流媒体设计,延迟低
👥 直播观看 大规模分发、兼容性 HLS 支持自适应码率,兼容性强
💬 视频通话 超低延迟、双向通信 WebRTC 点对点直连,延迟最低

🚀 学会这些协议你能做什么?

📱 短视频应用

  • ✅ 实现秒开播放(HTTP优化)
  • ✅ 支持离线缓存(断点续传)
  • ✅ 自适应码率播放(根据网络调整清晰度)

📺 直播应用

  • ✅ 手机推流直播(RTMP推流)
  • ✅ 万人在线观看(HLS分发)
  • ✅ 实时互动连麦(WebRTC通信)

💡 实际价值

  • 🎯 技术选型:知道什么场景用什么协议
  • 🛠️ 性能优化:理解协议特点,优化用户体验
  • 🔧 问题排查:遇到网络问题能快速定位原因
  • 📈 架构设计:设计高并发、低延迟的视频系统

🎯 本阶段学习目标

学完这个阶段,你将能够:

  • 🌐 理解HTTP协议:掌握基础网络传输
  • 📡 掌握RTMP推流:实现直播推流功能
  • 📺 理解HLS协议:掌握分片播放机制
  • 了解WebRTC:实现超低延迟通信

📖 第一部分:HTTP协议基础

🌐 什么是HTTP协议?

生活比喻:HTTP就像是智能快递系统

🚚 快递车 = HTTP请求(可以按需运输)
📦 包裹 = 视频数据(可以分批次运送)
🏠 收货人 = 客户端(你的应用)
🏢 发货仓库 = 服务器(视频服务器)

💡 关键特点:
• 📋 可以指定要哪部分包裹(Range请求)
• 📦 不需要等所有包裹到齐就能开始使用(流式播放)
• 📍 可以随时要求从特定位置开始发货(跳转播放)
• 🔄 支持分批次持续配送(边下载边播放)

🎯 为什么HTTP适合视频流播放?

与传统的"一次性下载整个文件"不同,HTTP协议支持:

  1. 📋 Range请求:可以请求文件的特定部分

    GET /video.mp4 HTTP/1.1
    Range: bytes=0-1048576  // 只要前1MB数据
    
  2. 🌊 流式传输:边下载边播放,无需等待完整文件

    下载进度: [████████░░] 80%
    播放进度: [██████░░░░] 60%  ← 可以同时进行!
    
  3. 🎯 随机访问:可以跳转到视频任意位置

    Range: bytes=5242880-  // 从第5MB开始播放(跳转功能)
    
  4. 🔄 断点续传:网络中断后可以从断点继续

    Range: bytes=2097152-  // 从已下载的2MB位置继续
    

💡 这就是为什么HTTP更像"智能快递"而不是"传统邮政"

  • 传统邮政:必须等所有信件都到齐才能阅读 ❌
  • 智能快递:可以分批配送,先到的先使用 ✅
📡 HTTP视频播放器实现

📋 HTTP流式播放核心逻辑说明

HTTP视频播放器的核心思想是边下载边播放,不需要等待完整文件下载完成。实现流程如下:

  1. 🔍 服务器能力检测

    • 发送HEAD请求检查服务器是否支持Range请求
    • 获取视频文件总大小,用于进度计算和分片下载
  2. 📦 智能分片策略

    • 优先下载视频头部(前1MB),包含元数据信息
    • 让播放器快速获得视频格式、分辨率等关键信息
    • 后台继续下载剩余内容,实现无缝播放
  3. 🎯 播放器协调

    • MediaPlayer异步准备,不阻塞UI线程
    • 建立播放进度监控,实时更新UI
    • 支持用户交互(暂停、跳转、拖拽进度条)
  4. 🛡️ 错误处理与恢复

    • 网络异常时自动重试机制
    • 资源清理,避免内存泄漏
    • 缓存文件管理,优化存储空间

💡 关键技术点

  • 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适合视频播放?
  1. 🚀 快速启播

    • 不需要等待完整文件下载
    • 下载几秒钟内容就能开始播放
    • 用户体验接近实时播放
  2. 🎯 灵活控制

    • 支持Range请求,可以跳转到任意位置
    • 支持暂停、快进、倒退等操作
    • 可以实现拖拽进度条功能
  3. 🌐 广泛支持

    • 所有服务器都支持HTTP协议
    • 可以利用CDN进行全球加速
    • 兼容性最好,无需特殊配置
  4. 📊 智能缓存

    • 可以预加载下一段内容
    • 支持本地缓存,减少重复下载
    • 网络中断后可以断点续传

📖 第二部分:流媒体协议概述

📺 什么是流媒体?

生活比喻:流媒体就像是自来水

🚰 自来水 = 打开水龙头就有水流出(不需要等水桶装满)
📺 流媒体 = 打开播放器就能看视频(不需要等文件下载完)

📊 各协议播放特点对比

🎯 不同协议的播放方式和特点

协议类型 播放方式 缓冲策略 启播延迟 直播延迟 优点 缺点 适用场景
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封装 → 🌐 网络传输 → 🏢 流媒体服务器

⚡ 关键技术挑战

  1. 实时性要求:音视频数据必须在采集后立即编码和传输
  2. 网络适应:根据网络状况动态调整码率和质量
  3. 同步问题:确保音视频时间戳同步,避免音画不同步
  4. 错误恢复:网络中断时快速重连,丢帧时智能补偿

🎯 实现流程详解

  1. 🔧 编码器准备阶段

    • 配置H.264视频编码器:分辨率、码率、帧率、关键帧间隔
    • 配置AAC音频编码器:采样率、码率、声道数、编码格式
    • 优化编码参数:平衡画质、文件大小和编码速度
  2. 🔗 RTMP连接建立

    • TCP连接:建立可靠的网络传输通道
    • RTMP握手:验证协议版本,协商传输参数
    • 应用连接:连接到指定的直播应用(如"live")
    • 流发布:开始发布指定名称的直播流
  3. 📡 实时数据传输

    • 音视频数据采集:从相机和麦克风获取原始数据
    • 编码处理:将原始数据压缩为H.264和AAC格式
    • RTMP封装:将编码数据打包为RTMP协议格式
    • 网络发送:通过TCP连接发送到流媒体服务器
  4. 📊 质量监控与优化

    • 实时码率监控:确保传输速度符合预期
    • 丢帧检测:监控编码器和网络丢帧情况
    • 网络状态评估:根据延迟和丢包率调整策略
    • 自适应调整:动态调整编码参数适应网络变化
/**
 * 📡 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分发 ← 🏢 流媒体服务器

💡 关键技术特点

  1. 📋 M3U8播放列表

    • 包含所有视频片段的URL和时长信息
    • 支持多码率版本,实现自适应播放
    • 动态更新,支持直播场景
  2. 🎞️ TS视频片段

    • 通常每个片段5-10秒
    • 独立解码,支持随机访问
    • 通过HTTP协议分发,利用CDN加速
  3. 🔄 自适应码率

    • 根据网络状况自动切换清晰度
    • 保证流畅播放体验
    • 智能预测和缓冲管理
  4. 📊 播放策略

    • 预加载多个片段,减少卡顿
    • 智能缓冲管理,平衡延迟和流畅度
    • 错误恢复机制,处理网络异常

🎯 实现流程

  1. 解析M3U8 → 获取片段列表和播放参数
  2. 创建媒体源 → 配置ExoPlayer的HLS媒体源
  3. 自适应轨道 → 设置码率选择策略
  4. 开始播放 → 自动下载和播放片段
/**
 * 📺 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

💡 关键技术组件

  1. 📡 信令服务器(Signaling Server)

    • 交换SDP(会话描述协议)信息
    • 传递ICE候选信息
    • 协调连接建立过程
    • 注意:不传输音视频数据
  2. 🧊 ICE(Interactive Connectivity Establishment)

    • 发现可用的网络路径
    • 处理NAT穿透问题
    • 选择最优连接路径
    • 包含STUN/TURN服务器支持
  3. 📋 SDP(Session Description Protocol)

    • 描述媒体能力和参数
    • 包含音视频编码格式
    • 网络地址和端口信息
    • Offer/Answer模式协商
  4. 🎬 媒体流处理

    • 实时音视频采集和编码
    • 网络传输优化
    • 自适应码率调整
    • 丢包恢复和错误纠正

⚡ 超低延迟的秘密

  • 直接连接:绕过服务器,点对点传输
  • 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的基础概念
  • 了解了信令服务器的作用

💡 学习建议
网络协议比较抽象,建议结合实际抓包工具观察数据传输。
重点理解各协议的适用场景和优缺点! 🌐

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐