Files
huangguo_android/lib/tools_base/headphone_monitor.dart
T
2026-09-15 15:44:13 +07:00

81 lines
2.9 KiB
Dart

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<void> 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;
}
}