Discussions
Struggling to port the Stream example code to Next.JS
over 1 year ago by mxmgamaleldin
So I tried porting the Stream example to Next.JS:
'use client'
import styles from './page.module.css'
import { useEffect, useRef, useState } from 'react';
import DID_API from '../config.json';
export default function Home() {
const talkVideoRef = useRef(null);
const peerConnectionRef = useRef(null);
const [streamId, setStreamId] = useState(null);
const [sessionId, setSessionId] = useState(null);
const [sessionClientAnswer, setSessionClientAnswer] = useState(null);
useEffect(() => {
if (DID_API.key === '') {
alert('Please put your API key inside ./config.json and restart.');
}
}, []);
const connectButtonHandler = async () => {
if (
peerConnectionRef.current &&
peerConnectionRef.current.connectionState === 'connected'
) {
return;
}
stopAllStreams();
closePC();
const sessionResponse = await fetch(`${DID_API.url}/talks/streams`, {
method: 'POST',
headers: {
Authorization: `Basic ${DID_API.key}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
source_url: 'https://cdn.midjourney.com/ae5badc6-28dd-4675-8de8-1dbedd844d92/0_1.png',
}),
});
const { id: newStreamId, offer, ice_servers: iceServers, session_id: newSessionId } =
await sessionResponse.json();
setStreamId(newStreamId);
setSessionId(newSessionId);
try {
const sessionClientAnswer = await createPeerConnection(offer, iceServers);
setSessionClientAnswer(sessionClientAnswer);
} catch (e) {
console.log('error during streaming setup', e);
stopAllStreams();
closePC();
return;
}
const sdpResponse = await fetch(
`${DID_API.url}/talks/streams/${streamId}/sdp`,
{
method: 'POST',
headers: {
Authorization: `Basic ${DID_API.key}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ answer: sessionClientAnswer, session_id: sessionId }),
}
);
};
const talkButtonHandler = async () => {
if (
peerConnectionRef.current?.signalingState === 'stable' ||
peerConnectionRef.current?.iceConnectionState === 'connected'
) {
const talkResponse = await fetch(
`${DID_API.url}/talks/streams/${streamId}`,
{
method: 'POST',
headers: {
Authorization: `Basic ${DID_API.key}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
script: {
type: 'text',
input:
'Hello, this is a test.',
provider: {
type: 'microsoft',
voice_id: 'en-GB-SoniaNeural',
},
},
config: {
stitch: true,
},
session_id: sessionId,
}),
}
);
}
};
const destroyButtonHandler = async () => {
await fetch(`${DID_API.url}/talks/streams/${streamId}`, {
method: 'DELETE',
headers: {
Authorization: `Basic ${DID_API.key}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ session_id: sessionId }),
});
stopAllStreams();
closePC();
};
const onIceGatheringStateChange = () => {
console.log(peerConnectionRef.current.iceGatheringState);
};
const onIceCandidate = (event) => {
console.log('onIceCandidate', event);
if (event.candidate) {
const { candidate, sdpMid, sdpMLineIndex } = event.candidate;
fetch(`${DID_API.url}/talks/streams/${streamId}/ice`, {
method: 'POST',
headers: {
Authorization: `Basic ${DID_API.key}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ candidate, sdpMid, sdpMLineIndex, session_id: sessionId }),
});
}
};
const onIceConnectionStateChange = () => {
console.log(peerConnectionRef.current.iceConnectionState);
if (
peerConnectionRef.current.iceConnectionState === 'failed' ||
peerConnectionRef.current.iceConnectionState === 'closed'
) {
stopAllStreams();
closePC();
}
};
const onConnectionStateChange = () => {
console.log(peerConnectionRef.current.connectionState);
};
const onSignalingStateChange = () => {
console.log(peerConnectionRef.current.signalingState);
};
const onTrack = (event) => {
const remoteStream = event.streams[0];
setVideoElement(remoteStream);
};
const createPeerConnection = async (offer, iceServers) => {
if (!peerConnectionRef.current) {
peerConnectionRef.current = new RTCPeerConnection({ iceServers });
peerConnectionRef.current.addEventListener('icegatheringstatechange', onIceGatheringStateChange, true);
peerConnectionRef.current.addEventListener('icecandidate', onIceCandidate, true);
peerConnectionRef.current.addEventListener('iceconnectionstatechange', onIceConnectionStateChange, true);
peerConnectionRef.current.addEventListener('connectionstatechange', onConnectionStateChange, true);
peerConnectionRef.current.addEventListener('signalingstatechange', onSignalingStateChange, true);
peerConnectionRef.current.addEventListener('track', onTrack, true);
}
await peerConnectionRef.current.setRemoteDescription(offer);
console.log('set remote sdp OK');
const sessionClientAnswer = await peerConnectionRef.current.createAnswer();
console.log('create local sdp OK');
await peerConnectionRef.current.setLocalDescription(sessionClientAnswer);
console.log('set local sdp OK');
return sessionClientAnswer;
};
const setVideoElement = (stream) => {
if (!stream) return;
talkVideoRef.current.srcObject = stream;
// safari hotfix
if (talkVideoRef.current.paused) {
talkVideoRef.current.play().then(() => {}).catch(() => {});
}
};
const stopAllStreams = () => {
if (talkVideoRef.current.srcObject) {
console.log('stopping video streams');
talkVideoRef.current.srcObject.getTracks().forEach((track) => track.stop());
talkVideoRef.current.srcObject = null;
}
};
const closePC = (pc = peerConnectionRef.current) => {
if (!pc) return;
console.log('stopping peer connection');
pc.close();
pc.removeEventListener('icegatheringstatechange', onIceGatheringStateChange, true);
pc.removeEventListener('icecandidate', onIceCandidate, true);
pc.removeEventListener('iceconnectionstatechange', onIceConnectionStateChange, true);
pc.removeEventListener('connectionstatechange', onConnectionStateChange, true);
pc.removeEventListener('signalingstatechange', onSignalingStateChange, true);
pc.removeEventListener('track', onTrack, true);
console.log('stopped peer connection');
if (pc === peerConnectionRef.current) {
peerConnectionRef.current = null;
}
};
return (
<>
<div className={styles.videoWrapper}>
<video ref={talkVideoRef} autoPlay />
</div>
<div className={styles.buttonsWrapper}>
<button onClick={connectButtonHandler}>Activate</button>
<button onClick={talkButtonHandler}>Interact</button>
<button onClick={destroyButtonHandler}>Destroy</button>
</div>
</>
);
}
I am getting a multitude of errors when pressing Connect. 500 Errors from /ice and I get "disconnected" and "failed" in the console .. meanwhile, all my credits got consumed.
Please help me!