亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

擺脫客戶端?網(wǎng)頁(yè)發(fā)起直播勢(shì)在必行!

econi / 3721人閱讀

摘要:背景近幾年直播行業(yè)飛速發(fā)展,但是由于端這方面功能的長(zhǎng)時(shí)間缺失,使得直播端以客戶端為主的出現(xiàn)使得網(wǎng)頁(yè)也可以成為直播端。通過(guò)發(fā)送消息到插件調(diào)起屏幕共享。的點(diǎn)對(duì)點(diǎn)連接的過(guò)程為呼叫端給接收端發(fā)送一個(gè)信息。下面簡(jiǎn)單介紹下使用聲網(wǎng)發(fā)起直播的流程。

背景

近幾年直播行業(yè)飛速發(fā)展,但是由于Web端這方面功能的長(zhǎng)時(shí)間缺失,使得直播端以客戶端為主;WebRTC 的出現(xiàn)使得網(wǎng)頁(yè)也可以成為直播端。那么究竟WebRTC是什么呢?

WebRTC,即Web Real-Time Communication,web實(shí)時(shí)通信技術(shù)。簡(jiǎn)單地說(shuō)就是在web瀏覽器里面引入實(shí)時(shí)通信,包括音視頻通話等,它使得實(shí)時(shí)通信變成一種標(biāo)準(zhǔn)功能,任何Web應(yīng)用都無(wú)需借助第三方插件和專有軟件,而是通過(guò)JavaScript API即可完成;而且WebRTC提供了視頻會(huì)議的核心技術(shù),包括音視頻的采集、編解碼、網(wǎng)絡(luò)傳輸、展示等功能,還支持跨平臺(tái),包括主流的PC和移動(dòng)端設(shè)備。

下面介紹下需要用到的幾個(gè)API:

getUserMedia

我們可以通過(guò)調(diào)用navigator.mediaDevices.getUserMedia(constraints)去初始化一個(gè)本地的音視頻流,然后把直播流通過(guò)video標(biāo)簽播放。代碼如下:

html:

js:

const constraints = {
  audio: false,
  video: true
};

async function init(e) {
  try {
    const stream = await navigator.mediaDevices.getUserMedia(constraints);
    const video  = document.querySelector("video");
    video.srcObject = stream;
  } catch (e) {
    console.log(e, "stream init error");
  }
}
document.querySelector("#showVideo").addEventListener("click", (e) => init(e));

示例效果:

當(dāng)然,如果有多個(gè)設(shè)備,就需要考慮設(shè)備選擇和設(shè)備切換的問(wèn)題。那就需要用到下面的這個(gè)API。

設(shè)備

我們看看如何用原生的Web API去獲取設(shè)備(以下示例代碼可適用于Chrome,其他瀏覽器暫未測(cè)試;具體瀏覽器兼容性可參考官方文檔,本文檔底部有鏈接)。

navigator.mediaDevices.enumerateDevices()

如果枚舉成功將會(huì)返回一個(gè)包含MediaDeviceInfo實(shí)例的數(shù)組,它包含了可用的多媒體輸入輸出設(shè)備的信息。

下面是調(diào)用代碼示例。

navigator.mediaDevices.enumerateDevices().then((devices) => {
  console.log(devices, "-----enumerateDevices------");
});

設(shè)備參數(shù)說(shuō)明:

deviceId:設(shè)備id,具有唯一性

groupId:設(shè)備組id,不具有唯一性

kind:設(shè)備類別(audioinput:音頻輸入設(shè)備,audiooutput:音頻輸出設(shè)備,videoinput:視頻輸入設(shè)備)

label:設(shè)備名稱(未經(jīng)過(guò)授權(quán)允許的設(shè)備,label值為空,授權(quán)允許后可拿到label的值,如下兩圖所示)

獲取的所有設(shè)備截圖(未授權(quán)):

videoinput已授權(quán)截圖:

獲取到設(shè)備列表后,可設(shè)置navigator.mediaDevices.getUserMedia(constraints)的constraints參數(shù)選擇所用設(shè)備。

const { audioList, videoList } = await getDevices();
const constraints = {
  audio: {
    deviceId: audioList[0].deviceId
  },
  video: {
    deviceId: videoList[0].deviceId
  }
};
navigator.mediaDevices.getUserMedia(constraints);

然而,我們?cè)诟鼡QdeviceId切換設(shè)備的時(shí)候發(fā)現(xiàn)一些異常情況。在某些deviceId之間切換時(shí),攝像頭畫(huà)面或者是麥克風(fēng)采集處并沒(méi)有發(fā)生變化。進(jìn)一步調(diào)試發(fā)現(xiàn),這些切換后沒(méi)有發(fā)生變化的deviceId都具有相同的groupId。因此,相同groupId下的設(shè)備,選擇一個(gè)用于切換即可。

篩選麥克風(fēng)、攝像頭設(shè)備示例:

function getDevices() {
  return new Promise((resolve) => {
    navigator.mediaDevices.enumerateDevices().then((devices) => {
      const audioGroup = {};
      const videoGroup = {};
      const cameraList = [];
      const micList = [];
      devices.forEach((device, index) => {
        if ((!device.groupId || !audioGroup[device.groupId]) && device.kind === "audioinput") {
          micList.push(device);
          audioGroup[device.groupId] = true;
        }

        if ((!device.groupId || !videoGroup[device.groupId]) && device.kind === "videoinput") {
          cameraList.push(device);
          videoGroup[device.groupId] = true;
        }
      });
      resolve({ cameraList, micList });
    });
  });
}

注意:在Chrome下,電腦外接攝像頭后拔出設(shè)備,此時(shí)還有可能獲取到拔出的設(shè)備信息,在進(jìn)行切換的時(shí)候會(huì)有問(wèn)題,可以采用在頁(yè)面進(jìn)行友好提示處理這種情況。

屏幕共享 MediaDevices.getDisplayMedia

Chrome 72+、Firefox 66+版本已經(jīng)實(shí)現(xiàn)了WebRTC規(guī)范中的MediaDevices.getDisplayMedia,具備屏幕共享功能。

navigator.mediaDevices.getDisplayMedia({
  video: true,
  audio: false
}).then(stream => {
  video.srcObject = stream;
}).catch(err => {
  console.error(err);
});

示例效果:

對(duì)于Chrome 72以下的版本,想要實(shí)現(xiàn)屏幕共享的功能需要借助Chrome插件去獲取screen(顯示器屏幕)、application windows(應(yīng)用窗口)和browser tabs(瀏覽器標(biāo)簽頁(yè))。 Chrome插件:由manifest.json和script.js組成。

manifest.json 填入一些基本數(shù)據(jù)。

background中scripts傳入需執(zhí)行的js文件。

添加permissions: ["desktopCapture"],用來(lái)開(kāi)啟屏幕共享的權(quán)限。

externally_connectable用來(lái)聲明哪些應(yīng)用和網(wǎng)頁(yè)可以通過(guò)runtime.connectruntime.sendMessage連接到插件。

{
   "manifest_version": 2,
   "name": "Polyv Web Screensharing",
   "permissions": [ "desktopCapture" ],
   "version": "0.0.1",
   "background": {
      "persistent": false,
      "scripts": [ "script.js" ]
   },
   "externally_connectable": {
      "matches": ["*://localhost:*/*"]
   }
}

script.js

// script.js
chrome.runtime.onMessageExternal.addListener(
  function(request, sender, sendResponse) {
    if (request.getStream) {
      // Gets chrome media stream token and returns it in the response.
      chrome.desktopCapture.chooseDesktopMedia(
        ["screen", "window", "tab"], sender.tab,
        function(streamId) {
          sendResponse({ streamId: streamId });
        });
      return true; // Preserve sendResponse for future use
    }
  }
);

在頁(yè)面中開(kāi)始屏幕共享。通過(guò)chrome.runtime.sendMessage發(fā)送消息到Chrome插件調(diào)起屏幕共享。獲取到streamId后,通過(guò)mediaDevices.getUserMedia得到stream。

const EXTENSION_ID = "";
const video = $("#videoId");
chrome.runtime.sendMessage(EXTENSION_ID, { getStream: true }, res => {
  console.log("res: ", res);
  if (res.streamId) {
    navigator.mediaDevices.getUserMedia({
      video: {
        mandatory: {
          chromeMediaSource: "desktop",
          chromeMediaSourceId: res.streamId
        }
      }
    }).then((stream) => {
      video.srcObject = stream;
      video.onloadedmetadata = function(e) {
        video.play();
      };
    })
  } else {
    // 取消選擇
  }
});

而Firefox 66版本以下,不需要像Chrome借助插件才能實(shí)現(xiàn)屏幕共享。Firefox 33之后可以直接通過(guò)使用mediaDevices.getUserMedia,指定約束對(duì)象mediaSource為screen、window、application來(lái)實(shí)現(xiàn)屏幕共享。不過(guò)在Firefox中,一次只能指定一種mediaSource。

navigator.mediaDevices.getUserMedia({
  video: {
    mediaSource: "window" 
  }
}).then(stream => {
    video.srcObject = stream;
});
傳輸

WebRTC的RTCPeerConnection可以建立點(diǎn)對(duì)點(diǎn)連接通信,RTCDataChannel提供了數(shù)據(jù)通信的能力。

WebRTC的點(diǎn)對(duì)點(diǎn)連接的過(guò)程為:

呼叫端給接收端發(fā)送一個(gè)offer信息。在發(fā)送給接收端之前先調(diào)用setLocalDescription存儲(chǔ)本地offer描述。

接收端收到offer消息后,先調(diào)用setRemoteDescription存儲(chǔ)遠(yuǎn)端offer,再創(chuàng)建一個(gè)answer信息給呼叫端。

RTCDataChannel提供了send方法和message事件。使用起來(lái)與WebSocket類似。

由于沒(méi)有服務(wù)器,以下代碼為呼叫端和接收端在同一頁(yè)面上,RTCPeerConnection對(duì)象之間是如何進(jìn)行數(shù)據(jù)交互。

// 創(chuàng)建數(shù)據(jù)通道
sendChannel = localConnection.createDataChannel("通道名稱", options);
sendChannel.binaryType = "arraybuffer";
  sendChannel.onopen = function() {
  sendChannel.send("Hi there!");
};
sendChannel.onmessage = function(evt) {
  console.log("send channel onmessage: ", evt.data);
};

// 遠(yuǎn)端接收實(shí)例
remoteConnection = new RTCPeerConnection(servers);
remoteConnection.onicecandidate = function(evt) {
  if (evt.candidate) {
    localConnection.addIceCandidate(new RTCIceCandidate(evt.candidate));
  }
};
// 當(dāng)一個(gè)RTC數(shù)據(jù)通道已被遠(yuǎn)端調(diào)用createDataChannel()添加到連接中時(shí)觸發(fā)
remoteConnection.ondatachannel = function() {
  const receiveChannel = event.channel;
  receiveChannel.binaryType = "arraybuffer";
  //接收到數(shù)據(jù)時(shí)觸發(fā)
  receiveChannel.onmessage = function(evt) {
    console.log("onmessage", evt.data); // log: Hi there!
  };
  receiveChannel.send("Nice!");
};

// 監(jiān)聽(tīng)是否有媒體流
remoteConnection.onaddstream = function(e) {
  peerVideo.srcObject = e.stream;
};

localConnection.addStream(stream);

// 創(chuàng)建呼叫實(shí)例
localConnection.createOffer().then(offer => {
  localConnection.setLocalDescription(offer);
  remoteConnection.setRemoteDescription(offer);
  remoteConnection.createAnswer().then(answer => {
    remoteConnection.setLocalDescription(answer);
    // 接收到answer
    localConnection.setRemoteDescription(answer);
  })
});

至此我們已經(jīng)介紹完畢瀏覽器設(shè)備檢測(cè)采集和屏幕分享的基本流程,但是光有這些可還遠(yuǎn)遠(yuǎn)不夠,一套完整的直播體系包括音視頻采集、處理、編碼和封裝、推流到服務(wù)器、服務(wù)器流分發(fā)、播放器流播放等等。如果想節(jié)省開(kāi)發(fā)成本,可以使用第三方SDK。下面簡(jiǎn)單介紹下使用聲網(wǎng)SDK發(fā)起直播的流程。

瀏覽器要求:

Chrome 58+

Firefox 56+

Safari 11+(屏幕共享不可用)

Opera 45+(屏幕共享不可用)

QQ 10+(屏幕共享不可用)

360 安全瀏覽器 9.1+(屏幕共享不可用)

設(shè)備檢測(cè)

調(diào)用AgoraRTC.getDevices獲取當(dāng)前瀏覽器檢測(cè)到的所有可枚舉設(shè)備,kind為"videoinput"是攝像頭設(shè)備,kind為"audioinput"是麥克風(fēng)設(shè)備,然后通過(guò)createStream初始化一個(gè)本地的流。 獲取設(shè)備:

AgoraRTC.getDevices((devices) => {
    const audioGroup = {};
    const videoGroup = {};
    const cameraList = [];
    const micList = [];
    devices.forEach((device, index) => {
        if ((!device.groupId || !audioGroup[device.groupId]) && device.kind === "audioinput") {
            micList.push(device);
            audioGroup[device.groupId] = true;
        }

        if ((!device.groupId || !videoGroup[device.groupId]) && device.kind === "videoinput") {
            cameraList.push(device);
            videoGroup[device.groupId] = true;
        }
    });
    return { cameraList, micList };
});

初始化本地流:

// uid:自定義頻道號(hào),cameraId設(shè)備Id
const stream = AgoraRTC.createStream({
    streamID: uid,
    audio: false,
    video: true,
    cameraId: cameraId,
    microphoneId: microphoneId
});
stream.init(() => {
    // clientCamera 
stream.play("clientCamera", { muted: true }); }, err => { console.error("AgoraRTC client init failed", err); });

stream.init()初始化直播流;如果當(dāng)前瀏覽器攝像頭權(quán)限為禁止,則調(diào)用失敗,可捕獲報(bào)錯(cuò)Media access NotAllowedError: Permission denied; 若攝像頭權(quán)限為詢問(wèn),瀏覽器默認(rèn)彈窗是否允許使用攝像頭,允許后調(diào)用play()可看到攝像頭捕獲的畫(huà)面。 如果不傳入cameraId,SDK會(huì)默認(rèn)獲取到設(shè)備的deviceId,如果權(quán)限是允許,同樣會(huì)顯示攝像頭畫(huà)面。

采集 攝像頭

順利拿到cameraId和microphoneId后就可以進(jìn)行直播。通過(guò)SDK提供的createStream創(chuàng)建一個(gè)音視頻流對(duì)象。執(zhí)行init方法初始化成功之后,播放音視頻(見(jiàn)上文)。最后通過(guò)client發(fā)布流以及推流到CDN(見(jiàn)下文)。

屏幕共享

Web 端屏幕共享,通過(guò)創(chuàng)建一個(gè)屏幕共享的流來(lái)實(shí)現(xiàn)的。Chrome屏幕共享需要下載插件,在創(chuàng)建的流的時(shí)候還需要傳入插件的extensionId。

const screenStream = AgoraRTC.createStream({
    streamID: ,
    audio: false,
    video: false,
    screen: true,
    extensionId: , // Chrome 插件id
    mediaSource: "screen" // Firefox
});
傳輸

通過(guò)AgoraRTC.createStream創(chuàng)建的音視頻流,通過(guò)publish發(fā)送到第三方服務(wù)商的SD-RTN(軟件定義實(shí)時(shí)傳輸網(wǎng)絡(luò))。

client.publish(screenStream, err => {
  console.error(err);
});

別的瀏覽器可以通過(guò)監(jiān)聽(tīng)到stream-added事件,通過(guò)subscribe訂閱遠(yuǎn)端音視頻流。

client.on("stream-added", evt => {
  const stream = evt.stream;
  client.subscribe(stream, err => {
    console.error(err);
  });
});

再通過(guò)startLiveStreaming推流到CDN。

// 編碼
client.setLiveTranscoding();
client.startLiveStreaming(, true)

在推攝像頭流的時(shí)候,關(guān)閉攝像頭,需要推一張占位圖。這個(gè)時(shí)候先用canvas畫(huà)圖,然后用WebRTC提供的captureStream捕獲靜態(tài)幀。再調(diào)用getVideoTracks,制定AgoraRTC.createStream的videoSource為該值。視頻源如來(lái)自 canvas,需要在 canvas 內(nèi)容不變時(shí),每隔 1 秒重新繪制 canvas 內(nèi)容,以保持視頻流的正常發(fā)布。

const canvas = document.createElement("canvas");
renderCanvas(canvas);
setInterval(() => {
  renderCanvas(canvas);
}, 1000);
canvasStream = canvas.captureStream();

const picStream = AgoraRTC.createStream({
  streamID: ,
  video: true,
  audio: false,
  videoSource: canvasStream.getVideoTracks()[0]
});

// 畫(huà)圖
function renderCanvas(canvas) {
  ...
}

一個(gè)client只能推一個(gè)流,所以在進(jìn)行屏幕共享的時(shí)候,需要?jiǎng)?chuàng)建兩個(gè)client,一個(gè)發(fā)送屏幕共享流,一個(gè)發(fā)送視頻流。屏幕共享流的video字段設(shè)為false。視頻流的video字段設(shè)為true。然后先通過(guò)setLiveTranscoding合圖再推流。

const users = [
  {
    x: 0, // 視頻幀左上角的橫軸位置,默認(rèn)為0
    y: 0, // 視頻幀左上角的縱軸位置,默認(rèn)為0
    width: 1280, // 視頻幀寬度,默認(rèn)為640
    height: 720, // 視頻幀高度,默認(rèn)為360
    zOrder: 0, // 視頻幀所處層數(shù);取值范圍為 [0,100];默認(rèn)值為 0,表示該區(qū)域圖像位于最下層
    alpha: 1.0, // 視頻幀的透明度,默認(rèn)值為 1.0
    uid: 888888, // 旁路推流的用戶 ID
  },
  {
    x: 0,
    y: 0,
    width: 1280,
    height: 720,
    zOrder: 1,
    alpha: 1.0,
    uid: 999999
  }
];
    
var liveTranscoding = {
  width: 640,
  height: 360,
  videoBitrate: 400,
  videoFramerate: 15,
  lowLatency: false,
  audioSampleRate: AgoraRTC.AUDIO_SAMPLE_RATE_48000,
  audioBitrate: 48,
  audioChannels: 1,
  videoGop: 30,
  videoCodecProfile: AgoraRTC.VIDEO_CODEC_PROFILE_HIGH,
  userCount: user.length,
  backgroundColor: 0x000000,
  transcodingUsers: users,
};
client.setLiveTranscoding(liveTranscoding);

因?yàn)闃I(yè)務(wù)需求是攝像頭和屏幕共享可以切換,攝像頭和屏幕共享的分辨率和碼率均不相同,屏幕共享需要更高的分辨率和碼率。但是開(kāi)發(fā)中發(fā)現(xiàn)切換時(shí)設(shè)置碼率無(wú)效。SDK那邊給的答復(fù)是:因?yàn)榫彺鎲?wèn)題,會(huì)以第一次推流設(shè)置的參數(shù)為準(zhǔn),將會(huì)在下個(gè)版本中修復(fù)。

參考文獻(xiàn):
MediaDevices.getUserMedia()
MedaiDevices.enumerateDevices()
HTMLMediaElement
MediaDevices/getDisplayMedia

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/104674.html

相關(guān)文章

  • 低代碼:當(dāng)今最新的流行語(yǔ)還是名歸實(shí)至?

    摘要:低代碼技術(shù)近期受到了廣泛的關(guān)注。談到低代碼,答案有兩部分當(dāng)今組織敏捷性勢(shì)在必行敏捷和快速有效地響應(yīng)內(nèi)外部變化的能力通常是現(xiàn)代企業(yè)成功最關(guān)的鍵因素。 低代碼技術(shù)近期受到了廣泛的關(guān)注。即使是領(lǐng)先的分析公司也對(duì)這一趨勢(shì)表達(dá)了自己的看法;事實(shí)上,F(xiàn)orrester預(yù)測(cè)低代碼收入增長(zhǎng)將超過(guò)68%,到2020年整體市場(chǎng)規(guī)模達(dá)到155億美元。 那么為什么低代碼解決方案最近受到如此多的關(guān)注呢? 它有保...

    mengera88 評(píng)論0 收藏0
  • 保利威無(wú)延遲直播:全面增強(qiáng)互動(dòng)體驗(yàn),大班課、帶貨直播轉(zhuǎn)化更高效!

    摘要:保利威無(wú)延遲直播可以在手機(jī)電腦上實(shí)現(xiàn)高參與度的互動(dòng),讓課堂體驗(yàn)再上一層,對(duì)于提升公開(kāi)課轉(zhuǎn)化率非常有幫助。無(wú)延遲體驗(yàn)?zāi)茏屩辈ジ咏€下體驗(yàn)。 ? ? 衡量一場(chǎng)直播是否成功,用戶互動(dòng)體驗(yàn)必然是關(guān)鍵一環(huán)。 ? 今年疫情影響下,云辦公、云上課、云會(huì)展、云購(gòu)物紛紛興起。帶貨直播、空中課堂、會(huì)展直播等多樣化的場(chǎng)景讓用戶對(duì)直播實(shí)時(shí)性、流暢性有了更高要求。 ? ...

    不知名網(wǎng)友 評(píng)論0 收藏0
  • UCloud+保利威=?Polyv云直播U享版PLive“出世“!提供2000分鐘云直播免費(fèi)測(cè)試額度

    摘要:視頻云聯(lián)合大促活動(dòng)對(duì)象新老用戶均可參加,各規(guī)則限購(gòu)次。云直播特惠套餐無(wú)需開(kāi)發(fā)提供超項(xiàng)功能全終端低延遲開(kāi)箱即用的直播服務(wù)。功能完善超項(xiàng)直播功能,可實(shí)現(xiàn)企業(yè)各類直播的互動(dòng)營(yíng)銷定制大數(shù)據(jù)運(yùn)營(yíng)等需求。 UCloud+保利威=?UCloud最近新上線了【CDN&視頻云聯(lián)合大促】活動(dòng):CDN低至0.01元/GB 9.9元搶實(shí)時(shí)音視頻10萬(wàn)分鐘時(shí)長(zhǎng)包,除了CDN流量包、URTC實(shí)時(shí)音視頻時(shí)長(zhǎng)包和云直播U...

    zollero 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

econi

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<