参考:https://www.jianshu.com/p/8b8023c7ed37
安装
$ npm install video.js
main.js中引入
import Video from 'video.js'
import 'video.js/dist/video-js.css'
Vue.prototype.$video = Video
使用(代码中有注释说明)
参考链接:https://www.jianshu.com/p/fede1032bc16
style =“width:100%; height:100%; object-fit:fill”的作用是使播放器自适应其父元素的大小
静音:实现静音播放,有一些浏览器需要静音才能实现加载之后自动播放
controls:将显示视频控件,如果不需要则去掉控制即可。
autoplay:视频在加载完成之后自动播放; 注意,这里在Chrome62.X上有坑,如果播放器大小小于400 * 300的话是不会自动播放的。
poster:视频封面图片;
loop:true / false,是否循环播放
CreatePlayer(id) {
var options = {
autoplay : true,
preload : true,
falsh: {
swf: './lib/video-js.swf'
}
}
let self = this;
return videojs(id, options, function onPlayerReady() {
videojs.log(`Your player${self.index} is ready!`);
// How about an event listener?
this.on('ended', function() {
videojs.log('Awww...over so soon?!');
});
this.on('error', function() {
console.log('error');
})
this.on("abort", function() {
console.log("abort");
});
this.on("emptied", function() {
console.log("emptied");
});
this.on('loadstart', () => {
self.player.play();
});
this.on('stalled ', function() {
console.log("stalled");
});
});
}
videoJs()接收三个参数,DOM元素的ID,videoJs的配置,以及一个回调函数。
playVideo(url) {
this.player = this.CreatePlayer(this._id);
this.player.src({
src : url,
type: 'rtmp/flv',
autoplay: true,
isFullscreen: true
});
}
enterFullScreen(ele) {
if (ele .requestFullscreen) {
ele .requestFullscreen();
} else if (ele .mozRequestFullScreen) {
ele .mozRequestFullScreen();
} else if (ele .webkitRequestFullScreen) {
ele .webkitRequestFullScreen();
}
}
exitFullscreen() {
var de = document;
if (de.exitFullscreen) {
de.exitFullscreen();
} else if (de.mozCancelFullScreen) {
de.mozCancelFullScreen();
} else if (de.webkitCancelFullScreen) {
de.webkitCancelFullScreen();
}
}
$('.vjs-big-play-button') // 获取该dom元素
self.player.dispose();
let _id = self._id;
let video_dom = "";
// 将video标签重新插入html
$('#' + self.mask_id).before(video_dom);
然后便可以重新实例化videojs,加载url播放视频
解决:
image.png
选择网站设置,将flash设置为允许(chrome默认不让flash播放)image.png
解决:将flash.swf下载到本地,从本地加载。设置videojs.options.flash =“本地swf文件地址”; 如果设置不成功,可以直接修改video.js里面的配置。
image.png
以下是关于nginx推流和拉流的疑惑分析:
参考:https://cloud.tencent.com/developer/article/1336238
RTMP:
RTMP(Real Time Messaging Protocol)实时消息传送协议是Adobe Systems公司为Flash播放器和服务器之间
音频、视频和数据传输 开发的开放协议。.这个协议建立在TCP协议或者轮询HTTP协议之上,是一个协议族,
包括RTMP基本协议及RTMPT/RTMPS/RTMPE等多种变种
它有多种变种:
1)RTMP工作在TCP之上,默认使用端口1935;
2)RTMPE在RTMP的基础上增加了加密功能;
3)RTMPT封装在 HTTP请求之上,可穿透 防火墙;
4)RTMPS类似RTMPT,增加了TLS/SSL的安全功能;
ffmpeg:
FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。
项目的名称来自MPEG视频编码标准,前面的"FF"代表"Fast Forward"。
H.264:
H.264最大的优势是具有很高的数据压缩比率,在同等图像质量的条件下,H.264的压缩比是MPEG-2的2倍以上,
是MPEG-4的1.5~2倍。举个例子,原始文件的大小如果为88GB,采用MPEG-2压缩标准压缩后变成3.5GB,压缩比为25∶1,
而采用H.264压缩标准压缩后变为879MB,从88GB到879MB,H.264的压缩比达到惊人的102∶1。
## H264是一种高压缩率的编码标准,如何压缩嘞?一般的视频采集都是25帧/秒,
## 也就是每秒截图25次,其实每一张图片的内容都相差不大,压缩的办法就是利用算法,
## 只将每张图片变动差异化的部分保存下来,这样视频文件就小多了
低码率(Low Bit Rate)对H.264的高的压缩比起到了重要的作用,和MPEG-2和MPEG-4 ASP等压缩技术相比,
H.264压缩技术将大大节省用户的下载时间和数据流量收费。
尤其值得一提的是,H.264在具有高压缩比的同时还拥有高质量流畅的图像,
正因为如此,经过H.264压缩的视频数据,在网络传输过程中所需要的带宽更少,也更加经济。
H.265是新的编码协议,也即是H.264的升级版。
他们核心区别的可以分两步看:
1同样的画质和同样的码率,H.265比H2.64 占用的存储空间要少理论50%。
2如果存储空间一样大,那么意味着,在一样的码率下H.265会比H2.64 画质要高一些理论值是30%~40%
据说能节省一半带宽,但需要机器更强的运算能力。
FFmpeg和h.264是什么关系?
H.264是标准(包含编码、解码),x264是标准的实现(只实现了编码),ffmpeg是一个框架,
但是里面包含了H.264的解码实现,所以ffmpeg + x264 就包含了H.264的编码、解码的实现了。
vi /usr/local/etc/nginx/nginx.conf
rtmp {
server {
listen 1935;
#直播流配置
application rtmplive {
live on;
#为 rtmp 引擎设置最大连接数。默认为 off
max_connections 1024;
}
application hls{
live on;
hls on;
hls_path /usr/local/var/www/hls;
hls_fragment 1s;
}
}
}
nginx常用方法::
重新加载配置文件: nginx -s reload
重新加载日志: nginx -s reopen
停止 nginx: nginx -s stop
有序退出 nginx: nginx -s quit
(1)、搭建本地视频直播,比如电脑上面有很多电影,我们可以通过推流的形式实现实时直播:
A:在电脑上播放推流内容
安装一个支持rtmp协议的视频播放器,Mac下可以用VLC
下载VLC 本地下载一个视频文件路径为 /Users/iOS002/Desktop/loginmovie.mp4 执行以下命令
ffmpeg -re -i /Users/iOS002/Desktop/loginmovie.mp4 -vcodec libx264 -acodec aac -strict -2 -f flv rtmp://localhost:1935/rtmplive/room
用vlc 然后打开 VLC 中 的 file -- Open Network, 直接输入代码中的 url:
即可以通过VLC来播放终端中实时推过来的 RTMP流。
B:通过手机观看电脑的推流
通过集成 ijkplayer 把地址换成推流的地址即可观看:
播放端用的针对RTMP优化过的ijkplayer,ijkplayer是基于FFmpeg的跨平台播放器,这个开源项目已经被多个 App 使用,其中映客、美拍和斗鱼使用了 ijkplayer。
(2)、桌面录制或者分享
ffmpeg -f avfoundation -i "1" -vcodec libx264 -preset ultrafast -acodec libfaac -f flv rtmp://localhost:1935/rtmplive/room
(3)、桌面+麦克风
ffmpeg -f avfoundation -i "1:0" -vcodec libx264 -preset ultrafast -acodec libmp3lame -ar 44100 -ac 1 -f flv rtmp://localhost:1935/rtmplive/room
(4)、桌面+麦克风,并且还要摄像头拍摄到自己
ffmpeg -f avfoundation -framerate 30 -i "1:0" \-f avfoundation -framerate 30 -video_size 640x480 -i "0" \-c:v libx264 -preset ultrafast \-filter_complex 'overlay=main_w-overlay_w-10:main_h-overlay_h-10' -acodec libmp3lame -ar 44100 -ac 1 -f flv rtmp://localhost:2016/rtmplive/room
可以用 LFLiveKit 集成到工程进行推流,LFLiveKit已经帮我们实现了视频采集、后台录制、美颜功能、支持h264、AAC编码,动态改变速率,RTMP传输等,我们开发的时候就很简单了只需把localhost:8080换成自己电脑的ip地址即可:
rtmp://10.0.0.17:1935/rtmplive/room
注意通过网络查看电脑的局域网 IP替换掉 localhost 即可。
A:通过VLC观看手机的推流 打开手机直播后,然后在电脑上打开VLC(同上),就能实现手机推流,在电脑上拉流播放了!!(注:手机需要和电脑连接同一网络!)
B:通过手机观看手机的推流(这也就是市面上的那些直播App的最终实现形式了) 通过集成 ijkplayer 把地址换成推流的地址即可观看。
如果你发现你的推流地址和拉流地址在电脑上都是好好的,但是通过手机实现的时候就是报错,那么估计就是因为Mac防火墙的问题。
以上关于nginx推流ffmpeg资料参考来源于大神:https://cloud.tencent.com/developer/article/1336238
前期实现:
之前的推流地址:直接拼接的rtmp和rtsp地址
如下:
rtsp://admin:123456Aa@192.168.1.44:554/Streaming/Channels/102
rtmp://192.168.1.113:1935/live/cal_20190218141704
rtmp://192.168.1.113:1935/live/cal_20190218141704
思路:javacv推流rtsp推到rtmp中,然后页面video中放的是url是rtmp地址。
代码如下:
/**
* 转流器 (不带弹出窗口的转流器)
* @param inputFile
* @param outputFile
* @throws Exception
* @throws FrameRecorder.Exception
* @throws InterruptedException
*/
public static void recordPush1(String inputFile,String outputFile,int v_rs) throws Exception, FrameRecorder.Exception, InterruptedException{
Loader.load(opencv_objdetect.class);
long startTime=0;
FrameGrabber grabber =FFmpegFrameGrabber.createDefault(inputFile);
System.out.println("开始获取FrameGrabber grabber:"+grabber);
try {
System.out.println("获取 grabber的start:"+grabber);
grabber.setImageHeight(480);
grabber.setImageWidth(860);
grabber.setOption("rtsp_transport", "tcp"); // 使用tcp的方式,不然会丢包很严重
grabber.start();
} catch (Exception e) {
try {
System.out.println("获取 grabber的restart:"+grabber);
grabber.restart();
} catch (Exception e1) {
System.out.println("出错了:"+grabber);
throw e;
}
}
System.out.println("结束获取FrameGrabber grabber:"+grabber);
OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
System.out.println("开始获取OpenCVFrameConverter.ToIplImage:"+converter);
Frame grabframe =grabber.grab();
opencv_core.IplImage grabbedImage =null;
if(grabframe!=null){
System.out.println("取到第一帧");
grabbedImage = converter.convert(grabframe);
}else{
System.out.println("没有取到第一帧");
}
System.out.println("结束获取converter:"+converter);
//如果想要保存图片,可以使用 opencv_imgcodecs.cvSaveImage("hello.jpg", grabbedImage);来保存图片
FrameRecorder recorder;
try {
recorder = FrameRecorder.createDefault(outputFile, 500, 500);
} catch (org.bytedeco.javacv.FrameRecorder.Exception e) {
throw e;
}
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // avcodec.AV_CODEC_ID_H264
recorder.setFormat("flv");
recorder.setFrameRate(v_rs);
recorder.setGopSize(v_rs);
System.out.println("准备开始推流..."+inputFile);
try {
recorder.start();
} catch (org.bytedeco.javacv.FrameRecorder.Exception e) {
try {
System.out.println("录制器启动失败,正在重新启动..."+outputFile);
if(recorder!=null)
{
System.out.println("尝试关闭录制器"+outputFile);
recorder.stop();
System.out.println("尝试重新开启录制器"+outputFile);
recorder.start();
}
} catch (org.bytedeco.javacv.FrameRecorder.Exception e1) {
throw e;
}
}
System.out.println("开始推流"+inputFile+"/"+outputFile);
//获取每一帧不展示
// CanvasFrame frame = new CanvasFrame("camera", CanvasFrame.getDefaultGamma() / grabber.getGamma());
// frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//frame.setAlwaysOnTop(true);
while ((grabframe=grabber.grab()) != null) {
System.out.println("推流..."+inputFile+"/"+outputFile);
// frame.showImage(grabframe);
grabbedImage = converter.convert(grabframe);
Frame rotatedFrame = converter.convert(grabbedImage);
if (startTime == 0) {
startTime = System.currentTimeMillis();
}
recorder.setTimestamp(1000 * (System.currentTimeMillis() - startTime));//时间戳
if(rotatedFrame!=null && rotatedFrame.imageHeight > 0 && rotatedFrame.imageWidth > 0){
recorder.record(rotatedFrame);
}
//Thread.sleep(2000);//切换成下一个
}
// frame.dispose();
/* recorder.stop();
recorder.release();
grabber.stop();
System.exit(2);*/
System.out.println("完成了读流的方法");
}
以下是大神的推流器实现,参考:https://blog.csdn.net/eguid_1/article/details/52678775
本功能采用按帧录制/推流,通过关闭播放窗口停止视频录制/推流
注:长时间运行该代码会导致内存溢出的原因是没有及时释放IplImage资源(由于javacv是jni方式调用C,部分对象需要手动释放资源,以防止内存溢出错误)
总的来说,我们已经实现了基本的推流器功能,那么需要注意的就是转换那里,不清楚为什么不做转换就不能推送到rtmp流媒体服务器,如果哪位有更好的方案希望可以联系博主,感谢!
public static void main(String[] args) throws Exception, InterruptedException, org.bytedeco.javacv.FrameRecorder.Exception {
recordCamera("output.mp4",25);
看到了摄像头窗口就说明已经开始录制,点击右上角关闭按钮即停止录制视频,在录制的时候刷新项目目录发现新生成了一个output.mp4文件,可以正常播放这个视频文件
/**
* 按帧录制视频
*
* @param inputFile-该地址可以是网络直播/录播地址,也可以是远程/本地文件路径
* @param outputFile
* -该地址只能是文件地址,如果使用该方法推送流媒体服务器会报错,原因是没有设置编码格式
* @throws FrameGrabber.Exception
* @throws FrameRecorder.Exception
* @throws org.bytedeco.javacv.FrameRecorder.Exception
*/
public static void frameRecord(String inputFile, String outputFile, int audioChannel)
throws Exception, org.bytedeco.javacv.FrameRecorder.Exception {
boolean isStart=true;//该变量建议设置为全局控制变量,用于控制录制结束
// 获取视频源
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputFile);
// 流媒体输出地址,分辨率(长,高),是否录制音频(0:不录制/1:录制)
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputFile, 1280, 720, audioChannel);
// 开始取视频源
recordByFrame(grabber, recorder, isStart);
}
private static void recordByFrame(FFmpegFrameGrabber grabber, FFmpegFrameRecorder recorder, Boolean status)
throws Exception, org.bytedeco.javacv.FrameRecorder.Exception {
try {//建议在线程中使用该方法
grabber.start();
recorder.start();
Frame frame = null;
while (status&& (frame = grabber.grabFrame()) != null) {
recorder.record(frame);
}
recorder.stop();
grabber.stop();
} finally {
if (grabber != null) {
grabber.stop();
}
}
}
public static void main(String[] args)
throws FrameRecorder.Exception, FrameGrabber.Exception, InterruptedException {
String inputFile = "rtsp://admin:admin@192.168.2.236:37779/cam/realmonitor?channel=1&subtype=0";
// Decodes-encodes
String outputFile = "recorde.mp4";
frameRecord(inputFile, outputFile,1);
}
//感觉自己写的录制有点复杂了。
到这里我们已经实现了直播功能的全部基本操作:推流,录制,简单的直播系统和多人视频等已经可以实现了;
重点,重点,重点,重要的事情说三遍,思路清了,一切就简单了。
未免混淆,另外整理一篇。
感觉和我本地写的不太一样,
目前本地需要解决的问题:
1.多个摄像头同时推流;-----线程可设置全部开启或者用时开启;
2.nginx推流是不是一个流媒体服务器只能对同一个摄像头推一次不能同时推,否则会报错,导致流媒体服务器关闭;
3.nginx推流到流媒体服务器后,放到video.js中的视频地址是rtmp地址;
4.实现视频的录制,录制的地址是rtsp还是rtmp,录制是可以直接录制还是服务器录到本地或者服务器(设计nginx推流关闭的问题);
5.如果一直开着会占用资源,但是实时监控必须开着,方便报警时进行视频的录制,可以报警后开启视频的录制功能,
但是有可能录制时已经有视频再推流了,两者会冲突,
所以视频推流时rtsp推流到ngingx-rtmp上,
录制是从媒流体服务器录制到文件中,
vue中的video.js的播放地址是rtmp或者是MP4(nginx已经成功推流的地址)