Refactor song submission process to include YouTube metadata fetching; remove YouTube link requirement from settings for improved flexibility
This commit is contained in:
parent
77df851e95
commit
a7929cf144
@ -8,8 +8,7 @@ const LobbyScreen = () => {
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [settings, setSettings] = useState({
|
||||
songsPerPlayer: 4,
|
||||
maxPlayers: 10,
|
||||
requireYoutubeLinks: true
|
||||
maxPlayers: 10
|
||||
});
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
@ -79,7 +78,6 @@ const LobbyScreen = () => {
|
||||
<h3><FontAwesomeIcon icon={faGear} /> Game Settings</h3>
|
||||
<p>Songs per player: {settings.songsPerPlayer}</p>
|
||||
<p>Max players: {settings.maxPlayers}</p>
|
||||
<p>YouTube links required: {settings.requireYoutubeLinks ? 'Yes' : 'No'}</p>
|
||||
|
||||
{isHost && (
|
||||
<button className="btn secondary" onClick={() => setShowSettings(true)}>
|
||||
@ -140,19 +138,6 @@ const LobbyScreen = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group checkbox">
|
||||
<label htmlFor="requireYoutubeLinks">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="requireYoutubeLinks"
|
||||
name="requireYoutubeLinks"
|
||||
checked={settings.requireYoutubeLinks}
|
||||
onChange={handleSettingsChange}
|
||||
/>
|
||||
Require YouTube links
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="modal-actions">
|
||||
<button className="btn secondary" onClick={() => setShowSettings(false)}>
|
||||
Cancel
|
||||
|
@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faPlus, faTrash, faCheck, faMusic, faVideoCamera, faSearch, faSpinner, faExternalLinkAlt, faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
const SongSubmissionScreen = () => {
|
||||
const { lobby, currentPlayer, addSong, removeSong, setPlayerReady, searchYouTube } = useGame();
|
||||
const { lobby, currentPlayer, addSong, removeSong, setPlayerReady, searchYouTube, getYouTubeMetadata } = useGame();
|
||||
const [songs, setSongs] = useState([]);
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const [songForm, setSongForm] = useState({
|
||||
@ -51,7 +51,7 @@ const SongSubmissionScreen = () => {
|
||||
if (player.songs && Array.isArray(player.songs)) {
|
||||
console.log('Found player songs for', player.name, ':', player.songs.length);
|
||||
console.log('Songs data:', player.songs);
|
||||
setSongs(player.songs);
|
||||
setSongs(player.songs || []);
|
||||
} else {
|
||||
console.log('No songs array for player:', player);
|
||||
setSongs([]);
|
||||
@ -65,7 +65,7 @@ const SongSubmissionScreen = () => {
|
||||
};
|
||||
|
||||
fetchPlayerSongs();
|
||||
// Include lobby in the dependency array to ensure this runs when the lobby state changes
|
||||
// Important: This effect should run whenever the lobby state changes
|
||||
}, [lobby, currentPlayer]);
|
||||
|
||||
// Extract video ID from YouTube URL
|
||||
@ -116,12 +116,34 @@ const SongSubmissionScreen = () => {
|
||||
const videoId = extractVideoId(value);
|
||||
if (videoId) {
|
||||
setSongForm({ youtubeLink: value });
|
||||
setSelectedVideo({
|
||||
|
||||
// Set basic information immediately for a responsive UI
|
||||
const basicVideoInfo = {
|
||||
id: videoId,
|
||||
url: value,
|
||||
thumbnail: `https://img.youtube.com/vi/${videoId}/mqdefault.jpg`
|
||||
});
|
||||
};
|
||||
setSelectedVideo(basicVideoInfo);
|
||||
setSearchResults([]); // Clear any existing search results
|
||||
|
||||
// Fetch additional metadata from the server
|
||||
try {
|
||||
setIsLoadingMetadata(true);
|
||||
const metadata = await getYouTubeMetadata(videoId);
|
||||
if (metadata) {
|
||||
// Update selected video with full metadata
|
||||
setSelectedVideo({
|
||||
...basicVideoInfo,
|
||||
title: metadata.title || 'Unknown Title',
|
||||
artist: metadata.artist || 'Unknown Artist',
|
||||
thumbnail: metadata.thumbnail || basicVideoInfo.thumbnail
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching video metadata:', error);
|
||||
} finally {
|
||||
setIsLoadingMetadata(false);
|
||||
}
|
||||
} else if (value.trim()) {
|
||||
// Clear any previous timeout
|
||||
if (searchTimeout.current) {
|
||||
@ -175,6 +197,9 @@ const SongSubmissionScreen = () => {
|
||||
// Use selected video data if available, otherwise fallback to search query or direct input
|
||||
let songData;
|
||||
|
||||
// Generate a consistent ID format that will work for deletion later
|
||||
const generateSongId = () => `song_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
if (selectedVideo) {
|
||||
// We have a selected video with full details - use all available metadata
|
||||
songData = {
|
||||
@ -182,7 +207,7 @@ const SongSubmissionScreen = () => {
|
||||
title: selectedVideo.title,
|
||||
artist: selectedVideo.artist,
|
||||
thumbnail: selectedVideo.thumbnail,
|
||||
id: `song_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` // Generate a unique ID
|
||||
id: generateSongId() // Consistent ID format
|
||||
};
|
||||
|
||||
console.log("Adding song with full metadata:", songData);
|
||||
@ -199,7 +224,7 @@ const SongSubmissionScreen = () => {
|
||||
youtubeLink: youtubeLink,
|
||||
// Include the videoId to make server-side processing easier
|
||||
videoId: videoId,
|
||||
id: `song_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` // Generate a unique ID
|
||||
id: generateSongId() // Consistent ID format
|
||||
};
|
||||
|
||||
console.log("Adding song with YouTube link:", songData);
|
||||
@ -210,12 +235,28 @@ const SongSubmissionScreen = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Add the song and manually update the local songs array to ensure UI reflects the change
|
||||
addSong(songData);
|
||||
// Add the song and update UI when the server responds
|
||||
// We use the Promise-based approach to wait for the server-generated ID for proper deletion
|
||||
|
||||
// Optimistically add the song to the local state to immediately reflect changes in UI
|
||||
// Note: The server response will ultimately override this if needed
|
||||
setSongs(prevSongs => [...prevSongs, songData]);
|
||||
// Add the song with callback to update songs when the server responds
|
||||
addSong(songData)
|
||||
.then((updatedLobby) => {
|
||||
console.log('Song added successfully, received updated lobby:', updatedLobby);
|
||||
if (updatedLobby && currentPlayer) {
|
||||
const player = updatedLobby.players.find(p => p.id === currentPlayer.id);
|
||||
if (player && player.songs) {
|
||||
console.log('Setting songs from addSong response:', player.songs);
|
||||
setSongs([...player.songs]); // Create a new array to ensure state update
|
||||
} else {
|
||||
console.warn('Player or songs not found in updated lobby');
|
||||
}
|
||||
} else {
|
||||
console.warn('Missing lobby or currentPlayer in response');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error adding song:', error);
|
||||
});
|
||||
|
||||
// Reset form
|
||||
setSongForm({ youtubeLink: '' });
|
||||
|
@ -86,7 +86,7 @@ export function GameProvider({ children }) {
|
||||
// Save game info when lobby is joined
|
||||
useEffect(() => {
|
||||
if (lobby && currentPlayer) {
|
||||
localStorage.setItem('songBattleGame', JSON.stringify({
|
||||
sessionStorage.setItem('songBattleGame', JSON.stringify({
|
||||
lobbyId: lobby.id,
|
||||
playerName: currentPlayer.name
|
||||
}));
|
||||
@ -330,18 +330,20 @@ export function GameProvider({ children }) {
|
||||
const addSong = (song) => {
|
||||
if (!socket || !isConnected || !lobby) {
|
||||
setError('Not connected to server or no active lobby');
|
||||
return;
|
||||
return Promise.reject('Not connected to server or no active lobby');
|
||||
}
|
||||
|
||||
console.log('Attempting to add song:', song);
|
||||
console.log('Current player state:', currentPlayer);
|
||||
console.log('Current lobby state before adding song:', lobby);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
socket.emit('add_song', { song }, (response) => {
|
||||
console.log('Song addition response:', response);
|
||||
if (response.error) {
|
||||
console.error('Error adding song:', response.error);
|
||||
setError(response.error);
|
||||
reject(response.error);
|
||||
} else if (response.lobby) {
|
||||
// Log detailed lobby state for debugging
|
||||
console.log('Song added successfully, full lobby response:', response.lobby);
|
||||
@ -365,11 +367,16 @@ export function GameProvider({ children }) {
|
||||
console.log('Lobby state after update (may not reflect immediate changes):', lobby);
|
||||
console.log('Updated lobby that was set:', updatedLobby);
|
||||
}, 0);
|
||||
|
||||
// Resolve with the updated lobby
|
||||
resolve(updatedLobby);
|
||||
} else {
|
||||
console.error('Song addition succeeded but no lobby data was returned');
|
||||
setError('Failed to update song list');
|
||||
reject('Failed to update song list');
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Search for songs on YouTube
|
||||
@ -392,6 +399,26 @@ export function GameProvider({ children }) {
|
||||
});
|
||||
};
|
||||
|
||||
// Get metadata for a YouTube video by ID
|
||||
const getYouTubeMetadata = (videoId) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!socket || !isConnected) {
|
||||
setError('Not connected to server');
|
||||
reject('Not connected to server');
|
||||
return;
|
||||
}
|
||||
|
||||
socket.emit('get_video_metadata', { videoId }, (response) => {
|
||||
if (response.error) {
|
||||
setError(response.error);
|
||||
reject(response.error);
|
||||
} else {
|
||||
resolve(response.metadata);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Remove a song
|
||||
const removeSong = (songId) => {
|
||||
if (!socket || !isConnected || !lobby) {
|
||||
@ -456,7 +483,8 @@ export function GameProvider({ children }) {
|
||||
setPlayerReady,
|
||||
submitVote,
|
||||
leaveLobby,
|
||||
searchYouTube
|
||||
searchYouTube,
|
||||
getYouTubeMetadata
|
||||
}}>
|
||||
{children}
|
||||
</GameContext.Provider>
|
||||
|
@ -28,8 +28,7 @@ class GameManager {
|
||||
settings: {
|
||||
songsPerPlayer: 3,
|
||||
maxPlayers: 10,
|
||||
minPlayers: 3,
|
||||
requireYoutubeLinks: false
|
||||
minPlayers: 3
|
||||
},
|
||||
players: [{
|
||||
id: hostId,
|
||||
@ -300,16 +299,13 @@ class GameManager {
|
||||
return { error: 'Maximum number of songs reached' };
|
||||
}
|
||||
|
||||
// We only require the YouTube link now
|
||||
if (!song.youtubeLink) {
|
||||
return { error: 'YouTube link is required' };
|
||||
}
|
||||
|
||||
// If the YouTube link isn't valid, return an error
|
||||
// If we have a YouTube link, validate it
|
||||
if (song.youtubeLink) {
|
||||
const videoId = await youtubeAPI.extractVideoId(song.youtubeLink);
|
||||
if (!videoId) {
|
||||
return { error: 'Invalid YouTube link' };
|
||||
}
|
||||
}
|
||||
|
||||
// Handle async metadata fetching
|
||||
return this._addSongToPlayer(playerIndex, lobby, lobbyId, song, playerId);
|
||||
@ -329,6 +325,20 @@ class GameManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata for a single YouTube video
|
||||
* @param {string} videoId - YouTube video ID
|
||||
* @returns {Promise<Object>} Video metadata
|
||||
*/
|
||||
async getYouTubeVideoMetadata(videoId) {
|
||||
try {
|
||||
return await youtubeAPI.getVideoMetadata(videoId);
|
||||
} catch (error) {
|
||||
console.error('Error getting YouTube video metadata:', error);
|
||||
return { error: 'Failed to get video metadata' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to add a song to a player
|
||||
* @param {number} playerIndex - Index of the player in the lobby
|
||||
|
@ -150,10 +150,11 @@ io.on('connection', (socket) => {
|
||||
});
|
||||
|
||||
// Add a song
|
||||
socket.on('add_song', ({ song }, callback) => {
|
||||
socket.on('add_song', async ({ song }, callback) => {
|
||||
try {
|
||||
console.log(`[DEBUG] Attempting to add song: ${JSON.stringify(song)}`);
|
||||
const result = gameManager.addSong(socket.id, song);
|
||||
// Fix: Properly await the result of the async addSong function
|
||||
const result = await gameManager.addSong(socket.id, song);
|
||||
|
||||
if (result.error) {
|
||||
console.log(`[DEBUG] Error adding song: ${result.error}`);
|
||||
@ -225,6 +226,24 @@ io.on('connection', (socket) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Get metadata for a single YouTube video
|
||||
socket.on('get_video_metadata', async ({ videoId }, callback) => {
|
||||
try {
|
||||
if (!videoId || typeof videoId !== 'string') {
|
||||
if (callback) callback({ error: 'Invalid video ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
const metadata = await gameManager.getYouTubeVideoMetadata(videoId);
|
||||
|
||||
// Send response to client
|
||||
if (callback) callback({ metadata });
|
||||
} catch (error) {
|
||||
console.error('Error getting YouTube metadata:', error);
|
||||
if (callback) callback({ error: 'Failed to get YouTube metadata' });
|
||||
}
|
||||
});
|
||||
|
||||
// Mark player as ready
|
||||
socket.on('player_ready', (_, callback) => {
|
||||
try {
|
||||
|
Loading…
x
Reference in New Issue
Block a user