Streamlit搭建内网视频通话系统

这是一个基于 Streamlit 的内网视频通话应用。让我为您详细解读代码的各个部分:

代码结构分析

1. 导入模块

复制代码
import streamlit as st  # 主框架
import av  # 音视频处理
from streamlit_webrtc import webrtc_streamer, WebRtcMode  # WebRTC集成
import sqlite3  # 数据库

2. 数据库设计

创建了5个核心表:

  • devices:设备信息(类型、屏幕尺寸等)

  • users:用户信息和在线状态

  • messages:聊天记录

  • call_invitations:通话邀请管理

  • notifications:通知系统

3. 设备管理

复制代码
def get_or_create_device_id():
    # 基于硬件信息生成唯一设备ID
    device_info = f"{platform.platform()}_{socket.gethostname()}"
    device_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, device_info))

4. 核心功能模块

用户认证系统
  • • 侧边栏用户名输入

  • • 自动设备注册

  • • 在线状态管理

通话管理系统
复制代码
def create_call_invitation(conn, from_user, to_user, call_type, room_id):
    # 创建通话邀请并生成房间ID
通知系统
  • • 实时通知铃铛显示

  • • 模态框接听界面

  • • 通知标记已读

聊天系统
  • • 实时消息显示

  • • 对话历史记录

  • • 支持富文本渲染

5. WebRTC 视频通话

复制代码
webrtc_ctx = webrtc_streamer(
    key=f"video-{room_id}",
    mode=WebRtcMode.SENDRECV,  # 双向音视频
    rtc_configuration=RTC_CONFIGURATION,
    media_stream_constraints={"video": True, "audio": True}
)

6. 响应式设计

CSS媒体查询适配不同设备:

  • 手机:垂直布局,触摸优化

  • 平板:自适应网格

  • 桌面:多列布局

工作流程

    1. 用户注册​ → 输入用户名自动注册设备
    1. 查看在线用户​ → 显示可通话对象
    1. 发起通话​ → 创建邀请并发送通知
    1. 接听邀请​ → 弹出模态框选择接听/拒绝
    1. 视频通话​ → 建立WebRTC连接
    1. 结束通话​ → 清理资源返回主界面

    import streamlit as st
    import av
    import threading
    import queue
    import json
    import logging
    import uuid
    import time
    import sqlite3
    from datetime import datetime, timedelta
    from streamlit_webrtc import webrtc_streamer, WebRtcMode, RTCConfiguration
    import socket
    import platform
    import asyncio
    from typing import Optional, Dict, List

    配置日志

    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger(name)

    页面配置 - 响应式设计

    st.set_page_config(
    page_title="内网视频通话",
    page_icon="📹",
    layout="wide",
    initial_sidebar_state="expanded"
    )

    修复Python 3.12的SQLite日期时间适配器警告

    def adapt_datetime(val):
    return val.isoformat()

    def convert_datetime(val):
    try:
    return datetime.fromisoformat(val.decode())
    except (ValueError, AttributeError):
    return datetime.now()

    sqlite3.register_adapter(datetime, adapt_datetime)
    sqlite3.register_converter("TIMESTAMP", convert_datetime)

    初始化数据库

    def init_db():
    conn = sqlite3.connect('video_chat.db', check_same_thread=False, detect_types=sqlite3.PARSE_DECLTYPES)
    c = conn.cursor()

    复制代码
     # 创建设备表
     c.execute('''CREATE TABLE IF NOT EXISTS devices
                  (id TEXT PRIMARY KEY, 
                   device_name TEXT,
                   username TEXT,
                   device_type TEXT,
                   screen_width INTEGER,
                   screen_height INTEGER,
                   user_agent TEXT,
                   first_seen TIMESTAMP,
                   last_seen TIMESTAMP)''')
     
     # 创建用户表
     c.execute('''CREATE TABLE IF NOT EXISTS users
                  (id TEXT PRIMARY KEY,
                   username TEXT UNIQUE,
                   device_id TEXT,
                   status TEXT,
                   last_active TIMESTAMP,
                   FOREIGN KEY (device_id) REFERENCES devices (id))''')
     
     # 创建消息表
     c.execute('''CREATE TABLE IF NOT EXISTS messages
                  (id TEXT PRIMARY KEY,
                   from_user TEXT,
                   to_user TEXT,
                   content TEXT,
                   timestamp TIMESTAMP,
                   is_read BOOLEAN)''')
     
     # 创建通话邀请表
     c.execute('''CREATE TABLE IF NOT EXISTS call_invitations
                  (id TEXT PRIMARY KEY,
                   from_user TEXT,
                   to_user TEXT,
                   call_type TEXT,
                   status TEXT,
                   room_id TEXT,
                   created_at TIMESTAMP,
                   responded_at TIMESTAMP)''')
     
     # 创建通知表
     c.execute('''CREATE TABLE IF NOT EXISTS notifications
                  (id TEXT PRIMARY KEY,
                   user_id TEXT,
                   title TEXT,
                   message TEXT,
                   notification_type TEXT,
                   is_read BOOLEAN,
                   created_at TIMESTAMP,
                   related_call_id TEXT)''')
     
     conn.commit()
     return conn

    获取或创建设备ID

    def get_or_create_device_id():
    if 'device_id' not in st.session_state:
    # 生成基于硬件和浏览器的唯一标识
    device_info = f"{platform.platform()}_{socket.gethostname()}"
    device_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, device_info))
    st.session_state.device_id = device_id

    复制代码
     return st.session_state.device_id

    检测设备类型

    def detect_device_type():
    try:
    user_agent = st.query_params.get('user_agent', '')
    if any(device in user_agent.lower() for device in ['mobile', 'android', 'iphone']):
    return "mobile"
    elif any(device in user_agent.lower() for device in ['tablet', 'ipad']):
    return "tablet"
    else:
    return "desktop"
    except:
    return "desktop"

    注册或更新设备信息

    def register_device(conn, username):
    device_id = get_or_create_device_id()
    device_type = detect_device_type()

    复制代码
     c = conn.cursor()
     
     # 检查设备是否已存在
     c.execute("SELECT * FROM devices WHERE id = ?", (device_id,))
     existing_device = c.fetchone()
     
     current_time = datetime.now()
     
     if existing_device:
         # 更新设备信息
         c.execute('''UPDATE devices 
                      SET username = ?, last_seen = ?
                      WHERE id = ?''', 
                  (username, current_time, device_id))
     else:
         # 插入新设备
         c.execute('''INSERT INTO devices 
                      (id, device_name, username, device_type, screen_width, screen_height, user_agent, first_seen, last_seen)
                      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)''',
                  (device_id, f"{device_type}_{device_id[:8]}", username, device_type, 
                   1920, 1080, "streamlit_app", current_time, current_time))
     
     # 更新用户信息
     user_id = str(uuid.uuid4())
     c.execute('''INSERT OR REPLACE INTO users 
                  (id, username, device_id, status, last_active)
                  VALUES (?, ?, ?, ?, ?)''',
              (user_id, username, device_id, "online", current_time))
     
     conn.commit()
     return device_id, user_id

    获取在线用户列表

    def get_online_users(conn, exclude_user=None):
    c = conn.cursor()

    复制代码
     if exclude_user:
         c.execute('''SELECT u.username, d.device_type, u.last_active 
                      FROM users u
                      JOIN devices d ON u.device_id = d.id
                      WHERE u.status = 'online' AND u.username != ?
                      ORDER BY u.last_active DESC''', (exclude_user,))
     else:
         c.execute('''SELECT u.username, d.device_type, u.last_active 
                      FROM users u
                      JOIN devices d ON u.device_id = d.id
                      WHERE u.status = 'online'
                      ORDER BY u.last_active DESC''')
     
     return c.fetchall()

    创建通话邀请

    def create_call_invitation(conn, from_user, to_user, call_type="video", room_id=None):
    if room_id is None:
    room_id = f"room_{uuid.uuid4().hex[:8]}"

    复制代码
     call_id = str(uuid.uuid4())
     current_time = datetime.now()
     
     c = conn.cursor()
     c.execute('''INSERT INTO call_invitations 
                  (id, from_user, to_user, call_type, status, room_id, created_at)
                  VALUES (?, ?, ?, ?, ?, ?, ?)''',
              (call_id, from_user, to_user, call_type, "pending", room_id, current_time))
     
     # 创建通知
     notification_id = str(uuid.uuid4())
     c.execute('''INSERT INTO notifications
                  (id, user_id, title, message, notification_type, is_read, created_at, related_call_id)
                  VALUES (?, ?, ?, ?, ?, ?, ?, ?)''',
              (notification_id, to_user, "视频通话邀请", 
               f"{from_user} 向您发起了视频通话邀请", "call_invitation", 
               False, current_time, call_id))
     
     conn.commit()
     return call_id, room_id

    获取待处理的通知

    def get_pending_notifications(conn, username):
    c = conn.cursor()
    c.execute('''SELECT id, title, message, notification_type, created_at, related_call_id
    FROM notifications
    WHERE user_id = ? AND is_read = FALSE
    ORDER BY created_at DESC''', (username,))
    return c.fetchall()

    标记通知为已读

    def mark_notification_read(conn, notification_id):
    c = conn.cursor()
    c.execute('''UPDATE notifications SET is_read = TRUE WHERE id = ?''', (notification_id,))
    conn.commit()

    处理通话邀请响应

    def respond_to_call_invitation(conn, call_id, response, username):
    """响应通话邀请:accept 或 reject"""
    c = conn.cursor()
    current_time = datetime.now()

    复制代码
     # 更新邀请状态
     c.execute('''UPDATE call_invitations 
                  SET status = ?, responded_at = ?
                  WHERE id = ? AND to_user = ?''',
              (response, current_time, call_id, username))
     
     # 获取房间ID
     c.execute('''SELECT room_id FROM call_invitations WHERE id = ?''', (call_id,))
     result = c.fetchone()
     room_id = result[0] if result else None
     
     conn.commit()
     return room_id if response == "accepted" else None

    获取待处理的通话邀请

    def get_pending_call_invitations(conn, username):
    c = conn.cursor()
    c.execute('''SELECT id, from_user, call_type, room_id, created_at
    FROM call_invitations
    WHERE to_user = ? AND status = 'pending'
    ORDER BY created_at DESC''', (username,))
    return c.fetchall()

    检查媒体权限状态

    def check_media_permissions():
    # 简化处理,实际部署时应该通过前端检测
    video_permission = st.session_state.get('video_permission', False)
    audio_permission = st.session_state.get('audio_permission', False)

    复制代码
     return video_permission, audio_permission

    请求媒体权限

    def request_media_permissions():
    st.warning("请允许摄像头和麦克风权限")

    复制代码
     col1, col2 = st.columns(2)
     with col1:
         if st.button("授予摄像头和麦克风权限"):
             st.session_state.video_permission = True
             st.session_state.audio_permission = True
             st.rerun()
     with col2:
         if st.button("稍后再说"):
             st.session_state.video_permission = False
             st.session_state.audio_permission = False

    WebRTC配置

    RTC_CONFIGURATION = RTCConfiguration(
    {"iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]}
    )

    初始化数据库连接

    conn = init_db()

    响应式CSS

    st.markdown("""

    <style> /* 响应式设计 */ @media (max-width: 768px) { .main-header { font-size: 1.5rem !important; } .user-card { padding: 8px !important; margin: 4px 0 !important; } .video-container { height: 200px !important; } .notification-bell { font-size: 1.2rem !important; } }

    @media (min-width: 769px) and (max-width: 1024px) {
    .video-container { height: 300px !important; }
    }

    @media (min-width: 1025px) {
    .video-container { height: 400px !important; }
    }

    /* 通用样式 */
    .user-card {
    border: 1px solid #ddd;
    border-radius: 10px;
    padding: 12px;
    margin: 8px 0;
    cursor: pointer;
    transition: all 0.3s ease;
    }

    .user-card:hover {
    background-color: #f5f5f5;
    transform: translateY(-2px);
    }

    .user-online {
    border-left: 4px solid #28a745;
    }

    .user-offline {
    border-left: 4px solid #6c757d;
    }

    .chat-window {
    border: 1px solid #ddd;
    border-radius: 10px;
    padding: 15px;
    margin: 10px 0;
    max-height: 400px;
    overflow-y: auto;
    }

    .video-container {
    border: 2px solid #007bff;
    border-radius: 10px;
    margin: 10px 0;
    background-color: #000;
    }

    .notification-bell {
    position: relative;
    display: inline-block;
    font-size: 1.5rem;
    }

    .notification-badge {
    position: absolute;
    top: -5px;
    right: -5px;
    background-color: #ff4b4b;
    color: white;
    border-radius: 50%;
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.8rem;
    }

    .notification-item {
    border-left: 4px solid #007bff;
    padding: 10px;
    margin: 5px 0;
    background-color: #f8f9fa;
    border-radius: 5px;
    }

    .call-invitation {
    border-left: 4px solid #28a745;
    background-color: #e8f5e8;
    }

    .modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0,0,0,0.5);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 1000;
    }

    .modal-content {
    background: white;
    padding: 20px;
    border-radius: 10px;
    width: 300px;
    text-align: center;
    }
    </style>
    """, unsafe_allow_html=True)

    应用标题和通知区域

    col1, col2 = st.columns([4, 1])
    with col1:
    st.markdown('

    📹 内网视频通话应用

    ', unsafe_allow_html=True)
    st.markdown("支持多设备适配的实时视频通信平台")

    with col2:
    # 通知铃铛
    pending_notifications = get_pending_notifications(conn, st.session_state.get('username', ''))
    notification_count = len(pending_notifications)

    复制代码
     st.markdown(f'''
     <div class="notification-bell">
         🔔
         {f'<span class="notification-badge">{notification_count}</span>' if notification_count > 0 else ''}
     </div>
     ''', unsafe_allow_html=True)
     
     if st.button("查看通知", use_container_width=True):
         st.session_state.show_notifications = not st.session_state.get('show_notifications', False)

    显示通知面板

    if st.session_state.get('show_notifications', False) and st.session_state.get('username'):
    st.subheader("📢 通知中心")
    notifications = get_pending_notifications(conn, st.session_state.username)

    复制代码
     if notifications:
         for notif_id, title, message, notif_type, created_at, call_id in notifications:
             with st.container():
                 col1, col2 = st.columns([4, 1])
                 with col1:
                     st.markdown(f"**{title}**")
                     st.markdown(f"{message}")
                     st.caption(f"时间: {created_at.strftime('%H:%M:%S') if hasattr(created_at, 'strftime') else created_at}")
                 
                 with col2:
                     if notif_type == "call_invitation":
                         col2_1, col2_2 = st.columns(2)
                         with col2_1:
                             if st.button("接听", key=f"accept_{notif_id}"):
                                 room_id = respond_to_call_invitation(conn, call_id, "accepted", st.session_state.username)
                                 mark_notification_read(conn, notif_id)
                                 st.session_state.call_active = True
                                 st.session_state.current_room = room_id
                                 st.session_state.show_notifications = False
                                 st.rerun()
                         with col2_2:
                             if st.button("拒绝", key=f"reject_{notif_id}"):
                                 respond_to_call_invitation(conn, call_id, "rejected", st.session_state.username)
                                 mark_notification_read(conn, notif_id)
                                 st.session_state.show_notifications = False
                                 st.rerun()
                     else:
                         if st.button("标记已读", key=f"read_{notif_id}"):
                             mark_notification_read(conn, notif_id)
                             st.session_state.show_notifications = False
                             st.rerun()
                 
                 st.markdown("---")
     else:
         st.info("暂无新通知")

    用户注册和登录

    with st.sidebar:
    st.header("👤 用户设置")

    复制代码
     # 用户名输入
     username = st.text_input("用户名", placeholder="请输入您的用户名", key="username_input")
     
     if username and username.strip():
         st.session_state.username = username.strip()
         # 注册设备
         device_id, user_id = register_device(conn, st.session_state.username)
         device_type = detect_device_type()
         
         st.success(f"设备已注册: {device_type.upper()}")
         st.info(f"设备ID: {device_id[:8]}...")
         
         # 通话设置
         st.header("⚙️ 通话设置")
         call_id = st.text_input("房间ID", value=f"room_{uuid.uuid4().hex[:8]}", key="room_input")
         
         # 媒体权限检查
         video_permission, audio_permission = check_media_permissions()
         
         if not video_permission or not audio_permission:
             request_media_permissions()
         else:
             video_enabled = st.checkbox("启用视频", value=video_permission, key="video_check")
             audio_enabled = st.checkbox("启用音频", value=audio_permission, key="audio_check")
         
         # 通话控制
         st.header("📞 通话控制")
         col1, col2 = st.columns(2)
         with col1:
             start_call = st.button("加入通话", type="primary", use_container_width=True, key="join_call")
         with col2:
             end_call = st.button("离开通话", type="secondary", use_container_width=True, key="leave_call")

    主内容区域

    if not st.session_state.get('username'):
    st.warning("请在侧边栏输入用户名开始使用")
    st.stop()

    初始化会话状态

    if 'current_chat' not in st.session_state:
    st.session_state.current_chat = None
    if 'call_active' not in st.session_state:
    st.session_state.call_active = False
    if 'messages' not in st.session_state:
    st.session_state.messages = {}
    if 'current_room' not in st.session_state:
    st.session_state.current_room = None
    if 'pending_invitation' not in st.session_state:
    st.session_state.pending_invitation = None
    if 'show_invitation_modal' not in st.session_state:
    st.session_state.show_invitation_modal = False

    检查是否有待处理的通话邀请

    if st.session_state.username and not st.session_state.call_active:
    pending_invitations = get_pending_call_invitations(conn, st.session_state.username)
    if pending_invitations and not st.session_state.get('show_invitation_modal', False):
    st.session_state.pending_invitation = pending_invitations[0]
    st.session_state.show_invitation_modal = True

    显示通话邀请模态框

    if st.session_state.get('show_invitation_modal', False) and st.session_state.pending_invitation:
    invitation_id, from_user, call_type, room_id, created_at = st.session_state.pending_invitation

    复制代码
     # 使用Streamlit原生组件创建模态框
     st.markdown("""
     <div class="modal-overlay">
         <div class="modal-content">
     """, unsafe_allow_html=True)
     
     st.info(f"📞 {from_user} 邀请您视频通话")
     
     accept_col, reject_col = st.columns(2)
     with accept_col:
         if st.button("接听通话", type="primary", use_container_width=True, key="modal_accept"):
             room_id = respond_to_call_invitation(conn, invitation_id, "accepted", st.session_state.username)
             st.session_state.call_active = True
             st.session_state.current_room = room_id
             st.session_state.show_invitation_modal = False
             st.session_state.pending_invitation = None
             st.rerun()
     with reject_col:
         if st.button("拒绝通话", type="secondary", use_container_width=True, key="modal_reject"):
             respond_to_call_invitation(conn, invitation_id, "rejected", st.session_state.username)
             st.session_state.show_invitation_modal = False
             st.session_state.pending_invitation = None
             st.rerun()
     
     st.markdown("</div></div>", unsafe_allow_html=True)

    在线用户列表

    st.header("👥 在线用户")
    online_users = get_online_users(conn, exclude_user=st.session_state.username)

    if online_users:
    cols = st.columns(3)
    for i, (user, dev_type, last_active) in enumerate(online_users):
    with cols[i % 3]:
    try:
    if isinstance(last_active, str):
    last_active = datetime.strptime(last_active, '%Y-%m-%d %H:%M:%S.%f')
    time_diff = (datetime.now() - last_active).seconds
    status_text = "刚刚" if time_diff < 60 else f"{time_diff//60}分钟前"
    except:
    status_text = "未知"

    复制代码
             st.markdown(f'''
             <div class="user-card user-online">
                 <strong>{user}</strong><br>
                 <small>{dev_type.upper()} • {status_text}</small>
             </div>
             ''', unsafe_allow_html=True)
             
             call_col, chat_col = st.columns(2)
             with call_col:
                 if st.button("视频通话", key=f"call_{user}", use_container_width=True):
                     # 创建通话邀请
                     call_id, room_id = create_call_invitation(conn, st.session_state.username, user)
                     st.session_state.current_chat = user
                     st.session_state.current_room = room_id
                     st.success(f"已向 {user} 发送通话邀请")
             with chat_col:
                 if st.button("发起聊天", key=f"chat_{user}", use_container_width=True):
                     st.session_state.current_chat = user

    else:
    st.info("暂无其他在线用户")

    聊天窗口

    if st.session_state.current_chat:
    st.header(f"💬 与 {st.session_state.current_chat} 的对话")

    复制代码
     # 初始化聊天记录
     if st.session_state.current_chat not in st.session_state.messages:
         st.session_state.messages[st.session_state.current_chat] = []
     
     # 显示聊天消息
     chat_container = st.container()
     with chat_container:
         for msg in st.session_state.messages[st.session_state.current_chat]:
             alignment = "right" if msg['from'] == st.session_state.username else "left"
             bg_color = "#007bff" if msg['from'] == st.session_state.username else "#f1f1f1"
             text_color = "white" if msg['from'] == st.session_state.username else "black"
             
             st.markdown(f"""
             <div style="text-align: {alignment}; margin: 5px 0;">
                 <div style="background: {bg_color}; color: {text_color}; 
                            display: inline-block; padding: 8px 12px; 
                            border-radius: 18px; max-width: 70%;">
                     {msg['content']}
                 </div>
                 <div style="font-size: 0.8em; color: #666; margin-top: 2px;">
                     {msg['time']}
                 </div>
             </div>
             """, unsafe_allow_html=True)
     
     # 消息输入和视频通话按钮
     col1, col2, col3 = st.columns([3, 1, 1])
     
     with col1:
         new_message = st.text_input("输入消息", label_visibility="collapsed", 
                                    placeholder="输入消息...", key="message_input")
     
     with col2:
         if st.button("发送", use_container_width=True, key="send_message") and new_message:
             timestamp = datetime.now().strftime("%H:%M")
             st.session_state.messages[st.session_state.current_chat].append({
                 'from': st.session_state.username,
                 'content': new_message,
                 'time': timestamp
             })
             st.rerun()
     
     with col3:
         if st.button("视频通话", type="primary", use_container_width=True, key="start_video_chat"):
             # 创建通话邀请
             call_id, room_id = create_call_invitation(conn, st.session_state.username, st.session_state.current_chat)
             st.session_state.call_active = True
             st.session_state.current_room = room_id
             st.success(f"已向 {st.session_state.current_chat} 发起视频通话")

    视频通话界面

    if st.session_state.call_active:
    st.header("📹 视频通话中")
    st.info(f"房间ID: {st.session_state.current_room or '默认房间'}")

    复制代码
     # 检查媒体权限
     video_permission, audio_permission = check_media_permissions()
     
     if not video_permission or not audio_permission:
         st.error("需要摄像头和麦克风权限才能进行视频通话")
         request_media_permissions()
     else:
         # 视频通话布局
         col1, col2 = st.columns(2)
         
         with col1:
             st.subheader("本地视频")
             try:
                 webrtc_ctx = webrtc_streamer(
                     key=f"video-{st.session_state.current_room or 'default'}",
                     mode=WebRtcMode.SENDRECV,
                     rtc_configuration=RTC_CONFIGURATION,
                     media_stream_constraints={
                         "video": True,
                         "audio": True,
                     },
                 )
             except Exception as e:
                 st.error(f"视频流初始化失败: {str(e)}")
                 st.info("请确保浏览器已授予摄像头和麦克风权限")
         
         with col2:
             st.subheader("远程视频")
             if st.session_state.current_chat:
                 st.info(f"等待 {st.session_state.current_chat} 接听...")
             else:
                 st.info("等待对方接听...")
         
         # 通话控制按钮
         st.markdown("---")
         col1, col2, col3 = st.columns([1, 2, 1])
         
         with col2:
             if st.button("结束通话", type="secondary", use_container_width=True, key="end_call"):
                 st.session_state.call_active = False
                 st.session_state.current_room = None
                 st.rerun()

    设备信息显示

    with st.expander("📱 设备信息"):
    device_info = f"""
    - 设备类型: {detect_device_type().upper()}
    - 设备ID: {get_or_create_device_id()}
    - 用户名: {st.session_state.username}
    - 在线用户数: {len(online_users) + 1}
    - 当前时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
    """
    st.markdown(device_info)

    使用说明

    with st.expander("❓ 使用说明"):
    st.markdown("""
    ### 使用指南:
    1. 用户注册: 在侧边栏输入用户名自动注册设备
    2. 用户列表: 查看在线用户,点击用户发起通话或聊天
    3. 通话邀请: 收到邀请时会弹出通知,可选择接听或拒绝
    4. 权限管理: 首次使用需要授权摄像头和麦克风

    复制代码
     ### 通知功能:
     - 🔔 右上角通知铃铛显示未读通知数量
     - 📞 收到通话邀请时会自动弹出接听界面
     - 💬 聊天过程中可随时发起视频通话
     
     ### 设备适配:
     - 📱 **手机**: 垂直布局,优化触摸操作
     - 📟 **平板**: 自适应网格布局
     - 💻 **电脑**: 多列布局,功能完整展示
     """)

    自动刷新页面以检查新通知

    if st.session_state.get('username'):
    # 每10秒自动刷新一次以检查新通知
    if 'last_refresh' not in st.session_state:
    st.session_state.last_refresh = time.time()

    复制代码
     current_time = time.time()
     if current_time - st.session_state.last_refresh > 10:  # 10秒刷新一次
         st.session_state.last_refresh = current_time
         st.rerun()

    应用退出时清理资源

    def cleanup():
    if 'conn' in locals():
    conn.close()

    import atexit
    atexit.register(cleanup)

相关推荐
伟大的大威2 小时前
LLM + TFLite 搭建离线中文语音指令 NLU并部署到 Android 设备端
python·ai·nlu
m5655bj2 小时前
Python 查找并高亮显示指定 Excel 数据
开发语言·python·excel
武子康3 小时前
Java-167 Neo4j CQL 实战:CREATE/MATCH 与关系建模速通 案例实测
java·开发语言·数据库·python·sql·nosql·neo4j
upward_tomato3 小时前
python中模拟浏览器操作之playwright使用说明以及打包浏览器驱动问题
开发语言·python
为你写首诗ge3 小时前
【python】python安装使用pytorch库环境配置
pytorch·python
信创天地3 小时前
RISC-V 2025年在国内的发展趋势
python·网络安全·系统架构·系统安全·运维开发
Danceful_YJ3 小时前
30.注意力汇聚:Nadaraya-Watson 核回归
pytorch·python·深度学习
FreeCode3 小时前
LangChain1.0智能体开发:人机协作
python·langchain·agent
2501_930412274 小时前
如何添加清华源到Conda?
开发语言·python·conda