Skip to main content

[WebService] Tokbox, OpenTok 開發筆記

筆記#

  • 一個使用者在連上 session 後,會得到一個 connectionId,所以一個使用者只會有一個獨立的 connectionId
  • 但一個使用者可以建立多個 publishersubscriber,每 publish 一個 publisher 就會產生一個 streamId,因此一個使用者可以有多個 streamId

Connection Id(連上 session 時)#

  • 連結 Session:有任何使用者(包含自己) Connect Session 時(session.connect())會觸發每個人 Session 物件的 connectionCreated 事件。如果你是第一次連入此 Session,則也會一併觸發所有先前已經在該 Session 內的使用者。在此 session 物件中(或 sessionConnected 事件)可以取得 connectionId (i.e., session.connection.connectionId)。
  • 斷開 Session:有任何除了自己以外的使用者斷開與 session 的連結時(session.disconnect()),會觸發 Session 物件的 connectionDestroyed 事件。使用者自己斷開連結時,則不會觸發 connectionDestroyed 事件,而是會觸發 sessionDisconnected 事件,因此當接收到 sessionDisconnected 事件時,一定是該使用者自己與 session 斷開連接
  • 取得 ConnectionEvent 物件:在 connectionCreatedconnectionDestroyed 事件中,都可以得到 ConnectionEvent 物件,可以從此物件中得到連接/斷連者的 connectionId (i.e., event.connection.connectionId)。
  • 取得 StreamEvent 物件:有任何除了自己以外的使用者在 session 發佈串流時,會觸發 streamCreated 事件,在這事件中會得 StreamEvent 物件,裡面可以取得用來訂閱(subscribe)的 stream 物件(即,event.stream),同時也包含 Connection 物件 在內,因此可以由 Connection 物件中的 connectionId (i.e., event.stream.connection.connectionId) 來判斷是屬於哪個使用者的串流。

Stream Id(發佈串流時 - publisher)#

  • 一個使用者在一個 Session 中只會有一個 connectionId,但他可以建立多個 Publisher,每個 publisher 都可以有獨自的串流,因此可以有多個 streamId
  • 取得自己的 streamId 可以透過 publisher.streamId
  • 取得他人的 streamId 可以透過監聽 streamCreated 事件中的 StreamEvent 取得(即,event.stream.streamId)。
session.on('connectionCreated', handleConnectionCreated); // 自己和他人的都同時會觸發,可以取得 connectionIdsession.on('connectionDestroyed', handleConnectionDestroyed); // 只有他人的會觸發,可以取得 connectionIdsession.on('streamCreated', handleStreamCreated); // 只有他人的會觸發,可以取得 stream 和 connectionIdsession.on('streamDestroyed', handleStreamDestroyed); // 只有他人的會觸發,可以取得 stream 和 connectionId
/* DEPRECATED */session.on('sessionDisconnected', handleSessionDisconnected); // 只有自己的會觸發,可以取得 connectionId

Moderator#

Moderation @ Tokbox

只有 Moderator 可以讓某個 clients 中斷連線(session.forceDisconnect(connection)),和中斷發佈(session.forceUnpublish(stream))。

Set up a Basic Web Client#

Set up a Basic Web Client @ Tokbox Developer

  1. 建立專案並載入 @opentok/client
  2. 設定驗證資訊,包含 apiKeysessionIdtoken
  3. 初始化 session 並與 session 建立連線;初始化 publisher 並發佈到 session 上
    • session = OT.initSession(apiKey, sessionId):初始化 session
    • session.connect(token, (error) => {...}):與 session 建立連線。在傳送或接收影音串連時前需要先與 session 建立連線。
  4. 註冊和 session 相關的事件
    • session.on('streamCreated', (event) => {...})
  5. 初始化 publisher,並在需要時將其發佈
    • publisher = OT.initPublisher(<element>, options, errorHandler):初始化 publisher
    • session.publish(publisher):將 publisher 的影音串流發佈到 session
  6. 初始化 subscriber,並訂閱他人的影音串流
    • 當新的影音串流在 session 中被建立時,Session 物件會發送 streamCreated 的事件,client 偵測到此事件時,可以選擇訂閱此串流(stream)
    • session.on('streamCreated', (event) => {session.subscribe(event.stream), <element>, options, errorHandler})

共享螢幕 Screen Sharing#

keywords: 共享螢幕, screen sharing, 分享螢幕, 畫面分享, 擷取螢幕#

Screen Sharing @ Developer Guides

注意項目#

  • 在 Chrome 72+, Firefox 52+ 和 Opera 59+ 之後,不再需要安裝瀏覽器的套件才能做到 screen sharing 的功能。
  • 在 macOS Catalina 後的作業系統,若要使用 screen-sharing 的功能,需要在「系統偏好設定 > 安全與隱私 > 隱私 > 螢幕錄影」中提供瀏覽器存取權限,否則 Publisher 會發送 accessDenied 的事件。
  • 目前在 Safari 和 Edge 不支援發佈螢幕錄影 @ 2020.01.02
  • 網頁需要透過 HTTPs 傳送資料才能使用

檢查瀏覽器是否支援分享螢幕#

透過 OT.checkScreenSharingCapability(<callback(response)>) 可以檢查該瀏覽器是否支援分享螢幕畫面:

OT.checkScreenSharingCapability((response) => {  const {    supported, // Boolean,該瀏覽器是否支援    supportedSources, // Object,顯示 application, screen, window 為 true / false    extensionRequired, // String,"chrome" 或 "Opera",針對較舊的瀏覽器須額外安裝套件    extensionRegistered, // Boolean,針對較舊的瀏覽器須額外註冊套件    extensionInstalled, // Boolean,針對較舊的瀏覽器須額外安裝套件  } = response;
  if (!supported || extensionRegistered === false) {    // This browser does not support screen sharing    console.log('This browser does not support screen sharing');  } else if (extensionInstalled === false) {    // Prompt to install the extension    console.log('Prompt to install the extension');  } else {    // Screen sharing is available.    console.log('Screen sharing is available.');  }});

將共享螢幕串流發佈#

要讓 client 共享螢幕,只需要設定 OT.initPublisher(<targetElement>, <properties>, <completionHandler>)propertiesvideoSource 即可,可接受的參數為 "screen", "application", "window"

  • 在 Firefox 中,所有這三個設定都是支援的
  • 在 Chrome 中,參數帶入 screen 後使用者可以選擇想要分享的來源
const publisher = OT.initPublisher(  'screen-preview',  {    videoSource: 'screen',  },  (error) => {    if (error) {      // Look at error.message to see what went wrong.    } else {      session.publish(publisher, (error) => {        if (error) {          // Look error.message to see what went wrong.        }      });    }  },);

initPublisher 中 property 在分享螢幕時預設的設定如下:

const publisher = OT.initPublisher(  'screen-preview',  {    videoSource: 'screen',    // 在分享螢幕時,預設的屬性值    maxResolution: { width: 1920, height: 1920 },    mirror: false,    fitMode: 'contain',    publishAudio: false,  },  error => {...});

其中在共享螢幕時,不要設定 resolution 這個屬性

避免共享畫面中看到自己的共享畫面#

此外在共享螢幕畫面時,如果使用者又能看到自己的螢幕畫面,會出現螢幕畫面裡又有螢幕畫面的情況,這種情形稱作 recursive "hall of mirrors" effect。要避免這樣的情況,只需額外建立一個 div 即可:

var screenPublisherElement = document.createElement('div');OT.initPublisher(screenPublisherElement, { videoSource: 'screen' });

透過 stream.videoType 了解串流的來源#

可以透過 stream 物件中的 videoType 屬性來得知該串流的 videoSource 為何,videoType 可以是 camerascreen

// 透過 event.stream.videoType 可以得知該串流的 videoSourcesession.on('streamCreated', function (event) {  var subOptions = { insertMode: 'append' };  if (event.stream.videoType === 'screen') {    // <div id="screens"></div>    session.subscribe(event.stream, 'screens', subOptions);  } else {    // <div id="people"></div>    session.subscribe(event.stream, 'people', subOptions);  }});

偵測使用者畫面大小改變#

當 client 共享的螢幕尺寸有變更時,publisher 和 subscriber 都會收到 videoDimensionsChanged 事件,這個事件(event)中會帶有 newValue, oldValuetarget 這些屬性,你可以用這些屬性來調整 publisher 在 DOM 中的大小:

var publisher = OT.initPublisher('some-element', { videoSource: 'screen' });
publisher.on('videoDimensionsChanged', function (event) {  const {    newValue,    oldValue,    target, // publisher 或 subscriber  } = event;  publisher.element.style.width = newValue.width + 'px';  publisher.element.style.height = newValue.height + 'px';});

當使用者停止共享螢幕#

當使用者停止共享螢幕時,Publisher 物件會發出 mediaStopped 事件。

若此 publisher 同時有發佈串流到 session 上,則會同時發送 streamDestroyed 事件,並且在該事件(event)中的 reason 屬性的值會是 mediaStopped;同時,所有有連結到該 session 的 clients,他們的 Session 物件都會發送 streamDestroyed 事件,並且事件(event)中的 reason 屬性值會是 mediaStopped

// 當使用者點擊停止共享畫面時publisher.on('mediaStopped', (event) => {...});
// 當使用者同時將串流發佈到 session 上時publisher.on('streamDestroyed', (event) => {  const { reason } = event;  if (reason === 'mediaStopped') {    // 若使用者點擊停止共享...  } else if (reason === 'forceUnpublished') {    // 若 moderator 強制使用者停止共享  }});

不論是 mediaStoppedstreamDestroyed 事件,他們預設的行為都是刪除 publisher(或 subscriber),如果要避免被刪除的話,可以呼叫 preventDefault() 方法。

訂閱共享螢幕的串流#

訂閱共享螢幕串流的方式與訂閱其他影音串流的方式相同,stream 物件中的 videoType 可以幫助判斷串流的來源是屬於螢幕(screen)或攝像頭(camera)。

session.on('streamCreated', function (event) {  var subOptions = {    appendMode: 'append',  };
  var parentElementId =    event.stream.videoType === 'screen' ? 'sub-screen-sharing-container' : 'sub-camera-container';  subscriber = session.subscribe(event.stream, parentElementId, subOptions);});

當 client 分享畫面的大小有所改變時,subscriber 一樣可以透過 videoDimensionsChanged 的事件來改變 HTML DOM 元素的大小:

subscriber.on('videoDimensionsChanged', function (event) {  const { newValue, oldValue, target } = event;  subscriber.element.style.width = newValue.width + 'px';  subscriber.element.style.height = newValue.height + 'px';  // You may want to adjust other UI.});

Tokbox Quick Start (Hello World Example)#

html#

<!-- STEP 0: load tokbox library --><!DOCTYPE html><html lang="en">  <head>    <title>React App</title>    <!-- OpenTok.js library -->    <script src="https://static.opentok.com/v2/js/opentok.js"></script>  </head>  <body>    <noscript>You need to enable JavaScript to run this app.</noscript>    <div id="root"></div>  </body></html>

JavaScript#

/* global OT */import React, { useEffect } from 'react';import './App.css';
// STEP 0: import opentok libraryimport OT from '@opentok/client';
// STEP 1: Authentication credentialsconst apiKey = '45828062';const sessionId = '2_MX40NTgyODA2Mn5-MTU3NzY3NDc3Njc1NH4wajIwUVNFUWpvemxGMk5QQnhzQW1aL1J-UH4';const token =  'T1==cGFydG5lcl9pZD00NTgyODA2MiZzaWc9ZGJmZGMzMzA5ODM5Nzc3MmY3MWU4Y2ZhYjlhMTQ4Y2Y0ZGU2NzRjMDpzZXNzaW9uX2lkPTJfTVg0ME5UZ3lPREEyTW41LU1UVTNOelkzTkRjM05qYzFOSDR3YWpJd1VWTkZVV3B2ZW14R01rNVFRbmh6UVcxYUwxSi1VSDQmY3JlYXRlX3RpbWU9MTU3NzY3NDc4NCZub25jZT0wLjk3NTk5NTYzMTY3OTE2NTEmcm9sZT1wdWJsaXNoZXImZXhwaXJlX3RpbWU9MTU3Nzc2MTE4NA==';
function App() {  useEffect(() => {    // STEP 2: connect to Session and create a Publisher,這時候就會請求前鏡頭權限    var session = OT.initSession(apiKey, sessionId);    var publisher = OT.initPublisher();    session.connect(token, function (err) {      // STEP 3: publish publisher,這時候會把影音串流發佈到 session      session.publish(publisher);    });
    // STEP 4: create subscriber,訂閱其他人在同一 session 上的影音串流    session.on('streamCreated', function (event) {      session.subscribe(event.stream);    });  });
  return (    <div className="App">      <header className="App-header"></header>    </div>  );}
export default App;