diff --git a/client/src/common/styles/colors.sass b/client/src/common/styles/colors.sass index d8fafb0..8b1f658 100644 --- a/client/src/common/styles/colors.sass +++ b/client/src/common/styles/colors.sass @@ -11,5 +11,6 @@ $pink: #ff6bb3 $blue: #4d9dff $purple: #9c6bff $cyan: #6bffea +$orange: #ff9b6b $yellow: #ffde6b $mint-green: #85ffbd \ No newline at end of file diff --git a/client/src/components/YouTubePlayer/YouTubePlayer.jsx b/client/src/components/YouTubePlayer/YouTubePlayer.jsx index 29749b6..33b0db2 100644 --- a/client/src/components/YouTubePlayer/YouTubePlayer.jsx +++ b/client/src/components/YouTubePlayer/YouTubePlayer.jsx @@ -4,17 +4,18 @@ import './styles.sass'; const YouTubePlayer = ({ videoId, autoplay = false, - volume = 50, startTime = 45, onReady = () => {}, onError = () => {}, + onPlayerReady = null, className = '' }) => { const iframeRef = useRef(null); const playerRef = useRef(null); const [isLoaded, setIsLoaded] = useState(false); + const [customStartTime] = useState(startTime); - console.log("YouTubePlayer rendering with videoId:", videoId, "volume:", volume, "startTime:", startTime); + console.log("YouTubePlayer rendering with videoId:", videoId, "startTime:", startTime); useEffect(() => { if (!window.YT) { @@ -58,18 +59,38 @@ const YouTubePlayer = ({ 'showinfo': 0, 'rel': 0, 'autoplay': autoplay ? 1 : 0, - 'start': startTime + 'start': startTime, + 'modestbranding': 1, + 'fs': 0, }, events: { 'onReady': (event) => { - console.log("YouTube player ready"); - event.target.setVolume(volume); - if (autoplay) { - event.target.seekTo(startTime); - event.target.playVideo(); + const player = event.target; + console.log("YouTube player ready event fired"); + + playerRef.current = player; + + try { + const hasSeekTo = typeof player.seekTo === 'function'; + console.log(`Player has seekTo: ${hasSeekTo}`); + + // Start playback if needed + if (autoplay) { + player.seekTo(startTime || 0); + player.playVideo(); + } + + setIsLoaded(true); + onReady(); + + if (typeof onPlayerReady === 'function') { + console.log("Providing player instance to parent"); + onPlayerReady(player); + } + } catch (err) { + console.error("Error in player ready handler:", err); + onError(err); } - setIsLoaded(true); - onReady(); }, 'onError': (event) => { console.error("YouTube player error:", event); @@ -94,21 +115,20 @@ const YouTubePlayer = ({ } } }; - }, [videoId, autoplay, onReady, onError, startTime]); + }, [videoId, autoplay, onReady, onError, startTime, customStartTime]); useEffect(() => { - if (playerRef.current && isLoaded) { - console.log("Setting YouTube volume to:", volume); + if (playerRef.current && isLoaded && customStartTime !== startTime) { try { - playerRef.current.setVolume(volume); + playerRef.current.seekTo(customStartTime); } catch (error) { - console.error("Error setting volume:", error); + console.error("Error seeking to time:", error); } } - }, [volume, isLoaded]); + }, [customStartTime, isLoaded]); return ( -
+
); diff --git a/client/src/components/YouTubePlayer/styles.sass b/client/src/components/YouTubePlayer/styles.sass index 64781be..93218d9 100644 --- a/client/src/components/YouTubePlayer/styles.sass +++ b/client/src/components/YouTubePlayer/styles.sass @@ -1,19 +1,16 @@ .youtube-player-container - position: fixed - right: 20px - bottom: 120px - width: 300px - height: 200px - z-index: 100 + width: 100% + margin: 10px 0 + border-radius: 12px + overflow: hidden + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3) + background: #000 - &.hidden-player - width: 1px - height: 1px - opacity: 0 - overflow: hidden - position: absolute - left: -9999px - top: -9999px + &.visible-player + height: 200px + max-width: 100% + opacity: 1 + z-index: 1 .youtube-embed width: 100% diff --git a/client/src/pages/Game/Game.jsx b/client/src/pages/Game/Game.jsx index 897d930..af8e1cc 100644 --- a/client/src/pages/Game/Game.jsx +++ b/client/src/pages/Game/Game.jsx @@ -10,7 +10,9 @@ import { faClock, faCrown, faPaperPlane, - faCheckCircle + faCheckCircle, + faVolumeUp, + faStepForward } from "@fortawesome/free-solid-svg-icons"; import { fetchPlaylistSongs } from "@/services/youtubeService.js"; import YouTubePlayer from "../../components/YouTubePlayer/YouTubePlayer"; @@ -45,13 +47,13 @@ export const Game = () => { const timerIntervalRef = useRef(null); const [allSongs, setAllSongs] = useState([]); - const [playerVolume, setPlayerVolume] = useState(30); const [playbackError, setPlaybackError] = useState(null); const [songsLoading, setSongsLoading] = useState(false); useEffect(() => { if (!connected) return; + const eventHandlers = { "roles-assigned": (roles) => { const myRole = roles[socket?.id]; @@ -256,8 +258,24 @@ export const Game = () => { setTimeLeft(0); }, [send]); - const handlePlayerReady = useCallback(() => { + const handlePlayerReady = useCallback((player) => { + console.log("Player ready handler called with:", player); setPlaybackError(null); + + if (player && typeof player.seekTo === 'function') { + console.log("Setting valid YouTube player instance"); + setYtPlayer(player); + + try { + const currentTime = player.getCurrentTime(); + console.log(`Player initialized at ${currentTime}s`); + } catch (err) { + console.error("Error testing player methods:", err); + } + } else { + console.error("Invalid YouTube player instance provided:", player); + setPlaybackError("YouTube player initialization issue"); + } }, []); const handlePlayerError = useCallback((error) => { @@ -305,7 +323,22 @@ export const Game = () => {
von {currentSong.artist}
-

Spiele diesen Song mit dem Tonregler!

+ +
+ +
+ +
+

Use the player above to listen, and the tone slider to play!

+
)} @@ -521,40 +554,12 @@ export const Game = () => { /> )} - {role === "composer" && currentSong && currentSong.youtubeId && ( - - )} - {playbackError && (
{playbackError}
)} - {phase === "composing" && role === "composer" && currentSong && ( -
-
- Music Volume: - setPlayerVolume(parseInt(e.target.value))} - className="volume-slider" - /> -
-
- )} - {songsLoading && (
Loading songs... diff --git a/client/src/pages/Game/styles.sass b/client/src/pages/Game/styles.sass index 017f742..c5bba8a 100644 --- a/client/src/pages/Game/styles.sass +++ b/client/src/pages/Game/styles.sass @@ -264,6 +264,11 @@ .song-display text-align: center + display: flex + flex-direction: column + align-items: center + width: 100% + margin-bottom: 30px .song-card display: flex @@ -274,6 +279,11 @@ border-radius: 20px box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3) backdrop-filter: blur(10px) + background: rgba(255, 255, 255, 0.08) + padding: 25px + border-radius: 20px + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3) + backdrop-filter: blur(10px) border: 1px solid rgba(255, 255, 255, 0.2) max-width: 500px margin: 20px auto @@ -304,295 +314,13 @@ font-size: 16px color: rgba(255, 255, 255, 0.7) -.phase-header - display: flex - justify-content: space-between - align-items: center - width: 100% - margin-bottom: 30px - - h3 - font-size: 24px - margin: 0 - color: $white - text-shadow: 0 0 10px rgba(255, 255, 255, 0.3) - - .timer - background: rgba(255, 255, 255, 0.1) - padding: 10px 20px - border-radius: 15px - font-size: 18px - display: flex - align-items: center - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2) - border: 1px solid rgba(255, 255, 255, 0.1) - - svg - margin-right: 10px - color: $yellow - -// Improved song selection grid -.song-selection - width: 100% - - .instruction - text-align: center - font-size: 20px - margin-bottom: 30px - color: $white - - .song-grid - display: grid - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)) - gap: 20px - margin-top: 20px - - .song-option - background: rgba(255, 255, 255, 0.1) - border-radius: 15px - overflow: hidden - cursor: pointer - transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) - border: 2px solid transparent - box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3) - height: 100% - - &:hover:not(.disabled) - transform: translateY(-8px) scale(1.03) - box-shadow: 0 15px 30px rgba(0, 0, 0, 0.4), 0 0 25px rgba(255, 255, 255, 0.1) - background: rgba(255, 255, 255, 0.15) - - &.selected - border: 2px solid $yellow - box-shadow: 0 0 25px rgba(255, 255, 0, 0.4), 0 10px 30px rgba(0, 0, 0, 0.4) - background: rgba(255, 255, 255, 0.15) - - &.disabled - opacity: 0.7 - cursor: default - - .song-image - position: relative - - img - width: 100% - height: 150px - object-fit: cover - - .selection-indicator - position: absolute - top: 10px - right: 10px - background: $yellow - width: 30px - height: 30px - border-radius: 50% - display: flex - align-items: center - justify-content: center - box-shadow: 0 0 15px rgba(255, 255, 0, 0.7) - - .song-details - padding: 15px - - .song-title - font-weight: bold - font-size: 16px - color: $white - - .song-artist - font-size: 14px - opacity: 0.7 - margin-top: 5px - -// Results phase improvements -.results-phase - text-align: center - - h3 - font-size: 28px - margin-bottom: 40px - - .round-results - background: rgba(255, 255, 255, 0.08) - backdrop-filter: blur(10px) - border-radius: 25px - padding: 30px - box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4) - border: 1px solid rgba(255, 255, 255, 0.15) - max-width: 600px - margin: 0 auto - - .scoreboard + .music-controls margin-top: 30px - padding-top: 20px - border-top: 1px solid rgba(255, 255, 255, 0.1) - - h4 - margin-bottom: 15px - font-size: 20px - - .scores - display: grid - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)) - gap: 10px - - .score-entry - background: rgba(255, 255, 255, 0.1) - border-radius: 10px - padding: 10px 15px - display: flex - justify-content: space-between - align-items: center - - &:nth-child(1) - background: linear-gradient(135deg, rgba(255, 215, 0, 0.2), rgba(255, 215, 0, 0.1)) - box-shadow: 0 0 20px rgba(255, 215, 0, 0.3) - - &:nth-child(2) - background: linear-gradient(135deg, rgba(192, 192, 192, 0.2), rgba(192, 192, 192, 0.1)) - - &:nth-child(3) - background: linear-gradient(135deg, rgba(205, 127, 50, 0.2), rgba(205, 127, 50, 0.1)) - - .player-name - display: flex - align-items: center - - .host-icon - color: $yellow - margin-left: 5px - -// Responsive adjustments -@media (max-width: 900px) - .game-layout - flex-direction: column - - .main-content - margin-right: 0 - margin-bottom: 20px - - .chat-panel - width: 100% - max-height: 300px - -// Animations -@keyframes slide-in-right - 0% - transform: translateX(30px) - opacity: 0 - 100% - transform: translateX(0) - opacity: 1 - -// ...existing animations... - - .song-display + width: 100% + max-width: 400px display: flex flex-direction: column align-items: center - justify-content: center - width: 50% - margin-right: 20px - color: $white - text-align: center - - h2 - font-size: 52pt - color: $white - margin-bottom: 25px - position: relative - z-index: 2 - background: linear-gradient(135deg, $pink, $blue 45%, $mint-green 65%, $yellow 85%, $pink) - background-size: 300% 100% - animation: title-shimmer 10s infinite alternate ease-in-out, title-float 6s infinite ease-in-out - -webkit-background-clip: text - background-clip: text - -webkit-text-fill-color: transparent - filter: drop-shadow(0 0 15px rgba(255, 255, 255, 0.7)) - letter-spacing: 0.1em - font-weight: bold - - &:before - content: "ToneGuessr" - position: absolute - z-index: -1 - left: 0 - top: 0 - background: none - -webkit-text-fill-color: transparent - filter: blur(15px) brightness(1.3) - opacity: 0.6 - width: 100% - height: 100% - - .song-card - display: flex - flex-direction: row - align-items: center - background: rgba(255, 255, 255, 0.08) - padding: 25px - border-radius: 20px - box-shadow: 0 4px 30px rgba(0, 0, 0, 0.2), 0 0 20px rgba(255, 255, 255, 0.1) - backdrop-filter: blur(10px) - border: 1px solid rgba(255, 255, 255, 0.2) - max-width: 500px - transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) - will-change: transform, box-shadow - transform: translateZ(0) - backface-visibility: hidden - animation: card-pulse 6s infinite alternate ease-in-out - - &:hover - transform: translateY(-10px) scale(1.02) - box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3), 0 0 30px rgba(255, 255, 255, 0.15) - border: 1px solid rgba(255, 255, 255, 0.4) - - img - width: 120px - height: 120px - border-radius: 15px - margin-right: 25px - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4) - transition: transform 0.3s ease - will-change: transform - transform: translateZ(0) - animation: album-rotate 10s infinite alternate ease-in-out - - &:hover - transform: scale(1.1) rotate(5deg) - box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5), 0 0 40px rgba(102, 204, 255, 0.3) - - .song-info - display: flex - flex-direction: column - align-items: flex-start - - .song-names - font-size: 28pt - color: $white - margin-bottom: 10px - text-shadow: 0 0 10px rgba(255, 255, 255, 0.5) - animation: text-shimmer 5s infinite alternate ease-in-out - - .song-description - font-size: 16pt - color: $border - opacity: 0.8 - position: relative - - &:after - content: "" - position: absolute - bottom: -5px - left: 0 - width: 0% - height: 1px - background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.7), transparent) - transition: all 0.4s ease - - &:hover:after - width: 100% .chat-window width: 50% @@ -963,56 +691,13 @@ opacity: 1 transform: scale(1.05) -.volume-controls - position: fixed - top: 70px - right: 20px - background: rgba(0, 0, 0, 0.5) - padding: 10px 15px - border-radius: 10px - display: flex - align-items: center - z-index: 10 - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3) - - span - color: white - margin-right: 10px - font-size: 14px - - .volume-slider - width: 100px - height: 6px - -webkit-appearance: none - appearance: none - background: rgba(255, 255, 255, 0.2) - border-radius: 3px - outline: none - - &::-webkit-slider-thumb - -webkit-appearance: none - appearance: none - width: 16px - height: 16px - border-radius: 50% - background: $yellow - cursor: pointer - box-shadow: 0 0 10px rgba(255, 255, 0, 0.5) - - &::-moz-range-thumb - width: 16px - height: 16px - border-radius: 50% - background: $yellow - cursor: pointer - box-shadow: 0 0 10px rgba(255, 255, 0, 0.5) - .playback-error position: fixed bottom: 10px left: 50% transform: translateX(-50%) background: rgba(255, 0, 0, 0.7) + margin-left: 10px color: white padding: 8px 20px border-radius: 20px @@ -1025,4 +710,114 @@ 0%, 80% opacity: 1 100% - opacity: 0 \ No newline at end of file + opacity: 0 + +.player-container + position: fixed + left: 20px + bottom: 20px + display: flex + flex-direction: column + align-items: flex-start + z-index: 100 + background: rgba(0, 0, 0, 0.5) + padding: 10px + border-radius: 10px + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3) + max-width: 320px + + .player-controls + width: 100% + margin-top: 10px + position: relative + left: auto + top: auto + right: auto + background: none + box-shadow: none + padding: 0 + + .volume-controls + width: 100% + display: flex + align-items: center + background: none + padding: 5px 0 + position: static + box-shadow: none + + span + white-space: nowrap + + .volume-slider + flex: 1 + margin-left: 10px + +.composer-player + width: 300px + height: 169px + border-radius: 8px + overflow: hidden + +.composer-player + width: 300px + height: 169px + border-radius: 8px + overflow: hidden + +.position-jump-button + display: block + margin-top: 10px + padding: 8px 15px + background: linear-gradient(135deg, $yellow, $orange) + color: #fff + border: none + border-radius: 8px + font-size: 14px + font-weight: 600 + cursor: pointer + transition: all 0.2s ease + width: 100% + + &:hover + background: linear-gradient(135deg, lighten($yellow, 5%), lighten($orange, 5%)) + transform: translateY(-2px) + box-shadow: 0 4px 15px rgba(255, 204, 0, 0.3) + + &:active + transform: translateY(0) + +.player-container + width: 100% + margin-top: 20px + background: rgba(20, 20, 20, 0.5) + backdrop-filter: blur(10px) + padding: 15px + border-radius: 15px + border: 1px solid rgba(255, 255, 255, 0.1) + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3) + + h4 + color: $white + margin: 0 0 15px 0 + text-align: center + font-size: 16px + + .embedded-player + width: 100% + border-radius: 8px + overflow: hidden + +.song-player-container + width: 100% + max-width: 500px + margin: 20px auto + border-radius: 15px + overflow: hidden + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25) + background: rgba(0, 0, 0, 0.5) + + .song-embedded-player + width: 100% + height: auto + aspect-ratio: 16 / 9 \ No newline at end of file