diff --git a/examples/package-lock.json b/examples/package-lock.json index 2a783afc..be2cc5bd 100644 --- a/examples/package-lock.json +++ b/examples/package-lock.json @@ -40,7 +40,8 @@ "ws": "^8.17.1", "wtfnode": "^0.9.1", "xmlbuilder2": "^3.0.2", - "yargs": "^17.7.2" + "yargs": "^17.7.2", + "zx": "^8.1.6" }, "engines": { "node": ">=14" @@ -2147,6 +2148,18 @@ "@types/range-parser": "*" } }, + "node_modules/@types/fs-extra": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", + "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, "node_modules/@types/got": { "version": "9.6.12", "resolved": "https://registry.npmjs.org/@types/got/-/got-9.6.12.tgz", @@ -2171,6 +2184,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/keyv": { "version": "3.1.4", "dev": true, @@ -2187,9 +2211,14 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "17.0.34", + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } }, "node_modules/@types/prop-types": { "version": "15.7.5", @@ -6687,6 +6716,13 @@ "node": ">= 0.6" } }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -7014,6 +7050,23 @@ "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==", "dev": true + }, + "node_modules/zx": { + "version": "8.1.6", + "resolved": "https://registry.npmjs.org/zx/-/zx-8.1.6.tgz", + "integrity": "sha512-SYAriWG+i2CFqMOJcF8QayI8wprlMYQsrmP6tFD7rSPnDLcImNSW7n/8crOYvNVrB2EFgz8LAQk23U1+Y7WrKA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "zx": "build/cli.js" + }, + "engines": { + "node": ">= 12.17.0" + }, + "optionalDependencies": { + "@types/fs-extra": ">=11", + "@types/node": ">=20" + } } }, "dependencies": { @@ -8522,6 +8575,17 @@ "@types/range-parser": "*" } }, + "@types/fs-extra": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", + "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", + "dev": true, + "optional": true, + "requires": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, "@types/got": { "version": "9.6.12", "resolved": "https://registry.npmjs.org/@types/got/-/got-9.6.12.tgz", @@ -8543,6 +8607,16 @@ "version": "3.0.0", "dev": true }, + "@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, "@types/keyv": { "version": "3.1.4", "dev": true, @@ -8557,8 +8631,13 @@ "dev": true }, "@types/node": { - "version": "17.0.34", - "dev": true + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "dev": true, + "requires": { + "undici-types": "~6.19.2" + } }, "@types/prop-types": { "version": "15.7.5", @@ -11910,6 +11989,12 @@ "mime-types": "~2.1.24" } }, + "undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -12147,6 +12232,16 @@ "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==", "dev": true + }, + "zx": { + "version": "8.1.6", + "resolved": "https://registry.npmjs.org/zx/-/zx-8.1.6.tgz", + "integrity": "sha512-SYAriWG+i2CFqMOJcF8QayI8wprlMYQsrmP6tFD7rSPnDLcImNSW7n/8crOYvNVrB2EFgz8LAQk23U1+Y7WrKA==", + "dev": true, + "requires": { + "@types/fs-extra": ">=11", + "@types/node": ">=20" + } } } } diff --git a/examples/package.json b/examples/package.json index cabbf250..386917ef 100644 --- a/examples/package.json +++ b/examples/package.json @@ -2,7 +2,10 @@ "name": "example", "version": "0.0.31", "description": "WebRTC Implementation for TypeScript (Node.js)", - "keywords": ["WebRTC", "node.js"], + "keywords": [ + "WebRTC", + "node.js" + ], "homepage": "https://github.com/shinyoshiaki/werift-webrtc", "repository": { "type": "git", @@ -13,7 +16,10 @@ "name": "shinyoshiaki" }, "main": "lib/index.js", - "files": ["lib", "src"], + "files": [ + "lib", + "src" + ], "scripts": { "format": "biome check --write ../examples" }, @@ -49,7 +55,8 @@ "ws": "^8.17.1", "wtfnode": "^0.9.1", "xmlbuilder2": "^3.0.2", - "yargs": "^17.7.2" + "yargs": "^17.7.2", + "zx": "^8.1.6" }, "engines": { "node": ">=14" diff --git a/examples/save_to_disk/rtp.ts b/examples/save_to_disk/rtp.ts new file mode 100644 index 00000000..03260be9 --- /dev/null +++ b/examples/save_to_disk/rtp.ts @@ -0,0 +1,26 @@ +import { randomUUID } from "crypto"; +import { + MediaRecorder, + Navigator, +} from "../../packages/webrtc/src/nonstandard"; +import { randomPort } from "../../packages/webrtc/src"; +import { $ } from "zx"; + +(async () => { + const path = `${__dirname}/tmp${randomUUID()}.webm`; + const recorder = new MediaRecorder({ + numOfTracks: 1, + path, + disableNtp: true, + }); + + const port = await randomPort(); + const navigator = new Navigator(); + const { track } = navigator.mediaDevices.getUdpMedia({ + port, + codec: { clockRate: 48000, mimeType: "audio/opus", payloadType: 96 }, + }); + await recorder.addTrack(track); + + $`gst-launch-1.0 audiotestsrc ! audioconvert ! audioresample ! opusenc ! rtpopuspay ! udpsink host=127.0.0.1 port=${port}`; +})(); diff --git a/packages/webrtc/src/nonstandard/navigator.ts b/packages/webrtc/src/nonstandard/navigator.ts index f6a33159..8442b059 100644 --- a/packages/webrtc/src/nonstandard/navigator.ts +++ b/packages/webrtc/src/nonstandard/navigator.ts @@ -1,6 +1,8 @@ import { randomBytes } from "crypto"; import { jspack } from "@shinyoshiaki/jspack"; import { MediaStream, MediaStreamTrack } from "../media/track"; +import { createSocket } from "dgram"; +import { RTCRtpCodecParameters } from ".."; export class Navigator { mediaDevices: MediaDevices; @@ -58,6 +60,31 @@ export class MediaDevices extends EventTarget { }; readonly getDisplayMedia = this.getUserMedia; + + readonly getUdpMedia = ({ + port, + codec, + }: { + port: number; + codec: ConstructorParameters[0]; + }) => { + const track = new MediaStreamTrack({ + kind: "audio", + codec: new RTCRtpCodecParameters(codec), + }); + + const udp = createSocket("udp4"); + udp.bind(port); + udp.on("message", (data) => { + track.writeRtp(data); + }); + + const disposer = () => { + udp.close(); + }; + + return { track, disposer }; + }; } interface MediaStreamConstraints { diff --git a/packages/webrtc/src/nonstandard/recorder/writer/webm.ts b/packages/webrtc/src/nonstandard/recorder/writer/webm.ts index 8104c484..2779fcc9 100644 --- a/packages/webrtc/src/nonstandard/recorder/writer/webm.ts +++ b/packages/webrtc/src/nonstandard/recorder/writer/webm.ts @@ -77,6 +77,10 @@ export class WebmFactory extends MediaWriter { }); const lipsync = new LipsyncCallback(); + if (inputTracks.length === 1) { + this.props.disableLipSync = true; + } + this.rtpSources = inputTracks.map(({ track, clockRate, codec }) => { const rtpSource = new RtpSourceCallback(); const rtcpSource = new RtcpSourceCallback();