| 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 = {}
|
|
|
| @app.route('/')
|
| def index():
|
| """提供主页"""
|
| return render_template('index.html')
|
|
|
| @app.route('/api/room', methods=['POST'])
|
| def create_room():
|
| """创建新房间"""
|
| room_id = str(uuid.uuid4())[:8]
|
| 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})
|
|
|
|
|
| @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()
|
| }
|
|
|
|
|
| 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)
|
|
|
|
|
| leave_room(room_id)
|
| logger.info(f"用户 {user_id} 离开房间 {room_id}")
|
|
|
| emit('left', {'roomId': room_id, 'userId': user_id})
|
|
|
|
|
| @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
|
|
|
|
|
| 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']
|
|
|
|
|
| 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
|
|
|
|
|
| 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']
|
|
|
|
|
| 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
|
|
|
|
|
| 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']
|
|
|
|
|
| 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__':
|
|
|
| os.makedirs('templates', exist_ok=True)
|
|
|
|
|
| port = 7860
|
| logger.info(f"在端口 {port} 上启动服务器")
|
| socketio.run(app, host='0.0.0.0', port=port, debug=True) |