Search code examples
iosnode.jsreactjsamazon-s3streaming

S3 video not streaming on iOS with React


As the title says I have some video saved on an s3 bucket. I set my nodejs to stream it on my react app. It works fine on all devices except iOS. I did some searches and I can't find the issue. The server is returning the initial bytes request as 206. I checked the headers but I can't find the issue: Here is my nodejs: After it reaches the path:

 if (!range) {
      res.status(400).send("returning err!");
      return;
    }
    s3.headObject({
      Bucket: process.env.AWS_BUCKET_NAME,
      Key: req.params.key
    }, function (err, data) {
      if (err) {
        // an error occurred
        console.error(err);
        return res.status(500).send("returning err");
      }
      
      const videoSize = Number(data.ContentLength);
      const CHUNK_SIZE = 10 ** 6;  // 1MB
      const start = Number(range.replace(/\D/g, ""));
      const end = Math.min(start + CHUNK_SIZE, videoSize - 1);


      var params = {
        Bucket: process.env.AWS_BUCKET_NAME,
        Key: req.params.key,
        Range: range,
      };
      var stream = s3.getObject(params).createReadStream();

      res.status(206);
      res.set('Content-Type', data.ContentType);
      res.set('Content-Disposition','inline');
      res.set('Accept-Ranges','bytes');
      res.set('Accept-Encoding', 'Identity');
      res.set('Content-Range',  'bytes ' + start + '-' + end + '/' + videoSize);
      res.set('Content-Length', data.ContentLength);
      res.set('X-Playback-Session-Id', req.header('X-Playback-Session-Id')); // Part of the attempt to fix
      res.set('Connection', 'keep-alive');
      res.set('Last-Modified', data.LastModified);
      res.set('ETag', data.ETag);
      stream.pipe(res);

Here is my Frontend React player code:

    <ReactPlayer
      ref={player}
      // onProgress={onProgress}
      playsinline={true}
      url={[{ src: source, type: 'video/mp4;' }]} // video location
      controls  // gives the front end video controls 
      width='100%'
      className='react-player'
      height='100%'
      allow='autoplay; encrypted-media'
      allowFullScreen
      // muted={true}
      playing={playing}
      onPlay={() => setPlaying(true)}
      // onPause={() => setPlaying(false)} //part of the attempt to fix
      // onSeek={(seek) => playerSeeker(seek)} //part of the attempt to fix
      config={{
        file: {
          attributes: {
            controlsList: 'nodownload'
          }
        }
      }}
      onContextMenu={e => e.preventDefault()}
      onEnded={(e) => onVideoEnded(e)}
      onError={(e) => onVideoError(e)}
    />

Again, the first request on iOS is returning a 206 success but node always ends the stream before it even start playing.


Solution

  • Turns out It was just the

      res.set('Content-Length', data.ContentLength);
    

    Instead of sending the full length for the video, I needed to return the length of the calculated range.