一. 前言
之前工作中要做网页端html5的无插件直播, 在网上找了很多资料, 最终以此套方案做了网页端无插件直播, 效果还行, 延时控制在了2秒内. 音视频编码支持情况见 flv.js.
二. 实现
FFMPEG 负责 rtmp 推流, 流数据缓存到缓冲区, ffmpeg从缓冲区拿数据解析并封装成flv格式推送到nginx
代码实现: ffmpeg 内存读取数据推流到 rtmp 服务器
nginx 配置文件
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#error_log logs/error.log debug;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
# 添加RTMP服务
rtmp {
server {
listen 1935;
#rtmp flv
application live {
live on;
#gop_cache on; #打开GOP缓存,减少首屏等待时间
}
#hls
application hls {
live on;
hls on; #开启hls
hls_path hlstmp/hls;
hls_fragment 2s; #一个ts 文件的时长 2s
}
}
}
http {
include ./mime.types;
default_type application/octet-stream;
server {
listen 8080;
listen [::]:8080 ipv6only=on; #支持ipv6
server_name localhost;
location / {
root html;
index index.html;
}
location = /index.html {
root html;
}
location /live {
flv_live on; #http-flv直播
chunked_transfer_encoding on; #open 'Transfer-Encoding: chunked' response
add_header 'Access-Control-Allow-Credentials' 'true'; #add additional HTTP header
add_header 'Access-Control-Allow-Origin' '*'; #add additional HTTP header
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header 'Cache-Control' 'no-cache';
}
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root hlstmp; #hls直播
add_header 'Access-Control-Allow-Credentials' 'true'; #add additional HTTP header
add_header 'Access-Control-Allow-Origin' '*'; #add additional HTTP header
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header 'Cache-Control' 'no-cache';
}
location /stat {
#configuration of push & pull status
rtmp_stat all;
rtmp_stat_format json; #json风格, 后端可获取推流状态来进行每一路流的管理
#rtmp_stat_stylesheet stat.xsl;
}
#location /stat.xsl {
# root html/nginxclient;
#}
location /control {
rtmp_control all; #configuration of control module of rtmp
}
}
server {
listen 8088 ssl; #支持https直播
listen [::]:8088 ssl ipv6only=on; #支持ipv6
server_name localhost;
ssl_certificate ../ssl/server.crt; #自己可用openssl生成
ssl_certificate_key ../ssl/server.key; #自己可用openssl生成
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_session_timeout 5m;
ssl_prefer_server_ciphers on;
location / {
root html;
index index.html;
}
location = /index.html {
root html;
}
location /live {
flv_live on;
chunked_transfer_encoding on; #open 'Transfer-Encoding: chunked' response
add_header 'Access-Control-Allow-Credentials' 'true'; #add additional HTTP header
add_header 'Access-Control-Allow-Origin' '*'; #add additional HTTP header
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header 'Cache-Control' 'no-cache';
}
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root hlstmp;
add_header 'Access-Control-Allow-Credentials' 'true'; #add additional HTTP header
add_header 'Access-Control-Allow-Origin' '*'; #add additional HTTP header
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header 'Cache-Control' 'no-cache';
}
}
}
flv.js demo, 包含延时追赶, 最大限度减小延时
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>flv.js demo</title>
<style>
.mainContainer {
display: block;
width: 1024px;
margin-left: auto;
margin-right: auto;
}
.urlInput {
display: block;
width: 100%;
margin-left: auto;
margin-right: auto;
margin-top: 8px;
margin-bottom: 8px;
}
.centeredVideo {
display: block;
width: 100%;
height: 576px;
margin-left: auto;
margin-right: auto;
margin-bottom: auto;
}
.controls {
display: block;
width: 100%;
text-align: left;
margin-left: auto;
margin-right: auto;
margin-top: 8px;
margin-bottom: 10px;
}
.logcatBox {
border-color: #CCCCCC;
font-size: 11px;
font-family: Menlo, Consolas, monospace;
display: block;
width: 100%;
text-align: left;
margin-left: auto;
margin-right: auto;
}
</style>
</head>
<body>
<div class="mainContainer">
<video name="videoElement" class="centeredVideo" id="videoElement" width="1024" height="576" controls muted autoplay>
Your browser is too old which doesn't support HTML5 video.
</video>
<p></p>
<input type="text" name="ip" value="" id="ip">
<p></p>
<input type="button" onclick="pullStream()" value="拉流" />
</div>
<script src="flv.min.js"></script>
<script>
var flvPlayer = null;
function pullStream () {
console.log("pullStream");
var date = new Date();
destoryVideo()
if (flvjs.isSupported()) {
startVideo(ip.value)
}
}
function startVideo(pullUrl) {
var videoElement = document.getElementById('videoElement');
flvPlayer = flvjs.createPlayer({
type: 'flv',
isLive: true,
duration: 0,
url: pullUrl
},
{
enableWorker: true,
enableStashBuffer: false,
stashInitialSize: 128,
lazyLoad: false,
lazyLoadMaxDuration: 0,
lazyLoadRecoverDuration: 0,
autoCleanupSourceBuffer: true,
deferLoadAfterSourceOpen: false,
fixAudioTimestampGap: false,
}
);
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
flvPlayer.play();
//避免时间长时间积累缓冲导致延迟越来越高
setInterval(function () {
if (!videoElement.buffered.length) {
return;
}
let end = flvPlayer.buffered.end(0);
let currentTime = videoElement.currentTime;
//console.log("end:" + end);
//console.log("currentTime:" + currentTime);
let diff = end - currentTime;
if (diff >= 1) {
videoElement.currentTime = parseFloat(end);
console.log("进行时延校正"+videoElement.currentTime);
}
//console.log(diff);
}, 3 * 1000);
}
videoElement.addEventListener('click', function(){
alert( '是否支持点播视频:' + flvjs.getFeatureList().mseFlvPlayback + ' 是否支持httpflv直播流:' + flvjs.getFeatureList().mseLiveFlvPlayback )
})
function destoryVideo() {
if (flvPlayer != null) {
flvPlayer.pause();
flvPlayer.unload();
flvPlayer.detachMediaElement();
flvPlayer.destroy();
flvPlayer = null;
}
}
function reloadVideo(){
destoryVideo()
startVideo()
}
</script>
</body>
</html>