├── pnpm-workspace.yaml ├── .vscode ├── extensions.json └── settings.json ├── .env.template ├── .gitignore ├── devtools ├── src │ ├── main.tsx │ ├── vite-env.d.ts │ ├── components │ │ ├── DatasetConnectionState.tsx │ │ ├── DebugToggle.tsx │ │ ├── AudioToggle.tsx │ │ ├── VideoToggle.tsx │ │ ├── ClientId.tsx │ │ ├── StandaloneToggle.tsx │ │ ├── AyameWebSdkVersion.tsx │ │ ├── SignalingUrl.tsx │ │ ├── CameraPermissionState.tsx │ │ ├── RoomId.tsx │ │ ├── CopyUrlButton.tsx │ │ ├── SignalingKey.tsx │ │ ├── MicrophonePermissionState.tsx │ │ ├── VideoResolution.tsx │ │ ├── RemoteVideo.tsx │ │ ├── LocalVideo.tsx │ │ ├── TransceiverDirection.tsx │ │ ├── ConnectionSettings.tsx │ │ ├── DisconnectButton.tsx │ │ ├── AudioCodecMimeType.tsx │ │ ├── VideoCodecMimeType.tsx │ │ ├── VideoInputDevice.tsx │ │ ├── AudioInputDevice.tsx │ │ ├── AudioOutputDevice.tsx │ │ ├── MediaSettings.tsx │ │ ├── RequestMediaPermissionButton.tsx │ │ └── ConnectButton.tsx │ ├── store │ │ ├── useStore.ts │ │ ├── createDeviceSlice.ts │ │ ├── createPermissionSlice.ts │ │ ├── createAyameSlice.ts │ │ ├── signals.ts │ │ └── createSettingsSlice.ts │ └── App.tsx ├── index.html ├── package.json ├── vite.config.mjs └── tsconfig.json ├── typedoc.json ├── tests ├── version.test.ts ├── devtools.test.ts └── codec.test.ts ├── CLAUDE.md ├── .github ├── renovate.json └── workflows │ ├── ci.yml │ ├── e2e-test.yml │ └── deploy.yml ├── tsconfig.json ├── src ├── types.d.ts ├── utils.ts └── ayame.ts ├── vite.config.mjs ├── playwright.config.mts ├── biome.jsonc ├── package.json ├── README.md ├── CHANGES.md ├── canary.py ├── LICENSE └── pnpm-lock.yaml /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "devtools" 3 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["biomejs.biome"] 3 | } 4 | -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | VITE_AYAME_SIGNALING_URL=wss://ayame-labo.shiguredo.app/signaling 2 | VITE_AYAME_ROOM_ID_PREFIX=github-login-name@ 3 | VITE_AYAME_ROOM_NAME=ayame-devtools 4 | VITE_AYAME_SIGNALING_KEY=signaling-key 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | typedoc/ 3 | apidoc/ 4 | .log/ 5 | dist/ 6 | .DS_Store 7 | 8 | # .env 9 | .env* 10 | !.env.template 11 | 12 | # playwright 13 | /test-results/ 14 | /playwright-report/ 15 | /blob-report/ 16 | /playwright/.cache/ 17 | -------------------------------------------------------------------------------- /devtools/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import App from './App' 4 | 5 | const root = createRoot(document.getElementById('root') as HTMLDivElement) 6 | root.render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": [ 3 | "./src/ayame.ts", 4 | "./src/types.d.ts" 5 | ], 6 | "tsconfig": "./tsconfig.json", 7 | "disableSources": true, 8 | "excludePrivate": true, 9 | "excludeProtected": true, 10 | "readme": "./README.md", 11 | "out": "typedoc" 12 | } -------------------------------------------------------------------------------- /devtools/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | VITE_AYAME_SIGNALING_URL: string 5 | VITE_AYAME_ROOM_ID_PREFIX: string 6 | VITE_AYAME_ROOM_NAME: string 7 | VITE_AYAME_SIGNALING_KEY: string 8 | } 9 | 10 | interface ImportMeta { 11 | readonly env: ImportMetaEnv 12 | } 13 | -------------------------------------------------------------------------------- /devtools/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ayame DevTools 6 | 7 | 8 | 9 | 10 |
11 |

Ayame DevTools

12 |
13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /devtools/src/components/DatasetConnectionState.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '../store/useStore' 2 | 3 | const DatasetConnectionState: React.FC = () => { 4 | const ayameConnectionState = useStore((state) => state.ayame.connectionState) 5 | 6 | // playwright の E2E テスト用 7 | return
8 | } 9 | 10 | export default DatasetConnectionState 11 | -------------------------------------------------------------------------------- /devtools/src/components/DebugToggle.tsx: -------------------------------------------------------------------------------- 1 | import type React from 'react' 2 | import { useStore } from '../store/useStore' 3 | 4 | const DebugToggle: React.FC = () => { 5 | const isEnable = useStore((state) => state.settings.debug) 6 | const toggleDebug = useStore((state) => state.toggleDebug) 7 | 8 | return ( 9 | toggleDebug(e.target.checked)} /> 10 | ) 11 | } 12 | 13 | export default DebugToggle 14 | -------------------------------------------------------------------------------- /devtools/src/components/AudioToggle.tsx: -------------------------------------------------------------------------------- 1 | import type React from 'react' 2 | import { useStore } from '../store/useStore' 3 | 4 | const AudioToggle: React.FC = () => { 5 | const isEnable = useStore((state) => state.settings.audio.isEnable) 6 | const toggleAudio = useStore((state) => state.toggleAudio) 7 | 8 | return ( 9 | toggleAudio(e.target.checked)} /> 10 | ) 11 | } 12 | 13 | export default AudioToggle 14 | -------------------------------------------------------------------------------- /devtools/src/components/VideoToggle.tsx: -------------------------------------------------------------------------------- 1 | import type React from 'react' 2 | import { useStore } from '../store/useStore' 3 | 4 | const VideoToggle: React.FC = () => { 5 | const isEnable = useStore((state) => state.settings.video.isEnable) 6 | const toggleVideo = useStore((state) => state.toggleVideo) 7 | 8 | return ( 9 | toggleVideo(e.target.checked)} /> 10 | ) 11 | } 12 | 13 | export default VideoToggle 14 | -------------------------------------------------------------------------------- /tests/version.test.ts: -------------------------------------------------------------------------------- 1 | import { version } from '@open-ayame/ayame-web-sdk' 2 | import { expect, test } from '@playwright/test' 3 | 4 | test('Ayame Web SDK のバージョンを確認', async ({ browser }) => { 5 | const sendrecv1 = await browser.newPage() 6 | 7 | await sendrecv1.goto('http://localhost:9000/') 8 | 9 | // Ayame Web SDK のバージョンを確認 10 | await expect(sendrecv1.locator('[data-testid="ayame-web-sdk-version"]')).toHaveText(version(), { 11 | timeout: 10000, 12 | }) 13 | 14 | await sendrecv1.close() 15 | }) 16 | -------------------------------------------------------------------------------- /devtools/src/components/ClientId.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '../store/useStore' 2 | 3 | const ClientId = () => { 4 | const clientId = useStore((state) => state.settings.clientId) 5 | const setClientId = useStore((state) => state.setClientId) 6 | 7 | const handleChange = (e: React.ChangeEvent) => { 8 | setClientId(e.target.value) 9 | } 10 | 11 | return 12 | } 13 | 14 | export default ClientId 15 | -------------------------------------------------------------------------------- /devtools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@open-ayame/ayame-devtools", 3 | "scripts": { 4 | "lint": "biome lint .", 5 | "fmt": "biome format --write .", 6 | "check": "tsc --noEmit" 7 | }, 8 | "dependencies": { 9 | "@open-ayame/ayame-web-sdk": "workspace:*", 10 | "react": "19.2.3", 11 | "react-dom": "19.2.3", 12 | "zustand": "5.0.9" 13 | }, 14 | "devDependencies": { 15 | "@types/react": "19.2.7", 16 | "@types/react-dom": "19.2.3", 17 | "@vitejs/plugin-react": "5.1.2" 18 | } 19 | } -------------------------------------------------------------------------------- /devtools/src/components/StandaloneToggle.tsx: -------------------------------------------------------------------------------- 1 | import type React from 'react' 2 | import { useStore } from '../store/useStore' 3 | 4 | const StandaloneToggle: React.FC = () => { 5 | const isEnable = useStore((state) => state.settings.standalone) 6 | const toggleStandalone = useStore((state) => state.toggleStandalone) 7 | 8 | return ( 9 | toggleStandalone(e.target.checked)} 13 | /> 14 | ) 15 | } 16 | 17 | export default StandaloneToggle 18 | -------------------------------------------------------------------------------- /devtools/vite.config.mjs: -------------------------------------------------------------------------------- 1 | import { resolve } from 'node:path' 2 | import react from '@vitejs/plugin-react' 3 | import { defineConfig } from 'vite' 4 | 5 | export default defineConfig({ 6 | plugins: [react()], 7 | base: process.env.NODE_ENV === 'production' ? '/ayame-web-sdk/devtools/' : '/', 8 | root: resolve(__dirname), 9 | dist: resolve(__dirname, 'dist'), 10 | resolve: { 11 | alias: { 12 | '@open-ayame/ayame-web-sdk': resolve(__dirname, '../dist/ayame.mjs'), 13 | }, 14 | }, 15 | envDir: resolve(__dirname, '..'), 16 | }) 17 | -------------------------------------------------------------------------------- /devtools/src/components/AyameWebSdkVersion.tsx: -------------------------------------------------------------------------------- 1 | import { version } from '@open-ayame/ayame-web-sdk' 2 | import { useEffect } from 'react' 3 | import { useStore } from '../store/useStore' 4 | const AyameVersion = () => { 5 | const ayameVersion = useStore((state) => state.ayame.version) 6 | const setAyameVersion = useStore((state) => state.setAyameVersion) 7 | 8 | useEffect(() => { 9 | setAyameVersion(version()) 10 | }, [setAyameVersion]) 11 | 12 | return {version()} 13 | } 14 | 15 | export default AyameVersion 16 | -------------------------------------------------------------------------------- /devtools/src/components/SignalingUrl.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '../store/useStore' 2 | 3 | const SignalingUrl = () => { 4 | const signalingUrl = useStore((state) => state.settings.signalingUrl) 5 | const setSignalingUrl = useStore((state) => state.setSignalingUrl) 6 | 7 | const handleChange = (e: React.ChangeEvent) => { 8 | setSignalingUrl(e.target.value) 9 | } 10 | 11 | return ( 12 | 13 | ) 14 | } 15 | 16 | export default SignalingUrl 17 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | - Premature Optimization is the Root of All Evil 4 | - 一切忖度しないこと 5 | - 常に日本語を利用すること 6 | - 全角と半角の間には半角スペースを入れること 7 | 8 | ## レビューについて 9 | 10 | - レビューはかなり厳しくすること 11 | - レビューの表現は、シンプルにすること 12 | - レビューの表現は、日本語で行うこと 13 | - レビューの表現は、指摘内容を明確にすること 14 | - レビューの表現は、指摘内容を具体的にすること 15 | - レビューの表現は、指摘内容を優先順位をつけること 16 | - レビューの表現は、指摘内容を優先順位をつけて、重要なものから順に記載すること 17 | - ドキュメントは別に書いているので、ドキュメトに付いては考慮しないこと 18 | - 変更点とリリースノートの整合性を確認すること 19 | 20 | ## コミットについて 21 | 22 | - 勝手にコミットしないこと 23 | - コミットメッセージは確認すること 24 | - コミットメッセージは日本語で書くこと 25 | - コミットメッセージは命令形で書くこと 26 | - コミットメッセージは〜するという形で書くこと 27 | -------------------------------------------------------------------------------- /devtools/src/components/CameraPermissionState.tsx: -------------------------------------------------------------------------------- 1 | import type React from 'react' 2 | import { useEffect } from 'react' 3 | import { useStore } from '../store/useStore' 4 | 5 | const CameraPermissionState: React.FC = () => { 6 | const cameraPermissionState = useStore((state) => state.permissionState.cameraState) 7 | const setCameraPermissionState = useStore((state) => state.setCameraPermissionState) 8 | 9 | useEffect(() => { 10 | setCameraPermissionState() 11 | }, [setCameraPermissionState]) 12 | 13 | return <>{cameraPermissionState} 14 | } 15 | 16 | export default CameraPermissionState 17 | -------------------------------------------------------------------------------- /devtools/src/components/RoomId.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '../store/useStore' 2 | 3 | const RoomId = () => { 4 | const roomId = useStore((state) => state.settings.roomId) 5 | const setRoomId = useStore((state) => state.setRoomId) 6 | 7 | const handleChange = (e: React.ChangeEvent) => { 8 | setRoomId(e.target.value) 9 | } 10 | 11 | return ( 12 | 19 | ) 20 | } 21 | 22 | export default RoomId 23 | -------------------------------------------------------------------------------- /devtools/src/components/CopyUrlButton.tsx: -------------------------------------------------------------------------------- 1 | import type React from 'react' 2 | import { useStore } from '../store/useStore' 3 | 4 | const CopyUrlButton: React.FC = () => { 5 | const generateUrlParams = useStore((state) => state.generateUrlParams) 6 | 7 | const handleClick = () => { 8 | const urlParams = generateUrlParams() 9 | window.history.replaceState(null, '', `?${urlParams}`) 10 | navigator.clipboard.writeText(window.location.href) 11 | } 12 | 13 | return ( 14 | 17 | ) 18 | } 19 | 20 | export default CopyUrlButton 21 | -------------------------------------------------------------------------------- /devtools/src/components/SignalingKey.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '../store/useStore' 2 | 3 | const SignalingKey = () => { 4 | const signalingKey = useStore((state) => state.settings.signalingKey) 5 | const setSignalingKey = useStore((state) => state.setSignalingKey) 6 | 7 | const handleChange = (e: React.ChangeEvent) => { 8 | setSignalingKey(e.target.value) 9 | } 10 | 11 | return ( 12 | 18 | ) 19 | } 20 | 21 | export default SignalingKey 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "[html]": { 4 | "editor.defaultFormatter": "vscode.html-language-features" 5 | }, 6 | "[javascript]": { 7 | "editor.defaultFormatter": "biomejs.biome" 8 | }, 9 | "[javascriptreact]": { 10 | "editor.defaultFormatter": "biomejs.biome" 11 | }, 12 | "[typescript]": { 13 | "editor.defaultFormatter": "biomejs.biome" 14 | }, 15 | "[typescriptreact]": { 16 | "editor.defaultFormatter": "biomejs.biome" 17 | }, 18 | "editor.codeActionsOnSave": { 19 | "quickfix.biome": "explicit", 20 | "source.organizeImports.biome": "explicit" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /devtools/src/components/MicrophonePermissionState.tsx: -------------------------------------------------------------------------------- 1 | import type React from 'react' 2 | import { useEffect } from 'react' 3 | import { useStore } from '../store/useStore' 4 | 5 | const MicrophonePermissionState: React.FC = () => { 6 | const microphonePermissionState = useStore((state) => state.permissionState.microphoneState) 7 | const setMicrophonePermissionState = useStore((state) => state.setMicrophonePermissionState) 8 | 9 | useEffect(() => { 10 | setMicrophonePermissionState() 11 | }, [setMicrophonePermissionState]) 12 | 13 | return <>{microphonePermissionState} 14 | } 15 | 16 | export default MicrophonePermissionState 17 | -------------------------------------------------------------------------------- /devtools/src/components/VideoResolution.tsx: -------------------------------------------------------------------------------- 1 | import type React from 'react' 2 | import { useStore } from '../store/useStore' 3 | 4 | const VideoResolution: React.FC = () => { 5 | const videoResolution = useStore((state) => state.settings.video.resolution) 6 | const setVideoResolution = useStore((state) => state.setVideoResolution) 7 | 8 | const handleChange = (event: React.ChangeEvent) => { 9 | setVideoResolution(event.target.value) 10 | } 11 | 12 | return ( 13 | 19 | ) 20 | } 21 | 22 | export default VideoResolution -------------------------------------------------------------------------------- /devtools/src/store/useStore.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand' 2 | import { type AyameSlice, createAyameSlice } from './createAyameSlice' 3 | import { type MediaDeviceSlice, createMediaDeviceSlice } from './createDeviceSlice' 4 | import { type PermissionSlice, createPermissionSlice } from './createPermissionSlice' 5 | import { type SettingsSlice, createSettingsSlice } from './createSettingsSlice' 6 | 7 | export const useStore = create()( 8 | (...a) => ({ 9 | ...createAyameSlice(...a), 10 | ...createPermissionSlice(...a), 11 | ...createMediaDeviceSlice(...a), 12 | ...createSettingsSlice(...a), 13 | }), 14 | ) 15 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ], 6 | "dependencyDashboard": false, 7 | "packageRules": [ 8 | { 9 | "matchUpdateTypes": [ 10 | "minor", 11 | "patch", 12 | "pin", 13 | "digest" 14 | ], 15 | "platformAutomerge": true, 16 | "automerge": true 17 | }, 18 | { 19 | "matchUpdateTypes": [ 20 | "minor", 21 | "patch", 22 | "pin", 23 | "digest" 24 | ], 25 | "matchPackagePatterns": [ 26 | "rollup" 27 | ], 28 | "groupName": "rollup", 29 | "platformAutomerge": true, 30 | "automerge": true 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | - "feature/*" 8 | - "releases/*" 9 | jobs: 10 | ci_job: 11 | runs-on: ubuntu-24.04 12 | strategy: 13 | matrix: 14 | node-version: ["20", "22", "23"] 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - uses: pnpm/action-setup@v4 21 | with: 22 | version: 10 23 | - run: pnpm install 24 | - run: pnpm run lint 25 | env: 26 | CI: true 27 | - run: pnpm run check 28 | env: 29 | CI: true 30 | - run: pnpm run build 31 | env: 32 | CI: true 33 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "es2022", 5 | "strict": true, 6 | "declaration": true, 7 | "strictNullChecks": true, 8 | "importHelpers": true, 9 | "moduleResolution": "Bundler", 10 | "experimentalDecorators": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "resolveJsonModule": true, 14 | "baseUrl": ".", 15 | "newLine": "LF", 16 | "types": ["webrtc", "node"], 17 | "paths": { 18 | "@/*": ["src/*"] 19 | }, 20 | "lib": ["dom", "esnext", "scripthost"], 21 | "declarationDir": "./dist", 22 | "outDir": "./dist", 23 | "rootDir": "src" 24 | }, 25 | "include": ["src/**/*.ts", "src/**/*.d.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /devtools/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "strict": true, 6 | "declaration": true, 7 | "strictNullChecks": true, 8 | "importHelpers": true, 9 | "moduleResolution": "Bundler", 10 | "experimentalDecorators": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "resolveJsonModule": true, 14 | "allowImportingTsExtensions": true, 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | "newLine": "LF", 18 | "paths": { 19 | "@open-ayame/ayame-web-sdk": ["../dist/ayame.d.ts"] 20 | }, 21 | "types": ["webrtc", "node"], 22 | "lib": ["dom", "esnext", "dom.iterable"], 23 | "declarationDir": "." 24 | }, 25 | "include": ["src/**/*"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /devtools/src/components/RemoteVideo.tsx: -------------------------------------------------------------------------------- 1 | import type React from 'react' 2 | import { useEffect, useRef } from 'react' 3 | import { useStore } from '../store/useStore' 4 | 5 | const RemoteVideo: React.FC = () => { 6 | const remoteMediaStream = useStore((state) => state.mediaStream.remote) 7 | const videoRef = useRef(null) 8 | 9 | useEffect(() => { 10 | if (videoRef.current) { 11 | videoRef.current.srcObject = remoteMediaStream 12 | } 13 | }, [remoteMediaStream]) 14 | 15 | return ( 16 | 24 | ) 25 | } 26 | 27 | export default RemoteVideo 28 | -------------------------------------------------------------------------------- /devtools/src/components/LocalVideo.tsx: -------------------------------------------------------------------------------- 1 | import type React from 'react' 2 | import { useEffect, useRef } from 'react' 3 | import { useStore } from '../store/useStore' 4 | 5 | const LocalVideo: React.FC = () => { 6 | const localMediaStream = useStore((state) => state.mediaStream.local) 7 | const videoRef = useRef(null) 8 | 9 | useEffect(() => { 10 | if (videoRef.current) { 11 | videoRef.current.srcObject = localMediaStream 12 | } 13 | }, [localMediaStream]) 14 | 15 | return ( 16 |