From 803fcb38d250e5a10d4fc83a3d06a3d0ffb72a5c Mon Sep 17 00:00:00 2001 From: "844143714@qq,com" <844143714@qq,com> Date: Sun, 26 Apr 2026 20:38:42 +0800 Subject: [PATCH] ptt --- app/src/main/assets/print_demo.html | 216 ++++++++---------- .../kingway/ptt/CallBackResolution.java | 5 + .../com/stand/standapp/AndroidInterface.kt | 69 ++++++ .../java/com/stand/standapp/LoginActivity.kt | 12 +- .../java/com/stand/standapp/MainActivity.kt | 23 +- .../stand/standapp/printer/PrinterManager.kt | 17 +- 6 files changed, 212 insertions(+), 130 deletions(-) diff --git a/app/src/main/assets/print_demo.html b/app/src/main/assets/print_demo.html index ffbb7ba..572cf1f 100644 --- a/app/src/main/assets/print_demo.html +++ b/app/src/main/assets/print_demo.html @@ -157,49 +157,35 @@
- PTT 业务接口测试 + PTT 业务功能测试 + PTT 未登录
- POC 账号: - -
-
- 频道 ID: - -
-
-
-
- 频道/群组名称: - -
-
- 录音相对路径: - + 群组 ID (十六进制): +
+ +
+ +
+
- - - - - - - - - - - - + + + +
-
PTT 接口响应将在此展示...
+
等待 PTT 登录成功后激活功能...
@@ -637,121 +623,109 @@ setTimeout(loadHistory, 500); - // === PTT 接口测试逻辑 === - var PTT_BASE = "http://127.0.0.1:8080/api/external/ptt"; + // === PTT 业务测试逻辑 === + var pttBtn = document.getElementById('ptt-speak-btn'); + var pttStatusTag = document.getElementById('ptt-status-tag'); + var pttResult = document.getElementById('ptt-result'); function showPttResult(msg) { - var container = document.getElementById('ptt-result'); var time = new Date().toLocaleTimeString(); var item = document.createElement('div'); item.className = 'log-item'; - var displayMsg = typeof msg === 'string' ? msg : JSON.stringify(msg, null, 2); - item.innerHTML = '[' + time + ']' + displayMsg + ''; - container.insertBefore(item, container.firstChild); + item.innerHTML = '[' + time + ']' + msg + ''; + pttResult.insertBefore(item, pttResult.firstChild); } - // 移除 async/await,使用 Promise - function pttRequest(path, method, query, body) { - if (method === undefined) method = 'GET'; - if (query === undefined) query = {}; - if (body === undefined) body = null; - - var url = PTT_BASE + path; - var qParams = []; - for (var key in query) { - qParams.push(encodeURIComponent(key) + "=" + encodeURIComponent(query[key])); + // PTT 登录成功回调 (由 Android 调用) + function onPttLoginSuccess() { + pttStatusTag.innerText = "PTT 已登录"; + pttStatusTag.className = "ws-status ws-connected"; + + // 激活按钮 + pttBtn.disabled = false; + pttBtn.style.background = "#ef4444"; + pttBtn.style.cursor = "pointer"; + + var actionBtns = document.querySelectorAll('.ptt-action-btn'); + for (var i = 0; i < actionBtns.length; i++) { + actionBtns[i].disabled = false; } - if (qParams.length > 0) url += "?" + qParams.join("&"); + + showPttResult("PTT 业务初始化成功,对讲功能已就绪"); + } - var options = { - method: method, - headers: { 'Content-Type': 'application/json' } - }; - if (body) options.body = JSON.stringify(body); + // 修复 PTT 按键逻辑:支持 TouchEvents 并处理异常中断 + if (pttBtn) { + var isDown = false; - fetch(url, options) - .then(function(response) { - if (path === '/record/play') { - showPttResult("流式播放接口已调用,请查看网络请求或直接点击预览链接"); - return; + var startSpeak = function(e) { + if (pttBtn.disabled) return; + e.preventDefault(); + if (!isDown) { + isDown = true; + pttBtn.style.background = "#b91c1c"; + pttBtn.innerText = "正在通话..."; + if (window.android && window.android.pttSpeakPlay) { + window.android.pttSpeakPlay(); } - return response.json(); - }) - .then(function(data) { - if (data) showPttResult(data); - }) - .catch(function(e) { - showPttResult("请求失败: " + e.message); - }); - } + showPttResult("申请话权..."); + } + }; - function pttCreateGroup() { - var name = document.getElementById('ptt-name').value; - var account = document.getElementById('ptt-account').value; - pttRequest('/group', 'POST', { gname: name }, [account]); - } + var stopSpeak = function(e) { + if (isDown) { + isDown = false; + pttBtn.style.background = "#ef4444"; + pttBtn.innerText = "长按对讲 (PTT)"; + if (window.android && window.android.pttSpeakRelease) { + window.android.pttSpeakRelease(); + } + showPttResult("释放话权"); + } + }; - function pttDeleteGroup() { - var gid = document.getElementById('ptt-gid').value; - pttRequest('/group/' + gid, 'DELETE'); - } - - function pttAddMembers() { - var gid = document.getElementById('ptt-gid').value; - var account = document.getElementById('ptt-account').value; - pttRequest('/group/' + gid + '/members', 'POST', {}, [account]); - } - - function pttKickMembers() { - var gid = document.getElementById('ptt-gid').value; - var account = document.getElementById('ptt-account').value; - pttRequest('/group/' + gid + '/members', 'DELETE', {}, [account]); + // 绑定事件 + pttBtn.addEventListener('touchstart', startSpeak); + pttBtn.addEventListener('mousedown', startSpeak); + + // 关键:除了 end/up,还要处理 cancel 和 leave + pttBtn.addEventListener('touchend', stopSpeak); + pttBtn.addEventListener('touchcancel', stopSpeak); + pttBtn.addEventListener('mouseup', stopSpeak); + pttBtn.addEventListener('mouseleave', stopSpeak); } function pttGetGroups() { - pttRequest('/groups', 'GET', { page: 1, limit: 10 }); + if (window.android && window.android.pttGetGroupInfo) { + window.android.pttGetGroupInfo(); + showPttResult("已请求群组列表"); + } } - function pttGetTempGroups() { - pttRequest('/temp-groups', 'GET', { page: 1, limit: 10 }); - } - - function pttGetRecords() { + function pttGetMembers() { var gid = document.getElementById('ptt-gid').value; - var now = new Date(); - var end = now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate() + ' ' + now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds(); - var start = "2024-01-01 00:00:00"; - pttRequest('/group/' + gid + '/records', 'POST', { start: start, end: end, page: 1, limit: 5 }, []); + if (window.android && window.android.pttGetGroupMemberInfo) { + window.android.pttGetGroupMemberInfo(gid); + showPttResult("已请求群组 [" + gid + "] 成员"); + } } - function pttGetRecordText() { - var path = document.getElementById('ptt-record-path').value; - if (!path) return alert("请输入录音路径"); - pttRequest('/record/text', 'GET', { path: path }); - } - - function pttPlayRecord() { - var path = document.getElementById('ptt-record-path').value; - if (!path) return alert("请输入录音路径"); - var url = PTT_BASE + '/record/play?path=' + encodeURIComponent(path); - window.open(url, '_blank'); - showPttResult("已尝试打开播放链接: " + url); - } - - function pttCreateTempGroup() { - var name = document.getElementById('ptt-name').value; - var account = document.getElementById('ptt-account').value; - pttRequest('/temp-group', 'POST', { gname: name, creatorid: 1001 }, [account]); - } - - function pttDeleteTempGroup() { + function pttJumpGroup() { var gid = document.getElementById('ptt-gid').value; - pttRequest('/temp-group/' + gid, 'DELETE'); + if (window.android && window.android.pttJumpGroup) { + window.android.pttJumpGroup(gid); + showPttResult("尝试切换至群组: " + gid); + } } - function pttGetTempMembers() { - var gid = document.getElementById('ptt-gid').value; - pttRequest('/temp-group/' + gid + '/members', 'GET'); + function pttForceRelease() { + if (window.android && window.android.pttForceRelease) { + window.android.pttForceRelease(); + isDown = false; + pttBtn.style.background = "#ef4444"; + pttBtn.innerText = "长按对讲 (PTT)"; + showPttResult("执行强制释放"); + } } diff --git a/app/src/main/java/com/example/kingway/ptt/CallBackResolution.java b/app/src/main/java/com/example/kingway/ptt/CallBackResolution.java index 58e94e1..9b47004 100644 --- a/app/src/main/java/com/example/kingway/ptt/CallBackResolution.java +++ b/app/src/main/java/com/example/kingway/ptt/CallBackResolution.java @@ -36,6 +36,9 @@ public class CallBackResolution { CallBackUtil.callBackLoinState(errorNum, loginId); loginState = isLoginConflict(errorNum); CallBackUtil.callBackLoginConflict(loginState); + if ("02".equals(errorNum)) { + com.stand.standapp.MainActivity.onPttLoginSuccess(); + } break; case "a2"://账号有视频功能 @@ -122,6 +125,8 @@ public class CallBackResolution { GroupMemberInfoDto infoDto = new GroupMemberInfoDto(memberId, tempTxt, status, memberNo, haveVideo); if (memberNo == 1) memberInfoDtos.clear(); memberInfoDtos.add(infoDto); + //输出群成员信息memberInfoDtos + Timber.tag(TAG).d("at_OEM_AT_cb group member info: " + infoDto); } break; diff --git a/app/src/main/java/com/stand/standapp/AndroidInterface.kt b/app/src/main/java/com/stand/standapp/AndroidInterface.kt index d9f9892..5197f41 100644 --- a/app/src/main/java/com/stand/standapp/AndroidInterface.kt +++ b/app/src/main/java/com/stand/standapp/AndroidInterface.kt @@ -11,10 +11,79 @@ import com.stand.standapp.R import com.stand.standapp.utils.LogManager import com.stand.standapp.printer.PrinterManager import timber.log.Timber +import com.example.kingway.ptt.SendAtUtil import org.json.JSONObject class AndroidInterface(private val activity: MainActivity) { + private var isSpeaking = false + + /** + * PTT 登录 + */ + @JavascriptInterface + fun pttLogin(account: String, pwd: String, ip: String) { + SendAtUtil.toLogin(account, pwd, ip, 1, 1) + } + + /** + * PTT 开始说话 (带状态保护) + */ + @JavascriptInterface + fun pttSpeakPlay() { + if (!isSpeaking) { + isSpeaking = true + SendAtUtil.speakPlay() + Timber.tag("PTT").d("Speak Play") + } + } + + /** + * PTT 停止说话 + */ + @JavascriptInterface + fun pttSpeakRelease() { + if (isSpeaking) { + isSpeaking = false + SendAtUtil.speakRelease() + Timber.tag("PTT").d("Speak Release") + } + } + + /** + * 强制释放 PTT (用于异常恢复) + */ + @JavascriptInterface + fun pttForceRelease() { + isSpeaking = false + SendAtUtil.speakRelease() + Timber.tag("PTT").d("Force Release") + } + + /** + * PTT 获取群组 + */ + @JavascriptInterface + fun pttGetGroupInfo() { + SendAtUtil.getGroupInfo() + } + + /** + * PTT 获取成员 + */ + @JavascriptInterface + fun pttGetGroupMemberInfo(gid: String) { + SendAtUtil.getGroupMemberInfo(gid) + } + + /** + * PTT 切换群组 + */ + @JavascriptInterface + fun pttJumpGroup(gid: String) { + SendAtUtil.jumpGroup(gid) + } + /** * JS 调用:获取打印机当前连接状态 */ diff --git a/app/src/main/java/com/stand/standapp/LoginActivity.kt b/app/src/main/java/com/stand/standapp/LoginActivity.kt index 4afac80..906d7b3 100644 --- a/app/src/main/java/com/stand/standapp/LoginActivity.kt +++ b/app/src/main/java/com/stand/standapp/LoginActivity.kt @@ -130,8 +130,18 @@ class LoginActivity : AppCompatActivity() { } Toast.makeText(this@LoginActivity, "登录成功", Toast.LENGTH_SHORT).show() - + + try { + // 确保 PTT 引擎已初始化 + com.example.kingway.ptt.MyApplication.init(this@LoginActivity) + // 异步初始化 PTT 登录 (区分 Android 业务登录) + com.example.kingway.ptt.SendAtUtil.toLogin("gzzdweb01", "123456", "61.243.1.123", 1, 1) + } catch (e: Exception) { + Timber.tag("Login").e(e, "检查更新失败") + } + // 初始化打印机 (异步) + PrinterManager.init(this@LoginActivity) startActivity(Intent(this@LoginActivity, MainActivity::class.java)) diff --git a/app/src/main/java/com/stand/standapp/MainActivity.kt b/app/src/main/java/com/stand/standapp/MainActivity.kt index 079f4d8..1ae2bd5 100644 --- a/app/src/main/java/com/stand/standapp/MainActivity.kt +++ b/app/src/main/java/com/stand/standapp/MainActivity.kt @@ -20,7 +20,27 @@ import com.stand.standapp.UpdateManager class MainActivity : AppCompatActivity() { + companion object { + private var instance: MainActivity? = null + + @JvmStatic + fun executeJs(script: String) { + instance?.let { activity -> + activity.runOnUiThread { + activity.mAgentWeb?.jsAccessEntrace?.quickCallJs(script) + } + } + } + + @JvmStatic + fun onPttLoginSuccess() { + Timber.tag("MainActivity").d("onPttLoginSuccess called from Native") + executeJs("onPttLoginSuccess") + } + } + private var mAgentWeb: AgentWeb? = null + private val mHandler = android.os.Handler(android.os.Looper.getMainLooper()) private val TIMEOUT_MS = 30000L @@ -30,8 +50,9 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - + instance = this try { + setContentView(R.layout.activity_main) val container = findViewById(R.id.container) diff --git a/app/src/main/java/com/stand/standapp/printer/PrinterManager.kt b/app/src/main/java/com/stand/standapp/printer/PrinterManager.kt index 2fd3025..60ed115 100644 --- a/app/src/main/java/com/stand/standapp/printer/PrinterManager.kt +++ b/app/src/main/java/com/stand/standapp/printer/PrinterManager.kt @@ -39,16 +39,19 @@ object PrinterManager { if (sf != null) return mContext = context.applicationContext - // 全异步初始化,不阻塞主线程跳转 - executor.execute { + // Serialport_Factory 构造函数内部会创建 Handler,必须在有 Looper 的线程(如主线程)调用 + mHandler.post { try { - Timber.tag("Printer").i("开始异步初始化打印机组件...") + Timber.tag("Printer").i("正在主线程初始化打印机组件...") sf = Serialport_Factory.getSerialport_Factory(mContext, mHandler) - // 启动自动重连定时任务 - executor.scheduleWithFixedDelay({ - checkAndConnect() - }, 0, 10, TimeUnit.SECONDS) + // 成功创建 factory 后,将后续耗时的连接任务丢给子线程 + executor.execute { + // 启动自动重连定时任务 + executor.scheduleWithFixedDelay({ + checkAndConnect() + }, 0, 10, TimeUnit.SECONDS) + } Timber.tag("Printer").i("打印机组件初始化任务已分发") } catch (e: Exception) {