feat(page): 주요 음악 서비스 관행에 맞춰 조작 보완 + 화면·버튼 동작 문서화

화면별 사용 흐름과 버튼 동작을 전수 정리한 뒤, 스포티파이 웹 플레이어·유튜브 뮤직·애플 뮤직에서
공통으로 쓰이는 관행과 대조해 빠져 있던 것을 채웠다.

추가/변경:
- 스피커 아이콘을 장식에서 음소거 토글 버튼으로(해제 시 직전 볼륨 복원). 세 서비스 공통 동작.
- 키보드 단축키: Space 재생/일시정지, M 음소거, 좌우 5초 탐색, Ctrl/Cmd+오른쪽 다음 곡.
  입력창에 포커스가 있으면 가로채지 않는다.
- 대기열 맨 위에 "지금 재생 중" 블록 추가. 재생바가 이미 폴링하는 값을 위로 올려 공유하므로
  같은 API 를 두 번 호출하지 않는다.
- 대기열의 곡을 누르면 그 곡부터 재생. 봇에 점프 액션이 없어 queue/set 으로 대상 곡부터 남긴 뒤
  skip 하는 방식이며, 건너뛴 곡이 빠지는 것까지 스포티파이 대기열과 동일하다.
- 검색 결과 카드에 재생시간 표기(같은 제목의 짧은 클립과 원곡 구분에 필요).

문서(page/README.md 5-1~5-3):
- 화면(ViewMode)별 진입 경로·보이는 것·할 수 있는 일 표
- 버튼/조작별 동작·호출 API·비활성 조건 전수 표
- 타 서비스 관행 대비 개선 항목과 미지원 항목(이전 곡·반복 재생: 봇 API 한계) 명시

검증: tsc 통과, lint 에러 0. 브라우저에서 음소거 토글(볼륨 0↔60 복원), Space 로 재생↔일시정지 전환,
"지금 재생 중" 표시, 대기열 곡의 "…부터 재생" 동작, 재생시간 표기를 실제 렌더링으로 확인.
This commit is contained in:
2026-09-29 14:05:38 +09:00
parent 4021862c51
commit 2b7a91774d
5 changed files with 257 additions and 23 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useCallback, useState } from "react";
import { useSession } from "next-auth/react";
import { ListMusic, X } from "lucide-react";
import TopNav from "@/components/layout/TopNav";
@@ -8,7 +8,7 @@ import LeftSidebar from "@/components/layout/LeftSidebar";
import MainContent from "@/components/player/MainContent";
import QueueSidebar from "@/components/player/QueueSidebar";
import PlayerBar from "@/components/player/PlayerBar";
import type { DiscordServer } from "@/types/music";
import type { DiscordServer, Track } from "@/types/music";
// 화면 모드 타입 정의
export type ViewMode = "SERVER_LIST" | "SERVER_DETAIL" | "SEARCH_RESULT";
@@ -20,6 +20,9 @@ export default function MusicPlayerLayout() {
const [searchQuery, setSearchQuery] = useState("");
// 모바일에서 대기열은 상시 컬럼이 아니라 오른쪽에서 밀려나오는 패널로 띄운다.
const [isQueueOpen, setIsQueueOpen] = useState(false);
// 재생바가 이미 폴링하는 현재 곡을 대기열 패널과 공유한다(같은 API 를 두 번 부르지 않기 위함).
const [nowPlaying, setNowPlaying] = useState<Track | null>(null);
const handleNowPlayingChange = useCallback((t: Track | null) => setNowPlaying(t), []);
const isAuthed = status === "authenticated";
// 서버를 고르기 전에는 재생/대기열이 의미가 없어, 화면을 차지하지 않게 숨긴다.
@@ -97,6 +100,7 @@ export default function MusicPlayerLayout() {
>
<QueueSidebar
selectedServer={selectedServer}
nowPlaying={nowPlaying}
onClose={() => setIsQueueOpen(false)}
/>
</div>
@@ -109,6 +113,7 @@ export default function MusicPlayerLayout() {
selectedServer={selectedServer}
onToggleQueue={() => setIsQueueOpen((v) => !v)}
isQueueOpen={isQueueOpen}
onNowPlayingChange={handleNowPlayingChange}
/>
)}