diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1032877..8ea3461 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -139,6 +139,12 @@
android:name=".abgate.ABGateTestActivity"
android:exported="true"
android:theme="@style/Theme.OxFaceLogin" />
+
+
+
\ No newline at end of file
diff --git a/app/src/main/assets/voices/IntegrationGuide.md b/app/src/main/assets/voices/IntegrationGuide.md
new file mode 100644
index 0000000..98cc505
--- /dev/null
+++ b/app/src/main/assets/voices/IntegrationGuide.md
@@ -0,0 +1,223 @@
+# 语音模块集成指南
+
+## 1. 在OXFaceOnlineActivity中集成语音模块
+
+### 1.1 初始化语音播放器
+
+在OXFaceOnlineActivity的onCreate方法中初始化语音播放器:
+
+```java
+public class OXFaceOnlineActivity extends BaseActivity {
+ private VoicePlayerManager voicePlayerManager;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_oxface_online);
+
+ // 初始化语音播放管理器
+ initVoicePlayer();
+
+ // ... 其他初始化代码
+ }
+
+ /**
+ * 初始化语音播放器
+ */
+ private void initVoicePlayer() {
+ try {
+ voicePlayerManager = VoicePlayerManager.getInstance();
+ voicePlayerManager.initialize(this);
+
+ // 设置播放监听器(可选)
+ voicePlayerManager.getAudioPlayer().setOnVoicePlayListener(new AudioPlayer.OnVoicePlayListener() {
+ @Override
+ public void onPlayStart(String filePath) {
+ LogManager.logInfo(TAG, "语音开始播放: " + filePath);
+ }
+
+ @Override
+ public void onPlayComplete(String filePath) {
+ LogManager.logInfo(TAG, "语音播放完成: " + filePath);
+ }
+
+ @Override
+ public void onPlayError(String filePath, String error) {
+ LogManager.logError(TAG, "语音播放错误: " + filePath + ", 错误信息: " + error);
+ }
+ });
+
+ LogManager.logInfo(TAG, "语音播放器初始化成功");
+ } catch (Exception e) {
+ LogManager.logError(TAG, "语音播放器初始化失败", e);
+ }
+ }
+}
+```
+
+### 1.2 在关键流程中添加语音提示
+
+在人脸识别和核销流程的关键节点添加语音提示:
+
+```java
+// 在人脸识别开始时
+private void startFaceRecognition() {
+ if (voicePlayerManager != null) {
+ voicePlayerManager.playVoice(VoiceType.FACE_RECOGNITION);
+ }
+ // ... 其他人脸识别逻辑
+}
+
+// 在订单核销成功时(进场)
+private void onVerificationSuccessEntry() {
+ if (voicePlayerManager != null) {
+ voicePlayerManager.playVoice(VoiceType.VERIFICATION_SUCCESS_ENTRY);
+ }
+ // ... 其他核销成功逻辑
+}
+
+// 在订单核销成功时(离场)
+private void onVerificationSuccessLeave() {
+ if (voicePlayerManager != null) {
+ voicePlayerManager.playVoice(VoiceType.VERIFICATION_SUCCESS_LEAVE);
+ }
+ // ... 其他核销成功逻辑
+}
+
+// 在检测到多人时
+private void onMultiplePeopleDetected() {
+ if (voicePlayerManager != null) {
+ voicePlayerManager.playVoice(VoiceType.ONE_PERSON_ENTRY);
+ }
+ // ... 其他多人检测逻辑
+}
+```
+
+### 1.3 在Activity销毁时释放资源
+
+```java
+@Override
+protected void onDestroy() {
+ super.onDestroy();
+
+ // 释放语音播放器资源
+ if (voicePlayerManager != null) {
+ voicePlayerManager.release();
+ voicePlayerManager = null;
+ }
+
+ // ... 其他资源释放代码
+}
+```
+
+## 2. 在门禁控制模块中集成语音模块
+
+### 2.1 在GateABController中添加语音提示
+
+```java
+public class GateABController {
+ private VoicePlayerManager voicePlayerManager;
+
+ public void initialize(Context context) {
+ // ... 其他初始化代码
+
+ // 获取语音播放器实例
+ voicePlayerManager = VoicePlayerManager.getInstance();
+
+ LogManager.logInfo(TAG, "GateABController初始化完成");
+ }
+
+ /**
+ * 开门操作
+ */
+ public void openGateAB(GateControlCallback callback) {
+ LogManager.logInfo(TAG, "开始执行AB门开门操作");
+
+ // 播放开门提示语音
+ if (voicePlayerManager != null) {
+ // 根据场景判断播放进场还是离场语音
+ boolean isLeaveScene = VenueSceneUtils.isLeaveScene(context);
+ if (isLeaveScene) {
+ voicePlayerManager.playVoice(VoiceType.VERIFICATION_SUCCESS_LEAVE);
+ } else {
+ voicePlayerManager.playVoice(VoiceType.VERIFICATION_SUCCESS_ENTRY);
+ }
+ }
+
+ // ... 其他开门逻辑
+ }
+
+ /**
+ * AB门状态异常时播放提示
+ */
+ public void onGateStateAbnormal() {
+ if (voicePlayerManager != null) {
+ voicePlayerManager.playVoice(VoiceType.CLOSE_AB_GATE);
+ }
+ }
+}
+```
+
+## 3. 配置管理
+
+### 3.1 启用/禁用语音播放
+
+```java
+// 启用语音播放
+VoiceConfigManager configManager = voicePlayerManager.getConfigManager();
+configManager.setVoiceEnabled(true);
+
+// 禁用语音播放
+configManager.setVoiceEnabled(false);
+```
+
+### 3.2 调整语音音量
+
+```java
+// 设置音量(0.0-1.0)
+VoiceConfigManager configManager = voicePlayerManager.getConfigManager();
+configManager.setVoiceVolume(0.8f);
+```
+
+## 4. 最佳实践
+
+### 4.1 避免语音重叠播放
+
+```java
+// 在播放新语音前停止当前播放
+if (voicePlayerManager != null) {
+ voicePlayerManager.stopCurrentVoice();
+ voicePlayerManager.playVoice(VoiceType.FACE_RECOGNITION);
+}
+```
+
+### 4.2 根据场景选择合适的语音
+
+```java
+// 根据进场/离场场景选择语音
+boolean isLeaveScene = VenueSceneUtils.isLeaveScene(context);
+if (isLeaveScene) {
+ voicePlayerManager.playVoice(VoiceType.VERIFICATION_SUCCESS_LEAVE);
+} else {
+ voicePlayerManager.playVoice(VoiceType.VERIFICATION_SUCCESS_ENTRY);
+}
+```
+
+### 4.3 错误处理
+
+```java
+// 检查语音播放器是否初始化成功
+if (voicePlayerManager != null && voicePlayerManager.getConfigManager().isVoiceEnabled()) {
+ voicePlayerManager.playVoice(VoiceType.FACE_RECOGNITION);
+} else {
+ LogManager.logWarning(TAG, "语音播放器未初始化或已禁用");
+}
+```
+
+## 5. 测试建议
+
+1. 确保所有语音文件都已放置在assets/voices目录中
+2. 测试不同场景下的语音播放效果
+3. 测试语音开关功能
+4. 测试语音音量调节功能
+5. 测试异常情况下的错误处理
\ No newline at end of file
diff --git a/app/src/main/assets/voices/README.md b/app/src/main/assets/voices/README.md
new file mode 100644
index 0000000..1b85bbb
--- /dev/null
+++ b/app/src/main/assets/voices/README.md
@@ -0,0 +1,72 @@
+# 语音文件说明
+
+## 语音文件列表
+
+请将以下wav格式的语音文件放置在本目录中:
+
+1. `select_order_confirm.wav` - 请选择订单确认核销
+2. `inquire_order.wav` - 正在查询您的订单 --去除
+3. `face_recognition.wav` - 正在进行人脸识别
+4. `close_ab_gate.wav` - 请关闭ab门
+5. `verification_success_entry.wav` - 核销成功请推门 (进场-识别成功)
+6. `verification_success_leave.wav` - 离场请推门 (离场-识别成功)
+7. `one_person_entry.wav` - 请1人进场进行核销
+8. `order_expired.wav` - 订单超期需要扫码补缴
+9. `course_sign_success.wav` - 课程签到成功
+
+## 文件格式要求
+
+- 格式:WAV
+- 编码:PCM
+- 采样率:推荐16kHz或22.05kHz
+- 位深度:16位
+- 声道:单声道
+
+## 使用说明
+
+语音模块会自动从assets目录复制文件到应用内部存储,然后进行播放。
+请确保所有语音文件都已放置在本目录中,否则对应的语音提示将无法播放。
+
+## 在项目中使用语音模块
+
+1. 初始化语音播放管理器:
+```java
+VoicePlayerManager voicePlayerManager = VoicePlayerManager.getInstance();
+voicePlayerManager.initialize(context);
+```
+
+2. 播放指定语音:
+```java
+// 播放人脸识别语音
+voicePlayerManager.playVoice(VoiceType.FACE_RECOGNITION);
+
+// 播放核销成功语音(进场)
+voicePlayerManager.playVoice(VoiceType.VERIFICATION_SUCCESS_ENTRY);
+```
+
+3. 配置语音参数:
+```java
+VoiceConfigManager configManager = voicePlayerManager.getConfigManager();
+configManager.setVoiceEnabled(true); // 启用语音
+configManager.setVoiceVolume(0.8f); // 设置音量
+```
+
+## 集成到人脸识别流程
+
+语音模块已设计为可轻松集成到OXFaceOnlineActivity中:
+
+1. 在onCreate中初始化语音播放器
+2. 在关键流程节点调用相应的语音播放方法
+3. 在onDestroy中释放资源
+
+## 测试语音模块
+
+项目中包含VoiceTestActivity测试界面,可以用来测试所有语音文件的播放效果。
+
+## 注意事项
+
+1. 语音文件必须为WAV格式,否则可能无法正常播放
+2. 建议在应用启动时预加载所有语音文件以提升响应速度
+3. 可通过VoiceConfigManager配置语音开关和音量
+4. 语音播放为异步操作,不会阻塞主线程
+5. 详细的集成指南请参考IntegrationGuide.md文件
\ No newline at end of file
diff --git a/app/src/main/assets/voices/close_ab_gate.wav b/app/src/main/assets/voices/close_ab_gate.wav
new file mode 100644
index 0000000..1ceb473
Binary files /dev/null and b/app/src/main/assets/voices/close_ab_gate.wav differ
diff --git a/app/src/main/assets/voices/course_sign_success.wav b/app/src/main/assets/voices/course_sign_success.wav
new file mode 100644
index 0000000..a29b1a3
Binary files /dev/null and b/app/src/main/assets/voices/course_sign_success.wav differ
diff --git a/app/src/main/assets/voices/face_recognition.wav b/app/src/main/assets/voices/face_recognition.wav
new file mode 100644
index 0000000..a07b93c
Binary files /dev/null and b/app/src/main/assets/voices/face_recognition.wav differ
diff --git a/app/src/main/assets/voices/inquire_order.wav b/app/src/main/assets/voices/inquire_order.wav
new file mode 100644
index 0000000..e41bca0
Binary files /dev/null and b/app/src/main/assets/voices/inquire_order.wav differ
diff --git a/app/src/main/assets/voices/one_person_entry.wav b/app/src/main/assets/voices/one_person_entry.wav
new file mode 100644
index 0000000..25a48c8
Binary files /dev/null and b/app/src/main/assets/voices/one_person_entry.wav differ
diff --git a/app/src/main/assets/voices/order_expired.wav b/app/src/main/assets/voices/order_expired.wav
new file mode 100644
index 0000000..2b1b68a
Binary files /dev/null and b/app/src/main/assets/voices/order_expired.wav differ
diff --git a/app/src/main/assets/voices/select_order_confirm.wav b/app/src/main/assets/voices/select_order_confirm.wav
new file mode 100644
index 0000000..9d28792
Binary files /dev/null and b/app/src/main/assets/voices/select_order_confirm.wav differ
diff --git a/app/src/main/assets/test_shell_commands.sh b/app/src/main/assets/voices/test_shell_commands.sh
similarity index 100%
rename from app/src/main/assets/test_shell_commands.sh
rename to app/src/main/assets/voices/test_shell_commands.sh
diff --git a/app/src/main/assets/voices/verification_success_entry.wav b/app/src/main/assets/voices/verification_success_entry.wav
new file mode 100644
index 0000000..4f9c98a
Binary files /dev/null and b/app/src/main/assets/voices/verification_success_entry.wav differ
diff --git a/app/src/main/assets/voices/verification_success_leave.wav b/app/src/main/assets/voices/verification_success_leave.wav
new file mode 100644
index 0000000..529fc1e
Binary files /dev/null and b/app/src/main/assets/voices/verification_success_leave.wav differ
diff --git a/app/src/main/java/com/ouxuan/oxface/DebugActivity.java b/app/src/main/java/com/ouxuan/oxface/DebugActivity.java
index ba0bcd6..dd77f77 100644
--- a/app/src/main/java/com/ouxuan/oxface/DebugActivity.java
+++ b/app/src/main/java/com/ouxuan/oxface/DebugActivity.java
@@ -264,6 +264,32 @@ public class DebugActivity extends Activity {
testGateAB485PeopleNum();
}
});
+
+ // 语音测试按钮
+ Button btnTestVoice = findViewById(R.id.btnTestVoice);
+ btnTestVoice.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ testVoiceFunction();
+ }
+ });
+ }
+
+ /**
+ * 测试语音功能
+ */
+ private void testVoiceFunction() {
+ logMessage("启动语音测试...");
+ try {
+ Intent intent = new Intent(this, com.ouxuan.oxface.device.voice.VoiceTestActivity.class);
+ startActivity(intent);
+ logMessage("已启动语音测试界面");
+ showToast("已启动语音测试界面");
+ } catch (Exception e) {
+ Log.e(TAG, "启动语音测试失败", e);
+ logMessage("启动语音测试失败: " + e.getMessage());
+ showToast("启动语音测试失败");
+ }
}
/**
diff --git a/app/src/main/java/com/ouxuan/oxface/device/voice/AudioPlayer.java b/app/src/main/java/com/ouxuan/oxface/device/voice/AudioPlayer.java
new file mode 100644
index 0000000..0728a40
--- /dev/null
+++ b/app/src/main/java/com/ouxuan/oxface/device/voice/AudioPlayer.java
@@ -0,0 +1,145 @@
+package com.ouxuan.oxface.device.voice;
+
+import android.media.MediaPlayer;
+import android.media.AudioManager;
+import com.ouxuan.oxface.utils.LogManager;
+
+/**
+ * 音频播放器
+ * 负责实际的音频文件播放
+ */
+public class AudioPlayer {
+ private static final String TAG = "AudioPlayer";
+
+ private MediaPlayer mediaPlayer;
+ private OnVoicePlayListener playListener;
+ private String currentPlayingFile;
+
+ public interface OnVoicePlayListener {
+ void onPlayStart(String filePath);
+ void onPlayComplete(String filePath);
+ void onPlayError(String filePath, String error);
+ }
+
+ /**
+ * 设置播放监听器
+ */
+ public void setOnVoicePlayListener(OnVoicePlayListener listener) {
+ this.playListener = listener;
+ }
+
+ /**
+ * 播放音频文件
+ */
+ public void play(String filePath) {
+ try {
+ // 如果正在播放其他文件,先停止
+ if (mediaPlayer != null && mediaPlayer.isPlaying()) {
+ stop();
+ }
+
+ currentPlayingFile = filePath;
+
+ mediaPlayer = new MediaPlayer();
+ mediaPlayer.setDataSource(filePath);
+ mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ mediaPlayer.prepareAsync();
+
+ mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ mediaPlayer.start();
+ LogManager.logInfo(TAG, "开始播放音频: " + filePath);
+ if (playListener != null) {
+ playListener.onPlayStart(filePath);
+ }
+ }
+ });
+
+ mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ LogManager.logInfo(TAG, "音频播放完成: " + filePath);
+ if (playListener != null) {
+ playListener.onPlayComplete(filePath);
+ }
+ releaseMediaPlayer();
+ }
+ });
+
+ mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+ @Override
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ String errorMsg = "播放错误: what=" + what + ", extra=" + extra;
+ LogManager.logError(TAG, "音频播放失败: " + filePath + ", " + errorMsg);
+ if (playListener != null) {
+ playListener.onPlayError(filePath, errorMsg);
+ }
+ releaseMediaPlayer();
+ return true;
+ }
+ });
+
+ } catch (Exception e) {
+ String errorMsg = "播放异常: " + e.getMessage();
+ LogManager.logError(TAG, "音频播放异常: " + filePath + ", " + errorMsg, e);
+ if (playListener != null) {
+ playListener.onPlayError(filePath, errorMsg);
+ }
+ releaseMediaPlayer();
+ }
+ }
+
+ /**
+ * 停止当前播放
+ */
+ public void stop() {
+ if (mediaPlayer != null) {
+ try {
+ if (mediaPlayer.isPlaying()) {
+ mediaPlayer.stop();
+ LogManager.logInfo(TAG, "音频播放已停止: " + currentPlayingFile);
+ }
+ } catch (Exception e) {
+ LogManager.logError(TAG, "停止音频播放时发生异常", e);
+ }
+ }
+ releaseMediaPlayer();
+ }
+
+ /**
+ * 释放MediaPlayer资源
+ */
+ private void releaseMediaPlayer() {
+ if (mediaPlayer != null) {
+ try {
+ mediaPlayer.release();
+ } catch (Exception e) {
+ LogManager.logError(TAG, "释放MediaPlayer资源时发生异常", e);
+ }
+ mediaPlayer = null;
+ }
+ currentPlayingFile = null;
+ }
+
+ /**
+ * 释放所有资源
+ */
+ public void release() {
+ releaseMediaPlayer();
+ }
+
+ /**
+ * 检查是否正在播放
+ */
+ public boolean isPlaying() {
+ return mediaPlayer != null && mediaPlayer.isPlaying();
+ }
+
+ /**
+ * 获取当前播放的文件路径
+ */
+ public String getCurrentPlayingFile() {
+ return currentPlayingFile;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ouxuan/oxface/device/voice/VoiceConfigManager.java b/app/src/main/java/com/ouxuan/oxface/device/voice/VoiceConfigManager.java
new file mode 100644
index 0000000..bf253c3
--- /dev/null
+++ b/app/src/main/java/com/ouxuan/oxface/device/voice/VoiceConfigManager.java
@@ -0,0 +1,101 @@
+package com.ouxuan.oxface.device.voice;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import com.ouxuan.oxface.utils.LogManager;
+
+/**
+ * 语音配置管理器
+ * 负责管理语音播放的相关配置
+ */
+public class VoiceConfigManager {
+ private static final String TAG = "VoiceConfigManager";
+ private static final String PREF_NAME = "voice_config";
+ private static final String KEY_VOICE_ENABLED = "voice_enabled";
+ private static final String KEY_VOICE_VOLUME = "voice_volume";
+ private static final String KEY_VOICE_SPEED = "voice_speed";
+
+ private SharedPreferences sharedPreferences;
+
+ public VoiceConfigManager(Context context) {
+ sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
+ LogManager.logInfo(TAG, "VoiceConfigManager初始化完成");
+ }
+
+ /**
+ * 检查语音播放是否启用
+ */
+ public boolean isVoiceEnabled() {
+ boolean enabled = sharedPreferences.getBoolean(KEY_VOICE_ENABLED, true);
+ LogManager.logDebug(TAG, "语音播放状态: " + (enabled ? "启用" : "禁用"));
+ return enabled;
+ }
+
+ /**
+ * 设置语音播放启用状态
+ */
+ public void setVoiceEnabled(boolean enabled) {
+ sharedPreferences.edit().putBoolean(KEY_VOICE_ENABLED, enabled).apply();
+ LogManager.logInfo(TAG, "语音播放状态已设置为: " + (enabled ? "启用" : "禁用"));
+ }
+
+ /**
+ * 获取语音音量
+ */
+ public float getVoiceVolume() {
+ float volume = sharedPreferences.getFloat(KEY_VOICE_VOLUME, 1.0f);
+ LogManager.logDebug(TAG, "语音音量: " + volume);
+ return volume;
+ }
+
+ /**
+ * 设置语音音量
+ */
+ public void setVoiceVolume(float volume) {
+ // 限制音量范围在0.0-1.0之间
+ volume = Math.max(0.0f, Math.min(1.0f, volume));
+ sharedPreferences.edit().putFloat(KEY_VOICE_VOLUME, volume).apply();
+ LogManager.logInfo(TAG, "语音音量已设置为: " + volume);
+ }
+
+ /**
+ * 获取语音播放速度
+ */
+ public float getVoiceSpeed() {
+ float speed = sharedPreferences.getFloat(KEY_VOICE_SPEED, 1.0f);
+ LogManager.logDebug(TAG, "语音播放速度: " + speed);
+ return speed;
+ }
+
+ /**
+ * 设置语音播放速度
+ */
+ public void setVoiceSpeed(float speed) {
+ // 限制速度范围在0.5-2.0之间
+ speed = Math.max(0.5f, Math.min(2.0f, speed));
+ sharedPreferences.edit().putFloat(KEY_VOICE_SPEED, speed).apply();
+ LogManager.logInfo(TAG, "语音播放速度已设置为: " + speed);
+ }
+
+ /**
+ * 重置为默认配置
+ */
+ public void resetToDefault() {
+ sharedPreferences.edit()
+ .putBoolean(KEY_VOICE_ENABLED, true)
+ .putFloat(KEY_VOICE_VOLUME, 1.0f)
+ .putFloat(KEY_VOICE_SPEED, 1.0f)
+ .apply();
+ LogManager.logInfo(TAG, "语音配置已重置为默认值");
+ }
+
+ /**
+ * 获取所有配置信息
+ */
+ public String getAllConfigInfo() {
+ return "语音配置信息: " +
+ "启用=" + isVoiceEnabled() +
+ ", 音量=" + getVoiceVolume() +
+ ", 速度=" + getVoiceSpeed();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ouxuan/oxface/device/voice/VoicePlayerManager.java b/app/src/main/java/com/ouxuan/oxface/device/voice/VoicePlayerManager.java
new file mode 100644
index 0000000..2ec9caf
--- /dev/null
+++ b/app/src/main/java/com/ouxuan/oxface/device/voice/VoicePlayerManager.java
@@ -0,0 +1,118 @@
+package com.ouxuan.oxface.device.voice;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.os.Handler;
+import android.os.Looper;
+import com.ouxuan.oxface.utils.LogManager;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * 语音播放管理器
+ * 负责管理预录制语音文件的播放
+ */
+public class VoicePlayerManager {
+ private static final String TAG = "VoicePlayerManager";
+ private static volatile VoicePlayerManager instance;
+
+ private Context context;
+ private AudioPlayer audioPlayer;
+ private VoiceResourceManager resourceManager;
+ private VoiceConfigManager configManager;
+ private ExecutorService executorService;
+ private Handler mainHandler;
+
+ private VoicePlayerManager() {
+ executorService = Executors.newSingleThreadExecutor();
+ mainHandler = new Handler(Looper.getMainLooper());
+ }
+
+ /**
+ * 获取单例实例
+ */
+ public static VoicePlayerManager getInstance() {
+ if (instance == null) {
+ synchronized (VoicePlayerManager.class) {
+ if (instance == null) {
+ instance = new VoicePlayerManager();
+ }
+ }
+ }
+ return instance;
+ }
+
+ /**
+ * 初始化语音播放管理器
+ */
+ public void initialize(Context context) {
+ this.context = context.getApplicationContext();
+ this.audioPlayer = new AudioPlayer();
+ this.resourceManager = new VoiceResourceManager(context);
+ this.configManager = new VoiceConfigManager(context);
+
+ LogManager.logInfo(TAG, "VoicePlayerManager初始化完成");
+ }
+
+ /**
+ * 播放指定语音
+ */
+ public void playVoice(VoiceType voiceType) {
+ if (!configManager.isVoiceEnabled()) {
+ LogManager.logDebug(TAG, "语音播放已禁用,跳过播放: " + voiceType.getFileName());
+ return;
+ }
+
+ String filePath = resourceManager.getVoiceFilePath(voiceType);
+ if (filePath != null) {
+ LogManager.logInfo(TAG, "播放语音: " + voiceType.getFileName());
+ audioPlayer.play(filePath);
+ } else {
+ LogManager.logWarning(TAG, "未找到语音文件: " + voiceType.getFileName());
+ }
+ }
+
+ /**
+ * 停止当前播放
+ */
+ public void stopCurrentVoice() {
+ if (audioPlayer != null) {
+ audioPlayer.stop();
+ }
+ }
+
+ /**
+ * 释放资源
+ */
+ public void release() {
+ if (audioPlayer != null) {
+ audioPlayer.release();
+ }
+ if (executorService != null && !executorService.isShutdown()) {
+ executorService.shutdown();
+ }
+ instance = null;
+ LogManager.logInfo(TAG, "VoicePlayerManager资源已释放");
+ }
+
+ /**
+ * 获取音频播放器实例(供外部使用)
+ */
+ public AudioPlayer getAudioPlayer() {
+ return audioPlayer;
+ }
+
+ /**
+ * 获取资源配置管理器
+ */
+ public VoiceResourceManager getResourceManager() {
+ return resourceManager;
+ }
+
+ /**
+ * 获取配置管理器
+ */
+ public VoiceConfigManager getConfigManager() {
+ return configManager;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ouxuan/oxface/device/voice/VoicePlayerUsageExample.java b/app/src/main/java/com/ouxuan/oxface/device/voice/VoicePlayerUsageExample.java
new file mode 100644
index 0000000..de92fa2
--- /dev/null
+++ b/app/src/main/java/com/ouxuan/oxface/device/voice/VoicePlayerUsageExample.java
@@ -0,0 +1,133 @@
+package com.ouxuan.oxface.device.voice;
+
+import android.content.Context;
+import com.ouxuan.oxface.utils.LogManager;
+
+/**
+ * 语音播放使用示例
+ * 展示如何在项目中使用语音播放模块
+ */
+public class VoicePlayerUsageExample {
+ private static final String TAG = "VoicePlayerUsageExample";
+
+ private VoicePlayerManager voicePlayerManager;
+ private Context context;
+
+ public VoicePlayerUsageExample(Context context) {
+ this.context = context;
+ }
+
+ /**
+ * 初始化语音播放器
+ */
+ public void initializeVoicePlayer() {
+ try {
+ voicePlayerManager = VoicePlayerManager.getInstance();
+ voicePlayerManager.initialize(context);
+
+ // 设置播放监听器
+ voicePlayerManager.getAudioPlayer().setOnVoicePlayListener(new AudioPlayer.OnVoicePlayListener() {
+ @Override
+ public void onPlayStart(String filePath) {
+ LogManager.logInfo(TAG, "语音开始播放: " + filePath);
+ }
+
+ @Override
+ public void onPlayComplete(String filePath) {
+ LogManager.logInfo(TAG, "语音播放完成: " + filePath);
+ }
+
+ @Override
+ public void onPlayError(String filePath, String error) {
+ LogManager.logError(TAG, "语音播放错误: " + filePath + ", 错误信息: " + error);
+ }
+ });
+
+ LogManager.logInfo(TAG, "语音播放器初始化成功");
+ } catch (Exception e) {
+ LogManager.logError(TAG, "语音播放器初始化失败", e);
+ }
+ }
+
+ /**
+ * 演示各种语音播放场景
+ */
+ public void demonstrateVoicePlayback() {
+ if (voicePlayerManager == null) {
+ LogManager.logError(TAG, "语音播放器未初始化");
+ return;
+ }
+
+ // 1. 订单相关语音
+ voicePlayerManager.playVoice(VoiceType.SELECT_ORDER_CONFIRM);
+
+ // 2. 人脸识别相关语音
+ voicePlayerManager.playVoice(VoiceType.FACE_RECOGNITION);
+
+ // 3. 门禁相关语音
+ voicePlayerManager.playVoice(VoiceType.CLOSE_AB_GATE);
+
+ // 4. 核销成功相关语音(根据场景选择)
+ // 进场场景
+ voicePlayerManager.playVoice(VoiceType.VERIFICATION_SUCCESS_ENTRY);
+
+ // 离场场景
+ // voicePlayerManager.playVoice(VoiceType.VERIFICATION_SUCCESS_LEAVE);
+
+ // 5. 其他场景语音
+ voicePlayerManager.playVoice(VoiceType.ONE_PERSON_ENTRY);
+ voicePlayerManager.playVoice(VoiceType.ORDER_EXPIRED);
+ voicePlayerManager.playVoice(VoiceType.COURSE_SIGN_SUCCESS);
+ }
+
+ /**
+ * 配置语音播放参数
+ */
+ public void configureVoicePlayback() {
+ if (voicePlayerManager == null) {
+ LogManager.logError(TAG, "语音播放器未初始化");
+ return;
+ }
+
+ // 获取配置管理器
+ VoiceConfigManager configManager = voicePlayerManager.getConfigManager();
+
+ // 启用语音播放
+ configManager.setVoiceEnabled(true);
+
+ // 设置音量
+ configManager.setVoiceVolume(0.8f);
+
+ // 设置播放速度
+ configManager.setVoiceSpeed(1.0f);
+
+ LogManager.logInfo(TAG, "语音播放配置已完成: " + configManager.getAllConfigInfo());
+ }
+
+ /**
+ * 预加载所有语音文件
+ */
+ public void preloadAllVoices() {
+ if (voicePlayerManager == null) {
+ LogManager.logError(TAG, "语音播放器未初始化");
+ return;
+ }
+
+ // 获取资源管理器并预加载所有语音文件
+ VoiceResourceManager resourceManager = voicePlayerManager.getResourceManager();
+ resourceManager.preloadAllVoices();
+
+ LogManager.logInfo(TAG, "所有语音文件预加载完成");
+ }
+
+ /**
+ * 释放语音播放器资源
+ */
+ public void releaseVoicePlayer() {
+ if (voicePlayerManager != null) {
+ voicePlayerManager.release();
+ voicePlayerManager = null;
+ LogManager.logInfo(TAG, "语音播放器资源已释放");
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ouxuan/oxface/device/voice/VoiceResourceManager.java b/app/src/main/java/com/ouxuan/oxface/device/voice/VoiceResourceManager.java
new file mode 100644
index 0000000..f2cedb1
--- /dev/null
+++ b/app/src/main/java/com/ouxuan/oxface/device/voice/VoiceResourceManager.java
@@ -0,0 +1,134 @@
+package com.ouxuan.oxface.device.voice;
+
+import android.content.Context;
+import android.os.Environment;
+import com.ouxuan.oxface.utils.LogManager;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 语音资源管理器
+ * 负责管理语音文件资源的加载和路径获取
+ */
+public class VoiceResourceManager {
+ private static final String TAG = "VoiceResourceManager";
+ private static final String VOICE_ASSETS_DIR = "voices";
+ private static final String VOICE_STORAGE_DIR = "oxface_voices";
+
+ private Context context;
+ private Map voicePathMap;
+
+ public VoiceResourceManager(Context context) {
+ this.context = context.getApplicationContext();
+ this.voicePathMap = new HashMap<>();
+ initVoiceResources();
+ }
+
+ /**
+ * 初始化语音资源
+ */
+ private void initVoiceResources() {
+ LogManager.logInfo(TAG, "初始化语音资源");
+
+ // 为每种语音类型初始化文件路径
+ for (VoiceType voiceType : VoiceType.values()) {
+ String filePath = getInternalStoragePath(voiceType.getFileName());
+ voicePathMap.put(voiceType, filePath);
+ LogManager.logDebug(TAG, "语音资源映射: " + voiceType.name() + " -> " + filePath);
+ }
+
+ LogManager.logInfo(TAG, "语音资源初始化完成,共" + voicePathMap.size() + "个语音文件");
+ }
+
+ /**
+ * 获取语音文件路径
+ */
+ public String getVoiceFilePath(VoiceType voiceType) {
+ String filePath = voicePathMap.get(voiceType);
+ if (filePath == null) {
+ LogManager.logWarning(TAG, "未找到语音类型对应的文件路径: " + voiceType.name());
+ return null;
+ }
+
+ // 检查文件是否存在
+ File voiceFile = new File(filePath);
+ if (!voiceFile.exists()) {
+ LogManager.logWarning(TAG, "语音文件不存在,尝试从assets复制: " + filePath);
+ if (!copyAssetToInternalStorage(voiceType.getFileName())) {
+ LogManager.logError(TAG, "从assets复制语音文件失败: " + voiceType.getFileName());
+ return null;
+ }
+ }
+
+ return filePath;
+ }
+
+ /**
+ * 获取内部存储路径
+ */
+ private String getInternalStoragePath(String fileName) {
+ File voiceDir = new File(context.getFilesDir(), VOICE_STORAGE_DIR);
+ if (!voiceDir.exists()) {
+ voiceDir.mkdirs();
+ }
+ return new File(voiceDir, fileName).getAbsolutePath();
+ }
+
+ /**
+ * 从assets复制文件到内部存储
+ */
+ private boolean copyAssetToInternalStorage(String fileName) {
+ String assetPath = VOICE_ASSETS_DIR + "/" + fileName;
+ String internalPath = getInternalStoragePath(fileName);
+
+ try {
+ InputStream inputStream = context.getAssets().open(assetPath);
+ FileOutputStream outputStream = new FileOutputStream(internalPath);
+
+ byte[] buffer = new byte[1024];
+ int length;
+ while ((length = inputStream.read(buffer)) > 0) {
+ outputStream.write(buffer, 0, length);
+ }
+
+ inputStream.close();
+ outputStream.close();
+
+ LogManager.logInfo(TAG, "语音文件复制成功: " + assetPath + " -> " + internalPath);
+ return true;
+ } catch (IOException e) {
+ LogManager.logError(TAG, "复制语音文件失败: " + assetPath, e);
+ return false;
+ }
+ }
+
+ /**
+ * 预加载所有语音文件
+ */
+ public void preloadAllVoices() {
+ LogManager.logInfo(TAG, "开始预加载所有语音文件");
+ int successCount = 0;
+ int failCount = 0;
+
+ for (VoiceType voiceType : VoiceType.values()) {
+ if (getVoiceFilePath(voiceType) != null) {
+ successCount++;
+ } else {
+ failCount++;
+ }
+ }
+
+ LogManager.logInfo(TAG, "语音文件预加载完成 - 成功: " + successCount + ", 失败: " + failCount);
+ }
+
+ /**
+ * 获取语音文件存储目录
+ */
+ public String getVoiceStorageDirectory() {
+ return new File(context.getFilesDir(), VOICE_STORAGE_DIR).getAbsolutePath();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ouxuan/oxface/device/voice/VoiceTestActivity.java b/app/src/main/java/com/ouxuan/oxface/device/voice/VoiceTestActivity.java
new file mode 100644
index 0000000..5584bcf
--- /dev/null
+++ b/app/src/main/java/com/ouxuan/oxface/device/voice/VoiceTestActivity.java
@@ -0,0 +1,142 @@
+package com.ouxuan.oxface.device.voice;
+
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import androidx.appcompat.app.AppCompatActivity;
+import com.ouxuan.oxface.R;
+import com.ouxuan.oxface.utils.LogManager;
+
+/**
+ * 语音模块测试Activity
+ * 用于测试语音播放功能
+ */
+public class VoiceTestActivity extends AppCompatActivity {
+ private static final String TAG = "VoiceTestActivity";
+
+ private VoicePlayerManager voicePlayerManager;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_voice_test);
+
+ initVoicePlayer();
+ initViews();
+ LogManager.logInfo(TAG, "VoiceTestActivity创建");
+ }
+
+ /**
+ * 初始化语音播放器
+ */
+ private void initVoicePlayer() {
+ try {
+ voicePlayerManager = VoicePlayerManager.getInstance();
+ voicePlayerManager.initialize(this);
+
+ // 设置播放监听器
+ voicePlayerManager.getAudioPlayer().setOnVoicePlayListener(new AudioPlayer.OnVoicePlayListener() {
+ @Override
+ public void onPlayStart(String filePath) {
+ LogManager.logInfo(TAG, "语音开始播放: " + filePath);
+ }
+
+ @Override
+ public void onPlayComplete(String filePath) {
+ LogManager.logInfo(TAG, "语音播放完成: " + filePath);
+ }
+
+ @Override
+ public void onPlayError(String filePath, String error) {
+ LogManager.logError(TAG, "语音播放错误: " + filePath + ", 错误信息: " + error);
+ }
+ });
+
+ LogManager.logInfo(TAG, "语音播放器初始化成功");
+ } catch (Exception e) {
+ LogManager.logError(TAG, "语音播放器初始化失败", e);
+ }
+ }
+
+ /**
+ * 初始化视图
+ */
+ private void initViews() {
+ // 订单相关按钮
+ Button btnSelectOrder = findViewById(R.id.btn_select_order);
+ Button btnInquireOrder = findViewById(R.id.btn_inquire_order);
+
+ // 人脸识别相关按钮
+ Button btnFaceRecognition = findViewById(R.id.btn_face_recognition);
+
+ // 门禁相关按钮
+ Button btnCloseAbGate = findViewById(R.id.btn_close_ab_gate);
+
+ // 核销成功相关按钮
+ Button btnVerificationEntry = findViewById(R.id.btn_verification_entry);
+ Button btnVerificationLeave = findViewById(R.id.btn_verification_leave);
+
+ // 其他场景按钮
+ Button btnOnePerson = findViewById(R.id.btn_one_person);
+ Button btnOrderExpired = findViewById(R.id.btn_order_expired);
+ Button btnCourseSign = findViewById(R.id.btn_course_sign);
+
+ // 控制按钮
+ Button btnEnableVoice = findViewById(R.id.btn_enable_voice);
+ Button btnDisableVoice = findViewById(R.id.btn_disable_voice);
+ Button btnStopVoice = findViewById(R.id.btn_stop_voice);
+
+ // 设置点击事件
+ btnSelectOrder.setOnClickListener(v -> playVoice(VoiceType.SELECT_ORDER_CONFIRM));
+ btnInquireOrder.setOnClickListener(v -> playVoice(VoiceType.INQUIRE_ORDER));
+ btnFaceRecognition.setOnClickListener(v -> playVoice(VoiceType.FACE_RECOGNITION));
+ btnCloseAbGate.setOnClickListener(v -> playVoice(VoiceType.CLOSE_AB_GATE));
+ btnVerificationEntry.setOnClickListener(v -> playVoice(VoiceType.VERIFICATION_SUCCESS_ENTRY));
+ btnVerificationLeave.setOnClickListener(v -> playVoice(VoiceType.VERIFICATION_SUCCESS_LEAVE));
+ btnOnePerson.setOnClickListener(v -> playVoice(VoiceType.ONE_PERSON_ENTRY));
+ btnOrderExpired.setOnClickListener(v -> playVoice(VoiceType.ORDER_EXPIRED));
+ btnCourseSign.setOnClickListener(v -> playVoice(VoiceType.COURSE_SIGN_SUCCESS));
+
+ btnEnableVoice.setOnClickListener(v -> setVoiceEnabled(true));
+ btnDisableVoice.setOnClickListener(v -> setVoiceEnabled(false));
+ btnStopVoice.setOnClickListener(v -> stopCurrentVoice());
+ }
+
+ /**
+ * 播放指定语音
+ */
+ private void playVoice(VoiceType voiceType) {
+ if (voicePlayerManager != null) {
+ voicePlayerManager.playVoice(voiceType);
+ }
+ }
+
+ /**
+ * 设置语音启用状态
+ */
+ private void setVoiceEnabled(boolean enabled) {
+ if (voicePlayerManager != null) {
+ voicePlayerManager.getConfigManager().setVoiceEnabled(enabled);
+ LogManager.logInfo(TAG, "语音播放已" + (enabled ? "启用" : "禁用"));
+ }
+ }
+
+ /**
+ * 停止当前语音播放
+ */
+ private void stopCurrentVoice() {
+ if (voicePlayerManager != null) {
+ voicePlayerManager.stopCurrentVoice();
+ LogManager.logInfo(TAG, "已停止当前语音播放");
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (voicePlayerManager != null) {
+ voicePlayerManager.release();
+ }
+ LogManager.logInfo(TAG, "VoiceTestActivity销毁");
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ouxuan/oxface/device/voice/VoiceType.java b/app/src/main/java/com/ouxuan/oxface/device/voice/VoiceType.java
new file mode 100644
index 0000000..b2a4122
--- /dev/null
+++ b/app/src/main/java/com/ouxuan/oxface/device/voice/VoiceType.java
@@ -0,0 +1,46 @@
+package com.ouxuan.oxface.device.voice;
+
+/**
+ * 语音类型枚举
+ * 定义所有可用的语音提示类型
+ */
+public enum VoiceType {
+ // 订单相关
+ SELECT_ORDER_CONFIRM("请选择订单确认核销", "select_order_confirm.wav"),
+ INQUIRE_ORDER("正在查询您的订单", "inquire_order.wav"),
+
+ // 人脸识别相关
+ FACE_RECOGNITION("正在进行人脸识别", "face_recognition.wav"),
+
+ // 门禁相关
+ CLOSE_AB_GATE("请关闭ab门", "close_ab_gate.wav"),
+
+ // 核销成功相关
+ VERIFICATION_SUCCESS_ENTRY("核销成功请推门", "verification_success_entry.wav"), // 进场-识别成功
+ VERIFICATION_SUCCESS_LEAVE("离场请推门", "verification_success_leave.wav"), // 离场-识别成功
+
+ // 人数相关
+ ONE_PERSON_ENTRY("请1人进场进行核销", "one_person_entry.wav"),
+
+ // 订单状态相关
+ ORDER_EXPIRED("订单超期需要扫码补缴", "order_expired.wav"),
+
+ // 课程相关
+ COURSE_SIGN_SUCCESS("课程签到成功", "course_sign_success.wav");
+
+ private final String description;
+ private final String fileName;
+
+ VoiceType(String description, String fileName) {
+ this.description = description;
+ this.fileName = fileName;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_debug.xml b/app/src/main/res/layout/activity_debug.xml
index 33e2db0..22dcbfd 100644
--- a/app/src/main/res/layout/activity_debug.xml
+++ b/app/src/main/res/layout/activity_debug.xml
@@ -287,6 +287,37 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_voice_test.xml b/app/src/main/res/layout/activity_voice_test.xml
new file mode 100644
index 0000000..2873f16
--- /dev/null
+++ b/app/src/main/res/layout/activity_voice_test.xml
@@ -0,0 +1,198 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file