meeting / app.py
mistpe's picture
Upload 3 files
805637c verified
from flask import Flask, render_template, request, jsonify
from flask_socketio import SocketIO, emit, join_room, leave_room
import os
import uuid
from datetime import datetime
import logging
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
socketio = SocketIO(app, cors_allowed_origins="*")
# 内存中存储活跃的房间和参与者
active_rooms = {} # {room_id: {'created_at': timestamp, 'participants': {user_id: user_data}}}
@app.route('/')
def index():
"""提供主页"""
return render_template('index.html')
@app.route('/api/room', methods=['POST'])
def create_room():
"""创建新房间"""
room_id = str(uuid.uuid4())[:8] # 生成短的唯一ID
active_rooms[room_id] = {
'created_at': datetime.now().isoformat(),
'participants': {}
}
logger.info(f"创建房间: {room_id}")
return jsonify({'roomId': room_id})
@app.route('/api/room/<room_id>', methods=['GET'])
def check_room(room_id):
"""检查房间是否存在"""
if room_id in active_rooms:
return jsonify({
'exists': True,
'participantCount': len(active_rooms[room_id]['participants'])
})
return jsonify({'exists': False})
# Socket.IO 事件处理器
@socketio.on('connect')
def handle_connect():
"""处理客户端连接"""
logger.info(f"客户端连接: {request.sid}")
emit('connected', {'sid': request.sid})
@socketio.on('disconnect')
def handle_disconnect():
"""处理客户端断开连接"""
logger.info(f"客户端断开连接: {request.sid}")
# 找到并从任何房间中移除用户
for room_id, room_data in list(active_rooms.items()):
for user_id, user_data in list(room_data['participants'].items()):
if user_data.get('sid') == request.sid:
# 通知房间中的其他人
emit('user_disconnected', {'userId': user_id}, room=room_id)
# 从房间中移除用户
del room_data['participants'][user_id]
logger.info(f"用户 {user_id} 已从房间 {room_id} 移除")
# 清理空房间
if not room_data['participants']:
del active_rooms[room_id]
logger.info(f"房间 {room_id} 已删除 (空)")
break
@socketio.on('join')
def handle_join(data):
"""处理用户加入房间"""
room_id = data.get('roomId')
display_name = data.get('displayName')
user_id = data.get('userId', str(uuid.uuid4())[:8])
if not room_id or not display_name:
emit('error', {'message': '房间ID和显示名称是必需的'})
return
# 检查房间是否存在
if room_id not in active_rooms:
active_rooms[room_id] = {
'created_at': datetime.now().isoformat(),
'participants': {}
}
logger.info(f"在加入时创建房间: {room_id}")
# 将用户添加到房间
active_rooms[room_id]['participants'][user_id] = {
'displayName': display_name,
'sid': request.sid,
'joinedAt': datetime.now().isoformat()
}
# 加入Socket.IO房间
join_room(room_id)
# 获取现有参与者列表发送给新用户
participants = []
for pid, pdata in active_rooms[room_id]['participants'].items():
if pid != user_id: # 不包括正在加入的用户
participants.append({
'userId': pid,
'displayName': pdata['displayName']
})
# 通知加入的用户
emit('joined', {
'roomId': room_id,
'userId': user_id,
'participants': participants
})
# 通知其他参与者有新用户加入
emit('user_joined', {
'userId': user_id,
'displayName': display_name
}, room=room_id, include_self=False)
logger.info(f"用户 {user_id} ({display_name}) 加入房间 {room_id}")
@socketio.on('leave')
def handle_leave(data):
"""处理用户离开房间"""
room_id = data.get('roomId')
user_id = data.get('userId')
if not room_id or not user_id:
emit('error', {'message': '房间ID和用户ID是必需的'})
return
if room_id in active_rooms and user_id in active_rooms[room_id]['participants']:
# 从房间数据中移除用户
del active_rooms[room_id]['participants'][user_id]
# 清理空房间
if not active_rooms[room_id]['participants']:
del active_rooms[room_id]
logger.info(f"房间 {room_id} 已删除 (空)")
else:
# 通知其他人用户已离开
emit('user_left', {'userId': user_id}, room=room_id)
# 离开Socket.IO房间
leave_room(room_id)
logger.info(f"用户 {user_id} 离开房间 {room_id}")
emit('left', {'roomId': room_id, 'userId': user_id})
# WebRTC信令事件
@socketio.on('offer')
def handle_offer(data):
"""处理offer信令"""
room_id = data.get('roomId')
target_id = data.get('targetId')
from_id = data.get('fromId')
offer = data.get('offer')
if not all([room_id, target_id, from_id, offer]):
emit('error', {'message': 'offer缺少必要数据'})
return
# 查找目标用户的socket ID
if (room_id in active_rooms and
target_id in active_rooms[room_id]['participants']):
target_sid = active_rooms[room_id]['participants'][target_id]['sid']
# 将offer发送给目标用户
emit('offer', {
'fromId': from_id,
'offer': offer
}, room=target_sid)
logger.debug(f"已转发来自 {from_id} 的offer到 {target_id}")
@socketio.on('answer')
def handle_answer(data):
"""处理answer信令"""
room_id = data.get('roomId')
target_id = data.get('targetId')
from_id = data.get('fromId')
answer = data.get('answer')
if not all([room_id, target_id, from_id, answer]):
emit('error', {'message': 'answer缺少必要数据'})
return
# 查找目标用户的socket ID
if (room_id in active_rooms and
target_id in active_rooms[room_id]['participants']):
target_sid = active_rooms[room_id]['participants'][target_id]['sid']
# 将answer发送给目标用户
emit('answer', {
'fromId': from_id,
'answer': answer
}, room=target_sid)
logger.debug(f"已转发来自 {from_id} 的answer到 {target_id}")
@socketio.on('ice_candidate')
def handle_ice_candidate(data):
"""处理ICE候选者信令"""
room_id = data.get('roomId')
target_id = data.get('targetId')
from_id = data.get('fromId')
candidate = data.get('candidate')
if not all([room_id, target_id, from_id, candidate]):
emit('error', {'message': 'ICE候选者缺少必要数据'})
return
# 查找目标用户的socket ID
if (room_id in active_rooms and
target_id in active_rooms[room_id]['participants']):
target_sid = active_rooms[room_id]['participants'][target_id]['sid']
# 将ICE候选者发送给目标用户
emit('ice_candidate', {
'fromId': from_id,
'candidate': candidate
}, room=target_sid)
logger.debug(f"已转发来自 {from_id} 的ICE候选者到 {target_id}")
@socketio.on('chat_message')
def handle_chat_message(data):
"""处理聊天消息"""
room_id = data.get('roomId')
from_id = data.get('fromId')
message = data.get('message')
if not all([room_id, from_id, message]):
emit('error', {'message': '聊天消息缺少必要数据'})
return
# 获取发送者的显示名称
if (room_id in active_rooms and
from_id in active_rooms[room_id]['participants']):
from_name = active_rooms[room_id]['participants'][from_id]['displayName']
# 向房间中的所有用户广播消息
emit('chat_message', {
'fromId': from_id,
'fromName': from_name,
'message': message,
'timestamp': datetime.now().isoformat()
}, room=room_id)
logger.debug(f"已广播来自 {from_id} 的聊天消息到房间 {room_id}")
@socketio.on('media_state_change')
def handle_media_state_change(data):
"""处理媒体状态变化"""
room_id = data.get('roomId')
user_id = data.get('userId')
audio_enabled = data.get('audioEnabled')
video_enabled = data.get('videoEnabled')
if not all([room_id, user_id]) or audio_enabled is None or video_enabled is None:
emit('error', {'message': '媒体状态变化缺少必要数据'})
return
# 向房间中的所有用户广播媒体状态变化
emit('media_state_change', {
'userId': user_id,
'audioEnabled': audio_enabled,
'videoEnabled': video_enabled
}, room=room_id)
logger.debug(f"已广播房间 {room_id} 中用户 {user_id} 的媒体状态变化")
# if __name__ == '__main__':
# # 确保templates目录存在
# os.makedirs('templates', exist_ok=True)
# port = int(os.environ.get('PORT', 5000))
# logger.info(f"在端口 {port} 上启动服务器")
# socketio.run(app, host='0.0.0.0', port=port, debug=True)
if __name__ == '__main__':
# 确保templates目录存在
os.makedirs('templates', exist_ok=True)
# 修改端口为7860
port = 7860
logger.info(f"在端口 {port} 上启动服务器")
socketio.run(app, host='0.0.0.0', port=port, debug=True)