WebRtcStreamer 是一个基于 WebRTC 协议的轻量级开源工具,可以在浏览器中直接播放 RTSP 视频流。它利用 WebRTC 的强大功能,提供低耽误的视频流播放体验,非常适合及时监控和其他视频流应用场景。
本文将先容如何在Vue.js项目中利用 WebRtcStreamer 实现及时视频流播放,并分享相干的代码示例。
注意:只支持H264格式
流媒体方式文章
利用 Vue 和 flv.js 实现流媒体视频播放:完整教程
VUE项目中优雅利用EasyPlayer及时播放摄像头多种格式视频利用版本信息为5.xxxx
实现步调
- 安装和设置 WebRtcStreamer 服务端
要利用 WebRtcStreamer,需要先在服务器上摆设其服务端。以下是基本的安装步调:
- 从 WebRtcStreamer 官方仓库 下载代码。
启动下令
或者双击exe程序
服务启动后,默认会监听 8000 端口,访问 http://<server_ip>:8000 可查看状态。
更改默认端口下令:webrtc-streamer.exe -o -H 0.0.0.0:9527
2.集成到vue中
webRtcStreamer.js 不需要在html文件中引入webRtcStreamer相干代码
- /**
- * @constructor
- * @param {string} videoElement - dom ID
- * @param {string} srvurl - WebRTC 流媒体服务器的 URL(默认为当前页面地址)
- */
- class WebRtcStreamer {
- constructor(videoElement, srvurl) {
- if (typeof videoElement === 'string') {
- this.videoElement = document.getElementById(videoElement);
- } else {
- this.videoElement = videoElement;
- }
- this.srvurl =
- srvurl || `${location.protocol}//${window.location.hostname}:${window.location.port}`;
- this.pc = null; // PeerConnection 实例
- // 媒体约束条件
- this.mediaConstraints = {
- offerToReceiveAudio: true,
- offerToReceiveVideo: true,
- };
- this.iceServers = null; // ICE 服务器配置
- this.earlyCandidates = []; // 提前收集的候选者
- }
- /**
- * HTTP 错误处理器
- * @param {Response} response - HTTP 响应
- * @throws {Error} 当响应不成功时抛出错误
- */
- _handleHttpErrors(response) {
- if (!response.ok) {
- throw Error(response.statusText);
- }
- return response;
- }
- /**
- * 连接 WebRTC 视频流到指定的 videoElement
- * @param {string} videourl - 视频流 URL
- * @param {string} audiourl - 音频流 URL
- * @param {string} options - WebRTC 通话的选项
- * @param {MediaStream} localstream - 本地流
- * @param {string} prefmime - 优先的 MIME 类型
- */
- connect(videourl, audiourl, options, localstream, prefmime) {
- this.disconnect();
- if (!this.iceServers) {
- console.log('获取 ICE 服务器配置...');
- fetch(`${this.srvurl}/api/getIceServers`)
- .then(this._handleHttpErrors)
- .then((response) => response.json())
- .then((response) =>
- this.onReceiveGetIceServers(response, videourl, audiourl, options, localstream, prefmime),
- )
- .catch((error) => this.onError(`获取 ICE 服务器错误: ${error}`));
- } else {
- this.onReceiveGetIceServers(
- this.iceServers,
- videourl,
- audiourl,
- options,
- localstream,
- prefmime,
- );
- }
- }
- /**
- * 断开 WebRTC 视频流,并清空 videoElement 的视频源
- */
- disconnect() {
- if (this.videoElement?.srcObject) {
- this.videoElement.srcObject.getTracks().forEach((track) => {
- track.stop();
- this.videoElement.srcObject.removeTrack(track);
- });
- }
- if (this.pc) {
- fetch(`${this.srvurl}/api/hangup?peerid=${this.pc.peerid}`)
- .then(this._handleHttpErrors)
- .catch((error) => this.onError(`hangup ${error}`));
- try {
- this.pc.close();
- } catch (e) {
- console.log(`Failure close peer connection: ${e}`);
- }
- this.pc = null;
- }
- }
- /**
- * 获取 ICE 服务器配置的回调
- * @param {Object} iceServers - ICE 服务器配置
- * @param {string} videourl - 视频流 URL
- * @param {string} audiourl - 音频流 URL
- * @param {string} options - WebRTC 通话的选项
- * @param {MediaStream} stream - 本地流
- * @param {string} prefmime - 优先的 MIME 类型
- */
- onReceiveGetIceServers(iceServers, videourl, audiourl, options, stream, prefmime) {
- this.iceServers = iceServers;
- this.pcConfig = iceServers || { iceServers: [] };
- try {
- this.createPeerConnection();
- let callurl = `${this.srvurl}/api/call?peerid=${this.pc.peerid}&url=${encodeURIComponent(
- videourl,
- )}`;
- if (audiourl) {
- callurl += `&audiourl=${encodeURIComponent(audiourl)}`;
- }
- if (options) {
- callurl += `&options=${encodeURIComponent(options)}`;
- }
- if (stream) {
- this.pc.addStream(stream);
- }
- this.earlyCandidates.length = 0;
- this.pc
- .createOffer(this.mediaConstraints)
- .then((sessionDescription) => {
- // console.log(`创建 Offer: ${JSON.stringify(sessionDescription)}`);
- if (prefmime !== undefined) {
- const [prefkind] = prefmime.split('/');
- const codecs = RTCRtpReceiver.getCapabilities(prefkind).codecs;
- const preferredCodecs = codecs.filter((codec) => codec.mimeType === prefmime);
- this.pc
- .getTransceivers()
- .filter((transceiver) => transceiver.receiver.track.kind === prefkind)
- .forEach((tcvr) => {
- if (tcvr.setCodecPreferences) {
- tcvr.setCodecPreferences(preferredCodecs);
- }
- });
- }
- this.pc
- .setLocalDescription(sessionDescription)
- .then(() => {
- fetch(callurl, {
- method: 'POST',
- body: JSON.stringify(sessionDescription),
- })
- .then(this._handleHttpErrors)
- .then((response) => response.json())
- .then((response) => this.onReceiveCall(response))
- .catch((error) => this.onError(`调用错误: ${error}`));
- })
- .catch((error) => console.log(`setLocalDescription error: ${JSON.stringify(error)}`));
- })
- .catch((error) => console.log(`创建 Offer 失败: ${JSON.stringify(error)}`));
- } catch (e) {
- this.disconnect();
- alert(`连接错误: ${e}`);
- }
- }
- /**
- * 创建 PeerConnection 实例
- */
- createPeerConnection() {
- console.log('创建 PeerConnection...');
- this.pc = new RTCPeerConnection(this.pcConfig);
- this.pc.peerid = Math.random(); // 生成唯一的 peerid
- // 监听 ICE 候选者事件
- this.pc.onicecandidate = (evt) => this.onIceCandidate(evt);
- this.pc.onaddstream = (evt) => this.onAddStream(evt);
- this.pc.oniceconnectionstatechange = () => {
- if (this.videoElement) {
- if (this.pc.iceConnectionState === 'connected') {
- this.videoElement.style.opacity = '1.0';
- } else if (this.pc.iceConnectionState === 'disconnected') {
- this.videoElement.style.opacity = '0.25';
- } else if (['failed', 'closed'].includes(this.pc.iceConnectionState)) {
- this.videoElement.style.opacity = '0.5';
- } else if (this.pc.iceConnectionState === 'new') {
- this.getIceCandidate();
- }
- }
- };
- return this.pc;
- }
- onAddStream(event) {
- console.log(`Remote track added: ${JSON.stringify(event)}`);
- this.videoElement.srcObject = event.stream;
- const promise = this.videoElement.play();
- if (promise !== undefined) {
- promise.catch((error) => {
- console.warn(`error: ${error}`);
- this.videoElement.setAttribute('controls', true);
- });
- }
- }
- onIceCandidate(event) {
- if (event.candidate) {
- if (this.pc.currentRemoteDescription) {
- this.addIceCandidate(this.pc.peerid, event.candidate);
- } else {
- this.earlyCandidates.push(event.candidate);
- }
- } else {
- console.log('End of candidates.');
- }
- }
- /**
- * 添加 ICE 候选者到 PeerConnection
- * @param {RTCIceCandidate} candidate - ICE 候选者
- */
- addIceCandidate(peerid, candidate) {
- fetch(`${this.srvurl}/api/addIceCandidate?peerid=${peerid}`, {
- method: 'POST',
- body: JSON.stringify(candidate),
- })
- .then(this._handleHttpErrors)
- .catch((error) => this.onError(`addIceCandidate ${error}`));
- }
- /**
- * 处理 WebRTC 通话的响应
- * @param {Object} message - 来自服务器的响应消息
- */
- onReceiveCall(dataJson) {
- const descr = new RTCSessionDescription(dataJson);
- this.pc
- .setRemoteDescription(descr)
- .then(() => {
- while (this.earlyCandidates.length) {
- const candidate = this.earlyCandidates.shift();
- this.addIceCandidate(this.pc.peerid, candidate);
- }
- this.getIceCandidate();
- })
- .catch((error) => console.log(`设置描述文件失败: ${JSON.stringify(error)}`));
- }
- getIceCandidate() {
- fetch(`${this.srvurl}/api/getIceCandidate?peerid=${this.pc.peerid}`)
- .then(this._handleHttpErrors)
- .then((response) => response.json())
- .then((response) => this.onReceiveCandidate(response))
- .catch((error) => this.onError(`getIceCandidate ${error}`));
- }
- onReceiveCandidate(dataJson) {
- if (dataJson) {
- dataJson.forEach((candidateData) => {
- const candidate = new RTCIceCandidate(candidateData);
- this.pc
- .addIceCandidate(candidate)
- .catch((error) => console.log(`addIceCandidate error: ${JSON.stringify(error)}`));
- });
- }
- }
- /**
- * 错误处理器
- * @param {string} message - 错误信息
- */
- onError(status) {
- console.error(`WebRTC 错误: ${status}`);
- }
- }
- export default WebRtcStreamer;
复制代码 组件中利用
- <template>
- <div>
- <video id="video" controls muted autoplay></video>
- <button @click="startStream">开始播放</button>
- <button @click="stopStream">停止播放</button>
- </div>
- </template>
- <script>
- import WebRtcStreamer from "@/utils/webRtcStreamer";
- export default {
- name: "VideoStreamer",
- data() {
- return {
- webRtcServer: null,
- };
- },
- methods: {
- startStream() {
- const srvurl = "127.0.0.1:9527"
- this.webRtcServer = new WebRtcStreamer(
- "video",
- `${location.protocol}//${srvurl}`
- );
- const videoPath = "stream_name"; // 替换为你的流地址
- this.webRtcServer.connect(videoPath);
- },
- stopStream() {
- if (this.webRtcServer) {
- this.webRtcServer.disconnect(); // 销毁
- }
- },
- },
- };
- </script>
- <style>
- video {
- width: 100%;
- height: 100%;
- object-fit: fill;
- }
- </style>
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |