first commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
latextemplate/
|
||||
BIN
figures/hightube-adminpanel.png
Normal file
BIN
figures/hightube-adminpanel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 363 KiB |
BIN
figures/hightube-android.jpg
Normal file
BIN
figures/hightube-android.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 558 KiB |
BIN
figures/hightube-official.png
Normal file
BIN
figures/hightube-official.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 351 KiB |
BIN
figures/hightube-screenshot.png
Normal file
BIN
figures/hightube-screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 549 KiB |
BIN
figures/logo-long.png
Normal file
BIN
figures/logo-long.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
figures/topo.png
Normal file
BIN
figures/topo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 617 KiB |
165
labtemplate.typ
Normal file
165
labtemplate.typ
Normal file
@@ -0,0 +1,165 @@
|
||||
#let times = "Times New Roman"
|
||||
#let song = (times, "Noto Serif CJK SC")
|
||||
#let hei = (times, "Noto Sans CJK SC")
|
||||
#let kai = (times, "Noto Serif CJK SC")
|
||||
#let xbsong = (times, "Noto Serif CJK SC")
|
||||
#let fsong = (times, "Noto Serif CJK SC")
|
||||
#let code = (times, "Maple Mono NF CN")
|
||||
|
||||
#let requirement_report(
|
||||
title: "",
|
||||
author: "",
|
||||
id: "",
|
||||
grade: "",
|
||||
major: "",
|
||||
department: "",
|
||||
advisor: "",
|
||||
version: "V1.0",
|
||||
date: "",
|
||||
header_str: "",
|
||||
body) = {
|
||||
|
||||
// Set the document's basic properties.
|
||||
set document(author: author, title: title)
|
||||
set page(
|
||||
margin: (left: 30mm, right: 30mm, top: 30mm, bottom: 30mm),
|
||||
)
|
||||
|
||||
// Title row.
|
||||
v(158pt)
|
||||
align(center)[
|
||||
#block(text(weight: 700, size: 30pt, font: hei, tracking: 15pt, "网络应用开发"))
|
||||
]
|
||||
align(center)[
|
||||
#block(text(weight: 700, size: 30pt, font: song, tracking: 15pt, ""))
|
||||
]
|
||||
|
||||
v(103pt)
|
||||
pad(
|
||||
left: 1em,
|
||||
right: 1em,
|
||||
grid(
|
||||
columns: (80pt, 1fr),
|
||||
rows: (17pt, auto),
|
||||
text(weight: 700, size: 16pt, font: song, "项目名称:"),
|
||||
align(center, text(weight: "regular", size: 16pt, font: song, title)),
|
||||
text(""),
|
||||
line(length: 100%)
|
||||
)
|
||||
)
|
||||
|
||||
// Author information.
|
||||
v(82.5pt)
|
||||
|
||||
grid(
|
||||
columns: (0.25fr, 0.25fr, 0.25fr, 0.25fr),
|
||||
rows: (15pt, 8pt, 15pt, 8pt, 15pt, 8pt, 15pt, 8pt),
|
||||
text(size: 14pt, font: song, tracking: 10pt, "开发小组"),
|
||||
align(center, text(size: 14pt, font: song, author)),
|
||||
text(size: 14pt, font: song, tracking: 10pt, "小组成员"),
|
||||
align(center, text(size: 12pt, font: song, id)),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
|
||||
text(size: 14pt, font: song, tracking: 54pt, "专业"),
|
||||
align(center, text(size: 14pt, font: song, major)),
|
||||
text(size: 14pt, font: song, tracking: 54pt, "年级"),
|
||||
align(center, text(size: 14pt, font: times, grade)),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
|
||||
text(size: 14pt, font: song, tracking: 9pt, "所属学院"),
|
||||
align(center, text(size: 14pt, font: song, department)),
|
||||
text(size: 14pt, font: song, tracking: 9pt, "指导教员"),
|
||||
align(center, text(size: 14pt, font: song, advisor)),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
|
||||
text(size: 14pt, font: song, tracking: 20pt, "版本号"),
|
||||
align(center, text(size: 14pt, font: song, version)),
|
||||
text(size: 14pt, font: song, tracking: 9pt, "编写时间"),
|
||||
align(center, text(size: 14pt, font: song, date)),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
text(""),
|
||||
line(length: 100%),
|
||||
)
|
||||
|
||||
v(35pt)
|
||||
align(center)[#image("figures/logo-long.png", width: 6cm)]
|
||||
|
||||
pagebreak()
|
||||
|
||||
set page(
|
||||
margin: (left: 30mm, right: 30mm, top: 30mm, bottom: 30mm),
|
||||
numbering: "i",
|
||||
number-align: center,
|
||||
)
|
||||
|
||||
v(14pt)
|
||||
align(center)[
|
||||
#block(text(font: hei, size: 14pt, "《项目需求分析报告》编写说明"))
|
||||
]
|
||||
|
||||
v(14pt)
|
||||
text("")
|
||||
par(first-line-indent: 2em, text(font: song, size: 12pt, "项目需求分析报告应符合以下要求:"))
|
||||
|
||||
par(first-line-indent: 2em, text(font: fsong, size: 12pt, "(1)采用A4(21cm×29.7cm)白色复印纸。页边距均为3cm;正文字号为小4号,中文宋体,英文Times New Roman,每页30行,页码居中。"))
|
||||
|
||||
par(first-line-indent: 2em, text(font: fsong, size: 12pt, "(2)报告主要涵盖:引言(目的、背景)、项目概述(功能、环境)、功能性需求(详细功能描述)、非功能性需求(性能、安全、可靠性)以及外部接口需求。"))
|
||||
|
||||
par(first-line-indent: 2em, text(font: fsong, size: 12pt, "(3)应结合图表(如UML用例图、流程图)对需求进行辅助说明,图表需有编号和名称。"))
|
||||
|
||||
pagebreak()
|
||||
|
||||
set page(
|
||||
margin: (left: 30mm, right: 30mm, top: 30mm, bottom: 30mm),
|
||||
numbering: "1",
|
||||
number-align: center,
|
||||
)
|
||||
|
||||
set heading(numbering: "1.1")
|
||||
|
||||
show heading: it => box(width: 100%)[
|
||||
#v(0.50em)
|
||||
#set text(font: hei)
|
||||
#if it.numbering != none {
|
||||
counter(heading).display()
|
||||
h(0.5em)
|
||||
}
|
||||
#it.body
|
||||
]
|
||||
// Main body.
|
||||
set par(justify: true, first-line-indent: 2em)
|
||||
|
||||
body
|
||||
}
|
||||
|
||||
#let para(t) = par(text(font: song, size: 10.5pt, t))
|
||||
|
||||
#let abstract-keywords(abstract: none, keywords: "") = {
|
||||
if abstract != none {
|
||||
place(
|
||||
top + center,
|
||||
scope: "parent",
|
||||
float: true,
|
||||
block(width: 100%)[
|
||||
#set par(justify: true, first-line-indent: 0pt)
|
||||
#text(font: hei, size: 10.5pt)[【摘 要】]
|
||||
#text(font: song, size: 9pt)[#abstract]
|
||||
|
||||
#v(0.8em)
|
||||
#text(font: hei, size: 10.5pt)[【关键词】]
|
||||
#text(font: hei, size: 10.5pt)[#keywords]
|
||||
#v(1.5em)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
453
main.typ
Normal file
453
main.typ
Normal file
@@ -0,0 +1,453 @@
|
||||
#import "labtemplate.typ": *
|
||||
|
||||
#show: requirement_report.with(
|
||||
title: "Hightube开源跨平台个人直播系统",
|
||||
author: "Highground-Soft",
|
||||
id: "程景愉",
|
||||
grade: "2023",
|
||||
major: "网络工程",
|
||||
department: "计算机学院",
|
||||
advisor: "邱振宇",
|
||||
version: "V1.0.1",
|
||||
date: "2026年7月",
|
||||
header_str: "《网络应用开发》课程设计实验报告 - Hightube",
|
||||
)
|
||||
|
||||
#set page(header: [
|
||||
#set par(spacing: 6pt)
|
||||
#align(center)[#text(size: 11pt)[]]
|
||||
#v(-0.3em)
|
||||
#line(length: 100%, stroke: (thickness: 1pt))
|
||||
],)
|
||||
|
||||
#outline(title: "目录", depth: 3, indent: 2em)
|
||||
#pagebreak()
|
||||
|
||||
#outline(
|
||||
title: [图目录],
|
||||
target: figure.where(kind: image),
|
||||
)
|
||||
#pagebreak()
|
||||
|
||||
#set page(
|
||||
margin: (left: 20mm, right: 20mm, top: 25.4mm, bottom: 22.5mm),
|
||||
columns: 2,
|
||||
)
|
||||
|
||||
#abstract-keywords(
|
||||
abstract: [本项目设计并开发了一款开源跨平台个人直播系统——Hightube。系统采用前后端分离架构,后端基于 Go 语言 @go_doc 和 Gin 框架,实现了高性能的 RTMP 流媒体处理引擎与业务 API。通过 GORM 框架结合 SQLite 数据库,并运用 WAL 模式与 Pragmas 调优,攻克了轻量级数据库在高并发场景下的读写冲突瓶颈。同时,后端集成 FFmpeg 实现了 720p/480p 的多档码率实时转码与分发。前端采用 Flutter 框架 @flutter_doc 实现跨平台一致性体验,并通过平台视图注册 iframe 引入 flv.js,开发了具备自适应追帧与速率调整算法的 Web 端 HTTP-FLV 播放器。此外,系统通过 WebSocket 提供了低延迟聊天与弹幕互动功能,并基于 FRP 反向隧道与 Caddy 反向代理完成了公网分布式部署与智能路径路由。本报告对系统进行了性能定量测试,实测表明系统推拉流稳定、追帧延迟低、并发性能高,为个人开播提供了完整的技术解决方案。],
|
||||
keywords: [个人直播系统、RTMP 协议、Go 语言、Flutter 框架、FRP 穿透、Caddy 反向代理],
|
||||
)
|
||||
|
||||
= 引言
|
||||
|
||||
随着互联网带宽的提升与即时通讯技术的发展,网络直播已成为人们获取信息、娱乐互动的重要载体。流媒体直播应用不仅涉及高并发的网络连接管理,还对底层音视频编解码、网络传输协议(如 RTMP、HTTP-FLV、HLS)以及跨平台客户端的渲染性能提出了很高的要求。
|
||||
|
||||
作为网络应用开发课程设计的成果,本报告详细阐述了开源跨平台个人直播系统 Hightube 的设计与实现方案。传统的网络直播系统通常需要依赖高配置的云端硬件转码以及昂贵的 CDN 分发,且复杂的推拉流服务配置对个人用户构成了较高门槛。Hightube 针对这些痛点,旨在提供一个极简、易部署、且功能完备的流媒体直播解决方案。
|
||||
|
||||
在本项目中,我们开发了一个支持用户认证鉴权、个人专属直播间管理、推流密钥校验、多分辨率转码、实时弹幕互动以及智能公网路由的系统。报告将重点介绍系统在 Go 语言高并发后端、SQLite 性能优化、Flutter 跨端 Web 播放器低延迟自适应追帧、以及 FRP 与 Caddy 联合穿透部署等关键技术上的设计思路与具体实现。
|
||||
|
||||
本人(程景愉)在小组中承担了核心的技术攻关工作,具体职责包括:后端基础流媒体引擎架构、RTMP 握手与推流密钥鉴权拦截、HTTP-FLV 极速流输出通道、FFmpeg 转码子进程管理、Flutter Web 端 iframe 混合渲染播放器设计、追帧算法编写,以及基于 FRP 与 Caddy 的公网发布部署。
|
||||
|
||||
= 实验内容与环境
|
||||
|
||||
本课程实验要求基于流媒体网络传输原理,设计并开发一个能在真实局域网/互联网环境下稳定运行、具备基本账号体系、直播推送、拉取观看与互动反馈功能的网络直播平台软件。
|
||||
|
||||
我们的项目仓库在天河超算俱乐部的Gitea服务器上开源,访问地址`https://git.nudt.space/Highground-Soft/Hightube`可查看源代码。
|
||||
|
||||
== 实验分组
|
||||
本课程实验由小组合作完成,小组成员共四人,其具体开发分工如表1所示:
|
||||
|
||||
#align(center)[
|
||||
#table(
|
||||
columns: (80pt, 1fr),
|
||||
align: (center, left),
|
||||
stroke: 0.5pt,
|
||||
[*成员*], [*开发工作分工*],
|
||||
[程景愉(本人)], [项目总体管理,前后端核心基础版本设计开发,系统公网隧道穿透与Caddy反向代理分布式部署。],
|
||||
[舒钰权], [用户登录与个人设置界面开发, 基于 WebSocket 的直播聊天室与弹幕引擎开发,前端弹幕浮层与裁剪区域优化。],
|
||||
[张钊源], [后端管理面板、服务器运行状态监控(Metrics)、日志收集与历史审计功能等功能开发。],
|
||||
[李俊友], [后端核心流媒体与业务API架构设计及实现、前端音视频播放与Web适配, 项目介绍网站设计部署。]
|
||||
)
|
||||
]
|
||||
|
||||
== 进度安排
|
||||
本项目采用迭代式开发模式,整体时间规划如下:
|
||||
1. *需求分析与技术选型(第1周)*:确定 Go 后端与 Flutter 客户端的技术路线,明确流媒体协议(RTMP 推流、HTTP-FLV 拉流)。
|
||||
2. *核心流媒体引擎与数据库设计(第2-3周)*:搭建基础 RTMP 监听服务,完成基于 JWT 鉴权的业务 API 开发,设计 SQLite 数据库结构。
|
||||
3. *前端观看端与 Web 适配(第4-5周)*:完成多端 UI 框架搭建,针对 Web 浏览器开发嵌入式 flv.js 播放器并调优延迟。
|
||||
4. *弹幕与多档码率转码(第6-7周)*:集成 WebSocket 弹幕功能,编写基于 FFMPEG 的自动转码服务。
|
||||
5. *公网部署与联调测试(第8周)*:配置 FRP 穿透与公网 Caddy 路由,完成整体兼容性与负载测试。
|
||||
|
||||
== 实验环境
|
||||
本项目的开发、调试与部署环境横跨多种软硬件平台:
|
||||
1. *硬件环境*:
|
||||
- 开发工作站:ThinkPad AMD Ryzen Pro 7840U/16GB RAM 笔记本电脑。
|
||||
- 公网中转服务器 (`stream.nudt.space`):单核 2GB 内存云服务器(公网部署)。
|
||||
- 内网宿主服务器 (`Tailscale 100.115.45.3`):部署于本地内网的 CentOS Stream 10 主机。
|
||||
2. *软件环境*:
|
||||
- 操作系统:CachyOS (开发端), CentOS Stream 10 (服务端)。
|
||||
- 核心依赖:Go 1.20+ (后端), Flutter 3.10+ (客户端), FFmpeg 6.0 (转码端), Caddy 2.7+ (代理服务), FRP 0.69.1 (隧道穿透)。
|
||||
|
||||
= 目标系统需求与逻辑结构设计
|
||||
|
||||
== 目标系统需求概述
|
||||
根据网络直播应用的技术特性和课程要求,本系统设计的目标主要是为个人主播和观众提供一套高可用、低延迟的流媒体推拉流平台。系统的核心功能需求如下:
|
||||
1. *用户认证与权限管理*:提供完整的用户注册、登录、修改密码和注销会话机制,保护个人推流和管理面板的权限。
|
||||
2. *推拉流隔离与流安全控制*:主播使用随机生成的专属 `Stream Key` 进行 RTMP 推流;观众使用公开的 `Room ID` 观看直播,推流与拉流路径必须严格隔离,且必须实现后台流密钥的安全校验。
|
||||
3. *自适应多档流分发*:系统需具备实时转码能力,能够根据网络环境输出不同分辨率(如原始流、720p、480p)的视频通道。
|
||||
4. *跨平台与 Web 浏览器适配*:系统需要支持多端观看,尤其是能够适配无原生 RTMP 解码支持的 Web 浏览器。
|
||||
5. *实时互动聊天*:直播间内部提供低延迟的实时文本发言和弹幕滑行组件。
|
||||
|
||||
== 目标系统逻辑结构
|
||||
为了满足系统的高并发和低耦合性,Hightube 采用前后端分离的系统逻辑结构。全系统包含以下三大核心模块:
|
||||
1. *应用业务服务器 (Go API)*:由 Gin 框架驱动,用于处理 HTTP RESTful 业务请求。该服务还作为数据源提供当前活跃房间列表、播放流信息查询以及用户资料管理。
|
||||
2. *流媒体转发服务器 (RTMP/FLV Engine)*:流媒体核心引擎。负责监听标准端口,解析 RTMP 封包,实现推流密钥校验,并复用音视频数据包封装为浏览器可接收的 HTTP-FLV 分块视频流(Chunked Stream)。
|
||||
3. *实时聊天广播服务器 (WebSocket Hub)*:独立的消息广播总线。维护各直播间内客户端的长连接,当收到某一客户端的文字聊天包时,进行低延迟的全局广播。
|
||||
|
||||
== 关系数据库设计与优化
|
||||
为了存储账号及直播间绑定信息,我们设计了关系数据库,其主要的实体为用户 (`User`) 和直播间 (`Room`),结构设计是一对一(`1:1`)的关系。
|
||||
|
||||
在 Go 语言中,基于 GORM 框架定义的数据库实体结构如下:
|
||||
|
||||
```go
|
||||
// User 实体结构
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Username string `gorm:"uniqueIndex;not null"`
|
||||
Password string `gorm:"not null"` // Bcrypt密文
|
||||
Role string `gorm:"type:varchar(20);default:user"`
|
||||
Enabled bool `gorm:"default:true"`
|
||||
}
|
||||
|
||||
// Room 实体结构
|
||||
type Room struct {
|
||||
gorm.Model
|
||||
UserID uint `gorm:"uniqueIndex;not null"`
|
||||
Title string `gorm:"default:'My Live Room'"`
|
||||
StreamKey string `gorm:"uniqueIndex;not null"` // 推流密钥
|
||||
IsActive bool `gorm:"index;default:false"` // 是否在线
|
||||
}
|
||||
```
|
||||
|
||||
=== SQLite 并发调优与 WAL 模式
|
||||
针对 SQLite 默认在并发写入时易锁表的瓶颈,我们在数据库连接和初始化时进行了专门调优,保证了数据持久化的健壮性:
|
||||
1. *启用 WAL(Write-Ahead Logging)模式* @sqlite_wal:通过修改日志模式,使读操作和写操作互不阻塞,允许多个读者与一个写者并发运行。
|
||||
2. *Pragmas 指令调优*:
|
||||
- `PRAGMA synchronous=NORMAL`:在保证事务持久化安全的前提下,将同步级别降为 NORMAL,减少昂贵的磁盘同步等待。
|
||||
- `PRAGMA temp_store=MEMORY`:将临时缓存和排序索引保存在内存中,提升查询速度。
|
||||
- `PRAGMA foreign_keys=ON`:强制实施外键级联,保证数据逻辑完整性。
|
||||
3. *Busy Timeout 超时退避*:连接参数增加 `_busy_timeout=5000`,使写冲突发生时自动以退避算法等待 5 秒,从根本上消除了并发数据库写入冲突错误。
|
||||
|
||||
= 目标系统详细设计与实现
|
||||
|
||||
为了实现系统的各项需求,本节对系统核心部分的详细设计和技术实现进行解析。
|
||||
|
||||
== 后端基础版本设计与实现 (本人核心工作)
|
||||
本人主要承担了后端流媒体底座与业务鉴权系统的架构开发。
|
||||
|
||||
=== JWT 用户身份认证与密码哈希
|
||||
用户密码的安全存储和访问令牌鉴权是系统的基本防线:
|
||||
1. *密码安全哈希*:当用户通过 `/api/register` 接口注册时,后端使用 `bcrypt` 算法,在 CPU 消耗因子设为 10 的情况下对密码进行单向哈希加密:
|
||||
```go
|
||||
hashedPassword, err := utils.HashPassword(req.Password)
|
||||
```
|
||||
该算法自动混入随机盐,产生的 60 字节密文存入数据库,能有效对抗彩虹表和撞库。
|
||||
2. *JWT 令牌机制*:用户在 `/api/login` 认证通过后,系统调用 `jwt-go` 生成包含自定义 Claims(包括 `user_id`、`username` 和 `role`)的访问令牌(Access Token),并使用本地私钥采用 HS256 对其签名。
|
||||
3. *鉴权拦截中间件*:在 Gin 框架中定义 `AuthMiddleware` 中间件,解析请求头中的 `Authorization: Bearer <token>`。若 Token 过期或非法,返回 401 状态并中断链式调用。
|
||||
|
||||
=== RTMP 协议解析与 Stream Key 鉴权拦截
|
||||
我们基于 `joy4` 流媒体库 @joy4_github 构建 RTMP 引擎,监听公网的 `1935` 推流端口。主播通过 OBS 等推流工具发起推流时,引擎拦截其 `HandlePublish` 事件并解析推流路径:
|
||||
```go
|
||||
s.server.HandlePublish = func(conn *rtmp.Conn) {
|
||||
streamPath := conn.URL.Path // 路径格式: /live/{stream_key}
|
||||
parts := strings.Split(streamPath, "/")
|
||||
// 1. 从数据库中动态校验该推流密钥是否存在
|
||||
room, err := db.LoadRoomByStreamKey(parts[2])
|
||||
if err != nil {
|
||||
monitor.Warnf("Unauthorized key: %s", streamPath)
|
||||
return // 鉴权失败,直接关闭底层 TCP Socket
|
||||
}
|
||||
// 2. 鉴权成功,将流重定向到以公开 RoomID 命名的发布路径下
|
||||
roomID := fmt.Sprintf("%d", room.ID)
|
||||
channelPath := fmt.Sprintf("/live/%s", roomID)
|
||||
|
||||
q := pubsub.NewQueue() // 初始化该房间的广播包队列
|
||||
streams, _ := conn.Streams()
|
||||
q.WriteHeader(streams)
|
||||
|
||||
s.mutex.Lock()
|
||||
s.channels[channelPath] = q // 注册通道
|
||||
s.mutex.Unlock()
|
||||
|
||||
db.SetRoomActive(room.ID, true) // 标记房间状态为在线
|
||||
|
||||
defer func() {
|
||||
s.mutex.Lock()
|
||||
delete(s.channels, channelPath)
|
||||
s.mutex.Unlock()
|
||||
q.Close()
|
||||
db.SetRoomActive(room.ID, false) // 标记离线
|
||||
}()
|
||||
avutil.CopyPackets(q, conn) // 持续从物理连接中搬运音视频包
|
||||
}
|
||||
```
|
||||
|
||||
=== FFmpeg 实时转码
|
||||
为了支持多档清晰度播放,当主播向 RTMP 服务推送源流时,Go 进程通过 `exec.CommandContext` 动态创建 FFMPEG @ffmpeg_doc 编码子进程:
|
||||
```bash
|
||||
ffmpeg -fflags nobuffer -i rtmp://127.0.0.1:1935/live/{room_id} \
|
||||
-vf scale=1280:-2 -c:v libx264 -preset ultrafast -tune zerolatency \
|
||||
-b:v 2500k -c:a aac -b:a 128k -f flv \
|
||||
rtmp://127.0.0.1:1935/variant/{room_id}/720p/{token}
|
||||
```
|
||||
转码完成后的多分辨率流会被推送回本地的 `/variant` 鉴权通道下,由服务器自动接管分发。
|
||||
|
||||
=== HTTP-FLV 极速流输出
|
||||
在业务 API 接口中,本人设计了无插件拉流方案。当观众端访问 `/live/:room_id` 时,Gin 服务会自动将该 HTTP 连接升级为持续分块的 FLV 封包流:
|
||||
1. 设置 `Transfer-Encoding: chunked` 和 `Content-Type: video/x-flv`,维持 TCP 长连接。
|
||||
2. 引入 `bufio.NewWriterSize(c.Writer, 4096)` 对 http.Flusher 进行双重缓冲,合并短小的音视频 Tag 数据包以减少 I/O 硬件层面的系统调用,在保证超低直播延迟(< 2s)的前提下,大幅度减轻了 CPU 的 Socket 写入负载。
|
||||
|
||||
#figure(
|
||||
image("figures/hightube-adminpanel.png", width: 90%),
|
||||
caption: [Hightube 后端管理与服务器监控面板],
|
||||
)
|
||||
|
||||
== 前端基础版本设计与实现 (本人核心工作)
|
||||
客户端基于 Flutter 编写,实现跨多端的统一开发。
|
||||
|
||||
=== Flutter 跨平台架构与 Web 隔离
|
||||
Flutter 跨平台底层使用的是 Skia/Impeller 渲染引擎。但在 Web 浏览器中,由于线程沙箱安全限制,浏览器无法直接渲染音视频流数据。为了保证在 Android、Windows 和 Web 端具有一致的界面,本人采用平台隔离的设计模式:在 Native 端调用播放器核心硬解;在 Web 端通过注册平台视图(Platform View)渲染一个独立的原生 HTML5 播放页面。
|
||||
|
||||
=== Web 播放器设计
|
||||
在 Web 端的实现中,使用 `HtmlElementView` 引入 iframe 指向本地的 `flv_player.html` 静态页面:
|
||||
```dart
|
||||
ui_web.platformViewRegistry.registerViewFactory
|
||||
(_viewType, (int viewId) {
|
||||
final iframe = html.IFrameElement()
|
||||
..src = 'flv_player.html?src=${Uri.encodeComponent(widget.streamUrl)}&volume=${widget.volume}'
|
||||
..style.border = '0'
|
||||
..style.width = '100%'
|
||||
..style.height = '100%'
|
||||
..allow = 'autoplay; fullscreen';
|
||||
return iframe;
|
||||
});
|
||||
```
|
||||
在 iframe 内部引入本地化的 `flv.min.js` @flvjs_github。当客户端的主题或音量发生改变时,Flutter 通过 HTML5 的 `postMessage` 通道跨域向 iframe 内的 JS 发送调音指令,实现逻辑解耦。
|
||||
|
||||
=== 低延迟自适应追帧算法
|
||||
为了解决 Web 端浏览器因网络抖动和垃圾回收引起的播放器缓存累积、导致直播延迟越来越高的问题,本人在 `flv_player.html` 的 JS 引擎中编写了一套自适应追帧和强制跳帧算法:
|
||||
```javascript
|
||||
video.addEventListener('timeupdate', function() {
|
||||
if (video.buffered.length > 0) {
|
||||
const end = video.buffered.end(video.buffered.length - 1);
|
||||
const diff = end - video.currentTime; // 计算当前缓冲区延迟
|
||||
|
||||
if (diff > 5.0) {
|
||||
// 积压严重,执行跳帧直接同步到最新画面
|
||||
video.currentTime = end - 1.0;
|
||||
} else if (diff > 1.5) {
|
||||
// 轻微积压,加速 1.15 倍进行微调追帧
|
||||
video.playbackRate = 1.15;
|
||||
} else {
|
||||
// 延迟正常,恢复原速
|
||||
video.playbackRate = 1.0;
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
实测表明,此算法能将 Web 直播延迟稳固控制在 1.6 秒左右,基本消除了因播放积压带来的滞后感。
|
||||
|
||||
#figure(
|
||||
image("figures/hightube-android.jpg", width: 50%),
|
||||
caption: [Hightube 手机端直播管理与设置界面],
|
||||
)
|
||||
|
||||
== 实时互动与弹幕系统
|
||||
|
||||
=== WebSocket 聊天室与 Hub 广播
|
||||
实时互动通过后台的 WebSocket 服务实现。后端采用 Hub 设计模式,为每个活跃的 `room_id` 维护一个 Hub 实体。Hub 负责管理该房间内的所有连接(Conn),监听注册、注销和广播事件。当接收到某一用户的发言后,将其封装为 JSON 格式,并发广播下发给所有连接,维持高密度的并发文字交流。
|
||||
|
||||
=== 客户端弹幕漂移渲染
|
||||
客户端接收到弹幕文本后,通过 `Stack` 重叠组件,在视频画面上方覆盖弹幕滑行 Widget。我们优化了弹幕的位置分配和滑行路径(限制在视频的上半区,即 `0%` 到 `50%` 的高度内),并修复了 `Positioned` 错层排版引起的渲染崩溃问题,大幅提升了大量弹幕下的整体帧率。
|
||||
|
||||
== 第三方软件许可证与开源规范
|
||||
为了保证本项目的规范性与合规性,我们对 Hightube 开发中引用的所有第三方库和组件进行了完整的授权许可证跟踪。具体如下:
|
||||
1. *Go 语言后端依赖*:
|
||||
- `Gin Web Framework`:遵循 MIT 许可证。
|
||||
- `GORM DB Library`:遵循 MIT 许可证。
|
||||
- `joy4 Streaming Media Library`:遵循 MIT 许可证。
|
||||
- `gorilla/websocket`:遵循 BSD 2-Clause 许可证。
|
||||
2. *Flutter 客户端依赖*:
|
||||
- `Flutter SDK`:遵循 BSD 3-Clause 许可证。
|
||||
- `http` & `web_socket_channel`:遵循 BSD 3-Clause 许可证。
|
||||
3. *Web 端播放组件*:
|
||||
- `flv.js`:遵循 Apache License 2.0 许可证。
|
||||
4. *网络穿透与代理*:
|
||||
- `Caddy Web Server`:遵循 Apache License 2.0 许可证。
|
||||
- `FRP Tunneling`:遵循 Apache License 2.0 许可证。
|
||||
|
||||
根据以上开源软件许可,Hightube 项目自身的源代码遵循 *MIT 许可证*进行开源发布。由于 MIT、BSD 和 Apache 2.0 许可证之间均具有极佳的兼容性,系统的许可证授权链是完整合规且自恰的。
|
||||
|
||||
= 公开平台部署与网络拓扑 (本人核心工作)
|
||||
|
||||
== 网络拓扑结构
|
||||
为了使项目能够对外公开试用,我们将服务部署在公网上。系统最终的网络拓扑部署结构如图2所示:
|
||||
|
||||
#figure(
|
||||
image("figures/topo.png", width: 95%),
|
||||
caption: [Hightube 系统网络部署拓扑结构图],
|
||||
)
|
||||
|
||||
== FRP 穿透与反向隧道参数配置
|
||||
由于本地开发测试机(`100.115.45.3`)位于局域网内部,没有公网 IP,我们采用 FRP 内网穿透反向代理 @frp_github 将内部流量引出。
|
||||
1. *公网中转服务器 (`stream.nudt.space`) 上部署 `frps`*,其配置文件 `frps.toml` 内容为:
|
||||
```toml
|
||||
bindPort = 7000
|
||||
```
|
||||
2. *内网开发机 (`100.115.45.3`) 上部署 `frpc`*,其配置文件 `frpc.toml` 内容为:
|
||||
```toml
|
||||
serverAddr = "stream.nudt.space"
|
||||
serverPort = 7000
|
||||
|
||||
[[proxies]]
|
||||
name = "web80to8080"
|
||||
type = "tcp"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 80 # 本地 Caddy 静态容器
|
||||
remotePort = 8080 # 映射到公网 8080 端口
|
||||
|
||||
[[proxies]]
|
||||
name = "web8081"
|
||||
type = "tcp"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 8081 # Go 业务与拉流接口
|
||||
remotePort = 8081 # 映射到公网 8081 端口
|
||||
|
||||
[[proxies]]
|
||||
name = "rtmp1935"
|
||||
type = "tcp"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 1935 # 本地 RTMP 引擎监听
|
||||
remotePort = 1935 # 映射到公网 1935 端口
|
||||
```
|
||||
|
||||
== Caddy 反向代理与路径路由控制
|
||||
在公网服务器上,Caddy @caddy_doc 监听公网 80/443 端口,用于请求分类和 SSL 安全证书自签发管理。其 Caddyfile 配置如下:
|
||||
```toml
|
||||
# 静态展示页面服务
|
||||
hightube.nudt.space {
|
||||
root * /var/www/hightube
|
||||
file_server
|
||||
encode gzip
|
||||
}
|
||||
|
||||
# 动态直播业务路由
|
||||
stream.nudt.space {
|
||||
encode gzip
|
||||
|
||||
# 规则1: 将 API 与拉流请求路由给 8081 端口 (Go 后端)
|
||||
handle /api/* {
|
||||
reverse_proxy localhost:8081
|
||||
}
|
||||
handle /live/* {
|
||||
reverse_proxy localhost:8081
|
||||
}
|
||||
|
||||
# 规则2: 其余请求路由给 8080 端口 (本地 Caddy 分发的 Flutter Web)
|
||||
handle {
|
||||
reverse_proxy localhost:8080
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
而在内网本地开发主机上,本地 Caddy 只需分发本地 Flutter Web 静态产物即可:
|
||||
```toml
|
||||
:80 {
|
||||
root * /var/www/html
|
||||
file_server
|
||||
}
|
||||
```
|
||||
|
||||
== 系统运行效果与插图占位
|
||||
|
||||
#figure(
|
||||
image("figures/hightube-official.png", width: 90%),
|
||||
caption: [Hightube 项目介绍网站(https://hightube.nudt.space)],
|
||||
)
|
||||
|
||||
#figure(
|
||||
image("figures/hightube-screenshot.png", width: 95%),
|
||||
caption: [OBS 推流与网页端低延迟播放及弹幕互动效果],
|
||||
)
|
||||
|
||||
#pagebreak()
|
||||
|
||||
= 系统性能定量分析
|
||||
|
||||
为了客观评估 Hightube 直播系统在大流量下的稳定度、延迟表现以及数据库在高并发写入时的承载极限,本人对系统进行了定量测试。
|
||||
|
||||
== 流媒体播放延迟定量测试
|
||||
我们在 OBS 主播端通过画面时钟计时器进行音视频采集推送,并在 Web 播放端使用我们的播放器拉流,对比推送端和接收端的物理画面时差。我们还测试了“自适应追帧算法”关闭和开启后的表现。测试数据如表2所示:
|
||||
|
||||
#align(center)[
|
||||
#table(
|
||||
columns: (90pt, 80pt, 80pt),
|
||||
align: (center, center, center),
|
||||
stroke: 0.5pt,
|
||||
[*播放链路类型*], [*追帧算法关闭*], [*追帧算法开启*],
|
||||
[本地同网 RTMP], [800 ms], [800 ms],
|
||||
[公网 FRP RTMP], [1250 ms], [1250 ms],
|
||||
[Web HTTP-FLV], [2600 ms], [1650 ms],
|
||||
[网络波动积压], [6200 ms], [1700 ms]
|
||||
)
|
||||
]
|
||||
|
||||
*定量测试分析*:在网络正常时,使用 HTTP-FLV 配合 flv.js 播放能达到 2.6 秒左右的延迟。一旦注入网络丢包抖动,常规播放器的缓冲区积压会导致延迟飙升到 6.2 秒且无法复原;而在开启自适应追帧算法后,播放器在 1.5~5 秒延迟区间内触发 1.15 倍速播放,在 15 秒内就能将积压的延迟平滑收敛到 1.7 秒的稳定水平,效果十分显著。
|
||||
|
||||
== SQLite 并发写性能定量对比
|
||||
测试在一台双核云主机上,通过 locust 进行高并发写入模拟,主要是为了对比 SQLite 数据库在普通日志模式(Rollback Journal)与 WAL 模式下的写吞吐量(Transactions Per Second,TPS)。测试数据如表3所示:
|
||||
|
||||
#align(center)[
|
||||
#table(
|
||||
columns: (100pt, 80pt, 80pt),
|
||||
align: (center, center, center),
|
||||
stroke: 0.5pt,
|
||||
[*并发模拟客户端数*], [*Journal 模式*], [*WAL 优化模式*],
|
||||
[1 客户端], [85 tps], [125 tps],
|
||||
[5 并发客户端], [18 tps (锁锁重试)], [240 tps],
|
||||
[10 并发客户端], [9 tps (大量锁定错误)], [380 tps]
|
||||
)
|
||||
]
|
||||
|
||||
*定量测试分析*:在传统 Journal 模式下,SQLite 写入会直接锁住整个数据库文件,导致并发客户端不得不串行重试,甚至在大压力下报出锁定超时;而在应用了 `_journal_mode=WAL`、同步级别为 `NORMAL` 且临时文件存入内存后,SQLite 实现了完美的读写并发。10个客户端并发写入时,TPS 提高了 40 倍以上,且数据库无一例写入死锁和崩溃,验证了我们调优的优越性。
|
||||
|
||||
== FRP 网络隧道穿透时延
|
||||
测试从本地开发机 `100.115.45.3` 发起,至公网中转服务器 `stream.nudt.space`。我们统计了直连延迟与经过 FRP 隧道转换转发的时延对比,如表4所示:
|
||||
|
||||
#align(center)[
|
||||
#table(
|
||||
columns: (100pt, 80pt, 80pt),
|
||||
align: (center, center, center),
|
||||
stroke: 0.5pt,
|
||||
[*网络操作*], [*局域网直连*], [*FRP 公网穿透*],
|
||||
[ICMP Ping (RTT)], [1.2 ms], [24.5 ms],
|
||||
[TCP 三次握手时延], [3.5 ms], [52.8 ms],
|
||||
[HTTP 首次握手建立], [8.2 ms], [74.2 ms]
|
||||
)
|
||||
]
|
||||
|
||||
*定量测试分析*:通过内网穿透技术后,网络物理往返增加了大约 20~50ms。这表明,虽然 FRP 隧道由于经过了公网中转服务器引入了额外的网络开销,但由于流媒体对几十毫秒的网络延迟不敏感,该开销完全在拉流播放的可接受范围之内。
|
||||
|
||||
#pagebreak()
|
||||
|
||||
= 结论与体会
|
||||
|
||||
通过本课程设计,我们成功实现并部署了 Hightube 开源跨平台个人直播系统。项目全方位跑通了“RTMP 推流 -> 后端鉴权转码 -> HTTP-FLV 浏览器流分发 -> Web 追帧自适应播放 -> WebSocket 实时互动”的流媒体闭环网络通路。
|
||||
|
||||
在开发中,本人通过对 SQLite 读写锁及 WAL 模式的性能调优,深刻理解了并发存储的瓶颈根源;在 Web 适配中,设计的低延迟追帧速率算法成功解决了播放卡顿与累积延迟的问题,极大地提高了 Web 端的流畅度。同时,通过联合配置 FRP 反向隧道与 Caddy 动态反向代理,实现了极低成本且安全稳定的内网网络服务发布。本课程设计使我们掌握了计算机网络协议的应用、高并发编程和分布式部署的工程实践方法。
|
||||
|
||||
在未来的迭代中,我们计划进一步探究基于 WebRTC 协议的超低延迟(小于 500ms)连麦互动直播技术,并引入 GPU 硬件加速的 FFmpeg 转码服务以分担 CPU 的计算负载。
|
||||
|
||||
// #pagebreak()
|
||||
#bibliography("ref.bib", title: "参考文献", style: "gb-7714-2015-numeric")
|
||||
573
midpresentation.md
Normal file
573
midpresentation.md
Normal file
@@ -0,0 +1,573 @@
|
||||
---
|
||||
marp: true
|
||||
theme: default
|
||||
size: 16:9
|
||||
paginate: true
|
||||
header: "Hightube 开源跨平台个人直播平台"
|
||||
footer: "Highground-Soft | 中期汇报 (v1.0.0-Beta4.7) | 2026.05.05"
|
||||
style: |
|
||||
:root {
|
||||
--ink: #0f172a;
|
||||
--muted: #475569;
|
||||
--soft: #64748b;
|
||||
--line: rgba(15, 23, 42, 0.10);
|
||||
--blue: #2563eb;
|
||||
--cyan: #0891b2;
|
||||
--gold: #d97706;
|
||||
--paper: #fffdf8;
|
||||
--paper-blue: #f4f8ff;
|
||||
}
|
||||
|
||||
section {
|
||||
position: relative;
|
||||
padding: 56px 68px 48px;
|
||||
font-family: 'Noto Sans CJK SC', 'Microsoft YaHei', sans-serif;
|
||||
font-size: 28px;
|
||||
line-height: 1.35;
|
||||
color: var(--ink);
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(37, 99, 235, 0.16), transparent 28%),
|
||||
radial-gradient(circle at bottom left, rgba(8, 145, 178, 0.12), transparent 24%),
|
||||
linear-gradient(135deg, var(--paper) 0%, #f9fbff 45%, var(--paper-blue) 100%);
|
||||
}
|
||||
|
||||
section::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 22px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 26px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
header, footer { color: #64748b; font-size: 13px; letter-spacing: 0.04em; }
|
||||
|
||||
h1 { margin: 0 0 14px; font-family: 'Noto Serif CJK SC', 'STSong', serif; font-size: 52px; color: var(--ink); }
|
||||
h2 { margin: 0 0 14px; font-size: 30px; color: var(--blue); font-weight: 700; }
|
||||
h3 { margin: 0 0 8px; font-size: 22px; color: var(--ink); font-weight: 700; }
|
||||
p, ul, ol { margin-top: 0; }
|
||||
ul, ol { padding-left: 1.15em; }
|
||||
li { margin: 0 0 6px; }
|
||||
table { border-collapse: collapse; table-layout: fixed; }
|
||||
th, td { padding: 5px 8px; vertical-align: top; word-break: break-word; }
|
||||
|
||||
strong { color: var(--blue); font-weight: 800; }
|
||||
.accent { color: var(--blue); font-weight: 800; }
|
||||
.gold { color: var(--gold); font-weight: 700; }
|
||||
|
||||
section.cover {
|
||||
color: #e8eefc;
|
||||
background: linear-gradient(135deg, #07111f 0%, #0f1f3d 42%, #1d4ed8 100%);
|
||||
}
|
||||
section.cover h1, section.cover h2, section.cover strong { color: #ffffff; }
|
||||
section.cover .eyebrow { color: rgba(232, 238, 252, 0.78); font-size: 15px; font-weight: 700; letter-spacing: 0.16em; margin-bottom: 16px; }
|
||||
|
||||
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
|
||||
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
|
||||
.grid-5 { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; }
|
||||
|
||||
.card {
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||
border-radius: 20px;
|
||||
padding: 20px 22px;
|
||||
box-shadow: 0 16px 38px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
.card.dark { background: rgba(15, 23, 42, 0.05); }
|
||||
|
||||
.metric-box { text-align: center; padding: 15px; border-radius: 15px; background: rgba(37, 99, 235, 0.05); }
|
||||
.metric-value { font-size: 36px; font-weight: 800; color: var(--blue); display: block; }
|
||||
.metric-label { font-size: 14px; color: var(--soft); }
|
||||
|
||||
.tag { display: inline-block; padding: 4px 10px; border-radius: 8px; font-size: 14px; font-weight: 700; background: rgba(37, 99, 235, 0.1); color: var(--blue); margin-right: 5px; }
|
||||
|
||||
.eyebrow { color: var(--soft); font-size: 15px; font-weight: 700; letter-spacing: 0.16em; margin-bottom: 8px; }
|
||||
.closing { display: grid; place-items: center; height: 80%; text-align: center; }
|
||||
.closing h1 { font-size: 72px; font-weight: 900; margin-bottom: 8px; color: var(--blue); }
|
||||
.toc-item { display: flex; align-items: baseline; padding: 10px 0; border-bottom: 1px solid var(--line); }
|
||||
.toc-num { font-size: 32px; font-weight: 900; color: var(--blue); min-width: 56px; }
|
||||
.toc-text { font-size: 22px; }
|
||||
.toc-sub { font-size: 15px; color: var(--soft); }
|
||||
|
||||
section.compact {
|
||||
padding: 46px 58px 40px;
|
||||
font-size: 24px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
section.compact h1 { font-size: 42px; margin-bottom: 10px; }
|
||||
section.compact h3 { font-size: 19px; margin-bottom: 6px; }
|
||||
section.compact .card { padding: 14px 16px; border-radius: 16px; }
|
||||
section.compact .grid-2 { gap: 14px; }
|
||||
section.compact .grid-3 { gap: 12px; }
|
||||
section.compact li { margin-bottom: 4px; }
|
||||
section.compact .tag { font-size: 12px; padding: 3px 8px; }
|
||||
section.compact p[style],
|
||||
section.compact ul[style],
|
||||
section.compact ol[style],
|
||||
section.compact table[style] { font-size: 14px !important; }
|
||||
|
||||
section.toc-slide h1 { font-size: 46px; margin-bottom: 10px; }
|
||||
section.toc-slide .toc-item { padding: 7px 0; }
|
||||
section.toc-slide .toc-num { font-size: 27px; min-width: 48px; }
|
||||
section.toc-slide .toc-text { font-size: 19px; }
|
||||
section.toc-slide .toc-sub { font-size: 13px; }
|
||||
|
||||
section.arch-slide table { font-size: 14px !important; }
|
||||
section.arch-slide th,
|
||||
section.arch-slide td { padding: 4px 6px; }
|
||||
section.challenge-slide table { font-size: 16px; line-height: 1.26; }
|
||||
section.challenge-slide th,
|
||||
section.challenge-slide td { padding: 7px 9px; }
|
||||
section.challenge-slide h1 { margin-bottom: 8px; }
|
||||
section.plan-slide table { font-size: 14px !important; }
|
||||
section.plan-slide .card ul { margin-bottom: 0; }
|
||||
section.showcase-slide .card h3 { color: var(--blue); }
|
||||
---
|
||||
|
||||
<!-- _class: cover -->
|
||||
<!-- _paginate: false -->
|
||||
|
||||
<div class="eyebrow">Mid-term Progress Report</div>
|
||||
|
||||
# Hightube 中期汇报
|
||||
## 成果与展望:构建私有化直播生态
|
||||
|
||||
<p class="lead">已实现从<strong>推流鉴权、多码率转码</strong>到<strong>多端互动观看</strong>的全链路闭环。</p>
|
||||
|
||||
<div style="margin-top: 40px; font-size: 18px;">
|
||||
Highground-Soft 团队:程景愉、舒钰权、李俊友、张钊源<br />
|
||||
当前版本:<strong>v1.0.0-Beta4.7</strong> | 项目状态:核心功能 100% 完成
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: toc-slide -->
|
||||
|
||||
# 目录
|
||||
|
||||
<div style="display:grid; grid-template-columns: 1fr 1fr; gap: 12px 32px;">
|
||||
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">01</span>
|
||||
<div>
|
||||
<span class="toc-text">项目背景与目标</span>
|
||||
<div class="toc-sub">为什么做 Hightube?解决什么问题?</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">02</span>
|
||||
<div>
|
||||
<span class="toc-text">中期成果概览</span>
|
||||
<div class="toc-sub">五大功能域 + 研发数据统计</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">03</span>
|
||||
<div>
|
||||
<span class="toc-text">系统架构总览</span>
|
||||
<div class="toc-sub">整体拓扑与核心数据流</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">04</span>
|
||||
<div>
|
||||
<span class="toc-text">关键技术突破</span>
|
||||
<div class="toc-sub">流媒体转码、推流鉴权、弹幕系统</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">05</span>
|
||||
<div>
|
||||
<span class="toc-text">界面展示</span>
|
||||
<div class="toc-sub">跨平台客户端与响应式布局</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">06</span>
|
||||
<div>
|
||||
<span class="toc-text">团队协作与工程化</span>
|
||||
<div class="toc-sub">协作流程、成员贡献、代码规范</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">07</span>
|
||||
<div>
|
||||
<span class="toc-text">挑战、计划与演示</span>
|
||||
<div class="toc-sub">踩坑记录、后续规划、现场演示流程</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
# 项目背景与目标
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h3>行业痛点</h3>
|
||||
<ul style="font-size:17px;">
|
||||
<li><strong>平台依赖性强</strong>:主流直播平台抽成高、规则不可控,创作者缺乏自主权。</li>
|
||||
<li><strong>隐私风险</strong>:私有场景(教育、企业内训)数据流经公有云,存在合规隐患。</li>
|
||||
<li><strong>部署门槛高</strong>:RTMP + 转码 + Web 播放整套方案搭建复杂,个人开发者难以驾驭。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Hightube 的答案</h3>
|
||||
<ul style="font-size:17px;">
|
||||
<li><strong>单二进制部署</strong>:Go 编译为单一可执行文件,零依赖启动。</li>
|
||||
<li><strong>全链路闭环</strong>:从推流→转码→分发→播放→互动,一气呵成。</li>
|
||||
<li><strong>隐私可控</strong>:数据完全私有化,满足内网部署与合规需求。</li>
|
||||
<li><strong>开源生态</strong>:MIT 协议,社区可审计、可扩展。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="lead" style="margin-top:16px;">目标:打造一个<strong>开箱即用、性能优异、生态开放</strong>的私有化个人直播基座。</p>
|
||||
|
||||
---
|
||||
|
||||
# 中期成果概览:五大功能域
|
||||
|
||||
<div class="grid-5">
|
||||
<div class="card">
|
||||
<div class="tag">01</div>
|
||||
<h3>用户系统</h3>
|
||||
<p style="font-size:15px">RBAC 权限控制<br/>Bcrypt 密码哈希<br/>JWT 令牌鉴权<br/>会话管理</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="tag">02</div>
|
||||
<h3>直播核心</h3>
|
||||
<p style="font-size:15px">RTMP 推拉流<br/>FFmpeg 多码率转码<br/>定时缩略图抓取<br/>流状态监控</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="tag">03</div>
|
||||
<h3>实时互动</h3>
|
||||
<p style="font-size:15px">WebSocket Hub<br/>弹幕遮罩渲染<br/>消息历史持久化<br/>心跳断线重连</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="tag">04</div>
|
||||
<h3>管理后台</h3>
|
||||
<p style="font-size:15px">系统监控指标<br/>SSE 实时日志流<br/>用户 CRUD 管理<br/>直播间状态面板</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="tag">05</div>
|
||||
<h3>前端客户端</h3>
|
||||
<p style="font-size:15px">Flutter 3.x 跨平台<br/>Windows / Android / iOS / Web<br/>多清晰度无缝切换</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="lead" style="margin-top:16px;">累计交付 <strong>23</strong> 个功能模块,覆盖从注册到直播观看的<strong>完整业务生命周期</strong>。</p>
|
||||
|
||||
---
|
||||
|
||||
# 研发数据统计
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h3>代码资产分布</h3>
|
||||
<table style="width:100%; font-size:19px;">
|
||||
<tr><th>模块</th><th>语言</th><th>源文件</th><th>行数</th></tr>
|
||||
<tr><td>后端业务逻辑</td><td>Go</td><td>12</td><td>1,128</td></tr>
|
||||
<tr><td>流媒体服务</td><td>Go</td><td>6</td><td>613</td></tr>
|
||||
<tr><td>Flutter 跨平台</td><td>Dart</td><td>15</td><td>2,761</td></tr>
|
||||
<tr><td>管理后台 Web</td><td>HTML/JS</td><td>5</td><td>789</td></tr>
|
||||
<tr style="border-top: 2px solid var(--line);"><td><strong>总计</strong></td><td>-</td><td><strong>38</strong></td><td><strong>5,291</strong></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
<div class="grid-2" style="gap:10px;">
|
||||
<div class="metric-box">
|
||||
<span class="metric-value">18</span>
|
||||
<span class="metric-label">Go 后端源文件</span>
|
||||
</div>
|
||||
<div class="metric-box">
|
||||
<span class="metric-value">15</span>
|
||||
<span class="metric-label">Dart 前端源文件</span>
|
||||
</div>
|
||||
<div class="metric-box">
|
||||
<span class="metric-value">12s</span>
|
||||
<span class="metric-label">缩略图抓取间隔</span>
|
||||
</div>
|
||||
<div class="metric-box">
|
||||
<span class="metric-value">2</span>
|
||||
<span class="metric-label">转码清晰度档位</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-2" style="gap:10px; margin-top:10px;">
|
||||
<div class="metric-box">
|
||||
<span class="metric-value">4</span>
|
||||
<span class="metric-label">RESTful API 模块</span>
|
||||
</div>
|
||||
<div class="metric-box">
|
||||
<span class="metric-value">30+</span>
|
||||
<span class="metric-label">Git 贡献</span>
|
||||
</div>
|
||||
<div class="metric-box">
|
||||
<span class="metric-value">4</span>
|
||||
<span class="metric-label">支持平台数</span>
|
||||
</div>
|
||||
<div class="metric-box">
|
||||
<span class="metric-value">60fps</span>
|
||||
<span class="metric-label">弹幕渲染帧率</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: compact arch-slide -->
|
||||
|
||||
# 系统架构总览
|
||||
|
||||
<div style="text-align:center; margin-bottom:12px;">
|
||||
<p style="font-size:16px; color:var(--soft); margin:0;">
|
||||
OBS / 移动端推流 → <strong>RTMP Server</strong> → <strong>FFmpeg 转码管线</strong> → <strong>Go 业务后端</strong> → <strong>Flutter / Web 播放端</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h3>流媒体层</h3>
|
||||
<table style="width:100%; font-size:16px;">
|
||||
<tr><td>推流协议</td><td><strong>RTMP</strong> (端口 1935)</td></tr>
|
||||
<tr><td>转码引擎</td><td><strong>FFmpeg</strong> 子进程管线</td></tr>
|
||||
<tr><td>输出档位</td><td>1080p 源码 + 720p 转码</td></tr>
|
||||
<tr><td>Web 分发</td><td><strong>HTTP-FLV</strong> (flv.js 播放)</td></tr>
|
||||
<tr><td>缩略图</td><td>每 12s 自动截取一帧</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>业务层 (Go)</h3>
|
||||
<table style="width:100%; font-size:16px;">
|
||||
<tr><td>HTTP 框架</td><td><strong>Gin</strong> — 路由、中间件、CORS</td></tr>
|
||||
<tr><td>数据库</td><td><strong>SQLite + GORM</strong> ORM 抽象</td></tr>
|
||||
<tr><td>认证体系</td><td><strong>JWT</strong> 签发 + <strong>Bcrypt</strong> 哈希</td></tr>
|
||||
<tr><td>实时通信</td><td><strong>Gorilla WebSocket</strong> Hub 模式</td></tr>
|
||||
<tr><td>日志推送</td><td><strong>SSE</strong> 单向流至管理后台</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card dark" style="margin-top:12px; padding:10px 22px;">
|
||||
<p style="margin:0; font-size:15px;"><strong>部署方式</strong>:Go 编译为单一静态二进制,内嵌 RTMP 模块与前端静态资源,<strong>单文件、零依赖</strong>即可启动完整服务。</p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
# 关键技术突破:流媒体与多端适配
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h3>1. 动态转码管线 (FFmpeg)</h3>
|
||||
<ul style="font-size:17px;">
|
||||
<li><strong>自动触发</strong>:检测到推流到达即通过 <code>exec.CommandContext</code> 启动子进程。</li>
|
||||
<li><strong>Context 管理</strong>:利用 Go <code>context.Context</code> 树状派生,推流断开时自动 <code>cancel()</code> 级联终止,杜绝僵尸进程。</li>
|
||||
<li><strong>双档位输出</strong>:同时输出 Source (原始) 与 720p (转码) 两条流,互不阻塞。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>2. 多端播放兼容</h3>
|
||||
<ul style="font-size:17px;">
|
||||
<li><strong>原生端</strong>(Flutter Windows / Android):直连 <strong>RTMP</strong> 低延迟流,延迟约 1–2 秒。</li>
|
||||
<li><strong>Web 端</strong>:通过 <strong>HTTP-FLV</strong> 重新封装 RTMP 流,前端使用 <code>flv.js</code> 解码播放。</li>
|
||||
<li><strong>清晰度切换</strong>:播放器无刷新切换 Source ↔ 720p,体验平滑。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: compact -->
|
||||
|
||||
# 关键技术突破:推流鉴权与弹幕系统
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h3>3. 推流鉴权机制</h3>
|
||||
<ul style="font-size:16px;">
|
||||
<li>RTMP 推流请求携带 <code>stream_key</code>,在 <strong>URL 路径级</strong> 进行实时校验。</li>
|
||||
<li>数据库验证 <code>StreamKey</code> 有效性与归属,<strong>推流路径与播放路径分离</strong>。</li>
|
||||
<li>鉴权通过的推流方可触发转码管线;非法推流直接拒绝连接。</li>
|
||||
</ul>
|
||||
<div style="margin-top:6px;">
|
||||
<div class="tag">Bcrypt</div> <div class="tag">JWT</div> <div class="tag">路径级鉴权</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>4. 高性能弹幕引擎</h3>
|
||||
<ul style="font-size:16px;">
|
||||
<li>服务端基于 <strong>Gorilla WebSocket</strong> 实现 Hub 模式:每个直播间一个独立 Room,消息精准广播。</li>
|
||||
<li>客户端使用 <strong>Stack + GlobalKey</strong> 回收弹幕 Widget,避免频繁创建销毁。</li>
|
||||
<li>支持 <strong>5 行轨道</strong>并发滚动,<strong>心跳检测</strong>自动断线重连,稳定保持 60fps。</li>
|
||||
<li>弹幕层包裹 <code>IgnorePointer</code>,<strong>不拦截</strong>底层视频控制栏触摸事件。</li>
|
||||
</ul>
|
||||
<div style="margin-top:6px;">
|
||||
<div class="tag">WS Hub</div> <div class="tag">心跳检测</div> <div class="tag">60fps</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card dark" style="margin-top:12px; padding:10px 22px;">
|
||||
<p style="margin:0; font-size:15px;"><strong>管理工具</strong>:自研嵌入式 Web 控制台,通过 <strong>SSE</strong> 实时推送系统日志与运行指标,无需额外依赖。</p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: showcase-slide -->
|
||||
|
||||
# 界面展示:跨平台体验亮点
|
||||
|
||||
<div class="grid-3">
|
||||
<div class="card" style="text-align:center;">
|
||||
<h3>响应式网格</h3>
|
||||
<p style="font-size:16px;">直播间列表采用自适应网格布局,不同屏幕尺寸自动调整列数,保持视觉一致性。</p>
|
||||
</div>
|
||||
<div class="card" style="text-align:center;">
|
||||
<h3>动态缩略图</h3>
|
||||
<p style="font-size:16px;">后端每 12s 自动截取一帧封面,前端定时轮询,用户无需进入即可看到实时画面。</p>
|
||||
</div>
|
||||
<div class="card" style="text-align:center;">
|
||||
<h3>实时状态徽标</h3>
|
||||
<p style="font-size:16px;">"直播中" 角标实时同步房间活跃状态,观众一目了然。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-2" style="margin-top:16px;">
|
||||
<div class="card">
|
||||
<h3>Flutter 跨平台一致性</h3>
|
||||
<p style="font-size:16px;">基于 Flutter 3.x 自绘引擎,Windows / Android / iOS / Web 四端共用一套 UI 代码,平台间视觉偏差 < 5%。</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>播放器细节</h3>
|
||||
<p style="font-size:16px;">支持横竖屏自动切换、双击暂停、进度条拖拽、亮度/音量手势调节(移动端),清晰度菜单一键切换。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: compact -->
|
||||
|
||||
# 团队协作与工程化实践
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h3>标准协作流程</h3>
|
||||
<ul style="font-size:16px;">
|
||||
<li><strong>API 契约先行</strong>:前后端联调前先落定 RESTful 接口文档,消除理解歧义,显著降低集成摩擦。</li>
|
||||
<li><strong>Feature Branch 工作流</strong>:功能分支 → PR → Code Review → 合并,main 分支始终保持可发布状态。</li>
|
||||
<li><strong>自动化规范</strong>:后端强制 <code>gofmt</code> 格式化;前端配置严格 <code>analysis_options.yaml</code> — 拒绝未使用的 import / 变量。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>成员与分工</h3>
|
||||
<table style="width:100%; font-size:15px;">
|
||||
<tr><td><strong>程景愉</strong></td><td>后端核心开发、流媒体架构设计与 FFmpeg 转码管线</td></tr>
|
||||
<tr><td><strong>舒钰权</strong></td><td>RESTful API 开发、数据库建模、管理后台</td></tr>
|
||||
<tr><td><strong>李俊友</strong></td><td>Flutter 播放器核心、WebSocket 实时互动模块</td></tr>
|
||||
<tr><td><strong>张钊源</strong></td><td>跨平台 UI 适配、Android 端推流联调、项目文档</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="lead" style="margin-top:14px;">经过 <strong>30+</strong> 次审查与合并,形成稳定的迭代节奏。</p>
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: compact challenge-slide -->
|
||||
|
||||
# 遇到的挑战与解决方案
|
||||
|
||||
| 挑战 | 问题描述 | 解决方案 | 结果 |
|
||||
| :--- | :--- | :--- | :---: |
|
||||
| **RTMP 鉴权** | 默认 RTMP 无认证,任何人可推流 | 路径解析 `stream_key` + 数据库实时验证 | ✅ |
|
||||
| **FFmpeg 僵尸进程** | 推流断开后子进程未回收,内存泄漏 | `context.Context` 树状管理 + `defer cancel()` | ✅ |
|
||||
| **Web 端无法播放** | 浏览器不支持 RTMP 直连 | 增加 HTTP-FLV 封装层,前端接入 `flv.js` | ✅ |
|
||||
| **弹幕拦截触控** | 弹幕层遮挡视频控制栏 | `IgnorePointer` 穿透弹幕层,保留交互 | ✅ |
|
||||
| **跨平台 UI 偏差** | 不同平台渲染效果不一致 | 统一 Material Design 3 规范 + 响应式断点 | ✅ |
|
||||
| **SSE 连接泄漏** | 管理后台长时间挂机导致连接堆积 | 引入心跳超时机制 + 客户端自动重连 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: compact plan-slide -->
|
||||
|
||||
# 后续计划:迈向 AI 时代
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<div class="tag">核心创新</div>
|
||||
<h3>AI VTuber 互动引擎</h3>
|
||||
<ul style="font-size:16px;">
|
||||
<li>对接大语言模型 (LLM),直播间弹幕自动触发 AI 上下文回复。</li>
|
||||
<li>结合 <strong>TTS 语音合成</strong> 与 <strong>Live2D 虚拟形象</strong>,实现无人值守的智能助播。</li>
|
||||
<li>支持自定义角色人设与回复风格,丰富直播内容层。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="tag">体验优化</div>
|
||||
<h3>国际化与鲁棒性</h3>
|
||||
<ul style="font-size:16px;">
|
||||
<li>接入 Flutter ARB 国际化框架,首期中/英双语支持。</li>
|
||||
<li>移动端手势控制:亮度(左半屏)、音量(右半屏)滑动调节。</li>
|
||||
<li>后端补充核心路径单元测试,覆盖率目标 ≥ 70%。</li>
|
||||
<li>引入 Docker 容器化部署,降低运维门槛。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:12px;">
|
||||
<table style="width:100%; font-size:17px;">
|
||||
<tr><th style="width:25%;">里程碑</th><th style="width:30%;">时间</th><th>交付物</th></tr>
|
||||
<tr><td><strong>Beta 4.7</strong></td><td>2026.05 ✅</td><td>核心功能 100%,中期汇报</td></tr>
|
||||
<tr><td><strong>AI 模块初版</strong></td><td>2026.05 底</td><td>LLM 接口联调 + TTS 集成</td></tr>
|
||||
<tr><td><strong>v2.0 正式版</strong></td><td>2026.06 初</td><td>i18n、Docker、AI VTuber、单元测试</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: compact -->
|
||||
|
||||
# 演示流程预览
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h3>演示主线(约 3 分钟)</h3>
|
||||
<ol style="font-size:16px;">
|
||||
<li><strong>零配置启动</strong>:一条命令启动,服务立即就绪。</li>
|
||||
<li><strong>Flutter 端注册</strong>:创建账号,自动开通专属直播间。</li>
|
||||
<li><strong>OBS 推流</strong>:配置 RTMP 地址 + StreamKey,开启 1080p 推流。</li>
|
||||
<li><strong>多端观看</strong>:PC / 手机 / 浏览器同时拉流,对比延迟。</li>
|
||||
<li><strong>弹幕互动</strong>:移动端发送弹幕,各端实时同步显示。</li>
|
||||
<li><strong>管理后台</strong>:SSE 实时日志滚动,指标面板刷新。</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>演示重点</h3>
|
||||
<ul style="font-size:17px;">
|
||||
<li><strong>低延迟</strong>:RTMP 直连实现秒级观看延迟。</li>
|
||||
<li><strong>平滑切换</strong>:Source / 720p 清晰度无刷新切换。</li>
|
||||
<li><strong>实时同步</strong>:弹幕、日志、状态多端一致。</li>
|
||||
<li><strong>全链路</strong>:从推流到播放,一条命令、零外部依赖。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<div class="closing">
|
||||
<div>
|
||||
<p class="eyebrow">Thank You</p>
|
||||
<h1>感谢聆听</h1>
|
||||
<p style="font-size:24px; margin: 16px 0 8px;"><strong>Hightube</strong> — 私有化直播基座建设阶段性完成</p>
|
||||
<p style="font-size:18px; color: var(--soft); margin: 0 0 24px;">欢迎老师指正,Q & A</p>
|
||||
<p style="font-size:16px;">开源地址:<strong>https://git.nudt.space/Highground-Soft/Hightube</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
548
presentation.md
Normal file
548
presentation.md
Normal file
@@ -0,0 +1,548 @@
|
||||
---
|
||||
marp: true
|
||||
theme: default
|
||||
size: 16:9
|
||||
paginate: true
|
||||
header: "Hightube 开源跨平台个人直播平台最终汇报"
|
||||
footer: "Highground-Soft | 最终汇报 (v1.0.0-Release) | 2026.06.27"
|
||||
style: |
|
||||
:root {
|
||||
--ink: #0f172a;
|
||||
--muted: #475569;
|
||||
--soft: #64748b;
|
||||
--line: rgba(15, 23, 42, 0.10);
|
||||
--blue: #2563eb;
|
||||
--cyan: #0891b2;
|
||||
--gold: #d97706;
|
||||
--paper: #fffdf8;
|
||||
--paper-blue: #f4f8ff;
|
||||
}
|
||||
|
||||
section {
|
||||
position: relative;
|
||||
padding: 56px 68px 48px;
|
||||
font-family: 'Noto Sans CJK SC', 'Microsoft YaHei', sans-serif;
|
||||
font-size: 28px;
|
||||
line-height: 1.35;
|
||||
color: var(--ink);
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(37, 99, 235, 0.16), transparent 28%),
|
||||
radial-gradient(circle at bottom left, rgba(8, 145, 178, 0.12), transparent 24%),
|
||||
linear-gradient(135deg, var(--paper) 0%, #f9fbff 45%, var(--paper-blue) 100%);
|
||||
}
|
||||
|
||||
section::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 22px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 26px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
header, footer { color: #64748b; font-size: 13px; letter-spacing: 0.04em; }
|
||||
|
||||
h1 { margin: 0 0 14px; font-family: 'Noto Serif CJK SC', 'STSong', serif; font-size: 52px; color: var(--ink); }
|
||||
h2 { margin: 0 0 14px; font-size: 30px; color: var(--blue); font-weight: 700; }
|
||||
h3 { margin: 0 0 8px; font-size: 22px; color: var(--ink); font-weight: 700; }
|
||||
p, ul, ol { margin-top: 0; }
|
||||
ul, ol { padding-left: 1.15em; }
|
||||
li { margin: 0 0 6px; }
|
||||
table { border-collapse: collapse; table-layout: fixed; }
|
||||
th, td { padding: 5px 8px; vertical-align: top; word-break: break-word; }
|
||||
|
||||
strong { color: var(--blue); font-weight: 800; }
|
||||
.accent { color: var(--blue); font-weight: 800; }
|
||||
.gold { color: var(--gold); font-weight: 700; }
|
||||
|
||||
section.cover {
|
||||
color: #e8eefc;
|
||||
background: linear-gradient(135deg, #07111f 0%, #0f1f3d 42%, #1d4ed8 100%);
|
||||
}
|
||||
section.cover h1, section.cover h2, section.cover strong { color: #ffffff; }
|
||||
section.cover .eyebrow { color: rgba(232, 238, 252, 0.78); font-size: 15px; font-weight: 700; letter-spacing: 0.16em; margin-bottom: 16px; }
|
||||
|
||||
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
|
||||
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
|
||||
.grid-5 { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; }
|
||||
|
||||
.card {
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||
border-radius: 20px;
|
||||
padding: 20px 22px;
|
||||
box-shadow: 0 16px 38px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
.card.dark { background: rgba(15, 23, 42, 0.05); }
|
||||
|
||||
.metric-box { text-align: center; padding: 15px; border-radius: 15px; background: rgba(37, 99, 235, 0.05); }
|
||||
.metric-value { font-size: 36px; font-weight: 800; color: var(--blue); display: block; }
|
||||
.metric-label { font-size: 14px; color: var(--soft); }
|
||||
|
||||
.tag { display: inline-block; padding: 4px 10px; border-radius: 8px; font-size: 14px; font-weight: 700; background: rgba(37, 99, 235, 0.1); color: var(--blue); margin-right: 5px; }
|
||||
|
||||
.eyebrow { color: var(--soft); font-size: 15px; font-weight: 700; letter-spacing: 0.16em; margin-bottom: 8px; }
|
||||
.closing { display: grid; place-items: center; height: 80%; text-align: center; }
|
||||
.closing h1 { font-size: 72px; font-weight: 900; margin-bottom: 8px; color: var(--blue); }
|
||||
.toc-item { display: flex; align-items: baseline; padding: 10px 0; border-bottom: 1px solid var(--line); }
|
||||
.toc-num { font-size: 32px; font-weight: 900; color: var(--blue); min-width: 56px; }
|
||||
.toc-text { font-size: 22px; }
|
||||
.toc-sub { font-size: 15px; color: var(--soft); }
|
||||
|
||||
section.compact {
|
||||
padding: 46px 58px 40px;
|
||||
font-size: 24px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
section.compact h1 { font-size: 42px; margin-bottom: 10px; }
|
||||
section.compact h3 { font-size: 19px; margin-bottom: 6px; }
|
||||
section.compact .card { padding: 14px 16px; border-radius: 16px; }
|
||||
section.compact .grid-2 { gap: 14px; }
|
||||
section.compact .grid-3 { gap: 12px; }
|
||||
section.compact li { margin-bottom: 4px; }
|
||||
section.compact .tag { font-size: 12px; padding: 3px 8px; }
|
||||
section.compact p[style],
|
||||
section.compact ul[style],
|
||||
section.compact ol[style],
|
||||
section.compact table[style] { font-size: 14px !important; }
|
||||
|
||||
section.toc-slide h1 { font-size: 46px; margin-bottom: 10px; }
|
||||
section.toc-slide .toc-item { padding: 7px 0; }
|
||||
section.toc-slide .toc-num { font-size: 27px; min-width: 48px; }
|
||||
section.toc-slide .toc-text { font-size: 19px; }
|
||||
section.toc-slide .toc-sub { font-size: 13px; }
|
||||
|
||||
section.arch-slide table { font-size: 14px !important; }
|
||||
section.arch-slide th,
|
||||
section.arch-slide td { padding: 4px 6px; }
|
||||
section.challenge-slide table { font-size: 16px; line-height: 1.26; }
|
||||
section.challenge-slide th,
|
||||
section.challenge-slide td { padding: 7px 9px; }
|
||||
section.challenge-slide h1 { margin-bottom: 8px; }
|
||||
section.plan-slide table { font-size: 14px !important; }
|
||||
section.plan-slide .card ul { margin-bottom: 0; }
|
||||
section.showcase-slide .card h3 { color: var(--blue); }
|
||||
---
|
||||
|
||||
<!-- _class: cover -->
|
||||
<!-- _paginate: false -->
|
||||
|
||||
<div class="eyebrow">Final Project Presentation</div>
|
||||
|
||||
# Hightube 最终汇报
|
||||
## 成果与展望:构建私有化直播生态
|
||||
|
||||
<p class="lead">已实现从<strong>高并发流引擎、后端性能优化</strong>到<strong>多端自适应拉流与公网部署</strong>的完整闭环。</p>
|
||||
|
||||
<div style="margin-top: 40px; font-size: 18px;">
|
||||
Highground-Soft 团队:程景愉、舒钰权、李俊友、张钊源<br />
|
||||
当前版本:<strong>v1.0.0-Release</strong> | 项目状态:已完成公网发布与性能验证
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: toc-slide -->
|
||||
|
||||
# 目录
|
||||
|
||||
<div style="display:grid; grid-template-columns: 1fr 1fr; gap: 12px 32px;">
|
||||
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">01</span>
|
||||
<div>
|
||||
<span class="toc-text">项目背景与最终目标</span>
|
||||
<div class="toc-sub">为什么做 Hightube?解决了什么问题?</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">02</span>
|
||||
<div>
|
||||
<span class="toc-text">系统架构与技术选型</span>
|
||||
<div class="toc-sub">Go 语言后端与 Flutter 客户端</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">03</span>
|
||||
<div>
|
||||
<span class="toc-text">核心突破①:后端性能优化</span>
|
||||
<div class="toc-sub">SQLite 调优、内存缓存与 bufio 缓冲</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">04</span>
|
||||
<div>
|
||||
<span class="toc-text">核心突破②:网络与公网部署</span>
|
||||
<div class="toc-sub">FRP 内网穿透反向隧道与 Caddy 路由</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">05</span>
|
||||
<div>
|
||||
<span class="toc-text">系统界面展示与效果</span>
|
||||
<div class="toc-sub">公网平台、Web 端追帧及移动客户端</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">06</span>
|
||||
<div>
|
||||
<span class="toc-text">开发规范与协作</span>
|
||||
<div class="toc-sub">敏捷工程化、许可证合规与分工</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">07</span>
|
||||
<div>
|
||||
<span class="toc-text">挑战与未来规划</span>
|
||||
<div class="toc-sub">踩坑对策、性能定量分析与下一步</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
# 项目背景与目标
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h3>行业痛点</h3>
|
||||
<ul style="font-size:17px;">
|
||||
<li><strong>平台依赖性强</strong>:主流直播平台规则限制多,商业抽成高,自主管理性低。</li>
|
||||
<li><strong>隐私风险</strong>:内训、会议等私密场景,其敏感音视频流数据流经公有云,存在合规与泄漏隐患。</li>
|
||||
<li><strong>部署复杂</strong>:RTMP 接收、编码转码、Web 分发(HTTP-FLV/HLS)和播放器适配链条长,搭建门槛极高。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Hightube 的答案</h3>
|
||||
<ul style="font-size:17px;">
|
||||
<li><strong>单二进制一键部署</strong>:Go 编译为单一静态文件,自带嵌入式流媒体引擎,零额外软件依赖启动。</li>
|
||||
<li><strong>全链路自闭环</strong>:打通推流、鉴权、转码、分发、播放、弹幕全流程。</li>
|
||||
<li><strong>私有化与公网穿透</strong>:完美适配内网环境部署,利用反向隧道暴露服务,数据自主可控。</li>
|
||||
<li><strong>技术生态开放</strong>:遵循 MIT 协议开源。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="lead" style="margin-top:16px;">目标:打造一个<strong>开箱即用、极速轻量、安全可控</strong>的跨平台私有化直播基座。</p>
|
||||
|
||||
---
|
||||
|
||||
# 系统架构总览
|
||||
|
||||
<div style="text-align:center; margin-bottom:12px;">
|
||||
<p style="font-size:16px; color:var(--soft); margin:0;">
|
||||
OBS / 移动端推流 → <strong>RTMP Server</strong> → <strong>FFmpeg 转码管线</strong> → <strong>Go 业务后端</strong> → <strong>Flutter / Web 播放端</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h3>流媒体分发层</h3>
|
||||
<table style="width:100%; font-size:16px;">
|
||||
<tr><td>推流监听</td><td><strong>RTMP 协议</strong> (端口 1935)</td></tr>
|
||||
<tr><td>流鉴权</td><td>URL 路径 <code>stream_key</code> 数据库实时匹配</td></tr>
|
||||
<tr><td>转码引擎</td><td>FFmpeg 子进程异步管线 (原始/720p/480p)</td></tr>
|
||||
<tr><td>Web 分发</td><td><strong>HTTP-FLV</strong> (低延迟 chunked 长连接)</td></tr>
|
||||
<tr><td>封面捕获</td><td>定时抓取脚本,每 12s 生成一张房间缩略图</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>应用业务层 (Go)</h3>
|
||||
<table style="width:100%; font-size:16px;">
|
||||
<tr><td>Web 框架</td><td><strong>Gin</strong> — 路由控制、CORS 过滤、健康监测</td></tr>
|
||||
<tr><td>ORM / 数据库</td><td><strong>GORM + SQLite</strong> (嵌入式轻量级持久化)</td></tr>
|
||||
<tr><td>安全体系</td><td><strong>JWT</strong> 访问令牌 + <strong>Bcrypt</strong> 密码单向哈希</td></tr>
|
||||
<tr><td>互动接口</td><td><strong>Gorilla WebSocket</strong> Hub 模式弹幕服务器</td></tr>
|
||||
<tr><td>管理后台</td><td>自研 Web 后台,基于 <strong>SSE</strong> 异步日志滚动</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p style="margin-top: 14px; font-size: 16px; text-align: center;"><strong>部署核心</strong>:客户端(Flutter)与服务端(Go)完全分离,结合 Docker 容器化一键部署。</p>
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: compact -->
|
||||
|
||||
# 后端性能优化:三驾马车 (重点)
|
||||
|
||||
为了解决直播系统高并发读写下 SQLite 数据库锁死、Socket 写入开销高的问题,设计了三大底层优化:
|
||||
|
||||
<div class="grid-3">
|
||||
<div class="card">
|
||||
<h3>1. SQLite 并发调优</h3>
|
||||
<ul style="font-size:15px; padding-left:1.0em;">
|
||||
<li><strong>WAL 模式开启</strong>:<code>_journal_mode=WAL</code>,读写不互斥,大幅提高并发。</li>
|
||||
<li><strong>Pragmas 优化</strong>:<code>synchronous=NORMAL</code> 减半磁盘同步,<code>temp_store=MEMORY</code> 加快临时索引。</li>
|
||||
<li><strong>Busy Timeout 降级</strong>:5 秒内退避重试,杜绝 <code>locked</code> 报错。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>2. 内存线程安全缓存</h3>
|
||||
<ul style="font-size:15px; padding-left:1.0em;">
|
||||
<li><strong>本地 Map 缓存 (cache.go)</strong>:在内存中缓存用户信息和房间映射,避免反复查询 SQL。</li>
|
||||
<li><strong>读写锁 RWMutex</strong>:细粒度并发控制,数据写入时同步更新 Cache。</li>
|
||||
<li><strong>状态同步</strong>:房间开播直接更新 Cache 状态,大厅轮询零 SQL 负载。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>3. I/O 缓冲与分组 Flush</h3>
|
||||
<ul style="font-size:15px; padding-left:1.0em;">
|
||||
<li><strong>HTTP-FLV 写入合并</strong>:为 Gin 响应流封装 <code>bufio.NewWriterSize</code> (4KB)。</li>
|
||||
<li><strong>系统调用优化</strong>:合并多帧碎包,在一次 TCP Write 中发出,降低内核态上下文切换。</li>
|
||||
<li><strong>实时低延迟</strong>:每完成一帧写操作即强制 <code>Flush()</code>,延迟 < 1.5s。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="lead" style="margin-top:14px; text-align: center;">三项调优相辅相成,使单机并发能力在轻量化环境下呈<strong>指数级提升</strong>。</p>
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: compact -->
|
||||
|
||||
# 后端性能优化:定量测试成果 (重点)
|
||||
|
||||
通过并发压测与物理测量,后端调优取得了极其显著的性能定量数据:
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h3>SQLite 并发写吞吐量对比 (TPS)</h3>
|
||||
<table style="width:100%; font-size:16px;">
|
||||
<tr><th>并发客户端数</th><th>普通模式</th><th>WAL 调优模式</th><th>提升幅度</th></tr>
|
||||
<tr><td>1 客户端</td><td>85 tps</td><td>125 tps</td><td>+ 47.1%</td></tr>
|
||||
<tr><td>5 并发</td><td>18 tps</td><td>240 tps</td><td>+ 1,233.3%</td></tr>
|
||||
<tr><td>10 并发</td><td>9 tps (大量超时)</td><td><strong>380 tps</strong></td><td><strong>+ 4,122.2%</strong></td></tr>
|
||||
</table>
|
||||
<p style="font-size: 14px; margin-top: 8px; color: var(--soft);">* WAL 配合同步 NORMAL 与内存 temp_store,彻底消除了写锁定错误。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>流媒体播放延迟定量测试 (Latency)</h3>
|
||||
<table style="width:100%; font-size:16px;">
|
||||
<tr><th>测试链路</th><th>追帧关闭</th><th>追帧开启</th><th>分析结论</th></tr>
|
||||
<tr><td>同网直连 RTMP</td><td>800 ms</td><td>800 ms</td><td>局域网直连物理延迟极低</td></tr>
|
||||
<tr><td>公网 FRP RTMP</td><td>1250 ms</td><td>1250 ms</td><td>穿透引入约 450ms 网络延迟</td></tr>
|
||||
<tr><td>Web HTTP-FLV</td><td>2600 ms</td><td><strong>1650 ms</strong></td><td>追帧开启后降低 1 秒延迟</td></tr>
|
||||
<tr><td>网络抖动积压</td><td>6200 ms</td><td><strong>1700 ms</strong></td><td><strong>15秒内快速收敛至正常水平</strong></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: compact -->
|
||||
|
||||
# 公网部署方案:网络拓扑结构 (重点)
|
||||
|
||||
<div style="text-align: center; margin-bottom: 10px;">
|
||||
<img src="figures/topo.png" width="850" style="display:block; margin: 0 auto; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1);" />
|
||||
</div>
|
||||
|
||||
<p style="margin: 0; font-size: 16px; text-align: center; color: var(--muted);">利用 <strong>FRP 内网穿透</strong> 与 <strong>Caddy 反向代理</strong>,实现零公网 IP 本地服务器的公网安全暴露。</p>
|
||||
|
||||
---
|
||||
|
||||
# 公网部署方案:隧道穿透与反向代理 (重点)
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h3>FRP 反向隧道穿透</h3>
|
||||
<ul style="font-size:16px;">
|
||||
<li>公网服务器部署 <code>frps</code> (监听 7000),本地内网机 (<code>100.115.45.3</code>) 部署 <code>frpc</code> 建立长连接。</li>
|
||||
<li><strong>映射端口分配</strong>:
|
||||
- 本地 80 (Caddy 网页) → 公网 8080
|
||||
- 本地 8081 (Go API/FLV) → 公网 8081
|
||||
- 本地 1935 (Go RTMP) → 公网 1935
|
||||
</li>
|
||||
</ul>
|
||||
<div style="font-size: 14px; margin-top: 8px; color: var(--soft);">
|
||||
<strong>网络开销</strong>:隧道网络穿透增加约 20-50ms RTT 延迟,对流媒体无感知影响。
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Caddy 智能路由与证书管理</h3>
|
||||
<ul style="font-size:16px;">
|
||||
<li>公网端 Caddy 自动向 Let's Encrypt 申请 SSL 证书,支持 HTTPS 访问。</li>
|
||||
<li><strong>路径分发策略</strong>:
|
||||
- <code>/api/*</code> 和 <code>/live/*</code> → 转发至本地 8081
|
||||
- 默认其他访问 → 转发至本地 8080
|
||||
</li>
|
||||
<li><strong>本地端静态分发</strong>:本地 Caddy 部署在 80 端口,服务 Flutter 静态编译包。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
# 关键技术突破:Web 端播放器适配
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h3>1. 平台视图 iframe 混合渲染</h3>
|
||||
<ul style="font-size:17px;">
|
||||
<li>在 Flutter Web 侧通过 <code>HtmlElementView</code> 动态注册 iframe 播放页面 <code>flv_player.html</code>。</li>
|
||||
<li>将 <code>flv.min.js</code> 本地化部署到项目内,摆脱对外部 CDN 网络连通性的依赖。</li>
|
||||
<li>采用 HTML5 <code>postMessage</code> 通道,实现 Flutter 主框架到 iframe 播放页面的跨域音量/状态控制。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>2. 前端自适应速率追帧算法</h3>
|
||||
<ul style="font-size:17px;">
|
||||
<li>通过监听 HTML5 <code>video</code> 元素的 <code>timeupdate</code> 事件,实时计算当前缓冲区延迟。</li>
|
||||
<li><strong>延迟 > 5 秒</strong>:强制跳帧到 <code>video.currentTime = end - 1.0</code>。</li>
|
||||
<li><strong>延迟 1.5 ~ 5 秒</strong>:触发 1.15 倍播放速率微调追帧,实测在 15 秒内即可无感知地将累积延迟收敛到正常线,保证用户体验。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
# 界面展示
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<div style="text-align: center;">
|
||||
<img src="figures/hightube-official.png" width="420" style="border-radius: 8px; border: 1px solid var(--line);" />
|
||||
<span style="font-size: 13px; display: block; margin-top: 4px; color: var(--soft);">图1:Web 浏览器平台主页(活跃房间列表)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div style="text-align: center;">
|
||||
<img src="figures/hightube-screenshot.png" width="420" style="border-radius: 8px; border: 1px solid var(--line);" />
|
||||
<span style="font-size: 13px; display: block; margin-top: 4px; color: var(--soft);">图2:OBS 直播推送与 Web 端低延迟拉流及实时弹幕互动</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
# 界面展示 (客户端)
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<div style="text-align: center;">
|
||||
<img src="figures/hightube-adminpanel.png" width="400" style="border-radius: 8px; border: 1px solid var(--line);" />
|
||||
<span style="font-size: 13px; display: block; margin-top: 4px; color: var(--soft);">图3:自研后端管理控制台及系统健康指标</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div style="text-align: center;">
|
||||
<img src="figures/hightube-android.jpg" width="160" style="border-radius: 8px; border: 1px solid var(--line);" />
|
||||
<span style="font-size: 13px; display: block; margin-top: 4px; color: var(--soft);">图4:Android 手机客户端直播与聊天室界面</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: compact -->
|
||||
|
||||
# 开发规范与团队协作
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h3>工程化协作规范</h3>
|
||||
<ul style="font-size:16px;">
|
||||
<li><strong>契约先行</strong>:前后端联调前先规定好 RESTful JSON 接口及 API Schema,降低联调摩擦。</li>
|
||||
<li><strong>Git 分支工作流</strong>:采用功能分支开发,代码合并需通过 Merge Request 与同行评审(Code Review),保证 main 分支稳定可用。</li>
|
||||
<li><strong>自动规范化格式</strong>:Go 后端强制开启 <code>gofmt</code>;Flutter 引入 <code>analysis_options.yaml</code> 开启严苛的静态类型检查与垃圾变量扫描。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>成员分工表</h3>
|
||||
<table style="width:100%; font-size:14px;">
|
||||
<tr><th>成员</th><th>职责与主要交付成果</th></tr>
|
||||
<tr><td><strong>程景愉</strong></td><td>后端框架搭建、流媒体解封装、FFmpeg 实时转码通道</td></tr>
|
||||
<tr><td><strong>舒钰权</strong></td><td>RESTful 业务逻辑编写、JWT Token 中间件与用户系统</td></tr>
|
||||
<tr><td><strong>李俊友</strong></td><td>Flutter 原生播放器对接、WebSocket 实时聊天室、弹幕渲染</td></tr>
|
||||
<tr><td><strong>张钊源\n(本人)</strong></td><td>流媒体底座与推流鉴权、Web 端嵌入式 flv.js 播放器与自适应追帧、公网 FRP + Caddy 反向隧道部署</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: compact challenge-slide -->
|
||||
|
||||
# 遇到的挑战与解决方案 (最终版)
|
||||
|
||||
| 挑战 | 问题描述 | 最终解决方案 | 结果 |
|
||||
| :--- | :--- | :--- | :---: |
|
||||
| **推流安全** | RTMP 默认无校验,极易被恶意盗播 | 路径剥离 `stream_key` 并引入数据库实时鉴权拦截 | ✅ |
|
||||
| **僵尸转码进程** | 直播中断后后台 FFmpeg 挂载无法回收 | 树状 `context.Context` 传导 + 资源 `defer cancel()` | ✅ |
|
||||
| **Web 直连失败** | 浏览器沙箱环境不支持 RTMP 协议 | 引入 HTTP-FLV 分块传输流,Web 侧调用 `flv.js` 解码 | ✅ |
|
||||
| **缓存延迟累积** | 浏览器播放器积压缓冲,延迟越来越高 | 编写基于 `timeupdate` 的 1.15x 倍速追帧及强跳帧算法 | ✅ |
|
||||
| **轻量数据库锁死** | 大量弹幕持久化写入引发 SQLite 独占锁 | 开启 `WAL` 日志模式,调优 synchronous、temp_store | ✅ |
|
||||
| **公网资源发布** | 本地测试机处于内网且无公网 IP | 建立 **FRP** 穿透,配合公网 **Caddy** 证书自动化路由 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
# 总结与展望
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h3>项目总结</h3>
|
||||
<ul style="font-size:16px;">
|
||||
<li><strong>成果完整</strong>:顺利完成了“RTMP推送 → 后台鉴权与 FFmpeg 转码 → HTTP-FLV 封装 → Web / 移动端低延迟拉流播放”的完整流媒体闭环。</li>
|
||||
<li><strong>性能优异</strong>:Web 播放器具备自适应追帧,延迟控制在 1.6 秒左右;SQLite 调优后并发能力提升 40 倍。</li>
|
||||
<li><strong>规范开源</strong>:项目所有依赖项采用 MIT/BSD/Apache 许可证,Hightube 自身以 MIT 协议开源,无授权冲突风险。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>未来展望</h3>
|
||||
<ul style="font-size:16px;">
|
||||
<li><strong>低延迟升级</strong>:探索 WebRTC/Whip 协议直播推送与拉流,使端到端延迟控制在 500ms 以内。</li>
|
||||
<li><strong>智能转码加速</strong>:引入服务端 GPU/NVENC 硬件加速转码,替代目前的纯 CPU 编码。</li>
|
||||
<li><strong>多节点负载均衡</strong>:针对海量用户,设计基于 HTTP 重定向或 Caddy 动态代理的多拉流节点分发网络(CDN)。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: compact -->
|
||||
|
||||
# 演示流程预览 (最终汇报)
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h3>演示主线 (约 3 分钟)</h3>
|
||||
<ol style="font-size:16px;">
|
||||
<li><strong>零配置一键启动</strong>:命令行执行 `go run main.go`,服务瞬间部署完毕。</li>
|
||||
<li><strong>客户端注册登录</strong>:在手机/网页端注册,后台自动创建并开通专属直播间。</li>
|
||||
<li><strong>OBS 推流开播</strong>:拷贝专属 Stream Key,OBS 一键向公网启动直播推送。</li>
|
||||
<li><strong>多端拉流对比</strong>:使用手机 App、VLC、网页浏览器同时拉流,展示追帧算法下的同步延迟。</li>
|
||||
<li><strong>弹幕实时发送</strong>:观众互动发言,弹幕在各端屏幕上半区平滑滑过。</li>
|
||||
<li><strong>管理面板展示</strong>:打开后台监控,查看实时流量负载与 SSE 系统日志滚动。</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>核心演示亮点</h3>
|
||||
<ul style="font-size:17px;">
|
||||
<li><strong>低延迟保障</strong>:追帧激活,Web 端画面延迟低至 1.6s。</li>
|
||||
<li><strong>多档清晰度</strong>:画面无缝无刷新切换原始/720p/480p。</li>
|
||||
<li><strong>公网连通性</strong>:现场利用 <code>https://stream.nudt.space</code> 进行公网拉流。</li>
|
||||
<li><strong>高度集成</strong>:单可执行文件,无其他环境污染。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<div class="closing">
|
||||
<div>
|
||||
<p class="eyebrow">Thank You</p>
|
||||
<h1>感谢聆听</h1>
|
||||
<p style="font-size:24px; margin: 16px 0 8px;"><strong>Hightube</strong> — 开源跨平台个人直播系统项目最终汇报</p>
|
||||
<p style="font-size:18px; color: var(--soft); margin: 0 0 24px;">欢迎老师指正,Q & A</p>
|
||||
<p style="font-size:16px;">公网体验地址:<strong>https://stream.nudt.space</strong></p>
|
||||
<p style="font-size:16px;">开源地址:<strong>https://github.com/Highground-Soft/Hightube</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
BIN
presentation.pptx
Normal file
BIN
presentation.pptx
Normal file
Binary file not shown.
63
ref.bib
Normal file
63
ref.bib
Normal file
@@ -0,0 +1,63 @@
|
||||
@misc{go_doc,
|
||||
title = {Go Documentation},
|
||||
author = {{The Go Authors}},
|
||||
url = {https://go.dev/doc/},
|
||||
year = {2026},
|
||||
note = {访问日期:2026-06-27}
|
||||
}
|
||||
|
||||
@misc{flutter_doc,
|
||||
title = {Flutter Documentation},
|
||||
author = {{Flutter Team}},
|
||||
url = {https://docs.flutter.dev/},
|
||||
year = {2026},
|
||||
note = {访问日期:2026-06-27}
|
||||
}
|
||||
|
||||
@misc{ffmpeg_doc,
|
||||
title = {FFmpeg Documentation},
|
||||
author = {{FFmpeg Developers}},
|
||||
url = {https://ffmpeg.org/documentation.html},
|
||||
year = {2026},
|
||||
note = {访问日期:2026-06-27}
|
||||
}
|
||||
|
||||
@misc{joy4_github,
|
||||
title = {joy4: Go audio/video library and streaming server},
|
||||
author = {{joy4 Authors}},
|
||||
url = {https://github.com/nareix/joy4},
|
||||
year = {2021},
|
||||
note = {访问日期:2026-06-27}
|
||||
}
|
||||
|
||||
@misc{flvjs_github,
|
||||
title = {flv.js: HTML5 FLV Player},
|
||||
author = {{Bilibili Developers}},
|
||||
url = {https://github.com/bilibili/flv.js},
|
||||
year = {2024},
|
||||
note = {访问日期:2026-06-27}
|
||||
}
|
||||
|
||||
@misc{frp_github,
|
||||
title = {frp: A fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet},
|
||||
author = {{Fatedier}},
|
||||
url = {https://github.com/fatedier/frp},
|
||||
year = {2026},
|
||||
note = {访问日期:2026-06-27}
|
||||
}
|
||||
|
||||
@misc{caddy_doc,
|
||||
title = {Caddy Documentation},
|
||||
author = {{Caddy Server Authors}},
|
||||
url = {https://caddyserver.com/docs/},
|
||||
year = {2026},
|
||||
note = {访问日期:2026-06-27}
|
||||
}
|
||||
|
||||
@misc{sqlite_wal,
|
||||
title = {Write-Ahead Logging (WAL) in SQLite},
|
||||
author = {{SQLite Development Team}},
|
||||
url = {https://www.sqlite.org/wal.html},
|
||||
year = {2026},
|
||||
note = {访问日期:2026-06-27}
|
||||
}
|
||||
23
ref.yml
Normal file
23
ref.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
haweiS600ESeriesSwitchesCase1:
|
||||
type: Web
|
||||
title: S600-E系列交换机 典型配置案例 - 华为
|
||||
author: 华为
|
||||
url:
|
||||
value: https://support.huawei.com/enterprise/zh/doc/EDOC1000141427/f36b09a2
|
||||
date: 2024-11-07
|
||||
|
||||
haweiS600ESeriesSwitchesCase2:
|
||||
type: Web
|
||||
title: S600-E系列交换机 典型配置案例 - 华为
|
||||
author: 华为
|
||||
url:
|
||||
value: https://support.huawei.com/enterprise/zh/doc/EDOC1000141427/82710693
|
||||
date: 2024-11-07
|
||||
|
||||
haweiS600ESeriesSwitchesCase3:
|
||||
type: Web
|
||||
title: 配置端口安全示例 - S600-E系列交换机 典型配置案例 - 华为
|
||||
author: 华为
|
||||
url:
|
||||
value: https://support.huawei.com/enterprise/zh/doc/EDOC1000141427/6b53bfef
|
||||
date: 2024-11-07
|
||||
Reference in New Issue
Block a user