1
0
mirror of https://github.com/spacebarchat/server.git synced 2024-11-22 10:22:39 +01:00

it doesnt work

This commit is contained in:
Puyodead1 2024-09-13 17:33:52 -04:00
parent 224d4020d0
commit 22a7fa3247
No known key found for this signature in database
GPG Key ID: A4FA4FEC0DD353FC
21 changed files with 1389 additions and 262 deletions

389
package-lock.json generated
View File

@ -33,6 +33,7 @@
"json-bigint": "^1.0.0", "json-bigint": "^1.0.0",
"jsonwebtoken": "^9.0.1", "jsonwebtoken": "^9.0.1",
"lambert-server": "^1.2.12", "lambert-server": "^1.2.12",
"mediasoup": "^3.14.14",
"missing-native-js-functions": "^1.4.3", "missing-native-js-functions": "^1.4.3",
"module-alias": "^2.2.3", "module-alias": "^2.2.3",
"morgan": "^1.10.0", "morgan": "^1.10.0",
@ -46,6 +47,8 @@
"probe-image-size": "^7.2.3", "probe-image-size": "^7.2.3",
"proxy-agent": "^6.3.0", "proxy-agent": "^6.3.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"sdp-transform": "^2.14.2",
"semantic-sdp": "^3.30.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tslib": "^2.6.1", "tslib": "^2.6.1",
"typeorm": "^0.3.17", "typeorm": "^0.3.17",
@ -70,6 +73,7 @@
"@types/node-os-utils": "^1.3.1", "@types/node-os-utils": "^1.3.1",
"@types/nodemailer": "^6.4.9", "@types/nodemailer": "^6.4.9",
"@types/probe-image-size": "^7.2.0", "@types/probe-image-size": "^7.2.0",
"@types/sdp-transform": "^2.4.9",
"@types/sharp": "^0.31.1", "@types/sharp": "^0.31.1",
"@types/ws": "^8.5.5", "@types/ws": "^8.5.5",
"@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/eslint-plugin": "^6.21.0",
@ -1306,6 +1310,27 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1" "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
} }
}, },
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
"license": "ISC",
"dependencies": {
"minipass": "^7.0.4"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@isaacs/fs-minipass/node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/@jimp/bmp": { "node_modules/@jimp/bmp": {
"version": "0.22.12", "version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz", "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz",
@ -2745,6 +2770,15 @@
"@types/express": "*" "@types/express": "*"
} }
}, },
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
"license": "MIT",
"dependencies": {
"@types/ms": "*"
}
},
"node_modules/@types/express": { "node_modules/@types/express": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
@ -2784,6 +2818,12 @@
"i18next": ">=17.0.11" "i18next": ">=17.0.11"
} }
}, },
"node_modules/@types/ini": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz",
"integrity": "sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg==",
"license": "MIT"
},
"node_modules/@types/json-bigint": { "node_modules/@types/json-bigint": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/@types/json-bigint/-/json-bigint-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/json-bigint/-/json-bigint-1.0.4.tgz",
@ -2819,6 +2859,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/ms": {
"version": "0.7.34",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==",
"license": "MIT"
},
"node_modules/@types/multer": { "node_modules/@types/multer": {
"version": "1.4.11", "version": "1.4.11",
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.11.tgz", "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.11.tgz",
@ -2906,6 +2952,13 @@
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
"dev": true "dev": true
}, },
"node_modules/@types/sdp-transform": {
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@types/sdp-transform/-/sdp-transform-2.4.9.tgz",
"integrity": "sha512-bVr+/OoZZy7wrHlNcEAAa6PAgKA4BoXPYVN2EijMC5WnGgQ4ZEuixmKnVs2roiAvr7RhIFVH17QD27cojgIZCg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/semver": { "node_modules/@types/semver": {
"version": "7.5.8", "version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
@ -4265,11 +4318,12 @@
"integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==" "integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg=="
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.6", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "^2.1.3"
}, },
"engines": { "engines": {
"node": ">=6.0" "node": ">=6.0"
@ -4609,6 +4663,12 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/equals-ignore-case": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/equals-ignore-case/-/equals-ignore-case-1.0.1.tgz",
"integrity": "sha512-krgK/Px09jhcc7wK5/lxApRv7XmIT/fSgrMwdaW/V1FmPJEIJMNGEMhe0U9tJ/97rPe75MHKPRqi7/8Tqz6NMA==",
"license": "ISC"
},
"node_modules/erlpack": { "node_modules/erlpack": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/erlpack/-/erlpack-0.1.4.tgz", "resolved": "https://registry.npmjs.org/erlpack/-/erlpack-0.1.4.tgz",
@ -5156,6 +5216,29 @@
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/fido2-lib": { "node_modules/fido2-lib": {
"version": "3.5.3", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/fido2-lib/-/fido2-lib-3.5.3.tgz", "resolved": "https://registry.npmjs.org/fido2-lib/-/fido2-lib-3.5.3.tgz",
@ -5280,6 +5363,12 @@
"node": "^10.12.0 || >=12.0.0" "node": "^10.12.0 || >=12.0.0"
} }
}, },
"node_modules/flatbuffers": {
"version": "24.3.25",
"resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-24.3.25.tgz",
"integrity": "sha512-3HDgPbgiwWMI9zVB7VYBHaMrbOO7Gm0v+yD2FV/sCKj+9NDeVL7BOBYUuhWAQGKWOzBo8S9WdMvV0eixO233XQ==",
"license": "Apache-2.0"
},
"node_modules/flatted": { "node_modules/flatted": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
@ -5345,6 +5434,18 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"license": "MIT",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/formidable": { "node_modules/formidable": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz",
@ -5642,6 +5743,23 @@
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true "dev": true
}, },
"node_modules/h264-profile-level-id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/h264-profile-level-id/-/h264-profile-level-id-2.0.0.tgz",
"integrity": "sha512-X4CLryVbVA0CtjTExS4G5U1gb2Z4wa32AF8ukVmFuLdw2JRq2aHisor7SY5SYTUUrUSqq0KdPIO18sql6IWIQw==",
"license": "ISC",
"dependencies": {
"@types/debug": "^4.1.12",
"debug": "^4.3.4"
},
"engines": {
"node": ">=16"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mediasoup"
}
},
"node_modules/has-flag": { "node_modules/has-flag": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@ -6562,6 +6680,200 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/mediasoup": {
"version": "3.14.14",
"resolved": "https://registry.npmjs.org/mediasoup/-/mediasoup-3.14.14.tgz",
"integrity": "sha512-BagHzOgtPUPxpPAHSMdFKICjtDuLNHznuk3YUUDDTFIwyTOYIXeX7NRklzMRAiRRKA9luXmjCjTNwkMQ4sb0aA==",
"hasInstallScript": true,
"license": "ISC",
"dependencies": {
"@types/ini": "^4.1.1",
"debug": "^4.3.7",
"flatbuffers": "^24.3.25",
"h264-profile-level-id": "^2.0.0",
"ini": "^5.0.0",
"node-fetch": "^3.3.2",
"supports-color": "^9.4.0",
"tar": "^7.4.3"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mediasoup"
}
},
"node_modules/mediasoup/node_modules/chownr": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/mediasoup/node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/mediasoup/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/mediasoup/node_modules/ini": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz",
"integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==",
"license": "ISC",
"engines": {
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/mediasoup/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/mediasoup/node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/mediasoup/node_modules/minizlib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz",
"integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==",
"license": "MIT",
"dependencies": {
"minipass": "^7.0.4",
"rimraf": "^5.0.5"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/mediasoup/node_modules/mkdirp": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
"license": "MIT",
"bin": {
"mkdirp": "dist/cjs/src/bin.js"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/mediasoup/node_modules/node-fetch": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/mediasoup/node_modules/rimraf": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
"integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==",
"license": "ISC",
"dependencies": {
"glob": "^10.3.7"
},
"bin": {
"rimraf": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/mediasoup/node_modules/supports-color": {
"version": "9.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz",
"integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/mediasoup/node_modules/tar": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
"license": "ISC",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
"minipass": "^7.1.2",
"minizlib": "^3.0.1",
"mkdirp": "^3.0.1",
"yallist": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/mediasoup/node_modules/yallist": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/merge-descriptors": { "node_modules/merge-descriptors": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
@ -6850,9 +7162,10 @@
} }
}, },
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
}, },
"node_modules/multer": { "node_modules/multer": {
"version": "1.4.5-lts.1", "version": "1.4.5-lts.1",
@ -7026,6 +7339,25 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
}, },
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": { "node_modules/node-fetch": {
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
@ -8222,6 +8554,15 @@
} }
] ]
}, },
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"license": "MIT",
"dependencies": {
"safe-buffer": "^5.1.0"
}
},
"node_modules/range-parser": { "node_modules/range-parser": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@ -8446,6 +8787,26 @@
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="
}, },
"node_modules/sdp-transform": {
"version": "2.14.2",
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.2.tgz",
"integrity": "sha512-icY6jVao7MfKCieyo1AyxFYm1baiM+fA00qW/KrNNVlkxHAd34riEKuEkUe4bBb3gJwLJZM+xT60Yj1QL8rHiA==",
"license": "MIT",
"bin": {
"sdp-verify": "checker.js"
}
},
"node_modules/semantic-sdp": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/semantic-sdp/-/semantic-sdp-3.30.0.tgz",
"integrity": "sha512-HxChWm8UMQG3qakFR/BidJGMtP1xk9KcOLDsSXZU01c9IIU1QnA1RODMYfST3MwnHsby4q7vu//m4qUDy8J+Dg==",
"license": "MIT",
"dependencies": {
"equals-ignore-case": "^1.0.0",
"randombytes": "^2.0.3",
"sdp-transform": "^2"
}
},
"node_modules/semver": { "node_modules/semver": {
"version": "7.6.3", "version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
@ -8493,11 +8854,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
}, },
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/serve-static": { "node_modules/serve-static": {
"version": "1.15.0", "version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
@ -10026,6 +10382,15 @@
"node": ">=6.0" "node": ">=6.0"
} }
}, },
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/webcrypto-core": { "node_modules/webcrypto-core": {
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.0.tgz", "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.0.tgz",

View File

@ -54,6 +54,7 @@
"@types/node-os-utils": "^1.3.1", "@types/node-os-utils": "^1.3.1",
"@types/nodemailer": "^6.4.9", "@types/nodemailer": "^6.4.9",
"@types/probe-image-size": "^7.2.0", "@types/probe-image-size": "^7.2.0",
"@types/sdp-transform": "^2.4.9",
"@types/sharp": "^0.31.1", "@types/sharp": "^0.31.1",
"@types/ws": "^8.5.5", "@types/ws": "^8.5.5",
"@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/eslint-plugin": "^6.21.0",
@ -89,6 +90,7 @@
"json-bigint": "^1.0.0", "json-bigint": "^1.0.0",
"jsonwebtoken": "^9.0.1", "jsonwebtoken": "^9.0.1",
"lambert-server": "^1.2.12", "lambert-server": "^1.2.12",
"mediasoup": "^3.14.14",
"missing-native-js-functions": "^1.4.3", "missing-native-js-functions": "^1.4.3",
"module-alias": "^2.2.3", "module-alias": "^2.2.3",
"morgan": "^1.10.0", "morgan": "^1.10.0",
@ -102,6 +104,8 @@
"probe-image-size": "^7.2.3", "probe-image-size": "^7.2.3",
"proxy-agent": "^6.3.0", "proxy-agent": "^6.3.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"sdp-transform": "^2.14.2",
"semantic-sdp": "^3.30.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tslib": "^2.6.1", "tslib": "^2.6.1",
"typeorm": "^0.3.17", "typeorm": "^0.3.17",
@ -113,7 +117,8 @@
"@spacebar/api": "dist/api", "@spacebar/api": "dist/api",
"@spacebar/cdn": "dist/cdn", "@spacebar/cdn": "dist/cdn",
"@spacebar/gateway": "dist/gateway", "@spacebar/gateway": "dist/gateway",
"@spacebar/util": "dist/util" "@spacebar/util": "dist/util",
"@spacebar/webrtc": "dist/webrtc"
}, },
"optionalDependencies": { "optionalDependencies": {
"erlpack": "^0.1.4", "erlpack": "^0.1.4",

View File

@ -19,13 +19,14 @@
process.on("unhandledRejection", console.error); process.on("unhandledRejection", console.error);
process.on("uncaughtException", console.error); process.on("uncaughtException", console.error);
import http from "http";
import * as Api from "@spacebar/api"; import * as Api from "@spacebar/api";
import * as Gateway from "@spacebar/gateway";
import { CDNServer } from "@spacebar/cdn"; import { CDNServer } from "@spacebar/cdn";
import express from "express"; import * as Gateway from "@spacebar/gateway";
import { green, bold } from "picocolors";
import { Config, initDatabase, Sentry } from "@spacebar/util"; import { Config, initDatabase, Sentry } from "@spacebar/util";
import * as Webrtc from "@spacebar/webrtc";
import express from "express";
import http from "http";
import { bold, green } from "picocolors";
const app = express(); const app = express();
const server = http.createServer(); const server = http.createServer();
@ -36,9 +37,11 @@ server.on("request", app);
const api = new Api.SpacebarServer({ server, port, production, app }); const api = new Api.SpacebarServer({ server, port, production, app });
const cdn = new CDNServer({ server, port, production, app }); const cdn = new CDNServer({ server, port, production, app });
const gateway = new Gateway.Server({ server, port, production }); const gateway = new Gateway.Server({ server, port, production });
const webrtc = new Webrtc.Server({ server: undefined, port: 3004, production });
process.on("SIGTERM", async () => { process.on("SIGTERM", async () => {
console.log("Shutting down due to SIGTERM"); console.log("Shutting down due to SIGTERM");
await webrtc.stop();
await gateway.stop(); await gateway.stop();
await cdn.stop(); await cdn.stop();
await api.stop(); await api.stop();
@ -54,7 +57,12 @@ async function main() {
await new Promise((resolve) => await new Promise((resolve) =>
server.listen({ port }, () => resolve(undefined)), server.listen({ port }, () => resolve(undefined)),
); );
await Promise.all([api.start(), cdn.start(), gateway.start()]); await Promise.all([
api.start(),
cdn.start(),
gateway.start(),
webrtc.start(),
]);
Sentry.errorHandler(app); Sentry.errorHandler(app);

View File

@ -17,15 +17,16 @@
*/ */
// process.env.MONGOMS_DEBUG = "true"; // process.env.MONGOMS_DEBUG = "true";
process.env.DEBUG = "mediasoup*";
require("module-alias/register"); require("module-alias/register");
import "reflect-metadata";
import cluster, { Worker } from "cluster";
import os from "os";
import { red, bold, yellow, cyan } from "picocolors";
import { initStats } from "./stats";
import { config } from "dotenv";
config();
import { execSync } from "child_process"; import { execSync } from "child_process";
import cluster, { Worker } from "cluster";
import { config } from "dotenv";
import os from "os";
import { bold, cyan, red, yellow } from "picocolors";
import "reflect-metadata";
import { initStats } from "./stats";
config();
const cores = process.env.THREADS ? parseInt(process.env.THREADS) : 1; const cores = process.env.THREADS ? parseInt(process.env.THREADS) : 1;

View File

@ -17,19 +17,19 @@
*/ */
import { Payload, WebSocket } from "@spacebar/gateway"; import { Payload, WebSocket } from "@spacebar/gateway";
import { genVoiceToken } from "../util/SessionUtils";
import { check } from "./instanceOf";
import { import {
Config, Config,
emitEvent, emitEvent,
Guild, Guild,
Member, Member,
Region,
VoiceServerUpdateEvent, VoiceServerUpdateEvent,
VoiceState, VoiceState,
VoiceStateUpdateEvent, VoiceStateUpdateEvent,
VoiceStateUpdateSchema, VoiceStateUpdateSchema,
Region,
} from "@spacebar/util"; } from "@spacebar/util";
import { genVoiceToken } from "../util/SessionUtils";
import { check } from "./instanceOf";
// TODO: check if a voice server is setup // TODO: check if a voice server is setup
// Notice: Bot users respect the voice channel's user limit, if set. // Notice: Bot users respect the voice channel's user limit, if set.
@ -136,6 +136,7 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) {
endpoint: guildRegion.endpoint, endpoint: guildRegion.endpoint,
}, },
guild_id: voiceState.guild_id, guild_id: voiceState.guild_id,
user_id: voiceState.user_id,
} as VoiceServerUpdateEvent); } as VoiceServerUpdateEvent);
} }
} }

View File

@ -16,6 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { VoiceOPCodes } from "../../webrtc";
// import { VoiceOPCodes } from "@spacebar/webrtc"; // import { VoiceOPCodes } from "@spacebar/webrtc";
export enum OPCODES { export enum OPCODES {
@ -63,7 +65,7 @@ export enum CLOSECODES {
} }
export interface Payload { export interface Payload {
op: OPCODES /* | VoiceOPCodes */; op: OPCODES | VoiceOPCodes;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
d?: any; d?: any;
s?: number; s?: number;

View File

@ -17,10 +17,10 @@
*/ */
import { Intents, ListenEventOpts, Permissions } from "@spacebar/util"; import { Intents, ListenEventOpts, Permissions } from "@spacebar/util";
import WS from "ws"; import { Client } from "@spacebar/webrtc";
import { Deflate, Inflate } from "fast-zlib"; import { Deflate, Inflate } from "fast-zlib";
import WS from "ws";
import { Capabilities } from "./Capabilities"; import { Capabilities } from "./Capabilities";
// import { Client } from "@spacebar/webrtc";
export interface WebSocket extends WS { export interface WebSocket extends WS {
version: number; version: number;
@ -42,6 +42,6 @@ export interface WebSocket extends WS {
member_events: Record<string, () => unknown>; member_events: Record<string, () => unknown>;
listen_options: ListenEventOpts; listen_options: ListenEventOpts;
capabilities?: Capabilities; capabilities?: Capabilities;
// client?: Client; client?: Client;
large_threshold: number; large_threshold: number;
} }

View File

@ -28,4 +28,5 @@ export interface GuildCreateSchema {
channels?: ChannelModifySchema[]; channels?: ChannelModifySchema[];
system_channel_id?: string; system_channel_id?: string;
rules_channel_id?: string; rules_channel_id?: string;
guild_template_code?: string; // TODO: move this
} }

View File

@ -41,6 +41,7 @@ export const ajv = new Ajv({
strict: true, strict: true,
strictRequired: true, strictRequired: true,
allowUnionTypes: true, allowUnionTypes: true,
removeAdditional: true,
}); });
addFormats(ajv); addFormats(ajv);

View File

@ -0,0 +1,25 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Spacebar and Spacebar Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export interface VoiceReadySchema {
experiments: string[];
ip: string;
modes: string[];
port: number;
ssrc: number; // seems to be a "base", first stream ssrc will be +1 with rtx +2
}

View File

@ -59,6 +59,7 @@ export * from "./RegisterSchema";
export * from "./RelationshipPostSchema"; export * from "./RelationshipPostSchema";
export * from "./RelationshipPutSchema"; export * from "./RelationshipPutSchema";
export * from "./RequestGuildMembersSchema"; export * from "./RequestGuildMembersSchema";
export * from "./responses";
export * from "./RoleModifySchema"; export * from "./RoleModifySchema";
export * from "./RolePositionUpdateSchema"; export * from "./RolePositionUpdateSchema";
export * from "./SelectProtocolSchema"; export * from "./SelectProtocolSchema";
@ -77,10 +78,10 @@ export * from "./UserSettingsSchema";
export * from "./Validator"; export * from "./Validator";
export * from "./VanityUrlSchema"; export * from "./VanityUrlSchema";
export * from "./VoiceIdentifySchema"; export * from "./VoiceIdentifySchema";
export * from "./VoiceReadySchema";
export * from "./VoiceStateUpdateSchema"; export * from "./VoiceStateUpdateSchema";
export * from "./VoiceVideoSchema"; export * from "./VoiceVideoSchema";
export * from "./WebAuthnSchema"; export * from "./WebAuthnSchema";
export * from "./WebhookCreateSchema"; export * from "./WebhookCreateSchema";
export * from "./WebhookExecuteSchema"; export * from "./WebhookExecuteSchema";
export * from "./WidgetModifySchema"; export * from "./WidgetModifySchema";
export * from "./responses";

View File

@ -21,6 +21,7 @@ import dotenv from "dotenv";
import http from "http"; import http from "http";
import ws from "ws"; import ws from "ws";
import { Connection } from "./events/Connection"; import { Connection } from "./events/Connection";
import { createWorkers } from "./util";
dotenv.config(); dotenv.config();
export class Server { export class Server {
@ -69,6 +70,8 @@ export class Server {
await initDatabase(); await initDatabase();
await Config.init(); await Config.init();
await initEvent(); await initEvent();
await createWorkers();
if (!this.server.listening) { if (!this.server.listening) {
this.server.listen(this.port); this.server.listen(this.port);
console.log(`[WebRTC] online on 0.0.0.0:${this.port}`); console.log(`[WebRTC] online on 0.0.0.0:${this.port}`);

View File

@ -18,10 +18,34 @@
import { WebSocket } from "@spacebar/gateway"; import { WebSocket } from "@spacebar/gateway";
import { Session } from "@spacebar/util"; import { Session } from "@spacebar/util";
import { getClients } from "../util";
export async function onClose(this: WebSocket, code: number, reason: string) { export async function onClose(this: WebSocket, code: number, reason: string) {
console.log("[WebRTC] closed", code, reason.toString()); console.log("[WebRTC] closed", code, reason.toString());
if (this.session_id) await Session.delete({ session_id: this.session_id }); if (this.session_id) await Session.delete({ session_id: this.session_id });
// we need to find all consumers on all clients that have a producer in our client
const clients = getClients(this.client?.channel_id!);
for (const client of clients) {
if (client.websocket.user_id === this.user_id) continue;
// if any consumer on this client has a producer id that is in our client, close it
client.consumers.forEach((consumer) => {
// check if any producers in our client have the same producer id
this.client?.producers.forEach((producer) => {
if (producer.id === consumer.producerId) {
console.log("[WebRTC] closing consumer", consumer.id);
consumer.close();
}
});
});
}
this.client?.producers.forEach((producer) => producer.close());
this.client?.consumers.forEach((consumer) => consumer.close());
this.client?.transports.producer.close();
this.removeAllListeners(); this.removeAllListeners();
} }

View File

@ -17,11 +17,12 @@
*/ */
import { Payload, Send, WebSocket } from "@spacebar/gateway"; import { Payload, Send, WebSocket } from "@spacebar/gateway";
import * as mediasoup from "mediasoup";
import { VoiceOPCodes } from "../util"; import { VoiceOPCodes } from "../util";
export async function onBackendVersion(this: WebSocket, data: Payload) { export async function onBackendVersion(this: WebSocket, data: Payload) {
await Send(this, { await Send(this, {
op: VoiceOPCodes.VOICE_BACKEND_VERSION, op: VoiceOPCodes.VOICE_BACKEND_VERSION,
d: { voice: "0.8.43", rtc_worker: "0.3.26" }, d: { voice: "0.1.0", rtc_worker: mediasoup.version },
}); });
} }

View File

@ -20,13 +20,30 @@ import { CLOSECODES, Payload, Send, WebSocket } from "@spacebar/gateway";
import { import {
validateSchema, validateSchema,
VoiceIdentifySchema, VoiceIdentifySchema,
VoiceReadySchema,
VoiceState, VoiceState,
} from "@spacebar/util"; } from "@spacebar/util";
import { endpoint, getClients, VoiceOPCodes, PublicIP } from "@spacebar/webrtc"; import {
import SemanticSDP from "semantic-sdp"; getClients,
const defaultSDP = require("./sdp.json"); getOrCreateRouter,
getWorker,
Stream,
VoiceOPCodes,
} from "@spacebar/webrtc";
export async function onIdentify(this: WebSocket, data: Payload) { export interface IdentifyPayload extends Payload {
d: {
server_id: string; //guild id
session_id: string; //gateway session
streams: Stream[];
token: string; //voice_states token
user_id: string;
video: boolean;
max_dave_protocol_version?: number; // present in v8, not sure what version added it
};
}
export async function onIdentify(this: WebSocket, data: IdentifyPayload) {
clearTimeout(this.readyTimeout); clearTimeout(this.readyTimeout);
const { server_id, user_id, session_id, token, streams, video } = const { server_id, user_id, session_id, token, streams, video } =
validateSchema("VoiceIdentifySchema", data.d) as VoiceIdentifySchema; validateSchema("VoiceIdentifySchema", data.d) as VoiceIdentifySchema;
@ -38,27 +55,59 @@ export async function onIdentify(this: WebSocket, data: Payload) {
this.user_id = user_id; this.user_id = user_id;
this.session_id = session_id; this.session_id = session_id;
const sdp = SemanticSDP.SDPInfo.expand(defaultSDP);
sdp.setDTLS( const worker = getWorker();
SemanticSDP.DTLSInfo.expand({ const router = await getOrCreateRouter(voiceState.channel_id);
setup: "actpass", console.debug(`onIdentify(router)`, router.id);
hash: "sha-256",
fingerprint: endpoint.getDTLSFingerprint(), const producerTransport = await router.createWebRtcTransport({
}), webRtcServer: worker.appData.webRtcServer!,
); enableUdp: true,
} as any);
// producerTransport.enableTraceEvent(["bwe", "probation"]);
// listen to any events
for (const event of producerTransport.eventNames()) {
if (typeof event !== "string") continue;
producerTransport.on(event as any, (...args) => {
console.debug(`producerTransport event: ${event}`, args);
});
}
// listen to any events
for (const event of producerTransport.observer.eventNames()) {
if (typeof event !== "string") continue;
producerTransport.observer.on(event as any, (...args) => {
console.debug(`producerTransport observer event: ${event}`, args);
});
}
// const consumerTransport = await router.createWebRtcTransport({
// webRtcServer: worker.appData.webRtcServer!,
// enableUdp: true,
// });
// consumerTransport.enableTraceEvent(["bwe", "probation"]);
// // listen to any events
// for (const event of consumerTransport.eventNames()) {
// if (typeof event !== "string") continue;
// consumerTransport.on(event as any, (...args) => {
// console.debug(`consumerTransport event: ${event}`, args);
// });
// }
this.client = { this.client = {
websocket: this, websocket: this,
out: { ssrc: 1,
tracks: new Map(),
},
in: {
audio_ssrc: 0,
video_ssrc: 0,
rtx_ssrc: 0,
},
sdp,
channel_id: voiceState.channel_id, channel_id: voiceState.channel_id,
codecs: [],
streams: streams!,
headerExtensions: [],
producers: new Map(),
consumers: new Map(),
transports: {
producer: producerTransport,
},
}; };
const clients = getClients(voiceState.channel_id)!; const clients = getClients(voiceState.channel_id)!;
@ -68,24 +117,30 @@ export async function onIdentify(this: WebSocket, data: Payload) {
clients.delete(this.client!); clients.delete(this.client!);
}); });
await Send(this, { const d = {
op: VoiceOPCodes.READY, op: VoiceOPCodes.READY,
d: { d: {
streams: [ streams: streams?.map((x) => ({
// { type: "video", ssrc: this.ssrc + 1, rtx_ssrc: this.ssrc + 2, rid: "100", quality: 100, active: false } ...x,
], ssrc: ++this.client!.ssrc, // first stream should be 2
ssrc: -1, rtx_ssrc: ++this.client!.ssrc, // first stream should be 3
port: endpoint.getLocalPort(), })),
ssrc: this.client.ssrc, // this is just a base, first stream ssrc will be +1 with rtx +2
ip: "192.168.10.112",
port: 20000,
modes: [ modes: [
"aead_aes256_gcm_rtpsize", "aead_aes256_gcm_rtpsize",
"aead_aes256_gcm", "aead_aes256_gcm",
"aead_xchacha20_poly1305_rtpsize",
"xsalsa20_poly1305_lite_rtpsize", "xsalsa20_poly1305_lite_rtpsize",
"xsalsa20_poly1305_lite", "xsalsa20_poly1305_lite",
"xsalsa20_poly1305_suffix", "xsalsa20_poly1305_suffix",
"xsalsa20_poly1305", "xsalsa20_poly1305",
], ],
ip: PublicIP, experiments: ["fixed_keyframe_interval"],
experiments: [], } as VoiceReadySchema,
}, };
});
console.debug(`onIdentify(ready packet)`, d);
await Send(this, d);
} }

View File

@ -18,8 +18,55 @@
import { Payload, Send, WebSocket } from "@spacebar/gateway"; import { Payload, Send, WebSocket } from "@spacebar/gateway";
import { SelectProtocolSchema, validateSchema } from "@spacebar/util"; import { SelectProtocolSchema, validateSchema } from "@spacebar/util";
import { PublicIP, VoiceOPCodes, endpoint } from "@spacebar/webrtc"; import { types as MediaSoupTypes } from "mediasoup";
import SemanticSDP, { MediaInfo, SDPInfo } from "semantic-sdp"; import * as sdpTransform from "sdp-transform";
import { getRouter, SUPPORTED_EXTENTIONS, VoiceOPCodes } from "../util";
// request:
// {
// "codecs": [
// {
// "name": "opus",
// "payload_type": 109,
// "priority": 1000,
// "rtx_payload_type": null,
// "type": "audio"
// },
// {
// "name": "H264",
// "payload_type": 126,
// "priority": 1000,
// "rtx_payload_type": 127,
// "type": "video"
// },
// {
// "name": "VP8",
// "payload_type": 120,
// "priority": 2000,
// "rtx_payload_type": 124,
// "type": "video"
// },
// {
// "name": "VP9",
// "payload_type": 121,
// "priority": 3000,
// "rtx_payload_type": 125,
// "type": "video"
// }
// ],
// "data": "a=fingerprint:sha-256 F1:31:51:8B:E9:C8:3F:33:61:41:5C:BA:7A:59:07:4A:DA:53:40:88:62:0B:DA:B0:4D:0C:58:9B:16:D8:9F:25\na=ice-options:trickle\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\na=extmap:2/recvonly urn:ietf:params:rtp-hdrext:csrc-audio-level\na=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid\na=ice-pwd:5ac55fdfff3ac50fbb6c2852baea62bf\na=ice-ufrag:5e6e9e9c\na=rtpmap:109 opus/48000/2\na=extmap:4 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\na=extmap:5 urn:ietf:params:rtp-hdrext:toffset\na=extmap:6/recvonly http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\na=extmap:7 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\na=rtpmap:120 VP8/90000\na=rtpmap:124 rtx/90000",
// "protocol": "webrtc",
// "rtc_connection_id": "70a69cc2-14d7-496e-ba4b-d16570a95ade",
// "sdp": "a=fingerprint:sha-256 F1:31:51:8B:E9:C8:3F:33:61:41:5C:BA:7A:59:07:4A:DA:53:40:88:62:0B:DA:B0:4D:0C:58:9B:16:D8:9F:25\na=ice-options:trickle\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\na=extmap:2/recvonly urn:ietf:params:rtp-hdrext:csrc-audio-level\na=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid\na=ice-pwd:5ac55fdfff3ac50fbb6c2852baea62bf\na=ice-ufrag:5e6e9e9c\na=rtpmap:109 opus/48000/2\na=extmap:4 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\na=extmap:5 urn:ietf:params:rtp-hdrext:toffset\na=extmap:6/recvonly http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\na=extmap:7 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\na=rtpmap:120 VP8/90000\na=rtpmap:124 rtx/90000"
// }
// response:
// {
// "audio_codec": "opus",
// "media_session_id": "40984f1105901745530fb81cfe5f5633",
// "sdp": "m=audio 50021 ICE/SDP\na=fingerprint:sha-256 4A:79:94:16:44:3F:BD:05:41:5A:C7:20:F3:12:54:70:00:73:5D:33:00:2D:2C:80:9B:39:E1:9F:2D:A7:49:87\nc=IN IP4 66.22.206.164\na=rtcp:50021\na=ice-ufrag:iLG8\na=ice-pwd:qMfFrCD0PcC/TxyfQM9H7t\na=fingerprint:sha-256 4A:79:94:16:44:3F:BD:05:41:5A:C7:20:F3:12:54:70:00:73:5D:33:00:2D:2C:80:9B:39:E1:9F:2D:A7:49:87\na=candidate:1 1 UDP 4261412862 66.22.206.164 50021 typ host\n",
// "video_codec": "H264"
// }
export async function onSelectProtocol(this: WebSocket, payload: Payload) { export async function onSelectProtocol(this: WebSocket, payload: Payload) {
if (!this.client) return; if (!this.client) return;
@ -29,39 +76,120 @@ export async function onSelectProtocol(this: WebSocket, payload: Payload) {
payload.d, payload.d,
) as SelectProtocolSchema; ) as SelectProtocolSchema;
const offer = SemanticSDP.SDPInfo.parse("m=audio\n" + data.sdp!); await Send(this, { op: VoiceOPCodes.MEDIA_SINK_WANTS, d: { any: 100 } });
this.client.sdp!.setICE(offer.getICE());
this.client.sdp!.setDTLS(offer.getDTLS());
const transport = endpoint.createTransport(this.client.sdp!); const router = getRouter(this.client.channel_id);
this.client.transport = transport; if (!router) {
transport.setRemoteProperties(this.client.sdp!); console.error("Could not find router");
transport.setLocalProperties(this.client.sdp!); this.close();
return;
}
const dtls = transport.getLocalDTLSInfo(); const clientAudioCodecs = data
const ice = transport.getLocalICEInfo(); .codecs!.filter((x) => x.type === "audio")
const port = endpoint.getLocalPort(); .sort((a, b) => a.priority - b.priority);
const fingerprint = dtls.getHash() + " " + dtls.getFingerprint();
const candidates = transport.getLocalCandidates();
const candidate = candidates[0];
const answer = const clientVideoCodecs = data
`m=audio ${port} ICE/SDP` + .codecs!.filter((x) => x.type === "video")
`a=fingerprint:${fingerprint}` + .sort((a, b) => a.priority - b.priority);
`c=IN IP4 ${PublicIP}` +
`a=rtcp:${port}` +
`a=ice-ufrag:${ice.getUfrag()}` +
`a=ice-pwd:${ice.getPwd()}` +
`a=fingerprint:${fingerprint}` +
`a=candidate:1 1 ${candidate.getTransport()} ${candidate.getFoundation()} ${candidate.getAddress()} ${candidate.getPort()} typ host`;
const serverAudioCodecs = router.rtpCapabilities.codecs!.filter(
(x) => x.kind === "audio",
);
const serverVideoCodecs = router.rtpCapabilities.codecs!.filter(
(x) => x.kind === "video",
);
const audioCodec = serverAudioCodecs.find((x) => {
return clientAudioCodecs.some(
(y) => y.name === x.mimeType.split("/")[1],
);
});
const videoCodec = serverVideoCodecs.find((x) => {
return clientVideoCodecs.some(
(y) => y.name === x.mimeType.split("/")[1],
);
});
if (!audioCodec || !videoCodec) {
console.error("Could not agree on a codec");
this.close();
return;
}
const sdp = sdpTransform.parse(data.sdp!);
this.client.sdp = sdp;
this.client.codecs = data.codecs!;
this.client.headerExtensions =
sdp.ext
?.filter((x) => SUPPORTED_EXTENTIONS.includes(x.uri))
.map((x) => ({
uri: x.uri as MediaSoupTypes.RtpHeaderExtensionUri,
id: x.value,
})) ?? [];
await this.client.transports.producer.connect({
dtlsParameters: {
fingerprints: [
{
algorithm: sdp.fingerprint!
.type as MediaSoupTypes.FingerprintAlgorithm,
value: sdp.fingerprint!.hash,
},
],
role: "client",
},
});
console.debug("producer transport connected");
// await this.client.transports.consumer.connect({
// dtlsParameters: {
// fingerprints: [
// {
// algorithm: sdp.fingerprint!
// .type as MediaSoupTypes.FingerprintAlgorithm,
// value: sdp.fingerprint!.hash,
// },
// ],
// role: "client",
// },
// });
// console.debug("consumer transport connected");
const iceParameters = this.client.transports.producer.iceParameters;
const iceCandidates = this.client.transports.producer.iceCandidates;
const iceCandidate = iceCandidates[0];
const dltsParamters = this.client.transports.producer.dtlsParameters;
const fingerprint = dltsParamters.fingerprints.find(
(x) => x.algorithm === "sha-256",
)!;
const sdpAnswer =
`m=audio ${iceCandidate.port} ICE/SDP\n` +
`a=fingerprint:sha-256 ${fingerprint.value}\n` +
`c=IN IP4 ${iceCandidate.ip}\n` +
`a=rtcp:${iceCandidate.port}\n` +
`a=ice-ufrag:${iceParameters.usernameFragment}\n` +
`a=ice-pwd:${iceParameters.password}\n` +
`a=fingerprint:sha-256 ${fingerprint.value}\n` +
`a=candidate:1 1 ${iceCandidate.protocol.toUpperCase()} ${
iceCandidate.priority
} ${iceCandidate.ip} ${iceCandidate.port} typ ${iceCandidate.type}\n`;
console.debug("onSelectProtocol sdp serialized\n", sdpAnswer);
await Send(this, { await Send(this, {
op: VoiceOPCodes.SESSION_DESCRIPTION, op: VoiceOPCodes.SELECT_PROTOCOL_ACK,
d: { d: {
video_codec: "H264", // audio_codec: audioCodec.mimeType.split("/")[1],
sdp: answer, // video_codec: videoCodec.mimeType.split("/")[1],
audioCodec: "opus",
videoCodec: "H264",
media_session_id: this.session_id, media_session_id: this.session_id,
audio_codec: "opus", sdp: sdpAnswer,
}, },
}); });
} }

View File

@ -25,15 +25,13 @@ export async function onSpeaking(this: WebSocket, data: Payload) {
if (!this.client) return; if (!this.client) return;
getClients(this.client.channel_id).forEach((client) => { getClients(this.client.channel_id).forEach((client) => {
if (client === this.client) return; if (client.websocket.user_id === this.user_id) return;
const ssrc = this.client!.out.tracks.get(client.websocket.user_id);
Send(client.websocket, { Send(client.websocket, {
op: VoiceOPCodes.SPEAKING, op: VoiceOPCodes.SPEAKING,
d: { d: {
user_id: client.websocket.user_id, user_id: client.websocket.user_id,
speaking: data.d.speaking, speaking: data.d.speaking,
ssrc: ssrc?.audio_ssrc || 0, ssrc: data.d.ssrc,
}, },
}); });
}); });

View File

@ -1,151 +1,322 @@
/* /*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend. Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Spacebar and Spacebar Contributors Copyright (C) 2023 Spacebar and Spacebar Contributors
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Payload, Send, WebSocket } from "@spacebar/gateway"; import { Payload, Send, WebSocket } from "@spacebar/gateway";
import { validateSchema, VoiceVideoSchema } from "@spacebar/util"; import { validateSchema, VoiceVideoSchema } from "@spacebar/util";
import { channels, getClients, VoiceOPCodes } from "@spacebar/webrtc"; import { types as MediaSoupTypes } from "mediasoup";
import { IncomingStreamTrack, SSRCs } from "medooze-media-server"; import { getClients, getRouter, VoiceOPCodes } from "../util";
import SemanticSDP from "semantic-sdp";
// request:
// {
// "audio_ssrc": 0,
// "rtx_ssrc": 197,
// "streams": [
// {
// "active": false,
// "max_bitrate": 2500000,
// "max_framerate": 30,
// "max_resolution": {
// "height": 720,
// "type": "fixed",
// "width": 1280
// },
// "quality": 100,
// "rid": "100",
// "rtx_ssrc": 197,
// "ssrc": 196,
// "type": "video"
// }
// ],
// "video_ssrc": 196
// }
export async function onVideo(this: WebSocket, payload: Payload) { export async function onVideo(this: WebSocket, payload: Payload) {
if (!this.client) return; if (!this.client) return;
const { transport, channel_id } = this.client; const { channel_id } = this.client;
if (!transport) return;
const d = validateSchema("VoiceVideoSchema", payload.d) as VoiceVideoSchema; const d = validateSchema("VoiceVideoSchema", payload.d) as VoiceVideoSchema;
console.log(d);
await Send(this, { op: VoiceOPCodes.MEDIA_SINK_WANTS, d: { any: 100 } }); await Send(this, { op: VoiceOPCodes.MEDIA_SINK_WANTS, d: { any: 100 } });
const id = "stream" + this.user_id; const router = getRouter(channel_id);
if (!router) {
console.error(`router not found`);
return;
}
var stream = this.client.in.stream!; this.client.producers.forEach((producer) => producer.close());
if (!stream) { this.client.consumers.forEach((consumer) => consumer.close());
stream = this.client.transport!.createIncomingStream(
// @ts-ignore
SemanticSDP.StreamInfo.expand({
id,
// @ts-ignore
tracks: [],
}),
);
this.client.in.stream = stream;
const interval = setInterval(() => { // const producer = await this.client.transports.producer.produce({
for (const track of stream.getTracks()) { // kind: "audio",
for (const layer of Object.values(track.getStats())) { // rtpParameters: {
console.log(track.getId(), layer.total); // codecs: this.client.codecs
} // .filter((x) => x.type === "audio")
} // .map((x) => {
}, 5000); // const a = MEDIA_CODECS.find(
// (y) =>
// y.kind === "audio" && y.mimeType.endsWith(x.name),
// );
stream.on("stopped", () => { // return {
console.log("stream stopped"); // clockRate: a!.clockRate,
clearInterval(interval); // mimeType: a!.mimeType,
// payloadType: x.payload_type,
// channels: a?.channels,
// parameters: a?.parameters,
// rtcpFeedback: a?.rtcpFeedback,
// };
// }),
// encodings: [
// {
// ssrc: d.audio_ssrc,
// },
// ],
// headerExtensions: this.client.headerExtensions,
// },
// paused: false,
// });
let videoProducer: MediaSoupTypes.Producer | null = null;
// if (d.video_ssrc !== 0) {
// videoProducer = await this.client.transports.producer.produce({
// kind: "video",
// rtpParameters: {
// codecs: [
// ...this.client.codecs
// .filter((x) => x.type === "video")
// .map((x) => {
// const a = MEDIA_CODECS.find(
// (y) =>
// y.kind === "video" &&
// y.mimeType.endsWith(x.name),
// );
// return {
// clockRate: a!.clockRate,
// mimeType: a!.mimeType,
// payloadType: x.payload_type,
// channels: a?.channels,
// parameters: a?.parameters,
// rtcpFeedback: a?.rtcpFeedback,
// };
// }),
// // {
// // payloadType: 126,
// // mimeType: "video/H264",
// // clockRate: 90000,
// // parameters: {
// // "level-asymmetry-allowed": 1,
// // },
// // rtcpFeedback: [
// // { type: "nack" },
// // { type: "nack", parameter: "pli" },
// // { type: "ccm", parameter: "fir" },
// // { type: "goog-remb" },
// // { type: "transport-cc" },
// // ],
// // },
// // {
// // payloadType: 120,
// // mimeType: "video/VP8",
// // clockRate: 90000,
// // rtcpFeedback: [
// // { type: "nack" },
// // { type: "nack", parameter: "pli" },
// // { type: "ccm", parameter: "fir" },
// // { type: "goog-remb" },
// // { type: "transport-cc" },
// // ],
// // },
// // ...this.client.codecs
// // .filter((x) => x.type === "video")
// // .map((x) => {
// // const a = MEDIA_CODECS.find(
// // (y) =>
// // y.kind === "video" &&
// // y.mimeType.endsWith(x.name),
// // );
// // return {
// // mimeType: "video/rtx",
// // clockRate: a!.clockRate,
// // payloadType: x.rtx_payload_type!,
// // parameters: {
// // apt: x.payload_type,
// // },
// // };
// // }),
// // {
// // payloadType: 127,
// // mimeType: "video/rtx",
// // clockRate: 90000,
// // parameters: {
// // apt: 126,
// // },
// // },
// ],
// encodings: [
// {
// ssrc: d.video_ssrc,
// // rtx: { ssrc: d.rtx_ssrc! },
// // codecPayloadType: 126,
// // rid: d.streams![0].rid,
// },
// // ...this.client.codecs.map((x) => ({
// // ssrc: d.video_ssrc,
// // codecPayloadType: x.payload_type,
// // })),
// ],
// headerExtensions: this.client.headerExtensions,
// },
// paused: false,
// });
// console.log(await videoProducer.dump());
// videoProducer.enableTraceEvent([
// "fir",
// "keyframe",
// "nack",
// "pli",
// "rtp",
// "sr",
// ]);
// // for (const event of videoProducer.eventNames()) {
// // if (typeof event !== "string") continue;
// // videoProducer.on(event as any, (...args) => {
// // console.debug(`videoProducer event: ${event}`, args);
// // });
// // }
// videoProducer.on("trace", (trace) => {
// console.debug(
// `videoproducer(trace):`,
// JSON.stringify(trace, null, 4),
// );
// });
// this.client.producers.set(d.video_ssrc, videoProducer);
// }
let audioProducer: MediaSoupTypes.Producer | null = null;
if (d.audio_ssrc !== 0) {
audioProducer = await this.client.transports.producer.produce({
kind: "audio",
rtpParameters: {
codecs: [
{
payloadType: 109,
mimeType: "audio/opus",
clockRate: 48000,
channels: 2,
rtcpFeedback: [
{ type: "nack" },
{ type: "transport-cc" },
],
},
],
encodings: [
{
ssrc: d.audio_ssrc,
},
],
headerExtensions: this.client.headerExtensions,
},
paused: false,
}); });
this.on("close", () => {
transport!.stop(); console.debug(
}); `audioProducer(dump)`,
const out = transport.createOutgoingStream( JSON.stringify(await audioProducer.dump(), null, 4),
// @ts-ignore
SemanticSDP.StreamInfo.expand({
id: "out" + this.user_id,
// @ts-ignore
tracks: [],
}),
); );
this.client.out.stream = out;
const clients = channels.get(channel_id)!; audioProducer.enableTraceEvent([
"fir",
"keyframe",
"nack",
"pli",
"rtp",
"sr",
]);
clients.forEach((client) => { audioProducer.on("trace", (trace) => {
if (client.websocket.user_id === this.user_id) return; console.debug(
if (!client.in.stream) return; `audioProducer(trace):`,
JSON.stringify(trace, null, 4),
);
});
client.in.stream?.getTracks().forEach((track) => { // for (const event of audioProducer.eventNames()) {
attachTrack.call(this, track, client.websocket.user_id); // if (typeof event !== "string") continue;
// audioProducer.on(event as any, (...args) => {
// console.debug(`audioproducer event: ${event}`, args);
// });
// }
this.client.producers.set(d.audio_ssrc, audioProducer);
}
const clients = getClients(this.client.channel_id);
console.log(`there are ${clients.size} clients`);
clients.forEach(async (client) => {
if (client.websocket.user_id === this.user_id) return;
// if (videoProducer) {
// console.debug(`consuming video for ${client.websocket.user_id}`);
// const videoConsumer = await client.transports.producer.consume({
// producerId: videoProducer.id,
// rtpCapabilities: router.rtpCapabilities,
// paused: false,
// });
// client.consumers.set(videoConsumer.id, videoConsumer);
// }
let ssrc = d.audio_ssrc;
if (audioProducer) {
console.debug(`consuming audio for ${client.websocket.user_id}`);
const audioConsumer = await client.transports.producer.consume({
producerId: audioProducer.id,
rtpCapabilities: router.rtpCapabilities,
paused: false,
}); });
console.log(audioConsumer as any);
ssrc = (audioConsumer as any).consumableRtpEncodings[0].ssrc;
console.debug(
`audioConsumer(dump; ${client.websocket.user_id})`,
JSON.stringify(await audioConsumer.dump(), null, 4),
);
client.consumers.set(audioConsumer.id, audioConsumer);
}
console.log(`sending video payload to ${client.websocket.user_id}`);
Send(client.websocket, {
op: VoiceOPCodes.VIDEO,
d: {
user_id: client.websocket.user_id,
streams: d.streams!,
audio_ssrc: ssrc,
// video_ssrc: d.video_ssrc,
} as VoiceVideoSchema,
}); });
}
if (d.audio_ssrc) {
handleSSRC.call(this, "audio", {
media: d.audio_ssrc,
rtx: d.audio_ssrc + 1,
});
}
if (d.video_ssrc && d.rtx_ssrc) {
handleSSRC.call(this, "video", {
media: d.video_ssrc,
rtx: d.rtx_ssrc,
});
}
}
function attachTrack(
this: WebSocket,
track: IncomingStreamTrack,
user_id: string,
) {
if (!this.client) return;
const outTrack = this.client.transport!.createOutgoingStreamTrack(
track.getMedia(),
);
outTrack.attachTo(track);
this.client.out.stream!.addTrack(outTrack);
var ssrcs = this.client.out.tracks.get(user_id)!;
if (!ssrcs)
ssrcs = this.client.out.tracks
.set(user_id, { audio_ssrc: 0, rtx_ssrc: 0, video_ssrc: 0 })
.get(user_id)!;
if (track.getMedia() === "audio") {
ssrcs.audio_ssrc = outTrack.getSSRCs().media!;
} else if (track.getMedia() === "video") {
ssrcs.video_ssrc = outTrack.getSSRCs().media!;
ssrcs.rtx_ssrc = outTrack.getSSRCs().rtx!;
}
Send(this, {
op: VoiceOPCodes.VIDEO,
d: {
user_id: user_id,
...ssrcs,
} as VoiceVideoSchema,
}); });
} }
function handleSSRC(this: WebSocket, type: "audio" | "video", ssrcs: SSRCs) {
if (!this.client) return;
const stream = this.client.in.stream!;
const transport = this.client.transport!;
const id = type + ssrcs.media;
var track = stream.getTrack(id);
if (!track) {
console.log("createIncomingStreamTrack", id);
track = transport.createIncomingStreamTrack(type, { id, ssrcs });
stream.addTrack(track);
const clients = getClients(this.client.channel_id)!;
clients.forEach((client) => {
if (client.websocket.user_id === this.user_id) return;
if (!client.out.stream) return;
attachTrack.call(this, track, client.websocket.user_id);
});
}
}

View File

@ -29,7 +29,7 @@ export enum VoiceOPCodes {
SELECT_PROTOCOL = 1, SELECT_PROTOCOL = 1,
READY = 2, READY = 2,
HEARTBEAT = 3, HEARTBEAT = 3,
SESSION_DESCRIPTION = 4, SELECT_PROTOCOL_ACK = 4,
SPEAKING = 5, SPEAKING = 5,
HEARTBEAT_ACK = 6, HEARTBEAT_ACK = 6,
RESUME = 7, RESUME = 7,
@ -41,4 +41,17 @@ export enum VoiceOPCodes {
MEDIA_SINK_WANTS = 15, MEDIA_SINK_WANTS = 15,
VOICE_BACKEND_VERSION = 16, VOICE_BACKEND_VERSION = 16,
CHANNEL_OPTIONS_UPDATE = 17, CHANNEL_OPTIONS_UPDATE = 17,
FLAGS = 18,
SPEED_TEST = 19,
PLATFORM = 20,
SECURE_FRAMES_PREPARE_PROTOCOL_TRANSITION = 21,
SECURE_FRAMES_EXECUTE_TRANSITION = 22,
SECURE_FRAMES_READY_FOR_TRANSITION = 23,
SECURE_FRAMES_PREPARE_EPOCH = 24,
MLS_EXTERNAL_SENDER_PACKAGE = 25,
MLS_KEY_PACKAGE = 26,
MLS_PROPOSALS = 27,
MLS_COMMIT_WELCOME = 28,
MLS_PREPARE_COMMIT_TRANSITION = 29,
MLS_WELCOME = 30,
} }

View File

@ -15,63 +15,387 @@
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { WebSocket } from "@spacebar/gateway"; import { WebSocket } from "@spacebar/gateway";
import MediaServer, { import * as mediasoup from "mediasoup";
IncomingStream, import { types as MediaSoupTypes } from "mediasoup";
OutgoingStream, import * as sdpTransform from "sdp-transform";
Transport,
} from "medooze-media-server";
import SemanticSDP from "semantic-sdp";
MediaServer.enableLog(true);
export const PublicIP = process.env.PUBLIC_IP || "127.0.0.1";
try {
const range = process.env.WEBRTC_PORT_RANGE || "4000";
var ports = range.split("-");
const min = Number(ports[0]);
const max = Number(ports[1]);
MediaServer.setPortRange(min, max);
} catch (error) {
console.error(
"Invalid env var: WEBRTC_PORT_RANGE",
process.env.WEBRTC_PORT_RANGE,
error,
);
process.exit(1);
}
export const endpoint = MediaServer.createEndpoint(PublicIP);
export const channels = new Map<string, Set<Client>>(); export const channels = new Map<string, Set<Client>>();
export const workers: MediaSoupTypes.Worker<AppData>[] = [];
export const routers = new Map<string, MediaSoupTypes.Router>();
export let nextWorkerIdx = 0;
export interface Client { export interface Client {
transport?: Transport;
websocket: WebSocket; websocket: WebSocket;
out: { ssrc: number;
stream?: OutgoingStream; sdp?: sdpTransform.SessionDescription;
tracks: Map<
string,
{
audio_ssrc: number;
video_ssrc: number;
rtx_ssrc: number;
}
>;
};
in: {
stream?: IncomingStream;
audio_ssrc: number;
video_ssrc: number;
rtx_ssrc: number;
};
sdp: SemanticSDP.SDPInfo;
channel_id: string; channel_id: string;
headerExtensions: MediaSoupTypes.RtpHeaderExtensionParameters[];
// secret_key?: Uint8Array;
codecs: Codec[];
streams: Stream[];
producers: Map<number, MediaSoupTypes.Producer>;
consumers: Map<string, MediaSoupTypes.Consumer>;
transports: {
producer: MediaSoupTypes.WebRtcTransport;
consumer?: MediaSoupTypes.WebRtcTransport;
};
} }
export function getClients(channel_id: string) { export function getClients(channel_id: string) {
if (!channels.has(channel_id)) channels.set(channel_id, new Set()); if (!channels.has(channel_id)) channels.set(channel_id, new Set());
return channels.get(channel_id)!; return channels.get(channel_id)!;
} }
export function getRouter(channelId: string) {
return routers.get(channelId);
}
export async function getOrCreateRouter(channel_id: string) {
if (!routers.has(channel_id)) {
const worker = getWorker();
const router = await worker.createRouter({
mediaCodecs: MEDIA_CODECS,
});
routers.set(channel_id, router);
return router;
}
return routers.get(channel_id)!;
}
export function getWorker() {
const worker = workers[nextWorkerIdx];
if (++nextWorkerIdx === workers.length) nextWorkerIdx = 0;
return worker;
}
export async function createWorkers() {
const numWorkers = 1;
for (let i = 0; i < numWorkers; i++) {
const worker = await mediasoup.createWorker({
logLevel: "debug",
logTags: [
"info",
"ice",
"dtls",
"rtp",
"srtp",
"rtcp",
"rtx",
"bwe",
"score",
"simulcast",
"svc",
"sctp",
],
});
worker.on("died", () => {
console.error(
"mediasoup Worker died, exiting in 2 seconds... [pid:%d]",
worker.pid,
);
setTimeout(() => process.exit(1), 2000);
});
worker.observer.on("newrouter", (router) => {
console.debug("new router [pid:%d]: %s", worker.pid, router.id);
router.observer.on("newrtpobserver", (rtpObserver) => {
console.debug(
"new RtpObserver [pid:%d]: %s",
worker.pid,
rtpObserver.id,
);
});
router.observer.on("newtransport", async (transport) => {
console.debug(
"new transport [pid:%d]: %s",
worker.pid,
transport.id,
);
await transport.enableTraceEvent();
(transport as MediaSoupTypes.WebRtcTransport).on(
"iceselectedtuplechange",
(tuple) => {
console.log(`transport(iceselectedtuplechange)`, tuple);
},
);
(transport as MediaSoupTypes.WebRtcTransport).on(
"icestatechange",
(icestate) => {
console.log(`transport(ice state change)`, icestate);
},
);
(transport as MediaSoupTypes.WebRtcTransport).on(
"trace",
(trace) => {
console.log(`transport(trace)`, trace);
},
);
(transport as MediaSoupTypes.WebRtcTransport).on(
"dtlsstatechange",
(dtlsstate) => {
console.log(`transport(dtls state change)`, dtlsstate);
},
);
(transport as MediaSoupTypes.WebRtcTransport).on(
"sctpstatechange",
(sctpstate) => {
console.log(`transport(sctp state change)`, sctpstate);
},
);
transport.observer.on("newproducer", (producer) => {
console.debug(
"new Producer [pid:%d]: %s",
worker.pid,
producer.id,
);
producer.on("score", (score) => {
console.log(`transport producer(score)`, score);
});
producer.on("trace", (trace) => {
console.log(`transport producer(trace)`, trace);
});
producer.on("videoorientationchange", (orientation) => {
console.log(
`transport producer(videoorientationchange)`,
orientation,
);
});
});
transport.observer.on("newconsumer", (consumer) => {
console.debug(
"new Consumer [pid:%d]: %s",
worker.pid,
consumer.id,
);
consumer.on("rtp", (rtpPacket) => {
console.log(`transport consumer(rtp)`, rtpPacket);
});
consumer.on("trace", (trace) => {
console.log(`transport consumer(trace)`, trace);
});
consumer.on("score", (score) => {
console.log(`transport consumer(score)`, score);
});
consumer.on("layerschange", (layers) => {
console.log(`transport consumer(layerschange)`, layers);
});
});
});
});
worker.observer.on("newwebrtcserver", (webRtcServer) => {
console.debug(
"new WebRtcServer [pid:%d]: %s",
worker.pid,
webRtcServer.id,
);
});
worker.observer.on("close", () => {
console.debug("mediasoup Worker closed [pid:%d]", worker.pid);
});
workers.push(worker);
// Create a WebRtcServer in this Worker.
// Each mediasoup Worker will run its own WebRtcServer, so those cannot
// share the same listening ports. Hence we increase the value in config.js
// for each Worker.
const webRtcServerOptions: MediaSoupTypes.WebRtcServerOptions = {
listenInfos: [
{
protocol: "udp",
ip: "0.0.0.0",
announcedAddress: "192.168.10.112",
port: 20000,
},
],
};
const webRtcServer = await worker.createWebRtcServer(
webRtcServerOptions,
);
worker.appData.webRtcServer = webRtcServer;
// Log worker resource usage every X seconds.
// setInterval(async () => {
// const usage = await worker.getResourceUsage();
// console.debug(
// "mediasoup Worker resource usage [pid:%d]: %o",
// worker.pid,
// usage,
// );
// const dump = await worker.dump();
// console.debug(
// "mediasoup Worker dump [pid:%d]: %o",
// worker.pid,
// dump,
// );
// }, 120000);
}
}
export interface AppData extends MediaSoupTypes.AppData {
webRtcServer?: MediaSoupTypes.WebRtcServer;
}
export interface Codec {
name: "opus" | "VP8" | "VP9" | "H264";
type: "audio" | "video";
priority: number;
payload_type: number;
rtx_payload_type?: number | null;
}
export const MEDIA_CODECS: MediaSoupTypes.RtpCodecCapability[] = [
{
kind: "audio",
mimeType: "audio/opus",
clockRate: 48000,
channels: 2,
rtcpFeedback: [{ type: "nack" }, { type: "transport-cc" }],
},
{
kind: "audio",
mimeType: "audio/multiopus",
clockRate: 48000,
channels: 4,
// Quad channel.
parameters: {
channel_mapping: "0,1,2,3",
num_streams: 2,
coupled_streams: 2,
},
rtcpFeedback: [{ type: "nack" }, { type: "transport-cc" }],
},
{
kind: "audio",
mimeType: "audio/multiopus",
clockRate: 48000,
channels: 6,
// 5.1.
parameters: {
channel_mapping: "0,4,1,2,3,5",
num_streams: 4,
coupled_streams: 2,
},
rtcpFeedback: [{ type: "nack" }, { type: "transport-cc" }],
},
{
kind: "audio",
mimeType: "audio/multiopus",
clockRate: 48000,
channels: 8,
// 7.1.
parameters: {
channel_mapping: "0,6,1,2,3,4,5,7",
num_streams: 5,
coupled_streams: 3,
},
rtcpFeedback: [{ type: "nack" }, { type: "transport-cc" }],
},
{
kind: "video",
mimeType: "video/VP8",
clockRate: 90000,
rtcpFeedback: [
{ type: "nack" },
{ type: "nack", parameter: "pli" },
{ type: "ccm", parameter: "fir" },
{ type: "goog-remb" },
{ type: "transport-cc" },
],
},
{
kind: "video",
mimeType: "video/VP9",
clockRate: 90000,
rtcpFeedback: [
{ type: "nack" },
{ type: "nack", parameter: "pli" },
{ type: "ccm", parameter: "fir" },
{ type: "goog-remb" },
{ type: "transport-cc" },
],
},
{
kind: "video",
mimeType: "video/H264",
clockRate: 90000,
parameters: {
"level-asymmetry-allowed": 1,
},
rtcpFeedback: [
{ type: "nack" },
{ type: "nack", parameter: "pli" },
{ type: "ccm", parameter: "fir" },
{ type: "goog-remb" },
{ type: "transport-cc" },
],
},
{
kind: "video",
mimeType: "video/H265",
clockRate: 90000,
parameters: {
"level-asymmetry-allowed": 1,
},
rtcpFeedback: [
{ type: "nack" },
{ type: "nack", parameter: "pli" },
{ type: "ccm", parameter: "fir" },
{ type: "goog-remb" },
{ type: "transport-cc" },
],
},
];
export interface Stream {
type: string;
rid: string; //number
quality: number;
}
export const SUPPORTED_EXTENTIONS = [
"urn:ietf:params:rtp-hdrext:sdes:mid",
"urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id",
"urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id",
"http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07",
"urn:ietf:params:rtp-hdrext:framemarking",
"urn:ietf:params:rtp-hdrext:ssrc-audio-level",
"urn:3gpp:video-orientation",
"urn:ietf:params:rtp-hdrext:toffset",
"http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01",
"http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time",
"http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time",
"http://www.webrtc.org/experiments/rtp-hdrext/playout-delay",
];

View File

@ -1,5 +1,4 @@
{ {
"exclude": ["./src/webrtc"],
"include": ["./src"], "include": ["./src"],
"compilerOptions": { "compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */ /* Visit https://aka.ms/tsconfig to read more about this file */
@ -37,7 +36,8 @@
"@spacebar/api*": ["./api"], "@spacebar/api*": ["./api"],
"@spacebar/gateway*": ["./gateway"], "@spacebar/gateway*": ["./gateway"],
"@spacebar/cdn*": ["./cdn"], "@spacebar/cdn*": ["./cdn"],
"@spacebar/util*": ["./util"] "@spacebar/util*": ["./util"],
"@spacebar/webrtc*": ["./webrtc"]
} /* Specify a set of entries that re-map imports to additional lookup locations. */, } /* Specify a set of entries that re-map imports to additional lookup locations. */,
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */