Compare commits
8 Commits
b3c7e243b5
...
e9147b1527
| Author | SHA1 | Date |
|---|---|---|
|
|
e9147b1527 | |
|
|
cc73549ebd | |
|
|
38c7494d73 | |
|
|
bfa19d36ac | |
|
|
d7e9b29513 | |
|
|
59f48520a2 | |
|
|
f01cc99952 | |
|
|
e5a6d073cd |
|
|
@ -238,6 +238,7 @@
|
||||||
<button class="btn ptt-btn ptt-action-btn" style="background: #0ea5e9;" onclick="pttGetCurrentGroup()">当前群组</button>
|
<button class="btn ptt-btn ptt-action-btn" style="background: #0ea5e9;" onclick="pttGetCurrentGroup()">当前群组</button>
|
||||||
<button class="btn ptt-btn ptt-action-btn btn-orange" onclick="pttForceRelease()" disabled>强制释放</button>
|
<button class="btn ptt-btn ptt-action-btn btn-orange" onclick="pttForceRelease()" disabled>强制释放</button>
|
||||||
<button class="btn ptt-btn ptt-action-btn" style="background: #000; color: #fca5a5;" onclick="pttSimulateConflict()" disabled>模拟冲突</button>
|
<button class="btn ptt-btn ptt-action-btn" style="background: #000; color: #fca5a5;" onclick="pttSimulateConflict()" disabled>模拟冲突</button>
|
||||||
|
<button class="btn ptt-btn ptt-action-btn" style="background: #10b981;" onclick="queryTempGroupId()">查询临时群 GID</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="ptt-result" class="log-container" style="background: #0f172a; margin-top: 10px; height: 120px;">
|
<div id="ptt-result" class="log-container" style="background: #0f172a; margin-top: 10px; height: 120px;">
|
||||||
|
|
@ -1143,6 +1144,24 @@
|
||||||
showPttResult("已发送 AT 指令: " + cmd);
|
showPttResult("已发送 AT 指令: " + cmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 注册临时群锁定回调
|
||||||
|
window.startPullGroupLock = function(gid) {
|
||||||
|
showPttResult("收到临时群锁定回调 startPullGroupLock, GID: " + gid);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.endPullGroupLock = function() {
|
||||||
|
showPttResult("收到临时群解锁回调 endPullGroupLock");
|
||||||
|
};
|
||||||
|
|
||||||
|
function queryTempGroupId() {
|
||||||
|
if (window.android && window.android.getTempGroupId) {
|
||||||
|
var gid = window.android.getTempGroupId();
|
||||||
|
showPttResult("当前在临时群的 GID: " + (gid || "无"));
|
||||||
|
} else {
|
||||||
|
showPttResult("接口 getTempGroupId 不可用");
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
"showJsonDialog" -> { showJsonDialog(args.optString(0, "")); "" }
|
"showJsonDialog" -> { showJsonDialog(args.optString(0, "")); "" }
|
||||||
"getTempCallStatus" -> getTempCallStatus()
|
"getTempCallStatus" -> getTempCallStatus()
|
||||||
"maximizeTempCall" -> { maximizeTempCall(); "" }
|
"maximizeTempCall" -> { maximizeTempCall(); "" }
|
||||||
|
"getTempGroupId" -> getTempGroupId()
|
||||||
"logout" -> { logout(); "" }
|
"logout" -> { logout(); "" }
|
||||||
else -> {
|
else -> {
|
||||||
Timber.tag("Bridge").w("Unknown method: $method")
|
Timber.tag("Bridge").w("Unknown method: $method")
|
||||||
|
|
@ -500,6 +501,10 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getTempGroupId(): String {
|
||||||
|
return TempCallActivity.currentTempGroupId
|
||||||
|
}
|
||||||
|
|
||||||
fun maximizeTempCall() {
|
fun maximizeTempCall() {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ class TempCallActivity : AppCompatActivity() {
|
||||||
private val MIN_SPEAK_DURATION = 200L
|
private val MIN_SPEAK_DURATION = 200L
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
private var pulseAnimator: ObjectAnimator? = null
|
private var pulseAnimator: ObjectAnimator? = null
|
||||||
|
private var alertPlayer: android.media.MediaPlayer? = null
|
||||||
|
private var alertPlayCount = 0
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@Volatile
|
@Volatile
|
||||||
|
|
@ -40,6 +42,9 @@ class TempCallActivity : AppCompatActivity() {
|
||||||
@Volatile
|
@Volatile
|
||||||
var currentGroupName: String = "临时会话"
|
var currentGroupName: String = "临时会话"
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
var currentTempGroupId: String = ""
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun isOpen(): Boolean = instance != null
|
fun isOpen(): Boolean = instance != null
|
||||||
|
|
||||||
|
|
@ -76,7 +81,9 @@ class TempCallActivity : AppCompatActivity() {
|
||||||
fun clearMinimizedState() {
|
fun clearMinimizedState() {
|
||||||
isMinimized = false
|
isMinimized = false
|
||||||
currentGroupName = "临时会话"
|
currentGroupName = "临时会话"
|
||||||
|
currentTempGroupId = ""
|
||||||
MainActivity.executeJs("onTempCallStateChanged('destroyed')")
|
MainActivity.executeJs("onTempCallStateChanged('destroyed')")
|
||||||
|
MainActivity.executeJs("if(window.endPullGroupLock){ window.endPullGroupLock(); }")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,12 +103,16 @@ class TempCallActivity : AppCompatActivity() {
|
||||||
|
|
||||||
val groupName = intent.getStringExtra("group_name") ?: "临时会话"
|
val groupName = intent.getStringExtra("group_name") ?: "临时会话"
|
||||||
currentGroupName = groupName
|
currentGroupName = groupName
|
||||||
|
currentTempGroupId = com.example.kingway.ptt.SendAtUtil.str2HexStr(groupName)
|
||||||
|
MainActivity.executeJs("if(window.startPullGroupLock){ window.startPullGroupLock('$currentTempGroupId'); }")
|
||||||
findViewById<TextView>(R.id.tv_group_name).text = groupName
|
findViewById<TextView>(R.id.tv_group_name).text = groupName
|
||||||
|
|
||||||
setupMinimizeButton()
|
setupMinimizeButton()
|
||||||
setupPttButton()
|
setupPttButton()
|
||||||
setupHangupButton()
|
setupHangupButton()
|
||||||
startPulseAnimation()
|
startPulseAnimation()
|
||||||
|
startVoiceAlert()
|
||||||
|
fetchPullRecord()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
|
@ -116,9 +127,14 @@ class TempCallActivity : AppCompatActivity() {
|
||||||
instance = null
|
instance = null
|
||||||
if (!isMinimized) {
|
if (!isMinimized) {
|
||||||
currentGroupName = "临时会话"
|
currentGroupName = "临时会话"
|
||||||
|
currentTempGroupId = ""
|
||||||
}
|
}
|
||||||
MainActivity.executeJs("onTempCallStateChanged('" + if (isMinimized) "minimized" else "destroyed" + "')")
|
MainActivity.executeJs("onTempCallStateChanged('" + if (isMinimized) "minimized" else "destroyed" + "')")
|
||||||
|
if (!isMinimized) {
|
||||||
|
MainActivity.executeJs("if(window.endPullGroupLock){ window.endPullGroupLock(); }")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
stopVoiceAlert()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拦截返回键
|
// 拦截返回键
|
||||||
|
|
@ -280,4 +296,99 @@ class TempCallActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startVoiceAlert() {
|
||||||
|
try {
|
||||||
|
findViewById<Button>(R.id.btn_confirm_alert)?.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
setOnClickListener {
|
||||||
|
stopVoiceAlert()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
alertPlayCount = 0
|
||||||
|
alertPlayer = android.media.MediaPlayer.create(this, R.raw.pull_alert).apply {
|
||||||
|
setOnCompletionListener {
|
||||||
|
alertPlayCount++
|
||||||
|
if (alertPlayCount < 3) {
|
||||||
|
try {
|
||||||
|
it.start()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
timber.log.Timber.tag("TempCallAlert").e(e, "Error repeating voice play")
|
||||||
|
stopVoiceAlert()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stopVoiceAlert()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
timber.log.Timber.tag("TempCallAlert").e(e, "Error initializing MediaPlayer")
|
||||||
|
findViewById<Button>(R.id.btn_confirm_alert)?.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopVoiceAlert() {
|
||||||
|
try {
|
||||||
|
findViewById<Button>(R.id.btn_confirm_alert)?.visibility = View.GONE
|
||||||
|
alertPlayer?.let { player ->
|
||||||
|
if (player.isPlaying) {
|
||||||
|
player.stop()
|
||||||
|
}
|
||||||
|
player.release()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
timber.log.Timber.tag("TempCallAlert").e(e, "Error stopping MediaPlayer")
|
||||||
|
} finally {
|
||||||
|
alertPlayer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchPullRecord() {
|
||||||
|
val serverUrl = AppConfig.getServerUrl(this)
|
||||||
|
val token = AppConfig.getAccessToken(this)
|
||||||
|
if (token.isEmpty()) {
|
||||||
|
Timber.tag("TempCallAlert").w("AccessToken is empty, cannot query pull record")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val url = "$serverUrl/api/vid-pull-record/my-list"
|
||||||
|
val request = okhttp3.Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.addHeader("Authorization", "Bearer $token")
|
||||||
|
.get()
|
||||||
|
.build()
|
||||||
|
|
||||||
|
com.stand.standapp.net.NetworkModule.defaultClient.newCall(request).enqueue(object : okhttp3.Callback {
|
||||||
|
override fun onFailure(call: okhttp3.Call, e: java.io.IOException) {
|
||||||
|
Timber.tag("TempCallAlert").e(e, "获取拉动记录失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
|
||||||
|
try {
|
||||||
|
val body = response.body?.string() ?: return
|
||||||
|
val json = org.json.JSONObject(body)
|
||||||
|
if (json.optInt("code", -1) == 0) {
|
||||||
|
val dataArray = json.optJSONArray("data")
|
||||||
|
if (dataArray != null && dataArray.length() > 0) {
|
||||||
|
val record = dataArray.optJSONObject(0) ?: return
|
||||||
|
val pullTitle = record.optString("pullTitle", "")
|
||||||
|
val pullTime = record.optString("pullTime", "")
|
||||||
|
val sourceUnitName = record.optString("sourceUnitName", "")
|
||||||
|
|
||||||
|
runOnUiThread {
|
||||||
|
findViewById<View>(R.id.ll_pull_info)?.visibility = View.VISIBLE
|
||||||
|
findViewById<TextView>(R.id.tv_pull_title)?.text = "标题:$pullTitle"
|
||||||
|
findViewById<TextView>(R.id.tv_pull_source_unit)?.text = "发送单位:$sourceUnitName"
|
||||||
|
findViewById<TextView>(R.id.tv_pull_time)?.text = "拉动时间:$pullTime"
|
||||||
|
findViewById<TextView>(R.id.tv_pull_content)?.text = "内容:请注意!正在对你单位进行视频拉动。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.tag("TempCallAlert").e(e, "解析拉动记录响应失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,50 @@
|
||||||
android:textSize="22sp"
|
android:textSize="22sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<!-- 视频拉动信息区域 (默认隐藏,只有成功获取到数据才展示) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_pull_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_pull_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_pull_source_unit"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:textColor="#CCFFFFFF"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_pull_time"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:textColor="#99FFFFFF"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_pull_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:textColor="#FFD700"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- 分隔线 -->
|
<!-- 分隔线 -->
|
||||||
<View
|
<View
|
||||||
android:layout_width="60dp"
|
android:layout_width="60dp"
|
||||||
|
|
@ -131,6 +175,18 @@
|
||||||
android:textColor="#10B981"
|
android:textColor="#10B981"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<!-- 用于确认并静音语音的按钮 -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_confirm_alert"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="@drawable/btn_minimize_bg"
|
||||||
|
android:text="确认"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:visibility="gone" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- 底部按钮区域 -->
|
<!-- 底部按钮区域 -->
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,60 @@
|
||||||
|
# 临时群组界面状态缓存与前端接口回调设计规约 (Design Spec)
|
||||||
|
|
||||||
|
## 1. 需求概述
|
||||||
|
为 StandAPP 临时呼叫功能增加群组锁定状态的缓存与回调机制:
|
||||||
|
1. **创建界面**:缓存当前的临时群十六进制 GID,并通知前端页面,调用 JS 接口 `startPullGroupLock(gid)`。
|
||||||
|
2. **销毁界面**:清除缓存的临时群 GID,并通知前端页面,调用 JS 接口 `endPullGroupLock()`。
|
||||||
|
3. **查询接口**:提供一个新的原生桥接方法 `getTempGroupId` 供前端 JS 主动查询当前在临时群的十六进制 GID。
|
||||||
|
4. **演示页面**:修改 `print_demo.html` 演示接收回调通知,并添加按钮演示查询当前临时群 GID。
|
||||||
|
|
||||||
|
## 2. 方案设计
|
||||||
|
|
||||||
|
### 2.1 临时群 GID 缓存逻辑 (`TempCallActivity.kt`)
|
||||||
|
* **静态成员变量**:在 `TempCallActivity.Companion` 中定义 `@Volatile var currentTempGroupId: String = ""`。
|
||||||
|
* **创建逻辑 (`onCreate`)**:
|
||||||
|
* 获取 `intent.getStringExtra("group_name")`(即临时群组名称,如 "临时群组1" )。
|
||||||
|
* 将名称使用 `SendAtUtil.str2HexStr(name)` 转为十六进制字符串(如 `"D5E1CAE6..."`),并存入 `currentTempGroupId`。
|
||||||
|
* 如果 `intent` 直接传了 `group_id`,可以直接使用;如果没有,则使用上述十六进制 GID 作为 fallback。
|
||||||
|
* 调用:
|
||||||
|
```kotlin
|
||||||
|
MainActivity.executeJs("if(window.startPullGroupLock){ window.startPullGroupLock('$currentTempGroupId'); }")
|
||||||
|
```
|
||||||
|
* **销毁逻辑 (`onDestroy` / `clearMinimizedState`)**:
|
||||||
|
* 销毁时如果 `!isMinimized`,则清除 `currentTempGroupId = ""`。
|
||||||
|
* 发生真实销毁时调用:
|
||||||
|
```kotlin
|
||||||
|
MainActivity.executeJs("if(window.endPullGroupLock){ window.endPullGroupLock(); }")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 原生桥接接口增加 (`AndroidInterface.kt`)
|
||||||
|
* 在 `dispatch` 方法的 `when(method)` 分支中增加:
|
||||||
|
* `"getTempGroupId" -> getTempGroupId()`
|
||||||
|
* 实现 `getTempGroupId` 接口:
|
||||||
|
```kotlin
|
||||||
|
fun getTempGroupId(): String {
|
||||||
|
return TempCallActivity.currentTempGroupId
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 演示页面修改 (`print_demo.html`)
|
||||||
|
* **注册回调方法**:
|
||||||
|
```javascript
|
||||||
|
window.startPullGroupLock = function(gid) {
|
||||||
|
showPttResult("收到临时群锁定回调 startPullGroupLock, GID: " + gid);
|
||||||
|
};
|
||||||
|
window.endPullGroupLock = function() {
|
||||||
|
showPttResult("收到临时群解锁回调 endPullGroupLock");
|
||||||
|
};
|
||||||
|
```
|
||||||
|
* **新增主动查询按钮**:
|
||||||
|
* 在 PTT 功能区域内添加一个按钮 “查询临时群 GID”:
|
||||||
|
```html
|
||||||
|
<button onclick="queryTempGroupId()" class="ws-btn">查询当前临时群 GID</button>
|
||||||
|
```
|
||||||
|
* JS 触发逻辑:
|
||||||
|
```javascript
|
||||||
|
function queryTempGroupId() {
|
||||||
|
var gid = window.android.getTempGroupId();
|
||||||
|
showPttResult("当前在临时群的 GID: " + (gid || "无"));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
# 临时呼叫页面语音提示与确认功能设计规约 (Design Spec)
|
||||||
|
|
||||||
|
## 1. 需求概述
|
||||||
|
在 `TempCallActivity` 弹出页面(临时呼叫页面)时,需要播放一段本地语音提示。
|
||||||
|
* **语音播报文本**:"您有一条拉动,请注意确认"。
|
||||||
|
* **播报循环规则**:若用户不操作,默认播放并重复 3 次。
|
||||||
|
* **交互设计**:
|
||||||
|
* 页面中央的信息卡片中新增一个“确认”按钮。
|
||||||
|
* 用户点击“确认”按钮时,能够立即静音/停止当前正在播放的语音,且该按钮在静音后隐藏。
|
||||||
|
* 点击“确认”按钮时仅关闭语音播放,暂时不需要关闭页面。
|
||||||
|
|
||||||
|
## 2. 方案设计
|
||||||
|
|
||||||
|
### 2.1 语音资源准备
|
||||||
|
* **文件名**:`pull_alert.wav`
|
||||||
|
* **保存路径**:`app/src/main/res/raw/pull_alert.wav`(若没有 `raw` 文件夹,将自动创建)
|
||||||
|
* **生成方式**:使用 Python 脚本调用 Edge TTS 等免费 TTS 接口,在线合成带有“您有一条拉动,请注意确认”文本的高质量 WAV 音频,并自动保存到对应目录下。
|
||||||
|
|
||||||
|
### 2.2 布局修改
|
||||||
|
* **目标文件**:`app/src/main/res/layout/activity_temp_call.xml`
|
||||||
|
* **修改位置**:在包含 `tv_talk_status` 的内部 `LinearLayout` 容器下方,添加“确认”按钮。
|
||||||
|
* **按钮样式**:
|
||||||
|
* 文字:"确认"
|
||||||
|
* 默认可见性:`android:visibility="gone"`(开始播放时显示,停止播放/确认后隐藏)
|
||||||
|
* 背景:复用现有 `@drawable/btn_minimize_bg`。
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- 用于确认并静音语音的按钮 -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_confirm_alert"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="@drawable/btn_minimize_bg"
|
||||||
|
android:text="确认"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 业务逻辑实现
|
||||||
|
* **目标文件**:`app/src/main/java/com/stand/standapp/TempCallActivity.kt`
|
||||||
|
* **主要逻辑**:
|
||||||
|
1. 定义 `MediaPlayer` 实例 `private var alertPlayer: MediaPlayer? = null` 及播放计数器 `private var alertPlayCount = 0`。
|
||||||
|
2. 实现播放方法 `private fun startVoiceAlert()`:
|
||||||
|
- 如果 `alertPlayer` 尚未初始化,利用 `MediaPlayer.create(this, R.raw.pull_alert)` 初始化。
|
||||||
|
- 显示“确认”按钮:`btnConfirmAlert.visibility = View.VISIBLE`。
|
||||||
|
- 监听播放完成事件:
|
||||||
|
```kotlin
|
||||||
|
alertPlayer?.setOnCompletionListener {
|
||||||
|
alertPlayCount++
|
||||||
|
if (alertPlayCount < 3) {
|
||||||
|
alertPlayer?.start()
|
||||||
|
} else {
|
||||||
|
stopVoiceAlert()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- 开始播放并设置 `alertPlayCount = 0`。
|
||||||
|
3. 实现停止方法 `private fun stopVoiceAlert()`:
|
||||||
|
- 安全释放 `MediaPlayer` 资源:
|
||||||
|
```kotlin
|
||||||
|
alertPlayer?.let { player ->
|
||||||
|
try {
|
||||||
|
if (player.isPlaying) {
|
||||||
|
player.stop()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 忽略错误
|
||||||
|
} finally {
|
||||||
|
player.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
alertPlayer = null
|
||||||
|
```
|
||||||
|
- 隐藏“确认”按钮:`btnConfirmAlert.visibility = View.GONE`。
|
||||||
|
4. 设置“确认”按钮的点击事件:
|
||||||
|
- 点击时调用 `stopVoiceAlert()`。
|
||||||
|
5. 页面销毁生命周期:
|
||||||
|
- 在 `onDestroy()` 中,必须调用 `stopVoiceAlert()` 以防止内存泄漏和页面销毁后后台仍在播放的情况。
|
||||||
|
|
||||||
|
## 3. 测试与自审规范
|
||||||
|
* **测试用例 1**:启动临时呼叫页面,验证语音提示是否以正常音量播放,且重复播放 3 次后自动停止,同时“确认”按钮自动隐藏。
|
||||||
|
* **测试用例 2**:语音播放期间点击“确认”按钮,验证语音是否立刻停止播放,且“确认”按钮是否立即隐藏。
|
||||||
|
* **测试用例 3**:语音播放期间直接关闭或最小化页面,验证后台音乐播放器是否正确释放,不存在背景音残留。
|
||||||
Loading…
Reference in New Issue