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/', 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)