亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

我們有時(shí)候在音頻通話過(guò)程中,想要改成視頻通話。如果掛斷當(dāng)前的通話再重新發(fā)起視頻通話就會(huì)顯得比較麻煩。
因此很多App提供了將音頻通話升級(jí)成視頻通話的功能,同時(shí)也有將視頻通話降為音頻通話的功能。

本文演示的是在本地模擬音頻通話,并且將音頻通話升級(jí)為視頻通話。

準(zhǔn)備#

界面很簡(jiǎn)單,2個(gè)video加上幾個(gè)按鈕。

<video id="localVideo" playsinline autoplay muted></video>
<video id="remoteVideo" playsinline autoplay></video>

<div>
    <button id="startBtn">開(kāi)始</button>
    <button id="callBtn">Call</button>
    <button id="upgradeBtn">升級(jí)為視頻通話</button>
    <button id="hangupBtn">掛斷</button>
</div>

用的是本地的adapter

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

js#

先來(lái)把元素拿到

const startBtn = document.getElementById('startBtn');
const callBtn = document.getElementById('callBtn');
const upgradeToVideoBtn = document.getElementById('upgradeBtn');
const hangupBtn = document.getElementById('hangupBtn');
const localVideo = document.getElementById('localVideo');   // 本地預(yù)覽
const remoteVideo = document.getElementById('remoteVideo'); // 接收方

監(jiān)聽(tīng)器#

設(shè)置一些監(jiān)聽(tīng)

localVideo.addEventListener('loadedmetadata', function () {
    console.log(`localVideo 寬高: ${this.videoWidth}px, ${this.videoHeight}px`);
});

remoteVideo.addEventListener('loadedmetadata', function () {
    console.log(`remoteVideo 寬高: ${this.videoWidth}px, ${this.videoHeight}px`);
});

let startTime;
remoteVideo.onresize = () => {
    console.log(`remoteVideo onresize 寬高: ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);
    if (startTime) {
        const elapsedTime = window.performance.now() - startTime;
        console.log(`建立連接耗時(shí): ${elapsedTime.toFixed(3)}ms`);
        startTime = null;
    }
};

startBtn.onclick = start;
callBtn.onclick = call;
upgradeToVideoBtn.onclick = upgrade;
hangupBtn.onclick = hangup;

打一些狀態(tài)變化的log

function onCreateSessionDescriptionError(error) {
    console.log(`rustfisher.com:創(chuàng)建會(huì)話描述失敗, session description err: ${error.toString()}`);
}

function onIceStateChange(pc, event) {
    if (pc) {
        console.log(`rustfisher.com:${getName(pc)} ICE狀態(tài): ${pc.iceConnectionState}`);
        console.log('rustfisher.com:ICE狀態(tài)變化: ', event);
    }
}

function onAddIceCandidateSuccess(pc) {
    console.log(`rustfisher.com:${getName(pc)} addIceCandidate success 添加ICE候選成功`);
}

function onAddIceCandidateError(pc, error) {
    console.log(`rustfisher.com:${getName(pc)} 添加ICE候選失敗 failed to add ICE Candidate: ${error.toString()}`);
}

function onSetLocalSuccess(pc) {
    console.log(`rustfisher.com:${getName(pc)} setLocalDescription 成功`);
}

function onSetSessionDescriptionError(error) {
    console.log(`rustfisher.com:設(shè)置會(huì)話描述失敗: ${error.toString()}`);
}

function onSetRemoteSuccess(pc) {
    console.log(`rustfisher.com:${getName(pc)} 設(shè)置遠(yuǎn)程描述成功 setRemoteDescription complete`);
}

// 輔助方法
function getName(pc) {
    return (pc === pc1) ? 'pc1' : 'pc2';
}

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

開(kāi)始#

獲取本地的音頻數(shù)據(jù)流,交給localVideo

function gotStream(stream) {
    console.log('獲取到了本地?cái)?shù)據(jù)流');
    localVideo.srcObject = stream;
    localStream = stream;
    callBtn.disabled = false;
}

function start() {
    console.log('請(qǐng)求本地?cái)?shù)據(jù)流 純音頻');
    startBtn.disabled = true;
    navigator.mediaDevices
        .getUserMedia({ audio: true, video: false })
        .then(gotStream)
        .catch(e => alert(`getUserMedia() error: ${e.name}`));
}

call#

發(fā)起音頻呼叫

function call() {
    callBtn.disabled = true;
    upgradeToVideoBtn.disabled = false;
    hangupBtn.disabled = false;
    console.log('開(kāi)始呼叫...');
    startTime = window.performance.now();
    const audioTracks = localStream.getAudioTracks();
    if (audioTracks.length > 0) {
        console.log(`使用的音頻設(shè)備: ${audioTracks[0].label}`);
    }
    const servers = null; // 就在本地測(cè)試
    pc1 = new RTCPeerConnection(servers);
    console.log('創(chuàng)建本地節(jié)點(diǎn) pc1');
    pc1.onicecandidate = e => onIceCandidate(pc1, e);
    pc2 = new RTCPeerConnection(servers);
    console.log('rustfisher.com:創(chuàng)建模擬遠(yuǎn)端節(jié)點(diǎn) pc2');
    pc2.onicecandidate = e => onIceCandidate(pc2, e);
    pc1.oniceconnectionstatechange = e => onIceStateChange(pc1, e);
    pc2.oniceconnectionstatechange = e => onIceStateChange(pc2, e);
    pc2.ontrack = gotRemoteStream;

    localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));
    console.log('rustfisher.com:將本地?cái)?shù)據(jù)流交給pc1');

    console.log('rustfisher.com:pc1開(kāi)始創(chuàng)建offer');
    pc1.createOffer(offerOptions).then(onCreateOfferSuccess, onCreateSessionDescriptionError);
}

function gotRemoteStream(e) {
    console.log('獲取到遠(yuǎn)程數(shù)據(jù)流', e.track, e.streams[0]);
    remoteVideo.srcObject = null;
    remoteVideo.srcObject = e.streams[0];
}

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

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

function onCreateAnswerSuccess(desc) {
    console.log(`rustfisher.com:pc2應(yīng)答成功:  ${desc.sdp}`);
    console.log('pc2 setLocalDescription start');
    pc2.setLocalDescription(desc).then(() => onSetLocalSuccess(pc2), onSetSessionDescriptionError);
    console.log('pc1 setRemoteDescription start');
    pc1.setRemoteDescription(desc).then(() => onSetRemoteSuccess(pc1), onSetSessionDescriptionError);
}
  • 創(chuàng)建RTCPeerConnection
  • 設(shè)置onicecandidate監(jiān)聽(tīng)I(yíng)CE候選
  • 設(shè)置oniceconnectionstatechange監(jiān)聽(tīng)I(yíng)CE連接狀態(tài)變化
  • 接收方監(jiān)聽(tīng)ontrack
  • 發(fā)送方pc1 addTrack將當(dāng)前數(shù)據(jù)流添加進(jìn)去
  • 發(fā)送方pc1創(chuàng)建offer createOffer
  • pc1創(chuàng)建好offer后,接收方pc2應(yīng)答 createAnswer

升級(jí)到視頻通話#

upgrade()方法處理升級(jí)操作

function upgrade() {
    upgradeToVideoBtn.disabled = true;
    navigator.mediaDevices
        .getUserMedia({ video: true })
        .then(stream => {
            console.log('rustfisher.com:獲取到了視頻流');
            const videoTracks = stream.getVideoTracks();
            if (videoTracks.length > 0) {
                console.log(`video device: ${videoTracks[0].label}`);
            }
            localStream.addTrack(videoTracks[0]);
            localVideo.srcObject = null; // 重置視頻流
            localVideo.srcObject = localStream;
            pc1.addTrack(videoTracks[0], localStream);
            return pc1.createOffer();
        })
        .then(offer => pc1.setLocalDescription(offer))
        .then(() => pc2.setRemoteDescription(pc1.localDescription))
        .then(() => pc2.createAnswer())
        .then(answer => pc2.setLocalDescription(answer))
        .then(() => pc1.setRemoteDescription(pc2.localDescription));
}

發(fā)送方去獲取音頻數(shù)據(jù)流getUserMedia。
將音頻軌道添加進(jìn)localStream,并且發(fā)送方也要添加軌道 pc1.addTrack。
創(chuàng)建offer createOffer

后面就是接收方pc2應(yīng)答

掛斷#

簡(jiǎn)單的掛斷功能如下

function hangup() {
    console.log('rustfisher.com:掛斷');
    pc1.close();
    pc2.close();
    pc1 = null;
    pc2 = null;

    const videoTracks = localStream.getVideoTracks();
    videoTracks.forEach(videoTrack => {
        videoTrack.stop();
        localStream.removeTrack(videoTrack);
    });

    localVideo.srcObject = null;
    localVideo.srcObject = localStream;

    hangupBtn.disabled = true;
    callBtn.disabled = false;
}

主要是把呼出方的流關(guān)閉掉

代碼流程描述圖#

將用戶的操作(按鈕)和主要代碼對(duì)應(yīng)起來(lái)

在音頻通話中,如何將WebRTC音頻通話升級(jí)為視頻通話

 

效果預(yù)覽#

效果預(yù)覽請(qǐng)參考WebRTC音頻通話升級(jí)為視頻通話 - AnRFDev - 博客園

原文鏈接:
https://www.cnblogs.com/rustfisher/p/15717958.html

分享到:
標(biāo)簽:WebRTC
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定