& {
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 | join
6 |
7 |
8 |
9 | write dataStream:
10 | write
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/examples/p2p-room/src/index.html:
--------------------------------------------------------------------------------
1 |
2 | ID:
3 |
4 | channel name:
5 | join
6 |
7 |
8 |
9 | write dataStream:
10 | write
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 | join
13 | leave
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 | Join
17 | Leave
18 |
19 |
20 |
21 |
22 |
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 | Join
22 | Leave
23 |
24 |
25 |
26 |
27 |
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 |
--------------------------------------------------------------------------------