Skip to content

Commit

Permalink
Use YT playlist management to enable media controls (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
ras1 authored Aug 7, 2024
1 parent 92394aa commit 6c74f8f
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 61 deletions.
2 changes: 1 addition & 1 deletion src/components/ButtonList.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ function ButtonList(props) {
<div className="buttonList">
<div>
<button
onClick={props.pickNextVideo}
onClick={() => props.playerRef.current?.getInternalPlayer()?.nextVideo()}
className='sytButton nextVideoButton'
>Next</button>
<ToggleableButton
Expand Down
10 changes: 6 additions & 4 deletions src/components/CurrentVideoInfo.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React from 'react';
import React, { useState } from 'react';
import { Textfit } from '@tomplum/react-textfit';

function CurrentVideoInfo(props) {
const [hideDescription, setHideDescription] = useState(true)

var titleOpts = {
flex: 1,
height: "110px",
Expand All @@ -16,15 +18,15 @@ function CurrentVideoInfo(props) {
}
return (
<>
<div id="videoTitleDisplay" className='currentVideoTitle' onClick={() => props.setCollapseDescription(!props.collapseDescription)}>
<div id="videoTitleDisplay" className='currentVideoTitle' onClick={() => setHideDescription(!hideDescription)}>
<Textfit style={titleOpts} mode="multi" max={44}>{props.currentVideo.title}</Textfit>
<a style={anchorOpts} href={`https://youtube.com/watch?v=${props.currentVideo.video_id}`} target="_blank" rel="noopener noreferrer">
<img alt='Go to Youtube' src={'/arrow-up-right.svg'}></img>
<img alt='Go to Youtube Site' src={'/arrow-up-right.svg'}></img>
</a>
</div>

<div className="contentRow">
<div className={`${props.collapseDescription ? 'hide' : ''}`}>
<div className={`${hideDescription ? 'hide' : ''}`}>
{props.currentVideo.description}
</div>
</div>
Expand Down
35 changes: 24 additions & 11 deletions src/components/Player.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,41 @@ import React from 'react';
import ReactPlayer from 'react-player';

function Player(props) {
function onReady(event) {
// no-op
const YOUTUBE_PLAYLIST_VIDEO_LIMIT = 200;

function onPlay() {
var internalPlayer = props.playerRef.current?.getInternalPlayer();
var videoData = internalPlayer.getVideoData();
var video = props.videos.find(v => v.video_id === videoData.video_id);
props.setCurrentVideo(video);
}

function onError() {
var internalPlayer = props.playerRef.current?.getInternalPlayer();
var videoUrl = internalPlayer.getVideoUrl()
if (!videoUrl) return;
console.log(`BROKEN VIDEO: ${getVideoId(videoUrl)}`);
internalPlayer.nextVideo();
}

function onError(event) {
console.log(`BROKEN VIDEO: ${JSON.stringify(props.videoId, null, 2)}`)
props.onEnd()
function getVideoId(url) {
const urlParams = new URLSearchParams(new URL(url).search);
return urlParams.get('v');
}

return(
<div className='playerWrapper' style={{display: props.hideVideo ? 'none' : 'block'}}>
return (
<div className='playerWrapper' style={{ display: props.hideVideo ? 'none' : 'block' }}>
<ReactPlayer
className='player'
url={`https://www.youtube.com/watch?v=${props.videoId}`}
ref={props.playerRef}
url={props.videos?.slice(0, YOUTUBE_PLAYLIST_VIDEO_LIMIT).map(v => `https://www.youtube.com/watch?v=${v.video_id}`)}
controls={true}
loop={props.repeatVideo}
onReady={(event) => onReady(event)}
onEnded={() => props.onEnd()}
onError={() => onError()}
playing={true}
width={"100%"}
height={"800px"}
onPlay={onPlay}
onError={onError}
config={{
youtube: {
playerVars: {},
Expand Down
2 changes: 1 addition & 1 deletion src/components/PlaylistSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default function PlaylistSelector(props) {
)}
</div>
<button
onClick={() => props.onShuffle()}
onClick={() => props.onCombinePlaylists()}
className="sytButton"
>Combine Playlists</button>
<button
Expand Down
2 changes: 1 addition & 1 deletion src/components/PlaylistSelectorItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ function PlaylistSelectorItem(props) {
return (
<div className="playlistSelectorItem">
<label className="checkbox_label">
<input
<input
type="checkbox"
value={props.value}
checked={props.checked}
Expand Down
36 changes: 16 additions & 20 deletions src/hooks/UseVideoPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,31 @@ import { useState, useEffect } from 'react';
import useVideoDataFetcher from '../hooks/UseVideoDataFetcher';

export default function useVideoPlayer(selectedPlaylistIds) {
const [currentVideo, setCurrentVideo] = useState({})
const [playedVideos, setPlayedVideos] = useState([])
const [hideDescription, setHideDescription] = useState(true)
const [currentVideos, setCurrentVideos] = useState([])
const [videoFetchResult, setVideoFetchPlaylistIds] = useVideoDataFetcher(selectedPlaylistIds)

useEffect(pickNextVideo, [videoFetchResult])
useEffect(shuffleVideos, [videoFetchResult])

function pickNextVideo() {
if (!videoFetchResult.isLoaded) { return; }
function randomInteger(min, max) { return Math.floor(Math.random() * (max - min)) + min; }
const videoIndex = randomInteger(0, videoFetchResult.videos.length);
const nextVideo = videoFetchResult.videos[videoIndex];
playVideo(nextVideo);
function shuffleVideos() {
if (!videoFetchResult.isLoaded) {
setCurrentVideos([]) // fix to allow combine playlist to work, breaks youtube playlist control
return;
}
const shuffledVideos = fisherYatesShuffle([...videoFetchResult.videos])
setCurrentVideos(shuffledVideos)
}

function playVideo(video) {
setCurrentVideo(video);
setHideDescription(true);
setPlayedVideos(played => [...played, video])
function fisherYatesShuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]]; // Swap elements
}
return array;
}

return {
currentVideo,
playedVideos,
pickNextVideo,
playVideo,
currentVideos,
setVideoFetchPlaylistIds,
hideDescription,
setHideDescription,
isLoaded: videoFetchResult.isLoaded
};
}
34 changes: 11 additions & 23 deletions src/pages/ShufflePlayer.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
import React, { useState } from 'react';
import React, { useState, useRef } from 'react';
import AppConstants from '../AppConstants';
import ButtonList from '../components/ButtonList';
import CurrentVideoInfo from '../components/CurrentVideoInfo';
import LoadingPlaceholder from '../components/LoadingPlaceholder';
import Player from '../components/Player';
import PlaylistSelector from '../components/PlaylistSelector';
import VideoSelector from '../components/VideoSelector';
import useLocalStorage from '../hooks/UseLocalStorage';
import usePlaylistDataFetcher from '../hooks/UsePlaylistDataFetcher';
import useVideoPlayer from '../hooks/UseVideoPlayer';

export default function ShufflePlayer() {
const [currentVideo, setCurrentVideo] = useState({})
const [repeatVideo, setRepeatVideo] = useState(false)
const [hideVideo, setHideVideo] = useState(true)

const playerRef = useRef(null);

const [selectedPlaylistIds, setSelectedPlaylistIds] = useLocalStorage(AppConstants.SelectedPlaylistIdsKey, []);
const [playlists, setPlaylists] = usePlaylistDataFetcher(selectedPlaylistIds);

const {
currentVideo,
playedVideos,
pickNextVideo,
playVideo,
currentVideos,
setVideoFetchPlaylistIds,
hideDescription,
setHideDescription,
isLoaded
} = useVideoPlayer(selectedPlaylistIds);

Expand All @@ -34,38 +31,29 @@ export default function ShufflePlayer() {

return <div>
<Player
videoId={currentVideo.video_id}
onEnd={() => pickNextVideo()}
videos={currentVideos}
repeatVideo={repeatVideo}
hideVideo={hideVideo}
playerRef={playerRef}
setCurrentVideo={setCurrentVideo}
/>

<CurrentVideoInfo
currentVideo={currentVideo}
collapseDescription={hideDescription}
setCollapseDescription={setHideDescription}
/>
<CurrentVideoInfo currentVideo={currentVideo} />

<div className='contentRow'>
<ButtonList
repeatVideo={repeatVideo}
setRepeatVideo={setRepeatVideo}
hideVideo={hideVideo}
setHideVideo={setHideVideo}
pickNextVideo={() => pickNextVideo()}
playerRef={playerRef}
/>
<PlaylistSelector
playlists={playlists}
isCollapsedDefault={true}
onShuffle={() => setVideoFetchPlaylistIds(selectedPlaylistIds)}
onCombinePlaylists={() => setVideoFetchPlaylistIds(selectedPlaylistIds)}
setPlaylistIds={setSelectedPlaylistIds}
setLoadedPlaylists={setPlaylists}
className='playlistSelector'
/>
<VideoSelector
videos={playedVideos}
isCollapsedDefault={true}
onVideoClicked={video => playVideo(video)}
/>
</div>
</div>
Expand Down

0 comments on commit 6c74f8f

Please sign in to comment.