24 changed files with 1398 additions and 23 deletions
-
6app/src/main/AndroidManifest.xml
-
223app/src/main/assets/voices/IntegrationGuide.md
-
72app/src/main/assets/voices/README.md
-
BINapp/src/main/assets/voices/close_ab_gate.wav
-
BINapp/src/main/assets/voices/course_sign_success.wav
-
BINapp/src/main/assets/voices/face_recognition.wav
-
BINapp/src/main/assets/voices/inquire_order.wav
-
BINapp/src/main/assets/voices/one_person_entry.wav
-
BINapp/src/main/assets/voices/order_expired.wav
-
BINapp/src/main/assets/voices/select_order_confirm.wav
-
0app/src/main/assets/voices/test_shell_commands.sh
-
BINapp/src/main/assets/voices/verification_success_entry.wav
-
BINapp/src/main/assets/voices/verification_success_leave.wav
-
26app/src/main/java/com/ouxuan/oxface/DebugActivity.java
-
145app/src/main/java/com/ouxuan/oxface/device/voice/AudioPlayer.java
-
101app/src/main/java/com/ouxuan/oxface/device/voice/VoiceConfigManager.java
-
118app/src/main/java/com/ouxuan/oxface/device/voice/VoicePlayerManager.java
-
133app/src/main/java/com/ouxuan/oxface/device/voice/VoicePlayerUsageExample.java
-
134app/src/main/java/com/ouxuan/oxface/device/voice/VoiceResourceManager.java
-
142app/src/main/java/com/ouxuan/oxface/device/voice/VoiceTestActivity.java
-
46app/src/main/java/com/ouxuan/oxface/device/voice/VoiceType.java
-
31app/src/main/res/layout/activity_debug.xml
-
198app/src/main/res/layout/activity_voice_test.xml
@ -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. 测试异常情况下的错误处理 |
@ -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文件 |
@ -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; |
|||
} |
|||
} |
@ -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(); |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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, "语音播放器资源已释放"); |
|||
} |
|||
} |
|||
} |
@ -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<VoiceType, String> 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(); |
|||
} |
|||
} |
@ -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销毁"); |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -0,0 +1,198 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:padding="16dp"> |
|||
|
|||
<LinearLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:orientation="vertical"> |
|||
|
|||
<!-- 标题 --> |
|||
<TextView |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:text="语音模块测试" |
|||
android:textSize="24sp" |
|||
android:textStyle="bold" |
|||
android:gravity="center" |
|||
android:layout_marginBottom="24dp" /> |
|||
|
|||
<!-- 订单相关 --> |
|||
<TextView |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:text="订单相关" |
|||
android:textSize="18sp" |
|||
android:textStyle="bold" |
|||
android:layout_marginBottom="8dp" /> |
|||
|
|||
<LinearLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:orientation="horizontal" |
|||
android:layout_marginBottom="16dp"> |
|||
|
|||
<Button |
|||
android:id="@+id/btn_select_order" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_weight="1" |
|||
android:text="选择订单" |
|||
android:layout_marginEnd="8dp" /> |
|||
|
|||
<Button |
|||
android:id="@+id/btn_inquire_order" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_weight="1" |
|||
android:text="查询订单" |
|||
android:layout_marginStart="8dp" /> |
|||
</LinearLayout> |
|||
|
|||
<!-- 人脸识别相关 --> |
|||
<TextView |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:text="人脸识别相关" |
|||
android:textSize="18sp" |
|||
android:textStyle="bold" |
|||
android:layout_marginBottom="8dp" |
|||
android:layout_marginTop="16dp" /> |
|||
|
|||
<Button |
|||
android:id="@+id/btn_face_recognition" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:text="人脸识别" |
|||
android:layout_marginBottom="16dp" /> |
|||
|
|||
<!-- 门禁相关 --> |
|||
<TextView |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:text="门禁相关" |
|||
android:textSize="18sp" |
|||
android:textStyle="bold" |
|||
android:layout_marginBottom="8dp" /> |
|||
|
|||
<Button |
|||
android:id="@+id/btn_close_ab_gate" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:text="关闭AB门" |
|||
android:layout_marginBottom="16dp" /> |
|||
|
|||
<!-- 核销成功相关 --> |
|||
<TextView |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:text="核销成功相关" |
|||
android:textSize="18sp" |
|||
android:textStyle="bold" |
|||
android:layout_marginBottom="8dp" /> |
|||
|
|||
<LinearLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:orientation="horizontal" |
|||
android:layout_marginBottom="16dp"> |
|||
|
|||
<Button |
|||
android:id="@+id/btn_verification_entry" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_weight="1" |
|||
android:text="进场核销成功" |
|||
android:layout_marginEnd="8dp" /> |
|||
|
|||
<Button |
|||
android:id="@+id/btn_verification_leave" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_weight="1" |
|||
android:text="离场核销成功" |
|||
android:layout_marginStart="8dp" /> |
|||
</LinearLayout> |
|||
|
|||
<!-- 其他场景 --> |
|||
<TextView |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:text="其他场景" |
|||
android:textSize="18sp" |
|||
android:textStyle="bold" |
|||
android:layout_marginBottom="8dp" /> |
|||
|
|||
<LinearLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:orientation="vertical" |
|||
android:layout_marginBottom="16dp"> |
|||
|
|||
<Button |
|||
android:id="@+id/btn_one_person" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:text="请1人进场" |
|||
android:layout_marginBottom="8dp" /> |
|||
|
|||
<Button |
|||
android:id="@+id/btn_order_expired" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:text="订单超期" |
|||
android:layout_marginBottom="8dp" /> |
|||
|
|||
<Button |
|||
android:id="@+id/btn_course_sign" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:text="课程签到成功" /> |
|||
</LinearLayout> |
|||
|
|||
<!-- 控制按钮 --> |
|||
<TextView |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:text="控制选项" |
|||
android:textSize="18sp" |
|||
android:textStyle="bold" |
|||
android:layout_marginBottom="8dp" |
|||
android:layout_marginTop="16dp" /> |
|||
|
|||
<LinearLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:orientation="horizontal" |
|||
android:layout_marginBottom="16dp"> |
|||
|
|||
<Button |
|||
android:id="@+id/btn_enable_voice" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_weight="1" |
|||
android:text="启用语音" |
|||
android:layout_marginEnd="8dp" /> |
|||
|
|||
<Button |
|||
android:id="@+id/btn_disable_voice" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_weight="1" |
|||
android:text="禁用语音" |
|||
android:layout_marginStart="8dp" |
|||
android:layout_marginEnd="8dp" /> |
|||
|
|||
<Button |
|||
android:id="@+id/btn_stop_voice" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_weight="1" |
|||
android:text="停止播放" |
|||
android:layout_marginStart="8dp" /> |
|||
</LinearLayout> |
|||
|
|||
</LinearLayout> |
|||
</ScrollView> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue