Skip to content

Commit

Permalink
fix: distinguish the 2 phases of joining the call: connect to the cal…
Browse files Browse the repository at this point in the history
…l AND wait for participants list

Signed-off-by: DorraJaouad <dorra.jaoued7@gmail.com>
  • Loading branch information
DorraJaouad committed Nov 2, 2024
1 parent 4b30af0 commit 855da4f
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 25 deletions.
18 changes: 16 additions & 2 deletions src/components/CallView/shared/EmptyCallView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import IconPhone from 'vue-material-design-icons/Phone.vue'
import { t } from '@nextcloud/l10n'

import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'

import { CONVERSATION, PARTICIPANT } from '../../../constants.js'
import { copyConversationLinkToClipboard } from '../../../utils/handleUrl.ts'
Expand All @@ -43,6 +44,7 @@ export default {

components: {
NcButton,
NcLoadingIcon,
IconAccountMultiple,
IconLinkVariant,
IconPhone,
Expand All @@ -66,11 +68,14 @@ export default {
},

computed: {

token() {
return this.$store.getters.getToken()
},

isConnecting() {
return this.$store.getters.isConnecting(this.token)
},

conversation() {
return this.$store.getters.conversation(this.token)
},
Expand Down Expand Up @@ -116,14 +121,19 @@ export default {
},

emptyCallViewIcon() {
if (this.isPhoneConversation) {
if (this.isConnecting) {
return NcLoadingIcon
} else if (this.isPhoneConversation) {
return IconPhone
} else {
return this.isPublicConversation ? IconLinkVariant : IconAccountMultiple
}
},

title() {
if (this.isConnecting) {
return t('spreed', 'Connecting …')
}
if (this.isPhoneConversation) {
return t('spreed', 'Calling …')
}
Expand All @@ -134,6 +144,10 @@ export default {
},

message() {
if (this.isConnecting) {
return ''
}

if (this.isPasswordRequestConversation || this.isFileConversation) {
return ''
}
Expand Down
10 changes: 5 additions & 5 deletions src/components/TopBar/CallButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
id="call_button"
:title="startCallTitle"
:aria-label="startCallLabel"
:disabled="startCallButtonDisabled || loading || isConnecting"
:disabled="startCallButtonDisabled || loading || isJoiningCall"
:type="startCallButtonType"
@click="handleClick">
<template #icon>
<NcLoadingIcon v-if="isConnecting || loading" />
<NcLoadingIcon v-if="isJoiningCall || loading" />
<IconPhoneDial v-else-if="isPhoneRoom" :size="20" />
<IconPhoneOutline v-else-if="silentCall" :size="20" />
<IconPhone v-else :size="20" />
Expand Down Expand Up @@ -276,7 +276,7 @@ export default {
return t('spreed', 'Join call')
}

if (this.isConnecting) {
if (this.isJoiningCall) {
return t('spreed', 'Connecting...')
}

Expand Down Expand Up @@ -347,8 +347,8 @@ export default {
return this.$store.getters.isInLobby
},

isConnecting() {
return this.$store.getters.isConnecting(this.token)
isJoiningCall() {
return this.$store.getters.isJoiningCall(this.token)
},

connectionFailed() {
Expand Down
107 changes: 91 additions & 16 deletions src/store/participantsStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ const state = {
},
inCall: {
},
joiningCall: {
},
connecting: {
},
connectionFailed: {
Expand Down Expand Up @@ -93,6 +95,10 @@ const getters = {
return !!(state.inCall[token] && Object.keys(state.inCall[token]).length > 0)
},

isJoiningCall: (state) => (token) => {
return !!(state.joiningCall[token] && Object.keys(state.joiningCall[token]).length > 0)
},

isConnecting: (state) => (token) => {
return !!(state.connecting[token] && Object.keys(state.connecting[token]).length > 0)
},
Expand Down Expand Up @@ -354,6 +360,19 @@ const mutations = {
Vue.delete(state.connectionFailed, token)
},

joiningCall(state, { token, sessionId, flags }) {
if (!state.joiningCall[token]) {
Vue.set(state.joiningCall, token, {})
}
Vue.set(state.joiningCall[token], sessionId, flags)
},

finishedJoiningCall(state, { token, sessionId }) {
if (state.joiningCall[token] && state.joiningCall[token][sessionId]) {
Vue.delete(state.joiningCall[token], sessionId)
}
},

connecting(state, { token, sessionId, flags }) {
if (!state.connecting[token]) {
Vue.set(state.connecting, token, {})
Expand Down Expand Up @@ -811,10 +830,24 @@ const actions = {
return false
},

async joinCall({ commit, getters }, { token, participantIdentifier, flags, silent, recordingConsent }) {
commit('connecting', { token, sessionId: participantIdentifier.sessionId, flags })
async joinCall({ commit, getters, state }, { token, participantIdentifier, flags, silent, recordingConsent }) {
// SUMMARY: join call process
// There are 2 main steps to join a call:
// 1. Join the call (signaling-join-call)
// 2A. Wait for the users list (signaling-users-in-room) INTERNAL server event
// 2B. Wait for the users list (signaling-users-changed) EXTERNAL server event
// In case of failure, we receive a signaling-join-call-failed event

if (!participantIdentifier?.sessionId) {
// Exception 1: We may receive the users list before the signaling-join-call event
// In this case, we use the isParticipantsListReceived flag to handle this case

// Exception 2: We may receive the users list in a second event of signaling-users-changed or signaling-users-in-room
// In this case, we always check if the list is the updated one (it has the current participant in the call)

const { sessionId } = participantIdentifier
let isParticipantsListReceived = null

if (!sessionId) {
console.error('Trying to join call without sessionId')
return
}
Expand All @@ -825,20 +858,62 @@ const actions = {
return
}

// Preparing the event listener for the signaling-join-call event
EventBus.once('signaling-join-call', () => {
commit('setInCall', {
token,
sessionId: participantIdentifier.sessionId,
flags,
})
commit('finishedConnecting', { token, sessionId: participantIdentifier.sessionId })
})
commit('joiningCall', { token, sessionId, flags })

// Preparing the event listener for the signaling-join-call-failed event
EventBus.once('signaling-join-call-failed', () => {
commit('finishedConnecting', { token, sessionId: participantIdentifier.sessionId })
})
const handleJoinCall = () => {
commit('setInCall', { token, sessionId, flags })
commit('finishedJoiningCall', { token, sessionId })

if (isParticipantsListReceived) {
isParticipantsListReceived = null
commit('finishedConnecting', { token, sessionId })
} else {
commit('connecting', { token, sessionId, flags })
}
}

const handleJoinCallFailed = () => {
commit('finishedJoiningCall', { token, sessionId })
isParticipantsListReceived = null
}

const handleUsersInRoom = (payload) => {
const participant = payload[0].find(p => p.sessionId === sessionId)
if (participant && participant.inCall !== PARTICIPANT.CALL_FLAG.DISCONNECTED) {
if (state.joiningCall[token]?.[sessionId]) {
isParticipantsListReceived = true
commit('connecting', { token, sessionId, flags })
return
}
commit('finishedConnecting', { token, sessionId })
EventBus.off('signaling-users-in-room', handleUsersInRoom)
}
}

const handleUsersChanged = (payload) => {
const participant = payload[0].find(p => p.nextcloudSessionId === sessionId)
if (participant && participant.inCall !== PARTICIPANT.CALL_FLAG.DISCONNECTED) {
if (state.joiningCall[token]?.[sessionId]) {
isParticipantsListReceived = true
commit('connecting', { token, sessionId, flags })
return
}
commit('finishedConnecting', { token, sessionId })
EventBus.off('signaling-users-changed', handleUsersChanged)
}
}

// Fallback in case we never receive the users list after joining the call
setTimeout(() => {
// If, by accident, we never receive a users list, just switch to
// "Waiting for others to join the call …" after some seconds.
commit('finishedConnecting', { token, sessionId })
}, 10000)

EventBus.once('signaling-join-call', handleJoinCall)
EventBus.once('signaling-join-call-failed', handleJoinCallFailed)
EventBus.on('signaling-users-in-room', handleUsersInRoom)
EventBus.on('signaling-users-changed', handleUsersChanged)

try {
const actualFlags = await joinCall(token, flags, silent, recordingConsent)
Expand Down
11 changes: 9 additions & 2 deletions src/store/participantsStore.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,8 @@ describe('participantsStore', () => {
test('joins call', async () => {
// Assert
expect(joinCall).toHaveBeenCalledWith(TOKEN, flags, false, false)
// Mock the signaling event
EventBus.emit('signaling-join-call')
expect(store.getters.isInCall(TOKEN)).toBe(true)
expect(store.getters.isConnecting(TOKEN)).toBe(true)
expect(store.getters.participantsList(TOKEN)).toStrictEqual([
Expand All @@ -587,7 +589,12 @@ describe('participantsStore', () => {
])

// Finished connecting to the call
EventBus.emit('signaling-users-in-room')
EventBus.emit('signaling-users-in-room', [[{
attendeeId: 1,
sessionId: 'session-id-1',
inCall: actualFlags,
participantType: PARTICIPANT.TYPE.USER,
}]])

expect(store.getters.isInCall(TOKEN)).toBe(true)
expect(store.getters.isConnecting(TOKEN)).toBe(false)
Expand Down Expand Up @@ -906,7 +913,7 @@ describe('participantsStore', () => {
flags,
silent: false,
})

EventBus.emit('signaling-join-call')
expect(store.getters.isInCall(TOKEN)).toBe(true)

leaveConversation.mockResolvedValue()
Expand Down

0 comments on commit 855da4f

Please sign in to comment.