feat: 集成 LiveKit 视频通话与状态指示器

- 添加 LiveKit SDK 依赖及 JitPack 仓库
- 新增 LiveKit 配置界面(URL、房间、Token、自动连接开关)
- 实现 LiveKitManager 管理连接状态
- 在 MainActivity 中动态生成 Token 并处理权限申请
- 添加状态指示器(statusIndicator)实时显示 MQTT/LiveKit 连接状态
- 新增监控脚本 monitor.py 用于远程查看视频流
- 更新版本号至 2603131822
This commit is contained in:
2026-03-14 10:47:38 +08:00
parent 9756e71a23
commit d8e875793d
14 changed files with 719 additions and 8 deletions

106
monitor.py Normal file
View File

@@ -0,0 +1,106 @@
import logging
import asyncio
import cv2
import numpy as np
from time import perf_counter
from livekit import rtc, api
# 直接写死你的 API_KEY 和 API_SECRET
API_KEY = "devkey"
API_SECRET = "secret"
URL = "ws://192.168.2.236:7880" # 直接使用你的 LIVEKIT_URL
IDENTITY = "win-client"
ROOM_NAME = "temi-room"
# 生成 token
def generate_token():
token = api.AccessToken(API_KEY, API_SECRET) \
.with_identity(IDENTITY) \
.with_name("Monitor Client1") \
.with_grants(api.VideoGrants(
room_join=True,
room=ROOM_NAME,
)).to_jwt()
return token
async def main():
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
room = rtc.Room()
try:
# 生成连接房间的 token
token = generate_token()
# 连接到 LiveKit 房间
await room.connect(URL, token)
logger.info("Connected to room: %s", room.name)
# 监听参与者连接事件
@room.on("participant_connected")
def on_participant_connected(participant: rtc.RemoteParticipant):
logger.info("Participant connected: %s %s", participant.sid, participant.identity)
async def receive_frames(stream: rtc.VideoStream, track_sid: str):
window_name = f"LiveKit Video Stream ({track_sid})"
cv2.namedWindow(window_name, cv2.WINDOW_AUTOSIZE)
last_time = perf_counter()
frame_count = 0
fps = 0.0
try:
async for event in stream:
frame = event.frame
rgb_frame = frame.convert(rtc.VideoBufferType.RGB24)
arr = np.frombuffer(rgb_frame.data, dtype=np.uint8)
try:
img = arr.reshape((rgb_frame.height, rgb_frame.width, 3))
img_bgr = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
frame_count += 1
now = perf_counter()
elapsed = now - last_time
if elapsed >= 1.0:
fps = frame_count / elapsed
frame_count = 0
last_time = now
cv2.putText(
img_bgr,
f"FPS: {fps:.1f}",
(10, 30),
cv2.FONT_HERSHEY_SIMPLEX,
1.0,
(0, 255, 0),
2,
cv2.LINE_AA,
)
cv2.imshow(window_name, img_bgr)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
except Exception as e:
logger.error("Error processing frame: %s", e)
finally:
cv2.destroyWindow(window_name)
# 监听 track 订阅事件
@room.on("track_subscribed")
def on_track_subscribed(track: rtc.Track, publication: rtc.RemoteTrackPublication, participant: rtc.RemoteParticipant):
logger.info("Track subscribed: %s", publication.sid)
if track.kind == rtc.TrackKind.KIND_VIDEO:
video_stream = rtc.VideoStream(track)
asyncio.ensure_future(receive_frames(video_stream, publication.sid))
# 保持连接并处理事件
await asyncio.Event().wait()
except Exception as e:
logger.error("Failed to connect or handle events: %s", e)
finally:
# 确保断开连接
await room.disconnect()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("连接已被用户中断")