Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 10 additions & 31 deletions src/components/RightSidebar/Participants/Participant.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@

<!-- Phone participant dial action -->
<div v-if="isInCall && canBeModerated && isPhoneActor"
id="participantNavigationId"
:id="participantNavigationId"
class="participant-row__dial-actions">
<NcButton v-if="!participant.inCall"
type="success"
Expand Down Expand Up @@ -464,8 +464,6 @@ export default {
isUserNameTooltipVisible: false,
isStatusTooltipVisible: false,
permissionsEditor: false,
speakingInterval: null,
timeSpeaking: null,
disabled: false,
}
},
Expand Down Expand Up @@ -706,7 +704,7 @@ export default {
},

participantSpeakingInformation() {
return this.$store.getters.getParticipantSpeakingInformation(this.token, this.attendeeId)
return this.$store.getters.getParticipantSpeakingInformation(this.attendeeId)
},

isParticipantSpeaking() {
Expand Down Expand Up @@ -864,22 +862,17 @@ export default {
}
return ''
},
},
watch: {
isParticipantSpeaking(speaking) {
if (speaking) {
if (!this.speakingInterval) {
this.speakingInterval = setInterval(this.computeElapsedTime, 1000)
}
} else {
if (speaking === undefined) {
this.timeSpeaking = 0
}
clearInterval(this.speakingInterval)
this.speakingInterval = null

timeSpeaking() {
if (!this.participantSpeakingInformation || this.isParticipantSpeaking === undefined) {
return 0
}

return this.participantSpeakingInformation.totalCountedTime
},
},

watch: {
phoneCallStatus(value) {
if (!value || !(value === 'ringing' || value === 'accepted')) {
this.disabled = false
Expand Down Expand Up @@ -1012,20 +1005,6 @@ export default {
}
},

computeElapsedTime() {
if (!this.participantSpeakingInformation) {
return null
}

const { speaking, lastTimestamp, totalCountedTime } = this.participantSpeakingInformation

if (!speaking) {
this.timeSpeaking = totalCountedTime
} else {
this.timeSpeaking = Date.now() - lastTimestamp + totalCountedTime
}
},

async dialOutPhoneNumber() {
try {
this.disabled = true
Expand Down
105 changes: 75 additions & 30 deletions src/store/participantsStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const state = {
* when quickly switching to a new conversation.
*/
cancelFetchParticipants: null,
speakingInterval: null,
}

const getters = {
Expand Down Expand Up @@ -178,16 +179,11 @@ const getters = {
* Gets the speaking information for the participant.
*
* @param {object} state - the state object.
* param {string} token - the conversation token.
* param {number} attendeeId - attendee's ID for the participant in conversation.
* @return {object|undefined}
*/
getParticipantSpeakingInformation: (state) => (token, attendeeId) => {
if (!state.speaking[token]) {
return undefined
}

return state.speaking[token][attendeeId]
getParticipantSpeakingInformation: (state) => (attendeeId) => {
return state.speaking[attendeeId]
},

/**
Expand Down Expand Up @@ -410,42 +406,70 @@ const mutations = {
*
* @param {object} state - current store state.
* @param {object} data - the wrapping object.
* @param {string} data.token - the conversation token participant is speaking in.
* @param {string} data.attendeeId - the attendee ID of the participant in conversation.
* @param {boolean} data.speaking - whether the participant is speaking or not
*/
setSpeaking(state, { token, attendeeId, speaking }) {
setSpeaking(state, { attendeeId, speaking }) {
// create a dummy object for current call
if (!state.speaking[token]) {
Vue.set(state.speaking, token, {})
if (!state.speaking[attendeeId]) {
Vue.set(state.speaking, attendeeId, { speaking, lastTimestamp: Date.now(), totalCountedTime: 0 })
}
if (!state.speaking[token][attendeeId]) {
Vue.set(state.speaking[token], attendeeId, { speaking: null, lastTimestamp: 0, totalCountedTime: 0 })
state.speaking[attendeeId].speaking = speaking
},

/**
* Tracks the interval id to update speaking information for a current call.
*
* @param {object} state - current store state.
* @param {number} interval - interval id.
*/
setSpeakingInterval(state, interval) {
Vue.set(state, 'speakingInterval', interval)
},

/**
* Update speaking information for a participant.
*
* @param {object} state - current store state.
* @param {object} data - the wrapping object.
* @param {string} data.attendeeId - the attendee ID of the participant in conversation.
* @param {boolean} data.speaking - whether the participant is speaking or not
*/
updateTimeSpeaking(state, { attendeeId, speaking }) {
if (!state.speaking[attendeeId]) {
return
}

const currentTimestamp = Date.now()
const currentSpeakingState = state.speaking[token][attendeeId].speaking

if (!currentSpeakingState && speaking) {
state.speaking[token][attendeeId].speaking = true
state.speaking[token][attendeeId].lastTimestamp = currentTimestamp
} else if (currentSpeakingState && !speaking) {
// when speaking has stopped, update the total talking time
state.speaking[token][attendeeId].speaking = false
state.speaking[token][attendeeId].totalCountedTime += (currentTimestamp - state.speaking[token][attendeeId].lastTimestamp)
const currentSpeakingState = state.speaking[attendeeId].speaking

if (!currentSpeakingState && !speaking) {
// false -> false, no updates
return
}

if (currentSpeakingState) {
// true -> false / true -> true, participant is still speaking or finished to speak, update total time
state.speaking[attendeeId].totalCountedTime += (currentTimestamp - state.speaking[attendeeId].lastTimestamp)
}

// false -> true / true -> false / true -> true, update timestamp of last check / signal
state.speaking[attendeeId].lastTimestamp = currentTimestamp
},

/**
* Purge the speaking information for recent call when local participant leaves call
* (including cases when the call ends for everyone).
*
* @param {object} state - current store state.
* @param {object} data - the wrapping object.
* @param {string} data.token - the conversation token.
*/
purgeSpeakingStore(state, { token }) {
Vue.delete(state.speaking, token)
purgeSpeakingStore(state) {
Vue.set(state, 'speaking', {})

if (state.speakingInterval) {
clearInterval(state.speakingInterval)
Vue.set(state, 'speakingInterval', null)
}
},

/**
Expand Down Expand Up @@ -1036,12 +1060,33 @@ const actions = {
}
},

setSpeaking(context, { token, attendeeId, speaking }) {
context.commit('setSpeaking', { token, attendeeId, speaking })
setSpeaking(context, { attendeeId, speaking }) {
// We should update time before speaking state, to be able to check previous state
context.commit('updateTimeSpeaking', { attendeeId, speaking })
context.commit('setSpeaking', { attendeeId, speaking })

if (!context.state.speakingInterval && speaking) {
const interval = setInterval(() => {
context.dispatch('updateIntervalTimeSpeaking')
}, 1000)
context.commit('setSpeakingInterval', interval)
}
},

updateIntervalTimeSpeaking(context) {
if (!context.state.speaking || !context.state.speakingInterval) {
return
}

for (const attendeeId in context.state.speaking) {
if (context.state.speaking[attendeeId].speaking) {
context.commit('updateTimeSpeaking', { attendeeId, speaking: true })
}
}
},

purgeSpeakingStore(context, { token }) {
context.commit('purgeSpeakingStore', { token })
purgeSpeakingStore(context) {
context.commit('purgeSpeakingStore')
},

processDialOutAnswer(context, { callid }) {
Expand Down
5 changes: 1 addition & 4 deletions src/utils/webrtc/SpeakingStatusHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default class SpeakingStatusHandler {
callParticipantModel.off('change:stoppedSpeaking', this.#handleSpeakingBound)
})

this.#store.dispatch('purgeSpeakingStore', { token: this.#store.getters.getToken() })
this.#store.dispatch('purgeSpeakingStore')
}

/**
Expand Down Expand Up @@ -114,7 +114,6 @@ export default class SpeakingStatusHandler {
*/
#handleLocalSpeaking(localMediaModel, speaking) {
this.#store.dispatch('setSpeaking', {
token: this.#store.getters.getToken(),
attendeeId: this.#store.getters.getAttendeeId(),
speaking,
})
Expand All @@ -126,7 +125,6 @@ export default class SpeakingStatusHandler {
*/
#handleLocalPeerId() {
this.#store.dispatch('setSpeaking', {
token: this.#store.getters.getToken(),
attendeeId: this.#store.getters.getAttendeeId(),
speaking: this.#localMediaModel.attributes.speaking,
})
Expand All @@ -149,7 +147,6 @@ export default class SpeakingStatusHandler {
}

this.#store.dispatch('setSpeaking', {
token: this.#store.getters.getToken(),
attendeeId,
speaking,
})
Expand Down