I am building a Java Springboot and react platform with twilio-video 2.27.0 - to allow to logged in members to video chat to each other.
I am getting what could look like a malfunction if its not fixed - that when leaving the room - the web cam light remains on -- I've tried to follow various references but nothing seems to work - the web cam light only stops on a hard refresh.
Twilio Video :How to destroy video room when user who created it leaves it?
Twilio room does not disconnect / Webcam LED remains on
track.stop is not turning the camera off anymore
I've tried stopping tracks, unpublishing, disable etc..
Also I get an error if a user clicks off the page without "leaving the room" and then they try and join the conference again - its like the room becomes unusable and a conference between them cant come back - the only way is to leave the room and re-create it again
import React, { Component } from 'react';
import Video from 'twilio-video';
import Button from '@mui/material/Button';
//import TextField from '@mui/material/TextField';
import { getToken, getUserDetails } from '../_globals/UserFunctions/UserFunctions';
class VideoChat extends Component {
constructor(props) {
super(props);
this.state = {
identity: "",
roomId: null,
username: '',
localMediaAvailable: true,
hasJoinedRoom: false,
remoteMediaAvailable: false,
remoteTracks: []
};
this.joinRoom = this.joinRoom.bind(this);
this.leaveRoom = this.leaveRoom.bind(this);
this.handleUsernameChange = this.handleUsernameChange.bind(this);
this.handleRoomChange = this.handleRoomChange.bind(this);
this.localMediaRef = React.createRef();
this.remoteMediaRef = React.createRef();
}
handleUsernameChange(event) {
this.setState({ username: event.target.value });
}
handleRoomChange(event) {
this.setState({ roomId: event.target.value });
}
startComponent() {
const userId = getUserDetails().id;
fetch(`http://localhost:9000/api/twilio/token?userId=${userId}`, {
headers: {
'Authorization': 'Bearer ' + getToken()
}})
.then(res => res.json())
.then(data => {
Video.connect(data.token, { name: this.state.roomId }).then(this.joinRoom, error => console.error(`Unable to connect to Room: ${error.message}`));
this.setState({ identity: userId });
});
}
componentWillUnmount() {
if (this.state.room) {
this.state.room.disconnect();
}
}
joinRoom(room) {
this.setState({
room: room,
localMediaAvailable: true,
hasJoinedRoom: true
});
Video.createLocalVideoTrack().then(track => {
const localMedia = this.localMediaRef.current;
localMedia.appendChild(track.attach());
localMedia.querySelector("div.label").innerHTML = room.localParticipant.identity;
});
console.log(`Joined as ${this.state.identity}`);
room.on('participantConnected', participant => {
console.log(`Participant ${participant.identity} has joined the room.`);
this.setState({ remoteMediaAvailable: true });
participant.tracks.forEach(publication => {
if (publication.isSubscribed) {
this.trackSubscribed(publication.track);
}
});
participant.on('trackSubscribed', track => {
this.trackSubscribed(track);
});
});
room.on('participantDisconnected', participant => {
console.log(`Participant ${participant.identity} has left the room.`);
this.setState({ hasJoinedRoom: false, localMediaAvailable: false, remoteMediaAvailable: false, remoteTracks: [] });
const remoteMedia = this.remoteMediaRef.current;
remoteMedia.innerHTML = ''; // Clear the container when a participant leaves
console.log("---------participantDisconnected", participant)
});
room.participants.forEach(participant => {
console.log(`Participant ${participant.identity} is connected to the room.`);
this.setState({ remoteMediaAvailable: true });
participant.tracks.forEach(publication => {
if (publication.isSubscribed) {
this.trackSubscribed(publication.track);
}
});
participant.on('trackSubscribed', track => {
// this.trackSubscribed(track);
const remoteMedia = this.remoteMediaRef.current;
remoteMedia.appendChild(track.attach());
remoteMedia.querySelector("div.label").innerHTML = this.getRemoteLabel(room);
});
});
room.on('disconnected', () => {
console.log(`Disconnected from Room ${room.name}`);
this.setState({ hasJoinedRoom: false, localMediaAvailable: false, remoteMediaAvailable: false, remoteTracks: [] });
//room.localParticipant.stop()
console.log("---------participantDisconnected", room.localParticipant)
// Detach the local media elements
//room.localParticipant.tracks.forEach(publication => {
// const attachedElements = publication.track.detach();
// attachedElements.forEach(element => element.remove());
//});
room.localParticipant.tracks.forEach(function(track) {
// track.stop();
// track.detach();
});
//localTrack.mediaStreamTrack.stop()
room.localParticipant.tracks.forEach((track) => {
console.log("---track", track.track)
room.localParticipant.unpublishTrack(track.track);
track.track.stop();
});
/*
room.localParticipant.tracks.forEach(publication => {
publication.track.stop();
const attachedElements = publication.track.detach();
console.log("unsubscribed from: " + publication.track)
attachedElements.forEach(element => element.remove());
});
*/
room.localParticipant.tracks.forEach(track => {
console.log("xxxxxx track", track)
//track.track.stop();
//track.mediaStreamTrack.stop()
})
room.localParticipant.videoTracks.forEach(video => {
console.log("xxxxxx video", video)
//const trackConst = [video][0].track;
//trackConst.stop();
//trackConst.detach().forEach(element => element.remove());
//room.localParticipant.unpublishTrack(trackConst);
});
//room.participants.forEach(participantDisconnected);
// room.localParticipant.tracks.forEach((publication: Video.LocalTrackPublication) => {
// console.log("---------publication", publication)
// publication.unpublish();
//if (publication.track.kind !== 'data') trackUnsubscribed(publication.track);
// });
});
}
getRemoteLabel(room) {
let identity = "";
for (let [key] of room.participants) {
identity = room.participants.get(key).identity;
}
return identity;
}
trackSubscribed(track) {
// Check if an HTML element for this track and participant already exists
const existingMediaElement = document.getElementById(`${track.kind}-${track.sid}`);
if (existingMediaElement) {
existingMediaElement.appendChild(track.attach());
} else {
// Create a new HTML element for the new media track
const mediaElement = document.createElement('div');
mediaElement.id = `${track.kind}-${track.sid}`;
mediaElement.appendChild(track.attach());
// Append the new element to the remote-media-div
const remoteMedia = this.remoteMediaRef.current;
remoteMedia.appendChild(mediaElement);
remoteMedia.querySelector("div.label").innerHTML = this.getRemoteLabel(this.state.room);
// Add the new track to the remoteTracks array in the state
this.setState({
remoteTracks: [...this.state.remoteTracks, track]
});
}
}
leaveRoom() {
const userId = getUserDetails().id;
const url = `http://localhost:9000/api/twilio/room?userId=${userId}`;
fetch(url, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${getToken()}`
}
})
.then(res => res.json())
.then(() => {
this.state.room.disconnect();
this.setState({
hasJoinedRoom: false,
localMediaAvailable: false,
remoteMediaAvailable: false,
remoteTracks: []
});
});
}
render() {
return (
<div>
{/*
<TextField id="username" label="Username" value={this.state.username} onChange={this.handleUsernameChange}/>
<TextField id="roomId" label="roomId" value={this.state.roomId} onChange={this.handleRoomChange}/>
*/}
<Button
variant={"contained"}
color={"primary"}
onClick={() => this.startComponent()}>
Join Room
</Button>
{this.state.hasJoinedRoom ? (
<div>
<Button
variant={"contained"}
color={"primary"}
onClick={this.leaveRoom}>
Leave Room
</Button>
<div className="feed feed1" id="remote-media-div" ref={this.remoteMediaRef}><div className="label"></div></div>
{this.state.localMediaAvailable ? (
<div className="feed feed2" id="local-media-div" ref={this.localMediaRef}><div className="label"></div></div>
) : (
<p>Local media not available.</p>
)}
</div>
) : (
<p>Joining room...</p>
)}
</div>
);
}
}
export default VideoChat;