import 'dart:async'; import 'package:audio_session/audio_session.dart'; import 'package:hgdj/hj_page/short_video/view/video_player_base_logic.dart'; import 'package:hgdj/tools_base/debug_log.dart'; import 'package:hgdj/tools_base/event_bus/event_bus_util.dart'; import 'package:hgdj/tools_base/event_bus/events.dart'; /// 监听音频环境变化,自动暂停所有视频播放,防止音频通过手机外放泄露隐私: /// 1. 耳机 / 蓝牙断开(音频切回外放) /// 2. 来电、微信通话等其他 App 抢占音频(中断) class HeadphoneMonitor { HeadphoneMonitor._(); static final HeadphoneMonitor instance = HeadphoneMonitor._(); /// 断开后会导致音频切回外放的「私密」输出设备类型(移除时需暂停) static const _privateOutputTypes = { AudioDeviceType.bluetoothA2dp, AudioDeviceType.bluetoothSco, AudioDeviceType.wiredHeadset, AudioDeviceType.wiredHeadphones, AudioDeviceType.usbAudio, }; bool _started = false; StreamSubscription? _noisySub; StreamSubscription? _interruptSub; StreamSubscription? _deviceSub; Future start() async { if (_started) return; _started = true; try { final session = await AudioSession.instance; await session.configure(const AudioSessionConfiguration.music()); // 耳机拔出 / 蓝牙断开(音频切回外放) _noisySub = session.becomingNoisyEventStream.listen((_) { debugLog('耳机断开 → 自动暂停所有播放'); pauseAll(); }); // 兜底:部分机型(华为/荣耀等)蓝牙断开不发 becomingNoisy 广播,改用更底层的设备移除回调兜底 _deviceSub = session.devicesChangedEventStream.listen((event) { final unplugged = event.devicesRemoved .any((d) => d.isOutput && _privateOutputTypes.contains(d.type)); if (unplugged) { debugLog('音频输出设备移除(蓝牙/耳机) → 自动暂停所有播放'); pauseAll(); } }); // 来电 / 微信通话等抢占音频(中断)→ 暂停;duck(短促提示音降音量)不处理 _interruptSub = session.interruptionEventStream.listen((event) { if (event.begin && event.type != AudioInterruptionType.duck) { debugLog('音频被打断(来电/通话等) → 自动暂停所有播放'); pauseAll(); } }); } catch (e) { _started = false; debugLog('HeadphoneMonitor.start error: $e'); } } /// 暂停所有正在播放的视频(长视频/直播/简单播放器/短视频列表) void pauseAll() { // 长视频详情页 / 直播 / 简单播放器(统一走 eventBus) eventBus.emit(PauseVideoEvent()); // 短视频列表页 VideoPlayerBaseLogic.pauseAll(); } void dispose() { _noisySub?.cancel(); _noisySub = null; _interruptSub?.cancel(); _interruptSub = null; _deviceSub?.cancel(); _deviceSub = null; _started = false; } }