How to get Twilio Video API working in VueJS - vue.js

I have followed through the following post on how to set up Twilio video API with javascript: https://www.twilio.com/docs/video/tutorials/get-started-with-twilio-video-node-express-server
This works fine but I am now running into problems trying to convert this code to work with VueJS. Everything seems to run and I see the HTML appended for the audio and video, however no video element appears on the page. How do I fix this
This is the code I have written/adapted
<script setup>
import {ref} from "vue";
const isFormVisible = ref(true);
const roomName = ref("");
const participants = ref([]);
import Twilio, { connect, createLocalTracks, createLocalVideoTrack } from 'twilio-video'
const startRoom = async (event) => {
console.log('started');
isFormVisible.value = false;
const response = await fetch("http://localhost:3000/join-room", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ roomName: roomName.value }),
});
const { token } = await response.json();
console.log(token);
// join the video room with the token
const room = await joinVideoRoom(roomName.value, token);
// render the local and remote participants' video and audio tracks
handleConnectedParticipant(room.localParticipant);
room.participants.forEach(handleConnectedParticipant);
room.on("participantConnected", handleConnectedParticipant);
room.on("participantDisconnected", handleDisconnectedParticipant);
window.addEventListener("pagehide", () => room.disconnect());
window.addEventListener("beforeunload", () => room.disconnect());
}
const handleDisconnectedParticipant = (participant) => {
// stop listening for this participant
participant.removeAllListeners();
// remove this participant's div from the page
//const participantDiv = document.getElementById(participant.identity);
//participantDiv.remove();
};
const handleConnectedParticipant = (participant) => {
// create a div for this participant's tracks
participants.value.push(participant);
// iterate through the participant's published tracks and
// call `handleTrackPublication` on them
participant.tracks.forEach((trackPublication) => {
handleTrackPublication(trackPublication, participant);
});
// listen for any new track publications
participant.on("trackPublished", handleTrackPublication);
};
const handleTrackPublication = (trackPublication, participant) => {
function displayTrack(track) {
// track.attach creates an HTMLVideoElement or HTMLAudioElement
// (depending on the type of track) and adds the video or audio stream
if(!participant.track) {
participant.track = track.attach().outerHTML;
}
else {
participant.track += track.attach().outerHTML;
}
console.log(track.attach().outerHTML);
console.log(track.attach());
}
// check if the trackPublication contains a `track` attribute. If it does,
// we are subscribed to this track. If not, we are not subscribed.
if (trackPublication.track) {
displayTrack(trackPublication.track);
}
// listen for any new subscriptions to this track publication
trackPublication.on("subscribed", displayTrack);
};
const joinVideoRoom = async (roomName, token) => {
// join the video room with the Access Token and the given room name
let connectOptions = {
name: roomName,
// logLevel: 'debug',
audio: true,
video: { width: 400 }
};
const room = await Twilio.connect(token, connectOptions);
return room;
};
</script>

Related

API resolved without sending a response - Next.js

I've this code to get nearby places and nearby beaches from a point, with Google maps. This is called from a Next.js component, via the useSWR hook.
All the data is returned correctly, but before first Axios call (const fetchNearbyPlaces = async (urlWithToken = null) => {...), I'm receiving this error in the console:
API resolved without sending a response for /api/google/places/33.807501/-78.70039, this may result in stalled requests.
I can't figure out what the error is, although there may be several because I'm a novice. I appreciate any suggestion.
const axios = require("axios");
const GetNearbyPlaces = async (req, res) => {
const {
latitude,
longitude,
} = req.query;
const radius = 50000;
const types = [
"airport",
"tourist_attraction",
"amusement_park",
"aquarium",
"art_gallery",
"bar",
"museum",
"night_club",
"cafe",
"restaurant",
"shopping_mall",
"store",
"spa",
];
function checkFunc(arr, val) {
return arr.some(arrVal => val === arrVal);
}
const url = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${latitude}%2C${longitude}&radius=${radius}&key=${process.env.CW_GOOGLE_MAPS_API_KEY}`;
const beachesUrl = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${latitude}%2C${longitude}&radius=${radius}&type=natural_feature&key=${process.env.CW_GOOGLE_MAPS_API_KEY}`;
try {
let results = [];
let beaches = [];
const fetchNearbyBeaches = async (urlWithToken = null) => {
await axios.get(urlWithToken ? urlWithToken : beachesUrl).then(data => {
beaches = [...beaches, ...data.data.results];
if (data?.data?.next_page_token) {
const newUrl = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?key=${process.env.CW_GOOGLE_MAPS_API_KEY}&pagetoken=${data.data.next_page_token}`;
setTimeout(() => {
fetchNearbyBeaches(newUrl);
}, 2000);
} else {
beaches.length > 5 && beaches.splice(5);
results.length > 5 && results.splice(5);
const finalResults = [...beaches, ...results];
finalResults.length > 10 && finalResults.splice(10);
return res.status(200).json({
data: {
results: finalResults,
},
success: true,
});
}
});
};
const fetchNearbyPlaces = async (urlWithToken = null) => {
await axios.get(urlWithToken ? urlWithToken : url).then(data => {
results = [...results, ...data.data.results];
if (data?.data?.next_page_token) {
const newUrl = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?key=${process.env.CW_GOOGLE_MAPS_API_KEY}&pagetoken=${data.data.next_page_token}`;
setTimeout(() => {
fetchNearbyPlaces(newUrl);
}, 2000);
} else {
const dirtyResultsWithDuplicates = [];
results.map(result => {
return types.map(type => {
if (checkFunc(result.types, type) && !result.types.includes("lodging")) {
dirtyResultsWithDuplicates.push(result);
}
});
});
const set = new Set(dirtyResultsWithDuplicates);
const filtered = Array.from(set);
results = filtered.length > 10 ? filtered.splice(10) : filtered;
return fetchNearbyBeaches();
}
});
};
fetchNearbyPlaces();
} catch (err) {
res.status(500).json({
message: err.message,
statusCode: 500,
});
}
};
export default GetNearbyPlaces;
The problem is with the backend application not the frontend component.
Nextjs expects a response to have been sent when the api handler function exits. If for example you have a databaseCall.then(sendResponse) in your api handler function what happens is that the handler function exits before the database returns.
Now this is not a problem if the database does return after that and sends the response, but it is if for example the database has an error. Because the handler function exits without a response already being sent Nextjs can't be sure that at that point there isn't a stalled request.
One way to fix this is by await-ing the db call(or whatever other async function you call) thereby preventing the handler function from exiting before some kind of response has been send.
The solution was added this object to mi API code.
export const config = {
api: {
externalResolver: true,
},
};
Documentation: https://nextjs.org/docs/api-routes/request-helpers

How to get total member count of any Discord server?

I'm trying to build a scraping script to get a bunch of Discord server's total members. I actually did that with Puppeteer like below but I think my IP address has been banned because I'm getting "Invite Invalid" error from Discord even though invite links are working.
My question is that does Discord have APIs to get any server's total member count? Or is there any 3rd party library for that purpose? Or any other method?
const puppeteer = require('puppeteer')
const discordMembers = async ({ server, browser }) => {
if (!server) return
let totalMembers
const page = await browser.newPage()
try {
await page.goto(`https://discord.com/invite/${server}`, {
timeout: 3000
})
const selector = '.pill-qMtBTq'
await page.waitForSelector(selector, {
timeout: 3000
})
const totalMembersContent = await page.evaluate(selector => {
return document.querySelectorAll(selector)[1].textContent
}, selector)
if (totalMembersContent) {
totalMembers = totalMembersContent
.replace(/ Members/, '')
.replace(/,/g, '')
totalMembers = parseInt(totalMembers)
}
} catch (err) {
console.log(err.message)
}
await page.close()
if (totalMembers) return totalMembers
}
const asyncForEach = async (array, callback) => {
for (let i = 0; i < array.length; i++) {
await callback(array[i], i, array)
}
}
const run = async () => {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox']
})
const servers = ['tQp4pSE', '3P5K3dzgdB']
await asyncForEach(servers, async server => {
const members = await discordMembers({ server, browser })
console.log({ server, members })
// result
// { server: 'tQp4pSE', members: 57600 }
// { server: '3P5K3dzgdB', members: 159106 }
})
await browser.close()
}
run()
Update: Mar 22, 2022
Thanks for #Vaviloff's answer we can actually access Discord's private APIs but the problem is it's only accessible over browser. I'm getting Request failed with status code 400 issue from Axios. Is it a CORS issue? How do we get the results in a Node.js app?
const axios = require('axios')
const discordMembers = async ({ server }) => {
try {
const apiResult = await axios({
data: {},
method: 'get',
url: `https://discord.com/api/v9/invites/${server}?with_counts=true&with_expiration=true`
})
console.log(apiResult)
} catch (err) {
console.log(err)
}
}
discordMembers({ server: 'tQp4pSE' })
A lot of modern web applications have their own internal APIs. Oftentimes you can spot frontend making requests to it, by using Networking tab in Devtools (filter by Fetch/XHR type):
Such API endpoints can change any time of course, but usually the last for a long time and is a rather convenient way of scraping
Currently Discord uses this URL for basic instance description:
https://discord.com/api/v9/invites/tQp4pSE?with_counts=true&with_expiration=true
By accessing it you get the desired data:
Update
To make your code work don't send any data in the request:
const apiResult = await axios({
method: 'get',
url: `https://discord.com/api/v9/invites/${server}?with_counts=true&with_expiration=true`
})

Can't get WebRTC to work in different networks

I am trying to transfer a video streaming from one browser to another with WebRTC and socket.io. It works just fine in the same network. No image is getting through across different ones.
I use socket-io as a signal server. I register two browsers in a "room" and then start sending signals.
The code which is executed in the browser from which the streaming is sent:
function joinRoom(room) {
if (room === '') {
alert('Please type a room ID')
} else {
data = { room: room};
socket.emit('join', data);
}
}
// SOCKET EVENT CALLBACKS =====================================================
socket.on('room_created', async () => {
console.log('Socket event callback: room_created')
await setLocalStream(mediaConstraints)
socket.emit('startc', {room: roomId, clientip: clientip})
isRoomCreator = true
})
socket.on('full_room', () => {
console.log('Socket event callback: full_room')
alert('The room is full, please try another one')
})
socket.on('startc', async () => {
console.log('Socket event callback: start_call')
if (isRoomCreator) {
rtcPeerConnection = new RTCPeerConnection(iceServers)
addLocalTracks(rtcPeerConnection)
rtcPeerConnection.ontrack = setRemoteStream
rtcPeerConnection.onicecandidate = sendIceCandidate
await createOffer(rtcPeerConnection)
}
})
socket.on('offer', async (event) => {
console.log('Socket event callback: offer')
if (!isRoomCreator) {
rtcPeerConnection = new RTCPeerConnection(iceServers)
addLocalTracks(rtcPeerConnection)
rtcPeerConnection.ontrack = setRemoteStream
rtcPeerConnection.onicecandidate = sendIceCandidate
rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(event))
await createAnswer(rtcPeerConnection)
}
})
socket.on('answer', (event) => {
console.log('answer');
console.log('Socket event callback: webrtc_answer')
rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(event))
})
socket.on('webrtc_ice_candidate', (event) => {
console.log('Socket event callback: webrtc_ice_candidate')
// ICE candidate configuration.
var candidate = new RTCIceCandidate({
sdpMLineIndex: event.label,
candidate: event.candidate,
})
rtcPeerConnection.addIceCandidate(candidate)
})
The code that accepts the streamed media (vuejs):
socket.on("offer", (data) => {
this.$emit("closeWaitingToConnect");
this.createAnswer(data);
});
joinMeToRoom() {
console.log("joinToRoom: ", this.room);
this.$socket.emit("join", this.room);
}, //joinMeToRoom ()
createAnswer: function(event) {
var roomId = this.room.room;
let sessionDescription
this.peer.ontrack = this.setRemoteStream
this.peer.onicecandidate = this.sendIceCandidate
this.peer.setRemoteDescription(new RTCSessionDescription(event))
try {
sessionDescription = this.peer.createAnswer().then((answer) => {
var anwer =
console.log('sessionDescription');
console.log(answer);
this.$socket.emit('answer', {
type: 'webrtc_answer',
sdp: answer,
sessionDescription: JSON.stringify(answer),
roomId,
})
return this.peer.setLocalDescription(answer)
});
} catch (error) {
console.error('cae: '+error)
}
},
getScreenPosition() {
const right = this.$refs.screen.getBoundingClientRect().right;
const bottom = this.$refs.screen.getBoundingClientRect().bottom;
return { bottom: bottom, right: right };
},
setRemoteStream(event) {
console.log('event setRemoteStream');
console.log(event);
var stream_screen = document.querySelector("video");
stream_screen.srcObject = event.streams[0];
stream_screen.play();
var remoteStream = event.stream
},
I have setup my own TURN server and tried paid versions. Still can't get the stream across different networks.
What am I missing?

onRemoteStreamListener doesn't get triggered on the Initiator's Side (Web App)

I am trying to build a video chat application between 2 users using the sdk provided by Connecty Cube everything works fine so far till.
User 1 (caller) : initiates the call
User 2 (opponent) : receives the call and accepts it
User 1 & User 2 : should get a call back function onRemoteStreamListener in order to start the Video Session between them
What actually happens User 2 only gets the Event for OnRemoteStreamListener and could view/hear User 1, while onRemoteStreamListener doesn't get triggered in User 1 side. I am not sure what is the problem as I have been going through the documentation provided by Connecty Cube and having it as my reference for the integration
link: https://developers.connectycube.com/js/videocalling?id=accept-a-call
ConnectyCube SDK Version : 3.9.1
Here are samples for the code:
User 1 (Caller Code)
async startVideoSession() {
try {
const {
dispatch,
getters: {
getConnectyCubeSessionInfo,
getUserData,
getSelectedVideoAppointement
}
} = this.$store;
const patientConnectyCubeUserResponse = await dispatch(
"getUserFromConnectyCube",
{
sessionInfo: getConnectyCubeSessionInfo,
email: getSelectedVideoAppointement.patient.email
}
);
const doctorConnectyCubeUserResponse = await dispatch(
"getUserFromConnectyCube",
{
sessionInfo: getConnectyCubeSessionInfo,
email: getUserData.email
}
);
const {
Credentials: { appId }
} = connectyCubeClient;
const client = new ConnectyCube();
await client.init({ appId, token: getConnectyCubeSessionInfo.token });
await client.createSession({
login: ########,
password: ##########
});
const token = client.service.sdkInstance.session.token;
await client.chat.connect({
userId: doctorConnectyCubeUserResponse.user.id,
password: token
});
const isConnected = await client.chat.isConnected;
if (isConnected) {
const calleesIds = [patientConnectyCubeUserResponse.user.id]; // User's ids
const sessionType = client.videochat.CallType.VIDEO; // AUDIO is also possible
const additionalOptions = { bandwidth: 256 };
const session = await client.videochat.createNewSession(
calleesIds,
sessionType,
additionalOptions
);
const mediaParams = {
audio: true,
video: true,
options: {
muted: true,
mirror: true
}
};
await session
.getUserMedia(mediaParams)
.then(localStream => {
session.attachMediaStream("doctor-video", localStream);
const extension = {};
session.call(extension, error => {
console.log(error);
});
})
.catch(err => {
console.error(err);
});
console.log("last console.log");
client.videochat.onAcceptCallListener = this.onAcceptCallListener;
// client.videochat.onUserNotAnswerListener = function(session, userId) {
// console.log("call refused");
// };
client.videochat.onSessionConnectionStateChangedListener = function(
session,
userID,
connectionState
) {
console.log("Connection state => \n", connectionState);
};
client.videochat.onRemoteStreamListener = this.OnRemoteStreamListener;
}
} catch (err) {
console.log("ERRRRRROR", err);
}
}
OnRemoteStreamListener(session, userId, remoteStream) {
// attach the remote stream to DOM element
console.log("STREAM FROM DOCTOR");
session.attachMediaStream("patient-video", remoteStream);
},
User 2 (opponent code)
async initalizeConnectyCube() {
const {
getters: { getPatientData }
} = this.$store;
const client = new ConnectyCube();
const { Credentials, Config } = connectyCubeClient;
await client.init(Credentials, Config);
const sessionInfo = await client.createSession();
await client.init({ appId: Credentials.appId, token: sessionInfo.token });
const sessionStatus = await client.createSession({
login: ########,
password: ########
});
const token = client.service.sdkInstance.session.token;
await client.chat.connect({
userId: sessionStatus.user.id,
password: token
});
const isUserConnected = await client.chat.isConnected;
if (isUserConnected) {
client.videochat.onCallListener = this.handleOnCallListener;
client.videochat.onRemoteStreamListener = this.handleOnRemoteStreamListener;
}
}
handleOnRemoteStreamListener(session, userID, remoteStream) {
// attach the remote stream to DOM element
console.log("STREAM FROM CALLER");
session.attachMediaStream("patient-video", remoteStream);
}

not playing audio from buffer stored in the database

I've spent 2 days with this thing and couldn't find a solution but seems like im getting closer. The goal is to record audio in the browser and store in the the database and get it whenever i need it.
I encoded the audio to base64 and sent it to the server stored as binary in the mongodb, created a get route in which i find the audio by id and send the buffer via res.send() along with the content type set to audio/webm (default mime type)
but the thing is I'm getting this blank video, seems like it's not knowing how to decode it or something. There might be something wrong with the content type.
navigator.mediaDevices.getUserMedia({ audio: true, video: false })
.then((mediaRecorderObj) => {
let mediaRecorder = new MediaRecorder(mediaRecorderObj, { mimType: 'audio/webm;base64' })
const m = mediaRecorder;
let chunk = []
startBtn.addEventListener('click', () => {
mediaRecorder.start()
console.log('started recording..');
})
endBtn.addEventListener('click', () => {
mediaRecorder.stop()
console.log('just stopped recording');
})
//-----When Data Is Available
mediaRecorder.ondataavailable = (e) => {
chunk.push(e.data);
}
//--------When finished adding it to chunk
mediaRecorder.onstop = () => {
const blob = new Blob(chunk, { 'type': 'audio/webm' })
chunk = []
const reader = new FileReader()
reader.readAsDataURL(blob)
reader.onloadend = async () => {
const buffer = reader.result
const bodyObj = {
voice: buffer
}
await fetch('http://localhost:3000/upload-spoken-bio', {
method: 'post',
body: JSON.stringify(bodyObj),
headers: { 'Content-Type': 'application/json' }
})
}
}
})
and this is the server side
//----------Upload
spokenBio.post('/upload-spoken-bio', async (req, res) => {
const buffer = req.body
try {
const newBuffer = new SpokenCon(buffer)
await newBuffer.save()
}
catch (err) {
console.log(err.message);
}
})
//----------Retrieve
spokenBio.get('/get-spoken-bio/:id', async (req, res) => {
const id = req.params.id
try {
const field = await SpokenCon.findById(id)
const binary = field.voice
res.set({ 'Content-Type': 'audio/webm' })
res.send(binary)
}
catch (err) {
console.log(err.message);
}
})