javascriptnode.jswebtorrent

WebTorrent : TypeError: Cannot read properties of undefined (reading 'find')


I'm working on a school project, I'm setting up some kind of streaming website. I'm supposed to be able to stream a movie using WebTorrent asynchronously. I'm using NodeJS, React and Mongo. I have some kind of 'home page' displaying movies thumbnails, when I click on one I get redirected to the streaming page with the torrent id in the url. With that the server can gather data for that movie from database. Here is the code for the streaming page :

import React, { useEffect, useState, useRef } from 'react';
import axios from 'axios';
import { useParams } from 'react-router-dom';

function TorrentStream() {
  const { id } = useParams();
  const videoRef = useRef(null);
  const [movie, setMovie] = useState(null);

  useEffect(() => {
    const fetchMovieDetails = async () => {
      try {
        const response = await axios.get(`https://localhost:3002/api/movies/${id}`);
        setMovie(response.data);
        console.log()
      } catch (error) {
        console.error('Error fetching movie details:', error);
      }
    };

    fetchMovieDetails();
  }, [id]);

  const handleStreamVideo = async () => {
    try {
      const response = await axios.get(`https://localhost:3002/api/movies/stream/${id}`, {
        headers: {
          'Range': 'bytes=0-'
        },
        responseType: 'blob'
      });
      const blobUrl = URL.createObjectURL(response.data);
      if (videoRef.current) {
        videoRef.current.src = blobUrl;
        videoRef.current.load();
        videoRef.current.play();
      }
    } catch (error) {
      console.error('Error fetching video:', error);
    }
  };

I have a small stream button that calls handleStreamVideo on click.This makes a call to this api function :

export const streamMovie = async (req, res) => {
    const { id } = req.params;
    try {
        const movie = await Movie.findById(id);
        if (!movie) {
            console.error('Movie not found');
            return res.status(404).json({ message: 'Movie not found' });
        }

        const magnetURI = movie.magnet_link;
        let existingTorrent = client.get(magnetURI);

        console.log('magnetURI:', magnetURI);
        console.log('existingTorrent:', existingTorrent);

        if (existingTorrent) {
            handleStream(existingTorrent, req, res);
        } else {
            client.add(magnetURI, { path: movie.storage_path }, torrent => {
                torrent.on('ready', () => handleStream(torrent, req, res));
                torrent.on('error', error => console.error('Error adding torrent:', error));
            });
        }
    } catch (err) {
        console.error('Error fetching movie:', err);
        res.status(500).json({ message: 'Error fetching movie', err });
    }
};

and as you can see, this calls one last function :

const handleStream = async (torrent, req, res) => {
    try {
        await torrent.ready;
        const file = torrent.files.find(file => file.name.endsWith('.mp4'));

        if (!file) {
            console.error('No suitable video file found in the torrent');
            return res.status(404).json({ message: 'No suitable video file found in the torrent' });
        }

        const range = req.headers.range;
        if (!range) {
            console.error('Range header is required');
            return res.status(400).send("Requires Range header");
        }

        const positions = range.replace(/bytes=/, "").split("-");
        const start = parseInt(positions[0], 10);
        const total = file.length;
        const end = positions[1] ? parseInt(positions[1], 10) : total - 1;
        const chunksize = (end - start) + 1;

        res.writeHead(206, {
            "Content-Range": `bytes ${start}-${end}/${total}`,
            "Accept-Ranges": "bytes",
            "Content-Length": chunksize,
            "Content-Type": "video/mp4"
        });

        const stream = file.createReadStream({ start, end });
        pump(stream, res, err => {
            if (err) console.error('Stream pipeline failed', err);
        });
    } catch (err) {
        console.error('Error handling stream:', err);
        res.status(500).json({ message: 'Error handling stream', err });
    }
};

Now when I click on the stream button I get this error saying :

Error handling stream: TypeError: Cannot read properties of undefined (reading 'find')
at handleStream (file:///backend/src/api/controllers/movieController.js:72:36)
at processTicksAndRejections (node:internal/process/task_queues:96:5)

talking about this line of code : const file = torrent.files.find(file => file.name.endsWith('.mp4')); from handleStream function. I checked in the torrent itself, and there is indeed a .mp4 file, so that's not the issue. The magnet uri is valid, I was able to download it and check the movie and everything.

I checked the value of existingTorrent in streamMovie with this statement : console.log('existingTorrent:', existingTorrent); and got this result : existingTorrent: Promise { <pending> }. I understand that there is an issue with loading the torrent or something but I'm really lost here, I've been stuck on this for a while now. I just want to have the streaming running when I click on the stream button


Solution

  • Promises are often used in asynchronous calls and you should properly await for them or use .then(fooSometing), to make a long story short.

    So the relevant line of code should be:

    let existingTorrent = await client.get(magnetURI);
    

    I didn't even read the docs but I was curious so here it says to await for this method.