1. nginx安装nginx-http-flv-module-1.2.10插件 下载地址nginx-http-flv-module-1.2.10
或者去git下载最新版本 github
安装nginx-http-flv-module-1.2.10去百度 切记 不能和nginx-rtmp-module一起装 因为 nginx-http-flv-module-1.2.10里面已经包含了nginx-rtmp-module
2. 配置nginx rmtp服务
server
{
listen 7703;
location /live { # 拉流时的 uri ,可以自行修改
flv_live on; # 打开 http-flv 服务
chunked_transfer_encoding on;
add_header Access-Control-Allow-Origin *; # 允许跨域
add_header Access-Control-Allow-Credentials true;
}
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
#application/x-mpegURL;
video/mp2t ts;
}
root /www/wwwroot/;
expires -1;
add_header Cache-Control no-cache;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Origin *;
}
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Origin *;
}
location /stat.xsl {
# XML stylesheet to view RTMP stats.
# Copy stat.xsl wherever you want
# and put the full directory path here
root /www/server/nginx-http-flv-module-1.2.10;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Origin *;
}
}
include /www/server/panel/vhost/nginx/*.conf;
include /www/server/nginx/conf/vhost/*.conf;
}
rtmp {
out_queue 4096;
out_cork 8;
max_streams 128;
timeout 15s;
drop_idle_publisher 15s;
log_interval 5s; #log模块在access.log中记录日志的间隔时间,对调试非常有用
log_size 1m; #log模块用来记录日志的缓冲区大小
server {
listen 7702;
chunk_size 8192;
application play {
play /www/wwwroot/html7702;
}
application hls {
live on; # 打开直播
hls on;
meta off;# 为了兼容网页前端的 flv.js,设置为 off 可以避免报错
gop_cache on; # 支持GOP缓存,以减少首屏时间
allow play all; # 允许来自任何 ip 的人拉流
hls_path /www/wwwroot/hls;
hls_fragment 1s;
hls_playlist_length 4s;
}
application myapp { # myapp 模块,可以自行更换名字
live on; # 打开直播
#hls on;
meta off;# 为了兼容网页前端的 flv.js,设置为 off 可以避免报错
gop_cache on; # 支持GOP缓存,以减少首屏时间
allow play all; # 允许来自任何 ip 的人拉流
#hls_path /www/wwwroot/myapp;
#hls_fragment 1s;
#hls_playlist_length 4s;
}
}
}
rmtp 推流地址:rmtp:域名:7702/myapp/yjx
拉流地址:http://域名:7703/live?port=7702&app=myapp&stream=yjx
3. vue播放
vue 安装flv.js
npm install --save flv.js
封装成vue组件
videoFlv.vue
<template>
<div>
<div class="hello" style="width:100%; height:100%;background-color:#000000;">
<section v-if="!loadStatus" style="width:100%; height:100%;">
<div class="video-box">
<video autoplay controls width="100%" height="220px" :id="id" muted></video>
<div class="left-up-video-img" @click="leftUpVideoImgClick"></div>
<div class="left-down-video-img" @click="leftDownVideoImgClick"></div>
<div class="right-up-video-img" @click="rightUpVideoImgClick"></div>
<div class="right-down-video-img" @click="rightDownVideoImgClick"></div>
</div>
</section>
<div v-else
style="width: 100%;height: 100%;display: flex;justify-content:center;align-items:center;cursor: pointer">
<span style="color: wheat;font-size: 14px;"><i class="el-icon-warning-outline"
style="margin-right: 10px"></i>{{ statusMsg }}</span>
</div>
</div>
</div>
</template>
<script>
import flvjs from "flv.js";
export default {
name: 'videoFlv',
comments() {
},
data() {
return {
src: "", //flv格式的地址
timerId: null,
loadStatus: true,
id: 'videoElement' + Math.random(),
statusMsg: '摄像机未开启,请联系',
flvPlayer: null,
lastDecodedFrame: 0,
videoElement: null,
headerData: {
// 页面设置属性
name: '直播', //页面名称
details: '', //页面描述
isPerson: false, // 是否显示个人中心
isBack: false, // 是否返回按钮
titleHeight: 35, // 高度
bgColor: 'rgba(249, 249, 249, 10)', //背景颜色
bgImg: '' // 背景图片
}
};
},
created() {
if (this.timerId) {
clearInterval(this.timerId)
this.timerId = null
}
},
mounted() {
this.$nextTick(() => {
this.setsrc();
})
},
destroyed() {
console.log("destorying video");
if (this.timerId) {
clearInterval(this.timerId)
this.timerId = null
}
if (this.flvPlayer !== null) {
this.flvPlayer.pause();
this.flvPlayer.destroy();
this.flvPlayer = null;
}
},
methods: {
createVideo() {
let that = this
console.log(that.flvPlayer);
if (that.flvPlayer) {
that.flvPlayer.pause();
that.flvPlayer.destroy();
that.flvPlayer = null;
}
if (that.timerId !== null) {
clearInterval(that.timerId);
}
that.videoElement = document.getElementById(that.id)
if (flvjs.isSupported()) {
that.flvPlayer = flvjs.createPlayer({
type: 'flv',
url: that.src,
isLive: true,
}, {
// cors: true, // 是否跨域
// enableWorker: true, // 是否多线程工作
// enableStashBuffer: false, // 是否启用缓存
// stashInitialSize: 128, // 缓存大小(kb) 默认384kb
// autoCleanupSourceBuffer: true // 是否自动清理缓存
enableWorker: false, //不启用分离线程
enableStashBuffer: false, //关闭IO隐藏缓冲区
isLive: true,
lazyLoad: false,
})
}
that.flvPlayer.on(flvjs.Events.ERROR, (errorType, errorDetail, errorInfo) => {
console.log("errorType:", errorType);
console.log("errorDetail:", errorDetail);
console.log("errorInfo:", errorInfo);
this.loadStatus = true
this.statusMsg = "正在重连。。。"
//视频出错后销毁重新创建
if (that.flvPlayer) {
that.flvPlayer.pause();
that.flvPlayer.unload();
that.flvPlayer.detachMediaElement();
that.flvPlayer.destroy();
that.flvPlayer = null;
}
that.createVideo();
});
that.flvPlayer.on("statistics_info", function (res) {
if (that.lastDecodedFrame == 0) {
that.lastDecodedFrame = res.decodedFrames;
return;
}
if (that.lastDecodedFrame != res.decodedFrames) {
that.lastDecodedFrame = res.decodedFrames;
} else {
that.lastDecodedFrame = 0;
if (that.flvPlayer) {
that.flvPlayer.pause();
that.flvPlayer.unload();
that.flvPlayer.detachMediaElement();
that.flvPlayer.destroy();
that.flvPlayer = null;
that.createVideo();
}
}
});
that.flvPlayer.attachMediaElement(this.videoElement)
that.flvPlayer.load()
that.flvPlayer.play()
if (that.timerId !== null) {
clearInterval(that.timerId);
}
that.timerId = setInterval(() => {
if (that.videoElement.buffered.length > 0) {
const end = that.videoElement.buffered.end(0); // 视频结尾时间
const current = that.videoElement.currentTime; // 视频当前时间
const diff = end - current;// 相差时间
console.log(diff);
const diffCritical = 4; // 这里设定了超过4秒以上就进行跳转
const diffSpeedUp = 1; // 这里设置了超过1秒以上则进行视频加速播放
const maxPlaybackRate = 4;// 自定义设置允许的最大播放速度
let playbackRate = 1.0; // 播放速度
if (diff > diffCritical) {
console.log("相差超过4秒,进行跳转");
that.videoElement.currentTime = end - 1.5;
playbackRate = Math.max(1, Math.min(diffCritical, 16));
} else if (diff > diffSpeedUp) {
console.log("相差超过1秒,进行加速");
playbackRate = Math.max(1, Math.min(diff, maxPlaybackRate, 16))
}
that.videoElement.playbackRate = playbackRate;
if (that.videoElement.paused) {
that.flvPlayer.play()
}
}
}, 1000);
},
setsrc() {
if (this.url) {
this.src = this.url;
this.createVideo();
}
},
leftUpVideoImgClick() {
this.$toast("我是左上按钮")
},
leftDownVideoImgClick() {
this.$toast("我是左下按钮")
},
rightUpVideoImgClick(){
this.$toast("我是右上按钮")
},
rightDownVideoImgClick(){
this.$toast("我是右下按钮")
}
},
props: {
url: {
default: null,
required: true,
type: String | null
},
},
watch: {
url: {
handler(newValue) {
if (newValue != null) {
this.loadStatus = false
this.src = newValue
this.$nextTick(() => {
this.setsrc();
})
}
},
deep: true,
immediate: true,
}
},
}
</script>
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
.video-box {
position: relative;
}
.video-box video {
display: inline-block;
vertical-align: baseline;
}
.video-box .left-up-video-img {
position: absolute;
top: 100px;
bottom: 10px;
left: 20px;
/*right: 20px;*/
width: 10px;
height: 10px;
z-index: 999;
background: url(../../../assets/images/icon_jt.png) no-repeat;
background-size: 100% 100%;
cursor: pointer
}
.video-box .left-down-video-img {
position: absolute;
top: 150px;
bottom: 10px;
left: 20px;
/*right: 20px;*/
width: 10px;
height: 10px;
z-index: 999;
background: url(../../../assets/images/icon_jt.png) no-repeat;
background-size: 100% 100%;
cursor: pointer
}
.video-box .right-up-video-img {
position: absolute;
top: 100px;
bottom: 10px;
/*left: 20px;*/
right: 20px;
width: 10px;
height: 10px;
z-index: 999;
background: url(../../../assets/images/icon_jt.png) no-repeat;
background-size: 100% 100%;
cursor: pointer
}
.video-box .right-down-video-img {
position: absolute;
top: 150px;
bottom: 10px;
/*left: 20px;*/
right: 20px;
width: 10px;
height: 10px;
z-index: 999;
background: url(../../../assets/images/icon_jt.png) no-repeat;
background-size: 100% 100%;
cursor: pointer
}
</style>
调用方法
<template>
<div>
<!-- <video-flv :url="src"></video-flv>-->
<div style="width: 100%; height:220px" v-for="(item,index) in urllist" :key="index">
<video-flv :url="item.url"/>
</div>
</div>
</template>
<script>
import VideoFlv from "@/views/tabbar/live/flvVideo";
export default {
components: {VideoFlv},
data() {
return {
urllist: [
{
//nginx 流媒体服务器
// url: "http://ip:7703/live?port=7702&app=myapp&stream=yjx"//推流地址 rtmp://yjx.yjx-show.top:7702/myapp /yjx
url: "http://ip:7703/live?port=7702&app=myapp&stream=yjx"//推流地址 rtmp://yjx.yjx-show.top:7702/myapp /yj
},
// {
// url: "http://yjx.yjx-show.top:7703/live?port=7702&app=myapp&stream=ypt"
// },
]
}
},
comments() {
flvVideo
}
}
</script>
<style lang="less" scoped>
//.dplayer {
// width: 500px;
//}
</style>
使用obs进行推流
直播延迟1-3秒