├── .eslintignore ├── bundler ├── shims │ └── process.js └── private.mjs ├── packages ├── core │ ├── src │ │ ├── version.ts │ │ ├── const.ts │ │ ├── plugin │ │ │ ├── interface │ │ │ │ ├── index.ts │ │ │ │ ├── plugin.ts │ │ │ │ └── connection.ts │ │ │ └── internal │ │ │ │ ├── person │ │ │ │ ├── connection │ │ │ │ │ ├── datachannel.ts │ │ │ │ │ └── messageBuffer.ts │ │ │ │ ├── plugin.ts │ │ │ │ └── util.ts │ │ │ │ └── unknown │ │ │ │ ├── plugin.ts │ │ │ │ ├── member.ts │ │ │ │ └── connection.ts │ │ ├── member │ │ │ ├── localPerson │ │ │ │ ├── agent │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── subscribing.ts │ │ │ │ │ └── publishing.ts │ │ │ │ └── factory.ts │ │ │ ├── person.ts │ │ │ └── remoteMember.ts │ │ ├── media │ │ │ ├── stream │ │ │ │ ├── index.ts │ │ │ │ ├── remote │ │ │ │ │ ├── video.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── audio.ts │ │ │ │ │ ├── data.ts │ │ │ │ │ ├── media.ts │ │ │ │ │ ├── factory.ts │ │ │ │ │ └── base.ts │ │ │ │ ├── local │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── customVideo.ts │ │ │ │ │ ├── data.ts │ │ │ │ │ ├── video.ts │ │ │ │ │ └── audio.ts │ │ │ │ ├── base.ts │ │ │ │ └── audioLevel.ts │ │ │ └── index.ts │ │ ├── validation.ts │ │ ├── subscription │ │ │ └── factory.ts │ │ ├── channel │ │ │ └── event.ts │ │ ├── index.ts │ │ └── publication │ │ │ └── factory.ts │ ├── tsconfig.json │ ├── tsconfig.build.json │ ├── jest.config.js │ ├── vitest.large.ts │ ├── vitest.middle.ts │ ├── vitest.small.ts │ ├── jest.setup.js │ ├── bundle.mjs │ └── LICENSE ├── room │ ├── src │ │ ├── const.ts │ │ ├── version.ts │ │ ├── util.ts │ │ ├── room │ │ │ ├── event.ts │ │ │ ├── p2p.ts │ │ │ └── sfu.ts │ │ ├── errors.ts │ │ ├── index.ts │ │ └── member │ │ │ ├── local │ │ │ ├── p2p.ts │ │ │ └── sfu.ts │ │ │ └── index.ts │ ├── tsconfig.json │ ├── tsconfig.build.json │ ├── jest.config.js │ ├── vitest.extra.ts │ ├── vitest.large.ts │ ├── jest.setup.js │ ├── LICENSE │ ├── bundle.mjs │ └── package.json ├── sfu-bot │ ├── src │ │ ├── const.ts │ │ ├── version.ts │ │ ├── index.ts │ │ ├── option.ts │ │ ├── util.ts │ │ ├── errors.ts │ │ └── forwarding.ts │ ├── tsconfig.json │ ├── tsconfig.build.json │ ├── jest.config.js │ ├── vitest.config.ts │ ├── jest.setup.js │ ├── bundle.mjs │ ├── LICENSE │ └── package.json ├── common │ ├── tsconfig.json │ ├── tsconfig.build.json │ ├── src │ │ ├── index.ts │ │ ├── promise.ts │ │ ├── util.ts │ │ └── error.ts │ ├── LICENSE │ └── package.json ├── model │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ └── domain.ts │ ├── tsconfig.build.json │ ├── LICENSE │ └── package.json ├── token │ ├── tsconfig.json │ ├── src │ │ ├── util.ts │ │ ├── index.ts │ │ ├── errors.ts │ │ ├── scope │ │ │ └── sfu.ts │ │ └── token.ts │ ├── tsconfig.build.json │ ├── README.md │ ├── bundle.mjs │ ├── LICENSE │ ├── example │ │ └── calcSize.ts │ └── package.json ├── rtc-api-client │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ ├── domain │ │ │ └── eventObserver.ts │ │ ├── util.ts │ │ ├── config.ts │ │ └── model │ │ │ └── event.ts │ ├── tsconfig.build.json │ ├── jest.config.js │ ├── LICENSE │ └── package.json ├── signaling-client │ ├── src │ │ ├── version.ts │ │ ├── utils │ │ │ ├── logger.ts │ │ │ └── event.ts │ │ ├── index.ts │ │ ├── clientEvent.ts │ │ └── payloadTypes.ts │ ├── tsconfig.json │ ├── .prettierrc.js │ ├── tsconfig.build.json │ ├── jest.config.js │ ├── LICENSE │ └── package.json ├── analytics-client │ ├── tsconfig.json │ ├── tsconfig.test.json │ ├── .prettierrc.js │ ├── tsconfig.build.json │ ├── src │ │ ├── index.ts │ │ ├── utils │ │ │ ├── logger.ts │ │ │ ├── backoff.ts │ │ │ └── event.ts │ │ └── payloadTypes.ts │ ├── jest.config.js │ ├── LICENSE │ └── package.json ├── rtc-rpc-api-client │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ ├── const.ts │ │ ├── util.ts │ │ ├── errors.ts │ │ └── event.ts │ ├── tsconfig.build.json │ ├── jest.config.js │ ├── LICENSE │ └── package.json └── sfu-api-client │ ├── src │ ├── index.ts │ ├── const.ts │ ├── util.ts │ └── errors.ts │ ├── tsconfig.json │ ├── tsconfig.build.json │ ├── LICENSE │ └── package.json ├── env.ts.template ├── examples ├── large-room │ ├── src │ │ ├── vite-env.d.ts │ │ ├── main.tsx │ │ └── const.ts │ ├── README.md │ ├── tsconfig.node.json │ ├── .gitignore │ ├── index.html │ ├── tsconfig.json │ ├── vite.config.ts │ └── package.json ├── core │ ├── README.md │ ├── babel.config.js │ ├── tsconfig.json │ ├── src │ │ └── index.html │ └── package.json ├── auto-subscribe │ ├── README.md │ ├── babel.config.js │ ├── tsconfig.json │ ├── package.json │ └── src │ │ ├── style.css │ │ └── index.html ├── p2p-room │ ├── babel.config.js │ ├── README.md │ ├── tsconfig.json │ ├── src │ │ └── index.html │ └── package.json ├── sfu-bot │ ├── babel.config.js │ ├── README.md │ ├── src │ │ └── index.html │ ├── tsconfig.json │ └── package.json ├── sfu-room │ ├── README.md │ ├── babel.config.js │ ├── src │ │ └── index.html │ ├── tsconfig.json │ └── package.json ├── simple-conference │ ├── README.md │ ├── babel.config.js │ ├── tsconfig.json │ ├── package.json │ └── src │ │ ├── style.css │ │ └── index.html └── tutorial │ ├── babel.config.js │ ├── README.md │ ├── tsconfig.json │ ├── src │ └── index.html │ └── package.json ├── .prettierrc.js ├── pnpm-workspace.yaml ├── lerna.json ├── .editorconfig ├── tsconfig.json ├── .github └── pull_request_template.md ├── tsconfig.build.json ├── .gitignore ├── vitest.config.ts ├── .eslintrc.js ├── LICENSE ├── biome.json ├── karma.base.js ├── README.md └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | -------------------------------------------------------------------------------- /bundler/shims/process.js: -------------------------------------------------------------------------------- 1 | export const process = undefined; 2 | -------------------------------------------------------------------------------- /packages/core/src/version.ts: -------------------------------------------------------------------------------- 1 | export const PACKAGE_VERSION = '2.2.1'; 2 | -------------------------------------------------------------------------------- /packages/room/src/const.ts: -------------------------------------------------------------------------------- 1 | export const defaultMaxSubscribers = 10; 2 | -------------------------------------------------------------------------------- /packages/room/src/version.ts: -------------------------------------------------------------------------------- 1 | export const PACKAGE_VERSION = '2.2.1'; 2 | -------------------------------------------------------------------------------- /packages/sfu-bot/src/const.ts: -------------------------------------------------------------------------------- 1 | export const defaultMaxSubscribers = 10; 2 | -------------------------------------------------------------------------------- /packages/sfu-bot/src/version.ts: -------------------------------------------------------------------------------- 1 | export const PACKAGE_VERSION = '2.0.3'; 2 | -------------------------------------------------------------------------------- /env.ts.template: -------------------------------------------------------------------------------- 1 | export const appId = ''; 2 | 3 | export const secret = ''; 4 | -------------------------------------------------------------------------------- /examples/large-room/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/core/src/const.ts: -------------------------------------------------------------------------------- 1 | export const MaxIceParamServerTTL = 24 * 60 * 60; 2 | -------------------------------------------------------------------------------- /packages/model/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/token/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/rtc-api-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/signaling-client/src/version.ts: -------------------------------------------------------------------------------- 1 | export const PACKAGE_VERSION = '1.0.4'; 2 | -------------------------------------------------------------------------------- /packages/analytics-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/rtc-rpc-api-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/signaling-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/core/src/plugin/interface/index.ts: -------------------------------------------------------------------------------- 1 | export * from './connection'; 2 | export * from './plugin'; 3 | -------------------------------------------------------------------------------- /packages/token/src/util.ts: -------------------------------------------------------------------------------- 1 | /**@private */ 2 | export const nowInSec = () => Math.floor(Date.now() / 1000); 3 | -------------------------------------------------------------------------------- /examples/core/README.md: -------------------------------------------------------------------------------- 1 | # Core ライブラリ による映像・音声・データの PubSub 2 | 3 | ## 起動方法 4 | 5 | [こちらを参照](/README.md#サンプルアプリの起動方法) 6 | -------------------------------------------------------------------------------- /packages/core/src/member/localPerson/agent/index.ts: -------------------------------------------------------------------------------- 1 | export * from './publishing'; 2 | export * from './subscribing'; 3 | -------------------------------------------------------------------------------- /packages/sfu-api-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api'; 2 | export * from './const'; 3 | export * from './errors'; 4 | -------------------------------------------------------------------------------- /examples/auto-subscribe/README.md: -------------------------------------------------------------------------------- 1 | # Room ライブラリ による Web 会議のサンプル 2 | 3 | ## 起動方法 4 | 5 | [こちらを参照](/README.md#サンプルアプリの起動方法) 6 | -------------------------------------------------------------------------------- /packages/core/src/media/stream/index.ts: -------------------------------------------------------------------------------- 1 | export * from './base'; 2 | export * from './local'; 3 | export * from './remote'; 4 | -------------------------------------------------------------------------------- /examples/core/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-typescript'], 3 | retainLines: true, 4 | }; 5 | -------------------------------------------------------------------------------- /examples/p2p-room/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-typescript'], 3 | retainLines: true, 4 | }; 5 | -------------------------------------------------------------------------------- /examples/sfu-bot/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-typescript'], 3 | retainLines: true, 4 | }; 5 | -------------------------------------------------------------------------------- /examples/sfu-room/README.md: -------------------------------------------------------------------------------- 1 | # Room ライブラリの SFURoom による 映像・音声の PubSub 2 | 3 | ## 起動方法 4 | 5 | [こちらを参照](/README.md#サンプルアプリの起動方法) 6 | -------------------------------------------------------------------------------- /examples/sfu-room/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-typescript'], 3 | retainLines: true, 4 | }; 5 | -------------------------------------------------------------------------------- /examples/simple-conference/README.md: -------------------------------------------------------------------------------- 1 | # Room ライブラリ による Web 会議のサンプル 2 | 3 | ## 起動方法 4 | 5 | [こちらを参照](/README.md#サンプルアプリの起動方法) 6 | -------------------------------------------------------------------------------- /examples/tutorial/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-typescript'], 3 | retainLines: true, 4 | }; 5 | -------------------------------------------------------------------------------- /packages/model/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './domain'; 2 | 3 | import * as domain from './domain'; 4 | export default domain; 5 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | tabWidth: 2, 4 | semi: true, 5 | singleQuote: true, 6 | }; 7 | -------------------------------------------------------------------------------- /examples/p2p-room/README.md: -------------------------------------------------------------------------------- 1 | # Room ライブラリの P2PRoom による 映像・音声・データの PubSub 2 | 3 | ## 起動方法 4 | 5 | [こちらを参照](/README.md#サンプルアプリの起動方法) 6 | -------------------------------------------------------------------------------- /examples/sfu-bot/README.md: -------------------------------------------------------------------------------- 1 | # Core ライブラリと SFU Bot ライブラリ による映像・音声の PubSub 2 | 3 | ## 起動方法 4 | 5 | [こちらを参照](/README.md#サンプルアプリの起動方法) 6 | -------------------------------------------------------------------------------- /examples/auto-subscribe/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-typescript'], 3 | retainLines: true, 4 | }; 5 | -------------------------------------------------------------------------------- /examples/simple-conference/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-typescript'], 3 | retainLines: true, 4 | }; 5 | -------------------------------------------------------------------------------- /packages/sfu-api-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { "types": ["jasmine"] } 4 | } 5 | -------------------------------------------------------------------------------- /packages/rtc-rpc-api-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './client'; 2 | export * from './errors'; 3 | export * from './event'; 4 | export * from './rpc'; 5 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | - 'examples/playground' 4 | - 'examples/playground-room' 5 | - 'examples/sfu-room-for-hosting' 6 | -------------------------------------------------------------------------------- /packages/analytics-client/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "resolveJsonModule": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/rtc-api-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './client'; 2 | export * from './config'; 3 | export * from './domain/api'; 4 | export * from './domain/channel'; 5 | -------------------------------------------------------------------------------- /packages/signaling-client/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | export interface Logger { 2 | debug(message: string): void; 3 | error(message: string, error: Error): void; 4 | } 5 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*"], 3 | "version": "independent", 4 | "npmClient": "pnpm", 5 | "$schema": "node_modules/lerna/schemas/lerna-schema.json" 6 | } 7 | -------------------------------------------------------------------------------- /packages/analytics-client/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | tabWidth: 2, 4 | semi: true, 5 | singleQuote: true, 6 | printWidth: 120, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["vitest/globals"] 5 | }, 6 | "exclude": ["example"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/room/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["vitest/globals"] 5 | }, 6 | "exclude": ["examples"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/sfu-bot/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["vitest/globals"] 5 | }, 6 | "exclude": ["example"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/signaling-client/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | tabWidth: 2, 4 | semi: true, 5 | singleQuote: true, 6 | printWidth: 120, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/common/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/model/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/room/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/sfu-bot/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/token/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/src/member/person.ts: -------------------------------------------------------------------------------- 1 | import type { Member } from '.'; 2 | 3 | export interface Person extends Member { 4 | readonly type: 'person'; 5 | readonly subtype: 'person'; 6 | } 7 | -------------------------------------------------------------------------------- /packages/analytics-client/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/rtc-api-client/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/rtc-rpc-api-client/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/sfu-api-client/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/signaling-client/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /examples/tutorial/README.md: -------------------------------------------------------------------------------- 1 | # チュートリアル 2 | 3 | 「クイックスタート」[記事](https://skyway.ntt.com/ja/docs/user-guide/javascript-sdk/quickstart/)のサンプルアプリ 4 | 5 | ## 起動方法 6 | 7 | [こちらを参照](/README.md#サンプルアプリの起動方法) 8 | -------------------------------------------------------------------------------- /packages/common/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './error'; 2 | export * from './event'; 3 | export * from './http'; 4 | export * from './logger'; 5 | export * from './promise'; 6 | export * from './util'; 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_size = 2 9 | indent_style = space 10 | -------------------------------------------------------------------------------- /examples/large-room/README.md: -------------------------------------------------------------------------------- 1 | # 大規模会議アプリのサンプル 2 | 3 | 「大規模会議アプリを実装する上での注意点」[記事](https://skyway.ntt.com/ja/docs/user-guide/tips/large-scale/)のサンプルアプリ 4 | 5 | ## 起動方法 6 | 7 | [こちらを参照](/README.md#サンプルアプリの起動方法) 8 | -------------------------------------------------------------------------------- /examples/large-room/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/analytics-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './analyticsClient'; 2 | export { ConnectionState } from './socket'; 3 | export { Event } from './utils/event'; 4 | export { Logger } from './utils/logger'; 5 | -------------------------------------------------------------------------------- /packages/signaling-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './signalingClient'; 2 | export { ConnectionState } from './socket'; 3 | export { Event } from './utils/event'; 4 | export { Logger } from './utils/logger'; 5 | -------------------------------------------------------------------------------- /packages/rtc-rpc-api-client/src/const.ts: -------------------------------------------------------------------------------- 1 | export const defaultDomain = 'rtc-api.skyway.ntt.com'; 2 | // export const defaultDomain = 'localhost:8080'; 3 | export const rpcTimeout = 20_000; 4 | export const MaxRetry = 8; 5 | -------------------------------------------------------------------------------- /packages/sfu-api-client/src/const.ts: -------------------------------------------------------------------------------- 1 | import type { SFUApiOptions } from '.'; 2 | 3 | export const defaultSFUApiOptions: Omit = { 4 | domain: 'sfu.skyway.ntt.com', 5 | secure: true, 6 | version: 4, 7 | }; 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "@skyway-sdk/*": ["packages/*/src"] 7 | }, 8 | "noEmit": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/sfu-bot/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './connection'; 2 | export * from './errors'; 3 | export * from './forwarding'; 4 | export * from './member'; 5 | export * from './option'; 6 | export * from './plugin'; 7 | export * from './version'; 8 | -------------------------------------------------------------------------------- /packages/analytics-client/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | export interface Logger { 2 | debug(message: string, ...optionalParams: any[]): void; 3 | warn(message: string, ...optionalParams: any[]): void; 4 | error(message: string, error: Error): void; 5 | } 6 | -------------------------------------------------------------------------------- /packages/signaling-client/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/'], 3 | setupFilesAfterEnv: ['./__tests__/jest.setup.ts'], 4 | testMatch: ['**/__tests__/**/*.test.ts'], 5 | transform: { 6 | '^.+\\.ts$': 'ts-jest', 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /packages/room/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('jest').Config} */ 2 | const config = { 3 | preset: 'ts-jest', 4 | setupFiles: ['./jest.setup.js'], 5 | testEnvironment: 'jsdom', 6 | testRegex: 'jest/.*\\.test\\.ts$', 7 | }; 8 | 9 | module.exports = config; 10 | -------------------------------------------------------------------------------- /packages/sfu-bot/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('jest').Config} */ 2 | const config = { 3 | preset: 'ts-jest', 4 | setupFiles: ['./jest.setup.js'], 5 | testEnvironment: 'jsdom', 6 | testRegex: 'jest/.*\\.test\\.ts$', 7 | }; 8 | 9 | module.exports = config; 10 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # 本リポジトリの運用方針について 2 | 3 | このリポジトリは公開用のミラーリポジトリであり、こちらで開発は行いません。 4 | 5 | ## Issue / Pull Request 6 | 7 | 受け付けておりません。 8 | 9 | Enterprise プランをご契約のお客様はテクニカルサポートをご利用ください。 10 | 詳しくは[SkyWay サポート](https://support.skyway.ntt.com/hc/ja)をご確認ください。 11 | -------------------------------------------------------------------------------- /packages/core/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('jest').Config} */ 2 | const config = { 3 | preset: 'ts-jest', 4 | setupFiles: ['./jest.setup.js'], 5 | testEnvironment: 'jsdom', 6 | testRegex: 'tests/jest/.*\\.test\\.ts$', 7 | }; 8 | 9 | module.exports = config; 10 | -------------------------------------------------------------------------------- /packages/rtc-api-client/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testMatch: ['**/*.test.ts'], 5 | collectCoverageFrom: ['src/**/*.ts'], 6 | moduleNameMapper: { 7 | uuid: require.resolve('uuid'), 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/rtc-api-client/src/domain/eventObserver.ts: -------------------------------------------------------------------------------- 1 | import type { Event } from '@skyway-sdk/common'; 2 | import type { ChannelEvent } from '@skyway-sdk/rtc-rpc-api-client'; 3 | 4 | export interface EventObserver { 5 | onEvent: Event; 6 | 7 | dispose: () => void; 8 | } 9 | -------------------------------------------------------------------------------- /packages/rtc-rpc-api-client/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testMatch: ['**/*.test.ts'], 5 | collectCoverageFrom: ['src/**/*.ts'], 6 | moduleNameMapper: { 7 | uuid: require.resolve('uuid'), 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /examples/large-room/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | 3 | import App from './App'; 4 | 5 | const rootElement = document.getElementById('root'); 6 | if (!rootElement) throw new Error('Root element not found'); 7 | const root = createRoot(rootElement); 8 | root.render(); 9 | -------------------------------------------------------------------------------- /packages/analytics-client/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/'], 3 | testMatch: ['**/__tests__/**/*.test.ts'], 4 | transform: { 5 | '^.+\\.ts$': [ 6 | 'ts-jest', 7 | { 8 | tsconfig: 'tsconfig.test.json', 9 | }, 10 | ], 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /packages/core/vitest.large.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, mergeConfig } from 'vitest/config'; 2 | import vitestConfig from '../../vitest.config'; 3 | 4 | export default mergeConfig( 5 | vitestConfig, 6 | defineConfig({ 7 | test: { 8 | outputFile: './test-large.json', 9 | }, 10 | }), 11 | ); 12 | -------------------------------------------------------------------------------- /packages/core/vitest.middle.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, mergeConfig } from 'vitest/config'; 2 | import vitestConfig from '../../vitest.config'; 3 | 4 | export default mergeConfig( 5 | vitestConfig, 6 | defineConfig({ 7 | test: { 8 | outputFile: './test-middle.json', 9 | }, 10 | }), 11 | ); 12 | -------------------------------------------------------------------------------- /packages/core/vitest.small.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, mergeConfig } from 'vitest/config'; 2 | import vitestConfig from '../../vitest.config'; 3 | 4 | export default mergeConfig( 5 | vitestConfig, 6 | defineConfig({ 7 | test: { 8 | outputFile: './test-small.json', 9 | }, 10 | }), 11 | ); 12 | -------------------------------------------------------------------------------- /packages/sfu-bot/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, mergeConfig } from 'vitest/config'; 2 | import vitestConfig from '../../vitest.config'; 3 | 4 | export default mergeConfig( 5 | vitestConfig, 6 | defineConfig({ 7 | test: { 8 | outputFile: './test-result.json', 9 | }, 10 | }), 11 | ); 12 | -------------------------------------------------------------------------------- /packages/room/vitest.extra.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, mergeConfig } from 'vitest/config'; 2 | import vitestConfig from '../../vitest.config'; 3 | 4 | export default mergeConfig( 5 | vitestConfig, 6 | defineConfig({ 7 | test: { 8 | testTimeout: 20_000, 9 | outputFile: './test-extra.json', 10 | }, 11 | }), 12 | ); 13 | -------------------------------------------------------------------------------- /packages/room/vitest.large.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, mergeConfig } from 'vitest/config'; 2 | import vitestConfig from '../../vitest.config'; 3 | 4 | export default mergeConfig( 5 | vitestConfig, 6 | defineConfig({ 7 | test: { 8 | testTimeout: 20_000, 9 | outputFile: './test-large.json', 10 | }, 11 | }), 12 | ); 13 | -------------------------------------------------------------------------------- /packages/token/src/index.ts: -------------------------------------------------------------------------------- 1 | import { v4 } from 'uuid'; 2 | 3 | /**@internal */ 4 | const uuidV4 = v4; 5 | 6 | export * from './encoder'; 7 | export * from './scope/sfu'; 8 | export * from './scope/v1-2'; 9 | export * from './scope/v3'; 10 | export * from './token'; 11 | export { uuidV4 }; 12 | export * from './errors'; 13 | export * from './util'; 14 | -------------------------------------------------------------------------------- /packages/token/README.md: -------------------------------------------------------------------------------- 1 | # Token 2 | 3 | SkyWay サービスの認証認可に使用する SkyWay Auth Token を生成するユーティリティライブラリです。 4 | 5 | Node.js とブラウザから利用できます。 6 | 7 | # インストール方法 8 | 9 | ```sh 10 | npm i @skyway-sdk/token 11 | ``` 12 | 13 | # ドキュメント 14 | 15 | [https://skyway.ntt.com/ja/docs/user-guide/authentication/](https://skyway.ntt.com/ja/docs/user-guide/authentication/) 16 | -------------------------------------------------------------------------------- /packages/core/jest.setup.js: -------------------------------------------------------------------------------- 1 | class MockRTCRtpTransceiver { 2 | constructor() { 3 | this.receiver = {}; 4 | this.sender = {}; 5 | } 6 | } 7 | 8 | global.RTCPeerConnection = class MockRTCPeerConnection { 9 | addTransceiver() { 10 | return new MockRTCRtpTransceiver(); 11 | } 12 | }; 13 | 14 | global.navigator.mediaDevices = new EventTarget(); 15 | -------------------------------------------------------------------------------- /packages/room/jest.setup.js: -------------------------------------------------------------------------------- 1 | class MockRTCRtpTransceiver { 2 | constructor() { 3 | this.receiver = {}; 4 | this.sender = {}; 5 | } 6 | } 7 | 8 | global.RTCPeerConnection = class MockRTCPeerConnection { 9 | addTransceiver() { 10 | return new MockRTCRtpTransceiver(); 11 | } 12 | }; 13 | 14 | global.navigator.mediaDevices = new EventTarget(); 15 | -------------------------------------------------------------------------------- /packages/sfu-bot/jest.setup.js: -------------------------------------------------------------------------------- 1 | class MockRTCRtpTransceiver { 2 | constructor() { 3 | this.receiver = {}; 4 | this.sender = {}; 5 | } 6 | } 7 | 8 | global.RTCPeerConnection = class MockRTCPeerConnection { 9 | addTransceiver() { 10 | return new MockRTCRtpTransceiver(); 11 | } 12 | }; 13 | 14 | global.navigator.mediaDevices = new EventTarget(); 15 | -------------------------------------------------------------------------------- /packages/core/src/media/stream/remote/video.ts: -------------------------------------------------------------------------------- 1 | import { RemoteMediaStreamBase } from './media'; 2 | 3 | export class RemoteVideoStream extends RemoteMediaStreamBase { 4 | readonly contentType = 'video'; 5 | 6 | /**@internal */ 7 | constructor( 8 | id: string, 9 | readonly track: MediaStreamTrack, 10 | ) { 11 | super(id, 'video', track); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/large-room/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2015", 5 | "lib": ["DOM", "ES2020", "DOM.Iterable"], 6 | "sourceMap": true, 7 | "declaration": true, 8 | "declarationMap": true, 9 | "noEmitOnError": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "strict": true 13 | }, 14 | "exclude": ["node_modules", "lib"] 15 | } 16 | -------------------------------------------------------------------------------- /examples/sfu-room/src/index.html: -------------------------------------------------------------------------------- 1 | 2 |

ID:

3 |
4 | channel name: 5 | 6 |
7 | 8 |
9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/sfu-bot/src/index.html: -------------------------------------------------------------------------------- 1 | 2 |

ID:

3 |
4 | channel name: 5 | 6 |
7 | 8 |
9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/large-room/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2015", 5 | "lib": ["DOM", "ES2020"], 6 | "sourceMap": true, 7 | "declaration": true, 8 | "declarationMap": true, 9 | "noEmitOnError": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "strict": false, 13 | "strictNullChecks": false 14 | }, 15 | "exclude": ["node_modules", "lib"] 16 | } 17 | -------------------------------------------------------------------------------- /examples/p2p-room/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2015", 5 | "lib": ["DOM", "ES2020"], 6 | "sourceMap": true, 7 | "declaration": true, 8 | "declarationMap": true, 9 | "noEmitOnError": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "strict": false, 13 | "strictNullChecks": false 14 | }, 15 | "exclude": ["node_modules", "lib"] 16 | } 17 | -------------------------------------------------------------------------------- /examples/sfu-bot/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2015", 5 | "lib": ["DOM", "ES2020"], 6 | "sourceMap": true, 7 | "declaration": true, 8 | "declarationMap": true, 9 | "noEmitOnError": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "strict": false, 13 | "strictNullChecks": false 14 | }, 15 | "exclude": ["node_modules", "lib"] 16 | } 17 | -------------------------------------------------------------------------------- /examples/sfu-room/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2015", 5 | "lib": ["DOM", "ES2020"], 6 | "sourceMap": true, 7 | "declaration": true, 8 | "declarationMap": true, 9 | "noEmitOnError": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "strict": false, 13 | "strictNullChecks": false 14 | }, 15 | "exclude": ["node_modules", "lib"] 16 | } 17 | -------------------------------------------------------------------------------- /examples/tutorial/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2015", 5 | "lib": ["DOM", "ES2020"], 6 | "sourceMap": true, 7 | "declaration": true, 8 | "declarationMap": true, 9 | "noEmitOnError": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "strict": false, 13 | "strictNullChecks": false 14 | }, 15 | "exclude": ["node_modules", "lib"] 16 | } 17 | -------------------------------------------------------------------------------- /examples/auto-subscribe/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2015", 5 | "lib": ["DOM", "ES2020"], 6 | "sourceMap": true, 7 | "declaration": true, 8 | "declarationMap": true, 9 | "noEmitOnError": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "strict": false, 13 | "strictNullChecks": false 14 | }, 15 | "exclude": ["node_modules", "lib"] 16 | } 17 | -------------------------------------------------------------------------------- /examples/simple-conference/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2015", 5 | "lib": ["DOM", "ES2020"], 6 | "sourceMap": true, 7 | "declaration": true, 8 | "declarationMap": true, 9 | "noEmitOnError": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "strict": false, 13 | "strictNullChecks": false 14 | }, 15 | "exclude": ["node_modules", "lib"] 16 | } 17 | -------------------------------------------------------------------------------- /packages/core/src/validation.ts: -------------------------------------------------------------------------------- 1 | const NAME_REGEX = /^[A-Za-z0-9\-._%*]{1,128}$/; 2 | 3 | /**@internal */ 4 | export function isValidName(input: unknown): boolean { 5 | if (input === undefined) return true; 6 | 7 | // 文字列でない場合は無効 8 | if (typeof input !== 'string') return false; 9 | 10 | // "*" 単体は無効 11 | if (input === '*') return false; 12 | 13 | // 正規表現にマッチしない場合は無効 14 | if (!NAME_REGEX.test(input)) return false; 15 | 16 | // すべてのチェックをパスしたら有効 17 | return true; 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/plugin/internal/person/connection/datachannel.ts: -------------------------------------------------------------------------------- 1 | export class DataChannelNegotiationLabel { 2 | constructor( 3 | readonly publicationId: string, 4 | readonly streamId: string, 5 | ) {} 6 | 7 | static fromLabel(label: string) { 8 | const { p, s } = JSON.parse(label); 9 | return new DataChannelNegotiationLabel(p, s); 10 | } 11 | 12 | toLabel() { 13 | return JSON.stringify({ 14 | p: this.publicationId, 15 | s: this.streamId, 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/sfu-bot/src/option.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defaultSFUApiOptions, 3 | type SFUApiOptions, 4 | } from '@skyway-sdk/sfu-api-client'; 5 | 6 | export type SFUBotPluginOptions = Omit & { 7 | endpointTimeout: number; 8 | ackTimeout: number; 9 | disableRestartIce: boolean; 10 | }; 11 | 12 | export const defaultSFUBotPluginOptions: SFUBotPluginOptions = { 13 | ...defaultSFUApiOptions, 14 | endpointTimeout: 30_000, 15 | ackTimeout: 10_000, 16 | disableRestartIce: false, 17 | }; 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | lib 4 | build 5 | .parcel-cache 6 | .cache 7 | packages/**/reports 8 | packages/**/coverage 9 | *.tgz 10 | 11 | # internal 12 | .github/dependabot.yml 13 | .github/workflows 14 | CHANGELOG.md 15 | DEV.md 16 | env.ts 17 | Release.md 18 | .vscode 19 | examples/playground 20 | examples/playground-room 21 | examples/sfu-room-for-hosting 22 | CNAME 23 | docs 24 | scripts 25 | e2e 26 | tests 27 | __tests__ 28 | packages/signaling-client/examples 29 | packages/analytics-client/examples 30 | jest 31 | test-*.json 32 | -------------------------------------------------------------------------------- /examples/core/src/index.html: -------------------------------------------------------------------------------- 1 | 2 |

ID:

3 |
4 | channel name: 5 | 6 |
7 | 8 |
9 | write dataStream: 10 | 11 |
12 |
13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/p2p-room/src/index.html: -------------------------------------------------------------------------------- 1 | 2 |

ID:

3 |
4 | channel name: 5 | 6 |
7 | 8 |
9 | write dataStream: 10 | 11 |
12 |
13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/core/src/media/stream/remote/index.ts: -------------------------------------------------------------------------------- 1 | import { RemoteAudioStream } from './audio'; 2 | import { RemoteStreamBase } from './base'; 3 | import { RemoteDataStream } from './data'; 4 | import { RemoteMediaStreamBase } from './media'; 5 | import { RemoteVideoStream } from './video'; 6 | 7 | export type RemoteStream = 8 | | RemoteDataStream 9 | | RemoteAudioStream 10 | | RemoteVideoStream; 11 | 12 | export { 13 | RemoteAudioStream, 14 | RemoteDataStream, 15 | RemoteMediaStreamBase, 16 | RemoteStreamBase, 17 | RemoteVideoStream, 18 | }; 19 | -------------------------------------------------------------------------------- /bundler/private.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | /* eslint-disable no-undef */ 3 | /* eslint-disable @typescript-eslint/no-var-requires */ 4 | 5 | const pkg = require('../package.json'); 6 | 7 | import { appendLicenses, createLicenses } from './license.mjs'; 8 | 9 | const dist = 'dist'; 10 | 11 | await $`rm -rf ${dist}`; 12 | await $`mkdir ${dist}`; 13 | 14 | await $`pnpm run compile`; 15 | 16 | await $`esbuild src/index.ts --bundle --inject:./bundler/shims/process.js --format=esm --target=es6 --outfile=${dist}/index.mjs`; 17 | 18 | const licenses = await createLicenses(pkg); 19 | await appendLicenses(`${dist}/index.mjs`, licenses); 20 | -------------------------------------------------------------------------------- /packages/token/src/errors.ts: -------------------------------------------------------------------------------- 1 | export const tokenErrors = { 2 | invalidParameter: { 3 | name: 'invalidParameter', 4 | detail: 'failed to decode token', 5 | solution: 'Use the correct token according to the specification', 6 | }, 7 | invalidAppIdParameter: { 8 | name: 'invalidAppIdParameter', 9 | detail: 'failed to get AppId', 10 | solution: 'Use the correct token according to the specification', 11 | }, 12 | invalidAnalyticsParameter: { 13 | name: 'invalidAnalyticsParameter', 14 | detail: 'failed to get analytics scope', 15 | solution: 'Use the correct token according to the specification', 16 | }, 17 | } as const; 18 | -------------------------------------------------------------------------------- /examples/tutorial/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SkyWay Tutorial 7 | 8 | 9 |

ID:

10 |
11 | room name: 12 | 13 | 14 |
15 | 16 |
17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/large-room/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@skyway-sdk/*": ["../../packages/*/src/dist"] 5 | }, 6 | "target": "ESNext", 7 | "useDefineForClassFields": true, 8 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 9 | "allowJs": false, 10 | "skipLibCheck": true, 11 | "esModuleInterop": false, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "ESNext", 16 | "moduleResolution": "Node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react-jsx" 21 | }, 22 | "include": ["src"], 23 | "references": [{ "path": "./tsconfig.node.json" }] 24 | } 25 | -------------------------------------------------------------------------------- /examples/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "core", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "author": "", 7 | "scripts": { 8 | "dev": "parcel ./src/index.html", 9 | "build:example": "npm run transpile && parcel build ./src/index.html --public-url ./", 10 | "format": "eslint ./src --fix", 11 | "lint": "eslint ./src --fix", 12 | "transpile": "babel src/main.ts -o src/main.js" 13 | }, 14 | "browserslist": [ 15 | "last 3 chrome versions" 16 | ], 17 | "dependencies": { 18 | "@skyway-sdk/core": "latest" 19 | }, 20 | "devDependencies": { 21 | "parcel": "^2.8.0", 22 | "@babel/cli": "^7.0.0", 23 | "@babel/core": "^7.0.0", 24 | "@babel/preset-typescript": "^7.18.6" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, type ViteUserConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | browser: { 7 | enabled: true, 8 | headless: true, 9 | provider: 'playwright', 10 | instances: [ 11 | { 12 | browser: 'chromium', 13 | // @ts-expect-error 14 | launch: { 15 | args: [ 16 | '--use-fake-ui-for-media-stream', 17 | '--use-fake-device-for-media-stream', 18 | ], 19 | }, 20 | }, 21 | ], 22 | }, 23 | testTimeout: 15_000, 24 | reporters: ['json', 'default'], 25 | // fileParallelism: false, 26 | retry: 2, 27 | }, 28 | }) as ViteUserConfig; 29 | -------------------------------------------------------------------------------- /examples/p2p-room/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "p2p-room", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "author": "", 7 | "scripts": { 8 | "build:example": "npm run transpile && parcel build ./src/index.html --public-url ./", 9 | "dev": "parcel ./src/index.html", 10 | "format": "eslint ./src --fix", 11 | "lint": "eslint ./src --fix", 12 | "transpile": "babel src/main.ts -o src/main.js" 13 | }, 14 | "browserslist": [ 15 | "last 3 chrome versions" 16 | ], 17 | "dependencies": { 18 | "@skyway-sdk/room": "latest" 19 | }, 20 | "devDependencies": { 21 | "@babel/cli": "^7.0.0", 22 | "@babel/core": "^7.0.0", 23 | "@babel/preset-typescript": "^7.18.6", 24 | "parcel": "^2.8.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/sfu-room/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sfu-room", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "author": "", 7 | "scripts": { 8 | "dev": "parcel ./src/index.html", 9 | "build:example": "npm run transpile && parcel build ./src/index.html --public-url ./", 10 | "format": "eslint ./src --fix", 11 | "lint": "eslint ./src --fix", 12 | "transpile": "babel src/main.ts -o src/main.js" 13 | }, 14 | "browserslist": [ 15 | "last 3 chrome versions" 16 | ], 17 | "dependencies": { 18 | "@skyway-sdk/room": "latest" 19 | }, 20 | "devDependencies": { 21 | "parcel": "^2.8.0", 22 | "@babel/cli": "^7.0.0", 23 | "@babel/core": "^7.0.0", 24 | "@babel/preset-typescript": "^7.18.6" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/tutorial/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tutorial", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "author": "", 7 | "scripts": { 8 | "build:example": "npm run transpile && parcel build ./src/index.html --public-url ./", 9 | "dev": "parcel ./src/index.html", 10 | "format": "eslint ./src --fix", 11 | "lint": "eslint ./src --fix", 12 | "transpile": "babel src/main.ts -o src/main.js" 13 | }, 14 | "browserslist": [ 15 | "last 3 chrome versions" 16 | ], 17 | "dependencies": { 18 | "@skyway-sdk/room": "latest" 19 | }, 20 | "devDependencies": { 21 | "@babel/cli": "^7.0.0", 22 | "@babel/core": "^7.0.0", 23 | "@babel/preset-typescript": "^7.18.6", 24 | "parcel": "^2.8.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/src/media/stream/local/index.ts: -------------------------------------------------------------------------------- 1 | import { LocalAudioStream } from './audio'; 2 | import { LocalStreamBase } from './base'; 3 | import { LocalCustomVideoStream } from './customVideo'; 4 | import { type DataStreamSubscriber, LocalDataStream } from './data'; 5 | import { LocalMediaStreamBase, type LocalMediaStreamOptions } from './media'; 6 | import { LocalVideoStream } from './video'; 7 | 8 | export type LocalStream = 9 | | LocalAudioStream 10 | | LocalVideoStream 11 | | LocalDataStream 12 | | LocalCustomVideoStream; 13 | 14 | export { 15 | LocalAudioStream, 16 | LocalCustomVideoStream, 17 | LocalDataStream, 18 | LocalMediaStreamBase, 19 | type LocalMediaStreamOptions, 20 | LocalStreamBase, 21 | LocalVideoStream, 22 | type DataStreamSubscriber, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/core/src/media/stream/remote/audio.ts: -------------------------------------------------------------------------------- 1 | import { AudioLevel } from '../audioLevel'; 2 | import { RemoteMediaStreamBase } from './media'; 3 | 4 | export class RemoteAudioStream extends RemoteMediaStreamBase { 5 | readonly contentType = 'audio'; 6 | private _audioLevel: AudioLevel | undefined; 7 | 8 | /**@internal */ 9 | constructor( 10 | id: string, 11 | readonly track: MediaStreamTrack, 12 | ) { 13 | super(id, 'audio', track); 14 | } 15 | 16 | /**@description [japanese] 直近100msにおける最大音量を取得する(値の範囲:0-1) */ 17 | getAudioLevel() { 18 | // 不要なリソース生成を行わないように初回実行時にAudioLevelインスタンスを生成する 19 | if (this._audioLevel === undefined) { 20 | this._audioLevel = new AudioLevel(this.track); 21 | } 22 | return this.track.enabled ? this._audioLevel.calculate() : 0; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/sfu-bot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sfu-bot-example", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "author": "", 7 | "scripts": { 8 | "dev": "parcel ./src/index.html", 9 | "build:example": "npm run transpile && parcel build ./src/index.html --public-url ./", 10 | "format": "eslint ./src --fix", 11 | "lint": "eslint ./src --fix", 12 | "transpile": "babel src/main.ts -o src/main.js" 13 | }, 14 | "browserslist": [ 15 | "last 3 chrome versions" 16 | ], 17 | "dependencies": { 18 | "@skyway-sdk/core": "latest", 19 | "@skyway-sdk/sfu-bot": "latest" 20 | }, 21 | "devDependencies": { 22 | "parcel": "^2.8.0", 23 | "@babel/cli": "^7.0.0", 24 | "@babel/core": "^7.0.0", 25 | "@babel/preset-typescript": "^7.18.6" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/large-room/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react'; 2 | import { defineConfig } from 'vite'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | base: './', 8 | resolve: { 9 | alias: { 10 | '@skyway-sdk/common': '@skyway-sdk/common/dist', 11 | '@skyway-sdk/core': '@skyway-sdk/core/dist', 12 | '@skyway-sdk/rtc-api-client': '@skyway-sdk/rtc-api-client/dist', 13 | '@skyway-sdk/rtc-rpc-api-client': '@skyway-sdk/rtc-rpc-api-client/dist', 14 | '@skyway-sdk/sfu-bot': '@skyway-sdk/sfu-bot/dist', 15 | '@skyway-sdk/sfu-api-client': '@skyway-sdk/sfu-api-client/dist', 16 | '@skyway-sdk/message-client': '@skyway-sdk/message-client/dist', 17 | '@skyway-sdk/token': '@skyway-sdk/token/dist', 18 | }, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /packages/core/src/plugin/internal/unknown/plugin.ts: -------------------------------------------------------------------------------- 1 | import type model from '@skyway-sdk/model'; 2 | 3 | import type { SkyWayChannelImpl } from '../../../channel'; 4 | import { SkyWayPlugin } from '../../interface/plugin'; 5 | import { UnknownMemberImpl } from './member'; 6 | 7 | export class UnknownPlugin extends SkyWayPlugin { 8 | readonly subtype = 'unknown'; 9 | 10 | readonly _createRemoteMember = ( 11 | channel: SkyWayChannelImpl, 12 | memberDto: model.Member, 13 | ) => { 14 | const person = new UnknownMemberImpl({ 15 | ...this._context, 16 | context: this._context!, 17 | channel, 18 | metadata: memberDto.metadata, 19 | id: memberDto.id, 20 | name: memberDto.name, 21 | plugin: this, 22 | subtype: memberDto.subtype, 23 | }); 24 | return person; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/src/member/remoteMember.ts: -------------------------------------------------------------------------------- 1 | import type { SkyWayConnection } from '../plugin/interface'; 2 | import type { Member, MemberImpl } from '.'; 3 | import type { LocalPersonImpl } from './localPerson'; 4 | 5 | export interface RemoteMember extends Member { 6 | readonly side: 'remote'; 7 | } 8 | 9 | /**@internal */ 10 | export interface RemoteMemberImplInterface extends MemberImpl { 11 | readonly side: 'remote'; 12 | 13 | _getConnection: (localPersonId: string) => SkyWayConnection | undefined; 14 | _getOrCreateConnection: (localPerson: LocalPersonImpl) => SkyWayConnection; 15 | _dispose: () => void; 16 | } 17 | 18 | /**@internal */ 19 | export function isRemoteMember( 20 | member: Member, 21 | ): member is RemoteMemberImplInterface { 22 | if (member.side === 'remote') { 23 | return true; 24 | } 25 | return false; 26 | } 27 | -------------------------------------------------------------------------------- /examples/auto-subscribe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auto-subscribe", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "author": "", 7 | "scripts": { 8 | "dev": "parcel ./src/index.html", 9 | "build:example": "npm run transpile && parcel build ./src/index.html --public-url ./", 10 | "format": "eslint ./src --fix", 11 | "lint": "eslint ./src --fix", 12 | "transpile": "babel src/main.ts -o src/main.js", 13 | "transpile:watch": "babel src/main.ts --watch -o src/main.js" 14 | }, 15 | "browserslist": [ 16 | "last 3 chrome versions" 17 | ], 18 | "dependencies": { 19 | "@skyway-sdk/room": "latest" 20 | }, 21 | "devDependencies": { 22 | "parcel": "^2.8.0", 23 | "@babel/cli": "^7.0.0", 24 | "@babel/core": "^7.0.0", 25 | "@babel/preset-typescript": "^7.18.6" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/signaling-client/src/clientEvent.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid'; 2 | 3 | const MAX_PAYLOAD_LENGTH = 20480; 4 | 5 | export type ClientEventType = 6 | | 'sendRequestSignalingMessage' 7 | | 'sendResponseSignalingMessage' 8 | | 'updateSkyWayAuthToken' 9 | | 'checkConnectivity'; 10 | 11 | export class ClientEvent { 12 | readonly eventId: string; 13 | 14 | data: string; 15 | 16 | constructor( 17 | readonly event: ClientEventType, 18 | readonly payload: Record = {}, 19 | ) { 20 | this.eventId = uuidv4(); 21 | this.data = JSON.stringify({ 22 | event: this.event, 23 | eventId: this.eventId, 24 | payload: this.payload, 25 | }); 26 | if (this.data.length > MAX_PAYLOAD_LENGTH) { 27 | throw new Error('payload size exceeds the upper limit'); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/simple-conference/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-conference", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "author": "", 7 | "scripts": { 8 | "dev": "parcel ./src/index.html", 9 | "build:example": "npm run transpile && parcel build ./src/index.html --public-url ./", 10 | "format": "eslint ./src --fix", 11 | "lint": "eslint ./src --fix", 12 | "transpile": "babel src/main.ts -o src/main.js", 13 | "transpile:watch": "babel src/main.ts --watch -o src/main.js" 14 | }, 15 | "browserslist": [ 16 | "last 3 chrome versions" 17 | ], 18 | "dependencies": { 19 | "@skyway-sdk/room": "latest" 20 | }, 21 | "devDependencies": { 22 | "parcel": "^2.8.0", 23 | "@babel/cli": "^7.0.0", 24 | "@babel/core": "^7.0.0", 25 | "@babel/preset-typescript": "^7.18.6" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/auto-subscribe/src/style.css: -------------------------------------------------------------------------------- 1 | /* normalize */ 2 | body { 3 | margin: 0; 4 | } 5 | 6 | /* global styles */ 7 | video { 8 | background-color: #111; 9 | width: 100%; 10 | } 11 | 12 | .heading { 13 | text-align: center; 14 | margin-bottom: 0; 15 | } 16 | 17 | .note { 18 | text-align: center; 19 | } 20 | 21 | .meta { 22 | text-align: center; 23 | font-size: 0.8rem; 24 | color: gray; 25 | } 26 | 27 | .container { 28 | margin-left: auto; 29 | margin-right: auto; 30 | width: 980px; 31 | } 32 | 33 | /* room */ 34 | .room { 35 | display: grid; 36 | grid-template-columns: 30% 40% 30%; 37 | gap: 8px; 38 | margin: 0 8px; 39 | } 40 | 41 | .room .remote-streams { 42 | background-color: #f6fbff; 43 | } 44 | 45 | .room .messages { 46 | background-color: #eee; 47 | min-height: 100px; 48 | padding: 8px; 49 | margin-top: 0; 50 | } 51 | -------------------------------------------------------------------------------- /examples/simple-conference/src/style.css: -------------------------------------------------------------------------------- 1 | /* normalize */ 2 | body { 3 | margin: 0; 4 | } 5 | 6 | /* global styles */ 7 | video { 8 | background-color: #111; 9 | width: 100%; 10 | } 11 | 12 | .heading { 13 | text-align: center; 14 | margin-bottom: 0; 15 | } 16 | 17 | .note { 18 | text-align: center; 19 | } 20 | 21 | .meta { 22 | text-align: center; 23 | font-size: 0.8rem; 24 | color: gray; 25 | } 26 | 27 | .container { 28 | margin-left: auto; 29 | margin-right: auto; 30 | width: 980px; 31 | } 32 | 33 | /* room */ 34 | .room { 35 | display: grid; 36 | grid-template-columns: 30% 40% 30%; 37 | gap: 8px; 38 | margin: 0 8px; 39 | } 40 | 41 | .room .remote-streams { 42 | background-color: #f6fbff; 43 | } 44 | 45 | .room .messages { 46 | background-color: #eee; 47 | min-height: 100px; 48 | padding: 8px; 49 | margin-top: 0; 50 | } 51 | -------------------------------------------------------------------------------- /packages/core/src/subscription/factory.ts: -------------------------------------------------------------------------------- 1 | import type model from '@skyway-sdk/model'; 2 | 3 | import type { SkyWayChannelImpl } from '../channel'; 4 | import type { RemoteMemberImplInterface } from '../member/remoteMember'; 5 | import { SubscriptionImpl } from '.'; 6 | 7 | /**@internal */ 8 | export function createSubscription( 9 | channel: SkyWayChannelImpl, 10 | { subscriberId, publicationId, id }: model.Subscription, 11 | ): SubscriptionImpl { 12 | const exist = channel._getSubscription(id); 13 | if (exist) return exist; 14 | 15 | const subscriber = channel._getMember( 16 | subscriberId, 17 | ) as RemoteMemberImplInterface; 18 | const publication = channel._getPublication(publicationId); 19 | const contentType = publication.contentType; 20 | 21 | const subscription = new SubscriptionImpl({ 22 | channel, 23 | id, 24 | subscriber, 25 | publication, 26 | contentType, 27 | }); 28 | 29 | return subscription; 30 | } 31 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { browser: true, node: true, es2021: true }, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'prettier', 7 | 'plugin:prettier/recommended', 8 | 'plugin:@typescript-eslint/eslint-recommended', 9 | ], 10 | parser: '@typescript-eslint/parser', 11 | parserOptions: { 12 | ecmaVersion: 12, 13 | sourceType: 'module', 14 | }, 15 | plugins: ['@typescript-eslint', 'prettier', 'simple-import-sort'], 16 | rules: { 17 | 'simple-import-sort/imports': 'error', 18 | 'simple-import-sort/exports': 'error', 19 | 'no-async-promise-executor': 'off', 20 | '@typescript-eslint/ban-types': 'off', 21 | '@typescript-eslint/no-empty-interface': 'off', 22 | 'no-empty': 'off', 23 | '@typescript-eslint/explicit-module-boundary-types': 'off', 24 | '@typescript-eslint/no-empty-function': 'warn', 25 | }, 26 | ignorePatterns: ['.eslintrc.js'], 27 | }; 28 | -------------------------------------------------------------------------------- /examples/large-room/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "large-room", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "rm -rf node_modules/.vite && vite", 7 | "build:example": "tsc && vite build", 8 | "preview": "rm -rf node_modules/.vite && vite preview", 9 | "type": "tsc --noEmit" 10 | }, 11 | "dependencies": { 12 | "@chakra-ui/react": "^2.2.1", 13 | "@emotion/css": "^11.1.3", 14 | "@skyway-sdk/room": "latest", 15 | "framer-motion": "^6.4.1", 16 | "hark": "^1.2.3", 17 | "qrcode": "^1.5.0", 18 | "qrcode.react": "^3.1.0", 19 | "react": "^18.0.0", 20 | "react-dom": "^18.0.0" 21 | }, 22 | "devDependencies": { 23 | "@types/hark": "^1.2.1", 24 | "@types/qrcode": "^1.4.2", 25 | "@types/qrcode.react": "^1.0.2", 26 | "@types/react": "^18.0.0", 27 | "@types/react-dom": "^18.0.0", 28 | "@vitejs/plugin-react": "^1.3.0", 29 | "typescript": "^4.6.3", 30 | "vite": "^5.4.8" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/simple-conference/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SkyWay - Room example 7 | 8 | 9 | 10 |
11 |

Room example

12 |
13 |
14 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 |
23 |

24 |         
25 |
26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /packages/core/src/media/stream/remote/data.ts: -------------------------------------------------------------------------------- 1 | import { Event } from '@skyway-sdk/common'; 2 | 3 | import { type DataStreamMessageType, objectFlag } from '../local/data'; 4 | import { RemoteStreamBase } from './base'; 5 | 6 | export class RemoteDataStream extends RemoteStreamBase { 7 | private _isEnabled = true; 8 | readonly contentType = 'data'; 9 | readonly onData = new Event(); 10 | 11 | /**@internal */ 12 | constructor( 13 | id: string, 14 | /**@internal */ 15 | public _datachannel: RTCDataChannel, 16 | ) { 17 | super(id, 'data'); 18 | 19 | _datachannel.onmessage = ({ data }) => { 20 | if (!this._isEnabled) { 21 | return; 22 | } 23 | 24 | if (typeof data === 'string' && data.includes(objectFlag)) { 25 | data = JSON.parse(data.slice(objectFlag.length)); 26 | } 27 | this.onData.emit(data); 28 | }; 29 | } 30 | 31 | /**@internal */ 32 | setIsEnabled(b: boolean) { 33 | this._isEnabled = b; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/rtc-api-client/src/util.ts: -------------------------------------------------------------------------------- 1 | import { type ErrorInfo, SkyWayError } from '@skyway-sdk/common'; 2 | 3 | export function createWarnPayload({ 4 | appId, 5 | detail, 6 | channelId, 7 | operationName, 8 | payload, 9 | }: { 10 | operationName: string; 11 | channelId?: string; 12 | appId?: string; 13 | detail: string; 14 | payload?: any; 15 | }) { 16 | const warn: any = { 17 | operationName, 18 | payload, 19 | detail, 20 | appId, 21 | channelId, 22 | }; 23 | 24 | return warn; 25 | } 26 | 27 | export function createError({ 28 | operationName, 29 | info, 30 | error, 31 | path, 32 | channelId, 33 | appId, 34 | payload, 35 | }: { 36 | operationName: string; 37 | info: ErrorInfo; 38 | error?: Error; 39 | path: string; 40 | payload?: any; 41 | channelId?: string; 42 | appId?: string; 43 | }) { 44 | return new SkyWayError({ 45 | error, 46 | info: info, 47 | payload: { payload, operationName, channelId, appId }, 48 | path, 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /packages/token/bundle.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | /* eslint-disable no-undef */ 3 | /* eslint-disable @typescript-eslint/no-var-requires */ 4 | 5 | import { appendLicenses, createLicenses } from '../../bundler/license.mjs'; 6 | 7 | const pkg = require('./package.json'); 8 | 9 | const globalName = 'skyway_token'; 10 | const dist = 'dist'; 11 | 12 | await $`pnpm compile`; 13 | 14 | await $`cp -r ../../bundler/shims ./ `; 15 | 16 | await $`esbuild src/index.ts --bundle --inject:./shims/process.js --format=esm --target=es6 --outfile=${dist}/index.mjs`; 17 | await $`esbuild src/index.ts --bundle --inject:./shims/process.js --format=iife --global-name=${globalName} --target=es6 --outfile=${dist}/${globalName}-latest.js`; 18 | 19 | const licenses = await createLicenses(pkg); 20 | await appendLicenses(`${dist}/index.mjs`, licenses); 21 | await appendLicenses(`${dist}/${globalName}-latest.js`, licenses); 22 | 23 | await $`cp ${dist}/${globalName}-latest.js ${dist}/${globalName}-${pkg.version}.js`; 24 | 25 | await $`rm -rf ./shims`; 26 | -------------------------------------------------------------------------------- /packages/sfu-api-client/src/util.ts: -------------------------------------------------------------------------------- 1 | import { type ErrorInfo, SkyWayError } from '@skyway-sdk/common'; 2 | 3 | export function createError({ 4 | operationName, 5 | info, 6 | error, 7 | path, 8 | payload, 9 | }: { 10 | operationName: string; 11 | info: ErrorInfo; 12 | error?: Error; 13 | path: string; 14 | payload?: any; 15 | }) { 16 | return new SkyWayError({ 17 | error, 18 | info: info, 19 | payload: { payload, operationName }, 20 | path, 21 | }); 22 | } 23 | 24 | export function createWarnPayload({ 25 | appId, 26 | detail, 27 | channelId, 28 | operationName, 29 | payload, 30 | memberId, 31 | botId, 32 | }: { 33 | operationName: string; 34 | channelId?: string; 35 | appId?: string; 36 | memberId?: string; 37 | botId?: string; 38 | detail: string; 39 | payload?: any; 40 | }) { 41 | const warn: any = { 42 | operationName, 43 | payload, 44 | detail, 45 | appId, 46 | channelId, 47 | memberId, 48 | botId, 49 | }; 50 | 51 | return warn; 52 | } 53 | -------------------------------------------------------------------------------- /packages/rtc-api-client/src/config.ts: -------------------------------------------------------------------------------- 1 | import type { LogFormat, LogLevel } from '@skyway-sdk/common'; 2 | import { RtcRpcApiConfig } from '@skyway-sdk/rtc-rpc-api-client'; 3 | import deepmerge from 'deepmerge'; 4 | 5 | export { RtcRpcApiConfig }; 6 | 7 | /**@internal */ 8 | export type RtcApiConfig = RtcRpcApiConfig & { eventSubscribeTimeout?: number }; 9 | 10 | export interface ConfigOptions { 11 | rtcApi: RtcApiConfig; 12 | log?: Partial<{ level: LogLevel; format: LogFormat }>; 13 | } 14 | 15 | export type TurnPolicy = 'enable' | 'disable' | 'turnOnly'; 16 | 17 | export class Config implements ConfigOptions { 18 | rtcApi: Required = { 19 | domain: 'rtc-api.skyway.ntt.com', 20 | timeout: 30_000, 21 | secure: true, 22 | eventSubscribeTimeout: 5000, 23 | }; 24 | log: Required = { 25 | level: 'error', 26 | format: 'object', 27 | }; 28 | 29 | constructor(options: Partial = {}) { 30 | Object.assign(this, deepmerge(this, options)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/room/src/util.ts: -------------------------------------------------------------------------------- 1 | import { type ErrorInfo, SkyWayError } from '@skyway-sdk/common'; 2 | import type { SkyWayContext } from '@skyway-sdk/core'; 3 | 4 | import type { Room } from './room/default'; 5 | 6 | export function createError({ 7 | operationName, 8 | context, 9 | info, 10 | error, 11 | path, 12 | payload, 13 | room, 14 | }: { 15 | operationName: string; 16 | path: string; 17 | info: ErrorInfo; 18 | context?: SkyWayContext; 19 | room?: Room; 20 | error?: Error; 21 | payload?: any; 22 | }) { 23 | const errPayload: any = { 24 | operationName, 25 | payload, 26 | }; 27 | 28 | if (room) { 29 | errPayload.appId = room._channel.appId; 30 | errPayload.roomId = room.id; 31 | if (room.localRoomMember) { 32 | errPayload.memberId = room.localRoomMember.id; 33 | } 34 | } 35 | if (context) { 36 | errPayload.info = context.info; 37 | errPayload.plugins = context.plugins.map((p) => p.subtype); 38 | } 39 | 40 | return new SkyWayError({ error, info, payload: errPayload, path }); 41 | } 42 | -------------------------------------------------------------------------------- /examples/large-room/src/const.ts: -------------------------------------------------------------------------------- 1 | import { 2 | nowInSec, 3 | type SFUBotPluginOptions, 4 | SkyWayAuthToken, 5 | type SkyWayConfigOptions, 6 | uuidV4, 7 | } from '@skyway-sdk/room'; 8 | 9 | import { appId, secret } from '../../../env'; 10 | 11 | const testToken = new SkyWayAuthToken({ 12 | jti: uuidV4(), 13 | iat: nowInSec(), 14 | exp: nowInSec() + 60 * 60 * 24, 15 | version: 3, 16 | scope: { 17 | appId: appId, 18 | rooms: [ 19 | { 20 | name: '*', 21 | methods: ['create', 'close', 'updateMetadata'], 22 | member: { 23 | name: '*', 24 | methods: ['publish', 'subscribe', 'updateMetadata'], 25 | }, 26 | sfu: { 27 | enabled: true, 28 | }, 29 | }, 30 | ], 31 | turn: { 32 | enabled: true, 33 | }, 34 | }, 35 | }); 36 | export const tokenString = testToken.encode(secret); 37 | export const contextOptions: Partial = { 38 | log: { level: 'debug' }, 39 | }; 40 | export const sfuOptions: Partial = {}; 41 | -------------------------------------------------------------------------------- /packages/common/src/promise.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from './logger'; 2 | 3 | const log = new Logger('packages/common/src/promise.ts'); 4 | 5 | /**@internal */ 6 | export class PromiseQueue { 7 | queue: { 8 | promise: () => Promise; 9 | done: (...args: any[]) => void; 10 | failed: (...args: any[]) => void; 11 | }[] = []; 12 | running = false; 13 | 14 | push = (promise: () => Promise) => 15 | new Promise((r, f) => { 16 | this.queue.push({ promise, done: r, failed: f }); 17 | if (!this.running) { 18 | this.run().catch((e) => { 19 | log.error('push', e); 20 | }); 21 | } 22 | }); 23 | 24 | private async run() { 25 | const task = this.queue.shift(); 26 | if (task) { 27 | this.running = true; 28 | 29 | try { 30 | const res = await task.promise(); 31 | task.done(res); 32 | } catch (error) { 33 | task.failed(error); 34 | } 35 | 36 | await this.run(); 37 | } else { 38 | this.running = false; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/analytics-client/src/utils/backoff.ts: -------------------------------------------------------------------------------- 1 | export class BackOff { 2 | count = 0; 3 | readonly times: number = 8; 4 | /**ms */ 5 | readonly interval: number = 100; 6 | /**ms */ 7 | readonly jitter: number = 0; 8 | 9 | /**20.4 sec {var sum=0;for(i=0;i<=8;i++){sum +=i ** 2 * 100}} */ 10 | constructor( 11 | props: Partial> = {}, 12 | ) { 13 | Object.assign(this, props); 14 | } 15 | 16 | async wait() { 17 | if (this.exceeded) { 18 | return false; 19 | } 20 | const timeout = this.timeout; 21 | this.count++; 22 | 23 | await new Promise((r) => setTimeout(r, timeout)); 24 | return true; 25 | } 26 | 27 | get timeout() { 28 | const timeout = 29 | this.count ** 2 * this.interval + 30 | this.count ** 2 * this.jitter * Math.random(); 31 | return timeout; 32 | } 33 | 34 | get exceeded() { 35 | return this.count >= this.times; 36 | } 37 | 38 | reset() { 39 | this.count = 0; 40 | } 41 | 42 | stop() { 43 | this.count = this.times; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/core/src/media/index.ts: -------------------------------------------------------------------------------- 1 | export type Codec = { 2 | mimeType: string; 3 | /** 4 | * @description [japanese] fmtpのパラメータを設定する */ 5 | parameters?: Partial; 6 | rate?: number; 7 | }; 8 | 9 | export type CodecParameters = { 10 | [key: string]: any; 11 | /** @description [japanese] 発話していない時の音声通信を停止する。デフォルトで有効 */ 12 | usedtx: boolean | number; 13 | }; 14 | 15 | export type DataType = string | Blob | ArrayBuffer; 16 | 17 | export type EncodingParameters = { 18 | /** @description [japanese] エンコード設定の選択をする場合は事前にIDを設定する必要がある */ 19 | id?: string; 20 | /** @description [japanese] 単位は bps */ 21 | maxBitrate?: number; 22 | /** 23 | * @description 24 | * [japanese] 基準の解像度から値で割った解像度を設定する。値は1以上である必要がある 25 | * @link https://www.w3.org/TR/webrtc/#dom-rtcrtpencodingparameters-scaleresolutiondownby 26 | */ 27 | scaleResolutionDownBy?: number; 28 | /** 29 | * @description 30 | * [japanese] 最大フレームレートをフレーム/秒単位で指定する 31 | * @link https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpEncodingParameters/maxFramerate 32 | */ 33 | maxFramerate?: number; 34 | }; 35 | -------------------------------------------------------------------------------- /packages/rtc-rpc-api-client/src/util.ts: -------------------------------------------------------------------------------- 1 | import { type ErrorInfo, SkyWayError } from '@skyway-sdk/common'; 2 | 3 | export function createError({ 4 | operationName, 5 | info, 6 | error, 7 | path, 8 | payload, 9 | channelId, 10 | appId, 11 | memberId, 12 | }: { 13 | operationName: string; 14 | info: ErrorInfo; 15 | error?: Error; 16 | path: string; 17 | payload?: any; 18 | channelId?: string; 19 | appId?: string; 20 | memberId?: string; 21 | }) { 22 | return new SkyWayError({ 23 | error, 24 | info: info, 25 | payload: { payload, operationName, channelId, appId, memberId }, 26 | path, 27 | }); 28 | } 29 | 30 | export function createWarnPayload({ 31 | appId, 32 | detail, 33 | channelId, 34 | operationName, 35 | payload, 36 | memberId, 37 | }: { 38 | operationName: string; 39 | channelId?: string; 40 | appId?: string; 41 | memberId?: string; 42 | detail: string; 43 | payload?: any; 44 | }) { 45 | const warn: any = { 46 | operationName, 47 | payload, 48 | detail, 49 | appId, 50 | channelId, 51 | memberId, 52 | }; 53 | 54 | return warn; 55 | } 56 | -------------------------------------------------------------------------------- /packages/core/bundle.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | /* eslint-disable no-undef */ 3 | /* eslint-disable @typescript-eslint/no-var-requires */ 4 | 5 | import { appendLicenses, createLicenses } from '../../bundler/license.mjs'; 6 | 7 | const pkg = require('./package.json'); 8 | 9 | await fs.writeFile( 10 | './src/version.ts', 11 | `export const PACKAGE_VERSION = '${pkg.version}';\n`, 12 | ); 13 | 14 | const globalName = 'skyway_core'; 15 | const dist = 'dist'; 16 | 17 | await $`pnpm run compile`; 18 | 19 | await $`cp -r ../../bundler/shims ./ `; 20 | 21 | await $`esbuild src/index.ts --bundle --inject:./shims/process.js --format=esm --target=es6 --outfile=${dist}/index.mjs`; 22 | await $`esbuild src/index.ts --bundle --inject:./shims/process.js --format=iife --global-name=${globalName} --target=es6 --outfile=${dist}/${globalName}-latest.js`; 23 | 24 | const licenses = await createLicenses(pkg); 25 | await appendLicenses(`${dist}/index.mjs`, licenses); 26 | await appendLicenses(`${dist}/${globalName}-latest.js`, licenses); 27 | 28 | await $`cp ${dist}/${globalName}-latest.js ${dist}/${globalName}-${pkg.version}.js`; 29 | 30 | await $`rm -rf ./shims`; 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 NTT DOCOMO BUSINESS, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/sfu-bot/bundle.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | /* eslint-disable no-undef */ 3 | /* eslint-disable @typescript-eslint/no-var-requires */ 4 | 5 | import { appendLicenses, createLicenses } from '../../bundler/license.mjs'; 6 | 7 | const pkg = require('./package.json'); 8 | 9 | await fs.writeFile( 10 | './src/version.ts', 11 | `export const PACKAGE_VERSION = '${pkg.version}';\n`, 12 | ); 13 | 14 | const globalName = 'skyway_sfu_bot'; 15 | const dist = 'dist'; 16 | 17 | await $`pnpm run compile`; 18 | 19 | await $`cp -r ../../bundler/shims ./ `; 20 | 21 | await $`esbuild src/index.ts --bundle --inject:./shims/process.js --format=esm --target=es6 --outfile=${dist}/index.mjs`; 22 | await $`esbuild src/index.ts --bundle --inject:./shims/process.js --format=iife --global-name=${globalName} --target=es6 --outfile=${dist}/${globalName}-latest.js`; 23 | 24 | const licenses = await createLicenses(pkg); 25 | await appendLicenses(`${dist}/index.mjs`, licenses); 26 | await appendLicenses(`${dist}/${globalName}-latest.js`, licenses); 27 | 28 | await $`cp ${dist}/${globalName}-latest.js ${dist}/${globalName}-${pkg.version}.js`; 29 | 30 | await $`rm -rf ./shims`; 31 | -------------------------------------------------------------------------------- /packages/common/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 NTT DOCOMO BUSINESS, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 NTT DOCOMO BUSINESS, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/model/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 NTT DOCOMO BUSINESS, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/room/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 NTT DOCOMO BUSINESS, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/sfu-bot/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 NTT DOCOMO BUSINESS, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/token/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 NTT DOCOMO BUSINESS, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/analytics-client/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 NTT DOCOMO BUSINESS, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/rtc-api-client/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 NTT DOCOMO BUSINESS, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/rtc-rpc-api-client/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 NTT DOCOMO BUSINESS, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/sfu-api-client/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 NTT DOCOMO BUSINESS, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/signaling-client/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 NTT DOCOMO BUSINESS, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/core/src/media/stream/remote/media.ts: -------------------------------------------------------------------------------- 1 | import { attachElement, type ContentType, detachElement } from '../base'; 2 | import { RemoteStreamBase } from './base'; 3 | 4 | export abstract class RemoteMediaStreamBase extends RemoteStreamBase { 5 | private _element?: HTMLVideoElement | HTMLAudioElement; 6 | constructor( 7 | readonly id: string, 8 | readonly contentType: ContentType, 9 | readonly track: MediaStreamTrack, 10 | ) { 11 | super(id, contentType); 12 | } 13 | 14 | /**@internal */ 15 | setIsEnabled(b: boolean) { 16 | this.track.enabled = b; 17 | } 18 | 19 | /** 20 | * @description [english] Attach the stream to the element. 21 | * @description [japanese] streamをelementに適用する. 22 | */ 23 | attach(element: HTMLVideoElement | HTMLAudioElement) { 24 | this._element = element; 25 | attachElement(element, this.track); 26 | } 27 | 28 | /** 29 | * @description [english] Detach the stream from the element. 30 | * @description [japanese] elementからstreamを取り除く. 31 | */ 32 | detach() { 33 | if (this._element) { 34 | detachElement(this._element, this.track); 35 | this._element = undefined; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/room/bundle.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | /* eslint-disable no-undef */ 3 | /* eslint-disable @typescript-eslint/no-var-requires */ 4 | 5 | import { appendLicenses, createLicenses } from '../../bundler/license.mjs'; 6 | 7 | const pkg = require('./package.json'); 8 | 9 | await fs.writeFile( 10 | './src/version.ts', 11 | `export const PACKAGE_VERSION = '${pkg.version}';\n`, 12 | ); 13 | 14 | const globalName = 'skyway_room'; 15 | const dist = 'dist'; 16 | 17 | await $`pnpm run compile`; 18 | await $`cp -r ../../bundler/shims ./ `; 19 | 20 | await $`esbuild src/index.ts --bundle --inject:./shims/process.js --format=esm --target=es6 --outfile=${dist}/index.mjs`; 21 | await $`esbuild src/index.ts --bundle --inject:./shims/process.js --format=iife --global-name=${globalName} --target=es6 --outfile=${dist}/${globalName}-latest.js`; 22 | 23 | const licenses = await createLicenses(pkg); 24 | await appendLicenses(`${dist}/index.mjs`, licenses); 25 | await appendLicenses(`${dist}/${globalName}-latest.js`, licenses); 26 | await fs.writeFile('../../THIRD_PARTY_LICENSE', licenses); 27 | 28 | await $`cp ${dist}/${globalName}-latest.js ${dist}/${globalName}-${pkg.version}.js`; 29 | 30 | await $`rm -rf ./shims`; 31 | -------------------------------------------------------------------------------- /examples/auto-subscribe/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SkyWay - Room example 7 | 8 | 9 | 10 |
11 |

Room example

12 |

13 | Change Room type (before join in a channel): 14 | p2p / sfu 15 |

16 |
17 |
18 | 19 | : 20 | 21 | 22 | 23 |
24 | 25 |
26 | 27 |
28 |

29 |         
30 |
31 |
32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /packages/core/src/plugin/interface/plugin.ts: -------------------------------------------------------------------------------- 1 | import { Event } from '@skyway-sdk/common'; 2 | import type model from '@skyway-sdk/model'; 3 | 4 | import type { SkyWayChannel } from '../../channel'; 5 | import type { SkyWayContext } from '../../context'; 6 | import type { LocalPersonImpl } from '../../member/localPerson'; 7 | import type { RemoteMemberImplInterface } from '../../member/remoteMember'; 8 | 9 | export interface SkyWayPluginInterface { 10 | subtype: string; 11 | } 12 | 13 | /**@internal */ 14 | export abstract class SkyWayPlugin implements SkyWayPluginInterface { 15 | subtype!: string; 16 | /**@internal */ 17 | _context?: SkyWayContext; 18 | /**@internal */ 19 | _onContextAttached = new Event(); 20 | 21 | /**@internal */ 22 | _attachContext(context: SkyWayContext) { 23 | this._context = context; 24 | this._onContextAttached.emit(context); 25 | } 26 | 27 | /**@internal */ 28 | _whenCreateLocalPerson?: (member: LocalPersonImpl) => Promise; 29 | 30 | /**@internal */ 31 | _whenDisposeLocalPerson?: (member: LocalPersonImpl) => Promise; 32 | 33 | /**@internal */ 34 | abstract _createRemoteMember( 35 | channel: SkyWayChannel, 36 | memberDto: model.Member, 37 | ): RemoteMemberImplInterface; 38 | } 39 | -------------------------------------------------------------------------------- /packages/rtc-api-client/src/model/event.ts: -------------------------------------------------------------------------------- 1 | import type model from '@skyway-sdk/model'; 2 | 3 | export type ChannelOpenedEvent = Record; 4 | export type ChannelClosedEvent = Record; 5 | export interface ChannelMetadataUpdatedEvent { 6 | channel: { metadata: string }; 7 | } 8 | 9 | export type ChangedEvent = Record; 10 | export interface MemberJoinedEvent { 11 | member: model.Member; 12 | } 13 | export interface MemberLeftEvent { 14 | member: model.Member; 15 | } 16 | export interface MemberMetadataUpdatedEvent { 17 | member: model.Member; 18 | } 19 | 20 | export interface StreamPublishedEvent { 21 | publication: model.Publication; 22 | } 23 | export interface StreamUnpublishedEvent { 24 | publication: model.Publication; 25 | } 26 | export interface PublicationMetadataUpdatedEvent { 27 | publication: model.Publication; 28 | } 29 | export interface PublicationDisabledEvent { 30 | publication: model.Publication; 31 | } 32 | export interface PublicationEnabledEvent { 33 | publication: model.Publication; 34 | } 35 | 36 | export interface StreamSubscribedEvent { 37 | subscription: model.Subscription; 38 | } 39 | export interface StreamUnsubscribedEvent { 40 | subscription: model.Subscription; 41 | } 42 | -------------------------------------------------------------------------------- /packages/sfu-api-client/src/errors.ts: -------------------------------------------------------------------------------- 1 | export const errors = { 2 | invalidParameter: { name: 'invalidParameter', detail: '', solution: '' }, 3 | invalidRequestParameter: { 4 | name: 'invalidRequestParameter', 5 | detail: 'リクエストの値が不正です', 6 | solution: '正しい値を入力してください', 7 | }, 8 | notFound: { 9 | name: 'notFound', 10 | detail: '対象のリソースが見つかりません', 11 | solution: '対象のリソースが存在するか確かめてください', 12 | }, 13 | maxSubscriberExceededError: { 14 | name: 'maxSubscribersExceededError', 15 | detail: 16 | 'forwardingのmaxSubscribersの制限を超えています。maxSubscribersの値を超えてSubscribeすることはできません', 17 | solution: 'maxSubscribersの範囲内でご利用ください', 18 | }, 19 | quotaExceededError: { 20 | name: 'quotaExceededError', 21 | detail: 'リソースの制限量を超えてリソースを利用することはできません', 22 | solution: 'リソース制限量の範囲内でご利用ください', 23 | }, 24 | timeout: { name: 'timeout', detail: '', solution: '' }, 25 | insufficientPermissions: { 26 | name: 'insufficientPermissions', 27 | detail: 'tokenの権限が不足しています', 28 | solution: 'tokenに必要な権限を付与してください', 29 | }, 30 | backendError: { name: 'backendError:', detail: '', solution: '' }, 31 | notAllowedConsumeError: { 32 | name: 'notAllowedConsumeError', 33 | detail: 'ForwardingからのConsume許可がありません', 34 | solution: 'Forwardingしているmemberによる許可操作が必要です', 35 | }, 36 | } as const; 37 | -------------------------------------------------------------------------------- /packages/common/src/util.ts: -------------------------------------------------------------------------------- 1 | /**@internal */ 2 | export class BackOff { 3 | count = 0; 4 | readonly times: number = 8; 5 | /**ms */ 6 | readonly interval: number = 100; 7 | /**ms */ 8 | readonly jitter: number = 0; 9 | 10 | /**20.4 sec {var sum=0;for(i=0;i<=8;i++){sum +=i ** 2 * 100}} */ 11 | constructor( 12 | props: Partial> = {}, 13 | ) { 14 | Object.assign(this, props); 15 | } 16 | 17 | /**if need wait return true */ 18 | async wait() { 19 | if (this.exceeded) { 20 | return false; 21 | } 22 | const timeout = this.timeout; 23 | this.count++; 24 | 25 | await new Promise((r) => setTimeout(r, timeout)); 26 | return true; 27 | } 28 | 29 | get timeout() { 30 | const timeout = 31 | this.count ** 2 * this.interval + 32 | this.count ** 2 * this.jitter * Math.random(); 33 | return timeout; 34 | } 35 | 36 | get exceeded() { 37 | return this.count >= this.times; 38 | } 39 | 40 | reset() { 41 | this.count = 0; 42 | } 43 | } 44 | 45 | /**@internal */ 46 | export const deepCopy = (o: T): T => JSON.parse(JSON.stringify(o)); 47 | 48 | /**@internal */ 49 | export interface RuntimeInfo { 50 | browserName: string; 51 | browserVersion: string; 52 | osName: string; 53 | osVersion: string; 54 | } 55 | -------------------------------------------------------------------------------- /packages/token/example/calcSize.ts: -------------------------------------------------------------------------------- 1 | import { secret } from '../../../env'; 2 | import { nowInSec } from '../dist'; 3 | import { type ChannelScope, SkyWayAuthToken, uuidV4 } from '../src'; 4 | 5 | const token = new SkyWayAuthToken({ 6 | version: 1, 7 | jti: uuidV4(), 8 | iat: nowInSec(), 9 | exp: nowInSec() + 60 * 60, 10 | scope: { 11 | app: { 12 | id: uuidV4(), 13 | actions: ['read'], 14 | channels: [...new Array(10)].map( 15 | () => 16 | ({ 17 | name: uuidV4(), 18 | actions: ['write'], 19 | members: [ 20 | { 21 | name: uuidV4(), 22 | actions: ['write'], 23 | publication: { actions: ['write'] }, 24 | subscription: { actions: ['write'] }, 25 | }, 26 | ], 27 | sfuBots: [ 28 | { 29 | actions: ['write'], 30 | forwardings: [ 31 | { 32 | actions: ['write'], 33 | subscription: { actions: ['write'] }, 34 | }, 35 | ], 36 | }, 37 | ], 38 | }) as ChannelScope, 39 | ), 40 | turn: true, 41 | }, 42 | }, 43 | }); 44 | const str = token.encode(secret); 45 | console.log(Buffer.from(str).length); 46 | -------------------------------------------------------------------------------- /packages/rtc-rpc-api-client/src/errors.ts: -------------------------------------------------------------------------------- 1 | import type { ResponseError } from './rpc'; 2 | 3 | export const errors = { 4 | timeout: { name: 'timeout', detail: '', solution: '' }, 5 | internalError: { name: 'internalError', detail: '', solution: '' }, 6 | invalidParameter: { name: 'invalidParameter', detail: '', solution: '' }, 7 | connectionDisconnected: { 8 | name: 'connectionDisconnected', 9 | detail: '', 10 | solution: '', 11 | }, 12 | websocketConnectionFailure: { 13 | name: 'connectionFailure', 14 | detail: 'サーバへの接続に失敗しました', 15 | solution: 16 | 'ネットワーク接続状況およびフリープランをご利用の場合はリソース利用量を確認してください', 17 | }, 18 | rpcResponseError: { 19 | name: 'rpcResponseError', 20 | detail: '', 21 | solution: '', 22 | error: {} as ResponseError, 23 | }, 24 | onClosedWhileRequesting: { 25 | name: 'onClosedWhileRequesting', 26 | detail: 'request中にクライアントが終了されました', 27 | solution: 'リクエストの完了を確認してからクライアントを終了させてください', 28 | }, 29 | failedToConnectRtcAPI: { 30 | name: 'failedToConnectRtcAPI', 31 | detail: 'rtc-api serverへの接続に失敗しました', 32 | solution: 33 | 'インターネット接続状況とTokenの内容が正しいか、またフリープランをご利用の場合はリソース利用量を確かめてください', 34 | }, 35 | failedToUpdateMemberTTL: { 36 | name: 'failedToUpdateMemberTTL', 37 | detail: 'updateMemberTTLを再試行しましたが、失敗しました', 38 | solution: 'インターネット接続状況を確認してください', 39 | }, 40 | } as const; 41 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/2.3.0/schema.json", 3 | "vcs": { 4 | "enabled": false, 5 | "clientKind": "git", 6 | "useIgnoreFile": false 7 | }, 8 | "files": { 9 | "ignoreUnknown": false, 10 | "includes": [ 11 | "**", 12 | "!**/dist", 13 | "!**/node_modules", 14 | "!**/docs", 15 | "!**/coverage" 16 | ] 17 | }, 18 | "formatter": { 19 | "enabled": true, 20 | "indentStyle": "space" 21 | }, 22 | "linter": { 23 | "enabled": true, 24 | "rules": { 25 | "recommended": true, 26 | "correctness": { 27 | "useExhaustiveDependencies": "off" 28 | }, 29 | "suspicious": { 30 | "noFocusedTests": "error", 31 | "noExplicitAny": "off" 32 | }, 33 | "style": { 34 | "noNonNullAssertion": "off" 35 | } 36 | } 37 | }, 38 | "javascript": { 39 | "formatter": { 40 | "quoteStyle": "single" 41 | } 42 | }, 43 | "assist": { 44 | "enabled": true, 45 | "actions": { 46 | "source": { 47 | "organizeImports": "on" 48 | } 49 | } 50 | }, 51 | "overrides": [ 52 | { 53 | "includes": ["**/tests/**"], 54 | "linter": { 55 | "rules": { 56 | "style": { 57 | "noNonNullAssertion": "off" 58 | } 59 | } 60 | } 61 | } 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /packages/core/src/media/stream/remote/factory.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@skyway-sdk/common'; 2 | 3 | import { errors } from '../../../errors'; 4 | import type { Codec } from '../../../media'; 5 | import { createError } from '../../../util'; 6 | import type { RemoteStream } from '.'; 7 | import { RemoteAudioStream } from './audio'; 8 | import { RemoteDataStream } from './data'; 9 | import { RemoteVideoStream } from './video'; 10 | 11 | const log = new Logger('packages/core/src/media/stream/remote/factory.ts'); 12 | 13 | /**@internal */ 14 | export const createRemoteStream = ( 15 | id: string, 16 | media: MediaStreamTrack | RTCDataChannel, 17 | codec: Codec, 18 | ): RemoteStream => { 19 | if (media instanceof RTCDataChannel) { 20 | const stream = new RemoteDataStream(id, media); 21 | stream.codec = codec; 22 | return stream; 23 | } else { 24 | if (media.kind === 'audio') { 25 | const stream = new RemoteAudioStream(id, media); 26 | stream.codec = codec; 27 | return stream; 28 | } else if (media.kind === 'video') { 29 | const stream = new RemoteVideoStream(id, media); 30 | stream.codec = codec; 31 | return stream; 32 | } 33 | } 34 | 35 | throw createError({ 36 | operationName: 'createRemoteStream', 37 | path: log.prefix, 38 | info: { ...errors.invalidArgumentValue, detail: 'invalid stream type' }, 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /packages/sfu-bot/src/util.ts: -------------------------------------------------------------------------------- 1 | import type { Channel, EncodingParameters, Member } from '@skyway-sdk/core'; 2 | 3 | export function getLayerFromEncodings( 4 | id: string, 5 | encodings: EncodingParameters[], 6 | ) { 7 | let layer = 0; 8 | for (; layer < encodings.length; layer++) { 9 | const encoding = encodings[layer]; 10 | if (encoding.id === id) { 11 | break; 12 | } 13 | } 14 | return layer; 15 | } 16 | 17 | export function moveToHead(arr: T[], selector: (o: T) => boolean) { 18 | const target = arr.find(selector); 19 | return [target, ...arr.filter((o) => !selector(o))]; 20 | } 21 | 22 | export function createWarnPayload({ 23 | channel, 24 | detail, 25 | operationName, 26 | payload, 27 | bot, 28 | }: { 29 | operationName: string; 30 | channel?: Channel; 31 | detail: string; 32 | payload?: any; 33 | bot?: Member; 34 | }) { 35 | const warn: any = { 36 | operationName, 37 | payload, 38 | detail, 39 | }; 40 | if (channel) { 41 | warn.appId = channel.appId; 42 | warn.channelId = channel.id; 43 | if (channel.localPerson) { 44 | warn.memberId = channel.localPerson.id; 45 | } 46 | } 47 | 48 | if (bot) { 49 | warn.botId = bot.id; 50 | warn.appId = bot.channel.appId; 51 | warn.channelId = bot.channel.id; 52 | warn.memberId = bot.channel.localPerson?.id; 53 | } 54 | 55 | return warn; 56 | } 57 | -------------------------------------------------------------------------------- /packages/core/src/channel/event.ts: -------------------------------------------------------------------------------- 1 | import type { Member } from '../member'; 2 | import type { Publication } from '../publication'; 3 | import type { Subscription } from '../subscription'; 4 | 5 | export type ChannelClosedEvent = Record; 6 | export type ChannelMetadataUpdatedEvent = { 7 | metadata: string; 8 | }; 9 | export type ListChangedEvent = Record; 10 | export type MemberJoinedEvent = { 11 | member: Member; 12 | }; 13 | export type MemberLeftEvent = { 14 | member: Member; 15 | }; 16 | export type MemberMetadataUpdatedEvent = { 17 | metadata: string; 18 | member: Member; 19 | }; 20 | export type MemberStateChangedEvent = { 21 | member: Member; 22 | }; 23 | 24 | export type StreamPublishedEvent = { 25 | publication: Publication; 26 | }; 27 | export type StreamUnpublishedEvent = { 28 | publication: Publication; 29 | }; 30 | export type PublicationMetadataUpdatedEvent = { 31 | publication: Publication; 32 | metadata: string; 33 | }; 34 | export type PublicationEnabledEvent = { 35 | publication: Publication; 36 | }; 37 | export type PublicationDisabledEvent = { 38 | publication: Publication; 39 | }; 40 | export type PublicationStateChangedEvent = { 41 | publication: Publication; 42 | }; 43 | 44 | export type StreamSubscribedEvent = { 45 | subscription: Subscription; 46 | }; 47 | export type StreamUnsubscribedEvent = { 48 | subscription: Subscription; 49 | }; 50 | -------------------------------------------------------------------------------- /packages/model/src/domain.ts: -------------------------------------------------------------------------------- 1 | export interface Channel { 2 | id: string; 3 | name: string; 4 | metadata?: string; 5 | members: Member[]; 6 | publications: Publication[]; 7 | subscriptions: Subscription[]; 8 | version: number; 9 | } 10 | 11 | export interface Member { 12 | id: string; 13 | name?: string; 14 | type: MemberType; 15 | subtype: string; 16 | metadata?: string; 17 | } 18 | 19 | export type MemberType = 'person' | 'bot'; 20 | 21 | export const PublicationType = ['p2p', 'sfu', null] as const; 22 | export type PublicationType = (typeof PublicationType)[number]; 23 | 24 | export interface Publication { 25 | id: string; 26 | channelId: Channel['id']; 27 | publisherId: Member['id']; 28 | origin?: Publication['id']; 29 | contentType: ContentType; 30 | metadata?: string; 31 | codecCapabilities: Codec[]; 32 | encodings: Encoding[]; 33 | isEnabled: boolean; 34 | type: PublicationType; 35 | } 36 | 37 | export type ContentType = 'audio' | 'video' | 'data'; 38 | 39 | export type Codec = { mimeType: string }; 40 | export interface Encoding { 41 | id: string; 42 | maxBitrate?: number; 43 | scaleResolutionDownBy?: number; 44 | maxFramerate?: number; 45 | } 46 | 47 | export interface Subscription { 48 | id: string; 49 | channelId: Channel['id']; 50 | publicationId: Publication['id']; 51 | publisherId: Member['id']; 52 | subscriberId: Member['id']; 53 | contentType: ContentType; 54 | } 55 | -------------------------------------------------------------------------------- /packages/core/src/plugin/interface/connection.ts: -------------------------------------------------------------------------------- 1 | import type { Event } from '@skyway-sdk/common'; 2 | 3 | import type { Member } from '../../member'; 4 | import type { LocalPersonImpl } from '../../member/localPerson'; 5 | import type { PublicationImpl } from '../../publication'; 6 | import type { SubscriptionImpl } from '../../subscription'; 7 | 8 | /**@internal */ 9 | export interface SkyWayConnection { 10 | readonly type: string; 11 | readonly localPerson: LocalPersonImpl; 12 | readonly remoteMember: Pick; 13 | readonly onDisconnect: Event; 14 | readonly onClose: Event; 15 | closed: boolean; 16 | close(props?: { reason?: string }): void; 17 | /**@throws {SkyWayError} */ 18 | startPublishing?( 19 | publication: PublicationImpl, 20 | subscriptionId: string, 21 | ): Promise; 22 | stopPublishing?(publication: PublicationImpl): Promise; 23 | startSubscribing?(subscription: SubscriptionImpl): Promise; 24 | stopSubscribing?(subscription: SubscriptionImpl): Promise; 25 | changePreferredEncoding?(subscription: SubscriptionImpl): Promise; 26 | } 27 | 28 | /**@internal */ 29 | export interface Transport { 30 | connectionState: TransportConnectionState; 31 | rtcPeerConnection: RTCPeerConnection; 32 | } 33 | 34 | export type TransportConnectionState = 35 | | 'new' 36 | | 'connecting' 37 | | 'connected' 38 | | 'reconnecting' 39 | | 'disconnected'; 40 | -------------------------------------------------------------------------------- /packages/room/src/room/event.ts: -------------------------------------------------------------------------------- 1 | import type { RoomMember } from '../member'; 2 | import type { RoomPublication } from '../publication'; 3 | import type { RoomSubscription } from '../subscription'; 4 | 5 | export type RoomClosedEvent = Record; 6 | export type RoomMetadataUpdatedEvent = { 7 | metadata: string; 8 | }; 9 | 10 | export type MemberJoinedEvent = { 11 | member: RoomMember; 12 | }; 13 | export type MemberLeftEvent = { 14 | member: RoomMember; 15 | }; 16 | export type ListChangedEvent = Record; 17 | export type MemberMetadataUpdatedEvent = { 18 | metadata: string; 19 | member: RoomMember; 20 | }; 21 | export type MemberStateChangedEvent = { 22 | member: RoomMember; 23 | }; 24 | 25 | export type StreamPublishedEvent = { 26 | publication: RoomPublication; 27 | }; 28 | export type StreamUnpublishedEvent = { 29 | publication: RoomPublication; 30 | }; 31 | 32 | export type PublicationMetadataUpdatedEvent = { 33 | publication: RoomPublication; 34 | metadata: string; 35 | }; 36 | export type PublicationEnabledEvent = { 37 | publication: RoomPublication; 38 | }; 39 | export type PublicationDisabledEvent = { 40 | publication: RoomPublication; 41 | }; 42 | export type PublicationStateChangedEvent = { 43 | publication: RoomPublication; 44 | }; 45 | 46 | export type StreamSubscribedEvent = { 47 | subscription: RoomSubscription; 48 | }; 49 | export type StreamUnsubscribedEvent = { 50 | subscription: RoomSubscription; 51 | }; 52 | -------------------------------------------------------------------------------- /packages/room/src/room/p2p.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | LocalPersonAdapter, 3 | LocalStream, 4 | PublicationImpl, 5 | SkyWayChannelImpl, 6 | } from '@skyway-sdk/core'; 7 | 8 | import { 9 | type LocalP2PRoomMember, 10 | LocalP2PRoomMemberImpl, 11 | } from '../member/local/p2p'; 12 | import type { RoomPublication } from '../publication'; 13 | import { RoomBase, type RoomMemberInit } from './base'; 14 | import type { Room } from './default'; 15 | 16 | export interface P2PRoom extends Room { 17 | /** 18 | * @description [japanese] RoomにMemberを参加させる 19 | */ 20 | join: (memberInit?: RoomMemberInit) => Promise; 21 | } 22 | 23 | /**@internal */ 24 | export class P2PRoomImpl extends RoomBase implements P2PRoom { 25 | protected _disableSignaling = false; 26 | localRoomMember?: LocalP2PRoomMemberImpl; 27 | 28 | constructor(channel: SkyWayChannelImpl) { 29 | super('p2p', channel); 30 | } 31 | 32 | protected _getTargetPublication( 33 | publicationId: string, 34 | ): RoomPublication | undefined { 35 | return this._getPublication(publicationId); 36 | } 37 | 38 | protected _createLocalRoomMember( 39 | local: LocalPersonAdapter, 40 | room: this, 41 | ): T { 42 | return new LocalP2PRoomMemberImpl(local, room) as T; 43 | } 44 | 45 | protected _isAcceptablePublication(p: PublicationImpl): boolean { 46 | // p2p以外を除外する 47 | if (p.type !== 'p2p') { 48 | return false; 49 | } 50 | return true; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/token/src/scope/sfu.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const forwardingActions = ['create', 'write', 'delete'] as const; 4 | const forwardingScopeSchema = z 5 | .object({ 6 | /** 7 | * 以下を複数指定可能 8 | * - write: Forwarding のすべての操作 9 | * - create: Forwarding の作成 (任意のメディアをSFU経由で新たに転送することができる) 10 | * - delete: Forwarding の削除 (SFU経由でのメディア転送を取りやめることができる) 11 | */ 12 | actions: z.array( 13 | // 型補完のため enum で定義しておく 14 | z 15 | .enum(forwardingActions) 16 | .refine((arg) => { 17 | return typeof arg === 'string'; // バリデーションとしては ForwardingAction 以外の文字列も許容する 18 | }), 19 | ), 20 | }) 21 | .passthrough(); 22 | export type ForwardingScope = z.input; 23 | 24 | const sfuBotActions = ['create', 'write', 'delete'] as const; 25 | /**@internal */ 26 | export const sfuScopeSchema = z 27 | .object({ 28 | /** 29 | * 以下を複数指定可能 30 | * - write: SFU Bot のすべての操作をすることができる 31 | * - create: SFU Bot の作成ができる 32 | * - delete: SFU Bot の削除ができる 33 | */ 34 | actions: z.array( 35 | // 型補完のため enum で定義しておく 36 | z 37 | .enum(sfuBotActions) 38 | .refine((arg) => { 39 | return typeof arg === 'string'; // バリデーションとしては SFUBotAction 以外の文字列も許容する 40 | }), 41 | ), 42 | /**forwarding リソースに関するオブジェクトを指定(forwardingオブジェクトについては後述) */ 43 | forwardings: z.array(forwardingScopeSchema), 44 | }) 45 | .passthrough(); 46 | export type SFUScope = z.input; 47 | -------------------------------------------------------------------------------- /packages/core/src/member/localPerson/agent/subscribing.ts: -------------------------------------------------------------------------------- 1 | import type { SubscriptionImpl } from '../../../subscription'; 2 | import type { LocalPersonImpl } from '../../localPerson'; 3 | 4 | export class SubscribingAgent { 5 | private _disposers: { [subscriptionId: string]: () => void } = {}; 6 | private _context = this._localPerson.context; 7 | constructor(private readonly _localPerson: LocalPersonImpl) {} 8 | 9 | async startSubscribing(subscription: SubscriptionImpl): Promise { 10 | if (this._context.config.internal.disableDPlane) { 11 | await new Promise((r) => setTimeout(r, 500)); 12 | return; 13 | } 14 | 15 | const publisher = subscription.publication.publisher; 16 | const connection = publisher._getOrCreateConnection(this._localPerson); 17 | 18 | if (connection.startSubscribing) { 19 | await connection.startSubscribing(subscription); 20 | 21 | const { removeListener } = subscription._onChangeEncoding.add( 22 | async () => { 23 | await connection.changePreferredEncoding?.(subscription); 24 | }, 25 | ); 26 | this._disposers[subscription.id] = removeListener; 27 | } 28 | } 29 | 30 | async stopSubscribing(subscription: SubscriptionImpl) { 31 | const publisher = subscription.publication.publisher; 32 | const connection = publisher._getConnection(this._localPerson.id); 33 | 34 | if (connection?.stopSubscribing) { 35 | await connection.stopSubscribing(subscription); 36 | this._disposers[subscription.id]?.(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@skyway-sdk/common'; 2 | export * from '@skyway-sdk/token'; 3 | export * from './channel'; 4 | export * from './channel/event'; 5 | export * from './config'; 6 | export * from './context'; 7 | export * from './errors'; 8 | export * from './external/analytics'; 9 | export * from './external/ice'; 10 | export * from './media'; 11 | export * from './media/factory'; 12 | export * from './media/stream'; 13 | export * from './media/stream/local'; 14 | export * from './media/stream/local/audio'; 15 | export * from './media/stream/local/customVideo'; 16 | export * from './media/stream/local/data'; 17 | export * from './media/stream/local/video'; 18 | export * from './media/stream/remote'; 19 | export * from './media/stream/remote/audio'; 20 | export * from './media/stream/remote/data'; 21 | export * from './media/stream/remote/factory'; 22 | export * from './media/stream/remote/media'; 23 | export * from './media/stream/remote/video'; 24 | export * from './member'; 25 | export * from './member/localPerson'; 26 | export * from './member/localPerson/adapter'; 27 | export * from './member/person'; 28 | export * from './member/remoteMember'; 29 | export * from './plugin/interface/connection'; 30 | export * from './plugin/interface/plugin'; 31 | export * from './plugin/internal/person/connection'; 32 | export * from './plugin/internal/person/member'; 33 | export * from './plugin/internal/person/util'; 34 | export * from './publication'; 35 | export * from './subscription'; 36 | export * from './util'; 37 | export * from './validation'; 38 | export * from './version'; 39 | -------------------------------------------------------------------------------- /packages/analytics-client/src/utils/event.ts: -------------------------------------------------------------------------------- 1 | type EventExecute = (arg: T) => void; 2 | 3 | export class Event { 4 | private _listeners = new Map>(); 5 | 6 | private _listenerIndex = 0; 7 | 8 | emit = (arg: T): void => { 9 | this._listeners.forEach((listener) => { 10 | listener(arg); 11 | }); 12 | }; 13 | 14 | removeAllListeners = (): void => { 15 | this._listeners.clear(); 16 | }; 17 | 18 | addListener = (listener: EventExecute): { removeListener: () => void } => { 19 | const id = this._listenerIndex; 20 | this._listeners.set(id, listener); 21 | this._listenerIndex++; 22 | const removeListener = () => { 23 | this._listeners.delete(id); 24 | }; 25 | 26 | return { removeListener }; 27 | }; 28 | 29 | addOneTimeListener = ( 30 | listener: EventExecute, 31 | ): { removeListener: () => void } => { 32 | const off = this.addListener((arg) => { 33 | off.removeListener(); 34 | listener(arg); 35 | }); 36 | 37 | return off; 38 | }; 39 | 40 | asPromise = (timeLimit?: number): Promise => 41 | new Promise((resolve, reject) => { 42 | let removeListener = () => {}; 43 | const timeout = 44 | timeLimit && 45 | setTimeout(() => { 46 | reject('Event asPromise timeout'); 47 | removeListener(); 48 | }, timeLimit); 49 | 50 | const off = this.addOneTimeListener((arg) => { 51 | if (timeout) clearTimeout(timeout); 52 | resolve(arg); 53 | }); 54 | removeListener = off.removeListener; 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /packages/signaling-client/src/utils/event.ts: -------------------------------------------------------------------------------- 1 | type EventExecute = (arg: T) => void; 2 | 3 | export class Event { 4 | private _listeners = new Map>(); 5 | 6 | private _listenerIndex = 0; 7 | 8 | emit = (arg: T): void => { 9 | this._listeners.forEach((listener) => { 10 | listener(arg); 11 | }); 12 | }; 13 | 14 | removeAllListeners = (): void => { 15 | this._listeners.clear(); 16 | }; 17 | 18 | addListener = (listener: EventExecute): { removeListener: () => void } => { 19 | const id = this._listenerIndex; 20 | this._listeners.set(id, listener); 21 | this._listenerIndex++; 22 | const removeListener = () => { 23 | this._listeners.delete(id); 24 | }; 25 | 26 | return { removeListener }; 27 | }; 28 | 29 | addOneTimeListener = ( 30 | listener: EventExecute, 31 | ): { removeListener: () => void } => { 32 | const off = this.addListener((arg) => { 33 | off.removeListener(); 34 | listener(arg); 35 | }); 36 | 37 | return off; 38 | }; 39 | 40 | asPromise = (timeLimit?: number): Promise => 41 | new Promise((resolve, reject) => { 42 | let removeListener = () => {}; 43 | const timeout = 44 | timeLimit && 45 | setTimeout(() => { 46 | reject('Event asPromise timeout'); 47 | removeListener(); 48 | }, timeLimit); 49 | 50 | const off = this.addOneTimeListener((arg) => { 51 | if (timeout) clearTimeout(timeout); 52 | resolve(arg); 53 | }); 54 | removeListener = off.removeListener; 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /packages/core/src/media/stream/remote/base.ts: -------------------------------------------------------------------------------- 1 | import { Event } from '@skyway-sdk/common'; 2 | 3 | import type { Codec } from '../../../media'; 4 | import type { 5 | Transport, 6 | TransportConnectionState, 7 | } from '../../../plugin/interface'; 8 | import type { ContentType, Stream, WebRTCStats } from '../base'; 9 | 10 | export abstract class RemoteStreamBase implements Stream { 11 | readonly side = 'remote'; 12 | /**@internal */ 13 | readonly _onConnectionStateChanged = new Event(); 14 | codec!: Codec; 15 | private _connectionState: TransportConnectionState = 'new'; 16 | 17 | /**@internal */ 18 | constructor( 19 | readonly id: string, 20 | readonly contentType: ContentType, 21 | ) {} 22 | 23 | /**@internal */ 24 | _setConnectionState(state: TransportConnectionState) { 25 | if (this._connectionState === state) return; 26 | this._connectionState = state; 27 | this._onConnectionStateChanged.emit(state); 28 | } 29 | 30 | /**@internal */ 31 | _getTransport: () => Transport | undefined = () => undefined; 32 | 33 | /**@internal */ 34 | _getStats: () => Promise = async () => [] as WebRTCStats; 35 | 36 | /**@internal */ 37 | _getRTCPeerConnection() { 38 | return this._getTransport()?.rtcPeerConnection; 39 | } 40 | 41 | /**@internal */ 42 | _getConnectionState() { 43 | return this._connectionState; 44 | } 45 | 46 | /**@internal */ 47 | toJSON() { 48 | return { 49 | contentType: this.contentType, 50 | id: this.id, 51 | codec: this.codec, 52 | side: this.side, 53 | }; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/token/src/token.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | import { appScopeSchema } from './scope/v1-2'; 4 | import { scopeV3Schema } from './scope/v3'; 5 | 6 | const authTokenBaseSchema = z.object({ 7 | /** トークンのユニークなid(uuid) */ 8 | jti: z.string().uuid(), 9 | /** トークンが発行された日時(UNIX timestamp) */ 10 | iat: z.number().nonnegative(), 11 | /** このトークンが無効になる時間を表すタイムスタンプ(UNIX timestamp) */ 12 | exp: z.number().nonnegative(), 13 | }); 14 | 15 | const authTokenV1_2Schema = z.intersection( 16 | authTokenBaseSchema, 17 | z.object({ 18 | /** 19 | * tokenの権限を表すクレーム[version:1,2,undefined] 20 | * */ 21 | scope: z.object({ 22 | app: appScopeSchema, 23 | }), 24 | /** 25 | * tokenのバージョン[version:1,2,undefined] 26 | * - 未指定やundefinedの場合は1として扱われます。 27 | * - 3の場合とでscopeの構造に違いがあります。 28 | * */ 29 | version: z 30 | .union([z.literal(1), z.literal(2), z.literal(undefined)]) 31 | .optional(), 32 | }), 33 | ); 34 | export type AuthTokenV1_2 = z.input; 35 | 36 | const authTokenV3Schema = z.intersection( 37 | authTokenBaseSchema, 38 | z.object({ 39 | /** 40 | * tokenの権限を表すクレーム[version:3] 41 | * */ 42 | scope: scopeV3Schema, 43 | /** 44 | * tokenのバージョン[version:3] 45 | * - 2以下の場合とでscopeの構造に違いがあります。 46 | * */ 47 | version: z.literal(3), 48 | }), 49 | ); 50 | export type AuthTokenV3 = z.input; 51 | 52 | /**@internal */ 53 | export const AuthTokenSchema = z.union([ 54 | authTokenV1_2Schema, 55 | authTokenV3Schema, 56 | ]); 57 | export type AuthToken = z.input; 58 | -------------------------------------------------------------------------------- /packages/common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@skyway-sdk/common", 3 | "version": "2.0.2", 4 | "description": "The official Next Generation JavaScript SDK for SkyWay", 5 | "homepage": "https://skyway.ntt.com/", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/skyway/js-sdk.git" 9 | }, 10 | "license": "MIT", 11 | "author": "NTT DOCOMO BUSINESS, Inc.", 12 | "main": "dist/index.js", 13 | "module": "dist/index.mjs", 14 | "types": "dist/index.d.ts", 15 | "files": [ 16 | "dist", 17 | "LICENSE" 18 | ], 19 | "scripts": { 20 | "build": "cp -r ../../bundler ./ && zx ./bundler/private.mjs && rm -rf ./bundler", 21 | "compile": "pnpm run compile:tsc && pnpm run compile:esbuild", 22 | "compile:esbuild": "esbuild src/index.ts --bundle --format=esm --target=es6 --outfile=dist/index.mjs", 23 | "compile:tsc": "rm -rf dist && tsc -p tsconfig.build.json", 24 | "format": "eslint ./src --fix", 25 | "license": "zx ../../scripts/license.mjs @skyway-sdk/common", 26 | "lint": "eslint ./src --fix", 27 | "graph": "dependency-cruiser --include-only '^src' --output-type dot src | dot -T svg > docs/dependencygraph.svg", 28 | "publish:npm": "pnpm dlx can-pnpm-publish --verbose && pnpm run build && npm publish --access public", 29 | "watch": "npm-run-all --parallel watch:tsc watch:esbuild", 30 | "watch:esbuild": "esbuild src/index.ts --bundle --watch --format=esm --target=es6 --outfile=dist/index.mjs", 31 | "watch:tsc": "tsc -p tsconfig.build.json -w" 32 | }, 33 | "dependencies": { 34 | "axios": "^1.7.7" 35 | }, 36 | "keywords": [ 37 | "webrtc", 38 | "skyway", 39 | "conferencing" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /packages/core/src/plugin/internal/person/plugin.ts: -------------------------------------------------------------------------------- 1 | import type model from '@skyway-sdk/model'; 2 | 3 | import type { SkyWayChannelImpl } from '../../../channel'; 4 | import type { SkyWayContext } from '../../../context'; 5 | import type { LocalPersonImpl } from '../../../member/localPerson'; 6 | import { SkyWayPlugin } from '../../interface/plugin'; 7 | import { MessageBuffer } from './connection/messageBuffer'; 8 | import { RemotePersonImpl } from './member'; 9 | 10 | export class PersonPlugin extends SkyWayPlugin { 11 | readonly subtype = 'person'; 12 | _messageBuffers: { [localPersonId: string]: MessageBuffer } = {}; 13 | 14 | readonly _whenCreateLocalPerson = async (person: LocalPersonImpl) => { 15 | if (person._signaling) { 16 | this._messageBuffers[person.id] = new MessageBuffer(person._signaling); 17 | } 18 | }; 19 | 20 | readonly _whenDisposeLocalPerson = async (person: LocalPersonImpl) => { 21 | const messageBuffer = this._messageBuffers[person.id]; 22 | if (messageBuffer) { 23 | messageBuffer.close(); 24 | delete this._messageBuffers[person.id]; 25 | } 26 | }; 27 | 28 | readonly _createRemoteMember = ( 29 | channel: SkyWayChannelImpl, 30 | memberDto: model.Member, 31 | ) => { 32 | const person = new RemotePersonImpl({ 33 | ...this._context, 34 | context: this._context!, 35 | channel, 36 | metadata: memberDto.metadata, 37 | id: memberDto.id, 38 | name: memberDto.name, 39 | plugin: this, 40 | }); 41 | return person; 42 | }; 43 | } 44 | 45 | export const registerPersonPlugin = (context: SkyWayContext) => { 46 | const plugin = new PersonPlugin(); 47 | context.registerPlugin(plugin); 48 | return plugin; 49 | }; 50 | -------------------------------------------------------------------------------- /packages/core/src/media/stream/local/customVideo.ts: -------------------------------------------------------------------------------- 1 | import { Logger, PromiseQueue } from '@skyway-sdk/common'; 2 | 3 | import type { VideoMediaTrackConstraints } from '../../factory'; 4 | import { 5 | emptyVideoTrack, 6 | LocalMediaStreamBase, 7 | type LocalMediaStreamOptions, 8 | } from './media'; 9 | 10 | const log = new Logger('packages/core/src/media/stream/local/customVideo.ts'); 11 | 12 | export interface ProcessedStream { 13 | track: MediaStreamTrack; 14 | setEnabled(enabled: boolean): Promise; 15 | dispose(): Promise; 16 | } 17 | 18 | export class LocalCustomVideoStream extends LocalMediaStreamBase { 19 | readonly contentType = 'video'; 20 | private _promiseQueue = new PromiseQueue(); 21 | private _stream: ProcessedStream | null; 22 | 23 | constructor( 24 | options: VideoMediaTrackConstraints & Partial = {}, 25 | ) { 26 | super(emptyVideoTrack, 'video', options); 27 | this._stream = null; 28 | } 29 | 30 | /**@internal */ 31 | async setStream(processedStream: ProcessedStream) { 32 | if (this._stream) { 33 | throw new Error('ProcessedStream is already exists'); 34 | } 35 | this._stream = processedStream; 36 | this._updateTrack(processedStream.track); 37 | } 38 | 39 | /**@internal */ 40 | async setEnabled(enabled: boolean) { 41 | await this._promiseQueue.push(async () => { 42 | await this._stream?.setEnabled(enabled); 43 | }); 44 | } 45 | 46 | /**@internal */ 47 | async updateTrack(track: MediaStreamTrack) { 48 | this._updateTrack(track); 49 | this._onEnableChanged.emit(track); 50 | } 51 | 52 | release(): void { 53 | this._stream?.dispose().catch(() => { 54 | log.error('release failed'); 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/common/src/error.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from './logger'; 2 | 3 | const log = new Logger('packages/common/src/error.ts'); 4 | 5 | export interface SkyWayErrorInterface extends Error { 6 | info: ErrorInfo; 7 | toJSON: () => object; 8 | } 9 | 10 | export class SkyWayError< 11 | PayloadType extends Record = Record, 12 | > 13 | extends Error 14 | implements SkyWayErrorInterface 15 | { 16 | private readonly id = Math.random().toString().slice(2, 10); 17 | payload?: PayloadType; 18 | path?: string; 19 | error?: Error; 20 | info!: ErrorInfo; 21 | 22 | /**@internal */ 23 | constructor( 24 | init: Pick, 'path' | 'payload' | 'error' | 'info'>, 25 | logging = true, 26 | ) { 27 | super(init.info.detail); 28 | Object.assign(this, init); 29 | this.name = this.info.name; 30 | 31 | if (logging) { 32 | const messages: any[] = [ 33 | 'SkyWayError', 34 | `name:${this.info.name}, detail:${this.info.detail}, solution:${this.info.solution}`, 35 | ]; 36 | if (this.path) { 37 | messages.push(this.path); 38 | } 39 | if (this.error) { 40 | messages.push(this.error); 41 | } 42 | if (this.payload) { 43 | messages.push(this.payload); 44 | } 45 | messages.push(this.id); 46 | 47 | log.warn(...messages); 48 | } 49 | } 50 | 51 | toJSON() { 52 | return { 53 | id: this.id, 54 | info: this.info, 55 | path: this.path, 56 | payload: this.payload, 57 | error: this.error, 58 | stack: this.stack, 59 | }; 60 | } 61 | } 62 | 63 | /**@internal */ 64 | export interface ErrorInfo { 65 | name: string; 66 | detail: string; 67 | solution: string; 68 | } 69 | -------------------------------------------------------------------------------- /karma.base.js: -------------------------------------------------------------------------------- 1 | const chromeFlags = [ 2 | '--use-fake-device-for-media-stream', 3 | '--use-fake-ui-for-media-stream', 4 | '--use-file-for-fake-audio-capture=./assets/1.wav', 5 | ]; 6 | const firefoxFlags = { 7 | 'media.navigator.permission.disabled': true, 8 | 'media.navigator.streams.fake': true, 9 | 'network.http.max-persistent-connections-per-server': 10000, 10 | }; 11 | 12 | module.exports = { 13 | basePath: '', 14 | frameworks: ['jasmine', 'karma-typescript'], 15 | exclude: [], 16 | reporters: ['progress', 'coverage'], 17 | port: 9876, 18 | colors: true, 19 | autoWatch: true, 20 | customLaunchers: { 21 | chrome_with_fake_device: { 22 | base: 'Chrome', 23 | flags: chromeFlags, 24 | }, 25 | chrome_headless_with_fake_device: { 26 | base: 'ChromeHeadless', 27 | flags: chromeFlags, 28 | }, 29 | safari: { 30 | base: 'Safari', 31 | }, 32 | FirefoxAutoAllowGUM: { 33 | base: 'Firefox', 34 | prefs: firefoxFlags, 35 | }, 36 | FirefoxHeadlessAutoAllowGUM: { 37 | base: 'FirefoxHeadless', 38 | prefs: firefoxFlags, 39 | }, 40 | }, 41 | singleRun: false, 42 | concurrency: Infinity, 43 | karmaTypescriptConfig: { 44 | compilerOptions: { 45 | baseUrl: '.', 46 | module: 'commonjs', 47 | target: 'ES2020', 48 | lib: ['DOM', 'ES2020'], 49 | sourceMap: true, 50 | declaration: true, 51 | declarationMap: true, 52 | noEmitOnError: true, 53 | skipLibCheck: true, 54 | esModuleInterop: true, 55 | strict: true, 56 | }, 57 | exclude: ['node_modules', 'example', 'examples', 'debug', 'typedoc', 'doc'], 58 | bundlerOptions: { 59 | transforms: [ 60 | require('karma-typescript-es6-transform')() 61 | ] 62 | }, 63 | }, 64 | }; 65 | -------------------------------------------------------------------------------- /packages/signaling-client/src/payloadTypes.ts: -------------------------------------------------------------------------------- 1 | export type Member = { 2 | id: string; 3 | name?: string; 4 | }; 5 | 6 | export type MessagePayload = { 7 | src: Member; 8 | requestEventId?: string; 9 | data: Record; 10 | }; 11 | 12 | const AcknowledgeReason = [ 13 | 'rateLimitExceeded', 14 | 'targetNotFound', 15 | 'payloadLengthExceeded', 16 | 'invalidPayload', 17 | 'unknown', 18 | 'parameterError', 19 | 'permissionError', 20 | ] as const; 21 | export type AcknowledgeReason = (typeof AcknowledgeReason)[number]; 22 | 23 | export type AcknowledgePayload = { 24 | eventId: string; 25 | ok: boolean; 26 | reason?: AcknowledgeReason; 27 | }; 28 | 29 | export function isMessagePayload(payload: any): payload is MessagePayload { 30 | if (!payload || typeof payload !== 'object') return false; 31 | if (!isMember(payload.src)) return false; 32 | if (!payload.data || typeof payload.data !== 'object') return false; 33 | return true; 34 | } 35 | 36 | export function isAcknowledgePayload( 37 | payload: any, 38 | ): payload is AcknowledgePayload { 39 | if (!payload || typeof payload !== 'object') return false; 40 | if (typeof payload.eventId !== 'string') return false; 41 | if (typeof payload.ok !== 'boolean') return false; 42 | if ( 43 | typeof payload.reason !== 'undefined' && 44 | (typeof payload.reason !== 'string' || 45 | !AcknowledgeReason.includes(payload.reason)) 46 | ) 47 | return false; 48 | return true; 49 | } 50 | 51 | export function isMember(arg: any): arg is Member { 52 | if (arg === undefined || Array.isArray(arg)) return false; 53 | if (typeof arg !== 'object') return false; 54 | if (typeof arg.id !== 'string') return false; 55 | if (typeof arg.name !== 'undefined' && typeof arg.name !== 'string') 56 | return false; 57 | return true; 58 | } 59 | -------------------------------------------------------------------------------- /packages/model/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@skyway-sdk/model", 3 | "version": "2.0.0", 4 | "description": "The official Next Generation JavaScript SDK for SkyWay", 5 | "homepage": "https://skyway.ntt.com/", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/skyway/js-sdk.git" 9 | }, 10 | "license": "MIT", 11 | "author": "NTT DOCOMO BUSINESS, Inc.", 12 | "main": "dist/index.js", 13 | "module": "dist/index.mjs", 14 | "types": "dist/index.d.ts", 15 | "files": [ 16 | "dist", 17 | "LICENSE" 18 | ], 19 | "scripts": { 20 | "build": "cp -r ../../bundler ./ && zx ./bundler/private.mjs && rm -rf ./bundler", 21 | "compile": "pnpm run compile:tsc && pnpm run compile:esbuild", 22 | "compile:esbuild": "esbuild src/index.ts --bundle --format=esm --target=es6 --outfile=dist/index.mjs", 23 | "compile:tsc": "rm -rf dist && tsc -p tsconfig.build.json", 24 | "format": "eslint ./src --fix", 25 | "license": "zx ../../scripts/license.mjs @skyway-sdk/model", 26 | "lint": "eslint ./src --fix", 27 | "gen": "openapi-typescript ../../skyway-rest-api-description/descriptions/rtc-api.v3-dev.skyway.io.json --output ./src/schema.ts", 28 | "graph": "dependency-cruiser --include-only '^src' --output-type dot src | dot -T svg > docs/dependencygraph.svg", 29 | "publish:npm": "pnpm dlx can-npm-publish --verbose && pnpm run build && npm publish --access public", 30 | "type": "tsc --noEmit -p ./tsconfig.json", 31 | "watch": "npm-run-all --parallel watch:tsc watch:esbuild", 32 | "watch:esbuild": "esbuild src/index.ts --bundle --watch --format=esm --target=es6 --outfile=dist/index.mjs", 33 | "watch:tsc": "tsc -p tsconfig.build.json -w" 34 | }, 35 | "devDependencies": {}, 36 | "keywords": [ 37 | "webrtc", 38 | "skyway", 39 | "conferencing" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /packages/core/src/plugin/internal/person/util.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@skyway-sdk/common'; 2 | 3 | import { detectDevice } from '../../../util'; 4 | import type { TransportConnectionState } from '../../interface'; 5 | 6 | const log = new Logger('packages/core/src/plugin/internal/person/util.ts'); 7 | 8 | /**@internal */ 9 | export const setEncodingParams = async ( 10 | sender: RTCRtpSender, 11 | newEncodings: RTCRtpEncodingParameters[], 12 | ) => { 13 | const info = log.createBlock({ label: 'setEncodingParams' }); 14 | 15 | const params = sender.getParameters(); 16 | info.debug('getParameters', { params, newEncodings }); 17 | 18 | if (params.encodings === undefined || params.encodings === null) { 19 | params.encodings = []; 20 | } 21 | params.encodings = newEncodings.map((encoding, i) => ({ 22 | ...(params.encodings[i] || {}), 23 | ...encoding, 24 | })); 25 | 26 | await sender.setParameters(params); 27 | }; 28 | 29 | /**@internal */ 30 | export const isSafari = () => 31 | detectDevice() === 'Safari12' || detectDevice() === 'Safari11'; 32 | 33 | /**@internal */ 34 | export function convertConnectionState( 35 | state: RTCPeerConnectionState | 'reconnecting', 36 | ): TransportConnectionState { 37 | switch (state) { 38 | case 'closed': 39 | case 'disconnected': 40 | case 'failed': 41 | return 'disconnected'; 42 | case 'connected': 43 | return 'connected'; 44 | case 'connecting': 45 | return 'connecting'; 46 | case 'new': 47 | return 'new'; 48 | case 'reconnecting': 49 | return 'reconnecting'; 50 | } 51 | } 52 | 53 | /**@internal */ 54 | export const statsToJson = (report: RTCStatsReport) => { 55 | const stats: any[] = []; 56 | report.forEach((stat) => { 57 | stats.push(JSON.parse(JSON.stringify(stat))); 58 | }); 59 | return stats; 60 | }; 61 | -------------------------------------------------------------------------------- /packages/core/src/plugin/internal/unknown/member.ts: -------------------------------------------------------------------------------- 1 | import type { SkyWayChannelImpl } from '../../../channel'; 2 | import type { SkyWayContext } from '../../../context'; 3 | import { MemberImpl } from '../../../member'; 4 | import type { LocalPersonImpl } from '../../../member/localPerson'; 5 | import type { RemoteMemberImplInterface } from '../../../member/remoteMember'; 6 | import { UnknownConnection } from './connection'; 7 | import type { UnknownPlugin } from './plugin'; 8 | 9 | export class UnknownMemberImpl 10 | extends MemberImpl 11 | implements RemoteMemberImplInterface 12 | { 13 | readonly type = 'bot'; 14 | readonly subtype: string; 15 | readonly side = 'remote'; 16 | readonly plugin: UnknownPlugin; 17 | 18 | private _connections: { [localPersonSystemId: string]: UnknownConnection } = 19 | {}; 20 | 21 | constructor(args: { 22 | channel: SkyWayChannelImpl; 23 | name?: string; 24 | id: string; 25 | metadata?: string; 26 | plugin: UnknownPlugin; 27 | subtype: string; 28 | context: SkyWayContext; 29 | }) { 30 | super(args); 31 | 32 | this.plugin = args.plugin; 33 | this.subtype = args.subtype; 34 | } 35 | 36 | /**@private */ 37 | _getConnection(localPersonId: string): UnknownConnection | undefined { 38 | return this._connections[localPersonId]; 39 | } 40 | 41 | /**@private */ 42 | _getOrCreateConnection(localPerson: LocalPersonImpl): UnknownConnection { 43 | const connection = 44 | this._getConnection(localPerson.id) ?? 45 | this._createConnection(localPerson, this); 46 | return connection; 47 | } 48 | 49 | private _createConnection( 50 | localPerson: LocalPersonImpl, 51 | endpointMember: RemoteMemberImplInterface, 52 | ): UnknownConnection { 53 | return new UnknownConnection(localPerson, endpointMember); 54 | } 55 | 56 | _dispose() {} 57 | } 58 | -------------------------------------------------------------------------------- /packages/room/src/room/sfu.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | LocalPersonAdapter, 3 | PublicationImpl, 4 | SkyWayChannelImpl, 5 | SkyWayContext, 6 | } from '@skyway-sdk/core'; 7 | import type { SFUBotPlugin } from '@skyway-sdk/sfu-bot'; 8 | 9 | import { 10 | type LocalSFURoomMember, 11 | LocalSFURoomMemberImpl, 12 | } from '../member/local/sfu'; 13 | import type { RoomPublicationImpl } from '../publication'; 14 | import { RoomBase, type RoomMemberInit } from './base'; 15 | import type { Room } from './default'; 16 | 17 | export interface SFURoom extends Room { 18 | /**@description [japanese] SFURoomにMemberを参加させる */ 19 | join: (memberInit?: RoomMemberInit) => Promise; 20 | } 21 | 22 | /**@internal */ 23 | export class SFURoomImpl extends RoomBase implements SFURoom { 24 | protected _disableSignaling = true; 25 | static async Create(context: SkyWayContext, channel: SkyWayChannelImpl) { 26 | const plugin = await SFURoomImpl._createBot(context, channel); 27 | const room = new SFURoomImpl(channel, plugin); 28 | return room; 29 | } 30 | 31 | localRoomMember?: LocalSFURoomMemberImpl; 32 | 33 | private constructor( 34 | channel: SkyWayChannelImpl, 35 | readonly _plugin: SFUBotPlugin, 36 | ) { 37 | super('sfu', channel); 38 | } 39 | 40 | protected _getTargetPublication( 41 | publicationId: string, 42 | ): RoomPublicationImpl | undefined { 43 | return this._getOriginPublication(publicationId); 44 | } 45 | 46 | protected _createLocalRoomMember( 47 | local: LocalPersonAdapter, 48 | room: this, 49 | ): T { 50 | return new LocalSFURoomMemberImpl(local, room) as T; 51 | } 52 | 53 | protected _isAcceptablePublication(p: PublicationImpl): boolean { 54 | // sfuのoriginとp2pを除外する 55 | if (p.type !== 'sfu' || !p.origin) { 56 | return false; 57 | } 58 | return true; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/sfu-api-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@skyway-sdk/sfu-api-client", 3 | "version": "2.0.2", 4 | "description": "The official Next Generation JavaScript SDK for SkyWay", 5 | "homepage": "https://skyway.ntt.com/", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/skyway/js-sdk.git" 9 | }, 10 | "license": "MIT", 11 | "author": "NTT DOCOMO BUSINESS, Inc.", 12 | "main": "dist/index.js", 13 | "module": "dist/index.mjs", 14 | "types": "dist/index.d.ts", 15 | "files": [ 16 | "dist", 17 | "LICENSE" 18 | ], 19 | "scripts": { 20 | "build": "cp -r ../../bundler ./ && zx ./bundler/private.mjs && rm -rf ./bundler", 21 | "compile": "pnpm run compile:tsc && pnpm run compile:esbuild", 22 | "compile:tsc": "rm -rf dist && tsc -p tsconfig.build.json", 23 | "compile:esbuild": "esbuild src/index.ts --bundle --format=esm --target=es6 --outfile=dist/index.mjs", 24 | "format": "eslint ./src --fix", 25 | "license": "zx ../../scripts/license.mjs @skyway-sdk/sfu-api-client", 26 | "lint": "eslint ./src --fix", 27 | "graph": "dependency-cruiser --include-only '^src' --output-type dot src | dot -T svg > docs/dependencygraph.svg", 28 | "pre:test": "cd ../../ && pnpm run build && cd packages/core", 29 | "publish:npm": "pnpm dlx can-npm-publish --verbose && pnpm run build && npm publish --access public", 30 | "type": "tsc --noEmit -p ./tsconfig.json", 31 | "watch": "npm-run-all --parallel watch:tsc watch:esbuild", 32 | "watch:esbuild": "esbuild src/index.ts --bundle --watch --format=esm --target=es6 --outfile=dist/index.mjs", 33 | "watch:tsc": "tsc -p tsconfig.build.json -w" 34 | }, 35 | "dependencies": { 36 | "@skyway-sdk/common": "workspace:*", 37 | "mediasoup-client": "^3.18.3" 38 | }, 39 | "keywords": [ 40 | "webrtc", 41 | "skyway", 42 | "conferencing", 43 | "sfu" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /packages/core/src/publication/factory.ts: -------------------------------------------------------------------------------- 1 | import type model from '@skyway-sdk/model'; 2 | import type { ContentType } from '@skyway-sdk/model'; 3 | 4 | import type { SkyWayChannelImpl } from '../channel'; 5 | import type { LocalStream } from '../media/stream/local'; 6 | import type { RemoteMemberImplInterface } from '../member/remoteMember'; 7 | import { PublicationImpl, type PublicationType } from '.'; 8 | 9 | /**@internal */ 10 | export function createPublication( 11 | channel: SkyWayChannelImpl, 12 | { 13 | publisherId, 14 | stream, 15 | origin, 16 | metadata, 17 | codecCapabilities, 18 | encodings, 19 | contentType, 20 | id, 21 | isEnabled, 22 | type, 23 | }: model.Publication & { stream?: T }, 24 | ): PublicationImpl { 25 | const exist = channel._getPublication(id); 26 | if (exist) { 27 | return exist as PublicationImpl; 28 | } 29 | 30 | contentType = contentType.toLowerCase() as ContentType; 31 | 32 | const originPublication = origin 33 | ? // todo fix originPublicationが不整合を起こすことがある 34 | channel._getPublication(origin) 35 | : undefined; 36 | 37 | // リレーされたPublicationのencodingsを設定する 38 | if (originPublication) { 39 | if (encodings.length === 0) { 40 | encodings = originPublication.encodings; 41 | } 42 | } 43 | 44 | const publisher = channel._getMember( 45 | publisherId, 46 | ) as RemoteMemberImplInterface; 47 | 48 | // typeがnullの場合はv2.0.0よりも前のバージョンにおけるp2pとして解釈する 49 | const publicationType: PublicationType = type ?? 'p2p'; 50 | 51 | const publication = new PublicationImpl({ 52 | id, 53 | channel, 54 | publisher, 55 | contentType, 56 | metadata, 57 | origin: originPublication, 58 | stream, 59 | codecCapabilities: codecCapabilities ?? [], 60 | encodings, 61 | isEnabled, 62 | type: publicationType, 63 | }); 64 | 65 | return publication; 66 | } 67 | -------------------------------------------------------------------------------- /packages/core/src/plugin/internal/unknown/connection.ts: -------------------------------------------------------------------------------- 1 | import { Event, Logger } from '@skyway-sdk/common'; 2 | 3 | import type { LocalPersonImpl } from '../../../member/localPerson'; 4 | import type { RemoteMemberImplInterface } from '../../../member/remoteMember'; 5 | import type { PublicationImpl } from '../../../publication'; 6 | import type { SubscriptionImpl } from '../../../subscription'; 7 | import type { SkyWayConnection } from '../../interface'; 8 | 9 | const log = new Logger( 10 | 'packages/core/src/plugin/internal/unknown/connection.ts', 11 | ); 12 | 13 | export class UnknownConnection implements SkyWayConnection { 14 | readonly type: string = 'unknown'; 15 | readonly onDisconnect = new Event(); 16 | readonly onClose = new Event(); 17 | closed = false; 18 | 19 | constructor( 20 | readonly localPerson: LocalPersonImpl, 21 | readonly remoteMember: RemoteMemberImplInterface, 22 | ) {} 23 | 24 | close() { 25 | this.closed = true; 26 | this.onClose.emit(); 27 | } 28 | 29 | async startPublishing(publication: PublicationImpl) { 30 | log.debug( 31 | `this is unknown type connection. should install ${this.remoteMember.subtype} plugin`, 32 | { publication }, 33 | ); 34 | } 35 | 36 | async stopPublishing(publication: PublicationImpl) { 37 | log.debug( 38 | `this is unknown type connection. should install ${this.remoteMember.subtype} plugin`, 39 | { publication }, 40 | ); 41 | } 42 | 43 | async startSubscribing(subscription: SubscriptionImpl) { 44 | log.debug( 45 | `this is unknown type connection. should install ${this.remoteMember.subtype} plugin`, 46 | { subscription }, 47 | ); 48 | } 49 | 50 | async stopSubscribing(subscription: SubscriptionImpl) { 51 | log.debug( 52 | `this is unknown type connection. should install ${this.remoteMember.subtype} plugin`, 53 | { subscription }, 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/room/src/errors.ts: -------------------------------------------------------------------------------- 1 | import { errors as coreErrors } from '@skyway-sdk/core'; 2 | import { errors as sfuErrors } from '@skyway-sdk/sfu-bot'; 3 | 4 | export const roomErrors = { 5 | invalidParameter: { name: 'invalidParameter', detail: '', solution: '' }, 6 | timeout: { name: 'timeout', detail: '', solution: '' }, 7 | internal: { name: 'internal', detail: '', solution: '' }, 8 | notImplemented: { 9 | name: 'notImplemented', 10 | detail: '対応していないRoomTypeです', 11 | solution: '正しいRoomTypeを指定してください', 12 | }, 13 | roomNotOpened: { 14 | name: 'roomNotOpened', 15 | detail: 'RoomがOpenされていません', 16 | solution: 'Roomの状態を確かめてください', 17 | }, 18 | subscribeOtherMemberType: { 19 | name: 'subscribeOtherMemberType', 20 | detail: 21 | 'RemoteMemberにSubscribe/Unsubscribeさせる場合、対象のMemberのTypeはPersonである必要があります', 22 | solution: '対象のRemoteMemberが正しいか確認してください', 23 | }, 24 | sfuPublicationNotSupportDataStream: { 25 | name: 'sfuPublicationNotSupportDataStream', 26 | detail: 27 | 'RoomでPublicationのtypeを"sfu"に設定したり、SFURoomを使用したりする場合、DataStreamを使うことは出来ません', 28 | solution: 29 | 'Roomを使用して、DataStreamのpublishのoptions.typeに"p2p"を指定してください', 30 | }, 31 | publicationNotHasOrigin: { 32 | name: 'publicationNotHasOrigin', 33 | detail: 'SFURoomで操作するPublicationはOriginをもつ必要があります', 34 | solution: 'SFURoomとP2PRoomを同一のIDで混在させていないか確かめてください', 35 | }, 36 | notFound: { 37 | name: 'notFound', 38 | detail: '参照しようとしていたものが見つかりません', 39 | solution: '参照しようとしたものが存在するか確かめてください', 40 | }, 41 | invalidPublicationTypeForP2PRoom: { 42 | name: 'invalidPublicationTypeForP2PRoom', 43 | detail: 'P2PRoomでSFUのPublicationをpublishすることは出来ません', 44 | solution: 'publishのoptions.typeに"p2p"を指定してください', 45 | }, 46 | invalidPublicationTypeForSFURoom: { 47 | name: 'invalidPublicationTypeForSFURoom', 48 | detail: 'SFURoomでP2PのPublicationをpublishすることは出来ません', 49 | solution: 'publishのoptions.typeに"sfu"を指定してください', 50 | }, 51 | } as const; 52 | 53 | export const errors = { ...coreErrors, ...sfuErrors, ...roomErrors } as const; 54 | -------------------------------------------------------------------------------- /packages/core/src/media/stream/base.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@skyway-sdk/common'; 2 | 3 | import { errors } from '../../errors'; 4 | import { createError } from '../../util'; 5 | 6 | const log = new Logger('packages/core/src/media/stream/base.ts'); 7 | 8 | export interface Stream { 9 | id: string; 10 | side: StreamSide; 11 | contentType: ContentType; 12 | } 13 | 14 | export type StreamSide = 'remote' | 'local'; 15 | export type ContentType = 'audio' | 'data' | 'video'; 16 | 17 | export type WebRTCStats = { id: string; type: string; [key: string]: any }[]; 18 | 19 | /**@internal */ 20 | export function attachElement( 21 | element: HTMLAudioElement | HTMLVideoElement, 22 | track: MediaStreamTrack, 23 | ) { 24 | if (element?.srcObject === undefined) { 25 | throw createError({ 26 | operationName: 'attachElement', 27 | info: errors.invalidElement, 28 | payload: { element }, 29 | path: log.prefix, 30 | }); 31 | } 32 | 33 | if (element.srcObject) { 34 | const stream = element.srcObject as MediaStream; 35 | 36 | const ended = stream.getTracks().find((t) => t.readyState === 'ended'); 37 | if (ended) { 38 | stream.removeTrack(ended); 39 | } 40 | 41 | const duplicate = stream.getTracks().find((t) => t.kind === track.kind); 42 | if (duplicate) { 43 | stream.removeTrack(duplicate); 44 | } 45 | 46 | stream.addTrack(track); 47 | } else { 48 | element.srcObject = new MediaStream([track]); 49 | } 50 | } 51 | 52 | /**@internal */ 53 | export function detachElement( 54 | element: HTMLAudioElement | HTMLVideoElement, 55 | track: MediaStreamTrack, 56 | ) { 57 | if (element?.srcObject === undefined) { 58 | throw createError({ 59 | operationName: 'attachElement', 60 | info: errors.invalidElement, 61 | payload: { element }, 62 | path: log.prefix, 63 | }); 64 | } 65 | 66 | const stream = element.srcObject as MediaStream; 67 | if (stream.getTracks().length > 0) { 68 | stream.removeTrack(track); 69 | } 70 | 71 | if (stream.getTracks().length === 0) { 72 | element.srcObject = null; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/core/src/plugin/internal/person/connection/messageBuffer.ts: -------------------------------------------------------------------------------- 1 | import { EventDisposer, Logger } from '@skyway-sdk/common'; 2 | 3 | import type { 4 | MessageEvent, 5 | SignalingSession, 6 | } from '../../../../external/signaling'; 7 | 8 | const log = new Logger( 9 | 'packages/core/src/plugin/internal/person/connection/messageBuffer.ts', 10 | ); 11 | 12 | /* 13 | connectionが生成されるイベントの通信経路と、 14 | メッセージングの通信経路が違うので、 15 | connectionが生成される前にメッセージを受信する 16 | タイミング問題が起きうる。 17 | その対策のためのコンポーネント 18 | */ 19 | 20 | export class MessageBuffer { 21 | private _indicateMessageBuffer: { 22 | [requesterIdName: string]: MessageEvent[]; 23 | } = {}; 24 | private _excludeConnectionIndicateBuffering = new Set(); 25 | private _disposer = new EventDisposer(); 26 | 27 | constructor(readonly signaling: SignalingSession) { 28 | this.signaling.onMessage 29 | .add((req) => { 30 | const requesterIdName = req.src.id + req.src.name; 31 | 32 | if (this._excludeConnectionIndicateBuffering.has(requesterIdName)) { 33 | return; 34 | } 35 | 36 | if (!this._indicateMessageBuffer[requesterIdName]) { 37 | this._indicateMessageBuffer[requesterIdName] = []; 38 | } 39 | this._indicateMessageBuffer[requesterIdName].push(req); 40 | }) 41 | .disposer(this._disposer); 42 | } 43 | 44 | resolveMessagingBuffer({ id, name }: { id: string; name?: string }) { 45 | const endpointIdName = id + name; 46 | 47 | const bufferedIndicates = this._indicateMessageBuffer[endpointIdName]; 48 | if (bufferedIndicates?.length > 0) { 49 | log.debug('resolveMessagingBuffer', { length: bufferedIndicates.length }); 50 | 51 | bufferedIndicates.forEach((req) => { 52 | this.signaling.onMessage.emit(req); 53 | }); 54 | delete this._indicateMessageBuffer[endpointIdName]; 55 | } 56 | this._excludeConnectionIndicateBuffering.add(endpointIdName); 57 | } 58 | 59 | close() { 60 | this._disposer.dispose(); 61 | this._indicateMessageBuffer = {}; 62 | this._excludeConnectionIndicateBuffering = new Set(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/rtc-rpc-api-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@skyway-sdk/rtc-rpc-api-client", 3 | "version": "2.0.2", 4 | "description": "The official Next Generation JavaScript SDK for SkyWay", 5 | "homepage": "https://skyway.ntt.com/", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/skyway/js-sdk.git" 9 | }, 10 | "license": "MIT", 11 | "author": "NTT DOCOMO BUSINESS, Inc.", 12 | "main": "dist/index.js", 13 | "module": "dist/index.mjs", 14 | "types": "dist/index.d.ts", 15 | "files": [ 16 | "dist", 17 | "LICENSE" 18 | ], 19 | "scripts": { 20 | "build": "cp -r ../../bundler ./ && zx ./bundler/private.mjs && rm -rf ./bundler", 21 | "compile": "pnpm run compile:tsc && pnpm run compile:esbuild", 22 | "compile:esbuild": "esbuild src/index.ts --bundle --format=esm --target=es6 --outfile=dist/index.mjs", 23 | "compile:tsc": "rm -rf dist && tsc -p tsconfig.build.json", 24 | "e2e": "jest ./e2e --forceExit --coverage", 25 | "format": "eslint ./src --fix", 26 | "license": "zx ../../scripts/license.mjs @skyway-sdk/rtc-rpc-api-client", 27 | "lint": "eslint ./src --fix", 28 | "gen": "openapi-typescript ../../skyway-rest-api-description/descriptions/rtc-api.v3-dev.skyway.io.json --output ./src/schema.ts", 29 | "graph": "dependency-cruiser --include-only '^src' --output-type dot src | dot -T svg > docs/dependencygraph.svg", 30 | "publish:npm": "pnpm dlx can-npm-publish --verbose && pnpm run build && npm publish --access public", 31 | "type": "tsc --noEmit -p ./tsconfig.json", 32 | "watch": "npm-run-all --parallel watch:tsc watch:esbuild", 33 | "watch:esbuild": "esbuild src/index.ts --bundle --watch --format=esm --target=es6 --outfile=dist/index.mjs", 34 | "watch:tsc": "tsc -p tsconfig.build.json -w" 35 | }, 36 | "dependencies": { 37 | "@skyway-sdk/common": "workspace:*", 38 | "@skyway-sdk/model": "workspace:*", 39 | "isomorphic-ws": "^4.0.1", 40 | "uuid": "^9.0.0" 41 | }, 42 | "devDependencies": { 43 | "@skyway-sdk/token": "workspace:*", 44 | "@types/uuid": "^9.0.1" 45 | }, 46 | "keywords": [ 47 | "webrtc", 48 | "skyway", 49 | "conferencing" 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /packages/rtc-api-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@skyway-sdk/rtc-api-client", 3 | "version": "2.0.3", 4 | "description": "The official Next Generation JavaScript SDK for SkyWay", 5 | "homepage": "https://skyway.ntt.com/", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/skyway/js-sdk.git" 9 | }, 10 | "license": "MIT", 11 | "author": "NTT DOCOMO BUSINESS, Inc.", 12 | "main": "dist/index.js", 13 | "module": "dist/index.mjs", 14 | "types": "dist/index.d.ts", 15 | "files": [ 16 | "dist", 17 | "LICENSE" 18 | ], 19 | "scripts": { 20 | "build": "cp -r ../../bundler ./ && zx ./bundler/private.mjs && rm -rf ./bundler", 21 | "compile": "pnpm run compile:tsc && pnpm run compile:esbuild", 22 | "compile:esbuild": "esbuild src/index.ts --bundle --format=esm --target=es6 --outfile=dist/index.mjs", 23 | "compile:tsc": "rm -rf dist && tsc -p tsconfig.build.json", 24 | "e2e": "jest ./e2e --forceExit --coverage", 25 | "format": "eslint ./src --fix", 26 | "license": "zx ../../scripts/license.mjs @skyway-sdk/rtc-api-client", 27 | "lint": "eslint ./src --fix", 28 | "graph": "dependency-cruiser --include-only '^src' --output-type dot src | dot -T svg > docs/dependencygraph.svg", 29 | "publish:npm": "pnpm dlx can-npm-publish --verbose && pnpm run build && npm publish --access public", 30 | "test": "jest ./tests --forceExit --coverage", 31 | "test-all": "pnpm run test && pnpm run e2e", 32 | "type": "tsc --noEmit -p ./tsconfig.build.json", 33 | "watch": "npm-run-all --parallel watch:tsc watch:esbuild", 34 | "watch:esbuild": "esbuild src/index.ts --bundle --watch --format=esm --target=es6 --outfile=dist/index.mjs", 35 | "watch:tsc": "tsc -p tsconfig.build.json -w" 36 | }, 37 | "dependencies": { 38 | "@skyway-sdk/rtc-rpc-api-client": "workspace:*", 39 | "@skyway-sdk/token": "workspace:*", 40 | "@skyway-sdk/common": "workspace:*", 41 | "deepmerge": "^4.3.1", 42 | "uuid": "^9.0.0" 43 | }, 44 | "devDependencies": { 45 | "@skyway-sdk/model": "workspace:*", 46 | "@types/uuid": "^9.0.1" 47 | }, 48 | "keywords": [ 49 | "webrtc", 50 | "skyway", 51 | "conferencing" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /packages/signaling-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@skyway-sdk/signaling-client", 3 | "version": "2.0.0", 4 | "description": "The official Next Generation JavaScript SDK for SkyWay", 5 | "homepage": "https://skyway.ntt.com/", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/skyway/js-sdk.git" 9 | }, 10 | "license": "MIT", 11 | "author": "NTT DOCOMO BUSINESS, Inc.", 12 | "main": "dist/index.js", 13 | "module": "dist/index.mjs", 14 | "types": "dist/index.d.ts", 15 | "files": [ 16 | "dist", 17 | "LICENSE" 18 | ], 19 | "scripts": { 20 | "build": "cp -r ../../bundler ./ && zx ./bundler/private.mjs && rm -rf ./bundler", 21 | "compile": "pnpm run compile:tsc && pnpm run compile:esbuild", 22 | "compile:esbuild": "esbuild src/index.ts --bundle --format=esm --target=es6 --outfile=dist/index.mjs", 23 | "compile:tsc": "rm -rf dist && tsc -p tsconfig.build.json", 24 | "fix": "pnpm run format && pnpm run lint", 25 | "format": "prettier --write src __tests__", 26 | "graph": "dependency-cruiser --include-only '^src' --output-type dot src | dot -T svg > docs/dependencygraph.svg", 27 | "license": "zx ../../scripts/license.mjs @skyway-sdk/signaling-client", 28 | "lint": "eslint --fix src __tests__", 29 | "publish:npm": "pnpm dlx can-npm-publish --verbose && pnpm run build && npm publish --access public", 30 | "test": "jest --silent", 31 | "test:console": "jest", 32 | "type": "tsc --noEmit -p ./tsconfig.build.json", 33 | "watch": "npm-run-all --parallel watch:tsc watch:esbuild", 34 | "watch:esbuild": "esbuild src/index.ts --bundle --watch --format=esm --target=es6 --outfile=dist/index.mjs", 35 | "watch:tsc": "tsc -p tsconfig.build.json -w" 36 | }, 37 | "dependencies": { 38 | "isomorphic-fetch": "^3.0.0", 39 | "isomorphic-ws": "^4.0.1", 40 | "uuid": "^9.0.0", 41 | "ws": "^8.17.1" 42 | }, 43 | "devDependencies": { 44 | "@types/isomorphic-fetch": "^0.0.39", 45 | "@types/jest": "^27.0.0", 46 | "@types/node": "^18.12.0", 47 | "@types/uuid": "^9.0.1", 48 | "@types/ws": "^8.5.10", 49 | "jest": "^27.0.6", 50 | "ts-jest": "^27.0.3" 51 | }, 52 | "keywords": [ 53 | "webrtc", 54 | "skyway", 55 | "conferencing" 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /packages/analytics-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@skyway-sdk/analytics-client", 3 | "version": "2.0.0", 4 | "description": "The official Next Generation JavaScript SDK for SkyWay", 5 | "homepage": "https://skyway.ntt.com/", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/skyway/js-sdk.git" 9 | }, 10 | "license": "MIT", 11 | "author": "NTT DOCOMO BUSINESS, Inc.", 12 | "main": "dist/index.js", 13 | "module": "dist/index.mjs", 14 | "types": "dist/index.d.ts", 15 | "files": [ 16 | "dist", 17 | "LICENSE" 18 | ], 19 | "scripts": { 20 | "build": "cp -r ../../bundler ./ && zx ./bundler/private.mjs && rm -rf ./bundler", 21 | "compile": "pnpm run compile:tsc && pnpm run compile:esbuild", 22 | "compile:esbuild": "esbuild src/index.ts --bundle --format=esm --target=es6 --outfile=dist/index.mjs", 23 | "compile:tsc": "rm -rf dist && tsc -p tsconfig.build.json", 24 | "fix": "pnpm run format && pnpm run lint", 25 | "format": "prettier --write src __tests__", 26 | "graph": "dependency-cruiser --include-only '^src' --output-type dot src | dot -T svg > docs/dependencygraph.svg", 27 | "license": "zx ../../scripts/license.mjs @skyway-sdk/analytics-client", 28 | "lint": "eslint --fix src __tests__", 29 | "publish:npm": "pnpm dlx can-npm-publish --verbose && pnpm run build && npm publish --access public", 30 | "test": "jest --silent --forceExit", 31 | "test:console": "jest --forceExit", 32 | "type": "tsc --noEmit -p ./tsconfig.build.json", 33 | "watch": "npm-run-all --parallel watch:tsc watch:esbuild", 34 | "watch:esbuild": "esbuild src/index.ts --bundle --watch --format=esm --target=es6 --outfile=dist/index.mjs", 35 | "watch:tsc": "tsc -p tsconfig.build.json -w" 36 | }, 37 | "dependencies": { 38 | "isomorphic-ws": "^4.0.1", 39 | "uuid": "^9.0.0", 40 | "ws": "^8.17.1" 41 | }, 42 | "devDependencies": { 43 | "@skyway-sdk/model": "workspace:*", 44 | "@skyway-sdk/common": "workspace:*", 45 | "@types/jest": "^27.0.0", 46 | "@types/node": "^18.12.0", 47 | "@types/uuid": "^9.0.1", 48 | "@types/ws": "^8.5.10", 49 | "jest": "^27.0.6", 50 | "jsonc-parser": "^3.3.1" 51 | }, 52 | "keywords": [ 53 | "webrtc", 54 | "skyway", 55 | "conferencing", 56 | "getstats" 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /packages/token/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@skyway-sdk/token", 3 | "version": "2.0.2", 4 | "description": "The official Next Generation JavaScript SDK for SkyWay", 5 | "homepage": "https://skyway.ntt.com/", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/skyway/js-sdk.git" 9 | }, 10 | "license": "MIT", 11 | "author": "NTT DOCOMO BUSINESS, Inc.", 12 | "main": "dist/index.js", 13 | "module": "dist/index.mjs", 14 | "types": "dist/index.d.ts", 15 | "files": [ 16 | "dist", 17 | "LICENSE" 18 | ], 19 | "scripts": { 20 | "build": "zx bundle.mjs", 21 | "compile": "pnpm run compile:tsc && pnpm run compile:esbuild", 22 | "compile:esbuild": "esbuild src/index.ts --bundle --format=esm --target=es6 --outfile=dist/index.mjs", 23 | "compile:tsc": "rm -rf dist && tsc -p tsconfig.build.json", 24 | "doc": "pnpm run doc:html && pnpm run doc:md", 25 | "doc:html": "rm -rf docs/html && typedoc --excludePrivate --disableSources --excludeInternal --tsconfig ./tsconfig.build.json --out docs/html --plugin typedoc-plugin-zod ./src/index.ts ", 26 | "doc:md": "rm -rf docs/md && typedoc --excludePrivate --disableSources --excludeInternal --tsconfig ./tsconfig.build.json --out docs/md --plugin typedoc-plugin-markdown ./src/index.ts ", 27 | "format": "eslint ./src --fix", 28 | "license": "zx ../../scripts/license.mjs @skyway-sdk/token", 29 | "lint": "eslint ./src --fix", 30 | "graph": "dependency-cruiser --include-only '^src' --output-type dot src | dot -T svg > docs/dependencygraph.svg", 31 | "publish:npm": "pnpm dlx can-npm-publish --verbose && pnpm run build && npm publish --access public", 32 | "watch": "npm-run-all --parallel watch:tsc watch:esbuild", 33 | "watch:esbuild": "esbuild src/index.ts --bundle --watch --format=esm --target=es6 --outfile=dist/index.mjs", 34 | "watch:tsc": "tsc -p tsconfig.build.json -w" 35 | }, 36 | "dependencies": { 37 | "@skyway-sdk/common": "workspace:*", 38 | "jsrsasign": "^11.1.0", 39 | "jwt-decode": "3.1.2", 40 | "uuid": "^9.0.0", 41 | "zod": "^3.24.1", 42 | "zod-validation-error": "^3.4.0" 43 | }, 44 | "devDependencies": { 45 | "@types/jsrsasign": "^10.5.14", 46 | "@types/uuid": "^9.0.1" 47 | }, 48 | "keywords": [ 49 | "webrtc", 50 | "skyway", 51 | "conferencing" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /packages/room/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@skyway-sdk/common'; 2 | export { 3 | AnalyticsSession, 4 | type AudioMediaTrackConstraints, 5 | type ChannelQuery, 6 | type ChannelState, 7 | type Codec, 8 | type CodecParameters, 9 | type ContentType, 10 | ContextConfig, 11 | createTestVideoTrack, 12 | type DataStreamMessageType, 13 | type DataStreamOptions, 14 | type DataStreamSubscriber, 15 | type DataType, 16 | type DisplayMediaTrackConstraints, 17 | type DisplayStreamOptions, 18 | detectDevice, 19 | type EncodingParameters, 20 | Event, 21 | Events, 22 | getBitrateFromPeerConnection, 23 | getRtcRtpCapabilities, 24 | LocalAudioStream, 25 | LocalCustomVideoStream, 26 | LocalDataStream, 27 | LocalMediaStreamBase, 28 | type LocalMediaStreamOptions, 29 | type LocalMemberConfig, 30 | type LocalStream, 31 | LocalStreamBase, 32 | LocalVideoStream, 33 | MediaDevice, 34 | type MemberSide, 35 | type MemberState, 36 | type MemberType, 37 | type PersonInit, 38 | type PublicationOptions, 39 | type PublicationState, 40 | RemoteAudioStream, 41 | RemoteDataStream, 42 | RemoteMediaStreamBase, 43 | type RemoteStream, 44 | RemoteStreamBase, 45 | RemoteVideoStream, 46 | type ReplaceStreamOptions, 47 | type RtcApiConfig, 48 | type RtcRpcApiConfig, 49 | type SkyWayConfigOptions, 50 | SkyWayContext, 51 | type SkyWayContextInterface, 52 | SkyWayStreamFactory, 53 | type Stream, 54 | StreamFactory, 55 | type StreamSide, 56 | type SubscriptionOptions, 57 | type SubscriptionState, 58 | sortEncodingParameters, 59 | type TransportConnectionState, 60 | type TurnPolicy, 61 | type TurnProtocol, 62 | type VideoMediaTrackConstraints, 63 | type WebRTCStats, 64 | } from '@skyway-sdk/core'; 65 | export * from '@skyway-sdk/token'; 66 | export { errors } from './errors'; 67 | export * from './member'; 68 | export * from './member/local/base'; 69 | export * from './member/local/default'; 70 | export * from './member/local/p2p'; 71 | export * from './member/local/sfu'; 72 | export * from './member/remote/base'; 73 | export * from './publication'; 74 | export * from './room'; 75 | export * from './room/base'; 76 | export * from './room/default'; 77 | export * from './room/event'; 78 | export * from './room/p2p'; 79 | export * from './room/sfu'; 80 | export * from './subscription'; 81 | export * from './version'; 82 | -------------------------------------------------------------------------------- /packages/sfu-bot/src/errors.ts: -------------------------------------------------------------------------------- 1 | import { errors as apiErrors } from '@skyway-sdk/sfu-api-client'; 2 | 3 | export const errors = { 4 | ...apiErrors, 5 | invalidParameter: { name: 'invalidParameter', detail: '', solution: '' }, 6 | timeout: { name: 'timeout', detail: '', solution: '' }, 7 | internal: { name: 'internal', detail: '', solution: '' }, 8 | sfuBotNotInChannel: { 9 | name: 'sfuBotNotInChannel', 10 | detail: 'SFUBotがChannelに存在しません', 11 | solution: '操作しようとしているSFUBotが正しいか確かめてください', 12 | }, 13 | remotePublisherId: { 14 | name: 'remotePublisherId', 15 | detail: 'publisherがremoteのPublicationをForwardingすることはできません', 16 | solution: 'PublicationがLocalでPublishされたものか確かめてください', 17 | }, 18 | dataStreamNotSupported: { 19 | name: 'dataStreamNotSupported', 20 | detail: 'dataStreamはSFUに対応していません', 21 | solution: 'ありません', 22 | }, 23 | streamNotExistInPublication: { 24 | name: 'streamNotExistInPublication', 25 | detail: 26 | 'PublicationにStreamがありません。RemoteMemberのPublicationのStreamにはアクセスできません', 27 | solution: '参照しているPublicationが目的のものか確かめてください。', 28 | }, 29 | invalidPreferredEncoding: { 30 | name: 'invalidPreferredEncoding', 31 | detail: 32 | 'preferredEncodingの値が不正です。エンコード設定切り替え機能が使えません', 33 | solution: '正しい文字列を入力してください', 34 | }, 35 | invalidEncodings: { 36 | name: 'invalidEncodings', 37 | detail: 38 | 'エンコード設定が設定されていません。エンコード設定切り替え機能が使えません', 39 | solution: 40 | 'エンコード設定切り替え機能を利用する場合はエンコード設定をしたPublicationをForwardingしてください', 41 | }, 42 | receiverNotFound: { 43 | name: 'receiverNotFound', 44 | detail: 'SFUはRemoteMemberのSubscriptionを操作できません', 45 | solution: 46 | 'SFUでsubscriptionの操作をする際にはLocalPersonがSubscribeしているSubscriptionインスタンスを利用してください', 47 | }, 48 | consumerNotFound: { 49 | name: 'consumerNotFound', 50 | detail: 'SFUはLocalPersonがUnsubscribeしたSubscriptionを操出来ません', 51 | solution: '操作対象のSubscriptionが正しいか確かめてください', 52 | }, 53 | forwardingNotFound: { 54 | name: 'forwardingNotFound', 55 | detail: '存在しないForwardingを操作しようとしています', 56 | solution: '対象のForwardingが正しいか確かめてください', 57 | }, 58 | netWorkError: { 59 | name: 'netWorkError', 60 | detail: '通信環境に問題があります', 61 | solution: 'ネットワーク接続状況を確認してください', 62 | }, 63 | confirmSubscriptionFailed: { 64 | name: 'confirmSubscriptionFailed', 65 | detail: 'Forwardingのconsume許可を出すのに失敗しました', 66 | solution: 'ありません', 67 | }, 68 | } as const; 69 | -------------------------------------------------------------------------------- /packages/core/src/member/localPerson/agent/publishing.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@skyway-sdk/common'; 2 | 3 | import { errors } from '../../../errors'; 4 | import type { RemoteMemberImplInterface } from '../../../member/remoteMember'; 5 | import type { PublicationImpl } from '../../../publication'; 6 | import type { SubscriptionImpl } from '../../../subscription'; 7 | import { createError } from '../../../util'; 8 | import type { LocalPersonImpl } from '../../localPerson'; 9 | 10 | const log = new Logger('packages/core/src/dataPlane/agent/publishing.ts'); 11 | 12 | export class PublishingAgent { 13 | readonly context = this._localPerson.context; 14 | constructor(private readonly _localPerson: LocalPersonImpl) {} 15 | 16 | /**@throws {SkyWayError} */ 17 | async startPublishing(subscription: SubscriptionImpl): Promise { 18 | if (this.context.config.internal.disableDPlane) { 19 | await new Promise((r) => setTimeout(r, 500)); 20 | return; 21 | } 22 | const publication: PublicationImpl = subscription.publication; 23 | const endpoint: RemoteMemberImplInterface = subscription.subscriber; 24 | 25 | // タイミング的にstreamのセットが完了していない可能性がある 26 | if (!publication.stream) { 27 | await this._localPerson.onStreamPublished 28 | .watch( 29 | (e) => e.publication.id === publication.id, 30 | this.context.config.rtcApi.timeout, 31 | ) 32 | .catch((error) => { 33 | throw createError({ 34 | operationName: 'PublishingAgent.startPublishing', 35 | context: this.context, 36 | channel: this._localPerson.channel, 37 | info: { 38 | ...errors.timeout, 39 | detail: 'PublishingAgent onStreamPublished', 40 | }, 41 | path: log.prefix, 42 | payload: { publication }, 43 | error, 44 | }); 45 | }); 46 | } 47 | 48 | const connection = endpoint._getOrCreateConnection(this._localPerson); 49 | 50 | if (connection.startPublishing) { 51 | await connection.startPublishing(publication, subscription.id); 52 | } 53 | } 54 | 55 | async stopPublishing( 56 | publication: PublicationImpl, 57 | endpoint: RemoteMemberImplInterface, 58 | ) { 59 | const connection = endpoint._getConnection(this._localPerson.id); 60 | if (connection?.stopPublishing) { 61 | connection.stopPublishing(publication).catch((err) => { 62 | log.error('stopPublishing failed', err); 63 | }); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/core/src/member/localPerson/factory.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@skyway-sdk/common'; 2 | import type model from '@skyway-sdk/model'; 3 | 4 | import type { PersonInit, SkyWayChannelImpl } from '../../channel'; 5 | import { MaxIceParamServerTTL } from '../../const'; 6 | import type { SkyWayContext } from '../../context'; 7 | import { errors } from '../../errors'; 8 | import { IceManager } from '../../external/ice'; 9 | import { setupSignalingSession } from '../../external/signaling'; 10 | import { createError } from '../../util'; 11 | import { LocalPersonImpl } from '.'; 12 | 13 | const log = new Logger('packages/core/src/member/person/local/factory.ts'); 14 | 15 | /**@internal */ 16 | export async function createLocalPerson( 17 | context: SkyWayContext, 18 | channel: SkyWayChannelImpl, 19 | memberDto: model.Member, 20 | { 21 | keepaliveIntervalSec, 22 | keepaliveIntervalGapSec, 23 | preventAutoLeaveOnBeforeUnload, 24 | disableSignaling, 25 | }: PersonInit = {}, 26 | ) { 27 | log.debug('createLocalPerson', { 28 | channel, 29 | memberDto, 30 | keepaliveIntervalSec, 31 | keepaliveIntervalGapSec, 32 | preventAutoLeaveOnBeforeUnload, 33 | }); 34 | 35 | const { iceParamServer } = context.config; 36 | 37 | const signalingSession = 38 | disableSignaling === true 39 | ? undefined 40 | : await setupSignalingSession(context, channel, memberDto); 41 | 42 | const iceManager = new IceManager({ 43 | ...iceParamServer, 44 | memberId: memberDto.id, 45 | channelId: channel.id, 46 | ttl: MaxIceParamServerTTL, 47 | context, 48 | }); 49 | 50 | await iceManager.updateIceParams().catch((err) => { 51 | throw createError({ 52 | operationName: 'createLocalPerson', 53 | context, 54 | channel, 55 | info: { ...errors.internal, detail: 'updateIceParams failed' }, 56 | path: log.prefix, 57 | error: err, 58 | }); 59 | }); 60 | 61 | const person = await LocalPersonImpl.Create({ 62 | iceManager, 63 | channel, 64 | signaling: signalingSession, 65 | analytics: context.analyticsSession, 66 | metadata: memberDto.metadata, 67 | name: memberDto.name, 68 | id: memberDto.id, 69 | keepaliveIntervalSec, 70 | keepaliveIntervalGapSec, 71 | preventAutoLeaveOnBeforeUnload, 72 | context, 73 | }); 74 | 75 | for (const plugin of context.plugins) { 76 | await plugin._whenCreateLocalPerson?.(person); 77 | person._onDisposed.once(async () => { 78 | await plugin._whenDisposeLocalPerson?.(person); 79 | }); 80 | } 81 | 82 | return person; 83 | } 84 | -------------------------------------------------------------------------------- /packages/core/src/media/stream/local/data.ts: -------------------------------------------------------------------------------- 1 | import { Event, Logger } from '@skyway-sdk/common'; 2 | 3 | import { errors } from '../../../errors'; 4 | import { createError } from '../../../util'; 5 | import { LocalStreamBase } from '.'; 6 | 7 | const log = new Logger('packages/core/src/media/stream/local/data.ts'); 8 | 9 | /**@description [japanese] DataStreamにて送受信できるデータの型。object型のデータを送信する場合、ArrayBufferなどの`JSON.stringify`に非対応な型をプロパティとして含めると正しいデータが送受信されないため、別途エンコード・デコード処理の実装が必要。 */ 10 | export type DataStreamMessageType = string | ArrayBuffer | object; 11 | 12 | export class LocalDataStream extends LocalStreamBase { 13 | readonly contentType = 'data'; 14 | /**@private */ 15 | readonly _onWriteData = new Event(); 16 | private _isEnabled = true; 17 | 18 | /** 19 | * @description [japanese] データストリームが書き込み可能な状態になったことを通知するイベント 20 | * イベントデータとして、書き込み可能になったデータストリームのSubscriberの情報が通知される。 21 | */ 22 | readonly onWritable = new Event(); 23 | /** 24 | * @description [japanese] データストリームが書き込み不可能な状態になったことを通知するイベント。 25 | * イベントデータとして、書き込み不可能になったデータストリームのSubscriberの情報が通知される。 26 | */ 27 | readonly onUnwritable = new Event(); 28 | 29 | constructor(public readonly options: DataStreamOptions = {}) { 30 | super('data'); 31 | this._setLabel('LocalDataStream'); 32 | } 33 | 34 | /**@internal */ 35 | setIsEnabled(b: boolean) { 36 | this._isEnabled = b; 37 | } 38 | 39 | /**@description [japanese] データを送信する */ 40 | write(data: DataStreamMessageType) { 41 | if (!this._isEnabled) { 42 | throw createError({ 43 | operationName: 'LocalDataStream.write', 44 | path: log.prefix, 45 | info: errors.disabledDataStream, 46 | }); 47 | } 48 | 49 | const isObject = 50 | !ArrayBuffer.isView(data) && 51 | !(data instanceof ArrayBuffer) && 52 | !(typeof data === 'string'); 53 | if (isObject) { 54 | data = objectFlag + JSON.stringify(data); 55 | } 56 | this._onWriteData.emit(data); 57 | } 58 | } 59 | 60 | /**@internal */ 61 | export const objectFlag = 'skyway_object:'; 62 | 63 | export type DataStreamOptions = { 64 | /** 65 | * @description [japanese] 再送待ち時間上限 66 | */ 67 | maxPacketLifeTime?: number; 68 | /** 69 | * @description [japanese] 再送回数上限 70 | */ 71 | maxRetransmits?: number; 72 | /** 73 | * @description [japanese] 順序制御 74 | */ 75 | ordered?: boolean; 76 | }; 77 | 78 | /**@description [japanese] データストリームをSubscribeしているMemberの情報 */ 79 | export type DataStreamSubscriber = { 80 | id: string; 81 | name?: string; 82 | }; 83 | -------------------------------------------------------------------------------- /packages/core/src/media/stream/audioLevel.ts: -------------------------------------------------------------------------------- 1 | /**@internal */ 2 | export class AudioLevel { 3 | // ChromeのAudioLevelに近い挙動とするために取得範囲と更新周期を100msとする 4 | private readonly LEVEL_RANGE_MS = 100; 5 | // ChromeのAudioLevelに近い挙動とするために100ms毎に1/4減衰させる 6 | private readonly DECAY_FACTOR = 0.25; // 1/4 7 | private readonly DECAY_INTERVAL_MS = 100; 8 | // 使用する環境によってサンプリングレートは8000〜48000Hzまで様々であるため、中間値24000Hzを想定したデフォルト値を設定する 9 | private readonly DEFAULT_BUFFER_SIZE = 24000 * (this.LEVEL_RANGE_MS / 1000); 10 | 11 | private currentMaxLevel = 0; 12 | private readonly analyser: AnalyserNode; 13 | private readonly audioContext: AudioContext; 14 | private decayTimer: NodeJS.Timeout | null; 15 | 16 | constructor(audioStreamTrack: MediaStreamTrack) { 17 | this.audioContext = new AudioContext(); 18 | 19 | this.analyser = this.setupAnalyser(audioStreamTrack); 20 | this.decayTimer = this.setDecayTimer(); 21 | } 22 | 23 | async [Symbol.dispose](): Promise { 24 | await this.dispose(); 25 | } 26 | 27 | calculate() { 28 | // マイクの切り替えを考慮して毎回AudioContextからsampleRateを取得する 29 | const sampleRate = this.audioContext.sampleRate; 30 | 31 | // LEVEL_RANGE_MS分の音声サンプルを取得する 32 | const duration = this.LEVEL_RANGE_MS / 1000; 33 | const bufferLength = sampleRate 34 | ? sampleRate * duration 35 | : this.DEFAULT_BUFFER_SIZE; 36 | const timeDomainData = new Float32Array(bufferLength); 37 | this.analyser.getFloatTimeDomainData(timeDomainData); 38 | let level = Math.max(...timeDomainData); 39 | 40 | // 大きな音が発生した場合その影響を残すために保持する 41 | const _currentMaxLevel = this.currentMaxLevel; 42 | if (level > _currentMaxLevel) { 43 | this.currentMaxLevel = level; 44 | } else { 45 | level = _currentMaxLevel; 46 | } 47 | 48 | return this.clamp(level, 0, 1); 49 | } 50 | 51 | private setupAnalyser(audioStreamTrack: MediaStreamTrack) { 52 | const mediaStream = new MediaStream([audioStreamTrack]); 53 | const source = this.audioContext.createMediaStreamSource(mediaStream); 54 | const analyser = this.audioContext.createAnalyser(); 55 | 56 | source.connect(analyser); 57 | 58 | return analyser; 59 | } 60 | 61 | private setDecayTimer() { 62 | // 100ms毎に現在の最大レベルを減衰させて一時的なピークの影響を抑える 63 | return setInterval(() => { 64 | this.currentMaxLevel = this.currentMaxLevel * this.DECAY_FACTOR; 65 | }, this.DECAY_INTERVAL_MS); 66 | } 67 | 68 | private clamp(value: number, min: number, max: number): number { 69 | return Math.min(Math.max(value, min), max); 70 | } 71 | 72 | private async dispose() { 73 | if (this.decayTimer) { 74 | clearInterval(this.decayTimer); 75 | this.decayTimer = null; 76 | } 77 | await this.audioContext.close(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/core/src/media/stream/local/video.ts: -------------------------------------------------------------------------------- 1 | import { Logger, PromiseQueue } from '@skyway-sdk/common'; 2 | 3 | import { errors } from '../../../errors'; 4 | import { createError } from '../../../util'; 5 | import type { 6 | DisplayMediaTrackConstraints, 7 | VideoMediaTrackConstraints, 8 | } from '../../factory'; 9 | import { LocalMediaStreamBase, type LocalMediaStreamOptions } from './media'; 10 | 11 | const log = new Logger('packages/core/src/media/stream/local/video.ts'); 12 | 13 | export class LocalVideoStream extends LocalMediaStreamBase { 14 | readonly contentType = 'video'; 15 | private _isEnabled = true; 16 | private _promiseQueue = new PromiseQueue(); 17 | 18 | constructor( 19 | track: MediaStreamTrack, 20 | options: VideoMediaTrackConstraints & 21 | DisplayMediaTrackConstraints & 22 | Partial = {}, 23 | ) { 24 | super(track, 'video', options); 25 | if (track.kind !== 'video') { 26 | throw createError({ 27 | operationName: 'LocalVideoStream.constructor', 28 | path: log.prefix, 29 | info: errors.invalidTrackKind, 30 | payload: { track }, 31 | }); 32 | } 33 | log.debug('LocalVideoStream spawned', this.toJSON()); 34 | } 35 | 36 | /**@internal */ 37 | async setEnabled(enabled: boolean) { 38 | await this._promiseQueue.push(async () => { 39 | // mute 40 | if (this._isEnabled === true && enabled === false) { 41 | this._isEnabled = enabled; 42 | 43 | this._disable('video'); 44 | 45 | log.debug('stopped', this.toJSON()); 46 | } 47 | // unmute 48 | else if (this._isEnabled === false && enabled === true) { 49 | this._isEnabled = enabled; 50 | 51 | if (this._options.stopTrackWhenDisabled) { 52 | const track = 53 | this._options.isDisplayMedia === true 54 | ? await this.enableDisplay() 55 | : await this.enableCamera(); 56 | 57 | this._updateTrack(track); 58 | this._onEnableChanged.emit(track); 59 | } else if (this._oldTrack) { 60 | this._updateTrack(this._oldTrack); 61 | this._onEnableChanged.emit(this._oldTrack); 62 | } 63 | 64 | log.debug('resumed', this.toJSON()); 65 | } 66 | }); 67 | } 68 | 69 | private async enableCamera() { 70 | const [track] = ( 71 | await navigator.mediaDevices.getUserMedia({ 72 | video: this.trackConstraints, 73 | }) 74 | ).getVideoTracks(); 75 | 76 | return track; 77 | } 78 | 79 | private async enableDisplay() { 80 | const [track] = ( 81 | await navigator.mediaDevices.getDisplayMedia({ 82 | video: this.trackConstraints, 83 | }) 84 | ).getVideoTracks(); 85 | 86 | return track; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /packages/sfu-bot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@skyway-sdk/sfu-bot", 3 | "version": "2.0.3", 4 | "description": "The official Next Generation JavaScript SDK for SkyWay", 5 | "homepage": "https://skyway.ntt.com/", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/skyway/js-sdk.git" 9 | }, 10 | "license": "MIT", 11 | "author": "NTT DOCOMO BUSINESS, Inc.", 12 | "main": "dist/index.js", 13 | "module": "dist/index.mjs", 14 | "types": "dist/index.d.ts", 15 | "files": [ 16 | "dist", 17 | "LICENSE" 18 | ], 19 | "scripts": { 20 | "build": "zx bundle.mjs", 21 | "compile": "pnpm run compile:tsc && pnpm run compile:esbuild", 22 | "compile:esbuild": "esbuild src/index.ts --bundle --format=esm --target=es6 --outfile=dist/index.mjs", 23 | "compile:tsc": "rm -rf dist && tsc -p tsconfig.build.json", 24 | "doc": "pnpm run doc:html && pnpm run doc:md", 25 | "doc:html": "rm -rf docs/html && typedoc --excludePrivate --disableSources --excludeInternal --tsconfig ./tsconfig.build.json --out docs/html ./src/index.ts ", 26 | "doc:md": "rm -rf docs/md && typedoc --excludePrivate --disableSources --excludeInternal --tsconfig ./tsconfig.build.json --out docs/md --plugin typedoc-plugin-markdown ./src/index.ts ", 27 | "e2e": "pnpm run test", 28 | "e2e:dev": "pnpm run test:dev", 29 | "format": "eslint ./src --fix", 30 | "license": "zx ../../scripts/license.mjs @skyway-sdk/sfu-bot", 31 | "lint": "eslint ./src --fix", 32 | "graph": "dependency-cruiser --include-only '^src' --output-type dot src | dot -T svg > docs/dependencygraph.svg", 33 | "pre:test": "cd ../../ && pnpm run build && cd packages/core", 34 | "publish:npm": "pnpm dlx can-npm-publish --verbose && pnpm run build && npm publish --access public", 35 | "test": "jest && vitest --config vitest.config.ts run ./tests", 36 | "test:dev": "vitest --config vitest.config.ts --browser.headless=false run ./tests", 37 | "type": "npm-run-all --parallel type:main", 38 | "type:main": "tsc --noEmit -p ./tsconfig.json", 39 | "watch": "npm-run-all --parallel watch:tsc watch:esbuild", 40 | "watch:esbuild": "esbuild src/index.ts --bundle --watch --format=esm --target=es6 --outfile=dist/index.mjs", 41 | "watch:tsc": "tsc -p tsconfig.build.json -w" 42 | }, 43 | "dependencies": { 44 | "@skyway-sdk/common": "workspace:*", 45 | "@skyway-sdk/core": "workspace:*", 46 | "mediasoup-client": "^3.18.3", 47 | "@skyway-sdk/sfu-api-client": "workspace:*", 48 | "lodash": "4.17.21" 49 | }, 50 | "keywords": [ 51 | "webrtc", 52 | "skyway", 53 | "conferencing", 54 | "sfu" 55 | ], 56 | "devDependencies": { 57 | "@skyway-sdk/model": "workspace:*", 58 | "@skyway-sdk/token": "workspace:*", 59 | "@types/lodash": "4.17.16" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SkyWay JS-SDK 2 | 3 | このリポジトリは、2023 年 1 月 31 日にリリースされた SkyWay の JavaScript SDK です。旧 SkyWay の JavaScript SDK とは互換性がありません。 4 | 5 | # 本リポジトリの運用方針について 6 | 7 | このリポジトリは公開用のミラーリポジトリであり、こちらで開発は行いません。 8 | 9 | ## Issue / Pull Request 10 | 11 | 受け付けておりません。 12 | 13 | Enterprise プランをご契約のお客様はテクニカルサポートをご利用ください。 14 | 詳しくは[SkyWay サポート](https://support.skyway.ntt.com/hc/ja)をご確認ください。 15 | 16 | # SDK のインストール方法 17 | 18 | ユーザアプリケーションで利用する際は NPM と CDN の2通りのインストール方法があります 19 | 20 | ## NPM を利用する場合 21 | 22 | npm がインストールされている環境下で以下のコマンドを実行します 23 | 24 | **Room ライブラリ** 25 | 26 | ```sh 27 | npm install @skyway-sdk/room 28 | ``` 29 | 30 | **Core ライブラリ** 31 | 32 | ```sh 33 | npm install @skyway-sdk/core 34 | ``` 35 | 36 | **その他のプラグインやユーティリティライブラリ** 37 | 38 | ```sh 39 | npm install @skyway-sdk/sfu-bot 40 | npm install @skyway-sdk/token 41 | ``` 42 | 43 | ## CDN を利用する場合 44 | 45 | 以下のスクリプト要素を HTML に追加します 46 | 47 | **Room ライブラリ** 48 | 49 | ```html 50 | 51 | ``` 52 | 53 | モジュールはグローバル変数の `skyway_room` に格納されるので以下のようにモジュールを取得することができます。 54 | 55 | ```js 56 | const { SkyWayContext, SkyWayStreamFactory, SkyWayRoom } = skyway_room; 57 | ``` 58 | 59 | # ドキュメント 60 | 61 | ## 公式サイト 62 | 63 | [https://skyway.ntt.com/ja/docs/user-guide/javascript-sdk/](https://skyway.ntt.com/ja/docs/user-guide/javascript-sdk/) 64 | 65 | ## API リファレンス 66 | 67 | - [Room ライブラリ](https://javascript-sdk.api-reference.skyway.ntt.com/room) 68 | - [Core ライブラリ](https://javascript-sdk.api-reference.skyway.ntt.com/core) 69 | - [SFU Bot ライブラリ](https://javascript-sdk.api-reference.skyway.ntt.com/sfu-bot) 70 | - [Token ライブラリ](https://javascript-sdk.api-reference.skyway.ntt.com/token) 71 | 72 | # このリポジトリのセットアップ方法(環境構築) 73 | 74 | このリポジトリのサンプルアプリを起動したり、SDK を利用者自身でビルドするために必要な手順。 75 | 76 | ## 初期設定時 77 | 78 | - Node.js をインストールする(バージョンは v20.0.0 以降) 79 | - corepack を有効化するために次のコマンドを実行する 80 | - `corepack enable pnpm` 81 | - ルートディレクトリで次のコマンドを実行する 82 | - `pnpm run first` 83 | - `env.ts.template`を`env.ts`にリネームし、ファイル中の appId と secret にダッシュボードで発行した appId と secret を入力する 84 | - appId と secret の発行方法は[こちら](https://skyway.ntt.com/ja/docs/user-guide/javascript-sdk/quickstart/#199) 85 | 86 | ## 更新時 87 | 88 | git で更新を同期した時や packages ディレクトリ以下のソースコードを編集した際にはルートディレクトリで以下のコマンドを実行する必要がある。 89 | 90 | ```sh 91 | pnpm run compile 92 | ``` 93 | 94 | # サンプルアプリの起動方法 95 | 96 | - examples ディレクトリ以下の任意のサンプルアプリのディレクトリに移動する 97 | - そのディレクトリで以下のコマンドを実行する 98 | 99 | - `npm i` 100 | - `npm run dev` 101 | 102 | - コマンドを実行するとローカルサーバが起動するので Web ブラウザでアクセスする 103 | 104 | # SDK のビルド方法 105 | 106 | - 環境構築のセクションの作業を実施する 107 | - ルートディレクトリで次のコマンドを実行する 108 | - `pnpm run build` 109 | 110 | # License 111 | 112 | - [LICENSE](/LICENSE) 113 | - [THIRD_PARTY_LICENSE](/THIRD_PARTY_LICENSE) 114 | -------------------------------------------------------------------------------- /packages/analytics-client/src/payloadTypes.ts: -------------------------------------------------------------------------------- 1 | export type OpenServerEventPayload = { 2 | statsRequest: { 3 | intervalSec: number; 4 | types: { 5 | type: string; 6 | properties: { 7 | [property: string]: { 8 | normalization: boolean; 9 | outputKey: string; 10 | contentType: ('audio' | 'video' | 'data')[]; 11 | }; 12 | }; 13 | }[]; 14 | }; 15 | }; 16 | 17 | export function isRecord(arg: unknown): arg is Record { 18 | if (typeof arg !== 'object') return false; 19 | if (arg === null) return false; 20 | if (Array.isArray(arg)) return false; 21 | return true; 22 | } 23 | 24 | export function isOpenServerEventPayload( 25 | payload: any, 26 | ): payload is OpenServerEventPayload { 27 | if (!payload || typeof payload !== 'object') return false; 28 | if (!payload.statsRequest || typeof payload.statsRequest !== 'object') 29 | return false; 30 | if ( 31 | !payload.statsRequest.intervalSec || 32 | typeof payload.statsRequest.intervalSec !== 'number' 33 | ) 34 | return false; 35 | if (!payload.statsRequest.types || !Array.isArray(payload.statsRequest.types)) 36 | return false; 37 | for (const statsRequestType of payload.statsRequest.types) { 38 | if (!statsRequestType.type || typeof statsRequestType.type !== 'string') 39 | return false; 40 | if (!statsRequestType.properties || !isRecord(statsRequestType.properties)) 41 | return false; 42 | 43 | for (const key of Object.keys(statsRequestType.properties)) { 44 | if ( 45 | !('normalization' in statsRequestType.properties[key]) || 46 | typeof statsRequestType.properties[key].normalization !== 'boolean' 47 | ) 48 | return false; 49 | if ( 50 | !statsRequestType.properties[key].outputKey || 51 | typeof statsRequestType.properties[key].outputKey !== 'string' 52 | ) 53 | return false; 54 | } 55 | } 56 | 57 | return true; 58 | } 59 | 60 | const AcknowledgeReason = ['invalidPayload', 'unexpected'] as const; 61 | export type AcknowledgeReason = (typeof AcknowledgeReason)[number]; 62 | 63 | export type AcknowledgePayload = { 64 | eventId: string; 65 | ok: boolean; 66 | reason?: AcknowledgeReason; 67 | }; 68 | 69 | export function isAcknowledgePayload( 70 | payload: any, 71 | ): payload is AcknowledgePayload { 72 | if (!payload || typeof payload !== 'object') return false; 73 | if (typeof payload.eventId !== 'string') return false; 74 | if (typeof payload.ok !== 'boolean') return false; 75 | if ( 76 | typeof payload.reason !== 'undefined' && 77 | (typeof payload.reason !== 'string' || 78 | !AcknowledgeReason.includes(payload.reason)) 79 | ) 80 | return false; 81 | return true; 82 | } 83 | 84 | export type ConnectionFailedEventPayload = { 85 | code?: number; 86 | reason?: string; 87 | }; 88 | -------------------------------------------------------------------------------- /packages/room/src/member/local/p2p.ts: -------------------------------------------------------------------------------- 1 | import { type ErrorInfo, Logger } from '@skyway-sdk/common'; 2 | import type { LocalPersonAdapter, LocalStream } from '@skyway-sdk/core'; 3 | 4 | import { errors } from '../../errors'; 5 | import type { RoomPublication } from '../../publication'; 6 | import type { P2PRoomImpl } from '../../room/p2p'; 7 | import type { RoomSubscription } from '../../subscription'; 8 | import { createError } from '../../util'; 9 | import { LocalRoomMemberBase, type RoomPublicationOptions } from './base'; 10 | import type { LocalRoomMember } from './default'; 11 | 12 | const log = new Logger('packages/room/src/member/local/p2p.ts'); 13 | 14 | export interface LocalP2PRoomMember extends LocalRoomMember { 15 | /** 16 | * @description [japanese] StreamをPublishする 17 | */ 18 | publish: ( 19 | stream: T, 20 | options?: RoomPublicationOptions, 21 | ) => Promise>; 22 | } 23 | 24 | /**@internal */ 25 | export class LocalP2PRoomMemberImpl 26 | extends LocalRoomMemberBase 27 | implements LocalP2PRoomMember 28 | { 29 | /**@private */ 30 | // biome-ignore lint/complexity/noUselessConstructor: Private constructor is intentional to control instantiation 31 | constructor(member: LocalPersonAdapter, room: P2PRoomImpl) { 32 | super(member, room); 33 | } 34 | 35 | async publish( 36 | stream: LocalStream, 37 | options: RoomPublicationOptions = {}, 38 | ): Promise> { 39 | if (options.type && options.type !== 'p2p') { 40 | throw createError({ 41 | operationName: 'LocalP2PRoomMemberImpl.publish', 42 | context: this._context, 43 | room: this.room, 44 | info: errors.invalidPublicationTypeForP2PRoom, 45 | path: log.prefix, 46 | }); 47 | } 48 | 49 | const roomPublication = await this._publishAsP2P(stream, options); 50 | return roomPublication as RoomPublication; 51 | } 52 | 53 | async unpublish(target: string | RoomPublication) { 54 | await this._unpublishAsP2P(target).catch((e) => { 55 | const [errorInfo, error] = e as [ErrorInfo, Error]; 56 | throw createError({ 57 | operationName: 'LocalP2PRoomMemberImpl.unpublish', 58 | context: this._context, 59 | room: this.room, 60 | info: errorInfo, 61 | path: log.prefix, 62 | error, 63 | }); 64 | }); 65 | } 66 | 67 | async unsubscribe(target: string | RoomSubscription) { 68 | await super.unsubscribe(target).catch((e) => { 69 | const [errorInfo, error] = e as [ErrorInfo, Error]; 70 | throw createError({ 71 | operationName: 'LocalP2PRoomMemberImpl.unsubscribe', 72 | context: this._context, 73 | room: this.room, 74 | info: errorInfo, 75 | path: log.prefix, 76 | error, 77 | }); 78 | }); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/room/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@skyway-sdk/room", 3 | "version": "2.2.1", 4 | "description": "The official Next Generation JavaScript SDK for SkyWay", 5 | "homepage": "https://skyway.ntt.com/", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/skyway/js-sdk.git" 9 | }, 10 | "license": "MIT", 11 | "author": "NTT DOCOMO BUSINESS, Inc.", 12 | "main": "dist/index.js", 13 | "module": "dist/index.mjs", 14 | "types": "dist/index.d.ts", 15 | "files": [ 16 | "dist", 17 | "LICENSE" 18 | ], 19 | "scripts": { 20 | "build": "zx bundle.mjs", 21 | "compile": "pnpm run compile:tsc && pnpm run compile:esbuild", 22 | "compile:esbuild": "esbuild src/index.ts --bundle --format=esm --target=es6 --outfile=dist/index.mjs", 23 | "compile:tsc": "rm -rf dist && tsc -p tsconfig.build.json", 24 | "doc": "pnpm run doc:html && pnpm run doc:md", 25 | "doc:html": "rm -rf docs/html && typedoc --excludePrivate --disableSources --excludeInternal --tsconfig ./tsconfig.build.json --out docs/html ./src/index.ts ", 26 | "doc:md": "rm -rf docs/md && typedoc --excludePrivate --disableSources --excludeInternal --tsconfig ./tsconfig.build.json --out docs/md --plugin typedoc-plugin-markdown ./src/index.ts ", 27 | "test": "pnpm run e2e", 28 | "e2e": "npm-run-all -p jest test-large test-extra", 29 | "format": "eslint ./src --fix && eslint ./tests --fix", 30 | "graph": "dependency-cruiser --include-only '^src' --output-type dot src | dot -T svg > docs/dependencygraph.svg", 31 | "jest": "jest", 32 | "license": "zx ../../scripts/license.mjs @skyway-sdk/room", 33 | "lint": "eslint ./src --fix && eslint ./tests --fix", 34 | "publish:npm": "pnpm dlx can-npm-publish --verbose && pnpm run build && npm publish --access public", 35 | "test-extra": "vitest --config vitest.extra.ts run ./tests/extra", 36 | "test-extra:dev": "vitest --config vitest.extra.ts --browser.headless=false run ./tests/extra", 37 | "test-large": "vitest --config vitest.large.ts run ./tests/large", 38 | "test-large:dev": "vitest --config vitest.large.ts --browser.headless=false run ./tests/large", 39 | "type": "pnpm run type:main", 40 | "type:main": "tsc --noEmit -p ./tsconfig.json", 41 | "type:prod": "tsc --noEmit -p ./tsconfig.build.json", 42 | "watch": "npm-run-all --parallel watch:tsc watch:esbuild", 43 | "watch:esbuild": "esbuild src/index.ts --bundle --watch --format=esm --target=es6 --outfile=dist/index.mjs", 44 | "watch:tsc": "tsc -p tsconfig.build.json -w" 45 | }, 46 | "dependencies": { 47 | "@skyway-sdk/common": "workspace:*", 48 | "@skyway-sdk/core": "workspace:*", 49 | "@skyway-sdk/sfu-bot": "workspace:*", 50 | "@skyway-sdk/token": "workspace:*", 51 | "uuid": "^9.0.0" 52 | }, 53 | "devDependencies": { 54 | "@skyway-sdk/model": "workspace:*", 55 | "@types/uuid": "^9.0.1" 56 | }, 57 | "keywords": [ 58 | "webrtc", 59 | "skyway", 60 | "conferencing" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "skyway-sdk", 3 | "private": true, 4 | "scripts": { 5 | "build": "lerna run build", 6 | "build:example": "pnpm run build && lerna run build:example", 7 | "clean": "pnpm run clean:example && lerna clean -y && rm -rf ./packages/*/dist && rm -rf ./packages/*/node_modules && rm -rf ./packages/*/bundler && rm -rf ./.parcel-cache && rm -rf node_modules", 8 | "clean:example": "rm -rf ./examples/*/dist && rm -rf ./examples/*/.parcel-cache && rm -rf ./examples/*/node_modules", 9 | "type": "lerna run type", 10 | "compile": "lerna run compile && lerna run type", 11 | "doc": "pnpm run compile && pnpm run doc:update", 12 | "doc:update": "lerna run doc && zx ./scripts/update-api-reference.mjs && zx ./scripts/insert-gtm-script.mjs", 13 | "format": "lerna run format", 14 | "lint": "biome check . --error-on-warnings", 15 | "lint:fix": "biome check . --write --error-on-warnings", 16 | "graph": "lerna run graph", 17 | "playground": "cd examples/playground && pnpm run dev", 18 | "playground-room": "cd examples/playground-room && pnpm run dev", 19 | "test": "pnpm run compile && zx ./scripts/test.mjs", 20 | "test:no-compile": "zx ./scripts/test.mjs", 21 | "test:sfu": "pnpm run compile && zx ./scripts/test-sfu.mjs", 22 | "test:sfu-no-compile": "zx ./scripts/test-sfu.mjs", 23 | "test:p2p": "pnpm run compile && zx ./scripts/test-p2p.mjs", 24 | "test:p2p-no-compile": "zx ./scripts/test-p2p.mjs", 25 | "watch": "lerna run watch", 26 | "upgrade-interactive": "pnpm dlx npm-check --update", 27 | "first": "pnpm i && pnpm run compile", 28 | "update-mirror": "zx scripts/update-mirror-repo.mjs", 29 | "changeset": "changeset", 30 | "changeset:version": "changeset version && pnpm i", 31 | "changeset:version:ci": "pnpm run changeset:version && pnpm run doc && pnpm run build", 32 | "changeset:publish": "pnpm run build && changeset publish" 33 | }, 34 | "devDependencies": { 35 | "@biomejs/biome": "^2.1.3", 36 | "@changesets/cli": "^2.27.9", 37 | "@types/jasmine": "^4.3.0", 38 | "@types/jasmine-ajax": "^3.3.5", 39 | "@types/jest": "^29.5.13", 40 | "@vitest/browser": "^3.0.5", 41 | "buffer": "^5.5.0||^6.0.0", 42 | "esbuild": "^0.25.0", 43 | "jasmine-ajax": "^4.0.0", 44 | "jasmine-core": "^4.4.0", 45 | "jest": "^29.7.0", 46 | "jest-environment-jsdom": "^29.7.0", 47 | "lerna": "^8.2.2", 48 | "npm-run-all": "^4.1.5", 49 | "organize-imports-cli": "^0.10.0", 50 | "playwright": "^1.50.1", 51 | "ts-jest": "^29.2.5", 52 | "typedoc": "^0.25.0", 53 | "typedoc-plugin-markdown": "3.17.1", 54 | "typedoc-plugin-zod": "^1.3.1", 55 | "typescript": "4.7.4", 56 | "vitest": "^3.0.5", 57 | "zx": "^8.4.0" 58 | }, 59 | "engines": { 60 | "node": ">=20.0.0", 61 | "npm": ">=10.8.1" 62 | }, 63 | "packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b" 64 | } 65 | -------------------------------------------------------------------------------- /packages/sfu-bot/src/forwarding.ts: -------------------------------------------------------------------------------- 1 | import { Event, Logger } from '@skyway-sdk/common'; 2 | import { 3 | createError, 4 | type Publication, 5 | type SkyWayContext, 6 | type Subscription, 7 | } from '@skyway-sdk/core'; 8 | import type { SFURestApiClient } from '@skyway-sdk/sfu-api-client'; 9 | 10 | import { errors } from './errors'; 11 | 12 | const log = new Logger('packages/sfu-bot/src/connection/sender.ts'); 13 | 14 | export class Forwarding { 15 | state: ForwardingState = 'started'; 16 | configure: ForwardingConfigure = this.props.configure; 17 | originPublication: Publication = this.props.originPublication; 18 | relayingPublication: Publication = this.props.relayingPublication; 19 | 20 | private _identifierKey: string = this.props.identifierKey; 21 | private _api: SFURestApiClient = this.props.api; 22 | private _context: SkyWayContext = this.props.context; 23 | 24 | /** @description [japanese] forwardingが終了された時に発火するイベント */ 25 | readonly onStopped = new Event(); 26 | 27 | /**@internal */ 28 | constructor( 29 | private props: { 30 | configure: ForwardingConfigure; 31 | originPublication: Publication; 32 | relayingPublication: Publication; 33 | api: SFURestApiClient; 34 | context: SkyWayContext; 35 | identifierKey: string; 36 | }, 37 | ) { 38 | this.relayingPublication.onSubscribed.add(async (e) => { 39 | await this.confirmSubscription(e.subscription).catch((e) => e); 40 | }); 41 | this.relayingPublication.subscriptions.forEach(async (subscription) => { 42 | await this.confirmSubscription(subscription).catch((e) => e); 43 | }); 44 | } 45 | 46 | get id() { 47 | return this.relayingPublication.id; 48 | } 49 | 50 | /**@private */ 51 | _stop() { 52 | this.state = 'stopped'; 53 | this.onStopped.emit(); 54 | } 55 | 56 | /**@internal */ 57 | toJSON() { 58 | return { 59 | id: this.id, 60 | configure: this.configure, 61 | originPublication: this.originPublication, 62 | relayingPublication: this.relayingPublication, 63 | }; 64 | } 65 | 66 | /** @private */ 67 | async confirmSubscription(subscription: Subscription) { 68 | log.debug('[start] Forwarding confirmSubscription'); 69 | const { message } = await this._api 70 | .confirmSubscription({ 71 | forwardingId: this.id, 72 | subscriptionId: subscription.id, 73 | identifierKey: this._identifierKey, 74 | }) 75 | .catch((error) => { 76 | log.error('Forwarding confirmSubscription failed:', error); 77 | throw createError({ 78 | operationName: 'Forwarding.confirmSubscription', 79 | context: this._context, 80 | info: errors.confirmSubscriptionFailed, 81 | path: log.prefix, 82 | payload: error, 83 | }); 84 | }); 85 | log.debug('[end] Forwarding confirmSubscription', { message }); 86 | } 87 | } 88 | 89 | export type ForwardingState = 'started' | 'stopped'; 90 | 91 | export interface ForwardingConfigure { 92 | maxSubscribers: number; 93 | } 94 | -------------------------------------------------------------------------------- /packages/room/src/member/index.ts: -------------------------------------------------------------------------------- 1 | import { Event } from '@skyway-sdk/common'; 2 | import type { Member, MemberSide, MemberState } from '@skyway-sdk/core'; 3 | 4 | import type { RoomPublication } from '../publication'; 5 | import type { RoomType } from '../room'; 6 | import type { Room } from '../room/default'; 7 | import type { RoomSubscription } from '../subscription'; 8 | 9 | export interface RoomMember { 10 | readonly id: string; 11 | readonly name?: string; 12 | readonly roomId: string; 13 | readonly roomName?: string; 14 | readonly roomType: RoomType; 15 | readonly metadata?: string; 16 | readonly side: MemberSide; 17 | state: RoomMemberState; 18 | /**@description [japanese] Memberが Publish した Publication のリスト */ 19 | readonly publications: RoomPublication[]; 20 | /**@description [japanese] Memberが Subscribe している Subscription のリスト */ 21 | readonly subscriptions: RoomSubscription[]; 22 | /**@description [japanese] MemberがRoomから出たときに発火するイベント*/ 23 | readonly onLeft: Event; 24 | /**@description [japanese] Memberのメタデータが更新された時に発火するイベント*/ 25 | readonly onMetadataUpdated: Event; 26 | /**@description [japanese] Memberのメタデータを更新する */ 27 | updateMetadata: (metadata: string) => Promise; 28 | /** 29 | * @description [japanese] Channelから退室する 30 | */ 31 | leave: () => Promise; 32 | } 33 | 34 | /**@internal */ 35 | export abstract class RoomMemberImpl implements RoomMember { 36 | readonly onLeft = new Event(); 37 | readonly onMetadataUpdated: Event; 38 | abstract readonly side: MemberSide; 39 | 40 | get id() { 41 | return this.member.id; 42 | } 43 | get name() { 44 | return this.member.name; 45 | } 46 | get roomId() { 47 | return this.room.id; 48 | } 49 | get roomName() { 50 | return this.room.name; 51 | } 52 | get roomType() { 53 | return this.room.type; 54 | } 55 | 56 | get state() { 57 | return this.member.state; 58 | } 59 | 60 | get metadata() { 61 | return this.member.metadata; 62 | } 63 | 64 | constructor( 65 | protected member: Member, 66 | public room: Room, 67 | ) { 68 | const { removeListener } = room.onMemberLeft.add((e) => { 69 | if (e.member.id === this.member.id) { 70 | removeListener(); 71 | this.onLeft.emit(); 72 | } 73 | }); 74 | this.onMetadataUpdated = member.onMetadataUpdated; 75 | } 76 | 77 | /**@private */ 78 | get _member() { 79 | return this.member; 80 | } 81 | 82 | get publications() { 83 | return this.room.publications.filter((p) => p.publisher.id === this.id); 84 | } 85 | 86 | get subscriptions() { 87 | return this.member.subscriptions.map((s) => 88 | this.room._getSubscription(s.id), 89 | ); 90 | } 91 | 92 | updateMetadata(metadata: string) { 93 | return this.member.updateMetadata(metadata); 94 | } 95 | 96 | leave() { 97 | return this.member.leave(); 98 | } 99 | 100 | /**@internal */ 101 | toJSON() { 102 | return { id: this.id, name: this.name, metadata: this.metadata }; 103 | } 104 | } 105 | 106 | export type RoomMemberState = MemberState; 107 | -------------------------------------------------------------------------------- /packages/rtc-rpc-api-client/src/event.ts: -------------------------------------------------------------------------------- 1 | import type { Member, Publication, Subscription } from '@skyway-sdk/model'; 2 | 3 | export type ChannelEvent = 4 | | ChannelCreatedEvent 5 | | ChannelDeletedEvent 6 | | ChannelMetadataUpdatedEvent 7 | | MemberAddedEvent 8 | | MemberRemovedEvent 9 | | MemberMetadataUpdatedEvent 10 | | StreamPublishedEvent 11 | | StreamUnpublishedEvent 12 | | PublicationMetadataUpdatedEvent 13 | | PublicationDisabledEvent 14 | | PublicationEnabledEvent 15 | | StreamSubscribedEvent 16 | | StreamUnsubscribedEvent; 17 | 18 | export interface ChannelSummary { 19 | id: string; 20 | version: number; 21 | metadata: string; 22 | } 23 | 24 | export type PublicationSummary = Omit; 25 | 26 | export type SubscriptionSummary = Omit< 27 | Subscription, 28 | 'channelId' | 'publisherId' | 'contentType' 29 | >; 30 | 31 | interface EventBase { 32 | type: string; 33 | data: { [key: string]: any; channel: ChannelSummary }; 34 | appId: string; 35 | } 36 | 37 | export interface ChannelCreatedEvent extends EventBase { 38 | type: 'ChannelCreated'; 39 | } 40 | export interface ChannelDeletedEvent extends EventBase { 41 | type: 'ChannelDeleted'; 42 | } 43 | export interface ChannelMetadataUpdatedEvent extends EventBase { 44 | type: 'ChannelMetadataUpdated'; 45 | data: { channel: ChannelSummary }; 46 | } 47 | 48 | export interface MemberAddedEvent extends EventBase { 49 | type: 'MemberAdded'; 50 | data: { member: Member; channel: ChannelSummary }; 51 | } 52 | export interface MemberRemovedEvent extends EventBase { 53 | type: 'MemberRemoved'; 54 | data: { member: Member; channel: ChannelSummary }; 55 | } 56 | export interface MemberMetadataUpdatedEvent extends EventBase { 57 | type: 'MemberMetadataUpdated'; 58 | data: { member: Member; metadata: string; channel: ChannelSummary }; 59 | } 60 | 61 | export interface StreamPublishedEvent extends EventBase { 62 | type: 'StreamPublished'; 63 | data: { publication: PublicationSummary; channel: ChannelSummary }; 64 | } 65 | export interface StreamUnpublishedEvent extends EventBase { 66 | type: 'StreamUnpublished'; 67 | data: { publication: PublicationSummary; channel: ChannelSummary }; 68 | } 69 | export interface PublicationMetadataUpdatedEvent extends EventBase { 70 | type: 'PublicationMetadataUpdated'; 71 | data: { 72 | publication: PublicationSummary; 73 | channel: ChannelSummary; 74 | }; 75 | } 76 | export interface PublicationEnabledEvent extends EventBase { 77 | type: 'PublicationEnabled'; 78 | data: { 79 | publication: PublicationSummary; 80 | channel: ChannelSummary; 81 | }; 82 | } 83 | export interface PublicationDisabledEvent extends EventBase { 84 | type: 'PublicationDisabled'; 85 | data: { 86 | publication: PublicationSummary; 87 | channel: ChannelSummary; 88 | }; 89 | } 90 | 91 | export interface StreamSubscribedEvent extends EventBase { 92 | type: 'StreamSubscribed'; 93 | data: { subscription: SubscriptionSummary; channel: ChannelSummary }; 94 | } 95 | export interface StreamUnsubscribedEvent extends EventBase { 96 | type: 'StreamUnsubscribed'; 97 | data: { subscription: SubscriptionSummary; channel: ChannelSummary }; 98 | } 99 | -------------------------------------------------------------------------------- /packages/core/src/media/stream/local/audio.ts: -------------------------------------------------------------------------------- 1 | import { Logger, PromiseQueue } from '@skyway-sdk/common'; 2 | 3 | import { errors } from '../../../errors'; 4 | import { createError } from '../../../util'; 5 | import type { AudioMediaTrackConstraints } from '../../factory'; 6 | import { AudioLevel } from '../audioLevel'; 7 | import { 8 | LocalMediaStreamBase, 9 | type LocalMediaStreamInterface, 10 | type LocalMediaStreamOptions, 11 | } from './media'; 12 | 13 | const log = new Logger('packages/core/src/media/stream/local/audio.ts'); 14 | 15 | export interface LocalAudioStreamInterface extends LocalMediaStreamInterface { 16 | readonly contentType: 'audio'; 17 | } 18 | 19 | export class LocalAudioStream 20 | extends LocalMediaStreamBase 21 | implements LocalAudioStreamInterface 22 | { 23 | readonly contentType = 'audio'; 24 | private _isEnabled = true; 25 | private _promiseQueue = new PromiseQueue(); 26 | private _audioLevel: AudioLevel | undefined; 27 | 28 | constructor( 29 | track: MediaStreamTrack, 30 | options: AudioMediaTrackConstraints & Partial = {}, 31 | ) { 32 | super(track, 'audio', options); 33 | 34 | if (track.kind !== 'audio') { 35 | throw createError({ 36 | operationName: 'LocalAudioStream.constructor', 37 | path: log.prefix, 38 | info: errors.invalidTrackKind, 39 | payload: { track }, 40 | }); 41 | } 42 | } 43 | 44 | /**@internal */ 45 | async setEnabled(enabled: boolean) { 46 | await this._promiseQueue.push(async () => { 47 | // mute 48 | if (this._isEnabled === true && enabled === false) { 49 | this._isEnabled = enabled; 50 | 51 | this._disable('audio'); 52 | 53 | log.debug('stopped'); 54 | } 55 | // unmute 56 | else if (this._isEnabled === false && enabled === true) { 57 | this._isEnabled = enabled; 58 | 59 | if (this._options.stopTrackWhenDisabled) { 60 | const track = 61 | this._options.isDisplayMedia === true 62 | ? await this.enableDisplay() 63 | : await this.enableMic(); 64 | 65 | this._updateTrack(track); 66 | this._onEnableChanged.emit(track); 67 | } else if (this._oldTrack) { 68 | this._updateTrack(this._oldTrack); 69 | this._onEnableChanged.emit(this._oldTrack); 70 | } 71 | 72 | log.debug('resumed'); 73 | } 74 | }); 75 | } 76 | 77 | private async enableMic() { 78 | const [track] = ( 79 | await navigator.mediaDevices.getUserMedia({ 80 | audio: this.trackConstraints, 81 | }) 82 | ).getAudioTracks(); 83 | 84 | return track; 85 | } 86 | 87 | private async enableDisplay() { 88 | const [track] = ( 89 | await navigator.mediaDevices.getDisplayMedia({ 90 | audio: this.trackConstraints, 91 | }) 92 | ).getAudioTracks(); 93 | 94 | return track; 95 | } 96 | 97 | /**@description [japanese] 直近100msにおける最大音量を取得する(値の範囲:0-1) */ 98 | getAudioLevel() { 99 | // 不要なリソース生成を行わないように初回実行時にAudioLevelインスタンスを生成する 100 | if (this._audioLevel === undefined) { 101 | this._audioLevel = new AudioLevel(this.track); 102 | } 103 | return this._isEnabled ? this._audioLevel.calculate() : 0; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /packages/room/src/member/local/sfu.ts: -------------------------------------------------------------------------------- 1 | import { type ErrorInfo, Logger } from '@skyway-sdk/common'; 2 | import type { LocalPersonAdapter, LocalStream } from '@skyway-sdk/core'; 3 | 4 | import { errors } from '../../errors'; 5 | import type { RoomPublication } from '../../publication'; 6 | import type { SFURoomImpl } from '../../room/sfu'; 7 | import type { RoomSubscription } from '../../subscription'; 8 | import { createError } from '../../util'; 9 | import { LocalRoomMemberBase, type RoomPublicationOptions } from './base'; 10 | import type { LocalRoomMember } from './default'; 11 | 12 | const log = new Logger('packages/room/src/member/local/sfu.ts'); 13 | 14 | export interface LocalSFURoomMember extends LocalRoomMember { 15 | /** 16 | * @description [japanese] RoomにStreamをPublishする 17 | */ 18 | publish: ( 19 | stream: T, 20 | options?: RoomPublicationOptions, 21 | ) => Promise>; 22 | } 23 | 24 | /**@internal */ 25 | export class LocalSFURoomMemberImpl 26 | extends LocalRoomMemberBase 27 | implements LocalSFURoomMember 28 | { 29 | /**@private */ 30 | // biome-ignore lint/complexity/noUselessConstructor: Private constructor is intentional to control instantiation 31 | constructor(member: LocalPersonAdapter, room: SFURoomImpl) { 32 | super(member, room); 33 | } 34 | 35 | async publish( 36 | stream: LocalStream, 37 | options: RoomPublicationOptions = {}, 38 | ): Promise> { 39 | if (options.type && options.type !== 'sfu') { 40 | throw createError({ 41 | operationName: 'LocalSFURoomMemberImpl.publish', 42 | context: this._context, 43 | room: this.room, 44 | info: errors.invalidPublicationTypeForSFURoom, 45 | path: log.prefix, 46 | }); 47 | } 48 | 49 | const roomPublication = await this._publishAsSFU(stream, options).catch( 50 | (errorInfo) => { 51 | throw createError({ 52 | operationName: 'LocalSFURoomMemberImpl.publish', 53 | context: this._context, 54 | room: this.room, 55 | info: errorInfo, 56 | path: log.prefix, 57 | }); 58 | }, 59 | ); 60 | 61 | return roomPublication as RoomPublication; 62 | } 63 | 64 | /** 65 | * @description [japanese] Room上のStreamをUnPublishする 66 | */ 67 | async unpublish(target: string | RoomPublication) { 68 | await this._unpublishAsSFU(target).catch((e) => { 69 | const [errorInfo, error] = e as [ErrorInfo, Error?]; 70 | throw createError({ 71 | operationName: 'LocalSFURoomMemberImpl.unpublish', 72 | context: this._context, 73 | room: this.room, 74 | info: errorInfo, 75 | path: log.prefix, 76 | error, 77 | }); 78 | }); 79 | } 80 | 81 | /** 82 | * @description [japanese] MemberがSubscribeしているStreamのSubscriptionをUnSubscribeする 83 | */ 84 | async unsubscribe(target: string | RoomSubscription) { 85 | await super.unsubscribe(target).catch((e) => { 86 | const [errorInfo, error] = e as [ErrorInfo, Error]; 87 | throw createError({ 88 | operationName: 'LocalSFURoomMemberImpl.unsubscribe', 89 | context: this._context, 90 | room: this.room, 91 | info: errorInfo, 92 | path: log.prefix, 93 | error, 94 | }); 95 | }); 96 | } 97 | } 98 | --------------------------------------------------------------------------------