如何在Flask睡眠检测网站中实现睡眠触发音频自动启停?
解决Flask睡眠检测网站的音频启停问题
核心问题分析
你之前尝试用playsound/winsound在后端播放音频失败,主要有两个原因:
- 阻塞机制:这类音频库是同步阻塞的,会卡住
gen_frames的帧推送循环,导致视频流中断。 - 播放位置错误:后端代码运行在服务器端,音频会在服务器设备播放,而非访问网站的用户设备——这完全不符合网站场景的需求。
下面给出两种可行方案,优先推荐前端控制音频的方案,适配网站的通用场景。
方案一:前端控制音频(推荐,适配网站场景)
通过WebSocket实时推送睡眠状态到前端,由浏览器控制音频播放/暂停,确保声音在用户设备上输出。
步骤1:修改Flask后端,添加WebSocket支持
先安装依赖:
pip install flask-socketio
更新Python代码:
from flask import Flask, render_template, Response import cv2 import numpy as np from imutils import face_utils import dlib from flask_socketio import SocketIO, emit app = Flask(__name__) app.config['SECRET_KEY'] = 'sleep-detector-secret' socketio = SocketIO(app, cors_allowed_origins="*") camera = cv2.VideoCapture(0) detector = dlib.get_frontal_face_detector() predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") last_status = "" # 记录上一次状态,避免重复推送 def compute(ptA, ptB): dist = np.linalg.norm(ptA - ptB) return dist def blinked(a, b, c, d, e, f): up = compute(b,d) + compute(c,e) down = compute(a,f) ratio = up/(2.0*down) if(ratio > 0.25): return 2 elif(ratio > 0.21 and ratio <= 0.25): return 1 else: return 0 def gen_frames(): global last_status while True: success, frame = camera.read() if not success: break gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) sleep = 0 drowsy = 0 active = 0 status = "" color = (0, 0, 0) faces = detector(gray) for face in faces: x1, y1, x2, y2 = face.left(), face.top(), face.right(), face.bottom() cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) landmarks = face_utils.shape_to_np(predictor(gray, face)) left_blink = blinked(landmarks[36], landmarks[37], landmarks[38], landmarks[41], landmarks[40], landmarks[39]) right_blink = blinked(landmarks[42], landmarks[43], landmarks[44], landmarks[47], landmarks[46], landmarks[45]) if(left_blink == 0 or right_blink == 0): sleep += 1 if(sleep > 6): status = "SLEEPING" color = (255, 0, 0) elif(left_blink == 1 or right_blink == 1): drowsy += 1 if(drowsy > 6): status = "Drowsy" color = (0, 0, 255) else: active += 1 if(active > 6): status = "Active" color = (0, 255, 0) cv2.putText(frame, status, (100, 100), cv2.FONT_HERSHEY_SIMPLEX, 1.2, color, 3) for (x, y) in landmarks: cv2.circle(frame, (x, y), 1, (255, 255, 255), -1) # 仅当状态变化时推送至前端 if status != last_status: last_status = status socketio.emit('sleep_status', {'status': status}) ret, buffer = cv2.imencode('.jpg', frame) frame = buffer.tobytes() yield(b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') @app.route('/') def index(): return render_template('index.html') @app.route('/video') def video(): return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame') if __name__ == "__main__": socketio.run(app, debug=True) # 用socketio启动服务
步骤2:修改前端HTML,添加音频控制逻辑
创建static文件夹,将alarm.wav放入其中,然后更新HTML:
<html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>SleepCatcher</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script> </head> <body> <div id="video"> <img src="{{ url_for('video') }}" /> </div> <!-- 隐藏的音频元素,循环播放闹钟 --> <audio id="alarmAudio" src="{{ url_for('static', filename='alarm.wav') }}" loop></audio> <script> const socket = io(); const alarmAudio = document.getElementById('alarmAudio'); let isPlaying = false; // 监听后端推送的状态 socket.on('sleep_status', function(data) { const status = data.status; if (status === 'SLEEPING' && !isPlaying) { // 浏览器可能阻止自动播放,可添加页面点击交互触发 alarmAudio.play().catch(err => console.log('音频播放被浏览器拦截,请点击页面后重试')); isPlaying = true; } else if (status !== 'SLEEPING' && isPlaying) { alarmAudio.pause(); alarmAudio.currentTime = 0; // 重置音频到开头 isPlaying = false; } }); // 添加页面点击事件,解决浏览器自动播放限制 document.body.addEventListener('click', () => { if (!isPlaying) { alarmAudio.load(); } }); </script> </body> </html>
方案二:后端本地播放音频(仅适用于本地运行Flask)
如果你只是在自己电脑上运行服务,想让电脑本身播放音频,可以用线程解决阻塞问题:
修改Python代码中的相关部分:
import threading from playsound import playsound is_alarm_playing = False def play_alarm(): global is_alarm_playing is_alarm_playing = True playsound('alarm.wav') is_alarm_playing = False # 在gen_frames的sleep>6判断块中: if(sleep > 6): status = "SLEEPING !!!" color = (255, 0, 0) if not is_alarm_playing: threading.Thread(target=play_alarm, daemon=True).start()
这种方式仅适合本地使用,部署到远程服务器时用户无法听到音频。
内容的提问来源于stack exchange,提问作者VeryNew
相关产品推荐
相关产品推荐

