增加了发布通道

This commit is contained in:
sansen
2026-07-23 19:37:20 +08:00
parent e3d03d74ea
commit e8a45032f8
35 changed files with 1489 additions and 413 deletions
+52 -5
View File
@@ -222,6 +222,7 @@ type TextMsg struct {
InvokerName string
InvokerUID string
Message string
InvokerID int
TargetMode int
TargetID string
}
@@ -261,6 +262,7 @@ type TSClient struct {
callback EventCallback
connected bool
host string // 服务器地址(用于文件传输 TCP 连接)
selfUID string // 当前连接的 TeamSpeak UID,用于识别同 identity 的残留 session
// 事件队列:保证所有 JNI 回调在同一个协程中顺序执行
evtQueueMu sync.Mutex // protects eventQueue replacement
@@ -303,6 +305,11 @@ func (c *TSClient) Connect(host, nickname, password, defaultChannel, defaultChan
return fmt.Sprintf("生成身份失败: %v", err)
}
// 计算 TeamSpeak UID 并存储,用于僵尸会话过滤(isStaleSelfSession)。
c.mu.Lock()
c.selfUID = crypto.GetUidFromPublicKey(identity.PublicKeyBase64())
c.mu.Unlock()
opts := []ts.ClientOption{}
if password != "" {
opts = append(opts, ts.WithServerPassword(password))
@@ -1423,6 +1430,25 @@ func (c *TSClient) isCurrentClient(client *ts.Client) bool {
return c.client == client
}
// isStaleSelfSession checks if a remote client event belongs to a stale session
// using the same identity as the current connection. This happens when the previous
// process crashed without sending clientdisconnect, leaving a "zombie" session on
// the server that the new connection must isolate from.
func (c *TSClient) isStaleSelfSession(uid string, clid int) bool {
c.mu.Lock()
selfUID := c.selfUID
selfClid := 0
if c.client != nil {
selfClid = int(c.client.ClientID())
}
c.mu.Unlock()
if selfUID == "" || uid == "" || uid != selfUID {
return false
}
return clid != selfClid
}
func (c *TSClient) queueClientEvent(client *ts.Client, evt queuedEvent) {
if c.isCurrentClient(client) {
c.queueEvent(evt)
@@ -1444,6 +1470,10 @@ func (c *TSClient) registerEvents(client *ts.Client) {
client.OnDisconnected(func(err error) {
c.StopReceiveAudio()
c.mu.Lock()
if c.client != client {
c.mu.Unlock()
return
}
c.connected = false
c.client = nil
c.mu.Unlock()
@@ -1455,12 +1485,13 @@ func (c *TSClient) registerEvents(client *ts.Client) {
})
client.OnTextMessage(func(msg ts.TextMessage) {
c.queueEvent(queuedEvent{
c.queueClientEvent(client, queuedEvent{
evtType: "textmessage",
data: &TextMsg{
InvokerName: msg.InvokerName,
InvokerUID: msg.InvokerUID,
Message: msg.Message,
InvokerID: int(msg.InvokerID),
TargetMode: msg.TargetMode,
TargetID: fmt.Sprintf("%d", msg.TargetID),
},
@@ -1468,7 +1499,11 @@ func (c *TSClient) registerEvents(client *ts.Client) {
})
client.OnClientEnter(func(info ts.ClientInfo) {
c.queueEvent(queuedEvent{
if c.isStaleSelfSession(info.UID, int(info.ID)) {
log.Printf("[TSBridge] ignoring stale self session enter clid=%d uid=%s", info.ID, info.UID)
return
}
c.queueClientEvent(client, queuedEvent{
evtType: "cliententer",
data: &Client{
ID: int(info.ID),
@@ -1481,8 +1516,13 @@ func (c *TSClient) registerEvents(client *ts.Client) {
})
client.OnClientLeave(func(data ts.ClientLeftViewEvent) {
// 不过滤僵尸的 leave 事件:GetClientsJSON 初始同步可能已包含僵尸,
// 需要 leave 事件来更新 UI 将其移除。同时确保音频解码器被清理。
if c.isStaleSelfSession(data.UID, int(data.ID)) {
log.Printf("[TSBridge] stale self session leave clid=%d uid=%s (allowing for UI cleanup)", data.ID, data.UID)
}
c.RemoveRemoteAudioClient(int(data.ID))
c.queueEvent(queuedEvent{
c.queueClientEvent(client, queuedEvent{
evtType: "clientleave",
data: struct {
ID int
@@ -1495,7 +1535,7 @@ func (c *TSClient) registerEvents(client *ts.Client) {
})
client.OnClientMoved(func(data ts.ClientMovedEvent) {
c.queueEvent(queuedEvent{
c.queueClientEvent(client, queuedEvent{
evtType: "clientmoved",
data: struct {
ID int
@@ -1510,6 +1550,10 @@ func (c *TSClient) registerEvents(client *ts.Client) {
client.OnKicked(func(reason string) {
c.StopReceiveAudio()
c.mu.Lock()
if c.client != client {
c.mu.Unlock()
return
}
c.connected = false
c.client = nil
c.mu.Unlock()
@@ -1526,6 +1570,9 @@ func (c *TSClient) registerEvents(client *ts.Client) {
}
// Keep the existing raw bridge active until an Android libopus decoder
// factory has been supplied and the Go receiver explicitly started.
if !c.isCurrentClient(client) {
return
}
c.queueEvent(queuedEvent{
evtType: "voicedata",
data: struct {
@@ -1545,7 +1592,7 @@ func (c *TSClient) registerEvents(client *ts.Client) {
})
client.OnPoked(func(evt ts.PokeEvent) {
c.queueEvent(queuedEvent{
c.queueClientEvent(client, queuedEvent{
evtType: "poked",
data: &PokeEvent{
InvokerID: int(evt.InvokerID),
+16 -1
View File
@@ -70,6 +70,13 @@ func (c *TSClient) ConnectWithIdentity(identityStr, host, nickname, password, de
return fmt.Sprintf("identity 解析失败: %v", err)
}
// 计算 TeamSpeak UIDbase64(SHA1(publicKey))),用于僵尸会话过滤。
// 不能直接存储 identity 字符串,因为 UID 是公钥的哈希,格式完全不同。
uid := crypto.GetUidFromPublicKey(identity.PublicKeyBase64())
c.mu.Lock()
c.selfUID = uid
c.mu.Unlock()
opts := []ts.ClientOption{}
if password != "" {
opts = append(opts, ts.WithServerPassword(password))
@@ -102,7 +109,15 @@ func (c *TSClient) ConnectWithIdentity(identityStr, host, nickname, password, de
return fmt.Sprintf("连接失败: %v", err)
}
c.cleanupDuplicateIdentitySessions(client, identity)
// 延迟执行僵尸清理:连接刚建立时客户端在默认频道,ListClients 可能尚未
// 收到服务器发来的完整客户端列表。延迟 3 秒确保所有 notifycliententerview
// 事件已到达,此时 ListClients 能发现不同频道中的僵尸会话。
go func() {
time.Sleep(3 * time.Second)
if c.isCurrentClient(client) {
c.cleanupDuplicateIdentitySessions(client, identity)
}
}()
return ""
}