Using Redux to Manage a Video Player
Asked Answered
A

3

7

What would be the ideal way to manage a video player using Redux, primarily in terms of dispatching actions to play/pause a video?

I'm working on building a video player in a React application, and I have event listeners on the video player dispatching relevant events to update the state. What would be the best way to have the parent component act on other components dispatching a PLAY or PAUSE action?

One use case that I would want to account for, for example, is one video being played and all other videos making sure to pause their playback.

Two ways that I've thought of would be:

1) Have the parent component check for changes in componentWillReceiveProps, and checking for something like

if (this.props.paused && !nextProps.paused) {
    this.refs.video.play()
}

2) Store a reference to the underlying video element in the state tree, and using middleware to act on certain actions (such as a PLAY action), such as

if (action.type === PLAY) {
    let state = getState();
    state.players[action.payload.playerId].play();
}

Would one strategy be more "correct" than the other, or is there another way that would make more sense?

Aguascalientes answered 9/3, 2016 at 18:6 Comment(1)
Did you ever find a solution that was satisfactory for your needs? Have any examples of this implemented?Hl
V
3

1) is the way to go.

Your video is simply a view component. In React you always want the component's props to dictate the output.

The problem with 2) is that the video object doesn't belong in the state. You're telling Redux too much about the implementation detail. Redux doesn't care about the implementation detail; it's just a state container.

UPDATE

On further reflection, I recommend componentDidUpdate() is the best place to place this logic. i.e.

componentDidUpdate(prevProps)
  if (prevProps.paused && !this.props.paused) {
    this.refs.video.play();
  }
}

The advantage being that componentDidUpdate() is called after re-rendering. It may not make a difference for your purposes, but it's possible that triggering a DOM event before React has had a chance to update the DOM may cause some unfortunate race conditions.

Of course this doesn't change the gist of my advice that output should always be props (or state) driven.

Vinasse answered 9/3, 2016 at 22:55 Comment(2)
I agree with that. The reason I thought of #2 is because redux middleware is where side effects can be triggered (http calls, logging, etc) and was thinking that triggering playback might be considered a side effect. I definitely didn't like the idea of keeping a reference to the video object in the state though. Thanks for the response!Aguascalientes
You might consider it a side effect, but if so it's very much a DOM side effect. That makes it React's domain. I've made an update concerning the best place to put this logic.Vinasse
T
2

I don't think that you need (2), (1) is pretty good. Now you handle only play action, but you can add pause there, like this:

if (this.props.paused && !nextProps.paused) {
  this.refs.video.play()
}

if (!this.props.paused && nextProps.paused) {
  this.refs.video.pause()
}

In this case your html player state will be synchronized with redux player state.

One use case that I would want to account for, for example, is one video being played and all other videos making sure to pause their playback.

In this case you can handle this situation in your reducer. I'd store all players by ids with paused state, like:

{
 1: {
   paused: true, 
 },
 2: {
   paused: false,
 },
 ...
}

You need to add player id in your action and pause other players, when you receive PLAY action:

function players(state = {}, action) {
  switch (action.type) {
    case PAUSE: 
      return {
        ...state,
        [action.id]: {
          paused: true
        }
      }
    case PLAY: 
      const nowPlaying = Object.keys(state).find(id => !state[id].paused);
      const newState = {
        ...state,
        [action.id]: {
          paused: false
        }
      };
      if (nowPlaying) {
        newState[nowPlaying].paused = true;
      }
      return newState;
  }
}
Tinderbox answered 10/3, 2016 at 10:54 Comment(0)
H
0
    import { createSlice } from '@reduxjs/toolkit';

const initialState = {};

const playersSlice = createSlice({
  name: 'players',
  initialState,
  reducers: {
    pausePlayer(state, action) {
      const playerId = action.payload;
      state[playerId].paused = true;
    },
    playPlayer(state, action) {
      const playerId = action.payload;
      const previouslyPlaying = Object.entries(state).find(([id, player]) => !player.paused);

      state[playerId].paused = false;

      // Pause previously playing player only if it exists and isn't the same player
      if (previouslyPlaying && previouslyPlaying[0] !== playerId) {
        state[previouslyPlaying[0]].paused = true;
      }
    },
  },
});

export const { pausePlayer, playPlayer } = playersSlice.actions;
export default playersSlice.reducer;
Hassan answered 3/10 at 18:26 Comment(2)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Dallis
Please explain what you've changed to address the issue. Code-only answers are not good answersCreative

© 2022 - 2024 — McMap. All rights reserved.