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 @@ + + + +