upload webcam react video during recording

264 Views Asked by At

I working on a react/flask app, where I need to upload a recorded video into firebase storage. I have implemented a way to record 10 secs chunks and upload them during recording, but it seemed like a work around solution, is there a better way to implement his and how to handle the chunks in firebase storage, is there a way to merge the chunks either on firebase or when using them in react app

The following is my current implemented code:

import React, { useRef, useState, useEffect } from 'react';
import Webcam from 'react-webcam';
import RecordRTC, { StereoAudioRecorder } from 'recordrtc';
import {
  ref,
  uploadBytes,
  getDownloadURL,
} from 'firebase/storage';
import { storage } from './firebaseConfig';
import { FiCamera, FiStopCircle, FiMic, FiVideo } from 'react-icons/fi';

export default function VideoRecording() {
  const webcamRef = useRef(null);
  const [recordingStatus, setRecordingStatus] = useState('notRecording');
  const [audioBlob, setAudioBlob] = useState(null);
  const [micStream, setMicStream] = useState(null);
  const [videoUrl, setVideoUrl] = useState(null);
  const [activeTab, setActiveTab] = useState('recording');
  const [isMicOn, setIsMicOn] = useState(false);
  const [mediaRecorder, setMediaRecorder] = useState(null);
  const [recordId, setRecordId] = useState(null);
  const [counter, setCounter] = useState(0);
  const [timerInterval, setTimerInterval] = useState(null);
  const [currentMediaRecorder, setCurrentMediaRecorder] = useState(null);
  const [recordedChunks, setRecordedChunks] = useState([]);

  useEffect(() => {
    if (recordingStatus === 'recording') {
      setTimerInterval(setInterval(() => {
        setCounter(prevCounter => prevCounter + 1);
      }, 1000));
    } else {
      clearInterval(timerInterval);
      setCounter(0);
    }
  }, [recordingStatus]);

  useEffect(() => {
    if (recordingStatus === 'recording' && counter % 10 === 0) {
      if (currentMediaRecorder) {
        currentMediaRecorder.stop();
        setCurrentMediaRecorder(null);
      }

      const recorder = new MediaRecorder(webcamRef.current.stream);
      setCurrentMediaRecorder(recorder);

      const audioRecorder = new RecordRTC(webcamRef.current.stream, {
        type: 'video',
        mimeType: 'video/webm',
        recorderType: StereoAudioRecorder,
        audio: micStream,
      });

      const newRecordedChunks = [...recordedChunks];

      recorder.ondataavailable = (e) => {
        if (e.data.size > 0) {
          newRecordedChunks.push(e.data);
          setRecordedChunks(newRecordedChunks);
        }
      };

      recorder.onstop = async () => {
        audioRecorder.stopRecording(() => {
          const audioBlob = audioRecorder.getBlob();
          setAudioBlob(audioBlob);
          stopMicrophone();
        });

        const videoBlob = new Blob(recordedChunks, {
            type: 'video/webm',
          });
    
          const videoFile = new File([videoBlob], `video_${counter}_${new Date().toISOString()}.webm`, {
            type: 'video/webm',
          });
    
          const audioFile = new File([audioBlob], `audio_${counter}_${new Date().toISOString()}.wav`, {
            type: 'audio/wav',
          });
    
          const videoRef = ref(storage, `videos/video_${counter}_${new Date().toISOString()}.webm`);
          const audioRef = ref(storage, `audio/audio_${counter}_${new Date().toISOString()}.wav`);
    
          try {
            console.log('Uploading video file...');
            await uploadBytes(videoRef, videoFile);
            console.log('Video file uploaded successfully.');
    
            console.log('Uploading audio file...');
            await uploadBytes(audioRef, audioFile);
            console.log('Audio file uploaded successfully.');
    
            const videoDownloadURL = await getDownloadURL(videoRef);
            setVideoUrl(videoDownloadURL);
    
          } catch (error) {
            console.error('Error uploading files:', error);
          }
      };

      recorder.start();
      audioRecorder.startRecording();
    }
  }, [counter, recordingStatus]);

  const startMicrophone = async () => {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      setMicStream(stream);
      setIsMicOn(true);
    } catch (error) {
      console.error('Error accessing microphone:', error);
    }
  };

  const stopMicrophone = () => {
    if (micStream) {
      micStream.getTracks().forEach((track) => track.stop());
      setMicStream(null);
      setIsMicOn(false);
    }
  };

  const handleStartRecording = async () => {
    if (recordingStatus === 'uploaded') {
      setRecordingStatus('notRecording');
      setVideoUrl(null);
      setAudioBlob(null);
    }

    const recorder = new MediaRecorder(webcamRef.current.stream);
    setMediaRecorder(recorder);
    const audioRecorder = new RecordRTC(webcamRef.current.stream, {
      type: 'video',
      mimeType: 'video/webm',
      recorderType: StereoAudioRecorder,
      audio: micStream,
    });

    // const recordedChunks = [];

    recorder.ondataavailable = (e) => {
      if (e.data.size > 0) {
        setRecordedChunks(prev => [...prev, e.data])
        // recordedChunks.push(e.data);
        console.log(recordedChunks);
      }
    };

    recorder.onstop = async () => {
      audioRecorder.stopRecording(() => {
        const audioBlob = audioRecorder.getBlob();
        setAudioBlob(audioBlob);
        stopMicrophone();
      });

      const videoBlob = new Blob(recordedChunks, {
        type: 'video/webm',
      });

      const videoFile = new File([videoBlob], `video_${counter}_${new Date().toISOString()}.webm`, {
        type: 'video/webm',
      });

      const audioFile = new File([audioBlob], `audio_${counter}_${new Date().toISOString()}.wav`, {
        type: 'audio/wav',
      });

      const videoRef = ref(storage, `videos/video_${counter}_${new Date().toISOString()}.webm`);
      const audioRef = ref(storage, `audio/audio_${counter}_${new Date().toISOString()}.wav`);

      try {
        console.log('Uploading video file...');
        await uploadBytes(videoRef, videoFile);
        console.log('Video file uploaded successfully.');

        console.log('Uploading audio file...');
        await uploadBytes(audioRef, audioFile);
        console.log('Audio file uploaded successfully.');

        const videoDownloadURL = await getDownloadURL(videoRef);
        setVideoUrl(videoDownloadURL);

        setRecordingStatus('uploaded');
        console.log('Recording uploaded successfully!');
      } catch (error) {
        console.error('Error uploading files:', error);
      }
    };

    // Generate a unique recordId
    const newRecordId = Date.now();
    setRecordId(newRecordId);

    recorder.start();
    audioRecorder.startRecording();
    startMicrophone();
    setRecordingStatus('recording');
  };

  const handleStopRecording = async () => {
    setRecordingStatus('stopped');

    if (mediaRecorder) {
      mediaRecorder.stop();
    }
  };

  const handleTabChange = (tab) => {
    setActiveTab(tab);
  };

  const formatTimer = (seconds) => {
    const minutes = Math.floor(seconds / 60);
    const remainingSeconds = seconds % 60;
    return `${minutes}:${remainingSeconds < 10 ? '0' : ''}${remainingSeconds}`;
  };

  return (
    <div className="flex flex-col items-center justify-center h-screen bg-slate-400">
      <div className="flex space-x-4">
        <button
          onClick={() => handleTabChange('recording')}
          className={`flex items-center justify-center bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full mb-4 ${
            activeTab === 'recording' ? 'bg-blue-700' : ''
          }`}
        >
          <FiCamera className="mr-2" /> Record
        </button>
        <button
          onClick={() => handleTabChange('video')}
          className={`flex items-center justify-center bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full mb-4 ${
            activeTab === 'video' ? 'bg-blue-700' : ''
          }`}
        >
          <FiVideo className="mr-2" /> View Video
        </button>
      </div>

      {activeTab === 'recording' && (
        <>
          <Webcam audio ref={webcamRef} className="border rounded mb-4" />
          {recordingStatus === 'notRecording' && (
            <button
              onClick={handleStartRecording}
              className="flex items-center justify-center bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full mb-4"
            >
              <FiCamera className="mr-2" /> Start Recording
            </button>
          )}
          {recordingStatus === 'recording' && (
            <div className="mb-4">
              <p className="text-2xl text-center font-semibold">
                {formatTimer(counter)}
              </p>
              <button
                onClick={handleStopRecording}
                className="flex items-center justify-center bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-full mt-4"
              >
                <FiStopCircle className="mr-2" /> Stop Recording
              </button>
            </div>
          )}
          {recordingStatus === 'uploaded' && videoUrl && (
            <div className="mb-4">
              <p className="text-lg font-bold text-green-600">Recording uploaded successfully!</p>
            </div>
          )}
          {micStream && isMicOn && (
            <div className="flex items-center justify-center border rounded p-4">
              <FiMic className="text-blue-500 mr-2" /> Microphone Enabled
            </div>
          )}
        </>
      )}

      {activeTab === 'video' && (
        <>
          {recordingStatus === 'uploaded' && videoUrl ? (
            <div className="mb-4">
              <video controls src={videoUrl} className="border rounded" />
            </div>
          ) : (
            <div className="flex items-center justify-center border rounded p-4">
              <FiVideo className="text-blue-500 mr-2" /> No recorded video available
            </div>
          )}
        </>
      )}
    </div>
  );
}
0

There are 0 best solutions below