Search code examples
reactjsalgorithmvideo-streaming

How to toggle between buffered (hidden) video and rendered video in this context?


I have a complicated react app dealing with video streams, but this question can boil down to something a lot simpler. Basically, I need to buffer a video "in the background", using a hidden div (don't ask me why), and meanwhile display the current video. Then it cycles through n number of videos every few seconds, buffering the next video while the current video plays.

I am using React, and for some reason doing this doesn't work:

const BufferedVideoRenderer = () => {
  return (
    <>
      <VideoPlayer streamId={bufferedStreamId} style={{ display: 'none' }} />
      <VideoPlayer streamId={currentStreamId} />
    </>
  )
}

And then you'd have a useEffect hook which loops through and sets the stream IDs to i and i + 1 basically (and looping back to 0 when it gets to the end). That doesn't work, because the IDs cycle forward! You end up with these states:

// stream IDs
[123, 456, 789]

// state 1
currentStreamId === 123
bufferedStreamId === 456

// state 2
currentStreamId === 456
bufferedStreamId === 789

// state 3
currentStreamId === 789
bufferedStreamId === 123

So the 456 which is buffered, is rendered in the 1st component slot in state 1, but in state 2 it's in the 2nd component slot! So the system doesn't reuse the buffered component slot.

How do you accomplish that? You can probably simulate it without React, just using either the DOM or a simple function.

I'm not even sure what the states would look like, it seems like somehow you would do this:

// stream IDs
[123, 456, 789]

// state 1, show current
currentStreamId === 123
bufferedStreamId === 456

// state 2, show buffered now
currentStreamId === 789
bufferedStreamId === 456 // keep this one in its slot

// state 2, show current now
currentStreamId === 789
bufferedStreamId === 123 // update this one now

So it seems like you need to go back and forth between which slot is shown, and at the same time, only update one of the IDs at a time...

How would you accomplish this?

I tried having two states and toggling back and forth which is displayed.


Solution

  • When you change the stream Id in the visible player, it's not going to magically transfer the pre-buffered data from the invisible player.

    You need to assign the even-indexed streamIds consistency to one player, and the odd-indexed streamIds consistently to the other player, and then switch visibility to determine which player the user sees. The technique is generally called "double-buffering" if you want to google about it.

    Let's say that you have a useState variable i that is the index of the video currently playing. Then you can do this:

    if ((i&1) == 0) {
        // even-numbered video playing
        return (
           <>
               <VideoPlayer streamId={streams[i]}/>
               <VideoPlayer streamId={streams[i+1]}  style={{ display: 'none' }}/>
           </>
        );
    } else {
        // odd-numbered video playing
        return (
           <>
               <VideoPlayer streamId={streams[i+1]}  style={{ display: 'none' }}/>
               <VideoPlayer streamId={streams[i]}/>
           </>
        );
    }
    

    Note that, because of the way matches new element against old ones, the first player is always the same video player instance, and the second player is always the same second player instance, even though we switch the code branches that return them.