Discussions

Ask a Question
Back to All

Struggling to port the Stream example code to Next.JS

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!