首先进行网页展示

一、需要准备的东西
①首先,利用SRS搭建一个直播推流平台。什么是SRS?SRS(Simple Realtime Server)是一个简单高效的实时视频服务器,支持RTMP、WebRTC、HLS、HTTP-FLV、SRT等多种实时流媒体协议。
②OBS推流软件(免费的),可以在steam进行下载或者访问官网下载。
③准备好前端的HTML源码,可以利用deepseek等AI工具帮你生成(假设你和我一样不会代码的话)。AI生成的HTML可能不会一步到位,需要让AI一步一步微调,所以需要很多耐心。
④利用node.js实现实时聊天功能。后端脚本同样也是通过AI来生成。
⑤宝塔面板管理网页。
二、SRS平台搭建与OBS推流
进入飞牛nas主页,点击docker-镜像仓库-搜索SRS。

选择第一个ossrs/srs下载,然后来到本地镜像,选择刚刚下载的ossrs/srs运行,按需勾选开机自启。

下一步,端口设置,1935是SRS直播监听端口,也就是后续填写推流地址时的端口。8080是SRS管理页面端口。以上端口如有冲突,请自行更改端口解决。

点击下一步并创建,选择容器并启动SRS。

接下来访问srs管理页面,使用你的飞牛nasIP加8080端口访问,例如:192.168.31.40:8080。

使用OBS推流至它给的rtmp地址,并点击开始直播检验srs是否正常运行。

依次点击SRS管理页面的SRS播放器、liveplayer。

点击play,如果有直播画面,表示SRS与OBS均正常运行,记住URL链接稍后网页HTML将要用到。

三、用AI生成源码
访问deepseek官网,点击开始对话,输入:帮我生成HTML并输出完整代码,要求网页实现对下面链接的播放以及一个实时聊天栏。实时聊天功能使用node.js做后端,并生成完整的对应的server.js文件。http://192.168.31.40:8080/live/livestream.flv
等待片刻,会得到完整的HTML与server.js代码,由于与AI提要求并修改初始源码的过程太过繁琐,这里不做展示了,下面直接放出与AI多次沟通小修之后的代码,后续可以复制粘贴下来替换掉相应的地址即可使用。
HTML:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>直播与聊天室</title>
<script src="https://cdn.bootcdn.net/ajax/libs/flv.js/1.6.2/flv.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.4/socket.io.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;500&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Noto Sans SC', sans-serif;
}
body {
background: #f0f2f5;
-webkit-user-select: none;
user-select: none;
}
/* 新增点击特效样式 */
.click-effect {
position: fixed;
width: 12px;
height: 12px;
background: #1890ff;
border-radius: 50%;
pointer-events: none;
animation: clickAnim 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28);
transform: translate(-50%, -50%);
will-change: transform, opacity;
z-index: 9999;
}
.click-effect::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
border: 2px solid rgba(24, 144, 255, 0.5);
border-radius: 50%;
animation: ripple 0.6s ease-out;
}
@keyframes clickAnim {
0% {
transform: scale(0) translate(-50%, -50%);
opacity: 0.8;
}
50% {
transform: scale(1.1) translate(-50%, -50%);
opacity: 1;
}
100% {
transform: scale(2.8) translate(-50%, -50%);
opacity: 0;
}
}
@keyframes ripple {
from {
transform: scale(0.6);
opacity: 1;
}
to {
transform: scale(3);
opacity: 0;
}
}
/* 原有样式 */
.container {
display: flex;
max-width: 1400px;
margin: 260px auto;
gap: 20px;
padding: 0 20px;
}
.video-container {
flex: 3;
background: #000;
border-radius: 12px;
overflow: hidden;
position: relative;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
#videoElement {
width: 100%;
height: auto;
aspect-ratio: 16/9;
}
.live-badge {
position: absolute;
top: 15px;
left: 15px;
background: #ff4d4d;
color: white;
padding: 6px 12px;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
}
.chat-container {
flex: 1;
background: white;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
}
.chat-header {
padding: 16px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-header h2 {
font-size: 18px;
color: #333;
}
.online-count {
color: #666;
font-size: 14px;
}
.chat-messages {
flex: 1;
padding: 16px;
overflow-y: auto;
max-height: 500px;
}
.message-item {
margin-bottom: 12px;
padding: 8px 12px;
background: #f8f9fa;
border-radius: 6px;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.chat-input {
padding: 16px;
border-top: 1px solid #eee;
display: flex;
gap: 10px;
}
.chat-input input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 6px;
outline: none;
}
.chat-input button {
padding: 10px 20px;
background: #1890ff;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s;
}
.chat-input button:hover {
background: #40a9ff;
}
@media (max-width: 768px) {
.container {
flex-direction: column;
}
.video-container {
order: 1;
}
}
</style>
</head>
<body>
<div class="container">
<!-- 左侧直播区域 -->
<div class="video-container">
<video id="videoElement" controls muted autoplay></video>
<div class="live-badge">直播</div>
</div>
<!-- 右侧聊天区域 -->
<div class="chat-container">
<div class="chat-header">
<h2>聊天室</h2>
<div class="online-count">在线: <span id="onlineCount">0</span></div>
</div>
<div class="chat-messages" id="chatMessages"></div>
<div class="chat-input">
<input type="text" id="messageInput" placeholder="输入消息...">
<button onclick="sendMessage()">发送</button>
</div>
</div>
</div>
<script>
// 新增点击特效功能
function createClickEffect(e) {
const effect = document.createElement('div');
effect.className = 'click-effect';
const event = e.touches ? e.touches[0] : e;
const posX = event.clientX;
const posY = event.clientY;
effect.style.cssText = `
left: ${posX}px;
top: ${posY}px;
`;
document.body.appendChild(effect);
const removeEffect = () => {
effect.style.animation = 'none';
effect.remove();
};
effect.addEventListener('animationend', removeEffect);
effect.addEventListener('webkitAnimationEnd', removeEffect);
}
// 事件监听(兼容处理)
if (window.PointerEvent) {
document.addEventListener('pointerdown', createClickEffect);
} else {
document.addEventListener('mousedown', createClickEffect);
document.addEventListener('touchstart', createClickEffect);
}
// 原有脚本功能
// 初始化视频播放器
if (flvjs.isSupported()) {
const videoElement = document.getElementById('videoElement');
const flvPlayer = flvjs.createPlayer({
type: 'flv',
url: '替换为你的SRS直播URL',
});
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
flvPlayer.play();
}
// WebSocket 实时聊天
const socket = io('替换为你的后端服务地址', {
transports: ['websocket']
});
const randomName = `游客${Math.floor(Math.random() * 1000)}`;
socket.emit('login', randomName);
socket.on('connect', () => {
console.log('已连接到服务器');
});
socket.on('connect_error', (err) => {
console.error('连接失败:', err);
});
socket.on('message', (msg) => {
appendMessage(`${msg.time} [${msg.user}]:${msg.text}`);
});
socket.on('updateUsers', (users) => {
document.getElementById('onlineCount').textContent = users.length;
});
function sendMessage() {
const input = document.getElementById('messageInput');
const text = input.value.trim();
if (text) {
socket.emit('message', text);
input.value = '';
}
}
function appendMessage(content) {
const messagesDiv = document.getElementById('chatMessages');
const msgElement = document.createElement('div');
msgElement.className = 'message-item';
msgElement.innerHTML = `
<div style="color:#666; font-size:12px;">${escapeHtml(content)}</div>
`;
messagesDiv.appendChild(msgElement);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
document.getElementById('messageInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendMessage();
});
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
</script>
</body>
</html>
server.js:
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: "*", // 生产环境应限制为具体域名
methods: ["GET", "POST"]
}
});
// 存储在线用户
let onlineUsers = new Map(); // 结构:{ socketId: { id, name } }
io.on('connection', (socket) => {
console.log(`新用户连接: ${socket.id}`);
// 新用户登录
socket.on('login', (username) => {
onlineUsers.set(socket.id, {
id: socket.id,
name: username || `用户${Math.random().toString(36).substr(2, 4)}`
});
updateOnlineUsers();
});
// 接收消息
socket.on('message', (msg) => {
const user = onlineUsers.get(socket.id);
const messageData = {
user: user.name,
text: msg,
time: new Date().toLocaleTimeString()
};
io.emit('message', messageData); // 广播给所有人
});
// 断开连接
socket.on('disconnect', () => {
onlineUsers.delete(socket.id);
updateOnlineUsers();
});
// 更新在线用户列表
function updateOnlineUsers() {
io.emit('updateUsers', Array.from(onlineUsers.values()));
}
});
server.listen(3000, () => {
console.log('WebSocket服务器运行在 http://localhost:3000');
});
以上两段代码先分别保存在两个文本文件中备用,推荐使用VSCode或者notapad++查看编辑代码防止出现语法错误或格式错误。
四、网页前端与后端部署
①前端部署
点开飞牛nas的docker镜像仓库搜索baota选择第一个pch18/baota下载。

下载完成在本地镜像中找到pch18/baota并运行,勾选开机自启,下一步直到启动服务。访问宝塔面板,你的飞牛nasIP+8888端口,例如192.168.31.40:8888

登陆后点击右侧网站,创建站点,如果有域名那么填写你的一个子域,没有域名就随意了。例如你未来想给该网站套的一个子域名:live.fnnas.com:1145

然后点击创建好的网站名,添加你的内网IP:端口

如果有外网访问需求的话,后续可以使用lucky做反向代理使用域名直接访问。只是搭建着内网玩玩的话,你的域名可以选择删除(删不删不影响)live.fnnas.com:1145。
接着我们点击根目录

再点击index.html文件右侧的编辑按钮

将先前的HTML代码复制粘贴进去,并更改图中这两项。
两项格式为:
http://192.168.31.40/live/livestream.flv
http://192.168.31.40:3000
其中192.168.31.40更改为你的内网nasIP,如果做了域名解析,还可以改成你的域名。

②后端部署
打开飞牛nas的ssh功能

进入宝塔页面选择终端并添加服务器,输入你的IP地址,飞牛管理员账号密码并保存。

输入
sudo -i
输入管理员密码(此步输入密码不会显示,输完直接回车即可)

输入下列命令安装node.js
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
等待安装完成时,我们先去飞牛nas新建文件夹取名chat-service,并右键文件夹复制文件夹原始路径。建议文件夹建在固态硬盘中,避免影响机械硬盘休眠。

在电脑上新建txt文件取名为server,双击打开并复制粘贴先前的server.js代码并保存、修改后缀名txt为js。


上传这个js文件到刚刚飞牛nas上新建的chat-service文件夹。
返回宝塔终端界面,输入下列代码安装依赖。
cd /volume1/webapps/chat-service
npm init -y
npm install express socket.io
依赖安装完成后,输入下列代码安装PM2(管理进程)ps:我基本没看过管理进程,不过不能没有。
sudo npm install -g pm2
启动服务
sudo pm2 start server.js --name "chat-service"
设置服务开机自启
sudo pm2 save
sudo pm2 startup
不出差错的话现在浏览器打开你的nasIP+3000端口显示如下界面则表示服务成功开起了。

最后,我们打开飞牛nasIP+1145端口实现直播与实时聊天~

五、结语
利用SRS与OBS推拉直播流、以及利用AI编写代码实现自己的直播网页,做成这个网站,这对我这个零代码人士来说是非常有成就感的。
由于篇幅太长了,所以我只分享了内网使用的部分,想让外网的朋友们进入直播间观看聊天还需要利用内网穿透或者有公网IP的话使用ddns就可以很方便的访问了。
欢迎各位大佬批评指正,做的不好的轻喷~