Skip to content

Commit

Permalink
Merge pull request #11782 from nextcloud/fix/11501/keep-talking-time
Browse files Browse the repository at this point in the history
fix(participants): move interval update of talking time counter to the store
  • Loading branch information
Antreesy authored Mar 14, 2024
2 parents d206d55 + 708d4f8 commit 4eb698f
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 65 deletions.
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

0 comments on commit 4eb698f

Please sign in to comment.