Skip to content

Commit

Permalink
refactor: Let user to select turn server
Browse files Browse the repository at this point in the history
  • Loading branch information
ci010 committed Jun 28, 2024
1 parent a50d837 commit 95d226e
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 49 deletions.
4 changes: 4 additions & 0 deletions xmcl-keystone-ui/locales/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1333,6 +1333,10 @@ transportType:
prflx: Peer Reflexive Candidate
relay: Relay Candidate
srflx: Server Reflexive Candidate
turnRegion:
guangzhou: Guangzhou, China
hk: Hong Kong
liaoning: Liaoning, China
tutorial:
feedbackDescription: If you encounter any problem, please click this button to send feedback!
hideNewsHeaderDescription: >-
Expand Down
4 changes: 4 additions & 0 deletions xmcl-keystone-ui/locales/zh-CN.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1280,6 +1280,10 @@ transportType:
prflx: 同伴反射地址
relay: 转发地址
srflx: 服务端反射地址
turnRegion:
guangzhou: 广州
hk: 香港
liaoning: 辽宁
tutorial:
feedbackDescription: 如果您遇到了问题,请点击这个按钮来联系开发团队。
hideNewsHeaderDescription: |-
Expand Down
2 changes: 2 additions & 0 deletions xmcl-keystone-ui/src/composables/peers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export function usePeerState(gameProfile: Ref<GameProfileAndTexture>) {
const group = computed(() => state.value?.group)
const groupState = computed(() => state.value?.groupState || 'closed')
const error = computed(() => state.value?.groupError)
const turnservers = computed(() => state.value?.turnservers || {})

function _setRemoteDescription(type: 'offer' | 'answer', description: string) {
return setRemoteDescription({
Expand All @@ -61,6 +62,7 @@ export function usePeerState(gameProfile: Ref<GameProfileAndTexture>) {

return {
device,
turnservers,
validIceServers,
natType,
refreshNatType: refreshNatType.refresh,
Expand Down
81 changes: 48 additions & 33 deletions xmcl-keystone-ui/src/views/Multiplayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,49 +32,34 @@
</div>

<div class="flex-grow" />
<v-tooltip
bottom
color="black"
<v-btn
v-shared-tooltip.left="_ => t('multiplayer.share')"
text
icon
@click="showShareInstance()"
>
<v-icon>
share
</v-icon>
</v-btn>

<v-menu
left
offset-y
>
<template #activator="{ on }">
<v-btn
id="manual-connect-button"
v-shared-tooltip.left="_ => t('multiplayer.manualConnect')"
text
icon
v-on="on"
@click="showShareInstance()"
>
<v-icon>
share
build
</v-icon>
</v-btn>
</template>
{{ t('multiplayer.share') }}
</v-tooltip>

<v-menu
left
offset-y
>
<template #activator="{ on }">
<v-tooltip
left
color="black"
>
<template #activator="{ on: onTooltip }">
<v-btn
id="manual-connect-button"
text
icon
v-on="{ ...on, ...onTooltip }"
>
<v-icon>
build
</v-icon>
</v-btn>
</template>
{{ t('multiplayer.manualConnect') }}
</v-tooltip>
</template>
<v-list>
<v-list-item @click="show()">
<v-list-item-title>
Expand Down Expand Up @@ -303,6 +288,28 @@
</v-list-item-action>
</v-list-item>

<v-list-item
v-if="allowTurn && turnserversItems.length > 0"
class="flex-1 flex-grow-0"
>
<v-list-item-avatar>
<!-- <v-icon>
swap_vert
</v-icon> -->
</v-list-item-avatar>
<v-list-item-content />
<v-list-item-action>
<v-select
v-model="preferredTurnserver"
filled
clearable
hide-details
:items="turnserversItems"
:placeholder="turnserversItems[0].text"
/>
</v-list-item-action>
</v-list-item>

<v-subheader class>
{{ t("multiplayer.connections") }}
</v-subheader>
Expand Down Expand Up @@ -497,7 +504,7 @@ const { show: showDelete, target: deleting, confirm: doDelete, model } = useSimp
console.log(`drop connection ${v}`)
drop(v)
})
const { connections, group, groupState, joinGroup, leaveGroup, drop, ips, device, natType, refreshingNatType, refreshNatType } = injection(kPeerState)
const { connections, turnservers, group, groupState, joinGroup, leaveGroup, drop, ips, device, natType, refreshingNatType, refreshNatType } = injection(kPeerState)
const { t } = useI18n()
const { handleUrl } = useService(BaseServiceKey)
const { state } = injection(kSettingsState)
Expand All @@ -511,6 +518,14 @@ const kernels = computed(() => [
{ value: 'webrtc', text: 'WebRTC' },
])
const preferredTurnserver = useLocalStorageCacheStringValue('peerPreferredTurn', '')
const turnserversItems = computed(() => Object.entries(turnservers.value).map(([key, value]) => ({ value: key, text: `${tLocale.value[value as string] || value} (${key})` })))
const tLocale = computed(() => ({
liaoning: t('turnRegion.liaoning'),
guangzhou: t('turnRegion.guangzhou'),
hk: t('turnRegion.hk'),
} as Record<string, string>))
const { errorColor, successColor, warningColor } = injection(kTheme)
const tGroupState = computed(() => ({
Expand Down
5 changes: 5 additions & 0 deletions xmcl-runtime-api/src/services/PeerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class PeerState {
connections = [] as Peer[]
validIceServers = [] as string[]
ips = [] as string[]
turnservers = {} as Record<string, string>
group = ''
groupState: 'connecting' | 'connected' | 'closing' | 'closed' = 'closed'
groupError?: Error
Expand Down Expand Up @@ -166,6 +167,10 @@ export class PeerState {
ipsSet(ips: string[]) {
this.ips = ips
}

turnserversSet(meta: Record<string, string>) {
this.turnservers = meta
}
}

export interface ShareInstanceOptions {
Expand Down
1 change: 1 addition & 0 deletions xmcl-runtime/peer/PeerContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ export interface PeerContext {

getNextIceServer(): RTCIceServer | undefined
getCurrentIceServer(): RTCIceServer | undefined
setTargetIceServer(server: RTCIceServer): void
}
34 changes: 24 additions & 10 deletions xmcl-runtime/peer/iceServers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,32 @@ export async function getIceServers() {
password: string
username: string
stuns: string[]
meta: Record<string, string>
} | {
stuns: string[]
} = await response.json() as any
const result: RTCIceServer[] = credential.stuns.map((s) => ({
urls: `stun:${s}`,
}))

if (credential.uris) {
result.unshift({
urls: credential.uris,
username: credential.username,
credential: credential.password,
})
if ('uris' in credential && credential.uris) {
for (const uri of credential.uris) {
result.unshift({
urls: uri,
username: credential.username,
credential: credential.password,
})
}
}

return result
return {
servers: result,
meta: 'meta' in credential ? credential.meta : undefined,
}
} else {
return []
return {
servers: [],
}
}
}

Expand Down Expand Up @@ -151,6 +161,7 @@ export function createIceServersProvider(
factory: PeerConnectionFactory,
onValidIceServer: (server: RTCIceServer) => void,
onIp: (ip: string) => void,
onMeta: (meta: Record<string, string>) => void,
) {
const passed: Record<string, RTCIceServer> = {}
const blocked: Record<string, RTCIceServer> = {}
Expand Down Expand Up @@ -178,8 +189,11 @@ export function createIceServersProvider(
}, _resolve)
},
async update() {
const fetched = await getIceServers()
testIceServers(factory, fetched, passed, blocked, onValidIceServer, onIp)
const { servers, meta } = await getIceServers()
if (meta) {
onMeta(meta)
}
testIceServers(factory, servers, passed, blocked, onValidIceServer, onIp)
},
get(preferredIceServers: RTCIceServer[] = []) {
const servers = Object.keys(passed).length > 0 ? Object.values(passed) : Object.values(blocked)
Expand Down
51 changes: 45 additions & 6 deletions xmcl-runtime/peer/multiplayerImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export interface InitiateOptions {
* Is the peer the initiator (create the offer)
*/
initiate?: boolean
/**
* The using ice server
*/
targetIceServer?: RTCIceServer
/**
* Use the ice server
*/
Expand Down Expand Up @@ -158,27 +162,58 @@ export function createMultiplayer() {
console.log('Public ip', ip)
state.then(s => s.ipsSet(Array.from(new Set([...s.ips, ip]))))
},
(meta) => {
state.then(s => s.turnserversSet(meta))
},
)
const portCandidate = 35565

const getContext = (remoteId: string | undefined, preferredIceServers: Array<RTCIceServer>): PeerContext => {
const createContext = (remoteId: string | undefined, targetIceServer: RTCIceServer | undefined, preferredIceServers: Array<RTCIceServer>): PeerContext => {
const isAllowTurn = () => localStorage.getItem('peerAllowTurn') === 'true'
let stunIndex = 0
let turnIndex = 0
let current: RTCIceServer | undefined
let triedTargetIceServer = false
return {
getCurrentIceServer: () => {
throw new Error('Method not implemented.')
getCurrentIceServer: () => current,
setTargetIceServer: (ice: RTCIceServer) => {
targetIceServer = ice
},
getNextIceServer: () => {
// select priority follow targetIceServer (turn) > common turns > common stuns

if (!triedTargetIceServer && targetIceServer && targetIceServer.credential) {
triedTargetIceServer = true
current = targetIceServer
return targetIceServer
}

const [stuns, turns] = iceServers.get(preferredIceServers)
if (isAllowTurn() && turns.length > 0) {
const preferredTurn = localStorage.getItem('peerPreferredTurn')
if (preferredTurn) {
const index = turns.findIndex(s => s.urls.includes(preferredTurn))
if (index !== -1) {
const cur = turns[index]
current = cur
return cur
}
}
const cur = turns[turnIndex]
turnIndex = (turnIndex + 1) % turns.length
if (turnIndex === 0) {
triedTargetIceServer = false
}
current = cur
return cur
}

const cur = stuns[stunIndex]
stunIndex = (stunIndex + 1) % stuns.length
if (stunIndex === 0) {
triedTargetIceServer = false
}
current = cur
return cur
},
onHeartbeat: (session, ping) => {
Expand Down Expand Up @@ -206,7 +241,9 @@ export function createMultiplayer() {
console.log(`Send local description ${remoteId}: ${sdp} ${type}`)
// Send to the group if the remoteId is set
const [stuns] = iceServers.get(preferredIceServers)
group.getGroup()?.sendLocalDescription(remoteId, sdp, type, candidates, stuns[stunIndex], stuns)
if (current) {
group.getGroup()?.sendLocalDescription(remoteId, sdp, type, candidates, current, stuns)
}
}

pBrotliCompress(JSON.stringify(payload)).then((s) => s.toString('base64')).then((compressed) => {
Expand Down Expand Up @@ -326,7 +363,7 @@ export function createMultiplayer() {
selectedCandidate: undefined,
}))

const ctx = getContext(remoteId, preferredIceServers)
const ctx = createContext(remoteId, options.targetIceServer, preferredIceServers)
const sess = new PeerSession(sessionId, await create(ctx, sessionId), ctx)

peers.add(sess)
Expand All @@ -348,7 +385,9 @@ export function createMultiplayer() {
if (!sess) {
console.log(`Not found the ${sender}. Initiate new connection`)
// Try to connect to the sender
sess = await initiate({ remoteId: sender, session, initiate: false, preferredIceServers })
sess = await initiate({ remoteId: sender, session, initiate: false, targetIceServer, preferredIceServers })
} else if (targetIceServer) {
sess.context.setTargetIceServer(targetIceServer)
}

// const currentIceServer = sess.context.getCurrentIceServer()
Expand Down

0 comments on commit 95d226e

Please sign in to comment.