I am building a simple music player but where I fail is at trying to execute one item from the array at a time. I am using React H5 Audio Player package to play the music. I am currently mapping through all the songs but I don't know how to properly play one at a time. I appreciate any help. I've been stuck on this for a few days.
import { SongContext } from '../../SongContext';
import AudioPlayer from 'react-h5-audio-player';
import 'react-h5-audio-player/lib/styles.css';
import './Player.css';
const Player = () => {
const { songs } = useContext(SongContext);
return (
<>
{songs.length > 0 &&
songs.map((song) => (
<div className="player" key={song.id}>
<AudioPlayer
// autoPlay
// src={song.preview}
showJumpControls={false}
customVolumeControls={[]}
customAdditionalControls={[]}
onPlay={() => console.log('playing')}
/>
</div>
))}
</>
);
};
export default Player;
Don't map all the songs at once, take a song by index (currentSong), and when it's done, use the onEnded event to increment the index, so the next one would play.
Example (codepen):
const Player = () => {
const { songs } = useContext(SongContext);
const [currentSong, setCurrentSong] = useState(0);
const song = songs[currentSong];
if(!song) return null; // don't render the player when no song is available
return (
<div className="player">
<AudioPlayer
autoPlay
src={song.preview}
showJumpControls={false}
customVolumeControls={[]}
customAdditionalControls={[]}
onPlay={() => console.log('playing')}
onEnded={() => setCurrentSong(i => i + 1)}
/>
</div>
);
};
Related
I was creating this React component and I was astonished by the fact that when I clicked on the video I got false on console immediatelly after the video started. I was expecting that it would print true when the video started, and false when it finished. I think I'm confusing the lifecycle of this particular component, and I would be really grateful if someone could clarify this to me.
const Video = ({src, type, index, isAutoPlay}) => {
const [play, setPlay] = useState(isAutoPlay)
const playRef = useRef();
useEffect(() => {
if (play && playRef.current) {
playRef.current.play();
}
return () => setPlay(false)
}, [play]);
return (
<>
<video
className="slide"
ref={playRef}
onClick={() => {
setPlay(true);
console.log(play);}}>
<source src={src} type={type} key={index}/>
Your browser does not support the video tag.
</video>
</>
)
}
I have a list of movies that I got from an API and I displayed them using the Map method, I'm trying to work on an option that when I hover on one of the cards I want to show the trailer of that specific movie in the place of the image. The problem that I face is when I hover, the video trailer is playing on all the cards, I would like to know how I can play that video on hover for a specific card.
My card component
const MovieCard = ({moviesList}) => {
const [isHover, setIsHover] = useState(false);
const trailer =
"https://player.vimeo.com/external/371433846.sd.mp4?s=236da2f3c0fd273d2c6d9a064f3ae35579b2bbdf&profile_id=139&oauth2_token_id=57447761";
return (
moviesList.map((singleMovie)=> {
const {id , title, poster_path, overview} = singleMovie;
return (
<article key={id} className="card"
onMouseEnter= {()=> setIsHover(true)}
onMouseLeave={()=> setIsHover(false)}>
<img src={`${ImgPath}` + poster_path} alt={title} className="image"/>
{isHover && <video src={trailer} autoPlay={true} loop></video>}
<div className="body-card">
<h1>{title}</h1>
<p>{`${overview.substring(0,200)}...`}</p>
</div>
</article>
)
})
)
}
export default MovieCard
Instead of boolean flag you can store the id and frankly, this functionality should go in the individual Card and you should render that Card in the loop.
Here's how you can do in the same implementation.
const MovieCard = ({moviesList}) => {
const [selected, setSelected] = useState(null);
const trailer =
"https://player.vimeo.com/external/371433846.sd.mp4?s=236da2f3c0fd273d2c6d9a064f3ae35579b2bbdf&profile_id=139&oauth2_token_id=57447761";
return (
moviesList.map((singleMovie)=> {
const {id , title, poster_path, overview} = singleMovie;
return (
<article key={id} className="card"
onMouseEnter= {()=> setSelected(id)}
onMouseLeave={()=> setSelected(null)}>
<img src={`${ImgPath}` + poster_path} alt={title} className="image"/>
{selected === id && <video src={trailer} autoPlay={true} loop></video>}
<div className="body-card">
<h1>{title}</h1>
<p>{`${overview.substring(0,200)}...`}</p>
</div>
</article>
)
})
)
}
Your movieCard component not actually a movieCard component. Because you are taking the list of movie as a props so this is a movieCardList component. Instead doing that you can create a movieCard component that renders one movieCard element and inside of that component you can show the trail video in separatly.
const movieCard =({movie} ) ={
const handleTrailerShow = () =>{}
return <div onSomeEvent={handleTrailerShow} >
....
</div>
}
And in your main component
const movieCardList =({movieList} ) ={
return movieList.map(movie=>
<MovieCard movie={movie} />
}
I'm using react-player https://github.com/cookpete/react-player to play my videos. My problem is, how can I pause other videos while selected video is playing?
const videoRef = useRef();
const updateVideoHandler = async (videoId, videoTitle) => {
setSelectedVideoId(videoId);
if (!selectedVideoId) {
videoRef?.current?.player?.player?.onPause();
}
};
<ReactPlayer
ref={videoRef}
onPlay={() => updateVideoHandler(video.id, video.title)}
playsinline={true}
playing={true}
controls={true}
url={video?.url}
width="100%"
height="100%"
playIcon={
<div
className="play-icon"
role="button"
tabIndex={0}
style={{ outline: "none" }}
>
{" "}
<img src="/images/play.png" alt="" />
</div>
}
light={video?.pic}
/>;
You could store all player instances in a Context and use a Provider and Consumer to pause all players if one starts playing.
Since you pass a playing boolean to ReactPlayer, you can easily store a id or reference of the current playing player.
For example:
PlayerProvider.jsx
export const PlayerContext = React.createContext({
play: (playerId) => true,
pause: (playerId) => true,
isPlaying: (playerId) => false,
});
function PlayerProvider({ children }) {
// store the id of the current playing player
const [playing, setPlaying] = useState('');
// set playing to the given id
const play = playerId => setPlaying(playerId);
// unset the playing player
const pause = () => setPlaying(false);
// returns true if the given playerId is playing
const isPlaying = playerId => playerId === playing;
return (
<PlayerContext.Provider value={{ play, pause, isPlaying }}>
{children}
</PlayerContext.Provider>
)
}
export default PlayerProvider;
Player.jsx
import { PlayerContext } from './PlayerProvider';
function Player({ video, id }) {
const { isPlaying, play, pause } = useContext(PlayerContext);
<ReactPlayer
ref={videoRef}
playsinline={true}
playing={isPlaying(id)}
controls={true}
url={video?.url}
width="100%"
height="100%"
onPause={() => pause(id)}
onEnded={() => pause(id)}
onClickPreview={() => play(id)}
playIcon={
<div
className="play-icon"
role="button"
tabIndex={0}
style={{ outline: "none" }}
>
{" "}
<img src="/images/play.png" alt="" />
</div>
}
light={video?.pic}
/>;
}
export default Player;
Page.jsx
import PlayerProvider from './PlayerProvider';
import Player from './Player';
function Page() {
return (
<PlayerProvider>
<Player video="/path/to/video1.mp4" id="player1" />
<Player video="/path/to/video2.mp4" id="player2" />
<Player video="/path/to/video3.mp4" id="player3" />
</PlayerProvider>
)
}
Actially this is very easy process to get
try it out
export const VideoPlayer = () =>{
const videoooo = useRef();
const pauseVideo = () => {
//at the place of pauseVideo you can use "stopVideo", "playVideo"
videoooo.current.contentWindow.postMessage(
'{"event":"command","func":"pauseVideo","args":""}',
"*"
);
};
return(<> <iframe
ref={videoooo}
id="myVideoId"
src="https://www.youtube.com/embed/Q63qjIXMqwU?enablejsapi=1"
></iframe>
<button
onClick={() => {
pauseVideo();
}}
>
Pause
</button></>)
}
it is very easy and useful syntax from js
// unset the playing player
const pause = () => setPlaying(false);
if you remove this line from the code it is working perfectly, when one video is already playing and you click on the second video, onPause is invoked and it is calling the pause function due to that setPlaying updating the playing value due to that page rerendering and in that time playing value is false and it does not match with any video id and that's why every video is stopped playing.
I implemented it using video-react here :
react-video-js
You need to use
controls,
preload='auto',
autoplay
Or
You can also pop-up a modal and show videos and use this
<div>
<button onClick={this.playVideo}>
Play!
</button>
<button onClick={this.pauseVideo}>
Pause!
</button>
</div>
where you need to store the states inside these onClick for play and pause depending upon the useref of a particular video else if you use a Modal then destroy it after close so that video doesn't play once you close.
I am creating a Tic-Tac-Toe Game and i reached the point i set an array of objects players as a state, each object in the array contains an id represents the id of clicked square and the player which is 'X' or 'O', but i couldn't render them on their corresponding squares, How can i use that array of objects to put the letter in the right place
const Game = () => {
//example [{id: 5, player: 'X'}, {id: 0, player; 'O'}]
const [players, setPlayers] = useState([]);
const [player, setPlayer] = useState('X')
const [picks, setPicks] = useState([])
const handleSquareClick = (id) => {
!picks.includes(id) && setPicks([...picks, id])
player === 'X' ? setPlayer('O') : setPlayer('X');
setPlayers(players => [...players, { id, player }])
}
return (
<div class="game">
<ul class="squares">
{
[...Array(9)].map((_, idx) => {
return (
<Square
players={players}
handleSquareClick={handleSquareClick}
id={idx}
picks={picks}
/>
)
})
}
</ul>
</div>
)
}
const Square = ({ players, handleSquareClick, id, picks }) => {
return (
<li
className="square"
onClick={() => handleSquareClick(id)}
>
{}
</li>
)
}
I would be thankful if someone told me how to make it or what is a better way to do it from the start (a better logic)
codesandbox
So I saw multiple things that could be fixed.
First, I'm not sure what players should do in the code (seems to me like a pick history) so I didnt removed it, but if not needed you can remove it. In this code it is not used (only for updating it).
Now, I changed the picks value to be an array of 9 which every element is a string (equals X or O), and the index to the element is the id of the Square clicked.
So if i clicked on Square 4 and I'm player X, then picks[4]="X".
Now I fixed another thing. You changed the current player (with setPlayer) before checking if the pick was valid. Then you could get into a situation that a player shouldn't pick a square because it was already picked, and you didn't update the square, but the other player has now his turn. So, I put the player === "X" ? setPlayer("O") : setPlayer("X"); inside the validity check of the square (if (picks[id] === null)).
import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
return (
<div className="App">
<Game />
</div>
);
}
const Game = () => {
const [players, setPlayers] = useState([]);
const [player, setPlayer] = useState("X");
const [picks, setPicks] = useState(new Array(9).fill(null));
const handleSquareClick = id => {
setPlayers(players => [...players, { id, player }]);
if (picks[id] === null) {
let newArr = picks;
newArr[id] = player;
setPicks(newArr);
player === "X" ? setPlayer("O") : setPlayer("X");
}
};
Finally, I passed to Square a new property called squarePick which should let the specific Sqaure know, what string to render (using the xidxand the array ofpicks`).
return (
<div class="game">
<ul class="squares">
{[...Array(9)].map((_, idx) => {
return (
<Square
handleSquareClick={handleSquareClick}
id={idx}
squarePick={picks[idx]}
/>
);
})}
</ul>
</div>
);
};
const Square = ({ handleSquareClick, id, squarePick }) => {
return (
<li className="square" onClick={() => handleSquareClick(id)}>
{squarePick}
</li>
);
};
Hope that helped you and it was understandable.
I have a Playlist component that is importing n number of <AudioPlayer /> components dependent on how many tracks are in the playlist.
What I need to do is pause all other <AudioPlayer /> components except the one that is currently playing. Otherwise, you can play multiple players at once which is no bueno.
In my Playlist component, I'm able to grab both the currently playing player as well as the 'not playing' players: when a player is played, it fires the onPlay() handler which grabs the refs of all of the currently mounted players, then I compare that to the playlist and finally, extract the players that are currently not playing.
What I'm stuck in is updating the state of the players so that when I press play on a different player, all of the other players are paused.
The <AudioPlayer /> components have a prop of playing which I should be able to use to pass down the updated playing state when a new player is played.
How can I do this?
Here is my Playlist.js code:
import React, { Component } from 'react'
import AudioPlayer from 'react-cl-audio-player'
const streamUrl = 'https://mystreamurl.com/'
const waveUrl = 'https://mywaveformurl.com/waveforms/'
class Playlist extends Component {
state = {
playlist: this.props.tracks,
playing: false,
currentPlayer: '',
notPlaying: []
}
onPlay = (refName, index) => {
// Get list of AudioPlayer components from refs
const players = Object.keys(this.refs).map((key, i) => {
return this.refs[key]
})
if (players) {
// get AudioPlayer that is playing
const currentPlayer = players[index]
// get playlist items that are not playing
const notPlaying = this.state.playlist.filter(
(track, trackIndex) => {
return trackIndex !== index
}
)
// Compare playlist items that are not playing to AudioPlayer list
// to find AudioPlayer components that are not playing
var notPlayingPlayers = players.filter(function(obj) {
return notPlaying.some(function(obj2) {
return obj.props.alt == obj2.track_audio
})
})
console.log('Not Playing Players', notPlayingPlayers)
// ^^ this gets me the <AudioPlayer /> components that arent playing
const notPlayingPause = notPlayingPlayers.map((item, x) => {
this.setState({
playing: false // I know this isn't correct.
})
})
}
}
render() {
const tracks = this.props.tracks
const sku = this.props.sku
const price = this.props.price
const addToCart = this.props.addToCart
const trackList = tracks.map((track, index) => {
const songs = [
{
url: `${streamUrl}${track.track_audio}_128.mp3`,
cover: '',
artist: {
name: ' ',
song: track.track_title
}
}
]
return (
<li
key={sku + '-' + track.track_audio}
className="playlist-track"
>
<span className="playlist-name">{track.track_title}</span>
<AudioPlayer
key={track.track_audio}
index={index}
songs={songs}
autoplay={false}
src={`${waveUrl}${track.track_audio}.png`}
alt={track.track_audio}
ref={track.track_audio + index}
onPlay={this.onPlay.bind(this, 'player', index)}
playing={this.state.playing}
/>
<span className="playlist-price">$1.99</span>
<button
type="button"
className="button playlist-button"
onClick={() => {
addToCart({
product_id: track.track_product_id,
quantity: 1,
price: price,
cart_item_data: {
name: track.track_title
}
})
}}
>
Add To Cart
</button>
</li>
)
})
return (
<div className="playlist-wrap">
<ul className="playlist">{trackList}</ul>
</div>
)
}
}
export default Playlist
Here's an image if it helps to make it more clear: