[WebService] Tokbox, OpenTok 開發筆記
- Tokbox Developer Center @ Official Website
- OpenTok Network Test @ Github
筆記
- 一個使用者在連上 session 後,會得到一個
connectionId
,所以一個使用者只會有一個獨立的connectionId
。 - 但一個使用者可以建立多個
publisher
和subscriber
,每 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 物件:在
connectionCreated
和connectionDestroyed
事件中,都可以得到 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); // 自己和他人的都同時會觸發,可以取得 connectionId
session.on('connectionDestroyed', handleConnectionDestroyed); // 只有他人的會觸發,可以取得 connectionId
session.on('streamCreated', handleStreamCreated); // 只有他人的會觸發,可以取得 stream 和 connectionId
session.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
- 建立專案並載入 @opentok/client
- 設定驗證資訊,包含
apiKey
、sessionId
和token
- 初始化 session 並與 session 建立連線;初始化 publisher 並發佈到 session 上
session = OT.initSession(apiKey, sessionId)
:初始化 sessionsession.connect(token, (error) => {...})
:與 session 建立連線。在傳送或接收影音串連時前需要先與 session 建立連線。
- 註冊和 session 相關的事件
session.on('streamCreated', (event) => {...})
- 初始化 publisher,並在需要時將其發佈
publisher = OT.initPublisher(<element>, options, errorHandler)
:初始化 publishersession.publish(publisher)
:將 publisher 的影音串流發佈到 session
- 初始化 subscriber,並訂閱他人的影音串流
- 當新的影音串流在 session 中被建立時,Session 物件會發送
streamCreated
的事件,client 偵測到此事件時,可以選擇訂閱此串 流(stream) session.on('streamCreated', (event) => {session.subscribe(event.stream), <element>, options, errorHandler})
- 當新的影音串流在 session 中被建立時,Session 物件會發送
共享螢幕 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>)
中 properties
的 videoSource
即可,可接受的參數為 "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
可以是 camera
或 screen
:
// 透過 event.stream.videoType 可以得知該串流的 videoSource
session.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
, oldValue
和 target
這些屬性,你可以用這些屬性來調整 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 強制使用者停止共享
}
});
不論是 mediaStopped
或 streamDestroyed
事件,他們預設的行為都是刪除 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)
- Quick Start Example @ CodePen
- Quick Start @ Tokbox
- @opentok/client @ npm
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 library
import OT from '@opentok/client';
// STEP 1: Authentication credentials
const 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;