/* 监控摄像头 RTSP 地址拼装 / 反解析(后台频道表单与测试脚本共用) * * 支持品牌: * hikvision 海康威视 / 萤石 rtsp://u:p@ip:554/Streaming/Channels/{通道}01 主码流 / {通道}02 子码流 * dahua 大华 rtsp://u:p@ip:554/cam/realmonitor?channel={通道}&subtype=0 主 / 1 子 * uniview 宇视 rtsp://u:p@ip:554/video1 主 / video2 子 * tplink TP-LINK / 水星 rtsp://u:p@ip:554/stream1 主 / stream2 子 * custom 其他 / 手动路径 rtsp://u:p@ip:554/{路径} */ ;(function (root) { const BRANDS = [ { key: 'hikvision', name: '海康威视 / 萤石' }, { key: 'dahua', name: '大华' }, { key: 'uniview', name: '宇视' }, { key: 'tplink', name: 'TP-LINK / 水星' }, { key: 'custom', name: '其他(手动填路径)' } ] const enc = (s) => String(s == null ? '' : s).trim() /** * 拼装 RTSP 地址 * o = { brand, host, port, user, pass, channel, stream('main'|'sub'), path } * 返回 { ok:true, url } 或 { ok:false, error } */ function build(o) { const brand = enc(o.brand) || 'hikvision' const host = enc(o.host) const port = enc(o.port) || '554' const user = enc(o.user) const pass = enc(o.pass) const ch = Math.max(1, Math.floor(Number(o.channel) || 1)) const stream = o.stream === 'sub' ? 'sub' : 'main' const path = enc(o.path).replace(/^\/+/, '') if (!host) return { ok: false, error: '请填写摄像头 IP 地址' } if (!/^[a-zA-Z0-9._-]+$/.test(host)) return { ok: false, error: `IP 地址格式不正确:${host}` } if (!/^\d{1,5}$/.test(port) || Number(port) > 65535) return { ok: false, error: `端口格式不正确:${port}` } if (brand === 'custom' && !path) return { ok: false, error: '「其他」品牌需要填写 RTSP 路径(如 Streaming/Channels/101)' } let suffix if (brand === 'hikvision') suffix = `/Streaming/Channels/${ch}0${stream === 'sub' ? 2 : 1}` else if (brand === 'dahua') suffix = `/cam/realmonitor?channel=${ch}&subtype=${stream === 'sub' ? 1 : 0}` else if (brand === 'uniview') suffix = `/video${stream === 'sub' ? 2 : 1}` else if (brand === 'tplink') suffix = `/stream${stream === 'sub' ? 2 : 1}` else suffix = `/${path}` const cred = user ? `${user}:${pass}@` : '' return { ok: true, url: `rtsp://${cred}${host}:${port}${suffix}` } } /** * 反解析:把 rtsp:// 地址还原为表单字段(品牌识别 best-effort) * 返回 { brand, host, port, user, pass, channel, stream, path } */ function parse(url) { const out = { brand: 'custom', host: '', port: '554', user: '', pass: '', channel: 1, stream: 'main', path: '' } const m = /^rtsp:\/\/(?:([^:\/@]+)(?::([^\/@]*))?@)?([^:\/@]+)(?::(\d{1,5}))?(?:\/(.*))?$/i.exec(String(url || '').trim()) if (!m) return out out.user = m[1] || '' out.pass = m[2] || '' out.host = m[3] || '' if (m[4]) out.port = m[4] const tail = m[5] || '' let mm if ((mm = /^Streaming\/Channels\/(\d+)0([12])$/i.exec(tail))) { out.brand = 'hikvision' out.channel = Math.max(1, parseInt(mm[1], 10) || 1) out.stream = mm[2] === '2' ? 'sub' : 'main' } else if ((mm = /^cam\/realmonitor\?channel=(\d+)&subtype=([01])$/i.exec(tail))) { out.brand = 'dahua' out.channel = Math.max(1, parseInt(mm[1], 10) || 1) out.stream = mm[2] === '1' ? 'sub' : 'main' } else if ((mm = /^video([12])$/i.exec(tail))) { out.brand = 'uniview' out.stream = mm[1] === '2' ? 'sub' : 'main' } else if ((mm = /^stream([12])$/i.exec(tail))) { out.brand = 'tplink' out.stream = mm[1] === '2' ? 'sub' : 'main' } else { out.brand = 'custom' out.path = tail } return out } const api = { BRANDS, build, parse } if (typeof module !== 'undefined' && module.exports) module.exports = api root.CameraURL = api })(typeof window !== 'undefined' ? window : globalThis)