1. 协议简介 1.1 RTMP RTMP是Real Time Messaging Protocol(实时消息传输协议)的首字母缩写。该协议基于TCP
,是一个协议族,包括RTMP基本协议RTMPT/RTMPS/RTMPE等多种变种。 RTMP是一种设计用来进行实时数据通信的网络协议,主要用来在Flash/AIR平台和支持RTMP协议的流媒体/交互服务器之间进行音视频和数据通信。 支持该协议的软件包括Adobe Media Server/Ultrant Media Server/red5等。RTMP与HTTP一样,都属于TCP/IP四层模型的应用层。
应用层协议,依靠TCP保证可靠传输。
默认端口:1953
,可能被防火墙屏蔽。
在流媒体/交互服务器之间进行音视频和数据通信
。
1.2 HLS HLS(HTTP Live Streaming)是一个由苹果公司提出的基于HTTP 的流媒体网络传输协议。 所以在 Apple 的全系列产品包括 iPhone、 iPad、safari 都不需要安装任何插件就可以原生支持播放 HLS, 现在 Android 也加入了对 HLS 的支持。
工作原理 : 把整个流分成一个个小的基于HTTP的TS文件来下载,每次只下载一些。 当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。
HLS请求基本的HTTP报文,与实时传输协议(RTP
)不同HLS可以穿过任何允许HTTP数据通过的防火墙或者代理服务器。它也很容易使用内容分发网络来传输媒体流。
HLS缺陷:
实时性差,延迟高。延迟基本在 10s+ 以上
ts 切片较小,会造成海量小文件,对存储和缓存有影响
HLS规范:
1.3 对比RTMP,HLS, HTTP-FLV 在开始之前,我们先要明确一个问题,根据应用场景不同,流媒体协议分为:
RTMP
>> 双端HLS
>> 拉流端HTTP-FLV
>> 拉流端。
RTMP
HTTP-FLV
HLS
传输协议
TCP
HTTP
HTTP
视频封装格式
flv
flv
ts
延时
1-3秒
1-3秒
5-20秒
Web支持
H5需要使用插件
H5需要使用插件
支持H5
数据
连续流
连续流
切片文件
ps: H5 是 HTML5
1. RTMP & HTTP-FLV
这两个协议实际上传输的数据是一样的,数据都是flv文件的tag
。
RTMP:实时播放服务器的 FLV 文件或服务器转发的FLV数据,本地无 FLV 缓存文件
,FLV保密性好
。
HTTP-FLV:将 FLV 下载到本地
再播放,FLV保密性不好
。
2. HLS & RTMP
RTMP:采用1935
端口,而非HTTP80
端口,在某些网络环境下可能被屏蔽
。
RTMP:是一种有状态协议
,需要为每一个播放视频流的客户端维护状态,服务器平滑扩展难度大
。
HLS:基于无状态HTTP协议
,客户端只需要按顺序使用下载的TS文件就可,负载均衡如同普通的HTTP文件服务器负载均衡一样。
3. HTTP-FLV
HTTP-FLV 结合了 RTMP 和 HLS 的优点,易用(HTTP协议)低延时(flv)
4. 为什么 RTPM 比 HLS 快
2. 封装简介 2.1 FLV 1. FLV的封装格式
FLV
(Flash Video),Adobe公司设计开发的一种流行的流媒体格式,由于其视频文件体积轻巧、封装简单等特点,使其很适合在互联网上进行应用。除了播放视频,在直播时也可以使用。采用FLV格式封装的文件后缀为.flv
,格式如下(FLV = FLV Header + Body
)
2. FLV Header
Header 部分记录了FLV的类型、版本、流信息、Header 长度等。 一般整个Header占用9
个字节,大于9个字节则表示头部信息在这基础之上还存在扩展数据。 FLV Header 的信息排布如下所示:
3. FLV Body
Body 是由一个个Tag
组成的,每个Tag下面有一块4
个字节的空间,用来记录这个Tag 的长度。 这个后置的PreviousTagSize
用于逆向
读取处理,表示的是前面的Tag的大小。 FLV Body 的信息排布如下:
4. FLV Tag
每个Tag 也是由两部分组成的:Tag Header
和 Tag Data
。 Tag Header 存放了当前Tag的类型,数据长度、时间戳、时间戳扩展、StreamsID等信息,然后再接着数据区Tag Data。 Tag的排布如下:
5. Tag Data
Tag Data分成 Audio
,Video
,Script
三种。
5.1 Audio Tag Data
音频的Tag Data又分为 AudioTagHeader
和 Data
数据区,其排布结构如下图所示:
5.2 Video Tag Data
Video Tag 由一个字节的VideoTagHeader
和 Video数据区
部分组成
Video数据区部分格式不确定。对于AVC (H.264)
编码部分,Video数据区排布如下:
5.3 Script Tag Data
2.2 TS
TS
(Transport Stream,传输流
),一种常见的视频封装格式,是基于MPEG-2
的封装格式(所以也叫MPEG-TS
),后缀为.ts
。
1. TS的分层结构
TS文件分为三层,如下所示(可以倒序看更好理解):
TS层
(Transport Stream) 在PES层基础上加入了数据流识别信息和传输信息。
PES层
(Packet Elemental Stream) 在ES层基础上加入时间戳(PTS/DTS)等信息。
ES层
(Elementary Stream) 压缩编码后的音视频数据。
2. TS层
ts包大小固定为188字节
,ts层分为三个部分:ts header
、adaptation field
、payload
。
◆ ts Header + adaptation field 格式如下:
2.1 TS Header
pid
决定了负载内容
的类型,主要包括:PAT表,PMT表,视频流,音频流。常用的PID值:
表
PAT
CAT
TSDT
EIT,ST
RST,ST
TDT,TOT,ST
PID
0x0000
0x0001
0x0002
0x0012
0x0013
0x0014
2.2 调整字段
2.3 PAT表结构(指明PMT表的PID值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 typedef struct TS_PAT { unsigned table_id :8 unsigned section_syntax_indicator :1 unsigned zero :1 unsigned reserved_1 :2 unsigned section_length :12 unsigned transport_stream_id :16 unsigned reserved_2 :2 unsigned version_number :5 unsigned current_next_indicator :1 unsigned section_number :8 unsigned last_section_number :8 for (int i=0 ;i<N;i++){ unsigned Program_number :16 unsigned Reversed_3 :3 if (Program_number == 0 ) Network_id :13 else Program_MAP_PID :13 } unsigned CRC_32 :32 }TS_PAT
2.4 PMT表(指明音视频流的PID值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 typedef struct TS_program_map_section { unsigned Table_id :8 unsigned Section_syntax_indicator :1 unsigned '0' :1 unsigned Reserved :2 unsigned Section_length :12 unsigned Program_number :16 unsigned Reserved :2 unsigned Version_number :5 unsigned Current_next_indicator :1 unsigned Section_number :8 unsigned Last_section_number :8 unsigned Reserved :3 unsigned PCR_PID :13 unsigned Reserved :4 unsigned Program_info_length :12 for (int i = 0 ; i < N; i++) Descriptr () for (int i = 0 ; i < N; i++){ unsigned Stream_type :8 unsigned Reserved :3 unsigned Elementary_PID :13 unsigned Reserved :4 unsigned ES_info_length :12 for (int j = 0 ; j < N2; j++) Descriptr () } unsigned CRC_32 :32 }
2.5 补充
3. PES 层
4. ES 层
3. HLS_Server(demo) 3.1 ffmpeg 命令生成m3u8切片
第一种方式
1 ffmpeg -i input.mp4 -c:v libx264 -c:a copy -f hls -hls_time 10 -hls_list_size 0 -hls_start_number 0 input/index.m3u8
注解 :
-c:v
codec(编解码器): video(视频)。 == -vcodec
-c:a
audio(音频) == -acodec
-f
(format) 以hls
格式
-hls_time n
: 设置每片的长度,默认值为2,单位为秒
-hls_list_size n
:设置播放列表保存的最多条目,设置为0会保存有所片信息,默认值为5
-hls_start_number n
:设置播放列表中sequence number的值为number,默认值为0
-hls_wrap n
:设置多少片之后开始覆盖,如果设置为0则不会覆盖,默认值为0 这个选项能够避免在磁盘上存储过多的片,而且能够限制写入磁盘的最多的片的数量
input 文件需要自己新建
第二种方式
1 2 ffmpeg -i input.mp4 -fflags flush_packets -max_delay 2 -flags -global_header -hls_time 5 -hls_list_size 0 -vcodec libx264 -acodec aac -r 30 -g 60 index.m3u8
注解:
-fflags
设置输入/输出文件或流的标志(flags) 用来激活或修改 FFmpeg 内部的标志,以控制特定的行为
-flush_packets
导致 FFmpeg 立即输出已经在内存缓冲中的数据包(packets) 而不是等待缓冲区满或其他条件触发输出。
-max_delay 2
:设置最大延迟时间为 2 秒
-flags -global_header
:这是一个设置视频编码器标志的选项。在这个情况下,-flags
用于设置特定的编码器标志。-global_header
标志指示在视频流的第一个关键帧(I帧)中包含全局头信息(global headers),这对于某些视频流的处理和解码非常重要。
-vcodec libx264
:指定视频编码器为 libx264,用于对视频进行 H.264 编码。
-acodec aac
:指定音频编码器为 AAC,用于对音频进行 AAC 编码。
3.2 源代码
Log.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #pragma once #pragma warning (disable:4996) #include <chrono> #include <string> static std::string GetCur_FormatTime (const char * format = "%Y-%m-%d %H:%M:%S" ) { time_t t = time (nullptr ); char str[64 ]; strftime (str, sizeof (str), format, localtime (&t)); std::string time; return time; } #define LOGI(format, ...) \ fprintf(stderr, "[INFO]%s, [%s:%d %s()]" format "\n" , GetCur_FormatTime().data(), __FILE__, __LINE__,__func__, ##__VA_ARGS__) #define LOGE(format, ...) \ fprintf(stderr, "[INFO]%s, [%s:%d %s()]" format "\n" , GetCur_FormatTime().data(), __FILE__, __LINE__, __func__, ##__VA_ARGS__)
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <WinSock2.h> #include "Log.h" #include "connection.h" #pragma comment(lib, "ws2_32.lib" ) int main () { WSADATA wsadata; if (WSAStartup (MAKEWORD (2 , 2 ), &wsadata) != 0 ) { LOGE ("WSAStartup error" ); return -1 ; } int port = 8080 ; SOCKET serverFd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); SOCKADDR_IN server_addr; server_addr.sin_family = AF_INET; server_addr.sin_addr.S_un.S_addr = htonl (INADDR_ANY); server_addr.sin_port = htons (port); if (bind (serverFd, (SOCKADDR*)&server_addr, sizeof (SOCKADDR)) == SOCKET_ERROR) { LOGE ("socket bind error" ); return -1 ; } if (listen (serverFd, SOMAXCONN) < 0 ) { LOGE ("socket bind error" ); return -1 ; } while (true ) { LOGI ("wait new connect" ); int len = sizeof (SOCKADDR); SOCKADDR_IN accept_addr; int clientFd = accept (serverFd, (SOCKADDR*)&accept_addr, &len); if (clientFd == SOCKET_ERROR) { LOGE ("accept connection error" ); break ; } LOGI ("new connect clientFd = %d" , clientFd); Connection conn (clientFd) ; conn.start (); } closesocket (serverFd); return 0 ; }
connection.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #pragma once #include "Log.h" class Connection { public : Connection (int clientFd); ~Connection (); int start () ; private : int mclientFd; };
connection.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 #include "connection.h" #include <WinSock2.h> #include <iostream> Connection::Connection (int clientFd) :mclientFd (clientFd) {} Connection::~Connection () { closesocket (mclientFd); } char buf[1500000 ];int Connection::start () { char bufRecv[2000 ] = { 0 }; int bufRecvSize = recv (mclientFd, bufRecv, sizeof (bufRecv), 0 ); char uri[100 ] = { 0 }; const char * sep = "\n" ; char * line = strtok (bufRecv, sep); while (line) { if (strstr (line, "GET" )) { if (sscanf (line, "GET %s HTTP/1.1\r\n" , &uri) != 1 ) LOGE ("parse uri error" ); } line = strtok (nullptr , sep); } printf ("uri = %s\n" , uri); std::string filename = "D:/ffmpeg/learn/m3u8/test" + std::string (uri); FILE* fp = fopen (filename.data (), "rb" ); if (!fp) { LOGE ("fopen error" ); return -1 ; } int buflen = fread (buf, 1 , sizeof (buf), fp); LOGI ("buflen = %d" , buflen); if (fp) fclose (fp); char http_headers[2000 ]; if (strcmp ("/index.m3u8" , uri) == 0 ) { sprintf (http_headers, "HTTP/1.1 200 OK\r\n" "Access-Control-Allow-Origin: * \r\n" "Connection: keep-alive\r\n" "Content-Length: %d\r\n" "Content-Type: application/vnd.apple.mpegurl; charset=utf-8\r\n" "Keep-Alive: timeout=30, max=100\r\n" "Server: hlsServer\r\n" "\r\n" , buflen); } else { sprintf (http_headers, "HTTP/1.1 200 OK\r\n" "Access-Control-Allow-Origin: * \r\n" "Connection: close\r\n" "Content-Length: %d\r\n" "Content-Type: video/mp2t; charset=utf-8\r\n" "Keep-Alive: timeout=30, max=100\r\n" "Server: hlsServer\r\n" "\r\n" , buflen); } send (mclientFd, http_headers, strlen (http_headers), 0 ); send (mclientFd, buf, buflen, 0 ); Sleep (10 ); return 0 ; }
抓包分析
4. Http-Flv Server(demo) 4.1 ffmpeg命令 mp4转flv 1 ffmpeg.exe -i source.mp4 -c:v libx264 -crf 24 destination.flv
4.2 源码
Log.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #pragma once #pragma warning (disable : 4996) #include <time.h> #include <string> static std::string Get_CurTime (const char * fmt = "%Y-%m-%d %H:%M:%S" ) { time_t t = time (nullptr ); char str[64 ]; strftime (str, sizeof (str), fmt, localtime (&t)); std::string time (str) ; return time; } #define LOGI(format, ...) \ fprintf(stderr, "[INFO]%s [%s:%d %s()]" format"\n" , Get_CurTime().data(), __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define LOGE(format, ...) \ fprintf(stderr, "[ERROR]%s [%s:%d %s()]" format"\n" , Get_CurTime().data(), __FILE__, __LINE__, __func__, ##__VA_ARGS__)
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 #include "Log.h" #include <WinSock2.h> #include <WS2tcpip.h> #pragma comment(lib, "ws2_32.lib" ) int main () { int port = 8080 ; const char * filename = "D:/ffmpeg/learn/test.flv" ; WSADATA wsaData; if (WSAStartup (MAKEWORD (2 , 2 ), &wsaData) != 0 ) { LOGE ("WSAStartup error" ); return -1 ; } SOCKADDR_IN sockAddr; sockAddr.sin_family = AF_INET; sockAddr.sin_addr.S_un.S_addr = htonl (INADDR_ANY); sockAddr.sin_port = htons (port); SOCKET serverFd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); if (bind (serverFd, (SOCKADDR*)&sockAddr, sizeof (SOCKADDR)) == SOCKET_ERROR) { LOGE ("socket bind error" ); return -1 ; } if (listen (serverFd, SOMAXCONN) < 0 ) { LOGE ("socket listen error" ); return -1 ; } const char http_headers[] = \ "HTTP/1.1 200 OK\r\n" \ "Access-Control-Allow-Origin: * \r\n" \ "Content-Type: video/x-flv\r\n" \ "Content-Length: -1\r\n" \ "Connection: Keep-Alive\r\n" \ "Expires: -1\r\n" \ "Pragma: no-cache\r\n" \ "\r\n" ; int http_headersLen = strlen (http_headers); while (true ) { LOGI ("wait connection" ); int len = sizeof (SOCKADDR); SOCKADDR_IN accept_addr; int clientFd = accept (serverFd, (SOCKADDR*)&accept_addr, &len); if (clientFd == SOCKET_ERROR) { LOGE ("accept connection error" ); break ; } LOGI ("new connetion clientFd = %d" , clientFd); FILE* fp = fopen (filename, "rb" ); if (!fp) return -1 ; unsigned char buf[5000 ]; char bufRecv[2000 ] = { 0 }; int times = 0 ; while (true ) { ++times; if (times == 1 ) { int bufRecvSize = recv (clientFd, bufRecv, 2000 , 0 ); LOGI ("bufRecvSize = %d, bufRecv =\n%s" , bufRecvSize, bufRecv); send (clientFd, http_headers, http_headersLen, 0 ); } else { Sleep (10 ); int bufLen = fread (buf, 1 , sizeof (buf), fp); int ret = send (clientFd, (char *)buf, bufLen, 0 ); if (ret <= 0 ) break ; } } if (fp) fclose (fp); closesocket (clientFd); LOGI ("close connect clientFd = %d" , clientFd); } closesocket (serverFd); return 0 ; }
抓包分析
1 ffplay -i http://127.0.0.1:8080
与运行结果一致
5. RTMPServer 5.1 RTMP协议推流流程简介 RTMP协议规范详细可看rtmp_specification_1.0.pdf
中文版
TCP三次握手后, RTMP协议的流媒体推流需经过以下步骤:
握手 RTMP连接都是以握手作为开始的 服务端与客户端需要通过3次交换报文完成握手,RTMP是由三个静态大小的块,而非可变的块 客户端与服务器发送相同的三个chunk,客户端发送c0,c1,c2,服务端发送s0,s1,s2
客户端发送 C0,C1 块,握手开始 服务端在发送 S0 和 S1 之前必须等待接收 C0, 也可等待 C1, 发送 S2 之前必须等待接收 C1 客户端在发送 C2 之前客户端必须等待接收 S1 客户端在发送任何数据之前客户端必须等待接收 S2 服务端在发送任何数据之前必须等待接收 C2.
RTMP建立连接
客户端发送命令消息
中的连接(connect
)到服务器,请求与一个服务应用实例建立连接。
服务器接收到连接命令消息后,发送确认窗口大小(Window Acknowledgement Size
)协议消息到客户端,同时连接到连接命令中提到的应用程序
, 并且发送设置带宽协议(Set Peer Bandwidth
)消息到客户端。
客户端处理设置带宽协议(Set Peer Bandwidth
)消息后,发送确认窗口大小(Window Acknowledgement Size
)协议消息到服务器端。
服务器发送用户控制消息
中的流开始(Stream Begin
)消息到客户端, 并发送命令消息
中的结果(_result
),通知客户端连接的状态。
RTMP建流
客户端在收到来自服务器的建流命令(createstream
)的成功结果(_result
)后发送play
命令。 服务器在接收到play
命令后,发送一个来设置块大小(SetChunkSize
)消息。 再发送另一个用户控制消息,指定事件“流记录”(StreamIsRecorded
)和流ID
, 这个消息的头2字节携带事件类型,最后4字节携带流ID。 再发送另一个用户控制消息,指定事件“流开始”(StreamBegin
)。向客户端指示流的开始。 如果客户端发送的播放(play
)命令成功,服务器发送命令消息(onStatus
),NeStream.Play.Start & NeStream.Play.Reset
。 只有当客户端发送的play
命令设置了reset
标志时,服务器才会发送NeStream.Play.Reset
。 如果没有找到要播放的流,服务器将发送onStatus
消息NeStream.Play.StreamNotFound
。 之后,客户端播放服务器发送的音频和视频数据
通过抓包理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Client : connect(‘livetv’) # Server : Window Acknowledgement Size 5000000 Set Peer Bandwidth 5000000,Dynamic Set Chunk Size 4096 _result(‘NetConnection.Connect.Success’) # Client : Window Acknowledgement Size 5000000 createStream() # Server : _result() # Client : getStreamLength() play(‘hunantv’) Set Buffer Length 1,3000ms # Server : Stream Begin 1 onStatus(‘NetStream.Play.Start’) RtmpSampleAccess() onMetaData() # Server : Video Data Audio Data …
5.2 源码 RTMPServer
6. 浏览器播放HLS和HTTP-FLV流 6.1 zlm 命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // rtsp推流(文件推流) ffmpeg -re -i test.mp4 -rtsp_transport tcp -c copy -f rtsp rtsp://127.0.0.1:554/live/test // rtsp推流(文件循环推流) ffmpeg -re -stream_loop -1 -i test.mp4 -rtsp_transport tcp -c copy -f rtsp rtsp://127.0.0.1:554/live/test // rtmp推流(文件推流) ffmpeg -re -i test.mp4 -vcodec h264_nvenc -acodec aac -f flv rtmp://192.168.1.3:1935/live/test // rtmp推流(文件循环推流) ffmpeg -re -stream_loop -1 -i test.mp4 -vcodec h264 -acodec aac -f flv rtmp://127.0.0.1:1935/live/test // ZLMediaKit支持多种流媒体协议的转换,协议转换后的播放地址 //rtsp播放 rtsp://127.0.0.1:554/live/test //rtmp播放 rtmp://127.0.0.1:1935/live/test //hls播放 http://127.0.0.1:80/live/test/hls.m3u8 //http-flv播放 http://127.0.0.1:80/live/test.live.flv //http-ts播放 http://127.0.0.1:80/live/test.live.ts
6.2 网页播放hls
视频需要先推流给zlm(服务端)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <meta http-equiv ="X-UA-Compatible" content ="ie=edge" > <title > HlsPlayer</title > <link href ="https://vjs.zencdn.net/7.20.3/video-js.css" rel ="stylesheet" /> </head > <body style ="text-align:center;" > <h1 > 网页端播放 HLS(<code > .m3u8</code > ) 视频</h1 > <p > 借助 video.js 和 videojs-contrib-hls</p > <p > 由于 videojs-contrib-hls 需要通过 XHR 来获取解析 m3u8 文件, 因此会遭遇跨域问题, 请设置浏览器运行跨域</p > <video id ="video" class ="video-js vjs-default-skin vjs-big-play-centered" controls style ="margin: auto" width ="640" height ="268" > <<source src ="http://127.0.0.1:80/live/test/hls.m3u8" type ="application/x-mpegURL" > </video > <script src ="https://vjs.zencdn.net/7.20.3/video.min.js" > </script > <script src ="https://unpkg.com/videojs-contrib-hls/dist/videojs-contrib-hls.js" > </script > <script > const player = videojs ('video' ); player.play (); </script > </body > </html >
6.3 网页播放http-flv 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > httpflvplayer</title > <style > #flv-player { width : 100% ; height : 100% ; max-height : 520px ; border-radius : 5px ; border : 1px solid #f0f0f0 ; } #flv-player :focus { outline : -webkit-focus-ring-color auto 0px ; } </style > </head > <body > <div > <video id ="flv-player" controls autoplay > Your browser is too old which doesn't support HTML5 video. </video > <div > <button id ="play" onclick ="play()" > 播放</button > <button id ="stop" onclick ="stop()" > 停止</button > </div > </div > </body > <script src ="flv/flv.min.js" > </script > <script > let eleFlvPlayer= document .getElementById ("flv-player" ); let eleBtnPlay = document .getElementById ("play" ); let eleBtnStop = document .getElementById ("stop" ); function play ( ){ let flvUrl = "http://127.0.0.1:80/live/test.live.flv" ; console .log (flvUrl); if (!flvjs.isSupported ()){ alert ("浏览器不支持flv.js!" ); return ; } if (typeof flvUrl === "undefined" || !flvUrl.endsWith (".flv" )){ alert ("播放地址格式不正确!" ); return ; } let mediaDataSource = { type : 'flv' , enableStashBuffer :true , isLive :true , withCredentials :false , hasAudio :true , hasVideo :true , url :flvUrl }; if (typeof player !== "undefined" ) { if (player != null ) { player.unload (); player.detachMediaElement (); player.destroy (); player = null ; } } player = flvjs.createPlayer (mediaDataSource, { enableWorker : false , lazyLoadMaxDuration : 3 * 60 , seekType : 'range' , }); player.attachMediaElement (eleFlvPlayer); player.load (); player.play (); } function stop ( ){ if (typeof player === "undefined" || player === null ) { alert ("播放器暂未启动!" ); return ; } player.pause (); player.unload (); player.detachMediaElement (); player.destroy (); player = null ; } eleBtnPlay.click (); </script > </html >
6.4 高性能HTTP-FLV服务器 6.4.1 配置boost库和jsoncpp库
6.4.2 源码 HTTP-FLVServer