基于HTML与Java的简易在线会议系统实现
概述
实现一个类似腾讯会议基本功能的演示系统,包含视频会议、屏幕共享、文字聊天等核心功能。这个实现使用HTML作为前端界面,Java作为后端服务,WebRTC技术实现实时通信。
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>
<style>
:root {
--primary-color: #2d8cf0;
--secondary-color: #19be6b;
--danger-color: #ed4014;
--dark-color: #17233d;
--light-color: #f8f8f9;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #2d8cf0);
color: var(--light-color);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
display: flex;
flex-direction: column;
height: 95vh;
}
header {
text-align: center;
padding: 20px;
background: rgba(23, 35, 61, 0.8);
border-radius: 12px;
margin-bottom: 20px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
h1 {
font-size: 2.5rem;
margin-bottom: 10px;
background: linear-gradient(90deg, #fff, #2d8cf0);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.subtitle {
font-size: 1.1rem;
opacity: 0.9;
}
.main-content {
display: flex;
flex: 1;
gap: 20px;
}
.video-section {
flex: 3;
display: flex;
flex-direction: column;
background: rgba(23, 35, 61, 0.8);
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.video-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
flex: 1;
margin-bottom: 20px;
}
.video-item {
background: #1a1f2d;
border-radius: 8px;
overflow: hidden;
position: relative;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4);
}
.video-item video {
width: 100%;
height: 100%;
object-fit: cover;
background: #000;
}
.user-info {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.7);
padding: 8px 12px;
font-size: 0.9rem;
}
.controls {
display: flex;
justify-content: center;
gap: 15px;
padding: 15px;
background: rgba(23, 35, 61, 0.9);
border-radius: 8px;
}
.control-btn {
width: 60px;
height: 60px;
border-radius: 50%;
border: none;
background: var(--dark-color);
color: white;
font-size: 1.5rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.control-btn:hover {
transform: translateY(-3px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.4);
}
.control-btn.active {
background: var(--primary-color);
}
.control-btn.end-call {
background: var(--danger-color);
}
.control-btn.end-call:hover {
background: #c5300c;
}
.side-panel {
flex: 1;
display: flex;
flex-direction: column;
gap: 20px;
}
.participants {
background: rgba(23, 35, 61, 0.8);
border-radius: 12px;
padding: 20px;
flex: 1;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.participants h2 {
margin-bottom: 15px;
font-size: 1.3rem;
color: var(--primary-color);
padding-bottom: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.participant-list {
list-style: none;
max-height: 300px;
overflow-y: auto;
}
.participant-list li {
padding: 10px 15px;
background: rgba(255, 255, 255, 0.05);
margin-bottom: 8px;
border-radius: 6px;
display: flex;
align-items: center;
}
.participant-list li:before {
content: "•";
color: var(--secondary-color);
font-size: 1.5rem;
margin-right: 10px;
}
.chat {
background: rgba(23, 35, 61, 0.8);
border-radius: 12px;
padding: 20px;
flex: 2;
display: flex;
flex-direction: column;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.chat h2 {
margin-bottom: 15px;
font-size: 1.3rem;
color: var(--primary-color);
padding-bottom: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.chat-messages {
flex: 1;
overflow-y: auto;
margin-bottom: 15px;
padding: 10px;
background: rgba(0, 0, 0, 0.2);
border-radius: 6px;
}
.message {
margin-bottom: 12px;
padding: 8px 12px;
border-radius: 6px;
background: rgba(45, 140, 240, 0.2);
}
.message.self {
background: rgba(25, 190, 107, 0.2);
text-align: right;
}
.message-sender {
font-weight: bold;
font-size: 0.9rem;
margin-bottom: 4px;
color: var(--primary-color);
}
.message.self .message-sender {
color: var(--secondary-color);
}
.chat-input {
display: flex;
gap: 10px;
}
.chat-input input {
flex: 1;
padding: 12px 15px;
border: none;
border-radius: 6px;
background: rgba(255, 255, 255, 0.1);
color: white;
font-size: 1rem;
}
.chat-input input:focus {
outline: none;
background: rgba(255, 255, 255, 0.15);
}
.chat-input button {
padding: 12px 20px;
border: none;
border-radius: 6px;
background: var(--primary-color);
color: white;
cursor: pointer;
transition: background 0.3s;
}
.chat-input button:hover {
background: #1a7ad9;
}
footer {
text-align: center;
padding: 20px;
margin-top: 20px;
font-size: 0.9rem;
opacity: 0.8;
}
.meeting-id {
display: flex;
justify-content: center;
gap: 10px;
margin: 15px 0;
}
.meeting-id span {
background: rgba(255, 255, 255, 0.1);
padding: 8px 15px;
border-radius: 6px;
font-family: monospace;
}
@media (max-width: 900px) {
.main-content {
flex-direction: column;
}
.video-container {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>简易在线会议系统</h1>
<p class="subtitle">使用HTML前端 + Java后端实现类似腾讯会议功能</p>
<div class="meeting-id">
<div>会议ID: <span>852-741-963</span></div>
<div>参会人数: <span>4</span></div>
</div>
</header>
<div class="main-content">
<div class="video-section">
<div class="video-container">
<div class="video-item">
<video id="localVideo" autoplay muted></video>
<div class="user-info">我 (主持人)</div>
</div>
<div class="video-item">
<video id="remoteVideo1" autoplay></video>
<div class="user-info">张工程师</div>
</div>
<div class="video-item">
<video id="remoteVideo2" autoplay></video>
<div class="user-info">李设计师</div>
</div>
<div class="video-item">
<video id="remoteVideo3" autoplay></video>
<div class="user-info">王产品经理</div>
</div>
</div>
<div class="controls">
<button class="control-btn active" id="micToggle">🎤</button>
<button class="control-btn active" id="cameraToggle">📷</button>
<button class="control-btn" id="screenShare">🖥️</button>
<button class="control-btn end-call" id="endCall">📞</button>
</div>
</div>
<div class="side-panel">
<div class="participants">
<h2>参会人员 (4)</h2>
<ul class="participant-list">
<li>我 (主持人)</li>
<li>张工程师</li>
<li>李设计师</li>
<li>王产品经理</li>
</ul>
</div>
<div class="chat">
<h2>会议聊天</h2>
<div class="chat-messages">
<div class="message">
<div class="message-sender">系统消息</div>
<div>欢迎使用简易会议系统!会议已开始</div>
</div>
<div class="message">
<div class="message-sender">张工程师</div>
<div>大家能听到我说话吗?</div>
</div>
<div class="message self">
<div class="message-sender">我</div>
<div>可以听到,声音很清晰</div>
</div>
<div class="message">
<div class="message-sender">李设计师</div>
<div>我共享一下设计稿,大家看看</div>
</div>
<div class="message">
<div class="message-sender">王产品经理</div>
<div>这个功能需要在下周三前完成</div>
</div>
</div>
<div class="chat-input">
<input type="text" id="messageInput" placeholder="输入消息...">
<button id="sendMessage">发送</button>
</div>
</div>
</div>
</div>
<footer>
<p>简易在线会议系统 | HTML + Java + WebRTC 实现 | © 2023 会议技术演示</p>
</footer>
</div>
<script>
// 模拟WebRTC功能
document.addEventListener('DOMContentLoaded', function() {
const micToggle = document.getElementById('micToggle');
const cameraToggle = document.getElementById('cameraToggle');
const screenShare = document.getElementById('screenShare');
const endCall = document.getElementById('endCall');
const messageInput = document.getElementById('messageInput');
const sendMessage = document.getElementById('sendMessage');
const chatMessages = document.querySelector('.chat-messages');
// 模拟视频流
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
const localVideo = document.getElementById('localVideo');
localVideo.srcObject = stream;
})
.catch(err => {
console.error('访问媒体设备失败:', err);
});
// 模拟远程视频流
const remoteVideos = [
document.getElementById('remoteVideo1'),
document.getElementById('remoteVideo2'),
document.getElementById('remoteVideo3')
];
// 模拟远程视频流
setTimeout(() => {
remoteVideos.forEach(video => {
video.srcObject = document.getElementById('localVideo').srcObject;
});
}, 1000);
// 麦克风控制
micToggle.addEventListener('click', function() {
this.classList.toggle('active');
console.log('麦克风状态:', this.classList.contains('active') ? '开启' : '关闭');
});
// 摄像头控制
cameraToggle.addEventListener('click', function() {
this.classList.toggle('active');
const localVideo = document.getElementById('localVideo');
localVideo.srcObject.getVideoTracks()[0].enabled = this.classList.contains('active');
console.log('摄像头状态:', this.classList.contains('active') ? '开启' : '关闭');
});
// 屏幕共享
screenShare.addEventListener('click', function() {
console.log('屏幕共享功能已启动');
alert('屏幕共享功能已启动(模拟)');
this.classList.add('active');
// 模拟屏幕共享
const screenShareElement = document.createElement('div');
screenShareElement.innerHTML = `
<div style="position:absolute; top:20px; right:20px; width:300px; height:200px; background:#000; border:2px solid #2d8cf0; border-radius:8px; overflow:hidden; z-index:100;">
<div style="position:absolute; top:0; left:0; right:0; background:#2d8cf0; color:white; padding:5px; font-size:12px;">屏幕共享中</div>
<div style="display:flex; align-items:center; justify-content:center; height:100%; color:#aaa;">屏幕共享内容</div>
</div>
`;
document.body.appendChild(screenShareElement);
});
// 结束通话
endCall.addEventListener('click', function() {
if (confirm('确定要结束会议吗?')) {
console.log('会议已结束');
alert('会议已结束,即将关闭页面(模拟)');
// 在实际应用中,这里会关闭所有媒体流并断开连接
}
});
// 发送消息
sendMessage.addEventListener('click', function() {
const message = messageInput.value.trim();
if (message) {
addMessage('我', message, true);
messageInput.value = '';
// 模拟回复
if (message.toLowerCase().includes('你好')) {
setTimeout(() => {
addMessage('张工程师', '你好!会议进行得怎么样?', false);
}, 1000);
}
}
});
// 按回车发送消息
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage.click();
}
});
function addMessage(sender, text, isSelf) {
const messageElement = document.createElement('div');
messageElement.classList.add('message');
if (isSelf) messageElement.classList.add('self');
messageElement.innerHTML = `
<div class="message-sender">${sender}</div>
<div>${text}</div>
`;
chatMessages.appendChild(messageElement);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
});
</script>
</body>
</html>
技术实现说明
前端实现 (HTML/JavaScript)
-
用户界面:
- 视频会议区域(本地和远程视频)
- 控制面板(麦克风、摄像头、屏幕共享、结束会议)
- 参会人员列表
- 文字聊天功能
-
核心功能:
- 使用WebRTC API实现音视频通信
getUserMedia
获取摄像头和麦克风权限RTCPeerConnection
建立对等连接RTCDataChannel
实现文字聊天功能- 屏幕共享功能(使用
getDisplayMedia
)
后端实现 (Java)
-
信令服务器:
- 使用Spring Boot构建WebSocket服务器
- 处理客户端之间的信令交换(SDP/ICE)
- 房间管理(创建/加入/离开会议)
-
主要功能:
java// 示例:WebSocket信令处理 @ServerEndpoint("/signaling") public class SignalingEndpoint { @OnOpen public void onOpen(Session session) { // 新连接建立 } @OnMessage public void onMessage(String message, Session session) { // 处理信令消息:offer/answer/candidate } @OnClose public void onClose(Session session) { // 连接关闭处理 } } // 示例:会议管理 @RestController @RequestMapping("/api/meeting") public class MeetingController { @PostMapping("/create") public ResponseEntity<String> createMeeting() { // 创建新会议,返回会议ID } @PostMapping("/join") public ResponseEntity<String> joinMeeting(@RequestParam String meetingId) { // 加入指定会议 } }
-
媒体服务器:
- 使用Kurento或Mediasoup处理多人会议中的媒体流转发
- 实现SFU架构,减轻客户端压力
系统架构
前端 (HTML/JS)
│
│ WebSocket
▼
Java后端 (Spring Boot)
│
│ 信令交换
▼
WebRTC P2P连接 / 媒体服务器
关键功能实现
-
视频会议:
- 使用WebRTC建立点对点连接
- 使用STUN/TURN服务器解决NAT穿透问题
-
屏幕共享:
- 使用
navigator.mediaDevices.getDisplayMedia()
获取屏幕流 - 将屏幕流添加到RTCPeerConnection中
- 使用
-
文字聊天:
- 使用RTCDataChannel实现点对点聊天
- 或通过WebSocket服务器转发消息
-
会议管理:
- 后端维护会议房间状态
- 处理用户加入/离开事件
部署与扩展
-
部署要求:
- HTTPS环境(WebRTC要求)
- STUN/TURN服务器
- 媒体服务器(用于多人会议)
-
扩展功能:
- 会议录制
- 虚拟背景
- 会议白板
- 文件共享
- 会议控制(静音、踢人)
以上实现展示了使用HTML和Java构建在线会议系统的基本架构和核心功能。实际开发中还需要考虑网络优化、安全性、错误处理等更多细节问题。