修复了直播界面看不到界面的问题

This commit is contained in:
ShiQi 2025-12-15 16:39:46 +08:00
parent 374c771578
commit 86b21d022a
16 changed files with 451 additions and 51 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
.node_modules/
**/node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# build output
build/
dist/
**/build/
**/dist/
# env
.env
.env.*
!.env.example
# OS/IDE
.DS_Store
Thumbs.db
.idea/
.vscode/

View File

@ -1 +1 @@
[{"C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\index.js":"1","C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\App.jsx":"2","C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\pages\\RoomPage.jsx":"3","C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\pages\\HomePage.jsx":"4","C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\services\\api.js":"5","C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\components\\RoomList.jsx":"6","C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\components\\CreateRoom.jsx":"7","C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\components\\LivePlayer.jsx":"8","C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\components\\StreamInfo.jsx":"9","C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\components\\RoomCard.jsx":"10"},{"size":310,"mtime":1765701583991,"results":"11","hashOfConfig":"12"},{"size":334,"mtime":1765701610578,"results":"13","hashOfConfig":"12"},{"size":2127,"mtime":1765701673204,"results":"14","hashOfConfig":"12"},{"size":1724,"mtime":1765701625879,"results":"15","hashOfConfig":"12"},{"size":533,"mtime":1765701603938,"results":"16","hashOfConfig":"12"},{"size":476,"mtime":1765701614401,"results":"17","hashOfConfig":"12"},{"size":1607,"mtime":1765701629855,"results":"18","hashOfConfig":"12"},{"size":3669,"mtime":1765701660353,"results":"19","hashOfConfig":"12"},{"size":1389,"mtime":1765701642070,"results":"20","hashOfConfig":"12"},{"size":619,"mtime":1765701612898,"results":"21","hashOfConfig":"12"},{"filePath":"22","messages":"23","suppressedMessages":"24","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"j0zk4s",{"filePath":"25","messages":"26","suppressedMessages":"27","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"28","messages":"29","suppressedMessages":"30","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"31","messages":"32","suppressedMessages":"33","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"34","messages":"35","suppressedMessages":"36","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"37","messages":"38","suppressedMessages":"39","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"40","messages":"41","suppressedMessages":"42","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"43","messages":"44","suppressedMessages":"45","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"46","messages":"47","suppressedMessages":"48","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"49","messages":"50","suppressedMessages":"51","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\index.js",[],[],"C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\App.jsx",[],[],"C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\pages\\RoomPage.jsx",[],[],"C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\pages\\HomePage.jsx",[],[],"C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\services\\api.js",[],[],"C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\components\\RoomList.jsx",[],[],"C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\components\\CreateRoom.jsx",[],[],"C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\components\\LivePlayer.jsx",[],[],"C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\components\\StreamInfo.jsx",[],[],"C:\\Users\\Administrator\\Desktop\\Project\\Test\\live-streaming\\client\\src\\components\\RoomCard.jsx",[],[]] [{"C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\index.js":"1","C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\App.jsx":"2","C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\pages\\HomePage.jsx":"3","C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\pages\\RoomPage.jsx":"4","C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\services\\api.js":"5","C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\components\\CreateRoom.jsx":"6","C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\components\\LivePlayer.jsx":"7","C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\components\\RoomList.jsx":"8","C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\components\\StreamInfo.jsx":"9","C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\components\\RoomCard.jsx":"10"},{"size":310,"mtime":1765770657007,"results":"11","hashOfConfig":"12"},{"size":334,"mtime":1765770657002,"results":"13","hashOfConfig":"12"},{"size":1724,"mtime":1765770657008,"results":"14","hashOfConfig":"12"},{"size":2127,"mtime":1765770657008,"results":"15","hashOfConfig":"12"},{"size":533,"mtime":1765770657009,"results":"16","hashOfConfig":"12"},{"size":1607,"mtime":1765770657003,"results":"17","hashOfConfig":"12"},{"size":4682,"mtime":1765786910702,"results":"18","hashOfConfig":"12"},{"size":476,"mtime":1765770657005,"results":"19","hashOfConfig":"12"},{"size":1389,"mtime":1765770657005,"results":"20","hashOfConfig":"12"},{"size":619,"mtime":1765770657004,"results":"21","hashOfConfig":"12"},{"filePath":"22","messages":"23","suppressedMessages":"24","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1w1jria",{"filePath":"25","messages":"26","suppressedMessages":"27","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"28","messages":"29","suppressedMessages":"30","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"31","messages":"32","suppressedMessages":"33","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"34","messages":"35","suppressedMessages":"36","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"37","messages":"38","suppressedMessages":"39","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"40","messages":"41","suppressedMessages":"42","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"43","messages":"44","suppressedMessages":"45","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"46","messages":"47","suppressedMessages":"48","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"49","messages":"50","suppressedMessages":"51","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\index.js",[],[],"C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\App.jsx",[],[],"C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\pages\\HomePage.jsx",[],[],"C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\pages\\RoomPage.jsx",[],[],"C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\services\\api.js",[],[],"C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\components\\CreateRoom.jsx",[],[],"C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\components\\LivePlayer.jsx",[],[],"C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\components\\RoomList.jsx",[],[],"C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\components\\StreamInfo.jsx",[],[],"C:\\Users\\Administrator\\Desktop\\zhibo_1\\live-streaming\\client\\src\\components\\RoomCard.jsx",[],[]]

View File

@ -5,26 +5,38 @@ import Hls from 'hls.js';
function LivePlayer({ room }) { function LivePlayer({ room }) {
const videoRef = useRef(null); const videoRef = useRef(null);
const playerRef = useRef(null); const playerRef = useRef(null);
const triedAltFlvRef = useRef(false);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [playerType, setPlayerType] = useState(null); const [playerType, setPlayerType] = useState(null);
const flvUrl = room?.streamUrls?.flv;
const hlsUrl = room?.streamUrls?.hls;
useEffect(() => { useEffect(() => {
if (!room?.isLive || !room?.streamUrls) return; if (!room?.isLive || !flvUrl) return;
triedAltFlvRef.current = false;
const video = videoRef.current; const video = videoRef.current;
const { flv: flvUrl, hls: hlsUrl } = room.streamUrls;
// 使 FLV () const getAltFlvUrl = (url) => {
if (flvjs.isSupported()) { try {
console.log('使用 FLV 播放器'); const u = new URL(url);
setPlayerType('FLV'); if (u.pathname.startsWith('/__defaultVhost__/')) return null;
u.pathname = `/__defaultVhost__${u.pathname}`;
return u.toString();
} catch {
return null;
}
};
const createFlvPlayer = (url) => {
const player = flvjs.createPlayer({ const player = flvjs.createPlayer({
type: 'flv', type: 'flv',
url: flvUrl, url,
isLive: true isLive: true
}, { }, {
enableWorker: true, enableWorker: false,
enableStashBuffer: false, enableStashBuffer: false,
stashInitialSize: 128 stashInitialSize: 128
}); });
@ -32,9 +44,36 @@ function LivePlayer({ room }) {
player.attachMediaElement(video); player.attachMediaElement(video);
player.load(); player.load();
player.play().catch(console.error); player.play().catch(console.error);
return player;
};
// 使 FLV ()
if (flvjs.isSupported()) {
console.log('使用 FLV 播放器');
setPlayerType('FLV');
const altFlvUrl = getAltFlvUrl(flvUrl);
let player = createFlvPlayer(flvUrl);
player.on(flvjs.Events.ERROR, (type, detail) => { player.on(flvjs.Events.ERROR, (type, detail) => {
console.error('FLV 错误:', type, detail); console.error('FLV 错误:', type, detail);
if (!triedAltFlvRef.current && altFlvUrl) {
triedAltFlvRef.current = true;
setError('播放出错,正在切换线路...');
try {
player.destroy();
} catch (e) {
console.error(e);
}
player = createFlvPlayer(altFlvUrl);
playerRef.current = player;
setError(null);
return;
}
setError('播放出错,正在重试...'); setError('播放出错,正在重试...');
// 3 // 3
setTimeout(() => { setTimeout(() => {
@ -48,7 +87,7 @@ function LivePlayer({ room }) {
playerRef.current = player; playerRef.current = player;
} }
// HLS // HLS
else if (Hls.isSupported()) { else if (hlsUrl && Hls.isSupported()) {
console.log('使用 HLS 播放器'); console.log('使用 HLS 播放器');
setPlayerType('HLS'); setPlayerType('HLS');
@ -73,7 +112,7 @@ function LivePlayer({ room }) {
playerRef.current = hls; playerRef.current = hls;
} }
// HLS (Safari) // HLS (Safari)
else if (video.canPlayType('application/vnd.apple.mpegurl')) { else if (hlsUrl && video.canPlayType('application/vnd.apple.mpegurl')) {
console.log('使用原生 HLS'); console.log('使用原生 HLS');
setPlayerType('Native HLS'); setPlayerType('Native HLS');
video.src = hlsUrl; video.src = hlsUrl;
@ -91,7 +130,7 @@ function LivePlayer({ room }) {
playerRef.current = null; playerRef.current = null;
} }
}; };
}, [room]); }, [room?.isLive, flvUrl, hlsUrl]);
if (!room?.isLive) { if (!room?.isLive) {
return ( return (

View File

@ -30,14 +30,14 @@ vhost __defaultVhost__ {
# HTTP-FLV 配置 (低延迟) # HTTP-FLV 配置 (低延迟)
http_remux { http_remux {
enabled on; enabled on;
mount [vhost]/[app]/[stream].flv; mount [app]/[stream].flv;
} }
# HTTP 回调配置 # HTTP 回调配置
http_hooks { http_hooks {
enabled on; enabled on;
on_publish http://api-server:3001/api/srs/on_publish; on_publish http://localhost:3001/api/srs/on_publish;
on_unpublish http://api-server:3001/api/srs/on_unpublish; on_unpublish http://localhost:3001/api/srs/on_unpublish;
} }
# GOP 缓存,提高首屏速度 # GOP 缓存,提高首屏速度

View File

@ -35,7 +35,6 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5", "@babel/generator": "^7.28.5",
@ -1129,7 +1128,6 @@
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
@ -1318,6 +1316,14 @@
"baseline-browser-mapping": "dist/cli.js" "baseline-browser-mapping": "dist/cli.js"
} }
}, },
"node_modules/basic-auth-connect": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/basic-auth-connect/-/basic-auth-connect-1.1.0.tgz",
"integrity": "sha512-rKcWjfiRZ3p5WS9e5q6msXa07s6DaFAMXoyowV+mb2xQG+oYdw2QEUyKi0Xp95JvXzShlM+oGy5QuqSK6TfC1Q==",
"dependencies": {
"tsscmp": "^1.0.6"
}
},
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@ -1399,7 +1405,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.9.0", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759", "caniuse-lite": "^1.0.30001759",
@ -1514,7 +1519,6 @@
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@ -1622,7 +1626,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"color-name": "~1.1.4" "color-name": "~1.1.4"
@ -1635,7 +1638,6 @@
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/combined-stream": { "node_modules/combined-stream": {
@ -1768,6 +1770,14 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/dateformat": {
"version": "4.6.3",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
"integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
"engines": {
"node": "*"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -2281,22 +2291,6 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"ideallyInert": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@ -2444,7 +2438,6 @@
"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",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -2517,6 +2510,14 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/http2-express": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/http2-express/-/http2-express-1.1.0.tgz",
"integrity": "sha512-rEKh5J8KBh76SZ9ejs4q2SQV5X5DA7pMamuGod2ifPLCRcgm8Ru1awbyPPr56JzzMpUY+MKc2NlUxsGpaaqO4Q==",
"engines": {
"node": ">= 20.0.0"
}
},
"node_modules/human-signals": { "node_modules/human-signals": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@ -3511,6 +3512,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@ -3673,6 +3679,28 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mkdirp": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz",
"integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==",
"bin": {
"mkdirp": "dist/cjs/src/bin.js"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -3702,6 +3730,28 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-media-server": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/node-media-server/-/node-media-server-2.7.4.tgz",
"integrity": "sha512-4nsqfukfnI6tY7d1deqBQSj3KywOiNVlPQkY0tLIjF62rzXo1SDXJXgUU+cVs5e06uO4x/55O4SiAaRH3li/Vg==",
"dependencies": {
"basic-auth-connect": "^1.1.0",
"chalk": "^4.1.2",
"dateformat": "^4.6.3",
"express": "^4.21.1",
"http2-express": "^1.0.0",
"lodash": "^4.17.21",
"minimist": "^1.2.8",
"mkdirp": "^2.1.6",
"ws": "^8.18.0"
},
"bin": {
"node-media-server": "bin/app.js"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.27", "version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
@ -4791,7 +4841,6 @@
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"has-flag": "^4.0.0" "has-flag": "^4.0.0"
@ -4867,6 +4916,14 @@
"nodetouch": "bin/nodetouch.js" "nodetouch": "bin/nodetouch.js"
} }
}, },
"node_modules/tsscmp": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
"integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
"engines": {
"node": ">=0.6.x"
}
},
"node_modules/type-detect": { "node_modules/type-detect": {
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
@ -5068,6 +5125,26 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0" "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
} }
}, },
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/y18n": { "node_modules/y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View File

@ -11,6 +11,7 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"express": "^4.18.2", "express": "^4.18.2",
"node-media-server": "^2.7.0",
"uuid": "^9.0.1" "uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
@ -51,7 +52,6 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5", "@babel/generator": "^7.28.5",
@ -1145,7 +1145,6 @@
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
@ -1334,6 +1333,14 @@
"baseline-browser-mapping": "dist/cli.js" "baseline-browser-mapping": "dist/cli.js"
} }
}, },
"node_modules/basic-auth-connect": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/basic-auth-connect/-/basic-auth-connect-1.1.0.tgz",
"integrity": "sha512-rKcWjfiRZ3p5WS9e5q6msXa07s6DaFAMXoyowV+mb2xQG+oYdw2QEUyKi0Xp95JvXzShlM+oGy5QuqSK6TfC1Q==",
"dependencies": {
"tsscmp": "^1.0.6"
}
},
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@ -1415,7 +1422,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.9.0", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759", "caniuse-lite": "^1.0.30001759",
@ -1530,7 +1536,6 @@
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@ -1638,7 +1643,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"color-name": "~1.1.4" "color-name": "~1.1.4"
@ -1651,7 +1655,6 @@
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/combined-stream": { "node_modules/combined-stream": {
@ -1784,6 +1787,14 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/dateformat": {
"version": "4.6.3",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
"integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
"engines": {
"node": "*"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -2459,7 +2470,6 @@
"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",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -2532,6 +2542,14 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/http2-express": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/http2-express/-/http2-express-1.1.0.tgz",
"integrity": "sha512-rEKh5J8KBh76SZ9ejs4q2SQV5X5DA7pMamuGod2ifPLCRcgm8Ru1awbyPPr56JzzMpUY+MKc2NlUxsGpaaqO4Q==",
"engines": {
"node": ">= 20.0.0"
}
},
"node_modules/human-signals": { "node_modules/human-signals": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@ -3526,6 +3544,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@ -3688,6 +3711,28 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mkdirp": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz",
"integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==",
"bin": {
"mkdirp": "dist/cjs/src/bin.js"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -3717,6 +3762,28 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-media-server": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/node-media-server/-/node-media-server-2.7.4.tgz",
"integrity": "sha512-4nsqfukfnI6tY7d1deqBQSj3KywOiNVlPQkY0tLIjF62rzXo1SDXJXgUU+cVs5e06uO4x/55O4SiAaRH3li/Vg==",
"dependencies": {
"basic-auth-connect": "^1.1.0",
"chalk": "^4.1.2",
"dateformat": "^4.6.3",
"express": "^4.21.1",
"http2-express": "^1.0.0",
"lodash": "^4.17.21",
"minimist": "^1.2.8",
"mkdirp": "^2.1.6",
"ws": "^8.18.0"
},
"bin": {
"node-media-server": "bin/app.js"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.27", "version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
@ -4806,7 +4873,6 @@
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"has-flag": "^4.0.0" "has-flag": "^4.0.0"
@ -4882,6 +4948,14 @@
"nodetouch": "bin/nodetouch.js" "nodetouch": "bin/nodetouch.js"
} }
}, },
"node_modules/tsscmp": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
"integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
"engines": {
"node": ">=0.6.x"
}
},
"node_modules/type-detect": { "node_modules/type-detect": {
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
@ -5083,6 +5157,26 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0" "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
} }
}, },
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/y18n": { "node_modules/y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View File

@ -15,6 +15,7 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"express": "^4.18.2", "express": "^4.18.2",
"node-media-server": "^2.7.0",
"uuid": "^9.0.1" "uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,13 +1,59 @@
require('dotenv').config(); require('dotenv').config();
const express = require('express'); const express = require('express');
const cors = require('cors'); const cors = require('cors');
const NodeMediaServer = require('node-media-server');
const roomsRouter = require('./routes/rooms'); const roomsRouter = require('./routes/rooms');
const srsRouter = require('./routes/srs'); const srsRouter = require('./routes/srs');
const errorHandler = require('./middleware/errorHandler'); const errorHandler = require('./middleware/errorHandler');
const roomStore = require('./store/roomStore');
const app = express(); const app = express();
const PORT = process.env.PORT || 3001; const PORT = process.env.PORT || 3001;
const startMediaServer = () => {
const host = process.env.SRS_HOST || 'localhost';
const rtmpPort = Number(process.env.SRS_RTMP_PORT || 1935);
const httpPort = Number(process.env.SRS_HTTP_PORT || 8080);
try {
const nms = new NodeMediaServer({
rtmp: {
port: rtmpPort,
chunk_size: 60000,
gop_cache: true,
ping: 30,
ping_timeout: 60
},
http: {
port: httpPort,
allow_origin: '*'
}
});
nms.on('prePublish', (id, streamPath) => {
const parts = String(streamPath || '').split('/').filter(Boolean);
const streamKey = parts[1];
if (streamKey) roomStore.setLiveStatus(streamKey, true);
});
nms.on('donePublish', (id, streamPath) => {
const parts = String(streamPath || '').split('/').filter(Boolean);
const streamKey = parts[1];
if (streamKey) roomStore.setLiveStatus(streamKey, false);
});
nms.run();
console.log(`Media Server RTMP: rtmp://${host}:${rtmpPort}/live (Stream Key = streamKey)`);
console.log(`Media Server HTTP-FLV: http://${host}:${httpPort}/live/{streamKey}.flv`);
} catch (e) {
console.error('[Media Server] Failed to start embedded media server:', e.message);
console.error('[Media Server] If ports 1935/8080 are in use, stop the occupying process or change SRS_RTMP_PORT/SRS_HTTP_PORT.');
}
};
startMediaServer();
// 中间件 // 中间件
app.use(cors({ app.use(cors({
origin: process.env.CLIENT_URL || 'http://localhost:3000', origin: process.env.CLIENT_URL || 'http://localhost:3000',

View File

@ -3,14 +3,18 @@ const router = express.Router();
const roomStore = require('../store/roomStore'); const roomStore = require('../store/roomStore');
const { validateRoom } = require('../middleware/validate'); const { validateRoom } = require('../middleware/validate');
const { generateStreamUrls } = require('../utils/streamUrl'); const { generateStreamUrls } = require('../utils/streamUrl');
const { getActiveStreamKeys } = require('../utils/srsHttpApi');
// GET /api/rooms - 获取所有房间 // GET /api/rooms - 获取所有房间
router.get('/', (req, res) => { router.get('/', async (req, res) => {
const rooms = roomStore.getAll(); const rooms = roomStore.getAll();
const activeStreamKeys = await getActiveStreamKeys({ app: 'live' });
res.json({ res.json({
success: true, success: true,
data: rooms.map(room => ({ data: rooms.map(room => ({
...room, ...room,
isLive: activeStreamKeys.size ? activeStreamKeys.has(room.streamKey) : room.isLive,
streamUrls: generateStreamUrls(room.streamKey) streamUrls: generateStreamUrls(room.streamKey)
})) }))
}); });
@ -31,7 +35,7 @@ router.post('/', validateRoom, (req, res) => {
}); });
// GET /api/rooms/:id - 获取单个房间 // GET /api/rooms/:id - 获取单个房间
router.get('/:id', (req, res) => { router.get('/:id', async (req, res) => {
const room = roomStore.getById(req.params.id); const room = roomStore.getById(req.params.id);
if (!room) { if (!room) {
@ -45,6 +49,7 @@ router.get('/:id', (req, res) => {
success: true, success: true,
data: { data: {
...room, ...room,
isLive: (await getActiveStreamKeys({ app: 'live' })).has(room.streamKey) || room.isLive,
streamUrls: generateStreamUrls(room.streamKey) streamUrls: generateStreamUrls(room.streamKey)
} }
}); });

View File

@ -0,0 +1,88 @@
const http = require('http');
const https = require('https');
const requestJson = (url, { timeoutMs = 2000 } = {}) => {
return new Promise((resolve, reject) => {
const u = new URL(url);
const lib = u.protocol === 'https:' ? https : http;
const req = lib.request(
{
protocol: u.protocol,
hostname: u.hostname,
port: u.port,
path: `${u.pathname}${u.search}`,
method: 'GET'
},
(res) => {
let raw = '';
res.setEncoding('utf8');
res.on('data', (chunk) => (raw += chunk));
res.on('end', () => {
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
return reject(new Error(`SRS HTTP API status ${res.statusCode}`));
}
try {
resolve(JSON.parse(raw || '{}'));
} catch (e) {
reject(new Error('Invalid JSON from SRS HTTP API'));
}
});
}
);
req.on('error', reject);
req.setTimeout(timeoutMs, () => {
req.destroy(new Error('SRS HTTP API request timeout'));
});
req.end();
});
};
const normalizeStreamName = (s) => {
if (!s) return null;
if (typeof s === 'string') return s;
return s.stream || s.name || s.id || null;
};
const isPublishActive = (streamObj) => {
const publish = streamObj && streamObj.publish;
if (!publish) return false;
if (typeof publish.active === 'boolean') return publish.active;
return Boolean(publish.cid);
};
let warnedOnce = false;
const getActiveStreamKeys = async ({ app = 'live' } = {}) => {
const host = process.env.SRS_HOST || 'localhost';
const apiPort = process.env.SRS_API_PORT || 1985;
const url = `http://${host}:${apiPort}/api/v1/streams?count=100`;
try {
const payload = await requestJson(url, { timeoutMs: 1500 });
const streams = payload.streams || payload.data?.streams || [];
const active = new Set();
for (const s of streams) {
if (app && s.app && s.app !== app) continue;
if (!isPublishActive(s)) continue;
const name = normalizeStreamName(s);
if (name) active.add(name);
}
return active;
} catch (e) {
if (!warnedOnce) {
warnedOnce = true;
console.warn(`[SRS] HTTP API unavailable at ${url}. Will fallback to callbacks/in-memory status.`);
}
return new Set();
}
};
module.exports = {
getActiveStreamKeys
};

View File

@ -6,7 +6,7 @@ const generateStreamUrls = (streamKey) => {
return { return {
// 推流地址 (给主播用) // 推流地址 (给主播用)
rtmp: `rtmp://${host}:${rtmpPort}/live/${streamKey}`, rtmp: `rtmp://${host}:${rtmpPort}/live`,
// 播放地址 (给观众用) // 播放地址 (给观众用)
flv: `http://${host}:${httpPort}/live/${streamKey}.flv`, flv: `http://${host}:${httpPort}/live/${streamKey}.flv`,

13
package-lock.json generated Normal file
View File

@ -0,0 +1,13 @@
{
"name": "zhibo_1",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "zhibo_1",
"version": "1.0.0",
"license": "ISC"
}
}
}

12
package.json Normal file
View File

@ -0,0 +1,12 @@
{
"name": "zhibo_1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

1
query Normal file
View File

@ -0,0 +1 @@
vmcompute