diff --git a/packages/client-core/src/components/ConferenceMode/ConferenceModeParticipant.tsx b/packages/client-core/src/components/ConferenceMode/ConferenceModeParticipant.tsx
index fe162f2961..b025ea9394 100644
--- a/packages/client-core/src/components/ConferenceMode/ConferenceModeParticipant.tsx
+++ b/packages/client-core/src/components/ConferenceMode/ConferenceModeParticipant.tsx
@@ -2,6 +2,7 @@ import classNames from 'classnames'
import React from 'react'
import { getAvatarURLForUser } from '@xrengine/client-core/src/user/components/UserMenu/util'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import {
Mic,
@@ -23,10 +24,11 @@ import { useUserMediaWindowHook } from '../UserMediaWindow'
import styles from './index.module.scss'
interface Props {
- peerId?: string | 'cam_me' | 'screen_me'
+ peerID: PeerID
+ type: 'cam' | 'screen'
}
-const ConferenceModeParticipant = ({ peerId }: Props): JSX.Element => {
+const ConferenceModeParticipant = ({ peerID, type }: Props): JSX.Element => {
const {
user,
volume,
@@ -35,7 +37,7 @@ const ConferenceModeParticipant = ({ peerId }: Props): JSX.Element => {
selfUser,
audioRef,
videoRef,
- isSelfUser,
+ isSelf,
videoStream,
audioStream,
enableGlobalMute,
@@ -51,16 +53,16 @@ const ConferenceModeParticipant = ({ peerId }: Props): JSX.Element => {
toggleVideo,
adjustVolume,
toggleGlobalMute
- } = useUserMediaWindowHook({ peerId })
+ } = useUserMediaWindowHook({ peerID, type })
return (
{
>
{(videoStream == null || videoStreamPaused || videoProducerPaused || videoProducerGlobalMute) && (
)}
-
+
-
+
{username}
@@ -93,7 +95,7 @@ const ConferenceModeParticipant = ({ peerId }: Props): JSX.Element => {
) : null}
- {enableGlobalMute && peerId !== 'cam_me' && peerId !== 'screen_me' && audioStream && (
+ {enableGlobalMute && !isSelf && audioStream && (
{
{audioStream && !audioProducerPaused ? (
- {isSelfUser ? (
- audioStreamPaused ? (
-
- ) : (
-
- )
- ) : audioStreamPaused ? (
-
- ) : (
-
- )}
+ {isSelf ? audioStreamPaused ? : : audioStreamPaused ? : }
) : null}
diff --git a/packages/client-core/src/components/ConferenceMode/index.tsx b/packages/client-core/src/components/ConferenceMode/index.tsx
index 5d4d8f2d10..4fb4506ab0 100644
--- a/packages/client-core/src/components/ConferenceMode/index.tsx
+++ b/packages/client-core/src/components/ConferenceMode/index.tsx
@@ -45,8 +45,8 @@ const ConferenceMode = (): JSX.Element => {
for (let user of displayedUsers) {
totalScreens += 1
-
- if (screenShareConsumers.find((consumer) => consumer.appData.peerId === user.id)) {
+ const peerID = Array.from(network.peers.values()).find((peer) => peer.userId === user.id)?.peerID
+ if (screenShareConsumers.find((consumer) => consumer.appData.peerID === peerID)) {
totalScreens += 1
}
}
@@ -61,17 +61,14 @@ const ConferenceMode = (): JSX.Element => {
})}
>
{(mediaState.isScreenAudioEnabled.value || mediaState.isScreenVideoEnabled.value) && (
-
+
)}
-
- {displayedUsers.map((user) => (
- <>
-
- {screenShareConsumers.find((consumer) => consumer.appData.peerId === user.id) && (
-
- )}
- >
- ))}
+
+ {consumers.map((consumer) => {
+ const peerID = consumer.appData.peerID
+ const type = consumer.appData.mediaTag === 'screen-video' ? 'screen' : 'cam'
+ return
+ })}
)
}
diff --git a/packages/client-core/src/components/Debug/index.tsx b/packages/client-core/src/components/Debug/index.tsx
index f1817f28ac..1ca0aa9f19 100755
--- a/packages/client-core/src/components/Debug/index.tsx
+++ b/packages/client-core/src/components/Debug/index.tsx
@@ -37,6 +37,7 @@ import { StatsPanel } from './StatsPanel'
import styles from './styles.module.scss'
export const Debug = ({ showingStateRef }) => {
+ useHookstate(getState(EngineState).frameTime).value
const engineRendererState = useEngineRendererState()
const engineState = useHookstate(getState(EngineState))
const { t } = useTranslation()
diff --git a/packages/client-core/src/components/UserMediaWindow/index.tsx b/packages/client-core/src/components/UserMediaWindow/index.tsx
index a595feb031..ac2a1c5043 100755
--- a/packages/client-core/src/components/UserMediaWindow/index.tsx
+++ b/packages/client-core/src/components/UserMediaWindow/index.tsx
@@ -22,6 +22,7 @@ import {
import { getAvatarURLForUser } from '@xrengine/client-core/src/user/components/UserMenu/util'
import { useAuthState } from '@xrengine/client-core/src/user/services/AuthService'
import { useNetworkUserState } from '@xrengine/client-core/src/user/services/NetworkUserService'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { AudioSettingAction, useAudioState } from '@xrengine/engine/src/audio/AudioState'
import { isMobile } from '@xrengine/engine/src/common/functions/isMobile'
import { Engine } from '@xrengine/engine/src/ecs/classes/Engine'
@@ -55,10 +56,11 @@ import Draggable from './Draggable'
import styles from './index.module.scss'
interface Props {
- peerId?: string | 'cam_me' | 'screen_me'
+ peerID?: PeerID
+ type?: 'screen' | 'cam'
}
-export const useUserMediaWindowHook = ({ peerId }) => {
+export const useUserMediaWindowHook = ({ peerID, type }: Props) => {
const [isPiP, setPiP] = useState(false)
const [videoStream, _setVideoStream] = useState
(null)
const [audioStream, _setAudioStream] = useState(null)
@@ -98,10 +100,12 @@ export const useUserMediaWindowHook = ({ peerId }) => {
const enableGlobalMute =
currentLocation?.locationSetting?.locationType?.value === 'showroom' &&
selfUser?.locationAdmins?.find((locationAdmin) => currentLocation?.id?.value === locationAdmin.locationId) != null
- const isSelf = peerId === 'cam_me' || peerId === 'screen_me'
+
+ const mediaNetwork = Engine.instance.currentWorld.mediaNetwork
+ const isSelf = !mediaNetwork || peerID === mediaNetwork?.peerID
const volume = isSelf ? audioState.microphoneGain.value : _volume
- const isScreen = Boolean(peerId && peerId.startsWith('screen_'))
- const userId = isScreen ? peerId!.replace('screen_', '') : peerId
+ const isScreen = type === 'screen'
+ const userId = mediaNetwork ? mediaNetwork?.peers!.get(peerID!)?.userId : ''
const user = userState.layerUsers.find((user) => user.id.value === userId)?.attach(Downgraded).value
const isCamVideoEnabled = isScreen ? mediastream.isScreenVideoEnabled : mediastream.isCamVideoEnabled
@@ -159,13 +163,13 @@ export const useUserMediaWindowHook = ({ peerId }) => {
const network = Engine.instance.currentWorld.mediaNetwork as SocketWebRTCClientNetwork
const videoConsumer = network.consumers?.find(
(c) =>
- c.appData.peerId === userId &&
+ c.appData.peerID === peerID &&
c.producerId === producerId &&
c.appData.mediaTag === (isScreen ? 'screen-video' : 'cam-video')
)
const audioConsumer = network.consumers?.find(
(c) =>
- c.appData.peerId === userId &&
+ c.appData.peerID === peerID &&
c.producerId === producerId &&
c.appData.mediaTag === (isScreen ? 'screen-audio' : 'cam-audio')
)
@@ -190,10 +194,10 @@ export const useUserMediaWindowHook = ({ peerId }) => {
} else {
const network = Engine.instance.currentWorld.mediaNetwork as SocketWebRTCClientNetwork
const videoConsumer = network.consumers?.find(
- (c) => c.appData.peerId === userId && c.appData.mediaTag === (isScreen ? 'screen-video' : 'cam-video')
+ (c) => c.appData.peerID === peerID && c.appData.mediaTag === (isScreen ? 'screen-video' : 'cam-video')
)
const audioConsumer = network.consumers?.find(
- (c) => c.appData.peerId === userId && c.appData.mediaTag === (isScreen ? 'screen-audio' : 'cam-audio')
+ (c) => c.appData.peerID === peerID && c.appData.mediaTag === (isScreen ? 'screen-audio' : 'cam-audio')
)
if (videoConsumer) {
;(videoConsumer as any).producerPaused = false
@@ -220,28 +224,28 @@ export const useUserMediaWindowHook = ({ peerId }) => {
}
useEffect(() => {
- if (peerId === 'cam_me') {
+ if (isSelf && !isScreen) {
setVideoStream(MediaStreams.instance.camVideoProducer)
setVideoStreamPaused(MediaStreams.instance.videoPaused)
- } else if (peerId === 'screen_me') setVideoStream(MediaStreams.instance.screenVideoProducer)
+ } else if (isSelf && isScreen) setVideoStream(MediaStreams.instance.screenVideoProducer)
}, [isCamVideoEnabled.value])
useEffect(() => {
- if (peerId === 'cam_me') {
+ if (isSelf && !isScreen) {
setAudioStream(MediaStreams.instance.camAudioProducer)
setAudioStreamPaused(MediaStreams.instance.audioPaused)
- } else if (peerId === 'screen_me') setAudioStream(MediaStreams.instance.screenAudioProducer)
+ } else if (isSelf && isScreen) setAudioStream(MediaStreams.instance.screenAudioProducer)
}, [isCamAudioEnabled.value])
useEffect(() => {
- if (peerId !== 'cam_me' && peerId !== 'screen_me') {
+ if (!isSelf) {
const network = Engine.instance.currentWorld.mediaNetwork as SocketWebRTCClientNetwork
if (network) {
const videoConsumer = network.consumers?.find(
- (c) => c.appData.peerId === userId && c.appData.mediaTag === (isScreen ? 'screen-video' : 'cam-video')
+ (c) => c.appData.peerID === peerID && c.appData.mediaTag === (isScreen ? 'screen-video' : 'cam-video')
)
const audioConsumer = network.consumers?.find(
- (c) => c.appData.peerId === userId && c.appData.mediaTag === (isScreen ? 'screen-audio' : 'cam-audio')
+ (c) => c.appData.peerID === peerID && c.appData.mediaTag === (isScreen ? 'screen-audio' : 'cam-audio')
)
if (videoConsumer) {
setVideoProducerPaused((videoConsumer as any).producerPaused)
@@ -258,7 +262,7 @@ export const useUserMediaWindowHook = ({ peerId }) => {
}, [consumers.value])
useEffect(() => {
- if (userHasInteracted.value && peerId !== 'cam_me' && peerId !== 'screen_me') {
+ if (userHasInteracted.value && !isSelf) {
videoRef.current?.play()
audioRef.current?.play()
if (harkListener) (harkListener as any).resume()
@@ -269,9 +273,23 @@ export const useUserMediaWindowHook = ({ peerId }) => {
if (!currentChannelInstanceConnection?.value) return
const mediaNetwork = Engine.instance.currentWorld.mediaNetwork as SocketWebRTCClientNetwork
const socket = mediaNetwork.socket
+ if (typeof socket?.on === 'function') socket?.on(MessageTypes.WebRTCPauseConsumer.toString(), pauseConsumerListener)
+ if (typeof socket?.on === 'function')
+ socket?.on(MessageTypes.WebRTCResumeConsumer.toString(), resumeConsumerListener)
+ if (typeof socket?.on === 'function') socket?.on(MessageTypes.WebRTCPauseProducer.toString(), pauseProducerListener)
+ if (typeof socket?.on === 'function')
+ socket?.on(MessageTypes.WebRTCResumeProducer.toString(), resumeProducerListener)
if (typeof socket?.on === 'function') socket?.on(MessageTypes.WebRTCCloseProducer.toString(), closeProducerListener)
return () => {
+ if (typeof socket?.on === 'function')
+ socket?.off(MessageTypes.WebRTCPauseConsumer.toString(), pauseConsumerListener)
+ if (typeof socket?.on === 'function')
+ socket?.off(MessageTypes.WebRTCResumeConsumer.toString(), resumeConsumerListener)
+ if (typeof socket?.on === 'function')
+ socket?.off(MessageTypes.WebRTCPauseProducer.toString(), pauseProducerListener)
+ if (typeof socket?.on === 'function')
+ socket?.off(MessageTypes.WebRTCResumeProducer.toString(), resumeProducerListener)
if (typeof socket?.on === 'function')
socket?.off(MessageTypes.WebRTCCloseProducer.toString(), closeProducerListener)
}
@@ -279,10 +297,10 @@ export const useUserMediaWindowHook = ({ peerId }) => {
useEffect(() => {
if (audioRef.current != null) {
- audioRef.current.id = `${peerId}_audio`
+ audioRef.current.id = `${peerID}_audio`
audioRef.current.autoplay = true
audioRef.current.setAttribute('playsinline', 'true')
- if (peerId === 'cam_me' || peerId === 'screen_me') {
+ if (isSelf) {
audioRef.current.muted = true
} else {
audioRef.current.volume = volume
@@ -312,13 +330,13 @@ export const useUserMediaWindowHook = ({ peerId }) => {
useEffect(() => {
if (videoRef.current != null) {
- videoRef.current.id = `${peerId}_video`
+ videoRef.current.id = `${peerID}_video`
videoRef.current.autoplay = true
videoRef.current.muted = true
videoRef.current.setAttribute('playsinline', 'true')
if (videoStream != null) {
setVideoDisplayReady(false)
- if (peerId === 'cam_me' || peerId === 'screen_me') setVideoProducerPaused(false)
+ if (isSelf) setVideoProducerPaused(false)
const originalTrackEnabledInterval = setInterval(() => {
if (videoStream.track.enabled) {
clearInterval(originalTrackEnabledInterval)
@@ -344,13 +362,13 @@ export const useUserMediaWindowHook = ({ peerId }) => {
}, [videoTrackId])
useEffect(() => {
- if (peerId === 'cam_me' || peerId === 'screen_me') {
+ if (isSelf) {
setAudioStreamPaused(MediaStreams.instance.audioPaused)
}
}, [MediaStreams.instance.audioPaused])
useEffect(() => {
- if (peerId === 'cam_me' || peerId === 'screen_me') {
+ if (isSelf) {
setVideoStreamPaused(MediaStreams.instance.videoPaused)
if (videoRef.current != null) {
if (MediaStreams.instance.videoPaused) {
@@ -362,12 +380,7 @@ export const useUserMediaWindowHook = ({ peerId }) => {
}, [MediaStreams.instance.videoPaused])
useEffect(() => {
- if (
- !(peerId === 'cam_me' || peerId === 'screen_me') &&
- !videoProducerPaused &&
- videoStream != null &&
- videoRef.current != null
- ) {
+ if (!isSelf && !videoProducerPaused && videoStream != null && videoRef.current != null) {
const originalTrackEnabledInterval = setInterval(() => {
if (videoStream.track.enabled) {
clearInterval(originalTrackEnabledInterval)
@@ -384,12 +397,7 @@ export const useUserMediaWindowHook = ({ peerId }) => {
}, [videoProducerPaused])
useEffect(() => {
- if (
- !(peerId === 'cam_me' || peerId === 'screen_me') &&
- !audioProducerPaused &&
- audioStream != null &&
- audioRef.current != null
- ) {
+ if (!isSelf && !audioProducerPaused && audioStream != null && audioRef.current != null) {
const originalTrackEnabledInterval = setInterval(() => {
if (audioStream.track.enabled) {
clearInterval(originalTrackEnabledInterval)
@@ -416,7 +424,7 @@ export const useUserMediaWindowHook = ({ peerId }) => {
const toggleVideo = async (e) => {
e.stopPropagation()
const mediaNetwork = Engine.instance.currentWorld.mediaNetwork as SocketWebRTCClientNetwork
- if (peerId === 'cam_me') {
+ if (isSelf && !isScreen) {
if (await configureMediaTransports(mediaNetwork, ['video'])) {
if (MediaStreams.instance.camVideoProducer == null) await createCamVideoProducer(mediaNetwork)
else {
@@ -426,7 +434,7 @@ export const useUserMediaWindowHook = ({ peerId }) => {
}
MediaStreamService.updateCamVideoState()
}
- } else if (peerId === 'screen_me') {
+ } else if (isSelf && isScreen) {
const videoPaused = MediaStreams.instance.toggleScreenShareVideoPaused()
if (videoPaused) await stopScreenshare(mediaNetwork)
// else await resumeProducer(mediaNetwork, MediaStreams.instance.screenVideoProducer)
@@ -449,7 +457,7 @@ export const useUserMediaWindowHook = ({ peerId }) => {
const toggleAudio = async (e) => {
e.stopPropagation()
const mediaNetwork = Engine.instance.currentWorld.mediaNetwork as SocketWebRTCClientNetwork
- if (peerId === 'cam_me') {
+ if (isSelf && !isScreen) {
if (await configureMediaTransports(mediaNetwork, ['audio'])) {
if (MediaStreams.instance.camAudioProducer == null) await createCamAudioProducer(mediaNetwork)
else {
@@ -459,7 +467,7 @@ export const useUserMediaWindowHook = ({ peerId }) => {
}
MediaStreamService.updateCamAudioState()
}
- } else if (peerId === 'screen_me') {
+ } else if (isSelf && isScreen) {
const audioPaused = MediaStreams.instance.toggleScreenShareAudioPaused()
if (audioPaused) await pauseProducer(mediaNetwork, MediaStreams.instance.screenAudioProducer)
else await resumeProducer(mediaNetwork, MediaStreams.instance.screenAudioProducer)
@@ -499,15 +507,14 @@ export const useUserMediaWindowHook = ({ peerId }) => {
}
const getUsername = () => {
- if (peerId === 'cam_me') return t('user:person.you')
- if (peerId === 'screen_me') return t('user:person.yourScreen')
- if (peerId?.startsWith('screen_')) return user?.name + "'s Screen"
+ if (isSelf && !isScreen) return t('user:person.you')
+ if (isSelf && isScreen) return t('user:person.yourScreen')
+ if (!isSelf && isScreen) return user?.name + "'s Screen"
return user?.name
}
const togglePiP = () => setPiP(!isPiP)
- const isSelfUser = peerId === 'cam_me' || peerId === 'screen_me'
const username = getUsername()
const userAvatarDetails = useHookstate(getState(WorldState).userAvatarDetails)
@@ -560,7 +567,7 @@ export const useUserMediaWindowHook = ({ peerId }) => {
selfUser,
audioRef,
videoRef,
- isSelfUser,
+ isSelf,
videoStream,
audioStream,
enableGlobalMute,
@@ -583,7 +590,7 @@ export const useUserMediaWindowHook = ({ peerId }) => {
}
}
-const UserMediaWindow = ({ peerId }: Props): JSX.Element => {
+const UserMediaWindow = ({ peerID, type }: Props): JSX.Element => {
const {
user,
isPiP,
@@ -593,7 +600,7 @@ const UserMediaWindow = ({ peerId }: Props): JSX.Element => {
selfUser,
audioRef,
videoRef,
- isSelfUser,
+ isSelf,
videoStream,
audioStream,
enableGlobalMute,
@@ -613,18 +620,18 @@ const UserMediaWindow = ({ peerId }: Props): JSX.Element => {
adjustVolume,
toggleGlobalMute,
rendered
- } = useUserMediaWindowHook({ peerId })
+ } = useUserMediaWindowHook({ peerID, type })
return (
{
})}
style={{
pointerEvents: 'auto',
- display: isSelfUser || rendered ? 'auto' : 'none'
+ display: isSelf || rendered ? 'auto' : 'none'
}}
onClick={() => {
if (isScreen && isPiP) togglePiP()
@@ -651,15 +658,15 @@ const UserMediaWindow = ({ peerId }: Props): JSX.Element => {
videoProducerGlobalMute ||
!videoDisplayReady) && (
)}
-
+
-
+
{username}
@@ -679,7 +686,7 @@ const UserMediaWindow = ({ peerId }: Props): JSX.Element => {
) : null}
- {enableGlobalMute && peerId !== 'cam_me' && peerId !== 'screen_me' && audioStream && (
+ {enableGlobalMute && !isSelf && audioStream && (
{
{audioStream && !audioProducerPaused ? (
{
})}
onClick={toggleAudio}
>
- {isSelfUser ? (
+ {isSelf ? (
audioStreamPaused ? (
) : (
diff --git a/packages/client-core/src/components/UserMediaWindows/index.tsx b/packages/client-core/src/components/UserMediaWindows/index.tsx
index 8533d56095..aa938cbb07 100755
--- a/packages/client-core/src/components/UserMediaWindows/index.tsx
+++ b/packages/client-core/src/components/UserMediaWindows/index.tsx
@@ -5,21 +5,24 @@ import { useMediaInstanceConnectionState } from '@xrengine/client-core/src/commo
import { useMediaStreamState } from '@xrengine/client-core/src/media/services/MediaStreamService'
import { accessAuthState } from '@xrengine/client-core/src/user/services/AuthService'
import { useNetworkUserState } from '@xrengine/client-core/src/user/services/NetworkUserService'
+import { MediaTagType } from '@xrengine/common/src/interfaces/MediaStreamConstants'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { Engine } from '@xrengine/engine/src/ecs/classes/Engine'
+import { ConsumerExtension, SocketWebRTCClientNetwork } from '../../transports/SocketWebRTCClientNetwork'
import { useShelfStyles } from '../Shelves/useShelfStyles'
import UserMediaWindow from '../UserMediaWindow'
import styles from './index.module.scss'
-export const UserMediaWindows = () => {
+export const filterWindows = (network: SocketWebRTCClientNetwork, consumers: ConsumerExtension[]) => {
const mediaState = useMediaStreamState()
const nearbyLayerUsers = mediaState.nearbyLayerUsers
const selfUserId = useState(accessAuthState().user.id)
const userState = useNetworkUserState()
const channelConnectionState = useMediaInstanceConnectionState()
- const network = Engine.instance.currentWorld.mediaNetwork
const currentChannelInstanceConnection = network && channelConnectionState.instances[network.hostId].ornull
- const displayedUsers =
+
+ const displayedUsers = (
network?.hostId && currentChannelInstanceConnection
? currentChannelInstanceConnection.channelType?.value === 'party'
? userState.channelLayerUsers?.value.filter((user) => {
@@ -33,9 +36,37 @@ export const UserMediaWindows = () => {
? userState.layerUsers.value.filter((user) => nearbyLayerUsers.value.includes(user.id))
: []
: []
+ ).map((user) => user.id)
+
+ const windows = [] as Array<{ peerID: PeerID; mediaTag?: MediaTagType }>
+
+ // filter out pairs of cam video & cam audio
+ consumers.forEach((consumer) => {
+ const isUnique = !windows.find(
+ (u) =>
+ consumer.appData.peerID === u.peerID &&
+ ((consumer.appData.mediaTag === 'cam-video' && u.mediaTag === 'cam-audio') ||
+ (consumer.appData.mediaTag === 'cam-audio' && u.mediaTag === 'cam-video'))
+ )
+ if (isUnique && displayedUsers.includes(network.peers.get(consumer.appData.peerID)?.userId!))
+ windows.push({ peerID: consumer.appData.peerID, mediaTag: consumer.appData.mediaTag })
+ })
+
+ // include a peer for each user without any consumers
+ if (network)
+ displayedUsers.forEach((userId) => {
+ const peerID = Array.from(network.peers.values()).find((peer) => peer.userId === userId)?.peerID
+ if (peerID && !windows.find((window) => window.peerID === peerID)) windows.push({ peerID })
+ })
+
+ return windows
+}
+
+export const UserMediaWindows = () => {
+ const mediaState = useMediaStreamState()
+ const network = Engine.instance.currentWorld.mediaNetwork as SocketWebRTCClientNetwork
- const consumers = mediaState.consumers.value
- const screenShareConsumers = consumers?.filter((consumer) => consumer.appData.mediaTag === 'screen-video') || []
+ const consumers = filterWindows(network, mediaState.consumers.get({ noproxy: true }))
const { topShelfStyle } = useShelfStyles()
@@ -43,17 +74,13 @@ export const UserMediaWindows = () => {
{(mediaState.isScreenAudioEnabled.value || mediaState.isScreenVideoEnabled.value) && (
-
+
)}
-
- {displayedUsers.map((user) => (
-
-
- {screenShareConsumers.find((consumer) => consumer.appData.peerId === user.id) && (
-
- )}
-
- ))}
+
+ {consumers.map(({ peerID, mediaTag }) => {
+ const type = mediaTag === 'screen-video' ? 'screen' : 'cam'
+ return
+ })}
)
diff --git a/packages/client-core/src/components/World/OfflineLocation.tsx b/packages/client-core/src/components/World/OfflineLocation.tsx
index 70260e12af..6dd36a947f 100644
--- a/packages/client-core/src/components/World/OfflineLocation.tsx
+++ b/packages/client-core/src/components/World/OfflineLocation.tsx
@@ -1,6 +1,7 @@
-import React from 'react'
+import React, { useEffect } from 'react'
import { useAuthState } from '@xrengine/client-core/src/user/services/AuthService'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { Engine } from '@xrengine/engine/src/ecs/classes/Engine'
import { getEngineState } from '@xrengine/engine/src/ecs/classes/EngineState'
import { Network, NetworkTopics } from '@xrengine/engine/src/networking/classes/Network'
@@ -15,22 +16,35 @@ export const OfflineLocation = () => {
const authState = useAuthState()
/** OFFLINE */
- useHookEffect(() => {
+ useEffect(() => {
if (engineState.sceneLoaded.value) {
const world = Engine.instance.currentWorld
const userId = Engine.instance.userId
+ const userIndex = 1
+ const peerID = 'peerID' as PeerID
+ const peerIndex = 1
world.hostIds.world.set(userId)
world.networks.set(userId, new Network(userId, NetworkTopics.world))
addOutgoingTopicIfNecessary(NetworkTopics.world)
- const index = 1
- NetworkPeerFunctions.createPeer(world.worldNetwork, userId, index, authState.user.name.value, world)
+ NetworkPeerFunctions.createPeer(
+ world.worldNetwork,
+ peerID,
+ peerIndex,
+ userId,
+ userIndex,
+ authState.user.name.value,
+ world
+ )
receiveJoinWorld({
highResTimeOrigin: performance.timeOrigin,
worldStartTime: performance.now(),
- cachedActions: []
+ cachedActions: [],
+ peerIndex,
+ peerID,
+ routerRtpCapabilities: undefined
})
}
}, [engineState.connectedWorld, engineState.sceneLoaded])
diff --git a/packages/client-core/src/media/services/MediaStreamService.ts b/packages/client-core/src/media/services/MediaStreamService.ts
index e6aba62233..a4bc11f2e7 100755
--- a/packages/client-core/src/media/services/MediaStreamService.ts
+++ b/packages/client-core/src/media/services/MediaStreamService.ts
@@ -7,7 +7,7 @@ import { Engine } from '@xrengine/engine/src/ecs/classes/Engine'
import { getNearbyUsers } from '@xrengine/engine/src/networking/functions/getNearbyUsers'
import { defineAction, defineState, dispatchAction, getState, useState } from '@xrengine/hyperflux'
-import { SocketWebRTCClientNetwork } from '../../transports/SocketWebRTCClientNetwork'
+import { ConsumerExtension, SocketWebRTCClientNetwork } from '../../transports/SocketWebRTCClientNetwork'
import { accessNetworkUserState } from '../../user/services/NetworkUserService'
//State
@@ -21,7 +21,7 @@ export const MediaState = defineState({
isFaceTrackingEnabled: false,
enableBydefault: true,
nearbyLayerUsers: [] as UserId[],
- consumers: [] as mediasoup.types.Consumer[]
+ consumers: [] as ConsumerExtension[]
})
})
diff --git a/packages/client-core/src/systems/AvatarUISystem.tsx b/packages/client-core/src/systems/AvatarUISystem.tsx
index 735e766429..19cd8aee7e 100644
--- a/packages/client-core/src/systems/AvatarUISystem.tsx
+++ b/packages/client-core/src/systems/AvatarUISystem.tsx
@@ -15,7 +15,7 @@ import { removeEntity } from '@xrengine/engine/src/ecs/functions/EntityFunctions
import { InputComponent } from '@xrengine/engine/src/input/components/InputComponent'
import { BaseInput } from '@xrengine/engine/src/input/enums/BaseInput'
import { NetworkObjectComponent } from '@xrengine/engine/src/networking/components/NetworkObjectComponent'
-import { NetworkObjectOwnedTag } from '@xrengine/engine/src/networking/components/NetworkObjectOwnedTag'
+import { NetworkObjectOwnedTag } from '@xrengine/engine/src/networking/components/NetworkObjectComponent'
import { shouldUseImmersiveMedia } from '@xrengine/engine/src/networking/MediaSettingsState'
import { Physics, RaycastArgs } from '@xrengine/engine/src/physics/classes/Physics'
import { CollisionGroups } from '@xrengine/engine/src/physics/enums/CollisionGroups'
@@ -183,7 +183,8 @@ export default async function AvatarUISystem(world: World) {
if (immersiveMedia && videoPreviewTimer === 0) {
const { ownerId } = getComponent(userEntity, NetworkObjectComponent)
const consumer = world.mediaNetwork!.consumers.find(
- (consumer) => consumer._appData.peerId === ownerId && consumer._appData.mediaTag === 'cam-video'
+ (consumer) =>
+ consumer.appData.peerID === world.mediaNetwork.peerID && consumer.appData.mediaTag === 'cam-video'
) as Consumer
const paused = consumer && (consumer as any).producerPaused
if (videoPreviewMesh.material.map) {
diff --git a/packages/client-core/src/transports/SocketWebRTCClientFunctions.ts b/packages/client-core/src/transports/SocketWebRTCClientFunctions.ts
index 6f023a880e..7522b00ce6 100755
--- a/packages/client-core/src/transports/SocketWebRTCClientFunctions.ts
+++ b/packages/client-core/src/transports/SocketWebRTCClientFunctions.ts
@@ -1,5 +1,4 @@
import {
- Consumer,
DtlsParameters,
MediaKind,
Transport as MediaSoupTransport,
@@ -11,7 +10,8 @@ import { MediaStreams } from '@xrengine/client-core/src/transports/MediaStreams'
import config from '@xrengine/common/src/config'
import { AuthTask } from '@xrengine/common/src/interfaces/AuthTask'
import { ChannelType } from '@xrengine/common/src/interfaces/Channel'
-import { MediaTagType } from '@xrengine/common/src/interfaces/MediaStreamConstants'
+import { MediaStreamAppData, MediaTagType } from '@xrengine/common/src/interfaces/MediaStreamConstants'
+import { PeerID, PeersUpdateType } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import multiLogger from '@xrengine/common/src/logger'
import { getSearchParamFromURL } from '@xrengine/common/src/utils/getSearchParamFromURL'
@@ -25,7 +25,6 @@ import {
SCREEN_SHARE_SIMULCAST_ENCODINGS
} from '@xrengine/engine/src/networking/constants/VideoConstants'
import { MessageTypes } from '@xrengine/engine/src/networking/enums/MessageTypes'
-import { MediaNetworkAction } from '@xrengine/engine/src/networking/functions/MediaNetworkAction'
import { NetworkPeerFunctions } from '@xrengine/engine/src/networking/functions/NetworkPeerFunctions'
import { JoinWorldRequestData, receiveJoinWorld } from '@xrengine/engine/src/networking/functions/receiveJoinWorld'
import {
@@ -45,15 +44,11 @@ import {
import { NetworkConnectionService } from '../common/services/NetworkConnectionService'
import { MediaStreamAction, MediaStreamService } from '../media/services/MediaStreamService'
import { accessAuthState } from '../user/services/AuthService'
-import { SocketWebRTCClientNetwork } from './SocketWebRTCClientNetwork'
+import { ConsumerExtension, SocketWebRTCClientNetwork } from './SocketWebRTCClientNetwork'
import { updateNearbyAvatars } from './UpdateNearbyUsersSystem'
const logger = multiLogger.child({ component: 'client-core:SocketWebRTCClientFunctions' })
-class ExtendedConsumer extends Consumer {
- producerPaused?: boolean
-}
-
export const getChannelTypeIdFromTransport = (network: SocketWebRTCClientNetwork) => {
const channelConnectionState = accessMediaInstanceConnectionState()
const mediaNetwork = Engine.instance.currentWorld.mediaNetwork
@@ -74,12 +69,6 @@ function actionDataHandler(message) {
}
}
-type PeersUpdateType = Array<{
- userId: UserId
- index: number
- name: string
-}>
-
export async function onConnectToInstance(network: SocketWebRTCClientNetwork) {
const isWorldConnection = network.topic === NetworkTopics.world
logger.info('Connecting to instance type: %o', { topic: network.topic, hostId: network.hostId })
@@ -110,12 +99,12 @@ export async function onConnectToInstance(network: SocketWebRTCClientNetwork) {
return logger.error(new Error('Unable to connect with credentials'))
}
- function peerUpdateHandler(peers: PeersUpdateType) {
+ function peerUpdateHandler(peers: Array) {
for (const peer of peers) {
- NetworkPeerFunctions.createPeer(network, peer.userId, peer.index, peer.name)
+ NetworkPeerFunctions.createPeer(network, peer.peerID, peer.peerIndex, peer.userID, peer.userIndex, peer.name)
}
- for (const [userId, peer] of network.peers) {
- if (!peers.find((p) => p.userId === userId)) NetworkPeerFunctions.destroyPeer(network, userId)
+ for (const [peerID, peer] of network.peers) {
+ if (!peers.find((p) => p.peerID === peerID)) NetworkPeerFunctions.destroyPeer(network, peerID)
}
logger.info('Updated peers %o', { topic: network.topic, peers })
}
@@ -234,13 +223,19 @@ export async function onConnectToMediaInstance(network: SocketWebRTCClientNetwor
dispatchAction(MediaStreams.actions.triggerUpdateConsumers({}))
}
- async function webRTCCreateProducerHandler(socketId, mediaTag, producerId, channelType: ChannelType, channelId) {
+ async function webRTCCreateProducerHandler(
+ peerID: PeerID,
+ mediaTag: MediaTagType,
+ producerId,
+ channelType: ChannelType,
+ channelId
+ ) {
const selfProducerIds = [MediaStreams.instance.camVideoProducer?.id, MediaStreams.instance.camAudioProducer?.id]
const channelConnectionState = accessMediaInstanceConnectionState()
const currentChannelInstanceConnection = channelConnectionState.instances[network.hostId].ornull
const consumerMatch = network.consumers?.find(
- (c) => c?.appData?.peerId === socketId && c?.appData?.mediaTag === mediaTag && c?.producerId === producerId
+ (c) => c?.appData?.peerID === peerID && c?.appData?.mediaTag === mediaTag && c?.producerId === producerId
)
if (
producerId != null &&
@@ -253,7 +248,7 @@ export async function onConnectToMediaInstance(network: SocketWebRTCClientNetwor
currentChannelInstanceConnection.channelId.value === channelId)
) {
// that we don't already have consumers for...
- await subscribeToTrack(network as SocketWebRTCClientNetwork, socketId, mediaTag)
+ await subscribeToTrack(network as SocketWebRTCClientNetwork, peerID, mediaTag)
}
}
@@ -320,6 +315,8 @@ export async function onConnectToMediaInstance(network: SocketWebRTCClientNetwor
network.consumers.forEach((consumer) => closeConsumer(network, consumer))
network.socket.off(MessageTypes.WebRTCCreateProducer.toString(), webRTCCreateProducerHandler)
dispatchAction(NetworkConnectionService.actions.mediaInstanceDisconnected({}))
+ network.socket.off(MessageTypes.WebRTCPauseConsumer.toString(), webRTCPauseConsumerHandler)
+ network.socket.off(MessageTypes.WebRTCResumeConsumer.toString(), webRTCResumeConsumerHandler)
network.socket.off(MessageTypes.WebRTCCloseConsumer.toString(), webRTCCloseConsumerHandler)
removeActionReceptor(consumerHandler)
}
@@ -327,6 +324,8 @@ export async function onConnectToMediaInstance(network: SocketWebRTCClientNetwor
network.socket.on('disconnect', disconnectHandler)
network.socket.on(MessageTypes.WebRTCCreateProducer.toString(), webRTCCreateProducerHandler)
network.socket.io.on('reconnect', reconnectHandler)
+ network.socket.on(MessageTypes.WebRTCPauseConsumer.toString(), webRTCPauseConsumerHandler)
+ network.socket.on(MessageTypes.WebRTCResumeConsumer.toString(), webRTCResumeConsumerHandler)
network.socket.on(MessageTypes.WebRTCCloseConsumer.toString(), webRTCCloseConsumerHandler)
addActionReceptor(consumerHandler)
@@ -421,7 +420,7 @@ export async function createTransport(network: SocketWebRTCClientNetwork, direct
kind,
rtpParameters,
appData
- }: { kind: MediaKind; rtpParameters: RtpParameters; appData: Record },
+ }: { kind: MediaKind; rtpParameters: RtpParameters; appData: MediaStreamAppData },
callback: (arg0: { id: string }) => void,
errback: (error: Error) => void
) => {
@@ -787,7 +786,7 @@ export function resetProducer(): void {
}
}
-export async function subscribeToTrack(network: SocketWebRTCClientNetwork, peerId: string, mediaTag: string) {
+export async function subscribeToTrack(network: SocketWebRTCClientNetwork, peerID: PeerID, mediaTag: MediaTagType) {
const socket = network.socket
if (!socket?.connected) return
const channelConnectionState = accessMediaInstanceConnectionState()
@@ -798,7 +797,7 @@ export async function subscribeToTrack(network: SocketWebRTCClientNetwork, peerI
// ask the server to create a server-side consumer object and send us back the info we need to create a client-side consumer
const consumerParameters = await network.request(MessageTypes.WebRTCReceiveTrack.toString(), {
mediaTag,
- mediaPeerId: peerId,
+ mediaPeerId: peerID,
rtpCapabilities: network.mediasoupDevice.rtpCapabilities,
channelType: channelType,
channelId: channelId
@@ -807,17 +806,17 @@ export async function subscribeToTrack(network: SocketWebRTCClientNetwork, peerI
// Only continue if we have a valid id
if (consumerParameters?.id == null) return
- const consumer: ExtendedConsumer = await network.recvTransport.consume({
+ const consumer = (await network.recvTransport.consume({
...consumerParameters,
- appData: { peerId, mediaTag, channelType },
+ appData: { peerID, mediaTag, channelType },
paused: true
- })
+ })) as unknown as ConsumerExtension
consumer.producerPaused = consumerParameters.producerPaused
// if we do already have a consumer, we shouldn't have called this method
const existingConsumer = network.consumers?.find(
- (c) => c?.appData?.peerId === peerId && c?.appData?.mediaTag === mediaTag
+ (c) => c?.appData?.peerID === peerID && c?.appData?.mediaTag === mediaTag
)
if (existingConsumer == null) {
network.consumers.push(consumer)
@@ -835,39 +834,72 @@ export async function subscribeToTrack(network: SocketWebRTCClientNetwork, peerI
dispatchAction(MediaStreams.actions.triggerUpdateConsumers({}))
}
-export async function unsubscribeFromTrack(network: SocketWebRTCClientNetwork, peerId: any, mediaTag: any) {
- const consumer = network.consumers.find((c) => c.appData.peerId === peerId && c.appData.mediaTag === mediaTag)
+export async function unsubscribeFromTrack(network: SocketWebRTCClientNetwork, peerID: PeerID, mediaTag: any) {
+ const consumer = network.consumers.find((c) => c.appData.peerID === peerID && c.appData.mediaTag === mediaTag)
await closeConsumer(network, consumer)
}
-export async function pauseConsumer(network: SocketWebRTCClientNetwork, consumer: ExtendedConsumer) {
- dispatchAction(MediaNetworkAction.pauseConsumer({ consumerId: consumer.id, pause: true }))
+export async function pauseConsumer(network: SocketWebRTCClientNetwork, consumer: ConsumerExtension) {
+ await network.request(MessageTypes.WebRTCPauseConsumer.toString(), {
+ consumerId: consumer.id
+ })
+ if (consumer && typeof consumer.pause === 'function')
+ network.mediasoupOperationQueue.add({
+ object: consumer,
+ action: 'pause'
+ })
}
-export async function resumeConsumer(network: SocketWebRTCClientNetwork, consumer: ExtendedConsumer) {
- dispatchAction(MediaNetworkAction.pauseConsumer({ consumerId: consumer.id, pause: false }))
+export async function resumeConsumer(network: SocketWebRTCClientNetwork, consumer: ConsumerExtension) {
+ await network.request(MessageTypes.WebRTCResumeConsumer.toString(), {
+ consumerId: consumer.id
+ })
+ if (consumer && typeof consumer.resume === 'function')
+ network.mediasoupOperationQueue.add({
+ object: consumer,
+ action: 'resume'
+ })
}
export async function pauseProducer(
network: SocketWebRTCClientNetwork,
producer: { appData: MediaTagType; id: any; pause: () => any }
) {
- dispatchAction(MediaNetworkAction.pauseProducer({ producerId: producer.id, pause: true }))
+ await network.request(MessageTypes.WebRTCPauseProducer.toString(), {
+ producerId: producer.id
+ })
+ if (producer && typeof producer.pause === 'function')
+ network.mediasoupOperationQueue.add({
+ object: producer,
+ action: 'pause'
+ })
}
export async function resumeProducer(
network: SocketWebRTCClientNetwork,
producer: { appData: MediaTagType; id: any; resume: () => any }
) {
- dispatchAction(MediaNetworkAction.pauseProducer({ producerId: producer.id, pause: false }))
+ await network.request(MessageTypes.WebRTCResumeProducer.toString(), {
+ producerId: producer.id
+ })
+ if (producer && typeof producer.resume === 'function')
+ network.mediasoupOperationQueue.add({
+ object: producer,
+ action: 'resume'
+ })
}
export async function globalMuteProducer(network: SocketWebRTCClientNetwork, producer: { id: any }) {
- dispatchAction(MediaNetworkAction.pauseProducer({ producerId: producer.id, pause: true, globalMute: true }))
+ await network.request(MessageTypes.WebRTCPauseProducer.toString(), {
+ producerId: producer.id,
+ globalMute: true
+ })
}
export async function globalUnmuteProducer(network: SocketWebRTCClientNetwork, producer: { id: any }) {
- dispatchAction(MediaNetworkAction.pauseProducer({ producerId: producer.id, pause: false }))
+ await network.request(MessageTypes.WebRTCResumeProducer.toString(), {
+ producerId: producer.id
+ })
}
export async function closeConsumer(network: SocketWebRTCClientNetwork, consumer: any) {
diff --git a/packages/client-core/src/transports/SocketWebRTCClientNetwork.ts b/packages/client-core/src/transports/SocketWebRTCClientNetwork.ts
index df7bb98e15..b5187fdde5 100755
--- a/packages/client-core/src/transports/SocketWebRTCClientNetwork.ts
+++ b/packages/client-core/src/transports/SocketWebRTCClientNetwork.ts
@@ -4,6 +4,7 @@ import { io as ioclient, Socket } from 'socket.io-client'
import config from '@xrengine/common/src/config'
import { Channel } from '@xrengine/common/src/interfaces/Channel'
+import { MediaStreamAppData } from '@xrengine/common/src/interfaces/MediaStreamConstants'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import multiLogger from '@xrengine/common/src/logger'
import { Engine } from '@xrengine/engine/src/ecs/classes/Engine'
@@ -29,6 +30,10 @@ import { onConnectToInstance } from './SocketWebRTCClientFunctions'
const logger = multiLogger.child({ component: 'client-core:SocketWebRTCClientNetwork' })
+export type WebRTCTransportExtension = Omit & { appData: MediaStreamAppData }
+export type ProducerExtension = Omit & { appData: MediaStreamAppData }
+export type ConsumerExtension = Omit & { appData: MediaStreamAppData; producerPaused: boolean }
+
// import { encode, decode } from 'msgpackr'
// Adds support for Promise to socket.io-client
@@ -94,8 +99,8 @@ export class SocketWebRTCClientNetwork extends Network {
dataProducer: DataProducer
heartbeat: NodeJS.Timer // is there an equivalent browser type for this?
- producers = [] as Producer[]
- consumers = [] as Consumer[]
+ producers = [] as ProducerExtension[]
+ consumers = [] as ConsumerExtension[]
sendActions() {
if (!this.ready) return
diff --git a/packages/client-core/src/transports/UpdateNearbyUsersSystem.ts b/packages/client-core/src/transports/UpdateNearbyUsersSystem.ts
index d88f49261b..287fa4d611 100755
--- a/packages/client-core/src/transports/UpdateNearbyUsersSystem.ts
+++ b/packages/client-core/src/transports/UpdateNearbyUsersSystem.ts
@@ -31,7 +31,7 @@ export const updateNearbyAvatars = () => {
const nearbyUserIds = mediaState.nearbyLayerUsers.value
network?.consumers.forEach((consumer) => {
- if (!nearbyUserIds.includes(consumer._appData.peerId)) {
+ if (!nearbyUserIds.includes(network.peers.get(consumer.appData.peerID)?.userId!)) {
dispatchAction(MediaStreams.actions.closeConsumer({ consumer }))
}
})
diff --git a/packages/common/src/interfaces/MediaStreamConstants.ts b/packages/common/src/interfaces/MediaStreamConstants.ts
index eb7e8cbbb2..e5e2eca766 100644
--- a/packages/common/src/interfaces/MediaStreamConstants.ts
+++ b/packages/common/src/interfaces/MediaStreamConstants.ts
@@ -1,11 +1,13 @@
import { ChannelType } from './Channel'
+import { PeerID } from './PeerID'
export type MediaStreamAppData = {
mediaTag: MediaTagType
- peerId: string
+ peerID: PeerID
direction: TransportDirection
channelType: ChannelType
channelId: string
+ clientDirection?: 'recv' | 'send'
}
export type MediaTagType = 'cam-video' | 'cam-audio' | 'screen-video' | 'screen-audio'
diff --git a/packages/common/src/interfaces/PeerID.ts b/packages/common/src/interfaces/PeerID.ts
new file mode 100644
index 0000000000..92ebe0e6da
--- /dev/null
+++ b/packages/common/src/interfaces/PeerID.ts
@@ -0,0 +1,14 @@
+import { OpaqueType } from './OpaqueType'
+import { UserId } from './UserId'
+
+export type PeerID = OpaqueType<'PeerID'> & string
+
+export const SelfPeerID = 'self' as PeerID
+
+export type PeersUpdateType = {
+ peerID: PeerID
+ peerIndex: number
+ userID: UserId
+ userIndex: number
+ name: string
+}
diff --git a/packages/common/src/interfaces/User.ts b/packages/common/src/interfaces/User.ts
index cd07ee82e7..b11768ff0d 100755
--- a/packages/common/src/interfaces/User.ts
+++ b/packages/common/src/interfaces/User.ts
@@ -32,15 +32,21 @@ export interface UserInterface {
relationType?: RelationshipType
inverseRelationType?: RelationshipType
avatarUrl?: string
+ /** @deprecated */
instanceId?: string
+ /** @deprecated */
instance?: InstanceInterface
+ /** @deprecated */
channelInstanceId?: string
+ /** @deprecated */
channelInstance?: InstanceInterface
+ /** @deprecated */
partyId?: string
+ /** @deprecated */
+ party?: Party
locationBans?: LocationBan[]
user_setting?: UserSetting
inviteCode?: string
- party?: Party
scopes?: UserScope[]
apiKey: UserApiKey
static_resources?: StaticResourceInterface
diff --git a/packages/engine/src/audio/systems/PositionalAudioSystem.ts b/packages/engine/src/audio/systems/PositionalAudioSystem.ts
index f21def1b03..c72d8b792f 100755
--- a/packages/engine/src/audio/systems/PositionalAudioSystem.ts
+++ b/packages/engine/src/audio/systems/PositionalAudioSystem.ts
@@ -20,7 +20,7 @@ import {
} from '../../ecs/functions/ComponentFunctions'
import { startQueryReactor } from '../../ecs/functions/SystemFunctions'
import { LocalAvatarTagComponent } from '../../input/components/LocalAvatarTagComponent'
-import { NetworkObjectComponent, NetworkObjectComponentType } from '../../networking/components/NetworkObjectComponent'
+import { NetworkObjectComponent } from '../../networking/components/NetworkObjectComponent'
import { shouldUseImmersiveMedia } from '../../networking/MediaSettingsState'
import {
AudioNodeGroup,
@@ -104,7 +104,7 @@ export default async function PositionalAudioSystem(world: World) {
const setMediaStreamVolumeActionQueue = createActionQueue(AudioSettingAction.setMediaStreamVolume.matches)
/** Weak map entry is automatically GC'd when network object is removed */
- const avatarAudioStreams: WeakMap = new WeakMap()
+ const avatarAudioStreams: WeakMap, MediaStream> = new WeakMap()
const positionalAudioPannerReactor = startQueryReactor(
[PositionalAudioComponent, TransformComponent],
@@ -156,9 +156,13 @@ export default async function PositionalAudioSystem(world: World) {
const networkedAvatarAudioEntities = networkedAvatarAudioQuery()
for (const entity of networkedAvatarAudioEntities) {
const networkObject = getComponent(entity, NetworkObjectComponent)
- const peerId = networkObject.ownerId
+ const peerID = networkObject.ownerId
const consumer = network?.consumers.find(
- (c: any) => c.appData.peerId === peerId && c.appData.mediaTag === 'cam-audio'
+ (c) =>
+ c.appData.mediaTag === 'cam-audio' &&
+ Array.from(network.peers.values()).find(
+ (peer) => c.appData.peerID === peer.peerID && peer.userId === networkObject.ownerId
+ )
)
// avatar still exists but audio stream does not
@@ -180,7 +184,7 @@ export default async function PositionalAudioSystem(world: World) {
}
// get existing stream - need to wait for UserWindowMedia to populate
- const existingAudioObject = document.getElementById(`${peerId}_audio`)! as HTMLAudioElement
+ const existingAudioObject = document.getElementById(`${peerID}_audio`)! as HTMLAudioElement
if (!existingAudioObject) continue
// mute existing stream
diff --git a/packages/engine/src/avatar/AnimationSystem.ts b/packages/engine/src/avatar/AnimationSystem.ts
index 8c95057cc7..642148d943 100644
--- a/packages/engine/src/avatar/AnimationSystem.ts
+++ b/packages/engine/src/avatar/AnimationSystem.ts
@@ -30,7 +30,9 @@ export function animationActionReceptor(
action: ReturnType,
world = Engine.instance.currentWorld
) {
- if (Engine.instance.userId === action.$from) return // Only run on other clients
+ // Only run on other peers
+ if (!world.worldNetwork || !action.$peer || world.worldNetwork.peerID === action.$peer) return
+
const avatarEntity = world.getUserAvatarEntity(action.$from)
const networkObject = getComponent(avatarEntity, NetworkObjectComponent)
diff --git a/packages/engine/src/avatar/AvatarControllerSystem.test.ts b/packages/engine/src/avatar/AvatarControllerSystem.test.ts
index 3cee7e77f4..668616ccf1 100644
--- a/packages/engine/src/avatar/AvatarControllerSystem.test.ts
+++ b/packages/engine/src/avatar/AvatarControllerSystem.test.ts
@@ -17,7 +17,7 @@ import { RigidBodyComponent } from '../physics/components/RigidBodyComponent'
import { setTransformComponent, TransformComponent } from '../transform/components/TransformComponent'
import { rotateBodyTowardsVector } from './AvatarControllerSystem'
import { AvatarControllerComponent } from './components/AvatarControllerComponent'
-import { createAvatar } from './functions/createAvatar'
+import { spawnAvatarReceptor } from './functions/spawnAvatarReceptor'
describe('AvatarControllerSystem', async () => {
beforeEach(async () => {
@@ -32,7 +32,8 @@ describe('AvatarControllerSystem', async () => {
const spawnAvatarAction = WorldNetworkAction.spawnAvatar({})
WorldNetworkActionReceptor.receiveSpawnObject(spawnAvatarAction)
- const avatarEntity = createAvatar(spawnAvatarAction)
+ spawnAvatarReceptor(spawnAvatarAction)
+ const avatarEntity = world.getUserAvatarEntity(Engine.instance.userId)
const ridigbody = getComponent(avatarEntity, RigidBodyComponent)
const testRotation = new Quaternion().copy(ridigbody.body.rotation() as Quaternion)
diff --git a/packages/engine/src/avatar/AvatarControllerSystem.ts b/packages/engine/src/avatar/AvatarControllerSystem.ts
index 4af755b973..e2a5a3427d 100755
--- a/packages/engine/src/avatar/AvatarControllerSystem.ts
+++ b/packages/engine/src/avatar/AvatarControllerSystem.ts
@@ -21,8 +21,10 @@ import { createEntity } from '../ecs/functions/EntityFunctions'
import { LocalInputTagComponent } from '../input/components/LocalInputTagComponent'
import { BaseInput } from '../input/enums/BaseInput'
import { AvatarMovementScheme, GamepadAxis } from '../input/enums/InputEnums'
+import { NetworkObjectAuthorityTag, NetworkObjectComponent } from '../networking/components/NetworkObjectComponent'
import { WorldNetworkAction } from '../networking/functions/WorldNetworkAction'
import { RigidBodyComponent } from '../physics/components/RigidBodyComponent'
+import { NameComponent } from '../scene/components/NameComponent'
import { setComputedTransformComponent } from '../transform/components/ComputedTransformComponent'
import { setTransformComponent, TransformComponent } from '../transform/components/TransformComponent'
import { AvatarInputSchema } from './AvatarInputSchema'
@@ -62,6 +64,7 @@ export default async function AvatarControllerSystem(world: World) {
if (hasComponent(avatarEntity, AvatarComponent)) {
const avatarComponent = getComponent(avatarEntity, AvatarComponent)
targetEntity = createEntity()
+ setComponent(targetEntity, NameComponent, `Camera Target for: ${getComponent(avatarEntity, NameComponent)}`)
setTransformComponent(targetEntity)
setComputedTransformComponent(targetEntity, avatarEntity, () => {
const avatarTransform = getComponent(avatarEntity, TransformComponent)
@@ -94,6 +97,25 @@ export default async function AvatarControllerSystem(world: World) {
const controller = getComponent(controlledEntity, AvatarControllerComponent)
updateAvatarControllerOnGround(controlledEntity)
if (controller.movementEnabled) {
+ /** Support multiple peers controlling the same avatar by detecting movement and overriding network authority.
+ * @todo we may want to make this an networked action, rather than lazily removing the NetworkObjectAuthorityTag
+ * if detecting input on the other user
+ */
+ if (
+ !hasComponent(controlledEntity, NetworkObjectAuthorityTag) &&
+ world.worldNetwork &&
+ controller.localMovementDirection.lengthSq() > 0.1
+ ) {
+ const networkObject = getComponent(controlledEntity, NetworkObjectComponent)
+ dispatchAction(
+ WorldNetworkAction.transferAuthorityOfObject({
+ ownerId: networkObject.ownerId,
+ networkId: networkObject.networkId,
+ newAuthority: world.worldNetwork?.peerID
+ })
+ )
+ setComponent(controlledEntity, NetworkObjectAuthorityTag)
+ }
moveAvatarWithVelocity(controlledEntity)
}
diff --git a/packages/engine/src/avatar/AvatarSpawnSystem.ts b/packages/engine/src/avatar/AvatarSpawnSystem.ts
index 602abd35c1..263fec8328 100755
--- a/packages/engine/src/avatar/AvatarSpawnSystem.ts
+++ b/packages/engine/src/avatar/AvatarSpawnSystem.ts
@@ -10,7 +10,7 @@ import { getEntityTreeNodeByUUID } from '../ecs/functions/EntityTree'
import { WorldNetworkAction } from '../networking/functions/WorldNetworkAction'
import { SpawnPointComponent } from '../scene/components/SpawnPointComponent'
import { TransformComponent } from '../transform/components/TransformComponent'
-import { createAvatar } from './functions/createAvatar'
+import { spawnAvatarReceptor } from './functions/spawnAvatarReceptor'
const randomPositionCentered = (area: Vector3) => {
return new Vector3((Math.random() - 0.5) * area.x, (Math.random() - 0.5) * area.y, (Math.random() - 0.5) * area.z)
@@ -63,7 +63,7 @@ export default async function AvatarSpawnSystem(world: World) {
const avatarSpawnQueue = createActionQueue(WorldNetworkAction.spawnAvatar.matches)
const execute = () => {
- for (const action of avatarSpawnQueue()) createAvatar(action)
+ for (const action of avatarSpawnQueue()) spawnAvatarReceptor(action)
// Keep a list of spawn points so we can send our user to one
for (const entity of spawnPointQuery.enter(world)) {
diff --git a/packages/engine/src/avatar/animation/AvatarAnimationGraph.ts b/packages/engine/src/avatar/animation/AvatarAnimationGraph.ts
index 86ee5ac5cd..0ebc10fa59 100644
--- a/packages/engine/src/avatar/animation/AvatarAnimationGraph.ts
+++ b/packages/engine/src/avatar/animation/AvatarAnimationGraph.ts
@@ -5,7 +5,7 @@ import { dispatchAction, getState } from '@xrengine/hyperflux'
import { EngineState } from '../../ecs/classes/EngineState'
import { Entity } from '../../ecs/classes/Entity'
import { getComponent, hasComponent } from '../../ecs/functions/ComponentFunctions'
-import { NetworkObjectOwnedTag } from '../../networking/components/NetworkObjectOwnedTag'
+import { NetworkObjectAuthorityTag, NetworkObjectOwnedTag } from '../../networking/components/NetworkObjectComponent'
import { WorldNetworkAction } from '../../networking/functions/WorldNetworkAction'
import { AnimationManager } from '../AnimationManager'
import { AvatarSettings } from '../AvatarControllerSystem'
@@ -46,13 +46,14 @@ export function createAvatarAnimationGraph(
): AnimationGraph {
if (!mixer) return null!
- const isOwnedEntity = hasComponent(entity, NetworkObjectOwnedTag)
-
const graph: AnimationGraph = {
states: {},
transitionRules: {},
currentState: null!,
- stateChanged: isOwnedEntity ? dispatchStateChange : null!
+ stateChanged: (name) => {
+ hasComponent(entity, NetworkObjectAuthorityTag) &&
+ dispatchAction(WorldNetworkAction.avatarAnimation({ newStateName: name, params: {} }))
+ }
}
// Initialize all the states
@@ -277,7 +278,7 @@ export function createAvatarAnimationGraph(
const movementTransitionRule = vectorLengthTransitionRule(locomotion, 0.001)
- if (isOwnedEntity) {
+ if (hasComponent(entity, NetworkObjectOwnedTag)) {
graph.transitionRules[AvatarStates.LOCOMOTION] = [
// Jump
{
@@ -385,11 +386,6 @@ export function createAvatarAnimationGraph(
return graph
}
-function dispatchStateChange(name: string, graph: AnimationGraph): void {
- const params = {}
- dispatchAction(WorldNetworkAction.avatarAnimation({ newStateName: name, params }))
-}
-
export function changeAvatarAnimationState(entity: Entity, newStateName: string): void {
const avatarAnimationComponent = getComponent(entity, AvatarAnimationComponent)
changeState(avatarAnimationComponent.animationGraph, newStateName)
diff --git a/packages/engine/src/avatar/functions/avatarFunctions.ts b/packages/engine/src/avatar/functions/avatarFunctions.ts
index 41d1931186..3651d71187 100644
--- a/packages/engine/src/avatar/functions/avatarFunctions.ts
+++ b/packages/engine/src/avatar/functions/avatarFunctions.ts
@@ -77,6 +77,9 @@ export const loadAvatarForUser = async (
avatarURL: string,
loadingEffect = getState(EngineState).avatarLoadingEffect.value
) => {
+ if (hasComponent(entity, AvatarPendingComponent) && getComponent(entity, AvatarPendingComponent).url === avatarURL)
+ return
+
if (loadingEffect) {
if (hasComponent(entity, AvatarControllerComponent)) {
getComponent(entity, AvatarControllerComponent).movementEnabled = false
diff --git a/packages/engine/src/avatar/functions/moveAvatar.test.ts b/packages/engine/src/avatar/functions/moveAvatar.test.ts
index 19d1598fe1..7870231bf2 100644
--- a/packages/engine/src/avatar/functions/moveAvatar.test.ts
+++ b/packages/engine/src/avatar/functions/moveAvatar.test.ts
@@ -13,8 +13,8 @@ import { WorldNetworkActionReceptor } from '../../networking/functions/WorldNetw
import { Physics } from '../../physics/classes/Physics'
import { RigidBodyComponent, RigidBodyFixedTagComponent } from '../../physics/components/RigidBodyComponent'
import { AvatarControllerComponent } from '../components/AvatarControllerComponent'
-import { createAvatar } from './createAvatar'
import { moveAvatarWithVelocity } from './moveAvatar'
+import { spawnAvatarReceptor } from './spawnAvatarReceptor'
// @todo this test is exhibiting odd behaviour
describe('moveAvatar function tests', () => {
@@ -38,7 +38,8 @@ describe('moveAvatar function tests', () => {
WorldNetworkActionReceptor.receiveSpawnObject(spawnAvatar, world)
- const entity = createAvatar(spawnAvatar)
+ spawnAvatarReceptor(spawnAvatar)
+ const entity = world.getUserAvatarEntity(Engine.instance.userId)
const camera = new PerspectiveCamera(60, 800 / 600, 0.1, 10000)
@@ -70,7 +71,8 @@ describe('moveAvatar function tests', () => {
WorldNetworkActionReceptor.receiveSpawnObject(spawnAvatar, world)
- const entity = createAvatar(spawnAvatar)
+ spawnAvatarReceptor(spawnAvatar)
+ const entity = world.getUserAvatarEntity(Engine.instance.userId)
const camera = new PerspectiveCamera(60, 800 / 600, 0.1, 10000)
@@ -104,7 +106,8 @@ describe('moveAvatar function tests', () => {
WorldNetworkActionReceptor.receiveSpawnObject(spawnAvatar, world)
- const entity = createAvatar(spawnAvatar)
+ spawnAvatarReceptor(spawnAvatar)
+ const entity = world.getUserAvatarEntity(Engine.instance.userId)
const camera = new PerspectiveCamera(60, 800 / 600, 0.1, 10000)
@@ -135,7 +138,8 @@ describe('moveAvatar function tests', () => {
WorldNetworkActionReceptor.receiveSpawnObject(spawnAvatar, world)
- const entity = createAvatar(spawnAvatar)
+ spawnAvatarReceptor(spawnAvatar)
+ const entity = world.getUserAvatarEntity(Engine.instance.userId)
const camera = new PerspectiveCamera(60, 800 / 600, 0.1, 10000)
diff --git a/packages/engine/src/avatar/functions/moveAvatar.ts b/packages/engine/src/avatar/functions/moveAvatar.ts
index 9a73ef5ab5..c3049beac8 100755
--- a/packages/engine/src/avatar/functions/moveAvatar.ts
+++ b/packages/engine/src/avatar/functions/moveAvatar.ts
@@ -18,6 +18,7 @@ import {
setComponent
} from '../../ecs/functions/ComponentFunctions'
import { AvatarMovementScheme } from '../../input/enums/InputEnums'
+import { NetworkObjectAuthorityTag } from '../../networking/components/NetworkObjectComponent'
import { Physics } from '../../physics/classes/Physics'
import { RigidBodyComponent } from '../../physics/components/RigidBodyComponent'
import { CollisionGroups } from '../../physics/enums/CollisionGroups'
@@ -33,7 +34,7 @@ import { AvatarControllerComponent } from '../components/AvatarControllerCompone
import { AvatarHeadDecapComponent } from '../components/AvatarIKComponents'
import { AvatarTeleportComponent } from '../components/AvatarTeleportComponent'
import { AvatarInputSettingsState } from '../state/AvatarInputSettingsState'
-import { avatarRadius } from './createAvatar'
+import { avatarRadius } from './spawnAvatarReceptor'
const _vec = new Vector3()
const _vec2 = new Vector3()
@@ -142,7 +143,7 @@ export const avatarApplyRotation = (entity: Entity) => {
/**
* Avatar movement via velocity spring and collider velocity
*/
-export const avatarApplyVelocity = (entity, forwardOrientation) => {
+export const avatarApplyVelocity = (entity: Entity, forwardOrientation: Quaternion) => {
const controller = getComponent(entity, AvatarControllerComponent) as ComponentType
const rigidBody = getComponent(entity, RigidBodyComponent)
const timeStep = getState(EngineState).fixedDeltaSeconds.value
@@ -177,7 +178,9 @@ export const avatarApplyVelocity = (entity, forwardOrientation) => {
}
}
- rigidBody.body.setLinvel(currentVelocity, true)
+ if (hasComponent(entity, NetworkObjectAuthorityTag)) {
+ rigidBody.body.setLinvel(currentVelocity, true)
+ }
}
export const avatarStepOverObstacles = (entity: Entity, forwardOrientation: Quaternion) => {
diff --git a/packages/engine/src/avatar/functions/resizeAvatar.ts b/packages/engine/src/avatar/functions/resizeAvatar.ts
index 80e05ea0e6..02af5fd19f 100644
--- a/packages/engine/src/avatar/functions/resizeAvatar.ts
+++ b/packages/engine/src/avatar/functions/resizeAvatar.ts
@@ -6,7 +6,7 @@ import { ComponentType, getComponent, hasComponent } from '../../ecs/functions/C
import { Physics } from '../../physics/classes/Physics'
import { AvatarComponent } from '../components/AvatarComponent'
import { AvatarControllerComponent } from '../components/AvatarControllerComponent'
-import { createAvatarCollider } from './createAvatar'
+import { createAvatarCollider } from './spawnAvatarReceptor'
export const resizeAvatar = (entity: Entity, height: number, center: Vector3) => {
const avatar = getComponent(entity, AvatarComponent) as ComponentType
diff --git a/packages/engine/src/avatar/functions/createAvatar.test.ts b/packages/engine/src/avatar/functions/spawnAvatarReceptor.test.ts
similarity index 95%
rename from packages/engine/src/avatar/functions/createAvatar.test.ts
rename to packages/engine/src/avatar/functions/spawnAvatarReceptor.test.ts
index 944957982c..e871d31396 100644
--- a/packages/engine/src/avatar/functions/createAvatar.test.ts
+++ b/packages/engine/src/avatar/functions/spawnAvatarReceptor.test.ts
@@ -18,9 +18,9 @@ import { AvatarAnimationComponent } from '../components/AvatarAnimationComponent
import { AvatarComponent } from '../components/AvatarComponent'
import { AvatarControllerComponent } from '../components/AvatarControllerComponent'
import { SpawnPoseComponent } from '../components/SpawnPoseComponent'
-import { createAvatar } from './createAvatar'
+import { spawnAvatarReceptor } from './spawnAvatarReceptor'
-describe('createAvatar', () => {
+describe('spawnAvatarReceptor', () => {
beforeEach(async () => {
createEngine()
await Physics.load()
@@ -38,7 +38,7 @@ describe('createAvatar', () => {
rotation: new Quaternion()
})
WorldNetworkActionReceptor.receiveSpawnObject(action)
- createAvatar(action)
+ spawnAvatarReceptor(action)
const entity = world.getUserAvatarEntity(Engine.instance.userId)
diff --git a/packages/engine/src/avatar/functions/createAvatar.ts b/packages/engine/src/avatar/functions/spawnAvatarReceptor.ts
similarity index 82%
rename from packages/engine/src/avatar/functions/createAvatar.ts
rename to packages/engine/src/avatar/functions/spawnAvatarReceptor.ts
index 690958d48c..232a441a39 100644
--- a/packages/engine/src/avatar/functions/createAvatar.ts
+++ b/packages/engine/src/avatar/functions/spawnAvatarReceptor.ts
@@ -1,12 +1,25 @@
import { Collider, ColliderDesc, RigidBody, RigidBodyDesc } from '@dimforge/rapier3d-compat'
import { AnimationClip, AnimationMixer, Group, Quaternion, Vector3 } from 'three'
+import { dispatchAction } from '@xrengine/hyperflux'
+
+import { FollowCameraComponent } from '../../camera/components/FollowCameraComponent'
import { Engine } from '../../ecs/classes/Engine'
-import { Entity } from '../../ecs/classes/Entity'
-import { addComponent, getComponent, hasComponent, setComponent } from '../../ecs/functions/ComponentFunctions'
+import { EngineActions } from '../../ecs/classes/EngineState'
+import { Entity, UndefinedEntity } from '../../ecs/classes/Entity'
+import {
+ addComponent,
+ getComponent,
+ hasComponent,
+ removeComponent,
+ setComponent
+} from '../../ecs/functions/ComponentFunctions'
+import { removeEntity } from '../../ecs/functions/EntityFunctions'
import { InputComponent } from '../../input/components/InputComponent'
import { LocalAvatarTagComponent } from '../../input/components/LocalAvatarTagComponent'
import { LocalInputTagComponent } from '../../input/components/LocalInputTagComponent'
+import { NetworkObjectAuthorityTag, NetworkObjectOwnedTag } from '../../networking/components/NetworkObjectComponent'
+import { NetworkPeerFunctions } from '../../networking/functions/NetworkPeerFunctions'
import { WorldNetworkAction } from '../../networking/functions/WorldNetworkAction'
import { Physics } from '../../physics/classes/Physics'
import { VectorSpringSimulator } from '../../physics/classes/springs/VectorSpringSimulator'
@@ -34,11 +47,27 @@ export const avatarRadius = 0.25
export const defaultAvatarHeight = 1.8
export const defaultAvatarHalfHeight = defaultAvatarHeight / 2
-export const createAvatar = (spawnAction: typeof WorldNetworkAction.spawnAvatar.matches._TYPE): Entity => {
+export const spawnAvatarReceptor = (spawnAction: typeof WorldNetworkAction.spawnAvatar.matches._TYPE) => {
const world = Engine.instance.currentWorld
const userId = spawnAction.$from
- const entity = world.getNetworkObject(spawnAction.$from, spawnAction.networkId)!
+ const existingAvatarEntity = world.getUserAvatarEntity(spawnAction.$from)
+
+ // already spawned into the world on another device or tab
+ if (existingAvatarEntity) {
+ const didSpawnEarlierThanThisClient = NetworkPeerFunctions.getCachedActionsForUser(userId).find(
+ (action) =>
+ WorldNetworkAction.spawnAvatar.matches.test(action) &&
+ action !== spawnAction &&
+ action.$time > spawnAction.$time
+ )
+ if (didSpawnEarlierThanThisClient) {
+ hasComponent(existingAvatarEntity, NetworkObjectAuthorityTag) &&
+ removeComponent(existingAvatarEntity, NetworkObjectAuthorityTag)
+ }
+ return
+ }
+ const entity = world.getNetworkObject(spawnAction.$from, spawnAction.networkId)!
const transform = getComponent(entity, TransformComponent)
// The visuals group is centered for easy actor tilting
@@ -103,8 +132,6 @@ export const createAvatar = (spawnAction: typeof WorldNetworkAction.spawnAvatar.
}
addComponent(entity, ShadowComponent, { receive: true, cast: true })
-
- return entity
}
export const createAvatarCollider = (entity: Entity): Collider => {
diff --git a/packages/engine/src/camera/systems/CameraSystem.ts b/packages/engine/src/camera/systems/CameraSystem.ts
index 0d505a5589..2c4ff1ada9 100755
--- a/packages/engine/src/camera/systems/CameraSystem.ts
+++ b/packages/engine/src/camera/systems/CameraSystem.ts
@@ -22,7 +22,7 @@ import {
removeQuery,
setComponent
} from '../../ecs/functions/ComponentFunctions'
-import { NetworkObjectOwnedTag } from '../../networking/components/NetworkObjectOwnedTag'
+import { NetworkObjectOwnedTag } from '../../networking/components/NetworkObjectComponent'
import { WorldNetworkAction } from '../../networking/functions/WorldNetworkAction'
import { RAYCAST_PROPERTIES_DEFAULT_VALUES } from '../../scene/components/CameraPropertiesComponent'
import { ObjectLayers } from '../../scene/constants/ObjectLayers'
diff --git a/packages/engine/src/common/functions/MatchesUtils.ts b/packages/engine/src/common/functions/MatchesUtils.ts
index e2fb48ad4e..64ad1d1d9a 100644
--- a/packages/engine/src/common/functions/MatchesUtils.ts
+++ b/packages/engine/src/common/functions/MatchesUtils.ts
@@ -2,6 +2,7 @@ import { Quaternion, Vector3 } from 'three'
import { matches, Validator } from 'ts-matches'
import { NetworkId } from '@xrengine/common/src/interfaces/NetworkId'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { Entity } from '../../ecs/classes/Entity'
@@ -32,6 +33,7 @@ const matchesQuatShape = matches.some(
const matchesVector3 = matches.guard((v): v is Vector3 => matchesVec3Shape.test(v))
const matchesQuaternion = matches.guard((v): v is Quaternion => matchesQuatShape.test(v))
const matchesUserId = matches.string as Validator
+const matchesPeerID = matches.string as Validator
const matchesNetworkId = matches.number as Validator
const matchesEntity = matches.number as Validator
@@ -47,6 +49,7 @@ const matchesWithDefault = (matches: Validator, defaultValue: ()
export {
matchesUserId,
+ matchesPeerID,
matchesNetworkId,
matchesEntity,
matchesVector3,
diff --git a/packages/engine/src/ecs/classes/EngineState.ts b/packages/engine/src/ecs/classes/EngineState.ts
index 498744ec5a..b279e58041 100644
--- a/packages/engine/src/ecs/classes/EngineState.ts
+++ b/packages/engine/src/ecs/classes/EngineState.ts
@@ -133,6 +133,10 @@ export class EngineActions {
user: matches.string.optional()
})
+ static avatarAlreadyInWorld = defineAction({
+ type: 'xre.world.AVATAR_ALREADY_IN_WORLD'
+ })
+
static interactedWithObject = defineAction({
type: 'xre.engine.Engine.INTERACTED_WITH_OBJECT' as const,
targetEntity: matchesEntity.optional(),
diff --git a/packages/engine/src/interaction/functions/equippableFunctions.test.ts b/packages/engine/src/interaction/functions/equippableFunctions.test.ts
index d011ed7de1..f7aebd8a18 100644
--- a/packages/engine/src/interaction/functions/equippableFunctions.test.ts
+++ b/packages/engine/src/interaction/functions/equippableFunctions.test.ts
@@ -1,6 +1,7 @@
import assert from 'assert'
import { NetworkId } from '@xrengine/common/src/interfaces/NetworkId'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { createMockNetwork } from '../../../tests/util/createMockNetwork'
@@ -28,7 +29,7 @@ describe.skip('equippableFunctions', () => {
addComponent(entity2, NetworkObjectComponent, {
ownerId: 'world' as UserId,
- authorityUserId: 'world' as UserId,
+ authorityPeerID: 'world' as PeerID,
networkId: 0 as NetworkId
})
@@ -42,7 +43,7 @@ describe.skip('equippableFunctions', () => {
const entity2: Entity = createEntity()
addComponent(entity2, NetworkObjectComponent, {
ownerId: 'world' as UserId,
- authorityUserId: 'world' as UserId,
+ authorityPeerID: 'world' as PeerID,
networkId: 0 as NetworkId
})
diff --git a/packages/engine/src/interaction/functions/equippableFunctions.ts b/packages/engine/src/interaction/functions/equippableFunctions.ts
index c5f6ce1b2b..21d0ec3bfb 100755
--- a/packages/engine/src/interaction/functions/equippableFunctions.ts
+++ b/packages/engine/src/interaction/functions/equippableFunctions.ts
@@ -5,7 +5,7 @@ import { Engine } from '../../ecs/classes/Engine'
import { Entity } from '../../ecs/classes/Entity'
import { getComponent, hasComponent, removeComponent } from '../../ecs/functions/ComponentFunctions'
import { NetworkObjectComponent } from '../../networking/components/NetworkObjectComponent'
-import { NetworkObjectOwnedTag } from '../../networking/components/NetworkObjectOwnedTag'
+import { NetworkObjectOwnedTag } from '../../networking/components/NetworkObjectComponent'
import { WorldNetworkAction } from '../../networking/functions/WorldNetworkAction'
import { EquippedComponent } from '../components/EquippedComponent'
import { EquipperComponent } from '../components/EquipperComponent'
@@ -18,7 +18,7 @@ export const equipEntity = (
): void => {
if (!hasComponent(equipperEntity, EquipperComponent) && !hasComponent(equippedEntity, EquippedComponent)) {
const networkComponent = getComponent(equippedEntity, NetworkObjectComponent)
- if (networkComponent.authorityUserId === Engine.instance.userId) {
+ if (networkComponent.authorityPeerID === Engine.instance.currentWorld?.worldNetwork.peerID) {
dispatchAction(
WorldNetworkAction.setEquippedObject({
object: {
@@ -34,8 +34,8 @@ export const equipEntity = (
WorldNetworkAction.requestAuthorityOverObject({
networkId: networkComponent.networkId,
ownerId: networkComponent.ownerId,
- newAuthority: Engine.instance.userId,
- $to: networkComponent.authorityUserId
+ newAuthority: Engine.instance.currentWorld?.worldNetwork.peerID,
+ $to: Engine.instance.currentWorld?.worldNetwork.peers.get(networkComponent.authorityPeerID)?.userId
})
)
}
@@ -48,7 +48,7 @@ export const unequipEntity = (equipperEntity: Entity): void => {
removeComponent(equipperEntity, EquipperComponent)
const networkComponent = getComponent(equipperComponent.equippedEntity, NetworkObjectComponent)
const networkOwnerComponent = getComponent(equipperComponent.equippedEntity, NetworkObjectOwnedTag)
- if (networkComponent.authorityUserId === Engine.instance.userId) {
+ if (networkComponent.authorityPeerID === Engine.instance.currentWorld?.worldNetwork.peerID) {
dispatchAction(
WorldNetworkAction.setEquippedObject({
object: {
@@ -64,7 +64,7 @@ export const unequipEntity = (equipperEntity: Entity): void => {
WorldNetworkAction.transferAuthorityOfObject({
networkId: networkComponent.networkId,
ownerId: networkComponent.ownerId,
- newAuthority: networkComponent.authorityUserId
+ newAuthority: networkComponent.authorityPeerID
})
)
}
diff --git a/packages/engine/src/interaction/systems/EquippableSystem.test.ts b/packages/engine/src/interaction/systems/EquippableSystem.test.ts
index 1536a13fd7..2235959d8b 100644
--- a/packages/engine/src/interaction/systems/EquippableSystem.test.ts
+++ b/packages/engine/src/interaction/systems/EquippableSystem.test.ts
@@ -2,9 +2,10 @@ import assert, { strictEqual } from 'assert'
import { Quaternion, Vector3 } from 'three'
import { NetworkId } from '@xrengine/common/src/interfaces/NetworkId'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { getHandTarget } from '../../avatar/components/AvatarIKComponents'
-import { createAvatar } from '../../avatar/functions/createAvatar'
+import { spawnAvatarReceptor } from '../../avatar/functions/spawnAvatarReceptor'
import { Engine } from '../../ecs/classes/Engine'
import {
addComponent,
@@ -42,12 +43,12 @@ describe.skip('EquippableSystem Integration Tests', () => {
addComponent(player, NetworkObjectComponent, {
ownerId: Engine.instance.userId,
- authorityUserId: Engine.instance.userId,
+ authorityPeerID: 'peer id' as PeerID,
networkId: 0 as NetworkId
})
const networkObject = getComponent(player, NetworkObjectComponent)
- createAvatar(
+ spawnAvatarReceptor(
WorldNetworkAction.spawnAvatar({
$from: Engine.instance.userId,
networkId: networkObject.networkId,
diff --git a/packages/engine/src/interaction/systems/EquippableSystem.ts b/packages/engine/src/interaction/systems/EquippableSystem.ts
index f43500a781..59c30d7c28 100644
--- a/packages/engine/src/interaction/systems/EquippableSystem.ts
+++ b/packages/engine/src/interaction/systems/EquippableSystem.ts
@@ -67,7 +67,7 @@ export function transferAuthorityOfObjectReceptor(
action: ReturnType,
world = Engine.instance.currentWorld
) {
- if (action.newAuthority !== Engine.instance.userId) return
+ if (action.newAuthority !== world.worldNetwork?.peerID) return
const equippableEntity = world.getNetworkObject(action.ownerId, action.networkId)!
if (hasComponent(equippableEntity, EquippableComponent)) {
dispatchAction(
diff --git a/packages/engine/src/networking/classes/Network.ts b/packages/engine/src/networking/classes/Network.ts
index 2a7f997d26..c0a9b3827b 100755
--- a/packages/engine/src/networking/classes/Network.ts
+++ b/packages/engine/src/networking/classes/Network.ts
@@ -1,3 +1,4 @@
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { Topic } from '@xrengine/hyperflux/functions/ActionFunctions'
@@ -57,22 +58,31 @@ export class Network {
dataConsumers = new Map()
/** Buffer holding all incoming Messages. */
- incomingMessageQueueUnreliableIDs: RingBuffer = new RingBuffer(100)
+ incomingMessageQueueUnreliableIDs: RingBuffer = new RingBuffer(100)
/** Buffer holding all incoming Messages. */
incomingMessageQueueUnreliable: RingBuffer = new RingBuffer(100)
+ /** Buffer holding Mediasoup operations */
+ mediasoupOperationQueue: RingBuffer = new RingBuffer(1000)
+
/** Connected peers */
- peers = new Map() as Map
+ peers = new Map() as Map
/** Publish to connected peers that peer information has changed */
updatePeers() {}
/** Map of numerical user index to user client IDs */
- userIndexToUserId = new Map()
+ userIndexToUserID = new Map()
/** Map of user client IDs to numerical user index */
- userIdToUserIndex = new Map()
+ userIDToUserIndex = new Map()
+
+ /** Map of numerical peer index to peer IDs */
+ peerIndexToPeerID = new Map()
+
+ /** Map of peer IDs to numerical peer index */
+ peerIDToPeerIndex = new Map()
/**
* The index to increment when a new user joins
@@ -80,12 +90,23 @@ export class Network {
*/
userIndexCount = 0
+ /**
+ * The index to increment when a new peer connects
+ * NOTE: Must only be updated by the host
+ */
+ peerIndexCount = 0
+
/**
* The UserId of the host
* - will either be a user's UserId, or an instance server's InstanceId
*/
hostId: UserId
+ /**
+ * The PeerID of the current user's instance
+ */
+ peerID: PeerID
+
/**
* The network is ready for sending messages and data
*/
diff --git a/packages/engine/src/networking/components/NetworkObjectAuthorityTag.ts b/packages/engine/src/networking/components/NetworkObjectAuthorityTag.ts
deleted file mode 100644
index 283ea05e0f..0000000000
--- a/packages/engine/src/networking/components/NetworkObjectAuthorityTag.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { createMappedComponent } from '../../ecs/functions/ComponentFunctions'
-
-export const NetworkObjectAuthorityTag = createMappedComponent('NetworkObjectAuthorityTag')
diff --git a/packages/engine/src/networking/components/NetworkObjectComponent.ts b/packages/engine/src/networking/components/NetworkObjectComponent.ts
index b732d8cd3a..0c740b9c65 100755
--- a/packages/engine/src/networking/components/NetworkObjectComponent.ts
+++ b/packages/engine/src/networking/components/NetworkObjectComponent.ts
@@ -1,19 +1,11 @@
import { Types } from 'bitecs'
import { NetworkId } from '@xrengine/common/src/interfaces/NetworkId'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { defineComponent } from '../../ecs/functions/ComponentFunctions'
-export type NetworkObjectComponentType = {
- /** The user who is authority over this object. */
- ownerId: UserId
- /** The user who is authority over this object. */
- authorityUserId: UserId
- /** The network id for this object (this id is only unique per owner) */
- networkId: NetworkId
-}
-
export const NetworkObjectComponent = defineComponent({
name: 'NetworkObjectComponent',
@@ -25,8 +17,8 @@ export const NetworkObjectComponent = defineComponent({
return {
/** The user who is authority over this object. */
ownerId: '' as UserId,
- /** The user who is authority over this object. */
- authorityUserId: '' as UserId,
+ /** The peer who is authority over this object. */
+ authorityPeerID: '' as PeerID,
/** The network id for this object (this id is only unique per owner) */
networkId: 0 as NetworkId
}
@@ -35,17 +27,27 @@ export const NetworkObjectComponent = defineComponent({
toJSON: (entity, component) => {
return {
ownerId: component.ownerId.value,
- authorityUserId: component.authorityUserId.value,
+ authorityPeerID: component.authorityPeerID.value,
networkId: component.networkId.value
}
},
onSet: (entity, component, json) => {
if (typeof json?.ownerId === 'string') component.ownerId.set(json.ownerId)
- if (typeof json?.authorityUserId === 'string') component.authorityUserId.set(json.authorityUserId)
+ if (typeof json?.authorityPeerID === 'string') component.authorityPeerID.set(json.authorityPeerID)
if (typeof json?.networkId === 'number') {
component.networkId.set(json.networkId)
NetworkObjectComponent.networkId[entity] = json.networkId
}
}
})
+
+/**
+ * Authority is peer-specific.
+ * Ownership is user-specific.
+ * An object is owned by one user, having multiple representations across peers as entities, of which only one is the authority.
+ * Authority can be transferred to other peer, including those operated by different users.
+ */
+export const NetworkObjectAuthorityTag = defineComponent({ name: 'NetworkObjectComponent' })
+
+export const NetworkObjectOwnedTag = defineComponent({ name: 'NetworkObjectComponent' })
diff --git a/packages/engine/src/networking/components/NetworkObjectOwnedTag.ts b/packages/engine/src/networking/components/NetworkObjectOwnedTag.ts
deleted file mode 100644
index 9c8ef986ee..0000000000
--- a/packages/engine/src/networking/components/NetworkObjectOwnedTag.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { createMappedComponent } from '../../ecs/functions/ComponentFunctions'
-
-export const NetworkObjectOwnedTag = createMappedComponent('NetworkObjectOwnedTag')
diff --git a/packages/engine/src/networking/enums/MessageTypes.ts b/packages/engine/src/networking/enums/MessageTypes.ts
index 3f0c9c4545..56073ddd27 100755
--- a/packages/engine/src/networking/enums/MessageTypes.ts
+++ b/packages/engine/src/networking/enums/MessageTypes.ts
@@ -12,11 +12,11 @@ export enum MessageTypes {
WebRTCTransportClose = 9,
WebRTCSendTrack = 10,
WebRTCReceiveTrack = 11,
- // WebRTCPauseConsumer = 12,
- // WebRTCResumeConsumer = 13,
+ WebRTCPauseConsumer = 12,
+ WebRTCResumeConsumer = 13,
WebRTCCloseConsumer = 14,
- // WebRTCPauseProducer = 15,
- // WebRTCResumeProducer = 16,
+ WebRTCPauseProducer = 15,
+ WebRTCResumeProducer = 16,
WebRTCCloseProducer = 17,
WebRTCMuteOtherProducer = 18,
WebRTCUnmuteOtherProducer = 19,
diff --git a/packages/engine/src/networking/functions/MediaNetworkAction.ts b/packages/engine/src/networking/functions/MediaNetworkAction.ts
deleted file mode 100644
index c4021acf6f..0000000000
--- a/packages/engine/src/networking/functions/MediaNetworkAction.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { defineAction } from '@xrengine/hyperflux'
-
-import { matches } from '../../common/functions/MatchesUtils'
-import { NetworkTopics } from '../classes/Network'
-
-export class MediaNetworkAction {
- static pauseConsumer = defineAction({
- type: 'xre.networking.media.PAUSE_CONSUMER',
- consumerId: matches.string,
- pause: matches.boolean,
- $cache: { removePrevious: true },
- $topic: NetworkTopics.media
- })
-
- static pauseProducer = defineAction({
- type: 'xre.networking.media.PAUSE_PRODUCER',
- producerId: matches.string,
- globalMute: matches.boolean.optional(),
- pause: matches.boolean,
- $cache: { removePrevious: true },
- $topic: NetworkTopics.media
- })
-}
diff --git a/packages/engine/src/networking/functions/NetworkPeerFunctions.test.ts b/packages/engine/src/networking/functions/NetworkPeerFunctions.test.ts
index 0ce3b06a1f..0a3955233f 100644
--- a/packages/engine/src/networking/functions/NetworkPeerFunctions.test.ts
+++ b/packages/engine/src/networking/functions/NetworkPeerFunctions.test.ts
@@ -1,6 +1,7 @@
import assert from 'assert'
import { NetworkId } from '@xrengine/common/src/interfaces/NetworkId'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { applyIncomingActions, clearOutgoingActions, getState } from '@xrengine/hyperflux'
@@ -23,43 +24,56 @@ describe('NetworkPeerFunctions', () => {
it('should add peer', () => {
const world = Engine.instance.currentWorld
const userId = 'user id' as UserId
- Engine.instance.userId = 'another user id' as UserId
+ const peerID = 'peer id' as PeerID
+ Engine.instance.userId = 'another user id' as UserId & PeerID
const userName = 'user name'
const userIndex = 1
+ const peerIndex = 2
const network = world.worldNetwork
- NetworkPeerFunctions.createPeer(network, userId, userIndex, userName, world)
+ NetworkPeerFunctions.createPeer(network, peerID, peerIndex, userId, userIndex, userName, world)
const worldState = getState(WorldState)
- assert(network.peers.get(userId))
- assert.equal(network.peers.get(userId)?.userId, userId)
- assert.equal(network.peers.get(userId)?.index, userIndex)
+ assert(network.peers.get(peerID))
+ assert.equal(network.peers.get(peerID)?.userId, userId)
+ assert.equal(network.peers.get(peerID)?.userIndex, userIndex)
+ assert.equal(network.peers.get(peerID)?.peerID, peerID)
+ assert.equal(network.peers.get(peerID)?.peerIndex, peerIndex)
assert.equal(worldState.userNames[userId]?.value, userName)
- assert.equal(network.userIndexToUserId.get(userIndex), userId)
- assert.equal(network.userIdToUserIndex.get(userId), userIndex)
+ assert.equal(network.userIndexToUserID.get(userIndex), userId)
+ assert.equal(network.userIDToUserIndex.get(userId), userIndex)
+ assert.equal(network.peerIndexToPeerID.get(peerIndex), peerID)
+ assert.equal(network.peerIDToPeerIndex.get(peerID), peerIndex)
})
it('should udpate peer if it already exists', () => {
const world = Engine.instance.currentWorld
const userId = 'user id' as UserId
+ const peerID = 'peer id' as PeerID
Engine.instance.userId = 'another user id' as UserId
const userName = 'user name'
const userName2 = 'user name 2'
const userIndex = 1
const userIndex2 = 2
+ const peerIndex = 3
+ const peerIndex2 = 4
const network = world.worldNetwork
const worldState = getState(WorldState)
- NetworkPeerFunctions.createPeer(network, userId, userIndex, userName, world)
- assert.equal(network.peers.get(userId)?.userId, userId)
- assert.equal(network.peers.get(userId)?.index, userIndex)
+ NetworkPeerFunctions.createPeer(network, peerID, peerIndex, userId, userIndex, userName, world)
+ assert.equal(network.peers.get(peerID)!.userId, userId)
+ assert.equal(network.peers.get(peerID)!.userIndex, userIndex)
+ assert.equal(network.peers.get(peerID)!.peerID, peerID)
+ assert.equal(network.peers.get(peerID)!.peerIndex, peerIndex)
assert.equal(worldState.userNames[userId].value, userName)
- NetworkPeerFunctions.createPeer(network, userId, userIndex2, userName2, world)
- assert.equal(network.peers.get(userId)?.userId, userId)
- assert.equal(network.peers.get(userId)?.index, userIndex2)
+ NetworkPeerFunctions.createPeer(network, peerID, peerIndex2, userId, userIndex2, userName2, world)
+ assert.equal(network.peers.get(peerID)!.userId, userId)
+ assert.equal(network.peers.get(peerID)!.userIndex, userIndex2)
+ assert.equal(network.peers.get(peerID)!.peerID, peerID)
+ assert.equal(network.peers.get(peerID)!.peerIndex, peerIndex2)
assert.equal(worldState.userNames[userId].value, userName2)
})
})
@@ -68,51 +82,52 @@ describe('NetworkPeerFunctions', () => {
it('should remove peer', () => {
const world = Engine.instance.currentWorld
const userId = 'user id' as UserId
+ const peerID = 'peer id' as PeerID
Engine.instance.userId = 'another user id' as UserId
const userName = 'user name'
const userIndex = 1
+ const peerIndex = 2
const network = world.worldNetwork
- NetworkPeerFunctions.createPeer(network, userId, userIndex, userName, world)
- NetworkPeerFunctions.destroyPeer(network, userId, world)
+ NetworkPeerFunctions.createPeer(network, peerID, peerIndex, userId, userIndex, userName, world)
+ NetworkPeerFunctions.destroyPeer(network, peerID, world)
- assert(!network.peers.get(userId))
+ assert(!network.peers.get(peerID))
- // indexes shouldn't be removed (no reason for these to ever change in a network)
- assert.equal(network.userIndexToUserId.get(userIndex), userId)
- assert.equal(userIndex, network.userIdToUserIndex.get(userId))
+ assert.equal(network.userIndexToUserID.get(userIndex), undefined)
+ assert.equal(network.userIDToUserIndex.get(userId), undefined)
+ assert.equal(network.peerIndexToPeerID.get(peerIndex), undefined)
+ assert.equal(network.peerIDToPeerIndex.get(peerID), undefined)
})
it('should remove peer and owned network objects', () => {
const world = Engine.instance.currentWorld
const userId = 'user id' as UserId
+ const peerID = 'peer id' as PeerID
Engine.instance.userId = 'another user id' as UserId
const userName = 'user name'
const userIndex = 1
+ const peerIndex = 5
const network = world.worldNetwork
- NetworkPeerFunctions.createPeer(network, userId, userIndex, userName, world)
+ NetworkPeerFunctions.createPeer(network, peerID, peerIndex, userId, userIndex, userName, world)
const networkId = 2 as NetworkId
const entity = createEntity()
addComponent(entity, NetworkObjectComponent, {
ownerId: userId,
- authorityUserId: userId,
+ authorityPeerID: peerID,
networkId
})
// process remove actions and execute entity removal
Engine.instance.store.defaultDispatchDelay = 0
- NetworkPeerFunctions.destroyPeer(network, userId, world)
+ NetworkPeerFunctions.destroyPeer(network, peerID, world)
clearOutgoingActions(network.topic)
applyIncomingActions()
world.execute(0)
- assert(!network.peers.get(userId))
- assert.equal(network.userIndexToUserId.get(1), userId)
- assert.equal(network.userIdToUserIndex.get(userId), 1)
-
assert(!world.getNetworkObject(userId, networkId))
})
})
diff --git a/packages/engine/src/networking/functions/NetworkPeerFunctions.ts b/packages/engine/src/networking/functions/NetworkPeerFunctions.ts
index b6809abb38..6d2dcaa15a 100644
--- a/packages/engine/src/networking/functions/NetworkPeerFunctions.ts
+++ b/packages/engine/src/networking/functions/NetworkPeerFunctions.ts
@@ -1,6 +1,9 @@
+import { Validator } from 'ts-matches'
+
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { getState } from '@xrengine/hyperflux'
-import { Action } from '@xrengine/hyperflux/functions/ActionFunctions'
+import { Action, ActionShape, ResolvedActionType } from '@xrengine/hyperflux/functions/ActionFunctions'
import { none } from '@xrengine/hyperflux/functions/StateFunctions'
import { Engine } from '../../ecs/classes/Engine'
@@ -11,37 +14,51 @@ import { Network } from '../classes/Network'
import { NetworkObjectComponent } from '../components/NetworkObjectComponent'
import { WorldState } from '../interfaces/WorldState'
import { WorldNetworkAction } from './WorldNetworkAction'
-import { WorldNetworkActionReceptor } from './WorldNetworkActionReceptor'
function createPeer(
network: Network,
- userId: UserId,
- index: number,
+ peerID: PeerID,
+ peerIndex: number,
+ userID: UserId,
+ userIndex: number,
name: string,
world = Engine.instance.currentWorld
) {
- console.log('[Network]: Create Peer', network.topic, userId, index, name)
-
- network.userIdToUserIndex.set(userId, index)
- network.userIndexToUserId.set(index, userId)
-
- network.peers.set(userId, {
- userId: userId,
- index: index
+ console.log('[Network]: Create Peer', network.topic, peerID, peerIndex, userID, userIndex, name)
+
+ network.userIDToUserIndex.set(userID, userIndex)
+ network.userIndexToUserID.set(userIndex, userID)
+ network.peerIDToPeerIndex.set(peerID, peerIndex)
+ network.peerIndexToPeerID.set(peerIndex, peerID)
+
+ network.peers.set(peerID, {
+ peerID,
+ peerIndex,
+ userId: userID,
+ userIndex
})
const worldState = getState(WorldState)
- worldState.userNames[userId].set(name)
+ worldState.userNames[userID].set(name)
}
-function destroyPeer(network: Network, userId: UserId, world = Engine.instance.currentWorld) {
- console.log('[Network]: Destroy Peer', network.topic, userId)
- if (!network.peers.has(userId))
- return console.warn(`[WorldNetworkActionReceptors]: tried to remove client with userId ${userId} that doesn't exit`)
- if (userId === Engine.instance.userId)
+function destroyPeer(network: Network, peerID: PeerID, world = Engine.instance.currentWorld) {
+ console.log('[Network]: Destroy Peer', network.topic, peerID)
+ if (!network.peers.has(peerID))
+ return console.warn(`[WorldNetworkActionReceptors]: tried to remove client with peerID ${peerID} that doesn't exit`)
+ const userID = network.peers.get(peerID)!.userId
+ if (userID === Engine.instance.userId)
return console.warn(`[WorldNetworkActionReceptors]: tried to remove local client`)
- network.peers.delete(userId)
+ network.peers.delete(peerID)
+
+ const userIndex = network.userIDToUserIndex.get(userID)!
+ network.userIDToUserIndex.delete(userID)
+ network.userIndexToUserID.delete(userIndex)
+
+ const peerIndex = network.peerIDToPeerIndex.get(peerID)!
+ network.peerIDToPeerIndex.delete(peerID)
+ network.peerIndexToPeerID.delete(peerIndex)
/**
* if no other connections exist for this user, and this action is occurring on the world network,
@@ -50,17 +67,17 @@ function destroyPeer(network: Network, userId: UserId, world = Engine.instance.c
if (network.topic === 'world') {
const remainingPeersForDisconnectingUser = Object.entries(world.networks.entries())
.map(([id, network]: [string, Network]) => {
- return network.peers.has(userId)
+ return network.peers.has(peerID)
})
.filter((peer) => !!peer)
if (!remainingPeersForDisconnectingUser.length) {
- Engine.instance.store.actions.cached = Engine.instance.store.actions.cached.filter((a) => a.$from !== userId)
- for (const eid of world.getOwnedNetworkObjects(userId)) removeEntity(eid)
+ Engine.instance.store.actions.cached = Engine.instance.store.actions.cached.filter((a) => a.$from !== userID)
+ for (const eid of world.getOwnedNetworkObjects(userID)) removeEntity(eid)
}
- clearCachedActionsForUser(network, userId)
- clearActionsHistoryForUser(userId)
+ clearCachedActionsForUser(userID)
+ clearActionsHistoryForUser(userID)
}
}
@@ -76,7 +93,7 @@ function clearActionsHistoryForUser(userId: UserId) {
}
}
-function clearCachedActionsForUser(network: Network, userId: UserId) {
+function clearCachedActionsForUser(userId: UserId) {
const cached = Engine.instance.store.actions.cached
for (const action of [...cached]) {
if (action.$from === userId) {
@@ -86,10 +103,22 @@ function clearCachedActionsForUser(network: Network, userId: UserId) {
}
}
-function getCachedActionsForUser(network: Network, toUserId: UserId) {
+function clearCachedActionsOfTypeForUser(userId: UserId, actionShape: Validator) {
+ const cached = Engine.instance.store.actions.cached
+ for (const action of [...cached]) {
+ if (action.$from === userId && actionShape.test(action)) {
+ const idx = cached.indexOf(action)
+ cached.splice(idx, 1)
+ }
+ }
+}
+
+function getCachedActionsForUser(toUserId: UserId) {
// send all cached and outgoing actions to joining user
const cachedActions = [] as Required[]
- for (const action of Engine.instance.store.actions.cached) {
+ for (const action of Engine.instance.store.actions.cached as Array<
+ ReturnType
+ >) {
if (action.$from === toUserId) continue
if (action.$to === 'all' || action.$to === toUserId) cachedActions.push({ ...action, $stack: undefined! })
}
@@ -102,5 +131,6 @@ export const NetworkPeerFunctions = {
destroyPeer,
destroyAllPeers,
clearCachedActionsForUser,
+ clearCachedActionsOfTypeForUser,
getCachedActionsForUser
}
diff --git a/packages/engine/src/networking/functions/WorldNetworkAction.ts b/packages/engine/src/networking/functions/WorldNetworkAction.ts
index 9dc566b7f3..7933cb5a9f 100644
--- a/packages/engine/src/networking/functions/WorldNetworkAction.ts
+++ b/packages/engine/src/networking/functions/WorldNetworkAction.ts
@@ -5,6 +5,7 @@ import { ParityValue } from '../../common/enums/ParityValue'
import {
matches,
matchesNetworkId,
+ matchesPeerID,
matchesQuaternion,
matchesUserId,
matchesVector3,
@@ -126,7 +127,7 @@ export class WorldNetworkAction {
type: 'xre.world.REQUEST_AUTHORITY_OVER_OBJECT',
ownerId: matchesUserId,
networkId: matchesNetworkId,
- newAuthority: matchesUserId,
+ newAuthority: matchesPeerID,
$topic: NetworkTopics.world
})
@@ -134,7 +135,7 @@ export class WorldNetworkAction {
type: 'xre.world.TRANSFER_AUTHORITY_OF_OBJECT',
ownerId: matchesUserId,
networkId: matchesNetworkId,
- newAuthority: matchesUserId,
+ newAuthority: matchesPeerID,
$topic: NetworkTopics.world
})
diff --git a/packages/engine/src/networking/functions/WorldNetworkActionReceptor.test.ts b/packages/engine/src/networking/functions/WorldNetworkActionReceptor.test.ts
index 91db1ec98a..ec97e97271 100644
--- a/packages/engine/src/networking/functions/WorldNetworkActionReceptor.test.ts
+++ b/packages/engine/src/networking/functions/WorldNetworkActionReceptor.test.ts
@@ -2,18 +2,19 @@ import assert from 'assert'
import { Quaternion, Vector3 } from 'three'
import { NetworkId } from '@xrengine/common/src/interfaces/NetworkId'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { createMockNetwork } from '../../../tests/util/createMockNetwork'
import { AvatarComponent } from '../../avatar/components/AvatarComponent'
-import { createAvatar } from '../../avatar/functions/createAvatar'
+import { spawnAvatarReceptor } from '../../avatar/functions/spawnAvatarReceptor'
import { Engine } from '../../ecs/classes/Engine'
import { defineQuery, getComponent, hasComponent } from '../../ecs/functions/ComponentFunctions'
import { createEngine } from '../../initializeEngine'
import { Physics } from '../../physics/classes/Physics'
import { NetworkTopics } from '../classes/Network'
import { NetworkObjectComponent } from '../components/NetworkObjectComponent'
-import { NetworkObjectOwnedTag } from '../components/NetworkObjectOwnedTag'
+import { NetworkObjectOwnedTag } from '../components/NetworkObjectComponent'
import WorldNetworkActionSystem from '../systems/WorldNetworkActionSystem'
import { NetworkPeerFunctions } from './NetworkPeerFunctions'
import { WorldNetworkAction } from './WorldNetworkAction'
@@ -31,13 +32,16 @@ describe('WorldNetworkActionReceptors', () => {
it('should spawn object owned by host', () => {
const hostUserId = 'world' as UserId
const userId = 'user id' as UserId
+ const peerID = 'peer id' as PeerID
+ const peerID2 = 'peer id 2' as PeerID
Engine.instance.userId = userId
const world = Engine.instance.currentWorld
const network = world.worldNetwork
+ network.peerID = peerID
- NetworkPeerFunctions.createPeer(network, hostUserId, 0, 'host', world)
- NetworkPeerFunctions.createPeer(network, userId, 1, 'user name', world)
+ NetworkPeerFunctions.createPeer(network, peerID, 0, hostUserId, 0, 'host', world)
+ NetworkPeerFunctions.createPeer(network, peerID2, 1, userId, 1, 'user name', world)
const objNetId = 3 as NetworkId
const objPrefab = 'generic prefab'
@@ -47,7 +51,8 @@ describe('WorldNetworkActionReceptors', () => {
$from: world.worldNetwork.hostId, // from host
prefab: objPrefab, // generic prefab
networkId: objNetId,
- $topic: NetworkTopics.world
+ $topic: NetworkTopics.world,
+ $peer: network.peerID
}),
world
)
@@ -62,21 +67,24 @@ describe('WorldNetworkActionReceptors', () => {
assert.equal(networkObjectOwnedEntities.length, 0)
assert.equal(getComponent(networkObjectEntities[0], NetworkObjectComponent).networkId, objNetId)
- assert.equal(getComponent(networkObjectEntities[0], NetworkObjectComponent).authorityUserId, hostUserId)
+ assert.equal(getComponent(networkObjectEntities[0], NetworkObjectComponent).authorityPeerID, peerID)
assert.equal(hasComponent(networkObjectEntities[0], NetworkObjectOwnedTag), false)
})
it('should spawn object owned by user', () => {
const userId = 'user id' as UserId
const hostId = 'host' as UserId
+ const peerID = 'peer id' as PeerID
+ const peerID2 = 'peer id 2' as PeerID
Engine.instance.userId = userId
const world = Engine.instance.currentWorld
const network = world.worldNetwork
+ network.peerID = peerID2
- NetworkPeerFunctions.createPeer(network, hostId, 0, 'host', world)
- NetworkPeerFunctions.createPeer(network, userId, 1, 'user name', world)
+ NetworkPeerFunctions.createPeer(network, peerID, 0, hostId, 0, 'host', world)
+ NetworkPeerFunctions.createPeer(network, peerID2, 1, userId, 1, 'user name', world)
const objParams = 123
const objNetId = 3 as NetworkId
@@ -86,7 +94,8 @@ describe('WorldNetworkActionReceptors', () => {
WorldNetworkAction.spawnObject({
$from: userId, // from user
prefab: objPrefab, // generic prefab
- networkId: objNetId
+ networkId: objNetId,
+ $peer: network.peerID
})
)
@@ -100,7 +109,7 @@ describe('WorldNetworkActionReceptors', () => {
assert.equal(networkObjectOwnedEntities.length, 1)
assert.equal(getComponent(networkObjectEntities[0], NetworkObjectComponent).networkId, objNetId)
- assert.equal(getComponent(networkObjectEntities[0], NetworkObjectComponent).authorityUserId, userId)
+ assert.equal(getComponent(networkObjectEntities[0], NetworkObjectComponent).authorityPeerID, peerID2)
assert.equal(hasComponent(networkObjectEntities[0], NetworkObjectOwnedTag), true)
})
@@ -108,14 +117,18 @@ describe('WorldNetworkActionReceptors', () => {
const hostUserId = 'world' as UserId
const userId = 'user id' as UserId
const userId2 = 'second user id' as UserId
+ const peerID = 'peer id' as PeerID
+ const peerID2 = 'peer id 2' as PeerID
+ const peerID3 = 'peer id 3' as PeerID
Engine.instance.userId = userId
const world = Engine.instance.currentWorld
const network = world.worldNetwork
+ network.peerID = peerID
- NetworkPeerFunctions.createPeer(network, hostUserId, 0, 'world', world)
- NetworkPeerFunctions.createPeer(network, userId, 1, 'user name', world)
- NetworkPeerFunctions.createPeer(network, userId2, 2, 'second user name', world)
+ NetworkPeerFunctions.createPeer(network, peerID, 0, hostUserId, 0, 'world', world)
+ NetworkPeerFunctions.createPeer(network, peerID2, 1, userId, 1, 'user name', world)
+ NetworkPeerFunctions.createPeer(network, peerID3, 2, userId2, 2, 'second user name', world)
const objParams = {
position: new Vector3(),
@@ -129,6 +142,7 @@ describe('WorldNetworkActionReceptors', () => {
$from: userId2, // from other user
prefab: objPrefab, // generic prefab
networkId: objNetId,
+ $peer: peerID3,
$topic: NetworkTopics.world
}),
world
@@ -144,27 +158,32 @@ describe('WorldNetworkActionReceptors', () => {
assert.equal(networkObjectOwnedEntities.length, 0)
assert.equal(getComponent(networkObjectEntities[0], NetworkObjectComponent).networkId, objNetId)
- assert.equal(getComponent(networkObjectEntities[0], NetworkObjectComponent).authorityUserId, userId2)
+ assert.equal(getComponent(networkObjectEntities[0], NetworkObjectComponent).authorityPeerID, peerID3)
assert.equal(hasComponent(networkObjectEntities[0], NetworkObjectOwnedTag), false)
})
it('should spawn avatar owned by user', async () => {
const userId = 'user id' as UserId
+ const peerID = 'peer id' as PeerID
Engine.instance.userId = userId
const world = Engine.instance.currentWorld
const network = world.worldNetwork
+ network.peerID = peerID
- NetworkPeerFunctions.createPeer(network, userId, 1, 'user name', world)
+ NetworkPeerFunctions.createPeer(network, peerID, 1, userId, 1, 'user name', world)
- const action = WorldNetworkAction.spawnAvatar({ networkId: 42 as NetworkId })
+ const action = WorldNetworkAction.spawnAvatar({
+ networkId: 42 as NetworkId,
+ $peer: peerID
+ })
WorldNetworkActionReceptor.receiveSpawnObject(action)
- createAvatar(action)
+ spawnAvatarReceptor(action)
const entity = world.getOwnedNetworkObjectWithComponent(userId, AvatarComponent)
assert.equal(getComponent(entity, NetworkObjectComponent).networkId, 42)
- assert.equal(getComponent(entity, NetworkObjectComponent).authorityUserId, userId)
+ assert.equal(getComponent(entity, NetworkObjectComponent).authorityPeerID, peerID)
assert.equal(hasComponent(entity, NetworkObjectOwnedTag), true)
})
})
@@ -235,7 +254,7 @@ describe('WorldNetworkActionReceptors', () => {
// assert.equal(networkObjectOwnedEntitiesAfter.length, 0)
// assert.equal(getComponent(networkObjectEntitiesAfter[0], NetworkObjectComponent).networkId, objNetId)
- // assert.equal(getComponent(networkObjectEntitiesAfter[0], NetworkObjectComponent).authorityUserId, hostUserId)
+ // assert.equal(getComponent(networkObjectEntitiesAfter[0], NetworkObjectComponent).authorityPeerID, hostUserId)
// assert.equal(hasComponent(networkObjectEntitiesAfter[0], NetworkObjectOwnedTag), false)
// })
@@ -279,7 +298,7 @@ describe('WorldNetworkActionReceptors', () => {
// assert.equal(networkObjectEntities.length, 1)
// assert.equal(networkObjectOwnedEntities.length, 0)
// assert.equal(
- // getComponent(networkObjectEntities[0], NetworkObjectComponent).authorityUserId,
+ // getComponent(networkObjectEntities[0], NetworkObjectComponent).authorityPeerID,
// world.worldNetwork.hostId
// )
@@ -305,7 +324,7 @@ describe('WorldNetworkActionReceptors', () => {
// assert.equal(getComponent(networkObjectEntities[0], NetworkObjectComponent).networkId, objNetId)
// assert.equal(
- // getComponent(networkObjectEntities[0], NetworkObjectComponent).authorityUserId,
+ // getComponent(networkObjectEntities[0], NetworkObjectComponent).authorityPeerID,
// world.worldNetwork.hostId
// )
// assert.equal(hasComponent(networkObjectEntities[0], NetworkObjectOwnedTag), false)
diff --git a/packages/engine/src/networking/functions/WorldNetworkActionReceptor.ts b/packages/engine/src/networking/functions/WorldNetworkActionReceptor.ts
index 02cad5ff3d..d7fc1dd551 100644
--- a/packages/engine/src/networking/functions/WorldNetworkActionReceptor.ts
+++ b/packages/engine/src/networking/functions/WorldNetworkActionReceptor.ts
@@ -1,6 +1,7 @@
import { none } from '@hookstate/core'
import { Quaternion, Vector3 } from 'three'
+import { PeerID, SelfPeerID } from '@xrengine/common/src/interfaces/PeerID'
import { dispatchAction } from '@xrengine/hyperflux'
import { Engine } from '../../ecs/classes/Engine'
@@ -9,6 +10,7 @@ import {
addComponent,
ComponentType,
getComponent,
+ getComponentState,
hasComponent,
removeComponent,
setComponent
@@ -21,27 +23,38 @@ import {
setTransformComponent,
TransformComponent
} from '../../transform/components/TransformComponent'
-import { NetworkObjectAuthorityTag } from '../components/NetworkObjectAuthorityTag'
-import { NetworkObjectComponent } from '../components/NetworkObjectComponent'
-import { NetworkObjectOwnedTag } from '../components/NetworkObjectOwnedTag'
+import {
+ NetworkObjectAuthorityTag,
+ NetworkObjectComponent,
+ NetworkObjectOwnedTag
+} from '../components/NetworkObjectComponent'
import { WorldNetworkAction } from './WorldNetworkAction'
const receiveSpawnObject = (
action: typeof WorldNetworkAction.spawnObject.matches._TYPE,
world = Engine.instance.currentWorld
) => {
+ const existingAvatar =
+ WorldNetworkAction.spawnAvatar.matches.test(action) && !!world.getUserAvatarEntity(action.$from)
+ if (existingAvatar) return
+
const entity = createEntity()
- addComponent(entity, NetworkObjectComponent, {
+ setComponent(entity, NetworkObjectComponent, {
ownerId: action.$from,
- authorityUserId: action.$from,
+ authorityPeerID: action.$peer ?? world.worldNetwork?.peerID ?? SelfPeerID,
networkId: action.networkId
})
+ const isAuthoritativePeer = !action.$peer || action.$peer === world.worldNetwork?.peerID
+
+ if (isAuthoritativePeer) {
+ setComponent(entity, NetworkObjectAuthorityTag)
+ }
+
const isOwnedByMe = action.$from === Engine.instance.userId
if (isOwnedByMe) {
- addComponent(entity, NetworkObjectOwnedTag, true)
- addComponent(entity, NetworkObjectAuthorityTag, true)
+ setComponent(entity, NetworkObjectOwnedTag)
}
const position = new Vector3()
@@ -68,7 +81,7 @@ const receiveRegisterSceneObject = (
setComponent(entity, NetworkObjectComponent, {
ownerId: action.$from,
- authorityUserId: action.$from,
+ authorityPeerID: action.$peer ?? world.worldNetwork?.peerID ?? SelfPeerID,
networkId: action.networkId
})
@@ -142,13 +155,13 @@ const receiveTransferAuthorityOfObject = (
`Warning - tried to get entity belonging to ${action.ownerId} with ID ${action.networkId}, but it doesn't exist`
)
- getComponent(entity, NetworkObjectComponent).authorityUserId = action.newAuthority
+ getComponentState(entity, NetworkObjectComponent).authorityPeerID.set(action.newAuthority)
- if (Engine.instance.userId === action.newAuthority) {
+ if (world?.worldNetwork.peerID === action.newAuthority) {
if (hasComponent(entity, NetworkObjectAuthorityTag))
return console.warn(`Warning - User ${Engine.instance.userId} already has authority over entity ${entity}.`)
- addComponent(entity, NetworkObjectAuthorityTag, true)
+ setComponent(entity, NetworkObjectAuthorityTag)
} else {
if (hasComponent(entity, NetworkObjectAuthorityTag)) removeComponent(entity, NetworkObjectAuthorityTag)
}
diff --git a/packages/engine/src/networking/functions/receiveJoinWorld.ts b/packages/engine/src/networking/functions/receiveJoinWorld.ts
index 0e80677793..2f5bb4f9e9 100644
--- a/packages/engine/src/networking/functions/receiveJoinWorld.ts
+++ b/packages/engine/src/networking/functions/receiveJoinWorld.ts
@@ -1,7 +1,7 @@
// spawnPose is temporary - just so portals work for now - will be removed in favor of instanceserver-instanceserver communication
import { Quaternion, Vector3 } from 'three'
-import { UserId } from '@xrengine/common/src/interfaces/UserId'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { getSearchParamFromURL } from '@xrengine/common/src/utils/getSearchParamFromURL'
import { dispatchAction, getState } from '@xrengine/hyperflux'
import { Action } from '@xrengine/hyperflux/functions/ActionFunctions'
@@ -17,10 +17,12 @@ export type JoinWorldRequestData = {
}
export type JoinWorldProps = {
+ peerIndex: number
+ peerID: PeerID
highResTimeOrigin: number
+ routerRtpCapabilities: any
worldStartTime: number
cachedActions: Required[]
- spectateUserId?: UserId | 'none'
}
export type SpawnInWorldProps = {
@@ -41,7 +43,7 @@ export const spawnLocalAvatarInWorld = (props: SpawnInWorldProps) => {
export const receiveJoinWorld = (props: JoinWorldProps) => {
if (!props) return
- const { highResTimeOrigin, worldStartTime, cachedActions } = props
+ const { highResTimeOrigin, worldStartTime, cachedActions, peerID } = props
console.log('RECEIVED JOIN WORLD RESPONSE', highResTimeOrigin, worldStartTime, cachedActions)
for (const action of cachedActions) Engine.instance.store.actions.incoming.push({ ...action, $fromCache: true })
@@ -53,6 +55,8 @@ export const receiveJoinWorld = (props: JoinWorldProps) => {
dispatchAction(EngineActions.joinedWorld({}))
+ Engine.instance.currentWorld.worldNetwork.peerID = peerID
+
Engine.instance.store.actions.outgoing[NetworkTopics.world].queue.push(
...Engine.instance.store.actions.outgoing[NetworkTopics.world].history
)
diff --git a/packages/engine/src/networking/functions/validateNetworkObjects.ts b/packages/engine/src/networking/functions/validateNetworkObjects.ts
index d5da48ccd8..735c0919fe 100644
--- a/packages/engine/src/networking/functions/validateNetworkObjects.ts
+++ b/packages/engine/src/networking/functions/validateNetworkObjects.ts
@@ -4,16 +4,16 @@ import { Network } from '../classes/Network'
import { NetworkPeerFunctions } from './NetworkPeerFunctions'
export async function validateNetworkObjects(world: World, network: Network): Promise {
- for (const [userId, client] of network.peers) {
- if (userId === Engine.instance.userId) continue
+ for (const [peerID, client] of network.peers) {
+ if (client.userId === Engine.instance.userId) continue
// Validate that user has phoned home recently
if (Date.now() - client.lastSeenTs > 30000) {
- console.log('Removing client ', userId, ' due to inactivity')
+ console.log('Removing client ', peerID, ' due to inactivity')
- NetworkPeerFunctions.destroyPeer(network, userId, world)
+ NetworkPeerFunctions.destroyPeer(network, peerID, world)
network.updatePeers()
- console.log('Disconnected Client:', client.userId)
+ console.log('Disconnected Client:', peerID)
if (client?.instanceRecvTransport) {
console.log('Closing instanceRecvTransport')
await client.instanceRecvTransport.close()
@@ -35,7 +35,7 @@ export async function validateNetworkObjects(world: World, network: Network): Pr
console.log('Closed channelSendTransport')
}
- console.log('Removed transports for', userId)
+ console.log('Removed transports for', peerID)
}
}
}
diff --git a/packages/engine/src/networking/interfaces/NetworkPeer.ts b/packages/engine/src/networking/interfaces/NetworkPeer.ts
index 65e49c8782..e2a268d786 100644
--- a/packages/engine/src/networking/interfaces/NetworkPeer.ts
+++ b/packages/engine/src/networking/interfaces/NetworkPeer.ts
@@ -1,19 +1,22 @@
import type SocketIO from 'socket.io'
+import { MediaTagType } from '@xrengine/common/src/interfaces/MediaStreamConstants'
import { NetworkId } from '@xrengine/common/src/interfaces/NetworkId'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
export interface NetworkPeer {
userId: UserId
- index: number
+ userIndex: number
spectating?: boolean
networkId?: NetworkId // to easily retrieve the network object correspending to this client
// The following properties are only present on the server
socket?: SocketIO.Socket
- socketId?: string
+ peerIndex: number
+ peerID: PeerID
lastSeenTs?: any
joinTs?: any
- media?: {}
+ media?: Record
consumerLayers?: {}
stats?: {}
instanceSendTransport?: any
diff --git a/packages/engine/src/networking/serialization/DataReader.test.ts b/packages/engine/src/networking/serialization/DataReader.test.ts
index d226838bc7..d0cff38143 100644
--- a/packages/engine/src/networking/serialization/DataReader.test.ts
+++ b/packages/engine/src/networking/serialization/DataReader.test.ts
@@ -3,6 +3,7 @@ import { TypedArray } from 'bitecs'
import { Group, Quaternion, Vector3 } from 'three'
import { NetworkId } from '@xrengine/common/src/interfaces/NetworkId'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { getState } from '@xrengine/hyperflux'
@@ -12,7 +13,14 @@ import { proxifyQuaternion, proxifyVector3 } from '../../common/proxies/createTh
import { Engine } from '../../ecs/classes/Engine'
import { EngineState } from '../../ecs/classes/EngineState'
import { Entity } from '../../ecs/classes/Entity'
-import { addComponent, ComponentType, getAllComponents, getComponent } from '../../ecs/functions/ComponentFunctions'
+import {
+ addComponent,
+ ComponentType,
+ getAllComponents,
+ getComponent,
+ hasComponent,
+ setComponent
+} from '../../ecs/functions/ComponentFunctions'
import { createEntity } from '../../ecs/functions/EntityFunctions'
import { createEngine } from '../../initializeEngine'
import { RigidBodyComponent } from '../../physics/components/RigidBodyComponent'
@@ -20,7 +28,7 @@ import { NameComponent } from '../../scene/components/NameComponent'
import { setTransformComponent, TransformComponent } from '../../transform/components/TransformComponent'
import { XRHandsInputComponent } from '../../xr/XRComponents'
import { XRHandBones } from '../../xr/XRHandBones'
-import { NetworkObjectAuthorityTag } from '../components/NetworkObjectAuthorityTag'
+import { NetworkObjectAuthorityTag } from '../components/NetworkObjectComponent'
import { NetworkObjectComponent } from '../components/NetworkObjectComponent'
import {
checkBitflag,
@@ -448,14 +456,16 @@ describe('DataReader', () => {
const view = createViewCursor()
const entity = createEntity()
const networkId = 5678 as NetworkId
- const userId = '0' as UserId
+ const userId = '0' as UserId & PeerID
const userIndex = 0
NetworkObjectComponent.networkId[entity] = networkId
const network = Engine.instance.currentWorld.worldNetwork
- network.userIndexToUserId = new Map([[userIndex, userId]])
- network.userIdToUserIndex = new Map([[userId, userIndex]])
+ network.userIndexToUserID = new Map([[userIndex, userId]])
+ network.userIDToUserIndex = new Map([[userId, userIndex]])
+ network.peerIndexToPeerID = new Map([[userIndex, userId]])
+ network.peerIDToPeerIndex = new Map([[userId, userIndex]])
// construct values for a valid quaternion
const [a, b, c] = [0.167, 0.167, 0.167]
@@ -471,7 +481,7 @@ describe('DataReader', () => {
addComponent(entity, NetworkObjectComponent, {
networkId,
- authorityUserId: userId,
+ authorityPeerID: userId,
ownerId: userId
})
@@ -519,15 +529,16 @@ describe('DataReader', () => {
const view = createViewCursor()
const entity = createEntity()
const networkId = 5678 as NetworkId
- const userId = 'user Id' as UserId
+ const userId = 'user id' as UserId
+ const peerID = 'peer id' as PeerID
Engine.instance.userId = userId
const userIndex = 0
NetworkObjectComponent.networkId[entity] = networkId
const network = Engine.instance.currentWorld.worldNetwork
- network.userIndexToUserId = new Map([[userIndex, userId]])
- network.userIdToUserIndex = new Map([[userId, userIndex]])
+ network.userIndexToUserID = new Map([[userIndex, userId]])
+ network.userIDToUserIndex = new Map([[userId, userIndex]])
const [x, y, z, w] = [1.5, 2.5, 3.5, 4.5]
@@ -536,13 +547,13 @@ describe('DataReader', () => {
transform.position.set(x, y, z)
transform.rotation.set(x, y, z, w)
- addComponent(entity, NetworkObjectComponent, {
+ setComponent(entity, NetworkObjectComponent, {
networkId,
- authorityUserId: userId,
+ authorityPeerID: peerID,
ownerId: userId
})
- addComponent(entity, NetworkObjectAuthorityTag, true)
+ setComponent(entity, NetworkObjectAuthorityTag)
writeEntity(view, networkId, entity)
@@ -580,8 +591,8 @@ describe('DataReader', () => {
const userIndex = 0
const network = Engine.instance.currentWorld.worldNetwork
- network.userIndexToUserId = new Map([[userIndex, userId]])
- network.userIdToUserIndex = new Map([[userId, userIndex]])
+ network.userIndexToUserID = new Map([[userIndex, userId]])
+ network.userIDToUserIndex = new Map([[userId, userIndex]])
const [x, y, z, w] = [1.5, 2.5, 3.5, 4.5]
@@ -618,10 +629,9 @@ describe('DataReader', () => {
const writeView = createViewCursor()
const network = Engine.instance.currentWorld.worldNetwork
- network.userIndexToUserId = new Map()
- network.userIdToUserIndex = new Map()
const userId = 'userId' as UserId
+ const peerID = 'peerID' as PeerID
const n = 50
const entities: Entity[] = Array(n)
.fill(0)
@@ -637,6 +647,7 @@ describe('DataReader', () => {
entities.forEach((entity) => {
const networkId = entity as unknown as NetworkId
const userIndex = entity
+ const peerIndex = entity
setTransformComponent(entity)
const transform = getComponent(entity, TransformComponent)
@@ -644,11 +655,13 @@ describe('DataReader', () => {
transform.rotation.set(rotX, rotY, rotZ, rotW)
addComponent(entity, NetworkObjectComponent, {
networkId,
- authorityUserId: userId,
+ authorityPeerID: peerID,
ownerId: userId
})
- network.userIndexToUserId.set(userIndex, userId)
- network.userIdToUserIndex.set(userId, userIndex)
+ network.userIndexToUserID.set(userIndex, userId)
+ network.userIDToUserIndex.set(userId, userIndex)
+ network.peerIndexToPeerID.set(peerIndex, peerID)
+ network.peerIDToPeerIndex.set(peerID, peerIndex)
})
writeEntities(writeView, entities)
@@ -688,14 +701,12 @@ describe('DataReader', () => {
const write = createDataWriter()
const network = Engine.instance.currentWorld.worldNetwork
- network.userIndexToUserId = new Map()
- network.userIdToUserIndex = new Map()
-
Engine.instance.userId = 'userId' as UserId
const userId = Engine.instance.userId
+ const peerID = 'peerID' as PeerID
const userIndex = 0
- network.userIndexToUserId.set(userIndex, userId)
- network.userIdToUserIndex.set(userId, userIndex)
+ network.userIndexToUserID.set(userIndex, userId)
+ network.userIDToUserIndex.set(userId, userIndex)
const n = 10
const entities: Entity[] = Array(n)
@@ -718,17 +729,18 @@ describe('DataReader', () => {
transform.rotation.set(rotX, rotY, rotZ, rotW)
addComponent(entity, NetworkObjectComponent, {
networkId,
- authorityUserId: userId,
+ authorityPeerID: peerID,
ownerId: userId
})
})
- const packet = write(Engine.instance.currentWorld, network, Engine.instance.userId, entities)
+ const packet = write(Engine.instance.currentWorld, network, Engine.instance.userId, peerID, entities)
const readView = createViewCursor(packet)
- const _tick = readUint32(readView)
const _userIndex = readUint32(readView)
+ const _peerIndex = readUint32(readView)
+ const _tick = readUint32(readView)
const count = readUint32(readView)
strictEqual(count, entities.length)
@@ -791,9 +803,8 @@ describe('DataReader', () => {
it('should createDataReader and return empty packet if no changes were made on a fixedTick not divisible by 60', () => {
const write = createDataWriter()
+ const peerID = 'peerID' as PeerID
const network = Engine.instance.currentWorld.worldNetwork
- network.userIndexToUserId = new Map()
- network.userIdToUserIndex = new Map()
const engineState = getState(EngineState)
engineState.fixedTick.set(1)
@@ -806,7 +817,7 @@ describe('DataReader', () => {
entities.forEach((entity) => {
const networkId = entity as unknown as NetworkId
- const userId = entity as unknown as UserId
+ const userId = entity as unknown as UserId & PeerID
const userIndex = entity
setTransformComponent(entity)
const transform = getComponent(entity, TransformComponent)
@@ -814,14 +825,14 @@ describe('DataReader', () => {
transform.rotation.set(x, y, z, w)
addComponent(entity, NetworkObjectComponent, {
networkId,
- authorityUserId: userId,
+ authorityPeerID: userId,
ownerId: userId
})
- network.userIndexToUserId.set(userIndex, userId)
- network.userIdToUserIndex.set(userId, userIndex)
+ network.userIndexToUserID.set(userIndex, userId)
+ network.userIDToUserIndex.set(userId, userIndex)
})
- const packet = write(Engine.instance.currentWorld, network, Engine.instance.userId, entities)
+ const packet = write(Engine.instance.currentWorld, network, Engine.instance.userId, peerID, entities)
strictEqual(packet.byteLength, 0)
@@ -835,11 +846,9 @@ describe('DataReader', () => {
it('should createDataReader and return populated packet if no changes were made but on a fixedTick divisible by 60', () => {
const write = createDataWriter()
const network = Engine.instance.currentWorld.worldNetwork
-
- network.userIndexToUserId = new Map()
- network.userIdToUserIndex = new Map()
const engineState = getState(EngineState)
engineState.fixedTick.set(60)
+ const peerID = 'peerID' as PeerID
const n = 10
const entities: Entity[] = Array(n)
@@ -850,7 +859,7 @@ describe('DataReader', () => {
entities.forEach((entity) => {
const networkId = entity as unknown as NetworkId
- const userId = entity as unknown as UserId
+ const userId = entity as unknown as UserId & PeerID
const userIndex = entity
setTransformComponent(entity)
@@ -859,26 +868,25 @@ describe('DataReader', () => {
transform.rotation.set(x, y, z, w)
addComponent(entity, NetworkObjectComponent, {
networkId,
- authorityUserId: userId,
+ authorityPeerID: userId,
ownerId: userId
})
- network.userIndexToUserId.set(userIndex, userId)
- network.userIdToUserIndex.set(userId, userIndex)
+ network.userIndexToUserID.set(userIndex, userId)
+ network.userIDToUserIndex.set(userId, userIndex)
})
- const packet = write(Engine.instance.currentWorld, network, Engine.instance.userId, entities)
+ const packet = write(Engine.instance.currentWorld, network, Engine.instance.userId, peerID, entities)
- strictEqual(packet.byteLength, 372)
+ strictEqual(packet.byteLength, 376)
})
it('should createDataReader and detect changes', () => {
const write = createDataWriter()
const network = Engine.instance.currentWorld.worldNetwork
- network.userIndexToUserId = new Map()
- network.userIdToUserIndex = new Map()
const engineState = getState(EngineState)
engineState.fixedTick.set(1)
+ const peerID = 'peerID' as PeerID
const n = 10
const entities: Entity[] = Array(n)
@@ -889,7 +897,7 @@ describe('DataReader', () => {
entities.forEach((entity) => {
const networkId = entity as unknown as NetworkId
- const userId = entity as unknown as UserId
+ const userId = entity as unknown as UserId & PeerID
const userIndex = entity
setTransformComponent(entity)
const transform = getComponent(entity, TransformComponent)
@@ -897,14 +905,14 @@ describe('DataReader', () => {
transform.rotation.set(x, y, z, w)
addComponent(entity, NetworkObjectComponent, {
networkId,
- authorityUserId: userId,
+ authorityPeerID: userId,
ownerId: userId
})
- network.userIndexToUserId.set(userIndex, userId)
- network.userIdToUserIndex.set(userId, userIndex)
+ network.userIndexToUserID.set(userIndex, userId)
+ network.userIDToUserIndex.set(userId, userIndex)
})
- let packet = write(Engine.instance.currentWorld, network, Engine.instance.userId, entities)
+ let packet = write(Engine.instance.currentWorld, network, Engine.instance.userId, peerID, entities)
strictEqual(packet.byteLength, 0)
@@ -920,14 +928,15 @@ describe('DataReader', () => {
TransformComponent.position.y[entity] = 1
TransformComponent.position.z[entity] = 1
- packet = write(Engine.instance.currentWorld, network, Engine.instance.userId, entities)
+ packet = write(Engine.instance.currentWorld, network, Engine.instance.userId, peerID, entities)
- strictEqual(packet.byteLength, 43)
+ strictEqual(packet.byteLength, 47)
readView = createViewCursor(packet)
- const _tick = readUint32(readView)
const _userIndex = readUint32(readView)
+ const _peerIndex = readUint32(readView)
+ const _tick = readUint32(readView)
const count = readUint32(readView)
strictEqual(count, 1) // only one entity changed
diff --git a/packages/engine/src/networking/serialization/DataReader.ts b/packages/engine/src/networking/serialization/DataReader.ts
index bb81e6f3cc..162664fcb8 100644
--- a/packages/engine/src/networking/serialization/DataReader.ts
+++ b/packages/engine/src/networking/serialization/DataReader.ts
@@ -2,6 +2,7 @@ import { TypedArray } from 'bitecs'
import { Quaternion, Vector3 } from 'three'
import { NetworkId } from '@xrengine/common/src/interfaces/NetworkId'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { AvatarComponent } from '../../avatar/components/AvatarComponent'
@@ -9,14 +10,14 @@ import { AvatarLeftHandIKComponent, AvatarRightHandIKComponent } from '../../ava
import { AvatarHeadIKComponent } from '../../avatar/components/AvatarIKComponents'
import { Entity, UndefinedEntity } from '../../ecs/classes/Entity'
import { World } from '../../ecs/classes/World'
-import { addComponent, getComponent, hasComponent } from '../../ecs/functions/ComponentFunctions'
+import { addComponent, getComponent, hasComponent, removeComponent } from '../../ecs/functions/ComponentFunctions'
import { RigidBodyComponent } from '../../physics/components/RigidBodyComponent'
import { NameComponent } from '../../scene/components/NameComponent'
import { TransformComponent } from '../../transform/components/TransformComponent'
import { XRHandsInputComponent } from '../../xr/XRComponents'
import { XRHandBones } from '../../xr/XRHandBones'
import { Network } from '../classes/Network'
-import { NetworkObjectAuthorityTag } from '../components/NetworkObjectAuthorityTag'
+import { NetworkObjectAuthorityTag } from '../components/NetworkObjectComponent'
import { expand, QUAT_MAX_RANGE, QUAT_PRECISION_MULT, VEC3_MAX_RANGE, VEC3_PRECISION_MULT } from './Utils'
import { flatten, Vector3SoA, Vector4SoA } from './Utils'
import {
@@ -293,27 +294,30 @@ export const readEntity = (v: ViewCursor, world: World, fromUserId: UserId) => {
// if (checkBitflag(changeMask, 1 << b++)) readXRHands(v, entity)
}
-export const readEntities = (v: ViewCursor, world: World, byteLength: number, fromUserId: UserId) => {
+export const readEntities = (v: ViewCursor, world: World, byteLength: number, fromUserID: UserId) => {
while (v.cursor < byteLength) {
const count = readUint32(v)
for (let i = 0; i < count; i++) {
- readEntity(v, world, fromUserId)
+ readEntity(v, world, fromUserID)
}
}
}
export const readMetadata = (v: ViewCursor, world: World) => {
const userIndex = readUint32(v)
+ const peerIndex = readUint32(v)
const fixedTick = readUint32(v)
- // if (userIndex === world.userIdToUserIndex.get(world.worldNetwork.hostId)! && !world.worldNetwork.isHosting) world.fixedTick = fixedTick
- return userIndex
+ // if (userIndex === world.peerIDToUserIndex.get(world.worldNetwork.hostId)! && !world.worldNetwork.isHosting) world.fixedTick = fixedTick
+ return { userIndex, peerIndex }
}
export const createDataReader = () => {
return (world: World, network: Network, packet: ArrayBuffer) => {
const view = createViewCursor(packet)
- const userIndex = readMetadata(view, world)
- const fromUserId = network.userIndexToUserId.get(userIndex)
- if (fromUserId) readEntities(view, world, packet.byteLength, fromUserId)
+ const { userIndex, peerIndex } = readMetadata(view, world)
+ const fromUserID = network.userIndexToUserID.get(userIndex)
+ const fromPeerID = network.peerIndexToPeerID.get(peerIndex)
+ const isLoopback = fromPeerID && fromPeerID === network.peerID
+ if (fromUserID && !isLoopback) readEntities(view, world, packet.byteLength, fromUserID)
}
}
diff --git a/packages/engine/src/networking/serialization/DataWriter.test.ts b/packages/engine/src/networking/serialization/DataWriter.test.ts
index 46206bfbb4..fd647bb2c3 100644
--- a/packages/engine/src/networking/serialization/DataWriter.test.ts
+++ b/packages/engine/src/networking/serialization/DataWriter.test.ts
@@ -2,6 +2,7 @@ import { strictEqual } from 'assert'
import { Group, Matrix4, Quaternion, Vector3 } from 'three'
import { NetworkId } from '@xrengine/common/src/interfaces/NetworkId'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { getState } from '@xrengine/hyperflux'
@@ -338,6 +339,7 @@ describe('DataWriter', () => {
const entity = createEntity()
const networkId = 999 as NetworkId
const userId = '0' as UserId
+ const peerID = 'peer id' as PeerID
const userIndex = 0
// construct values for a valid quaternion
@@ -356,7 +358,7 @@ describe('DataWriter', () => {
addComponent(entity, NetworkObjectComponent, {
networkId,
- authorityUserId: userId,
+ authorityPeerID: peerID,
ownerId: userId
})
@@ -418,7 +420,7 @@ describe('DataWriter', () => {
entities.forEach((entity) => {
const networkId = entity as unknown as NetworkId
- const userId = entity as unknown as UserId
+ const userId = entity as unknown as UserId & PeerID
const userIndex = entity
NetworkObjectComponent.networkId[entity] = networkId
@@ -431,7 +433,7 @@ describe('DataWriter', () => {
addComponent(entity, NetworkObjectComponent, {
networkId,
- authorityUserId: userId,
+ authorityPeerID: userId,
ownerId: userId
})
})
@@ -486,6 +488,7 @@ describe('DataWriter', () => {
it('should createDataWriter', () => {
const world = Engine.instance.currentWorld
+ const peerID = 'peerID' as PeerID
const write = createDataWriter()
@@ -503,7 +506,7 @@ describe('DataWriter', () => {
entities.forEach((entity) => {
const networkId = entity as unknown as NetworkId
- const userId = entity as unknown as UserId
+ const userId = entity as unknown as UserId & PeerID
const userIndex = entity
NetworkObjectComponent.networkId[entity] = networkId
@@ -516,16 +519,16 @@ describe('DataWriter', () => {
addComponent(entity, NetworkObjectComponent, {
networkId,
- authorityUserId: userId,
+ authorityPeerID: userId,
ownerId: userId
})
})
const network = Engine.instance.currentWorld.worldNetwork
- const packet = write(world, network, Engine.instance.userId, entities)
+ const packet = write(world, network, Engine.instance.userId, peerID, entities)
const expectedBytes =
- 3 * Uint32Array.BYTES_PER_ELEMENT +
+ 4 * Uint32Array.BYTES_PER_ELEMENT +
n *
(1 * Uint32Array.BYTES_PER_ELEMENT +
4 * Uint8Array.BYTES_PER_ELEMENT +
@@ -536,8 +539,9 @@ describe('DataWriter', () => {
const readView = createViewCursor(packet)
- const tick = readUint32(readView)
const userIndex = readUint32(readView)
+ const peerIndex = readUint32(readView)
+ const tick = readUint32(readView)
const count = readUint32(readView)
strictEqual(count, entities.length)
diff --git a/packages/engine/src/networking/serialization/DataWriter.ts b/packages/engine/src/networking/serialization/DataWriter.ts
index 85ac91ec58..ab709f2cb3 100644
--- a/packages/engine/src/networking/serialization/DataWriter.ts
+++ b/packages/engine/src/networking/serialization/DataWriter.ts
@@ -1,6 +1,7 @@
import { Group } from 'three'
import { NetworkId } from '@xrengine/common/src/interfaces/NetworkId'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { AvatarComponent } from '../../avatar/components/AvatarComponent'
@@ -400,16 +401,17 @@ export const writeEntities = (v: ViewCursor, entities: Entity[]) => {
else v.cursor = 0 // nothing written
}
-export const writeMetadata = (v: ViewCursor, network: Network, userId: UserId, world: World) => {
- writeUint32(v, network.userIdToUserIndex.get(userId)!)
+export const writeMetadata = (v: ViewCursor, network: Network, userId: UserId, peerID: PeerID, world: World) => {
+ writeUint32(v, network.userIDToUserIndex.get(userId)!)
+ writeUint32(v, network.peerIDToPeerIndex.get(peerID)!)
writeUint32(v, world.fixedTick)
}
export const createDataWriter = (size: number = 100000) => {
const view = createViewCursor(new ArrayBuffer(size))
- return (world: World, network: Network, userId: UserId, entities: Entity[]) => {
- writeMetadata(view, network, userId, world)
+ return (world: World, network: Network, userId: UserId, peerID: PeerID, entities: Entity[]) => {
+ writeMetadata(view, network, userId, peerID, world)
writeEntities(view, entities)
return sliceViewCursor(view)
}
diff --git a/packages/engine/src/networking/serialization/ViewCursor.ts b/packages/engine/src/networking/serialization/ViewCursor.ts
index b9618ff83c..6fe0d80111 100644
--- a/packages/engine/src/networking/serialization/ViewCursor.ts
+++ b/packages/engine/src/networking/serialization/ViewCursor.ts
@@ -1,7 +1,7 @@
import { TypedArray } from 'bitecs'
+import { Engine } from '../../ecs/classes/Engine'
import { Entity } from '../../ecs/classes/Entity'
-import { useWorld } from '../../ecs/functions/SystemHooks'
import { NetworkObjectComponent } from '../components/NetworkObjectComponent'
export type ViewCursor = DataView & { cursor: number; shadowMap: Map }
@@ -50,7 +50,7 @@ export const writePropIfChanged = (v: ViewCursor, prop: TypedArray, entity: Enti
const shadow = shadowMap.get(prop)! || (shadowMap.set(prop, prop.slice().fill(0)) && shadowMap.get(prop))!
// TODO: we should be handling the fixedDelta check more explicitly, passing down through all the functions
- const changed = shadow[entity] !== prop[entity] || useWorld().fixedTick % 60 === 0
+ const changed = shadow[entity] !== prop[entity] || Engine.instance.currentWorld.fixedTick % 60 === 0
shadow[entity] = prop[entity]
diff --git a/packages/engine/src/networking/systems/MediaStreamSystem.ts b/packages/engine/src/networking/systems/MediaStreamSystem.ts
index bee2a1dea4..a7346643e8 100755
--- a/packages/engine/src/networking/systems/MediaStreamSystem.ts
+++ b/packages/engine/src/networking/systems/MediaStreamSystem.ts
@@ -1,37 +1,33 @@
-import { createActionQueue, removeActionQueue } from '@xrengine/hyperflux'
-
import { World } from '../../ecs/classes/World'
-import { MediaNetworkAction } from '../functions/MediaNetworkAction'
export default async function MediaStreamSystem(world: World) {
- const getConsumer = (consumerId: string) => world.mediaNetwork.consumers.find((c) => c.id === consumerId)
- const getProducer = (producerId: string) => world.mediaNetwork.producers.find((c) => c.id === producerId)
-
- const pauseConsumerQueue = createActionQueue(MediaNetworkAction.pauseConsumer.matches)
- const pauseProducerQueue = createActionQueue(MediaNetworkAction.pauseProducer.matches)
+ let executeInProgress = false
const execute = () => {
- if (!world.mediaNetwork) return
-
- for (const action of pauseConsumerQueue()) {
- const consumer = getConsumer(action.consumerId)
- if (consumer && !consumer?.closed && !consumer?._closed) {
- action.pause ? consumer.pause() : consumer.resume()
- }
- }
-
- for (const action of pauseProducerQueue()) {
- const producer = getProducer(action.producerId)
- if (producer && !producer?.closed && !producer?._closed) {
- action.pause ? producer.pause() : producer.resume()
+ const network = world.mediaNetwork
+ if (!network) return
+
+ if (network?.mediasoupOperationQueue.getBufferLength() > 0 && !executeInProgress) {
+ executeInProgress = true
+ const buffer = network.mediasoupOperationQueue.pop() as any
+ if (buffer.object && buffer.object.closed !== true && buffer.object._closed !== true) {
+ try {
+ if (buffer.action === 'resume') buffer.object.resume()
+ else if (buffer.action === 'pause') buffer.object.pause()
+ executeInProgress = false
+ } catch (err) {
+ executeInProgress = false
+ console.log('Pause or resume error')
+ console.log(err)
+ console.log(buffer.object)
+ }
+ } else {
+ executeInProgress = false
}
}
}
- const cleanup = async () => {
- removeActionQueue(pauseConsumerQueue)
- removeActionQueue(pauseProducerQueue)
- }
+ const cleanup = async () => {}
return { execute, cleanup }
}
diff --git a/packages/engine/src/networking/systems/OutgoingNetworkSystem.ts b/packages/engine/src/networking/systems/OutgoingNetworkSystem.ts
index bc31bfcffe..407a1aff52 100644
--- a/packages/engine/src/networking/systems/OutgoingNetworkSystem.ts
+++ b/packages/engine/src/networking/systems/OutgoingNetworkSystem.ts
@@ -2,7 +2,7 @@ import { Engine } from '../../ecs/classes/Engine'
import { World } from '../../ecs/classes/World'
import { defineQuery, removeQuery } from '../../ecs/functions/ComponentFunctions'
import { TransformComponent } from '../../transform/components/TransformComponent'
-import { NetworkObjectAuthorityTag } from '../components/NetworkObjectAuthorityTag'
+import { NetworkObjectAuthorityTag } from '../components/NetworkObjectComponent'
import { NetworkObjectComponent } from '../components/NetworkObjectComponent'
import { createDataWriter } from '../serialization/DataWriter'
@@ -20,10 +20,9 @@ const authoritativeNetworkTransformsQuery = defineQuery([
const serializeAndSend = (world: World, serialize: ReturnType) => {
const ents = Engine.instance.isEditor ? networkTransformsQuery(world) : authoritativeNetworkTransformsQuery(world)
if (ents.length > 0) {
- const userId = Engine.instance.currentWorld.worldNetwork?.isHosting
- ? Engine.instance.currentWorld.worldNetwork.hostId
- : Engine.instance.userId
- const data = serialize(world, world.worldNetwork, userId, ents)
+ const userID = Engine.instance.userId
+ const peerID = Engine.instance.currentWorld.worldNetwork.peerID
+ const data = serialize(world, world.worldNetwork, userID, peerID, ents)
// todo: insert historian logic here
diff --git a/packages/engine/src/physics/systems/PhysicsSystem.ts b/packages/engine/src/physics/systems/PhysicsSystem.ts
index b163377397..4fd6d29b70 100755
--- a/packages/engine/src/physics/systems/PhysicsSystem.ts
+++ b/packages/engine/src/physics/systems/PhysicsSystem.ts
@@ -10,7 +10,7 @@ import { Entity } from '../../ecs/classes/Entity'
import { World } from '../../ecs/classes/World'
import { defineQuery, getComponent, hasComponent, removeQuery } from '../../ecs/functions/ComponentFunctions'
import { NetworkObjectComponent } from '../../networking/components/NetworkObjectComponent'
-import { NetworkObjectOwnedTag } from '../../networking/components/NetworkObjectOwnedTag'
+import { NetworkObjectOwnedTag } from '../../networking/components/NetworkObjectComponent'
import { WorldNetworkAction } from '../../networking/functions/WorldNetworkAction'
import {
ColliderComponent,
@@ -109,12 +109,6 @@ export default async function PhysicsSystem(world: World) {
const colliderQuery = defineQuery([ColliderComponent, Not(GLTFLoadedComponent)])
const groupColliderQuery = defineQuery([ColliderComponent, GLTFLoadedComponent])
const allRigidBodyQuery = defineQuery([RigidBodyComponent, Not(RigidBodyFixedTagComponent)])
- const networkedAvatarBodyQuery = defineQuery([
- RigidBodyComponent,
- NetworkObjectComponent,
- Not(NetworkObjectOwnedTag),
- AvatarComponent
- ])
const collisionQuery = defineQuery([CollisionComponent])
const teleportObjectQueue = createActionQueue(WorldNetworkAction.teleportObject.matches)
@@ -177,7 +171,6 @@ export default async function PhysicsSystem(world: World) {
removeQuery(world, colliderQuery)
removeQuery(world, groupColliderQuery)
removeQuery(world, allRigidBodyQuery)
- removeQuery(world, networkedAvatarBodyQuery)
removeQuery(world, collisionQuery)
removeActionQueue(teleportObjectQueue)
diff --git a/packages/engine/tests/equippables/equippables.test.ts b/packages/engine/tests/equippables/equippables.test.ts
index cdde5e34c6..23a1d02da5 100644
--- a/packages/engine/tests/equippables/equippables.test.ts
+++ b/packages/engine/tests/equippables/equippables.test.ts
@@ -3,6 +3,7 @@ import assert from 'assert'
import { Matrix4, Mesh, MeshNormalMaterial, Quaternion, SphereGeometry, Vector3 } from 'three'
import { NetworkId } from '@xrengine/common/src/interfaces/NetworkId'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { applyIncomingActions, clearOutgoingActions } from '@xrengine/hyperflux'
@@ -31,11 +32,16 @@ describe.skip('Equippables Integration Tests', () => {
it('Can equip and unequip', async () => {
const world = Engine.instance.currentWorld
- const hostUserId = 'world' as UserId
+ const hostUserId = 'world' as UserId & PeerID
world.worldNetwork.hostId = hostUserId
const hostIndex = 0
- world.worldNetwork.peers.set(hostUserId, { userId: hostUserId, index: hostIndex })
+ world.worldNetwork.peers.set(hostUserId, {
+ peerID: hostUserId,
+ peerIndex: hostIndex,
+ userId: hostUserId,
+ userIndex: hostIndex
+ })
const userId = 'user id' as UserId
const userName = 'user name'
@@ -63,7 +69,7 @@ describe.skip('Equippables Integration Tests', () => {
// initially the object is owned by server
addComponent(equippableEntity, NetworkObjectComponent, {
ownerId: world.worldNetwork.hostId,
- authorityUserId: world.worldNetwork.hostId,
+ authorityPeerID: world.worldNetwork.peerID,
networkId: 0 as NetworkId
})
diff --git a/packages/hyperflux/functions/ActionFunctions.ts b/packages/hyperflux/functions/ActionFunctions.ts
index 460ae9f691..66ea29c6ab 100644
--- a/packages/hyperflux/functions/ActionFunctions.ts
+++ b/packages/hyperflux/functions/ActionFunctions.ts
@@ -2,6 +2,7 @@ import { MathUtils } from 'three'
import { matches, Validator } from 'ts-matches'
import { OpaqueType } from '@xrengine/common/src/interfaces/OpaqueType'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import multiLogger from '@xrengine/common/src/logger'
import { deepEqual } from '@xrengine/engine/src/common/functions/deepEqual'
@@ -43,6 +44,12 @@ export type ActionOptions = {
*/
$uuid?: string
+ /**
+ * The id of the sender's socket
+ * Will be undefined if dispatched locally or not in a network
+ */
+ $peer?: PeerID
+
/**
* The id of the sender
*/
diff --git a/packages/instanceserver/src/NetworkFunctions.ts b/packages/instanceserver/src/NetworkFunctions.ts
index 8203028958..6554337b62 100755
--- a/packages/instanceserver/src/NetworkFunctions.ts
+++ b/packages/instanceserver/src/NetworkFunctions.ts
@@ -1,7 +1,9 @@
import { DataConsumer, DataProducer } from 'mediasoup/node/lib/types'
import { Socket } from 'socket.io'
+import type { SocketId } from 'socket.io-adapter'
import { Instance } from '@xrengine/common/src/interfaces/Instance'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserInterface } from '@xrengine/common/src/interfaces/User'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { SpawnPoseComponent } from '@xrengine/engine/src/avatar/components/SpawnPoseComponent'
@@ -13,7 +15,6 @@ import { getComponent } from '@xrengine/engine/src/ecs/functions/ComponentFuncti
import { MessageTypes } from '@xrengine/engine/src/networking/enums/MessageTypes'
import { NetworkPeerFunctions } from '@xrengine/engine/src/networking/functions/NetworkPeerFunctions'
import { JoinWorldProps, JoinWorldRequestData } from '@xrengine/engine/src/networking/functions/receiveJoinWorld'
-import { WorldNetworkAction } from '@xrengine/engine/src/networking/functions/WorldNetworkAction'
import { AvatarProps, WorldState } from '@xrengine/engine/src/networking/interfaces/WorldState'
import { Object3DComponent } from '@xrengine/engine/src/scene/components/Object3DComponent'
import { TransformComponent } from '@xrengine/engine/src/transform/components/TransformComponent'
@@ -219,26 +220,31 @@ export const authorizeUserToJoinServer = async (app: Application, instance: Inst
return true
}
-export function getUserIdFromSocketId(network: SocketWebRTCServerNetwork, socketId: string) {
- const client = Array.from(network.peers.values()).find((c) => c.socketId === socketId)
+export function getUserIdFromPeerID(network: SocketWebRTCServerNetwork, socketID: SocketId | PeerID) {
+ const client = Array.from(network.peers.values()).find((c) => c.peerID === socketID)
return client?.userId
}
export const handleConnectingPeer = async (network: SocketWebRTCServerNetwork, socket: Socket, user: UserInterface) => {
const userId = user.id
const avatarDetail = user.avatar
+ const peerID = socket.id as PeerID
// Create a new client object
// and add to the dictionary
- const userIndex = network.userIndexCount++
- network.peers.set(userId, {
+ const existingUser = Array.from(network.peers.values()).find((client) => client.userId === userId)
+ const userIndex = existingUser ? existingUser.userIndex : network.userIndexCount++
+ const peerIndex = network.peerIndexCount++
+
+ network.peers.set(peerID, {
userId,
- index: userIndex,
+ userIndex: userIndex,
socket: socket,
- socketId: socket.id,
+ peerIndex,
+ peerID,
lastSeenTs: Date.now(),
joinTs: Date.now(),
- media: {},
+ media: {} as any,
consumerLayers: {},
stats: {},
dataConsumers: new Map(), // Key => id of data producer
@@ -252,10 +258,10 @@ export const handleConnectingPeer = async (network: SocketWebRTCServerNetwork, s
thumbnailURL: avatarDetail.thumbnailResource?.url || ''
})
- network.userIdToUserIndex.set(userId, userIndex)
- network.userIndexToUserId.set(userIndex, userId)
+ network.userIDToUserIndex.set(userId, userIndex)
+ network.userIndexToUserID.set(userIndex, userId)
- const spectating = network.peers.get(userId)!.spectating
+ const spectating = network.peers.get(peerID)!.spectating
network.app.service('message').create(
{
@@ -276,7 +282,7 @@ export async function handleJoinWorld(
network: SocketWebRTCServerNetwork,
socket: Socket,
data: JoinWorldRequestData,
- callback: Function,
+ callback: (props: JoinWorldProps) => unknown,
userId: UserId,
user: UserInterface
) {
@@ -284,11 +290,15 @@ export async function handleJoinWorld(
const world = Engine.instance.currentWorld
- const cachedActions = NetworkPeerFunctions.getCachedActionsForUser(network, userId)
+ const cachedActions = NetworkPeerFunctions.getCachedActionsForUser(userId)
+
+ const peerID = socket.id as PeerID
network.updatePeers()
callback({
+ peerIndex: network.peerIDToPeerIndex.get(peerID)!,
+ peerID,
routerRtpCapabilities: network.routers.instance[0].rtpCapabilities,
highResTimeOrigin: performance.timeOrigin,
worldStartTime: world.startTime,
@@ -298,26 +308,6 @@ export async function handleJoinWorld(
if (data.inviteCode && !network.app.isChannelInstance) await getUserSpawnFromInvite(network, user, data.inviteCode!)
}
-export function disconnectClientIfConnected(network: SocketWebRTCServerNetwork, socket: Socket, userId: UserId) {
- // If we are already logged in, kick the other socket
- const client = network.peers.get(userId)
- if (client) {
- if (client.socketId === socket.id) {
- logger.info('Client already logged in, disallowing new connection')
- return true
- }
-
- // kick old client instead of new one
- logger.info('Client already exists, kicking the old client and disconnecting')
- client.socket?.emit(MessageTypes.Kick.toString(), 'You joined this world on another device')
- client.socket?.disconnect()
- handleDisconnect(network, client.socket!)
-
- // return true anyway, new client will send another connect to world request which will pass
- return true
- }
-}
-
const getUserSpawnFromInvite = async (
network: SocketWebRTCServerNetwork,
user: UserInterface,
@@ -383,38 +373,33 @@ const getUserSpawnFromInvite = async (
export function handleIncomingActions(network: SocketWebRTCServerNetwork, socket: Socket, message) {
if (!message) return
-
- const userIdMap = {} as { [socketId: string]: UserId }
- for (const [id, client] of network.peers) userIdMap[client.socketId!] = id
- if (!userIdMap[socket.id])
- throw new Error('Received actions from a peer that does not exist: ' + JSON.stringify(message))
+ const networkPeer = network.peers.get(socket.id as PeerID)
+ if (!networkPeer) throw new Error('Received actions from a peer that does not exist: ' + JSON.stringify(message))
const actions = /*decode(new Uint8Array(*/ message /*))*/ as Required[]
for (const a of actions) {
- a['$fromSocketId'] = socket.id
- a.$from = userIdMap[socket.id]
+ a.$peer = socket.id as PeerID
+ a.$from = networkPeer.userId
dispatchAction(a)
}
// logger.info('SERVER INCOMING ACTIONS: %s', JSON.stringify(actions))
}
export async function handleHeartbeat(network: SocketWebRTCServerNetwork, socket: Socket): Promise {
- const userId = getUserIdFromSocketId(network, socket.id)!
+ const peerID = socket.id as PeerID
// logger.info('Got heartbeat from user ' + userId + ' at ' + Date.now())
- if (network.peers.has(userId)) network.peers.get(userId)!.lastSeenTs = Date.now()
+ if (network.peers.has(peerID)) network.peers.get(peerID)!.lastSeenTs = Date.now()
}
export async function handleDisconnect(network: SocketWebRTCServerNetwork, socket: Socket): Promise {
- const userId = getUserIdFromSocketId(network, socket.id) as UserId
- const disconnectedClient = network.peers.get(userId)
- if (!disconnectedClient)
- return logger.warn(
- 'Disconnecting client ' + userId + ' was undefined, probably already handled from JoinWorld handshake.'
- )
+ const userId = getUserIdFromPeerID(network, socket.id) as UserId
+ const peerID = socket.id as PeerID
+ const disconnectedClient = network.peers.get(peerID)
+ if (!disconnectedClient) return logger.warn(`Tried to handle disconnect for peer ${peerID} but was not foudn`)
// On local, new connections can come in before the old sockets are disconnected.
// The new connection will overwrite the socketID for the user's client.
// This will only clear transports if the client's socketId matches the socket that's disconnecting.
- if (socket.id === disconnectedClient?.socketId) {
+ if (socket.id === disconnectedClient?.peerID) {
const state = getState(WorldState)
const userName = state.userNames[userId].value
@@ -432,9 +417,9 @@ export async function handleDisconnect(network: SocketWebRTCServerNetwork, socke
}
)
- NetworkPeerFunctions.destroyPeer(network, userId, Engine.instance.currentWorld)
+ NetworkPeerFunctions.destroyPeer(network, peerID, Engine.instance.currentWorld)
network.updatePeers()
- logger.info('Disconnecting clients for user ' + userId)
+ logger.info(`Disconnecting user ${userId} on socket ${peerID}`)
if (disconnectedClient?.instanceRecvTransport) disconnectedClient.instanceRecvTransport.close()
if (disconnectedClient?.instanceSendTransport) disconnectedClient.instanceSendTransport.close()
if (disconnectedClient?.channelRecvTransport) disconnectedClient.channelRecvTransport.close()
@@ -450,11 +435,11 @@ export async function handleLeaveWorld(
data,
callback
): Promise {
- const userId = getUserIdFromSocketId(network, socket.id)!
+ const peerID = socket.id as PeerID
for (const [, transport] of Object.entries(network.mediasoupTransports))
- if ((transport as any).appData.peerId === userId) closeTransport(network, transport)
- if (network.peers.has(userId)) {
- NetworkPeerFunctions.destroyPeer(network, userId, Engine.instance.currentWorld)
+ if (transport.appData.peerID === peerID) closeTransport(network, transport)
+ if (network.peers.has(peerID)) {
+ NetworkPeerFunctions.destroyPeer(network, peerID, Engine.instance.currentWorld)
network.updatePeers()
}
if (callback !== undefined) callback({})
diff --git a/packages/instanceserver/src/SocketFunctions.ts b/packages/instanceserver/src/SocketFunctions.ts
index 8784254506..af1a2a1e58 100644
--- a/packages/instanceserver/src/SocketFunctions.ts
+++ b/packages/instanceserver/src/SocketFunctions.ts
@@ -10,7 +10,6 @@ import { WebRtcTransportParams } from '@xrengine/server-core/src/types/WebRtcTra
import {
authorizeUserToJoinServer,
- disconnectClientIfConnected,
handleConnectingPeer,
handleDisconnect,
handleHeartbeat,
@@ -24,9 +23,13 @@ import {
handleWebRtcCloseProducer,
handleWebRtcConsumerSetLayers,
handleWebRtcInitializeRouter,
+ handleWebRtcPauseConsumer,
+ handleWebRtcPauseProducer,
handleWebRtcProduceData,
handleWebRtcReceiveTrack,
handleWebRtcRequestCurrentProducers,
+ handleWebRtcResumeConsumer,
+ handleWebRtcResumeProducer,
handleWebRtcSendTrack,
handleWebRtcTransportClose,
handleWebRtcTransportConnect,
@@ -112,8 +115,6 @@ export const setupSocketFunctions = (network: SocketWebRTCServerNetwork, socket:
* @todo Check if the user is banned
*/
- disconnectClientIfConnected(network, socket, userId)
-
await handleConnectingPeer(network, socket, user)
} catch (e) {
authTask.status = 'fail'
@@ -164,6 +165,14 @@ export const setupSocketFunctions = (network: SocketWebRTCServerNetwork, socket:
handleWebRtcReceiveTrack(network, socket, data, callback)
)
+ socket.on(MessageTypes.WebRTCPauseConsumer.toString(), async (data, callback) =>
+ handleWebRtcPauseConsumer(network, socket, data, callback)
+ )
+
+ socket.on(MessageTypes.WebRTCResumeConsumer.toString(), async (data, callback) =>
+ handleWebRtcResumeConsumer(network, socket, data, callback)
+ )
+
socket.on(MessageTypes.WebRTCCloseConsumer.toString(), async (data, callback) =>
handleWebRtcCloseConsumer(network, socket, data, callback)
)
@@ -172,6 +181,14 @@ export const setupSocketFunctions = (network: SocketWebRTCServerNetwork, socket:
handleWebRtcConsumerSetLayers(network, socket, data, callback)
)
+ socket.on(MessageTypes.WebRTCResumeProducer.toString(), async (data, callback) =>
+ handleWebRtcResumeProducer(network, socket, data, callback)
+ )
+
+ socket.on(MessageTypes.WebRTCPauseProducer.toString(), async (data, callback) =>
+ handleWebRtcPauseProducer(network, socket, data, callback)
+ )
+
socket.on(MessageTypes.WebRTCRequestCurrentProducers.toString(), async (data, callback) =>
handleWebRtcRequestCurrentProducers(network, socket, data, callback)
)
diff --git a/packages/instanceserver/src/SocketWebRTCServerNetwork.ts b/packages/instanceserver/src/SocketWebRTCServerNetwork.ts
index 96874271bd..6c92bd1ba8 100755
--- a/packages/instanceserver/src/SocketWebRTCServerNetwork.ts
+++ b/packages/instanceserver/src/SocketWebRTCServerNetwork.ts
@@ -1,6 +1,16 @@
-import { Consumer, DataProducer, Producer, Router, Transport, WebRtcTransport, Worker } from 'mediasoup/node/lib/types'
+import {
+ Consumer,
+ DataProducer,
+ Producer,
+ Router,
+ Transport,
+ TransportInternal,
+ WebRtcTransport,
+ Worker
+} from 'mediasoup/node/lib/types'
import { MediaStreamAppData } from '@xrengine/common/src/interfaces/MediaStreamConstants'
+import { PeersUpdateType } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { Engine } from '@xrengine/engine/src/ecs/classes/Engine'
import { Network } from '@xrengine/engine/src/networking/classes/Network'
@@ -16,7 +26,12 @@ import { startWebRTC } from './WebRTCFunctions'
const logger = multiLogger.child({ component: 'instanceserver:webrtc:network' })
-export type WebRTCTransportExtension = WebRtcTransport & { appData: MediaStreamAppData }
+export type WebRTCTransportExtension = Omit & {
+ appData: MediaStreamAppData
+ internal: TransportInternal
+}
+export type ProducerExtension = Omit & { appData: MediaStreamAppData }
+export type ConsumerExtension = Omit & { appData: MediaStreamAppData }
export class SocketWebRTCServerNetwork extends Network {
workers: Worker[] = []
@@ -31,8 +46,8 @@ export class SocketWebRTCServerNetwork extends Network {
mediasoupTransports: WebRTCTransportExtension[] = []
transportsConnectPending: Promise[] = []
- producers = [] as Producer[]
- consumers = [] as Consumer[]
+ producers = [] as ProducerExtension[]
+ consumers = [] as ConsumerExtension[]
constructor(hostId: UserId, topic: Topic, app: Application) {
super(hostId, topic)
@@ -44,11 +59,13 @@ export class SocketWebRTCServerNetwork extends Network {
const userNames = getState(WorldState).userNames
const peers = Array.from(this.peers.values()).map((peer) => {
return {
- userId: peer.userId,
- index: peer.index,
+ peerID: peer.peerID,
+ peerIndex: peer.peerIndex,
+ userID: peer.userId,
+ userIndex: peer.userIndex,
name: userNames[peer.userId].value
}
- })
+ }) as Array
if (peers.length)
for (const [socketID, socket] of this.app.io.of('/').sockets)
socket.emit(MessageTypes.UpdatePeers.toString(), peers)
@@ -60,8 +77,6 @@ export class SocketWebRTCServerNetwork extends Network {
const actions = [...Engine.instance.store.actions.outgoing[this.topic].queue]
if (!actions.length) return
- const userIdMap = {} as { [socketId: string]: UserId }
- for (const [id, client] of this.peers) userIdMap[client.socketId!] = id
const outgoing = Engine.instance.store.actions.outgoing
for (const [socketID, socket] of this.app.io.of('/').sockets) {
@@ -73,7 +88,7 @@ export class SocketWebRTCServerNetwork extends Network {
outgoing[this.topic].queue.splice(idx, 1)
}
if (!action.$to) continue
- const toUserId = userIdMap[socketID]
+ const toUserId = this.peers.get(socketID)?.userId
if (action.$to === 'all' || (action.$to === 'others' && toUserId !== action.$from) || action.$to === toUserId) {
arr.push(action)
}
diff --git a/packages/instanceserver/src/WebRTCFunctions.ts b/packages/instanceserver/src/WebRTCFunctions.ts
index 41b99cba1f..137f9a1e91 100755
--- a/packages/instanceserver/src/WebRTCFunctions.ts
+++ b/packages/instanceserver/src/WebRTCFunctions.ts
@@ -1,31 +1,36 @@
import { createWorker } from 'mediasoup'
import {
- Consumer,
DataConsumer,
DataConsumerOptions,
DataProducer,
DataProducerOptions,
- Producer,
+ MediaKind,
Router,
RtpCodecCapability,
+ RtpParameters,
Transport,
WebRtcTransport
} from 'mediasoup/node/lib/types'
import os from 'os'
-import SocketIO from 'socket.io'
+import { Socket } from 'socket.io'
+import { MediaStreamAppData, MediaTagType } from '@xrengine/common/src/interfaces/MediaStreamConstants'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { Engine } from '@xrengine/engine/src/ecs/classes/Engine'
import { MessageTypes } from '@xrengine/engine/src/networking/enums/MessageTypes'
-import { MediaNetworkAction } from '@xrengine/engine/src/networking/functions/MediaNetworkAction'
-import { NetworkPeerFunctions } from '@xrengine/engine/src/networking/functions/NetworkPeerFunctions'
import config from '@xrengine/server-core/src/appconfig'
import { localConfig, sctpParameters } from '@xrengine/server-core/src/config'
import multiLogger from '@xrengine/server-core/src/ServerLogger'
import { WebRtcTransportParams } from '@xrengine/server-core/src/types/WebRtcTransportParams'
-import { getUserIdFromSocketId } from './NetworkFunctions'
-import { SocketWebRTCServerNetwork, WebRTCTransportExtension } from './SocketWebRTCServerNetwork'
+import { getUserIdFromPeerID } from './NetworkFunctions'
+import {
+ ConsumerExtension,
+ ProducerExtension,
+ SocketWebRTCServerNetwork,
+ WebRTCTransportExtension
+} from './SocketWebRTCServerNetwork'
const logger = multiLogger.child({ component: 'instanceserver:webrtc' })
@@ -69,11 +74,12 @@ export async function startWebRTC(network: SocketWebRTCServerNetwork): Promise
- async (producer: Producer): Promise => {
- const userId = getUserIdFromSocketId(network, socket.id)!
- const selfClient = network.peers.get(userId)!
- if (selfClient?.socketId != null) {
+ (network: SocketWebRTCServerNetwork, socket: Socket, channelType: string, channelId?: string) =>
+ async (producer: ProducerExtension): Promise => {
+ const peerID = socket.id as PeerID
+ const userId = getUserIdFromPeerID(network, socket.id)!
+ const selfClient = network.peers.get(peerID)!
+ if (selfClient?.peerID != null) {
for (const [, client] of network.peers) {
logger.info(`Sending media for "${userId}".`)
client?.media &&
@@ -85,7 +91,7 @@ export const sendNewProducer =
)
selfClient.socket!.emit(
MessageTypes.WebRTCCreateProducer.toString(),
- client.userId,
+ peerID,
subName,
producer.id,
channelType,
@@ -98,73 +104,76 @@ export const sendNewProducer =
// Create consumer for each client!
export const sendCurrentProducers = async (
network: SocketWebRTCServerNetwork,
- socket: SocketIO.Socket,
+ socket: Socket,
userIds: string[],
channelType: string,
channelId?: string
): Promise => {
- const selfUserId = getUserIdFromSocketId(network, socket.id)!
- const selfClient = network.peers.get(selfUserId)!
- if (selfClient?.socketId) {
- for (const [userId, client] of network.peers) {
+ const peerID = socket.id as PeerID
+ const selfUserId = getUserIdFromPeerID(network, socket.id)!
+ const selfClient = network.peers.get(peerID)!
+ if (selfClient?.peerID) {
+ for (const [peerID, client] of network.peers) {
if (
!(
- userId === selfUserId ||
- (userIds.length > 0 && !userIds.includes(userId)) ||
+ client.userId === selfUserId ||
+ (userIds.length > 0 && !userIds.includes(client.userId)) ||
!client.media ||
- !client.socketId
+ !client.peerID
)
- ) {
- const cachedActions = NetworkPeerFunctions.getCachedActionsForUser(network, userId)
- if (cachedActions.find((action) => MediaNetworkAction.pauseProducer.matches.test(action) && !action.pause))
- Object.entries(client.media).map(([subName, subValue]) => {
- if ((subValue as any).channelType === channelType && (subValue as any).channelId === channelId)
- selfClient.socket!.emit(
- MessageTypes.WebRTCCreateProducer.toString(),
- client.userId,
- subName,
- (subValue as any).producerId,
- channelType,
- channelId
- )
- })
- }
+ )
+ Object.entries(client.media).map(([subName, subValue]) => {
+ if (
+ (subValue as any).channelType === channelType &&
+ (subValue as any).channelId === channelId &&
+ !(subValue as any).paused
+ )
+ selfClient.socket!.emit(
+ MessageTypes.WebRTCCreateProducer.toString(),
+ peerID,
+ subName,
+ (subValue as any).producerId,
+ channelType,
+ channelId
+ )
+ })
}
}
}
export const handleConsumeDataEvent =
- (network: SocketWebRTCServerNetwork, socket: SocketIO.Socket) =>
+ (network: SocketWebRTCServerNetwork, socket: Socket) =>
async (dataProducer: DataProducer): Promise => {
- const userId = getUserIdFromSocketId(network, socket.id)!
+ const peerID = socket.id as PeerID
+ const userId = getUserIdFromPeerID(network, socket.id)!
logger.info('Data Consumer being created on server by client: ' + userId)
- if (!network.peers.has(userId)) {
+ if (!network.peers.has(peerID)) {
return false
}
- const newTransport: Transport = network.peers.get(userId)!.instanceRecvTransport
+ const newTransport: Transport = network.peers.get(peerID)!.instanceRecvTransport
const outgoingDataProducer = network.outgoingDataProducer
if (newTransport) {
try {
const dataConsumer = await newTransport.consumeData({
dataProducerId: outgoingDataProducer.id,
- appData: { peerId: userId, transportId: newTransport.id }
+ appData: { peerID, transportId: newTransport.id }
})
dataConsumer.on('dataproducerclose', () => {
dataConsumer.close()
- if (network.peers.has(userId)) network.peers.get(userId)!.dataConsumers!.delete(dataProducer.id)
+ if (network.peers.has(peerID)) network.peers.get(peerID)!.dataConsumers!.delete(dataProducer.id)
})
logger.info('Setting data consumer to room state.')
- if (!network.peers.has(userId)) {
+ if (!network.peers.has(peerID)) {
return socket.emit(MessageTypes.WebRTCConsumeData.toString(), { error: 'client no longer exists' })
}
- network.peers.get(userId)!.dataConsumers!.set(dataProducer.id, dataConsumer)
+ network.peers.get(peerID)!.dataConsumers!.set(dataProducer.id, dataConsumer)
- const dataProducerOut = network.peers.get(userId)!.dataProducers!.get('instance')
+ const dataProducerOut = network.peers.get(peerID)!.dataProducers!.get('instance')
// Data consumers are all consuming the single producer that outputs from the server's message queue
socket.emit(MessageTypes.WebRTCConsumeData.toString(), {
@@ -185,7 +194,10 @@ export const handleConsumeDataEvent =
}
}
-export async function closeTransport(network: SocketWebRTCServerNetwork, transport: WebRtcTransport): Promise {
+export async function closeTransport(
+ network: SocketWebRTCServerNetwork,
+ transport: WebRTCTransportExtension
+): Promise {
logger.info(`Closing transport id "${transport.id}", appData: %o`, transport.appData)
// our producer and consumer event handlers will take care of
// calling closeProducer() and closeConsumer() on all the producers
@@ -196,20 +208,21 @@ export async function closeTransport(network: SocketWebRTCServerNetwork, transpo
}
}
-export async function closeProducer(network: SocketWebRTCServerNetwork, producer: Producer): Promise {
+export async function closeProducer(network: SocketWebRTCServerNetwork, producer: ProducerExtension): Promise {
logger.info(`Closing producer id "${producer.id}", appData: %o`, producer.appData)
await producer.close()
network.producers = network.producers.filter((p) => p.id !== producer.id)
+ const appData = producer.appData as MediaStreamAppData
- if (network.peers.has(producer.appData.peerId as UserId)) {
- delete network.peers.get(producer.appData.peerId as UserId)!.media![producer.appData.mediaTag as any]
+ if (network.peers.has(appData.peerID)) {
+ delete network.peers.get(appData.peerID)!.media![producer.appData.mediaTag]
}
}
export async function closeProducerAndAllPipeProducers(
network: SocketWebRTCServerNetwork,
- producer: Producer
+ producer: ProducerExtension
): Promise {
logger.info(`Closing producer id "${producer?.id}" and all pipe producers, appData: %o`, producer?.appData)
if (producer != null) {
@@ -226,11 +239,11 @@ export async function closeProducerAndAllPipeProducers(
)
// remove this track's info from our roomState...mediaTag bookkeeping
- delete network.peers.get(producer.appData.peerId as UserId)?.media![producer.appData.mediaTag as any]
+ delete network.peers.get(producer.appData.peerID)?.media![producer.appData.mediaTag]
}
}
-export async function closeConsumer(network: SocketWebRTCServerNetwork, consumer: Consumer): Promise {
+export async function closeConsumer(network: SocketWebRTCServerNetwork, consumer: ConsumerExtension): Promise {
await consumer.close()
network.consumers = network.consumers.filter((c) => c.id !== consumer.id)
@@ -241,12 +254,12 @@ export async function closeConsumer(network: SocketWebRTCServerNetwork, consumer
}
}
- delete network.peers.get(consumer.appData.peerId as UserId)?.consumerLayers![consumer.id]
+ delete network.peers.get(consumer.appData.peerID)?.consumerLayers![consumer.id]
}
export async function createWebRtcTransport(
network: SocketWebRTCServerNetwork,
- { peerId, direction, sctpCapabilities, channelType, channelId }: WebRtcTransportParams
+ { peerID, direction, sctpCapabilities, channelType, channelId }: WebRtcTransportParams
): Promise {
const { listenIps, initialAvailableOutgoingBitrate } = localConfig.mediasoup.webRtcTransport
const mediaCodecs = localConfig.mediasoup.router.mediaCodecs as RtpCodecCapability[]
@@ -284,26 +297,26 @@ export async function createWebRtcTransport(
enableSctp: true,
numSctpStreams: sctpCapabilities.numStreams,
initialAvailableOutgoingBitrate: initialAvailableOutgoingBitrate,
- appData: { peerId, channelType, channelId, clientDirection: direction }
+ appData: { peerID, channelType, channelId, clientDirection: direction }
})
}
export async function createInternalDataConsumer(
network: SocketWebRTCServerNetwork,
dataProducer: DataProducer,
- userId: string
+ peerID: PeerID
): Promise {
try {
const consumer = await network.outgoingDataTransport.consumeData({
dataProducerId: dataProducer.id,
- appData: { peerId: userId, transportId: network.outgoingDataTransport.id },
+ appData: { peerID, transportId: network.outgoingDataTransport.id },
maxPacketLifeTime: dataProducer.sctpStreamParameters!.maxPacketLifeTime,
maxRetransmits: dataProducer.sctpStreamParameters!.maxRetransmits,
ordered: false
})
consumer.on('message', (message) => {
network.incomingMessageQueueUnreliable.add(toArrayBuffer(message))
- network.incomingMessageQueueUnreliableIDs.add(userId)
+ network.incomingMessageQueueUnreliableIDs.add(peerID)
// forward data to clients in world immediately
// TODO: need to include the userId (or index), so consumers can validate
network.sendData(message)
@@ -317,17 +330,17 @@ export async function createInternalDataConsumer(
export async function handleWebRtcTransportCreate(
network: SocketWebRTCServerNetwork,
- socket,
+ socket: Socket,
data: WebRtcTransportParams,
callback
): Promise {
try {
- const userId = getUserIdFromSocketId(network, socket.id)!
- const { direction, peerId, sctpCapabilities, channelType, channelId } = Object.assign(data, { peerId: userId })
+ const peerID = socket.id as PeerID
+ const { direction, sctpCapabilities, channelType, channelId } = Object.assign(data)
const existingTransports = network.mediasoupTransports.filter(
(t) =>
- t.appData.peerId === peerId &&
+ t.appData.peerID === peerID &&
t.appData.direction === direction &&
(channelType === 'instance'
? t.appData.channelType === 'instance'
@@ -335,7 +348,7 @@ export async function handleWebRtcTransportCreate(
)
await Promise.all(existingTransports.map((t) => closeTransport(network, t)))
const newTransport: WebRtcTransport = await createWebRtcTransport(network, {
- peerId,
+ peerID: peerID,
direction,
sctpCapabilities,
channelType,
@@ -350,16 +363,16 @@ export async function handleWebRtcTransportCreate(
// Distinguish between send and create transport of each client w.r.t producer and consumer (data or mediastream)
if (direction === 'recv') {
- if (channelType === 'instance' && network.peers.has(userId)) {
- network.peers.get(userId)!.instanceRecvTransport = newTransport
+ if (channelType === 'instance' && network.peers.has(peerID)) {
+ network.peers.get(peerID)!.instanceRecvTransport = newTransport
} else if (channelType !== 'instance' && channelId) {
- network.peers.get(userId)!.channelRecvTransport = newTransport
+ network.peers.get(peerID)!.channelRecvTransport = newTransport
}
} else if (direction === 'send') {
- if (channelType === 'instance' && network.peers.has(userId)) {
- network.peers.get(userId)!.instanceSendTransport = newTransport
- } else if (channelType !== 'instance' && channelId && network.peers.has(userId)) {
- network.peers.get(userId)!.channelSendTransport = newTransport
+ if (channelType === 'instance' && network.peers.has(peerID)) {
+ network.peers.get(peerID)!.instanceSendTransport = newTransport
+ } else if (channelType !== 'instance' && channelId && network.peers.has(peerID)) {
+ network.peers.get(peerID)!.channelSendTransport = newTransport
}
}
@@ -395,11 +408,11 @@ export async function handleWebRtcTransportCreate(
}
newTransport.observer.on('dtlsstatechange', (dtlsState) => {
- if (dtlsState === 'closed') closeTransport(network, newTransport)
+ if (dtlsState === 'closed') closeTransport(network, newTransport as unknown as WebRTCTransportExtension)
})
// Create data consumers for other clients if the current client transport receives data producer on it
newTransport.observer.on('newdataproducer', handleConsumeDataEvent(network, socket))
- newTransport.observer.on('newproducer', sendNewProducer(network, socket, channelType, channelId))
+ newTransport.observer.on('newproducer', sendNewProducer(network, socket, channelType, channelId) as any)
// logger.log('Callback from transportCreate with options:');
// logger.log(clientTransportOptions);
callback({ transportOptions: clientTransportOptions })
@@ -411,12 +424,13 @@ export async function handleWebRtcTransportCreate(
export async function handleWebRtcProduceData(
network: SocketWebRTCServerNetwork,
- socket,
+ socket: Socket,
data,
callback
): Promise {
try {
- const userId = getUserIdFromSocketId(network, socket.id)
+ const peerID = socket.id as PeerID
+ const userId = getUserIdFromPeerID(network, socket.id)
if (!userId) {
logger.info('userId could not be found for socketId ' + socket.id)
return
@@ -427,7 +441,7 @@ export async function handleWebRtcProduceData(
return callback({ error: errorMessage })
}
- if (!network.peers.has(userId)) {
+ if (!network.peers.has(peerID)) {
const errorMessage = `Client no longer exists for userId "${userId}".`
logger.error(errorMessage)
return callback({ error: errorMessage })
@@ -440,7 +454,7 @@ export async function handleWebRtcProduceData(
label,
protocol,
sctpStreamParameters,
- appData: { ...(appData || {}), peerID: userId, transportId }
+ appData: { ...(appData || {}), peerID, transportId }
}
logger.info('Data producer params: %o', options)
if (transport) {
@@ -448,8 +462,8 @@ export async function handleWebRtcProduceData(
const dataProducer = await transport.produceData(options)
network.dataProducers.set(label, dataProducer)
logger.info(`User ${userId} producing data.`)
- if (network.peers.has(userId)) {
- network.peers.get(userId)!.dataProducers!.set(label, dataProducer)
+ if (network.peers.has(peerID)) {
+ network.peers.get(peerID)!.dataProducers!.set(label, dataProducer)
const currentRouter = network.routers.instance.find(
(router) => router.id === (transport as any)?.internal.routerId
@@ -471,15 +485,15 @@ export async function handleWebRtcProduceData(
network.dataProducers.delete(label)
logger.info("data producer's transport closed: " + dataProducer.id)
dataProducer.close()
- network.peers.get(userId)!.dataProducers!.delete(label)
+ network.peers.get(peerID)!.dataProducers!.delete(label)
})
- const internalConsumer = await createInternalDataConsumer(network, dataProducer, userId)
+ const internalConsumer = await createInternalDataConsumer(network, dataProducer, peerID)
if (internalConsumer) {
- if (!network.peers.has(userId)) {
+ if (!network.peers.has(peerID)) {
logger.error('Client no longer exists.')
return callback({ error: 'Client no longer exists.' })
}
- network.peers.get(userId)!.dataConsumers!.set(label, internalConsumer)
+ network.peers.get(peerID)!.dataConsumers!.set(label, internalConsumer)
// transport.handleConsumeDataEvent(socket);
logger.info('transport.handleConsumeDataEvent(socket)')
// Possibly do stuff with appData here
@@ -506,7 +520,7 @@ export async function handleWebRtcProduceData(
}
}
-export async function handleWebRtcTransportClose(network: SocketWebRTCServerNetwork, socket, data, callback) {
+export async function handleWebRtcTransportClose(network: SocketWebRTCServerNetwork, socket: Socket, data, callback) {
const { transportId } = data
const transport = network.mediasoupTransports[transportId]
if (transport) {
@@ -515,7 +529,7 @@ export async function handleWebRtcTransportClose(network: SocketWebRTCServerNetw
callback({ closed: true })
}
-export async function handleWebRtcTransportConnect(network: SocketWebRTCServerNetwork, socket, data, callback) {
+export async function handleWebRtcTransportConnect(network: SocketWebRTCServerNetwork, socket: Socket, data, callback) {
const { transportId, dtlsParameters } = data,
transport = network.mediasoupTransports[transportId]
if (transport) {
@@ -535,12 +549,12 @@ export async function handleWebRtcTransportConnect(network: SocketWebRTCServerNe
}
}
-export async function handleWebRtcCloseProducer(network: SocketWebRTCServerNetwork, socket, data, callback) {
+export async function handleWebRtcCloseProducer(network: SocketWebRTCServerNetwork, socket: Socket, data, callback) {
const { producerId } = data
const producer = network.producers.find((p) => p.id === producerId)!
try {
const hostClient = Array.from(network.peers.entries()).find(([, client]) => {
- return client.media && client.media![producer.appData.mediaTag as any]?.producerId === producerId
+ return client.media && client.media![producer.appData.mediaTag]?.producerId === producerId
})!
if (hostClient && hostClient[1]) hostClient[1].socket!.emit(MessageTypes.WebRTCCloseProducer.toString(), producerId)
await closeProducerAndAllPipeProducers(network, producer)
@@ -550,8 +564,22 @@ export async function handleWebRtcCloseProducer(network: SocketWebRTCServerNetwo
callback({ closed: true })
}
-export async function handleWebRtcSendTrack(network: SocketWebRTCServerNetwork, socket, data, callback) {
- const userId = getUserIdFromSocketId(network, socket.id)
+type HandleWebRtcSendTrackData = {
+ appData: MediaStreamAppData
+ transportId: any
+ kind: MediaKind
+ rtpParameters: RtpParameters
+ paused: boolean
+}
+
+export async function handleWebRtcSendTrack(
+ network: SocketWebRTCServerNetwork,
+ socket: Socket,
+ data: HandleWebRtcSendTrackData,
+ callback
+) {
+ const peerID = socket.id as PeerID
+ const userId = getUserIdFromPeerID(network, socket.id)
const { transportId, kind, rtpParameters, paused = false, appData } = data
const transport = network.mediasoupTransports[transportId]
@@ -561,15 +589,15 @@ export async function handleWebRtcSendTrack(network: SocketWebRTCServerNetwork,
}
try {
- const newProducerAppData = { ...appData, peerId: userId, transportId }
+ const newProducerAppData = { ...appData, peerID, transportId } as MediaStreamAppData
const existingProducer = await network.producers.find((producer) => producer.appData === newProducerAppData)
if (existingProducer) await closeProducer(network, existingProducer)
- const producer = await transport.produce({
+ const producer = (await transport.produce({
kind,
rtpParameters,
paused,
appData: newProducerAppData
- })
+ } as any)) as unknown as ProducerExtension
const routers = network.routers[`${appData.channelType}:${appData.channelId}`]
const currentRouter = routers.find((router) => router.id === (transport as any)?.internal.routerId)!
@@ -592,8 +620,8 @@ export async function handleWebRtcSendTrack(network: SocketWebRTCServerNetwork,
}
network.producers?.push(producer)
- if (userId && network.peers.has(userId)) {
- network.peers.get(userId)!.media![appData.mediaTag] = {
+ if (userId && network.peers.has(peerID)) {
+ network.peers.get(peerID)!.media![appData.mediaTag] = {
paused,
producerId: producer.id,
globalMute: false,
@@ -603,11 +631,11 @@ export async function handleWebRtcSendTrack(network: SocketWebRTCServerNetwork,
}
}
- for (const [clientUserId, client] of network.peers) {
- if (clientUserId !== userId && client.socket) {
+ for (const [clientPeerID, client] of network.peers) {
+ if (clientPeerID !== peerID && client.socket) {
client.socket!.emit(
MessageTypes.WebRTCCreateProducer.toString(),
- userId,
+ peerID,
appData.mediaTag,
producer.id,
appData.channelType,
@@ -622,18 +650,27 @@ export async function handleWebRtcSendTrack(network: SocketWebRTCServerNetwork,
}
}
+type HandleWebRtcReceiveTrackData = {
+ mediaPeerId: PeerID
+ mediaTag: MediaTagType
+ rtpCapabilities: any
+ channelType: string
+ channelId: string
+}
+
export async function handleWebRtcReceiveTrack(
network: SocketWebRTCServerNetwork,
- socket,
- data,
+ socket: Socket,
+ data: HandleWebRtcReceiveTrackData,
callback
): Promise {
- const userId = getUserIdFromSocketId(network, socket.id)!
+ const peerID = socket.id as PeerID
const { mediaPeerId, mediaTag, rtpCapabilities, channelType, channelId } = data
+ console.log(data, network.producers)
const producer = network.producers.find(
(p) =>
p.appData.mediaTag === mediaTag &&
- p.appData.peerId === mediaPeerId &&
+ p.appData.peerID === mediaPeerId &&
(channelType === 'instance'
? p.appData.channelType === channelType
: p.appData.channelType === channelType && p.appData.channelId === channelId)
@@ -641,31 +678,32 @@ export async function handleWebRtcReceiveTrack(
const transport = Object.values(network.mediasoupTransports).find(
(t) =>
- (t as any).appData.peerId === userId &&
- (t as any).appData.clientDirection === 'recv' &&
+ t.appData.peerID === peerID &&
+ t.appData.clientDirection === 'recv' &&
(channelType === 'instance'
- ? (t as any).appData.channelType === channelType
- : (t as any).appData.channelType === channelType && (t as any).appData.channelId === channelId) &&
- (t as any).closed === false
+ ? t.appData.channelType === channelType
+ : t.appData.channelType === channelType && t.appData.channelId === channelId) &&
+ t.closed === false
)!
// @todo: the 'any' cast here is because WebRtcTransport.internal is protected - we should see if this is the proper accessor
const router = network.routers[`${channelType}:${channelId}`].find(
- (router) => router.id === (transport as any)?.internal.routerId
+ (router) => router.id === transport?.internal.routerId
)
+ console.log(producer, router)
if (!producer || !router || !router.canConsume({ producerId: producer.id, rtpCapabilities })) {
const msg = `Client cannot consume ${mediaPeerId}:${mediaTag}, ${producer}`
- logger.error(`recv-track: ${userId} ${msg}`)
+ logger.error(`recv-track: ${peerID} ${msg}`)
return callback({ error: msg })
}
if (transport) {
try {
- const consumer = await (transport as any).consume({
+ const consumer = (await transport.consume({
producerId: producer.id,
rtpCapabilities,
paused: true, // see note above about always starting paused
- appData: { peerId: userId, mediaPeerId, mediaTag, channelType: channelType, channelId: channelId }
- })
+ appData: { peerID, mediaPeerId, mediaTag, channelType: channelType, channelId: channelId }
+ })) as unknown as ConsumerExtension
// we need both 'transportclose' and 'producerclose' event handlers,
// to make sure we close and clean up consumers in all circumstances
@@ -681,8 +719,8 @@ export async function handleWebRtcReceiveTrack(
// stick this consumer in our list of consumers to keep track of
network.consumers.push(consumer)
- if (network.peers.has(userId)) {
- network.peers.get(userId)!.consumerLayers![consumer.id] = {
+ if (network.peers.has(peerID)) {
+ network.peers.get(peerID)!.consumerLayers![consumer.id] = {
currentLayer: null,
clientSelectedLayer: null
}
@@ -690,8 +728,8 @@ export async function handleWebRtcReceiveTrack(
// update above data structure when layer changes.
consumer.on('layerschange', (layers) => {
- if (network.peers.has(userId) && network.peers.get(userId)!.consumerLayers![consumer.id]) {
- network.peers.get(userId)!.consumerLayers![consumer.id].currentLayer = layers && layers.spatialLayer
+ if (network.peers.has(peerID) && network.peers.get(peerID)!.consumerLayers![consumer.id]) {
+ network.peers.get(peerID)!.consumerLayers![consumer.id].currentLayer = layers && layers.spatialLayer
}
})
@@ -714,11 +752,47 @@ export async function handleWebRtcReceiveTrack(
}
}
-export async function handleWebRtcCloseConsumer(
+export async function handleWebRtcPauseConsumer(
+ network: SocketWebRTCServerNetwork,
+ socket,
+ data,
+ callback
+): Promise {
+ const { consumerId } = data
+ const consumer = network.consumers.find((c) => c.id === consumerId)
+ if (consumer) {
+ network.mediasoupOperationQueue.add({
+ object: consumer,
+ action: 'pause'
+ })
+ socket.emit(MessageTypes.WebRTCPauseConsumer.toString(), consumer.id)
+ }
+ callback({ paused: true })
+}
+
+export async function handleWebRtcResumeConsumer(
network: SocketWebRTCServerNetwork,
socket,
data,
callback
+): Promise {
+ const { consumerId } = data
+ const consumer = network.consumers.find((c) => c.id === consumerId)
+ if (consumer) {
+ network.mediasoupOperationQueue.add({
+ object: consumer,
+ action: 'resume'
+ })
+ socket.emit(MessageTypes.WebRTCResumeConsumer.toString(), consumer.id)
+ }
+ callback({ resumed: true })
+}
+
+export async function handleWebRtcCloseConsumer(
+ network: SocketWebRTCServerNetwork,
+ socket: Socket,
+ data,
+ callback
): Promise {
const { consumerId } = data
const consumer = network.consumers.find((c) => c.id === consumerId)
@@ -730,7 +804,7 @@ export async function handleWebRtcCloseConsumer(
export async function handleWebRtcConsumerSetLayers(
network: SocketWebRTCServerNetwork,
- socket,
+ socket: Socket,
data,
callback
): Promise {
@@ -741,11 +815,74 @@ export async function handleWebRtcConsumerSetLayers(
callback({ layersSet: true })
}
-export async function handleWebRtcRequestCurrentProducers(
+export async function handleWebRtcResumeProducer(
network: SocketWebRTCServerNetwork,
socket,
data,
callback
+): Promise {
+ const peerID = socket.id as PeerID
+ const { producerId } = data
+ const producer = network.producers.find((p) => p.id === producerId)
+ logger.info('resume-producer: %o', producer?.appData)
+ if (producer) {
+ network.mediasoupOperationQueue.add({
+ object: producer,
+ action: 'resume'
+ })
+ // await producer.resume();
+ if (peerID && network.peers.has(peerID)) {
+ network.peers.get(peerID)!.media![producer.appData.mediaTag as any].paused = false
+ network.peers.get(peerID)!.media![producer.appData.mediaTag as any].globalMute = false
+ // const hostClient = Array.from(network.peers.entries()).find(([, client]) => {
+ // return client.media && client.media![producer.appData.mediaTag as any]?.producerId === producerId
+ // })!
+ // if (hostClient && hostClient[1])
+ // hostClient[1].socket!.emit(MessageTypes.WebRTCResumeProducer.toString(), producer.id)
+ }
+ for (const [, client] of network.peers) {
+ if (client && client.socket) client.socket.emit(MessageTypes.WebRTCResumeProducer.toString(), producer.id)
+ }
+ }
+ callback({ resumed: true })
+}
+
+export async function handleWebRtcPauseProducer(
+ network: SocketWebRTCServerNetwork,
+ socket,
+ data,
+ callback
+): Promise {
+ const peerID = socket.id as PeerID
+ const { producerId, globalMute } = data
+ const producer = network.producers.find((p) => p.id === producerId)
+ if (producer) {
+ network.mediasoupOperationQueue.add({
+ object: producer,
+ action: 'pause'
+ })
+ if (peerID && network.peers.has(peerID) && network.peers.get(peerID)!.media![producer.appData.mediaTag as any]) {
+ network.peers.get(peerID)!.media![producer.appData.mediaTag as any].paused = true
+ network.peers.get(peerID)!.media![producer.appData.mediaTag as any].globalMute = globalMute || false
+ // const hostClient = Array.from(network.peers.entries()).find(([, client]) => {
+ // return client.media && client.media![producer.appData.mediaTag as any]?.producerId === producerId
+ // })!
+ // if (hostClient && hostClient[1])
+ // hostClient[1].socket!.emit(MessageTypes.WebRTCPauseProducer.toString(), producer.id, true)
+ }
+ for (const [, client] of network.peers) {
+ if (client && client.socket)
+ client.socket.emit(MessageTypes.WebRTCPauseProducer.toString(), producer.id, globalMute || false)
+ }
+ }
+ callback({ paused: true })
+}
+
+export async function handleWebRtcRequestCurrentProducers(
+ network: SocketWebRTCServerNetwork,
+ socket: Socket,
+ data,
+ callback
): Promise {
const { userIds, channelType, channelId } = data
await sendCurrentProducers(network, socket, userIds || [], channelType, channelId)
@@ -754,7 +891,7 @@ export async function handleWebRtcRequestCurrentProducers(
export async function handleWebRtcInitializeRouter(
network: SocketWebRTCServerNetwork,
- socket,
+ socket: Socket,
data,
callback
): Promise {
diff --git a/packages/instanceserver/src/channels.ts b/packages/instanceserver/src/channels.ts
index 6284439dfd..a126caf4cd 100755
--- a/packages/instanceserver/src/channels.ts
+++ b/packages/instanceserver/src/channels.ts
@@ -7,6 +7,7 @@ import { decode } from 'jsonwebtoken'
import { IdentityProviderInterface } from '@xrengine/common/src/dbmodels/IdentityProvider'
import { Channel } from '@xrengine/common/src/interfaces/Channel'
import { Instance } from '@xrengine/common/src/interfaces/Instance'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
import { UserInterface } from '@xrengine/common/src/interfaces/User'
import { UserId } from '@xrengine/common/src/interfaces/UserId'
import { Engine } from '@xrengine/engine/src/ecs/classes/Engine'
@@ -227,7 +228,7 @@ const loadEngine = async (app: Application, sceneId: string) => {
const topic = app.isChannelInstance ? NetworkTopics.media : NetworkTopics.world
const network = new SocketWebRTCServerNetwork(hostId, topic, app)
- app.transport = network
+ app.network = network
const initPromise = network.initialize()
world.networks.set(hostId, network)
@@ -266,6 +267,8 @@ const loadEngine = async (app: Application, sceneId: string) => {
NetworkPeerFunctions.createPeer(
network,
+ 'server' as PeerID,
+ network.peerIndexCount++,
hostId,
network.userIndexCount++,
'server-' + hostId,
@@ -401,8 +404,10 @@ const shutdownServer = async (app: Application, instanceId: string) => {
// todo: this could be more elegant
const getActiveUsersCount = (app: Application, userToIgnore: UserInterface) => {
- const activeClients = app.transport.peers
- const activeUsers = [...activeClients].filter(([id]) => id !== Engine.instance.userId && id !== userToIgnore.id)
+ const activeClients = app.network.peers
+ const activeUsers = [...activeClients].filter(
+ ([id, client]) => client.userId !== Engine.instance.userId && client.userId !== userToIgnore.id
+ )
return activeUsers.length
}
@@ -482,7 +487,7 @@ const handleUserDisconnect = async (
// check if there are no peers connected (1 being the server,
// 0 if the serer was just starting when someone connected and disconnected)
- if (app.transport.peers.size <= 1) {
+ if (app.network.peers.size <= 1) {
logger.info('Shutting down instance server as there are no users present.')
await shutdownServer(app, instanceId)
}
diff --git a/packages/instanceserver/src/start.ts b/packages/instanceserver/src/start.ts
index c3ab4daf15..2f957da0a8 100755
--- a/packages/instanceserver/src/start.ts
+++ b/packages/instanceserver/src/start.ts
@@ -48,7 +48,7 @@ const onSocket = async (app: Application, socket: Socket) => {
if (!getEngineState().joinedWorld.value) {
await new Promise((resolve) => matchActionOnce(EngineActions.joinedWorld.matches, resolve))
}
- setupSocketFunctions(app.transport, socket)
+ setupSocketFunctions(app.network, socket)
}
export const instanceServerPipe = pipe(
diff --git a/packages/server-core/declarations.ts b/packages/server-core/declarations.ts
index d35465a3ed..af9ba08672 100755
--- a/packages/server-core/declarations.ts
+++ b/packages/server-core/declarations.ts
@@ -27,7 +27,7 @@ export type Application = ExpressFeathers & {
agonesSDK: any
sync: any
io: any //SocketIO.Server
- transport: SocketWebRTCServerNetwork
+ network: SocketWebRTCServerNetwork
seed: () => Application // function
serverMode: ServerTypeMode
diff --git a/packages/server-core/src/types/WebRtcTransportParams.ts b/packages/server-core/src/types/WebRtcTransportParams.ts
index 6ec8007a0c..879892d04e 100755
--- a/packages/server-core/src/types/WebRtcTransportParams.ts
+++ b/packages/server-core/src/types/WebRtcTransportParams.ts
@@ -1,4 +1,5 @@
import { ChannelType } from '@xrengine/common/src/interfaces/Channel'
+import { PeerID } from '@xrengine/common/src/interfaces/PeerID'
// Types borrowed from Mediasoup
@@ -16,7 +17,7 @@ type SctpCapabilities = {
numStreams: NumSctpStreams
}
export type WebRtcTransportParams = {
- peerId?: string
+ peerID?: PeerID
direction: 'recv' | 'send'
sctpCapabilities: SctpCapabilities
channelType: ChannelType