WebRTC模拟传输视频流,video通过本地节点peer传输视频流

举报
AnRFDev 发表于 2021/12/15 15:37:43 2021/12/15
【摘要】 video -> video在网页上,video传输视频到另一个video。 htmlvideo用source设定视频路径<video id="fromVideo" playsinline controls loop muted> <source src="../../src/video/s1.mp4" type="video/mp4"> <p>当前浏览器不支持视频播放</p>...

video -> video

在网页上,video传输视频到另一个video。

html

videosource设定视频路径

<video id="fromVideo" playsinline controls loop muted>
    <source src="../../src/video/s1.mp4" type="video/mp4">
    <p>当前浏览器不支持视频播放</p>
</video>

<video id="toVideo" playsinline autoplay></video>

2个video元素,一个预设视频路径,另一个准备接收视频流。

js

需要监听canplay事件。

'use strict';

const srcVideo = document.getElementById('fromVideo');
const toVideo = document.getElementById('toVideo');

srcVideo.addEventListener('canplay', () => {
  let stream;
  const fps = 0;
  if (srcVideo.captureStream) {
    stream = srcVideo.captureStream(fps);
  } else if (srcVideo.mozCaptureStream) {
    stream = srcVideo.mozCaptureStream(fps);
  } else {
    console.error('rustfisher.com: 不支持captureStream方法!');
    stream = null;
  }
  toVideo.srcObject = stream;
});

captureStream方法返回一个MediaStream对象,拥有实时数据流。

参考HTMLMediaElement.captureStream()

本地直接打开html,captureStream(fps)方法报错

Uncaught DOMException: 
    Failed to execute 'captureStream' on 'HTMLMediaElement': 
        Cannot capture from element with cross-origin data
        at HTMLVideoElement.<anonymous>

需要在HTTPS下运行。调试的时候需要注意。

示例效果链接

模拟连接节点

前面是从一个video传到另一个video。现在我们来模拟通过RTCPeerConnection来传输视频流。

准备工作

html上摆放2个video,第一个fromVideo作为源,toVideo显示接收后的视频

<div id="container">
    <video id="fromVideo" playsinline controls muted>
        <source src="../../src/video/v5_1.mp4" type="video/mp4">
        <p>当前浏览器不支持视频播放</p>
    </video>

    <video id="toVideo" playsinline autoplay controls></video>
    <p>受网速影响,视频加载需要一些时间</p>
</div>

<script src="../../src/js/adapter-2021.js"></script>
<script src="js/pc.js" async></script>

网速好的也可以用webrtc.github.io adapter

<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

js中拿到这2个video元素

'use strict';

const srcVideo = document.getElementById('fromVideo');
const toVideo = document.getElementById('toVideo');

获取视频流

数据流从一个节点传输到另一个节点,先定义变量

let stream; // 源头的数据流

let pc1; // 源
let pc2; // 目标
const offerOptions = {
    offerToReceiveAudio: 1,
    offerToReceiveVideo: 1
};

let startTime; // 记录的开始时间

srcVideo拿视频流。

function tryGetStream() {
    if (stream) {
        console.log('已经有视频流了,这里跳过');
        return;
    }
    if (srcVideo.captureStream) {
        stream = srcVideo.captureStream();
        console.log('captureStream获取到了视频流', stream);
        call();
    } else if (srcVideo.mozCaptureStream) {
        stream = srcVideo.mozCaptureStream();
        console.log('mozCaptureStream()获取到了视频流', stream);
        call();
    } else {
        console.log('不支持 captureStream()');
    }
}

srcVideo.oncanplay = tryGetStream; // 监听播放事件

if (srcVideo.readyState >= 3) {
    console.log('已经可以播放视频了 直接去拿视频流');
    tryGetStream();
}

srcVideo.play(); // 开始播放

和前面的例子类似,尝试用captureStream方法获取视频流。
call()方法在后面。

监听toVideo的一些事件

toVideo.onloadedmetadata = () => {
    console.log(`toVideo onloadedmetadata 宽高${toVideo.videoWidth}px, ${toVideo.videoHeight}`);
};

toVideo.onresize = () => {
    console.log(`toVideo onresize: ${toVideo.videoWidth}x${toVideo.videoHeight}`);
    if (startTime) {
        const elapsedTime = window.performance.now() - startTime;
        console.log('resize耗时: ' + elapsedTime.toFixed(3) + 'ms');
        startTime = null; // 消耗掉这个开始时间标志
    }
};

传输

新建2个RTCPeerConnection,开始传输。

function call() {
    console.log('call 开始.....');
    startTime = window.performance.now();
    const videoTracks = stream.getVideoTracks();
    const audioTracks = stream.getAudioTracks();
    if (videoTracks.length > 0) {
        console.log(`使用的视频设备: ${videoTracks[0].label}`);
    }
    if (audioTracks.length > 0) {
        console.log(`使用音频设备: ${audioTracks[0].label}`);
    }
    const servers = null;
    pc1 = new RTCPeerConnection(servers);
    console.log('创建本地节点pc1');
    pc1.onicecandidate = e => onIceCandidate(pc1, e);

    pc2 = new RTCPeerConnection(servers);
    console.log('创建远端节点pc2');
    pc2.onicecandidate = e => onIceCandidate(pc2, e);
    
    pc1.oniceconnectionstatechange = e => onIceStateChange(pc1, e);
    pc2.oniceconnectionstatechange = e => onIceStateChange(pc2, e);

    pc2.ontrack = gotRemoteStream;

    stream.getTracks().forEach(track => pc1.addTrack(track, stream));
    console.log('pc1加载视频流');

    console.log('pc1 创建offer');
    pc1.createOffer(onCreateOfferSuccess, onCreateSessionDescriptionError, offerOptions);
}


function onCreateSessionDescriptionError(error) {
    console.log(`创建会话描述出错: ${error.toString()}`);
}

function onCreateOfferSuccess(desc) {
    console.log(`Offer from pc1${desc.sdp}`);
    console.log('pc1 setLocalDescription start');
    pc1.setLocalDescription(desc, () => onSetLocalSuccess(pc1), onSetSessionDescriptionError);
    console.log('pc2 setRemoteDescription start');
    pc2.setRemoteDescription(desc, () => onSetRemoteSuccess(pc2), onSetSessionDescriptionError);
    console.log('pc2 createAnswer start');
    pc2.createAnswer(onCreateAnswerSuccess, onCreateSessionDescriptionError);
}

function onSetLocalSuccess(pc) {
    console.log(`${getName(pc)} setLocalDescription complete`);
}

function onSetRemoteSuccess(pc) {
    console.log(`${getName(pc)} setRemoteDescription complete`);
}

function onSetSessionDescriptionError(error) {
    console.log(`Failed to set session description: ${error.toString()}`);
}

function gotRemoteStream(event) {
    if (toVideo.srcObject !== event.streams[0]) {
        toVideo.srcObject = event.streams[0];
        console.log('pc2 收到远端数据流', event);
    }
}

function onCreateAnswerSuccess(desc) {
    console.log(`Answer from pc2: ${desc.sdp}`);
    console.log('pc2 setLocalDescription start');
    pc2.setLocalDescription(desc, () => onSetLocalSuccess(pc2), onSetSessionDescriptionError);
    console.log('pc1 setRemoteDescription start');
    pc1.setRemoteDescription(desc, () => onSetRemoteSuccess(pc1), onSetSessionDescriptionError);
}

function onIceCandidate(pc, event) {
    getOtherPc(pc).addIceCandidate(event.candidate)
        .then(
            () => onAddIceCandidateSuccess(pc),
            err => onAddIceCandidateError(pc, err)
        );
    console.log(`${getName(pc)} ICE candidate: ${event.candidate ? event.candidate.candidate : '(null)'}`);
}

function onAddIceCandidateSuccess(pc) {
    console.log(`${getName(pc)} addIceCandidate success`);
}

function onAddIceCandidateError(pc, error) {
    console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);
}

function onIceStateChange(pc, event) {
    if (pc) {
        console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);
        console.log('ICE state change event: ', event);
    }
}

function getName(pc) {
    return (pc === pc1) ? 'pc1' : 'pc2';
}

function getOtherPc(pc) {
    return (pc === pc1) ? pc2 : pc1;
}

call()方法发起模拟传输的流程。新建节点后,设置ICE相关接听。监听ICE候选变化和连接状态变化。
pc1创建offer,pc2应答(answer)。pc2拿到视频流后,直接显示出来。

call与相关流程图示
pc-flow.png

效果

示例效果请参考链接https://www.an.rustfisher.com/webrtc/capture/video-local-peer/pc.html

小结

本文的两个例子还没有用到真正的传输服务,因此是模拟进行视频流的传输。可以熟悉一下video元素以及captureStream的用法,和创建节点和设置ICE的流程。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。