twilio-video.js
twilio-video.js copied to clipboard
"hearbeat" messages are still sent even after disconnecting LocalParticipant from the room
- [x] I have verified that the issue occurs with the latest twilio-video.js release and is not marked as a known issue in the CHANGELOG.md.
- [x] I reviewed the Common Issues and open GitHub issues and verified that this report represents a potentially new issue.
- [x] I verified that the Quickstart application works in my environment.
- [x] I am not sharing any Personally Identifiable Information (PII) or sensitive account information (API keys, credentials, etc.) when reporting this issue.
Code to reproduce the issue:
class Room extends Emitter {
public static attachTracks (tracks: Track[], container: any) {
tracks.forEach(track => {
if (track.kind !== 'data' && track._isEnabled) {
container && container.appendChild(track.attach())
}
})
}
public nativeRoom = null
private dataTrack = null
private container = null
constructor ({ room, dataTrack }) {
super()
this.nativeRoom = room
this.dataTrack = dataTrack
// TODO: need to develop
console.log('roomJoined', room)
// When a Participant adds a Track, attach it to the DOM.
room.on('trackSubscribed', (...p) => this.emit('trackSubscribed', ...p))
// When a Participant removes a Track, detach it from the DOM.
room.on('trackUnsubscribed', (track: Track, publication: TrackPublication, participant: Participant) => {
this.detachTracks([track])
this.emit('trackUnsubscribed', track, publication, participant)
})
// When a RemoteTrack was enabled by a RemoteParticipant in the Room
this.nativeRoom.on('trackEnabled', (publication: TrackPublication, participant: Participant) => {
Room.attachTracks([publication.track], this.container)
this.emit('trackEnabled', publication, participant)
})
// When a RemoteTrack was disabled by a RemoteParticipant in the Room
room.on('trackDisabled', (publication: TrackPublication, participant: Participant) => {
this.detachTracks([publication.track])
this.emit('trackDisabled', publication, participant)
})
// When a RemoteParticipant left the Room, detach RemoteParticipant's Tracks.
room.on('participantDisconnected', (participant: Participant) => {
this.detachParticipantTracks(participant)
this.emit('participantDisconnected', participant)
})
// Once the LocalParticipant left the room, detach the Tracks
// of all Participants, including that of the LocalParticipant.
room.on('disconnected', (room: TwilioVideoRoom, error: any) => {
room.localParticipant.tracks.forEach(publication => {
publication.track.stop()
})
this.detachParticipantTracks(room.localParticipant)
this.emit('disconnected', room, error)
})
this.nativeRoom.participants.forEach(participant => {
participant.on('trackSubscribed', (track: Track, publication: TrackPublication) => {
this.onTrackSubscribed(track)
})
})
}
public detachTracks (tracks: Track[]) {
tracks.forEach(track => {
if (track.kind === 'audio' || track.kind === 'video') {
track.detach().forEach(detachedElement => {
detachedElement.remove()
})
}
})
}
// Attach the Participant's Tracks to the DOM.
public attachParticipantTracks (participant: Participant, container: any) {
const tracks = this.getSubscribedParticipantTracks(participant)
Room.attachTracks(tracks, container)
}
// Detach the Participant's Tracks from the DOM.
public detachParticipantTracks (participant: Participant) {
const tracks = this.getSubscribedParticipantTracks(participant)
this.detachTracks(tracks)
}
private getSubscribedParticipantTracks = (participant: Participant) => {
return Array.from<TrackPublication>(participant.tracks.values())
.reduce((acc, publication) => {
if (publication.track) {
acc.push(publication.track)
}
return acc
}, [])
}
public onEnableMicrophone () {
this.nativeRoom.localParticipant.audioTracks.forEach(audioTrackPublication => {
audioTrackPublication.track.enable()
})
}
public onDisableMicrophone () {
this.nativeRoom.localParticipant.audioTracks.forEach(audioTrackPublication => {
audioTrackPublication.track.disable()
})
}
public onStart (container, { activeCall }) {
return new Promise(resolve => {
this.container = container
let remoteAddedTracksCount = 0
const onTrackSubscribed = (track, participantRole) => {
this.onTrackSubscribed(track)
this.attachTrack(container, track)
if (participantRole === USER_ROLES.fieldTehnical) {
remoteAddedTracksCount += 1
if (remoteAddedTracksCount >= REMOTE_TRACKS_COUNT) {
resolve()
}
}
}
this.nativeRoom.participants.forEach(participant => {
const participantRole = participant.identity.split(':')[1]
this.attachParticipantTracks(participant, container)
// if refresh page
participant.on('trackSubscribed', (track: Track, publication: TrackPublication) => {
onTrackSubscribed(track, participantRole)
})
// if user moved to another page
if (participantRole === USER_ROLES.fieldTehnical) {
const remoteTrackPublications = Array.from<TrackPublication>(participant.tracks.values())
.filter(publication => !!publication.track)
if (remoteTrackPublications.length >= REMOTE_TRACKS_COUNT) {
resolve()
}
}
})
this.nativeRoom.on('participantConnected', participant => {
const participantRole = participant.identity.split(':')[1]
participant.tracks.forEach(publication => {
// If the TrackPublication is already subscribed to, then attach the Track to the DOM.
if (publication.isSubscribed) {
onTrackSubscribed(publication.track, participantRole)
}
})
// Once the Track is subscribed to, attach the Track to the DOM.
participant.on('trackSubscribed', track => {
onTrackSubscribed(track, participantRole)
})
})
this.nativeRoom.localParticipant.audioTracks.forEach(audioTrackPublication => {
if (_get(activeCall, ['properties', 'isMutedMicrophone'])) {
audioTrackPublication.track.disable()
} else {
audioTrackPublication.track.enable()
}
})
})
}
private onTrackSubscribed = track => {
if (track.kind === TRACK_TYPES.data) {
track.on('message', event => {
const data = JSON.parse(event)
const { id, meta: { role } } = data
if (deliveryManager.hasSending(id)) {
deliveryManager.get(id).resolve()
deliveryManager.remove(id)
} else {
this.emit('message', data)
if (role !== USER_ROLES.wearableDeviceUser) {
return
}
this.onSendToDataTrack({ id: data.id, meta: { role: USER_ROLES.computerUser }, type: MESSAGE_TYPES.received })
}
})
return
}
}
private attachTrack = (container, track) => {
if (track._isEnabled) {
container.appendChild(track.attach())
}
}
public onHold () {
this.onDisableMicrophone()
this.nativeRoom.participants.values().forEach(participant => {
this.detachTracks(participant.tracks)
})
this.container = null
}
public onEnd () {
this.nativeRoom.disconnect()
}
private onSendToDataTrack (data) {
this.dataTrack.send(JSON.stringify(data))
}
public onSendMessage (data) {
const messageId = data.id || uuid()
this.onSendToDataTrack({ ...data, id: messageId })
const checkDelivery = (resolve, reject) => {
deliveryManager.send({ id: messageId, isDelivered: false, resolve, reject })
setTimeout(() => {
if (deliveryManager.get(messageId)) {
deliveryManager.remove(messageId)
}
return reject(`Message with id:${messageId} was not sent over the DataTrack`)
}, TIME_TO_MESSAGE_DELIVERY)
}
return new Promise(checkDelivery)
}
public onSendDeliveredMessage (data) {
const messageId = data.id || uuid()
return new Promise((resolve, reject) => {
deliveryManager.send({ id: messageId, isDelivered: true, resolve, reject })
resolve()
})
}
}
export default Room
Expected behavior:
LocalParticipant should be disconnected from the Group/P2P room and shouldn't get any "hearbeat" messages.
Actual behavior:
Incoming and outgoing "hearbeat" messages are still sent even after disconnecting LocalParticipant from the Group/P2P room.
Logs: frontend-app-twilio-logs
Software versions:
- [x] Browser(s): 87.0.4280.141
- [x] Operating System: Windows
- [x] twilio-video.js: 2.7.2
- [x] Third-party libraries (e.g., Angular, React, etc.): ReactJS
Hey @DmitryMorozov228 , thank you for reporting! Do you have an example code on how you would use the class you provided?
Hi @charliesantos , The samples of code here:
1.
export const removeCallHandlersById = ({ id }) => (dispatch, getState) => {
const room = roomManager.getRoomById(id)
// disconnect from the Twilio room
room && room.onEnd()
dispatch(onRemoveCalls([id]))
roomManager.removeRoom({ id })
keepAliveManager.remove({ id })
}
2.
export const onToggleMicrophone = () => (dispatch, getState) => {
const state = getState()
const { id, properties }: any = getActiveCall(state)
const isMutedMicrophone = !properties.isMutedMicrophone
const room = roomManager.getRoomById(id)
if (isMutedMicrophone) {
// mute Local Participant microphone
room.onDisableMicrophone()
} else {
// unmute Local Participant microphone
room.onEnableMicrophone()
}
dispatch(updateCallById({
id,
properties: {
isMutedMicrophone,
},
}))
}
3.
// Send data to the DataTrack
room.onSendMessage(data)
.then(() => {
dispatch(markMessageAsSent({ id, messageId: data.id }))
})
.catch(() => {
dispatch(markMessageAsNotSent({ id, messageId: data.id }))
})
Hey @DmitryMorozov228 thanks for the example code. We are tracking this internally now (JSDK-3125) and will investigate for a fix.
Hi @charliesantos , Any updates on this?