使用nodejs简单配置后端
// 引入必要的库
import webSocketStream from 'websocket-stream' // WebSocket流处理库
import ffmpegInstaller from '@ffmpeg-installer/ffmpeg' // FFmpeg安装器
import ffmpeg from 'fluent-ffmpeg' // FFmpeg命令构建器
import { WebSocketServer } from 'ws' // WebSocket服务器实现
// 设置FFmpeg路径
ffmpeg.setFfmpegPath(ffmpegInstaller.path)
// 创建WebSocket服务器配置
const WS_CONFIG = {
port: 8888,
perMessageDeflate: false // 禁用压缩以降低延迟
}
// 初始化WebSocket服务器
const wss = new WebSocketServer(WS_CONFIG)
// 监听客户端连接事件
wss.on('connection', handleConnection)
/**
* 处理客户端连接
* @param {WebSocket} ws - WebSocket客户端实例
* @param {Request} req - HTTP请求对象
*/
function handleConnection(ws, req) {
// 从URL路径获取视频流地址(去掉前导斜杠)
const streamUrl = req.url.slice(1)
// 创建WebSocket二进制流
const wsStream = webSocketStream(ws, {
binary: true, // 启用二进制传输
highWaterMark: 1024 * 1024 // 增加缓冲区大小
})
// 配置FFmpeg转码命令
const ffmpegCommand = ffmpeg(streamUrl)
// 输入参数配置
.addInputOption(
'-analyzeduration', '100000', // 增加分析时长
'-max_delay', '1000000' // 增加最大延迟
)
// 事件监听
.on('start', () => console.log(`转码开始: ${streamUrl}`))
.on('codecData', (data) => console.log('编解码信息:', data))
.on('error', (err) => {
console.error('转码错误:', err.message)
wsStream.end() // 出错时关闭流
})
.on('end', () => {
console.log('转码结束')
wsStream.end() // 正常结束时关闭流
})
// 输出配置
.outputFormat('flv') // FLV容器格式
.videoCodec('copy') // 视频流直接复制(不重新编码)
// .noAudio() // 可选: 禁用音频输出
// 监听WebSocket关闭事件
wsStream.on('close', () => {
console.log('客户端断开连接')
ffmpegCommand.kill('SIGKILL') // 强制终止FFmpeg进程
})
try {
// 将FFmpeg输出管道连接到WebSocket流
ffmpegCommand.pipe(wsStream)
} catch (error) {
console.error('管道连接失败:', error)
wsStream.end()
}
}
前端多个播放器
import { onMounted, ref, nextTick } from 'vue'
import flvjs from 'flv.js';
const initFlvPlayer = (videoElement, url) => {
if (url.startsWith('rtmp://')) {
if (flvjs.isSupported()) {
const player = flvjs.createPlayer(
{
type: "flv",
url: "ws://localhost:8088/" + url,
isLive: true,
}
)
player.on(flvjs.Events.ERROR, (errType, errDetail) => {
console.error("播放错误:", errType, errDetail);
// 尝试重新连接
setTimeout(() => {
destroyPlayer(player); //销毁
initFlvPlayer(videoElement, url);
}, 3000);
});
player.attachMediaElement(videoElement)
player.load()
player.play()
return player
}
}
}
// 销毁播放器
const destroyPlayer = (player) => {
try {
if (player && typeof player.destroy === 'function') {
player.pause()
player.unload()
player.detachMediaElement()
player.destroy()
}
} catch (e) {
console.error('销毁播放器时出错:', e)
}
}
onMounted(() => {
//需要监控多个视频流
//测试链接可用
//rtmp://ns8.indexforce.com/home/mystream
//rtsp://stream.strba.sk:1935/strba/VYHLAD_JAZERO.stream
nextTick(() => {
const videoElements = document.querySelectorAll('.task-data-item video')
videoElements.forEach((videoElement, index) => {
const task = taskDataSum.value[index]
if (task && task.videoUrl) {
// if (task.videoUrl.includes('rtmp://') || task.videoUrl.includes('rtsp://')) {
initFlvPlayer(videoElement, task.videoUrl)
// }
}
})
})
})