Aún no tienes acceso a esta clase

Crea una cuenta y continúa viendo este curso

Terminando de detallar nuestro player

25/29
Recursos

Aportes 78

Preguntas 12

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesión.

Hola!

Implemente la solución aplicando useLayoutEffect.

Según la documentación funciona igual que useEffect, pero renderiza de forma síncrona.

import React, { useLayoutEffect } from 'react';

const Player = (props) => {
  const { id } = props.match.params;
  const hasPlaying = Object.keys(props.playing).length > 0;

  useLayoutEffect(() => {
    props.setVideoSource(id);
  }, []);

  return hasPlaying ? (
    <section className='player'>
      <video className='player__item' controls autoPlay>
        <source src={props.playing.source} type='video/mp4' />
      </video>
      <div className='player__back'>
        <button type='button' onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </section>
  ) : <NotFound />;
};

Agregando un loading mediante el hook useState.

const Player = ({ history, match, playing, getVideoSource }) => {
  const { id } = match.params;
  const [loading, setLoading] = useState(true);
  const hasPlaying = Object.keys(playing).length > 0;

  useEffect(() => {
    getVideoSource(id);
    setLoading(false);
  }, []);

  const handleBtnBack = () => history.goBack();

  if (loading) {
    return <h2>Cargando...</h2>;
  }

  return hasPlaying ? (
    <div className='player'>
      <video controls autoPlay>
        <source src={playing.source} type='video/mp4' />
      </video>
      <div className='Player-back'>
        <button type='button' onClick={handleBtnBack}>
          Regresar
        </button>
      </div>
    </div>
  ) : (
    <Redirect to='/404/' />
  );
};

otra forma de hacer la logica del reducer:

state.trends.concat(state.originals).find(item => item.id === Number(action.payload)) || []

basicamente se usa el metodo concat para unir los 2 arrays y luego el metodo find para filtrar.

El link del source de los videos ya no es publico, al menos a mi me dio un error 401 al intentar llegar a ellos, por lo que les dejo esta opcion
"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
solo deben de cambiar el _source _ de cada video en el initialState del archivo /src/index.js

Hola , buenas noches. Compañeros mi solucion fue colocarle un setTimeout al Redirect , asi siempre va a cargar el video antes que llevarme al error.

import React, { useEffect } from "react";
import { connect } from "react-redux";
import { getVideoSource } from "../actions/index";
import { Redirect } from "react-router-dom";
import "../assets/styles/components/Player.scss";
const Player = props => {
  const { id } = props.match.params;
  const hasPlaying = Object.keys(props.playing).length > 0;

  useEffect(() => {
    props.getVideoSource(id);
  }, []);

  return hasPlaying ? (
    <div className="Player">
      <video controls autoPlay>
        <source src={props.playing.source} type="video/mp4" />
      </video>
      <div className="Player-back">
        <button
          type="button"
          onClick={() => {
            props.history.goBack();
          }}
        >
          Regresar
        </button>
      </div>
    </div>
  ) : (
      setTimeout(<Redirect to="/404/" />, 1000)
    );
};
const mapStateToProps = state => {
  return {
    playing: state.playing
  };
};

const mapDispatchToProps = {
  getVideoSource
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Player);

Para los que no pueden ver los videos porque ya no están disponibles, aquí dejo mi store con videos disponibles, espero que no los deshabiliten.
Ingresen al link para verlo
https://stackblitz.com/edit/platzi-redux-router-store-videos?file=index.js

Para el bug que se presenta, me tomó un poco de tiempo entender por qué sucedía y en unos comentarios anteriores un compañero da una solución muy lógica, que soluciona el problema de forma concreta. Sucede que el hook useEffect funciona asincronamente por decirlo así, es decir que cuando la ejecución va en la línea del useEffect, antes de que termine o mientras se ejecuta, la ejecución continua.

Inicialmente (primera ejecución) el playing es {} (Objeto vacío), es decir que en primera instancia el hasPlaying es false.

Cuando llega la ejecución al return el hasPlaying aún es vacía, pero como “al mismo tiempo” se estaba ejecutando el useEffect, éste cambió el estado, por eso se vuelve a renderizar y en este nuevo renderizado el hasPlaying ya es true, porque encontró el objeto que buscaba, entonces ya se renderiza normal, por eso sucede como esa intermitencia de que se alcanza a ver el 404 por pequeñísimo instante. Ahora bien todo esto ocurre en una fracción de segundo, por eso puede que sea casi que imperceptible.

La solución que el compañero en un comentario da es que se use el hook useLayoutEffect funciona igual al useEffect solo que lo que hará es que éste se ejecute de forma síncrona, es decir la ejecución no llega al return si el useLayoutEffect no ha terminado de ejecutarse o sea, no se muestra nada y como modifica el estado vuelve y renderiza con eso ya tenemos el hasPlaying true o en el peor de los casos false.

Haga de cuenta de useLayoutEffect es como si tuviera async/await.

const Player = ({ match, playing, getVideoSource, history }) => {

	const { id } = match.params

	const hasPlaying = Object.keys(playing).length > 0

	useLayoutEffect(() => {
		getVideoSource(id)
	}, [])

	return hasPlaying ? (
		<div className='Player'>
			<video controls autoPlay>
				<source src={playing.source} type='video/mp4' />
			</video>
			<div className='Player-back'>
				<button type='button' onClick={() => history.goBack()}>
					Regresar
				</button>
			</div>
		</div>
	) :
		<NotFound />
}

Así es como lo he entendido, si estoy equivocado en algo, por favor házmelo saber, y si no me has entendido también para intentar de otra forma.

Una solución podría ser tratar los 2 eventos por separado: cargar video y video no encontrado:

import React, { useEffect, useState } from 'react'
import { connect } from 'react-redux'
import { getVideoSource } from '../actions'

import NotFound from './NotFound'

import '../assets/styles/components/Player.scss'

const Player = props => {
  const { id } = props.match.params
  const [ loading, setLoading ] = useState(true)
  const hasPlaying = Object.keys(props.playing).length > 0

  useEffect(() => {
    props.getVideoSource(id)
    setLoading(false)
  }, [])

  if (loading) return <h2>Cargando video...</h2>
  
  return hasPlaying ? (
		<div className="player">
			<video controls autoPlay>
				<source src={props.playing.source} type="video/mp4" />
			</video>
			<div className="Player-back">
				<button type="button" onClick={() => props.history.goBack()}>
					Regresar
				</button>
			</div>
		</div>
	) : <NotFound />
}

const mapStateToProps = state => (
  {
    playing: state.playing
  }
)

const mapDispatchToProps = {
	getVideoSource,
}

export default connect(mapStateToProps, mapDispatchToProps)(Player)

con ECMA script 6 podemos parsear strings a numeros anteponiendo el signo +, para este caso seria: item.id === +action.payload

Pensé que algo estaba mal, puesto que siempre me reproducía el mismo video

Hola yo hago lo siguiente :

-Dejo en null el atributo playing para ser mucho mas exacto en que no se ha cargado
absolutamente nada.
-La idea es que una vez que en el REDUCER se intente buscar el video,tendra como valor un objeto con o
sin atributos.
En el momento que tenga un valor(objeto) lo considero que ya se ha cargado el video y ahora lo que hago es que si esta vacio, quieres decir que no se encontro,y si es todo lo contrario retorno el componente ya que si se habra encontrado.

const initialState = {
	playing : null,
	...
}

En el componente :

const media = ({id,playing,setPlaying}) => {
   useEffect(() =>{
     setPlaying(id)
   },[])

  const render = () => {
    if(!playing) return <h3>cargando...</h3>
    if(!Object.keys(playing).length) return <h3>No existe el video</h3>
    return (
       <div className='video-player'>
		<video
         src={playing.source} 
         controls
	      autoPlay
         />
       </div>
    )
  }
}

Esta es la clase que mas me ha costado del curso 😕

a mi no me reproducía el video. Parece que la URL es prvada y no permite visualizar.

Podéis sino usar algún video de stock y sustituirlo en el index.js principal.

os dejo uno si alguien necesita
https://static.videezy.com/system/resources/previews/000/044/479/original/banana.mp4

Yo utilice useState.

<code>
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { getVideoSource } from '../actions';
import '../assets/styles/components/Player.scss';

const Player = (props) => {
  const { id } = props.match.params;
  const [loading, changeLoading] = useState(true);
  const hasPlaying = Object.keys(props.playing).length > 0;

  useEffect(() => {
    props.getVideoSource(id);
    changeLoading(false);
  }, []);

  return loading ?
    (<h1>Loading...</h1>) :
    hasPlaying ? (
      <div className='Player'>
        <video controls autoPlay>
          <source src={props.playing.source} type='video/mp4' />
        </video>
        <div className='Player-back'>
          <button type='button' onClick={() => props.history.goBack()}>
            Regresar
          </button>
        </div>
      </div>
    ) : <Redirect to='/404' />;
};

const mapStateToProps = (state) => {
  return {
    playing: state.playing,
  };
};

const mapDispatchToProps = {
  getVideoSource,
};

export default connect(mapStateToProps, mapDispatchToProps)(Player);
</code>

En este bloque el profesor puso original en lugar de originals por si tienen el mismo problema que yo.

case 'GET_VIDEO_SOURCE':
      return {
        ...state,
        playing: state.trends.find((item) => item.id === Number(action.payload)) || state.originals.find((item) => item.id === Number(action.payload)) || [],
      };```

Que tal!
Les comparto el initialState actualizado con nuevos sources para los videos, ya que los que venían originalmente no se podían acceder.

https://gist.github.com/Fredyspt/087cc10ad1ac0513eb2683cb684b5a15

🦄 Acá el código trabajado hasta el momento: https://github.com/AndresCampuzano/React-Router-and-Redux/commits/master

Video sin copy right:

http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4

😉

 const handlePlay = (idPlayer)=>{
        props.getVideosSource(idPlayer)
    }

 <Link to={`/player/${id}`}>
                    <img 
                        className="carousel-item__details--img" 
                        src={playIcon} 
                        alt="" 
                        onClick={()=>handlePlay(id)}
                    />
                </Link>
 const [ loading, setLoading ] = useState(true);

loading ? null :hasPlaying?( ...
const Player = (props) => {
    const { id } = props.match.params;
    const hasPlaying = Object.keys(props.playing).length>0
    const [ loading, setLoading ] = useState(true);
    useEffect(()=>{
        console.log(loading)
        setLoading(false)
        props.getVideosSource(id)
    },[])
    return loading ? null :hasPlaying?(
        <div className="Player">
            <video controls autoPlay>
                <source src={props.playing.source} type="video/mp4" />
            </video>
            <div className="Player-back">
                <button type="button" onClick={()=>props.history.goBack()}>
                    Regresar
                </button>
            </div>
        </div>
    ):<Redirect to="/404" />
}

20 Enero 2022 React Router Dom v6

El uso de esto ya no funciona

const { id } = props.match.params;
props.history.goBack()

Por lo cual se debe utilizar lo siguiente para capturar los params

import { useParams } from "react-router-dom";

const { id } = useParams();

Y para volver atras

import { useNavigate } from "react-router-dom";

const navigate = useNavigate();

<button type="button" onClick={() => navigate("../")}>
	Regresar
</button>

Hola!
Las url de los videos ya no están disponibles 😦

otra forma de hacer la lógica del reducer:

playing: [...state.trends, ...state.originals].find(x => x.id == Number(action.payload))

Hola! por si a alguno le comienza a dar error 401 Unauth al momento de darle play al video, me parece que es debido a que los videos han dejado de ser publicos (Diciembre 2020) y se requiere un TOKEN para accesar a ellos.

Una alternativa es buscar mas sample videos en internet y reemplazar el source de los items en nuestra API. Pueden revisar estos que estan alojados en google-api: Public videos gist

Si en algún momento no se les reproduce el video, es por falta de permisos, para obtenerlos, lo que hice fue buscar una biblioteca de videos gratuita y actualizar las rutas. esta es una de las que mire y actualice.

https://player.vimeo.com/external/444828749.sd.mp4?s=497e66c950ceb28b638ee115a172b70706399cb3&profile_id=165&oauth2_token_id=57447761

Me queda la duda, esperando alguién haya aplicado. Sí hago el front con lo aplicado en el curso, es necesario aplicar router y redirect usando el backend de django?

/* eslint-disable jsx-a11y/heading-has-content */
/* eslint-disable no-nested-ternary */
/* eslint-disable func-call-spacing */
/* eslint-disable react/destructuring-assignment */
/* eslint-disable react/prop-types */
/* eslint-disable jsx-a11y/media-has-caption */
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';

import { getVideoSuorce } from '../actions';
import '../assets/styles/components/Player.scss';

const Player = (props) => {
  const { id } = props.match.params;
  const [loading, changeLoading] = useState(true);
  const hasPlaying = Object.keys(props.playing).length > 0;

  useEffect(() => {
    props.getVideoSuorce(id);
    changeLoading(false);
  }, []);

  return loading
    ? <h1 />
    : hasPlaying ? (
      <div className="Player">
        <video controls autoPlay>
          <source src={props.playing.source} type="video/mp4" />
      Your browser does not support HTML5 video.
        </video>
        <div className="Player-back">
          <button type="button" onClick={() => props.history.goBack()}>
                Regresar
          </button>
        </div>
      </div>
    ) : <Redirect to="/404/" />;
};

const mapStateToProps = (state) => ({
  playing: state.playing,
});

const mapDispatchToProps = {
  getVideoSuorce,
};

export default connect(mapStateToProps, mapDispatchToProps)(Player);

Lo que hice fue añadir un estado para determinar cuando esta cargando y mientras carga regreso un div del tamaño del viewport para evitar mostrar el header

const [loading, setLoading] = useState(true);
useEffect(() => {
    props.getVideoSource(id);
    setLoading(false);
  }, []);
  if (loading) return <div style={{ height: '100vh', width: '100vw' }} />;
  return hasPlaying ? (
    <div className='Player'>
      <video controls autoPlay>
        <source src={props.playing.source} type='video/mp4' />
      </video>
      <div className='Player-back'>
        <button type='button' onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  ) : <Redirect to='/404/' />;

La idea que tengo es simplemente revisar si esta cargando el elemento

const [loading, changeLoading] = useState(true);
const { id } = props.match.params;
const hasPlaying = Object.keys(props.playing).length > 0;

useEffect(() => {
    props.getVideoSource(id);
    changeLoading(false);
}), [];

if(loading) return <div>Cargando...</div>

return hasPlaying ? (
    <div className="Player">
        <video controls autoPlay>
            <source src={props.playing.source} type="video/mp4" />
        </video>
        <div className="Player-back">
            <button type="button" onClick={() => props.history.goBack()}>Regresar</button>
        </div>
    </div>
) : <Redirect to="/404/" />

Solo añadi un setTimeOut al componente Redirect para que sea lo ultimo en cargar en caso de que hasPlaying demore.

return hasPlaying ? ( 
        <div className="Player">
            <video controls autoPlay>
                <source src={props.playing.source} type="video/mp4" />
            </video>
            <div className="Player-back">
                <button type="button" onClick={() => {props.history.goBack()}}>
                    Regresar
                </button>
            </div>
        </div>
     ) : (
        setTimeout(() => {
            <Redirect to="/404/" />   
        },0)
     )

Me resulta SUPER CONFUSO que llame al método getVideoSource() cuando en realidad es un setter, porque ese método lo que está haciendo es setear el estado de playing en la “base de datos” que se está simulando.

Hola. Pues mi solución fue intentar algo con Async await, no se si sea la mejor solución. Al hacerlo me topé con un problema en la configuración del Babel ya que necesité incluir el plugin de @babel/plugin-transform-runtime para poder trabajar con asincronismo.

useEffect(() => {
    let didCancel = false;
    (async () => {
      !didCancel && setloading(true)
      try {
        await props.getVideoSource(id);
        !didCancel && sethasPlaying(true);
      } catch (err) {
        sethasPlaying(false);
      } finally {
        !didCancel && setloading(false);
      }
    })();
    return () => { didCancel = true; }
  }, []);

De esa manera agregué un estado de loading. ¿Qué opinan?

En lo personal pienso que cuando ya esto se realiza en la practica la mayoria son llamadas a APIs por lo que creo que lo mejor es manejar las promesas y async await. En lo personal es mejor implementar un Loading… mientras nuestros datos son obtenidos.

como playing:{} hace parte del estado global, siempre va a tener algo adentro menos la primera vez que se monte el componente Player y se ejecute props.getVideoSource(id), entonces hasPlaying siempre sera true después de que tenga un primer valor seteado y no funcionara para lo que queremos hacer.

lo que propongo para este reto es mapear los videos en el componente Player desde el estado y hacer el filtro directamente en el componente Player. De esta forma la variable video siempre tendrá el video o undefined, y nos servirá para la validación del render 🤗🤗:

const VideoPlayer = (props) => {
  const { id } = props.match.params;
  const { videos } = props;
  const video = videos.find((item) => item.id === Number(id));

  return video ? (
    <div className="Player">
      <video controls>
        <source src={video.source} type="video/mp4" />
      </video>
      <div className="Player-back">
        <button type="button" onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  ) : (
    <Redirect to="/404" />
  );
};

const mapStateToProps = (state) => ({
  playing: state.playing,
  videos: [...state.mylist, ...state.trends, ...state.originals],
});

export default connect(mapStateToProps, null)(VideoPlayer);

En el initialState el playing es un Array no un objeto

initialState = {
  "user": {},
  "playing": [],
  "myList": [],
  ...

No me arrancaba, simplemente no cargaba nada. La solicioón fué cambiar

"playing":{} por "playing":[]

Yo voy haciendo el ejercicio junto con el profesor, pero no lo voy haciendo igual.
Yo quise añadirle interactividad a mi código conectándome a una API, y así pude notar la diferencia entre dos tipos de actions:

  1. Traslado del payload puro, sin ningun tipo de tratamiento (como un fetch) asíncrono de por medio,
  2. Con un tratamiento de por medio, del payload.

El payload es el argumento que pasas a la función Action.

En la forma 1, usas esta forma:

const loginRequest = (payload) => ({
  type: 'LOGIN_REQUEST',
  payload,
});

export default loginRequest;

Simplemente el payload que pasas de argumento, se lo pasas al Reducer. No hay más.



En la forma 2, tienes que hacer algo para transformar el payload original en algo más:

import axios from 'axios';
import { moviePath } from '../utils/Vars';

const getVideoSource = (payload) => async (dispatch) => {
  const response = await axios.get(`${moviePath}${payload}/videos?api_key=${process.env.APIKey}&language=en-US`);

  dispatch({
    type: 'LOAD_SOURCE_VIDEO',
    payload: {
      id: payload,
      key: response.data.results.find((item) => item.site === 'YouTube'),
    },
  });
};

export default getVideoSource;

En este código recibo el Payload (un ID de una película) y hago un fetch a la API para traer la url de Youtube, y pasarlo al reproductor (REDUCER).
En este caso el Dispatch, es el middleware de Redux-thunk.
Esa es la forma de manejar el asincronismo con redux.

Para los interesados en usar una pantalla de carga, les comparto este recurso donde pueden encontrar loaders hechos con puro CSS: https://loading.io/css/

práctica:

Da error.

En vez de mandar la información en el componente Player…yo mande la informacion al objeto playing desde CarouselItem; usando onClick en el boton de PlayIcon se dispara una funcion la cual actualiza nuestro objeto playing.

import React from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; 
import { setFavorite, deleteFavorite, sendVideoSource } from '../actions'; 
import '../assets/styles/components/CarouselItem.scss';
import playIcon from '../assets/static/play-icon.png';
import plusIcon from '../assets/static/plus-icon.png';
import removeIcon from '../assets/static/remove-icon.png';

const CarouselItem = (props) => {
  const { id, cover, title, year, contentRating, duration, language, isMyList } = props;

  const handleSetFavorite = () => {
    props.setFavorite({
      id, cover, title, year, contentRating, duration, language,
    });
  };

  const handleDeleteFavorite = (itemId) => {
    props.deleteFavorite(itemId);
  };

  const handlePlayIcon = (id) => {
    props.sendVideoSource(id);
  };

  return (
    <div className="carousel-item">
      <img className="carousel-item__img" src={cover} alt={title} />
      <div className="carousel-item__details">
        <div>
          <Link to={`/player/${id}`}>
            <img
              className="carousel-item__details--img"
              src={playIcon}
              alt="Play Icon"
              onClick={() => handlePlayIcon(id)}
            />
          </Link>
          {
            isMyList ?
              <img
                className="carousel-item__details--img"
                src={removeIcon}
                alt="Remove Icon"
                onClick={() => handleDeleteFavorite(id)}
              />
            :
              <img
                className="carousel-item__details--img"
                src={plusIcon}
                alt="Plus Icon"
                onClick={handleSetFavorite}
              />
          }
        </div>
        <p className="carousel-item__details--title">{title}</p>
        <p className="carousel-item__details--subtitle">{`${year} ${contentRating} ${duration}`}</p>
        <p className="carousel-item__details--language">{`${language}`}</p>
      </div>
    </div>
  );
};

CarouselItem.propTypes = {
  cover: PropTypes.string,
  title: PropTypes.string,
  year: PropTypes.number,
  contentRating: PropTypes.string,
  duration: PropTypes.number,
  language: PropTypes.string,
};

const mapDispatchToProps = {
  setFavorite,
  deleteFavorite,
  sendVideoSource,  
};

export default connect(null, mapDispatchToProps)(CarouselItem);

Y en el componente Player solo traje la informacion del objeto playing…podiendo usar el <Redirect to="/404/" /> en dado caso que se escriba la url directamente en el navegador. EJEMPLO: /player/78
no existe el id 78 por lo cual nos mandara al /404/

import React from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import '../assets/styles/components/Player.scss';

const Player = (props) => {
  const { playing } = props;
  const hasPlaying = Object.keys(playing).length > 0;

  return hasPlaying ? (
    <div className="Player">
      <video controls autoPlay>
        <source src={playing.source} type="video/mp4" />
      </video>
      <div className="Player-back">
        <button type="button" onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  ) : <Redirect to="/404/" />;
};

// para obtener el objeto "playing"
const mapStateToProps = (state) => {
  return {
    playing: state.playing,
  };
};

export default connect(mapStateToProps, null)(Player);
class Player extends React.Component {
  componentDidMount() {
    const { id } = this.props.match.params;
    this.props.getVideoSource(id);
  }

  render() {
    const hasPlaying = Object.keys(this.props.playing).length > 0;
    return hasPlaying ? (
      <div className="Player">
        <video controls autoPlay>
          <source src={this.props.playing.source} type="video/mp4" />
        </video>
        <div className="Player-back">
          <button type="button" onClick={() => this.props.history.goBack()}>
            regresar
          </button>
        </div>
      </div>
    ) : (
      <NotFound />
    );
  }
}
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { getVideoSource } from "../actions";
import "../assets/styles/components/Player.scss";

const Player = props => {
  const { id } = props.match.params;
  const hasPlaying = Object.keys(props.playing).length > 0;

  useEffect(() => {
    props.getVideoSource(id);
  }, []);

  return hasPlaying ? (
    <div className="Player">
      <video controls autoPlay>
        <source src={props.playing.source} type="video/mp4" />
      </video>
      <div className="Player-back">
        <button type="button" onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  ) : (
    <h1>Loading ... </h1>
  );
};

const mapStateToProps = state => {
  return {
    playing: state.playing
  };
};

const mapDispatchToProps = {
  getVideoSource
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Player);

bueno esta clase me costo, e igual la vi como 5 veces jejejej

Ami esto fue lo que me funciono

import React, {useEffect}  from 'react';
import '../assets/styles/components/Player.scss';
/* importamos connect redux */
import { connect } from 'react-redux';
import { getVideoSource } from '../actions';
import NotFound from '../containers/NotFound';


const Player = props => {
  const { id } = props.match.params;
  const { playing=[] } = props;

const hasPlaying = Object.keys(playing).length > 0;

  useEffect(() => {
    props.getVideoSource(id);
  }, []);

  return hasPlaying ?  (
    <div className="Player">
        <video controls autoPlay>
          <source src={props.playing.source} type="video/mp4" />
        </video>
         <div className="Player-back">
           <button type= "button" onClick={() => {props.history.goBack();}}>
             Regresar
           </button>
         </div>
    </div>
  ) : <NotFound />
};
/**  creamos la function mapstatetoprops*/
const mapStateToProps = state => {
  return {
    playing: state.playing
  };
};

const mapDispacthToProps = {
  getVideoSource
};
/** conectamos nuestro componente con redux */
export default connect(mapStateToProps,mapDispacthToProps)(Player);

¿Cómo se podría usar el find o algún otro método para filtrar elementos en un array de dos dimensiones? 😪
.

// Mi state se ve algo así
const initialState = {
	categories: [
		{
			title: "Trends",
			videos: [
				//... videos
			]
		},
		{
			title: "Platzi originals",
			videos: [
				// ...videos
			]
		}
		....
	]
}

Esta es mi propuesta, mientras hasPlaying = True, renderiza el video, sino envía una etiqueta <h1>

const Player = props => {
    const { id } = props.match.params;
    const hasPlaying = Object.keys(props.playing).length > 0;

    useEffect(() => {
        props.getVideoSource(id);
    }, []);

    return hasPlaying ?  (
        <div className="Player">
            <video controls autoPlay>
                <source src="" type="video/mp4" />  
            </video>
            <div className="Player-back">
                <button type="button" onClick={() => props.history.goBack() }>
                    Regresar
                </button>
            </div>
        </div>
    ): <h1>Descargando recurso...</h1>;
};

como en el CarouselItem tenemos la informacion de cada elemento de las listas, cuando se realiza el click sobre player, en lugar de tener un link podria tener un onClick, que cargue la información en el state.playing y luego redireccione a Player usado props.history, de esa forma al montar el componente player ya en el state esta ese valor y nos ahorramos las busquedas en las listas.

mi propuesta es:
en initialState definir playing como null
en reducers declarar una variable const error = new Error()
en lugar de devolver [] si no encuentra el video source, devolver error
en el player declarar variable const isPlayingError = playing instanceof Error
adaptar la vista de Player:

const isPlayingError = playing instanceof Error;
    return playing 
    ? isPlayingError 
        ? <Redirect to="/404" />
        :(
            <div className="Player">
                <video controls autoPlay>
                    <source src={playing.source} type="video/mp4" />
                </video>
                <div className="Player-back">
                    <button type="button" onClick={() => history.goBack()}>Regresar</button>
                </div>
            </div>
        )
    : (
      <h1>Buscando origen del video ...</h1>  
    );

para resolver el reto puedes cambiar el orden: return !hasPlaying ? <NotFound /> : ( y ya todo el codigo

      <div className="Player">
         <video controls autoPlay>
             <source src={props.playing.source}  type="video/mp4" />
         </video>
         <div className="Player-back">
           <button type="button" onClick={() => props.history.goBack()}>
               Regresar
           </button>
         </div>
      </div>
    ): [];
};

Esta fue mi solución y funciono, Mas no se si esta bien
 

Yo lo hice de esta manera, y lo hice un poco diferente a como lo hizo el profesor Oscar

import React from 'react';
import '../assets/styles/components/Player.scss';
import { connect } from 'react-redux';
import NotFound from './NotFound';

const Player = (props) => {
  const { id } = props?.match?.params;
  const video =
    props?.trends.find((trend) => trend.id.toString() === id.toString()) ||
    props?.originals.find((original) => original.id.toString() === id);
  console.log(video);

  const handleGoBack = () => {
    props.history.goBack();
  };

  if (!video) return <NotFound />;

  return (
    <div className="player">
      <video controls autoPlay>
        <source src={video.source} type="video/mp4" />
      </video>
      <div className="player-back">
        <button type="button" onClick={handleGoBack}>
          Regresar
        </button>
      </div>
    </div>
  );
};
const mapStateToProps = (state) => {
  return { trends: state.trends, originals: state.originals };
};
export default connect(mapStateToProps)(Player);

yo utilice redirect y le puse un setTimeout

  return hasPlaying ? (
    <div className='Player'>
      <video controls autoPlay>
        <source src={props.playing.source} type='video/mp4' />
      </video>
      <div className='Player-back'>
        <button type='button' onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  ) : setTimeout(() => {
    <Redirect to='/404/' />;
  }, 0);
};

Tengo una duda! El action.payload ¿Que es en nuestro archivo de Player? Que propiedad es, en que se transforma, no se si me doy a entender

const Player = props => {

    const { id } = props.match.params;
    const [ loading, setLoading ] = useState(true);
    const hasPlaying = Object.keys(props.playing).length > 0;
    

    useEffect(() => {
        props.getVideoSource(id);
        setLoading(false);
    }, []); 

    if (loading) return <h2>Cargando video...</h2>

    return hasPlaying ? (
        <div className="Player">
            <video controls autoPlay>
                <source src={props.playing.source} type="video/mp4" />
            </video>
            <div className="Player-back">
                <button type="button" onClick={() => props.history.goBack()}>
                    Regresar
                </button>
            </div>
        </div>
    )
    :
    <Redirect to='/404' />
};

const mapStateToProps = state => {
    return {
        playing: state.playing,
    }
};

const mapDispatchToProps = {
    getVideoSource,
}

export default connect(mapStateToProps, mapDispatchToProps)(Player);

Es normal no entender nada hasta este punto del curso? 😦

Para resolver lo que se indicó en el vídeo, hice lo siguiente

  return hasPlaying && (
    <div className='Player'>
      <video controls autoPlay>
        <source src={props.playing.source} type='video/mp4' />
      </video>
      <div className='Player-back'>
        <button type='button' onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  )

Le agregue algo más para poder tener en cuenta si es que el id del vídeo no existe, así quedó mi Player.jsx

/* eslint-disable react/destructuring-assignment */
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import { getVideoSource } from '../actions'
import '../assets/styles/components/Player.scss'
import NotFound from './NotFound'

const Player = (props) => {
  // Esto lo manda router al momento de generar la ruta que es en player/:id <- Eso
  const { id } = props.match.params
  const hasPlaying = Object.keys(props.playing).length > 0
  const result = props.playing

  useEffect(() => {
    props.getVideoSource(id)
  }, [])

  if (result.length === 0) {
    return <NotFound />
  }

  return hasPlaying && (
    <div className='Player'>
      <video controls autoPlay>
        <source src={props.playing.source} type='video/mp4' />
      </video>
      <div className='Player-back'>
        <button type='button' onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  )
}

const mapStateToProps = (state) => {
  return {
    playing: state.playing,
  }
}

const mapDispatchToProps = {
  getVideoSource,
}

export default connect(mapStateToProps, mapDispatchToProps)(Player)

Les dejo mi action…

    case 'GET_VIDEO_SOURCE':
      const { trends, originals } = state;
      return {
        ...state,
        playing: trends.concat(originals).find(item => item.id === Number(action.payload))
      }```

Lo que hice fue encapsular el redirect en una arrow function para que no lo ejecutara automáticamente:

: () => <Redirect to="/404/" />

Look my code!

Esta es mi solución:

const  hasPlaying = async () => {
    try {
        const response = await Object.keys(props.playing).length > 0
        return response
    }
    catch(error) {
        console.error(error)
    }
}
import React, { useLayoutEffect } from 'react';
import { connect } from 'react-redux';
import { getVideoSource } from '../actions';
import '../assets/styles/components/Player.scss';
import NotFound from './NotFound';

const Player = (props) => {
  {}
  // const { id } = props.match.params;
  const { playing, match: { params: { id } } } = props;
  const hasPlaying = Object.keys(playing).length > 0;

  useLayoutEffect(() => {
    props.getVideoSource(id);
  }, []);

  return hasPlaying ? (
    <div className='Player'>
      <video controls autoPlay>
        <source src={playing.source} type='video/mp4' />
      </video>
      <div className='Player-back'>
        <button type='button' onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  ) : <NotFound />;
};

const mapStateToProps = (state) => {
  return {
    playing: state.playing,
  };
};
const mapDispatchToProps = {
  getVideoSource,
};
export default connect(mapStateToProps, mapDispatchToProps)(Player);```
export const PlayerScreen = () => {

    const { trends, originals, playing } = useSelector(state => state.movies)
    const { goBack } = useHistory();
    const { id } = useParams();
    const dispatch = useDispatch()

    useEffect(() => {
        dispatch(moviesVideoPlayingAction(id))
    }, [trends, originals])

    return playing ? (
        <div className="Player">
            {
                playing &&
                <video controls autoPlay>
                    <source src={playing.source} type="video/mp4" />
                </video>
            }
            <div
                className="Player-back"
                onClick={() => goBack()}
            >
                <button type="button">
                    Regresar
                </button>
            </div>
        </div>
    ) : (
        <NotFoundScreen />
    )
}

Lo que he hecho ha sido cambiar la comprobación de

const hasPlaying = Object.keys(....

por una función que retorna una promesa, de esa manera si la condición se cumple resuelve con true y si no con false.

const hasPlaying = () => {
	return new Promise((resolve, reject) => {
		if (Object.keys(....)){
			resolve(true)
		} else {
			reject()
		}
	});
};

Espero que esto os ayude, aun así la lista de favoritos no me funciona con este método al igual que con el de Oscar a pesar de haberlo añadido a la lógica de mi reducer.

No se si fuí al único que se le dificulto un poco entender esta clase, quisiera compartir una explicación que me hice a mi mismo para entenderlo mejor, tal vez le pueda ayudar a alguien. 😄

Para entenderlo vamos a analizarlo paso a paso.

Estamos enviando una ruta dinámica, que varía según el id

Esto hace, que cuando demos click nos mande a dicha ruta, ahora necesito configurar este comportamiento, para que en esa ruta se configure mi source como quiero.

  1. Primero voy a hacer match con ese Id para recibirlo en mi player
  1. Luego conectaré mi app al store para traer mi playing y poder incluso modificarlo cuando reciba el id.
  1. Una vez conectada vamos a configurar el action y el reducer para poder: según el id que reciba devolver el video correspondiente y así poder tomar mi source del video.

Aquí básicamente tomo mis trends y mis originals y busco en ellos el item el cual coinsida con el id que le pasé al payload, además tengo que convertirlo en número ya que lo recibo como string.

  1. Ya que tengo mi store configurado voy a traer ciclodevida a mi componente para que cuando se monte el elemento le pasemos el id al action y este pueda traer el item que itera para usar su source.
  1. Echo esto voy a validar si hay un item reproduciendose, si es el paso paso mi componente con el source.
  1. Por último ya que esto me daba un bugg que me mandaba a 404 voy a configurar un estado que valide el loading

Hay muy buenas soluciones dadas por los compañeros en comentarios anterioes.
yo utilice el useLayoutEffect.

import React, { useLayoutEffect } from 'react';

  useLayoutEffect(() => {
    props.getVideoSource(id);
  }, []);

<NotFound />

La funcionalidad del video player se puede también usando solo el estado global, aqui esta la otra forma. (Ambas formas son validas)

import React from 'react';
import { connect } from 'react-redux';
import '../assets/styles/components/Player.scss';

const Player = (props) => {
    const { videos, history } = props;
    const { id } = props.match.params;
    let video = videos.find(el => el.id == id);
    return (
        <div className="Player">
            {video && <video autoPlay>
                <source src={video.source} media="video/mp4" />
            </video>}
            <div className="Player-back">
                <button type="button" onClick={() => history.goBack()}>Regresar</button>
            </div>
        </div>
    );
};

const mapStateToProp = state => {
    return {
        videos: [...state.trends, ...state.originals]
    };
}

export default connect(mapStateToProp, null)(Player);

A quien pueda interesar… leyendo los comentarios me di cuenta que ya los links de los videos no funcionan, entonces cambié algunos de los links (que si de 2 o 3 primeros en trends) por unos que si sirvieran a modo de tener videos que carguen y reproduzcan y videos que no…
Luego se me ocurrió este código usando “onerror” a la hora de cargar el video a modo de que si el link funciona, bien y reproduce; caso contrario, nos vamos al 404:
IMPORTANTE, en index.js, debes colocar la variable “validator” en el initial state:
const initialState = {
“user”: {},
“playing”: {},
“mylist”: [],
“validator”:0,
… todo lo demás…}

En Player.jsx :
import React,{useState,useLayoutEffect} from 'react’
import {Link} from 'react-router-dom’
import '…/assets/styles/components/Player.scss’
import {connect} from 'react-redux’
import {getVideoSource} from '…/actions’
import NotFound from '…/containers/NotFound’
import {Redirect} from ‘react-router-dom’

const Player= props =>{
const { id } = props.match.params;
const hasPlaying = Object.keys(props.playing).length >0

const [validator,setValidator]=useState(props.validator)

useLayoutEffect(()=>{
    props.getVideoSource(id);
},[])

const handler= (event)=>{
    setValidator(Object.keys(event).length)
}


return hasPlaying? (
    <div className = "Player">
        <video id='video' controls autoPlay onError={handler}>
            <source src={props.playing.source} type="video/mp4"/>
        </video>
        <div className="Player-back">
            <Link to ='/'>
                <button type="button">
                    Regresar
                </button>
            </Link>
            
        </div>
        {
            validator>0?
            <Redirect to='/404/' />:
            console.log('allGood')
        }
    </div>
): <NotFound/>

};

const mapStateToProps =(state)=>{
return{
playing:state.playing,
validator: state.validator
}
}

const mapDispatchToProps = {
getVideoSource,
}

export default connect(mapStateToProps,mapDispatchToProps)(Player)

Mi solución fue simular un “cargando…” ya que en la primera corrida, el estado no contiene nada en playing, por lo tanto se debe capturar.

Les pongo en el código un setTimeout para tener un poco de visibilidad sobre lo que realmente esta pasando:

import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { Redirect } from "react-router-dom";
import { getVideoSource } from "../actions";
import "../assets/styles/components/Player.scss";

const TEST_MP4 =
  "enlace_a_un_video.mp4";

const Player = (props) => {
  const { id } = props.match.params;
  const hasPlaying = Object.keys(props.playing).length > 0;
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    setTimeout(() => {
      props.getVideoSource(id);
      setIsLoading(false);
    }, 2000);
  }, []);

  return isLoading ? (
    <h3>Cargando {JSON.stringify(props.playing)}</h3>
  ) : hasPlaying ? (
    <div className="player">
      <video controls autoPlay>
        {/* <source src={props.playing.source} type="video/mp4" /> */}
        <source src={TEST_MP4} type="video/mp4" />
      </video>
      <div className="Player-back">
        <button
          type="button"
          onClick={() => {
            props.history.goBack();
          }}
        >
          Regresar
        </button>
      </div>
    </div>
  ) : (
    <Redirect to="/404" />
  );
};

const mapStateToProps = (state) => {
  return {
    playing: state.playing,
  };
};

const mapDispatchToProps = {
  getVideoSource,
};

export default connect(mapStateToProps, mapDispatchToProps)(Player);

no me quedo claro por que es que se muestra el notFound y luego se carga el video…

Mi solución:
Player.jsx

import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { getVideoSource } from '../actions';
import NotFound from '../components/NotFound';
import '../assets/styles/containers/Player.scss';

const Player = (props) => {
  const { playing, match } = props;
  const { id } = match.params;

  const hasPlaying = Object.keys(playing).length > 0;

  useEffect(() => {
    props.getVideoSource(id);
  }, []);

  return hasPlaying ? (
    <div>
      <div className='Player'>
        <video controls={true}>
          <source src={playing.source} type='video/mp4' />
        </video>
      </div>
      <div className='Player-back'>
        <button type='button' onClick={() => props.history.goBack()}>
          Regresar
        </button>
      </div>
    </div>
  ) : (
    <NotFound />
  );
};

const mapStateToProps = (state) => {
  return {
    playing: state.playing,
  };
};

const mapDispatchToProps = {
  getVideoSource,
};

export default connect(mapStateToProps, mapDispatchToProps)(Player);

reducers.js

    case GET_VIDEO_SOURCE:
      return {
        ...state,
        playing: state.trends.find((item) => item.id === Number(action.payload)) ||
                 state.originals.find((item) => item.id === Number(action.payload)) ||
                 [],
      };

Me da error 401, por falta de credenciales, para cargar la ruta del video

La verdad siempre he tenido dudas con los actions y los reducers, algo me dice que sólo deben utilizarse si es necesario alterar el estado global.

En este caso, lo hice sin necesidad de tener playing en el store y traje la información del video directamente desde el estado.

import React from "react";
import { isEmpty } from "lodash";
import { useSelector } from "react-redux";
import { Redirect, useHistory, useParams } from "react-router-dom";

import "../assets/styles/components/Player.scss";

export default function Player() {
  const history = useHistory();
  const { id } = useParams();

  const playing = useSelector(
    (state) =>
      state.trends.find((item) => item.id === Number(id)) ||
      state.originals.find((item) => item.id === Number(id)) ||
      {}
  );

  if (!isEmpty(playing)) {
    return (
      <div className="Player">
        <video controls autoPlay>
          <source src={playing.source} type="video/mp4" />
        </video>
        <div className="Player-back">
          <button onClick={() => history.goBack()}>Regresar</button>
        </div>
      </div>
    );
  } else {
    return <Redirect to="/404" />;
  }
const Player = props => {

    const { id } = props.match.params;

    const hasPlaying = Object.keys(props.playing).length > 0;

    useEffect(() => {
        props.getVideoSource(id);
    }, [])

    if (!props.playing) return <h3>cargando...</h3>

    return hasPlaying ? (
        <div className="Player">
            <video controls autoPlay>
                <source src={props.playing.source} type="video/mp4" />
            </video>
            <div className="Player-back">
                <button type="button" onClick={() => props.history.goBack()}>
                    Regresar
                </button>
            </div>
        </div>
    ) : <Redirect to='/404' />;
}



const mapStateToProps = state => {
    return {
        playing: state.playing
    }
}

const mapDispatchToProps = {
    getVideoSource,
}

export default connect(mapStateToProps, mapDispatchToProps)(Player);

a

n

No puedo reproducir el video se queda en negro y en la consola me muestra un error Failed to load resource: net::ERR_NAME_NOT_RESOLVED

Me he dado cuenta que para enviar y recibir el estado por medio del connect es mas tardio que el render tambien aportando que el uso de useEffect es asincrono asi que creo que ahi otra opcion para realizar un <Link/> donde no se muestre el url del video sin la necesidad de usar un playing en nuestra store , y la unica forma para que el usuario entre a ver este video en el player es haciendo click en el video donde.
1.modificariamos la ruta <Route exact path="/player/:id" component={Player}/> a <Route exact path="/player/" component={Player}/> (logrando de que la unica forma para que entre el usuario al player del video sea el interactuando con la pagina y no por medios de links)
2.luego iremos donde tenemos el link y digitamos en “to” lo siguiente: to={{pathname:/player,props:{url:url}}}
3.y listo nuestro player ya tiene capturado el url del video lo unico que falta es ir al <source/> del <video/> y su “src” sera =this.props.location.props.url(this.props son los props del constructor)

// lo que debemos colocar en el archivo donde tenemos nuestras rutas
<Route exact path="/player/" component={Player}/>
//en el archivo donde tenemos el video 
 <Link 
                    to={{
                            pathname:`/player`,
                            props:{
                                url:url
                            }
                        }}
 >
// y en el archivo del player 
constructor(props){
this.props=props
}
render(){
	<video className="player_video" controls autoPlay>
          	<source src={this.props.location.props.url} type="video/mp4"/>
         </video>
}

si alguien tiene un aporte o un fallo en mi aporte les agradeceria si me lo escriben