Skip to content

Commit

Permalink
Merge pull request #26 from SevenOutman/beta
Browse files Browse the repository at this point in the history
Graduate 1.6 Beta
  • Loading branch information
SevenOutman authored Aug 1, 2023
2 parents 0ffa21f + bed87db commit 307c9bb
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 18 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI

on:
push:
branches: [master, alpha]
branches: [master, beta]
pull_request:
branches: [master]

Expand Down
7 changes: 6 additions & 1 deletion demo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ function App() {
}}
>
<div style={{ width: 600 }}>
<APlayer audio={playlist} theme="auto" initialLoop="all" />
<APlayer
audio={playlist}
appearance="fixed"
theme="auto"
initialLoop="all"
/>
</div>
</div>
);
Expand Down
44 changes: 39 additions & 5 deletions src/components/controller.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { useCallback } from "react";
import { clsx } from "clsx";
import { ReactComponent as IconMenu } from "../assets/menu.svg";
import { ReactComponent as IconPlay } from "../assets/play.svg";
import { ReactComponent as IconPause } from "../assets/pause.svg";
import { ReactComponent as IconSkip } from "../assets/skip.svg";
import { ReactComponent as IconLrc } from "../assets/lrc.svg";
import { ReactComponent as IconOrderList } from "../assets/order-list.svg";
import { ReactComponent as IconOrderRandom } from "../assets/order-random.svg";
import { ReactComponent as IconLoopAll } from "../assets/loop-all.svg";
import { ReactComponent as IconLoopOne } from "../assets/loop-one.svg";
import { ReactComponent as IconLoopNone } from "../assets/loop-none.svg";
import { formatAudioDuration } from "../utils/formatAudioDuration";
import { ProgressBar } from "./progress";
import React, { useCallback } from "react";
import { PlaylistLoop, PlaylistOrder } from "../hooks/usePlaylist";
import { Volume } from "./volume";

Expand All @@ -25,6 +30,12 @@ type PlaybackControlsProps = {
loop: PlaylistLoop;
onLoopChange: (loop: PlaylistLoop) => void;
onSeek?: (second: number) => void;
isPlaying: boolean;
onTogglePlay?: () => void;
onSkipForward?: () => void;
onSkipBack?: () => void;
showLyrics?: boolean;
onToggleLyrics?: () => void;
};

export function PlaybackControls({
Expand All @@ -42,6 +53,12 @@ export function PlaybackControls({
loop,
onLoopChange,
onSeek,
isPlaying,
onTogglePlay,
onSkipForward,
onSkipBack,
showLyrics = true,
onToggleLyrics,
}: PlaybackControlsProps) {
// Switch order between "list" and "random"
const handleOrderButtonClick = useCallback(() => {
Expand Down Expand Up @@ -96,9 +113,18 @@ export function PlaybackControls({
{formatAudioDuration(audioDurationSeconds)}
</span>
</span>
<span className="aplayer-icon aplayer-icon-back"></span>
<span className="aplayer-icon aplayer-icon-play"></span>
<span className="aplayer-icon aplayer-icon-forward"></span>
<span className="aplayer-icon aplayer-icon-back" onClick={onSkipBack}>
<IconSkip />
</span>
<span className="aplayer-icon aplayer-icon-play" onClick={onTogglePlay}>
{isPlaying ? <IconPause /> : <IconPlay />}
</span>
<span
className="aplayer-icon aplayer-icon-forward"
onClick={onSkipForward}
>
<IconSkip />
</span>
<Volume
themeColor={themeColor}
volume={volume}
Expand Down Expand Up @@ -130,7 +156,15 @@ export function PlaybackControls({
>
<IconMenu />
</button>
<button className="aplayer-icon aplayer-icon-lrc"></button>
<button
type="button"
className={clsx("aplayer-icon aplayer-icon-lrc", {
"aplayer-icon-lrc-inactivity": !showLyrics,
})}
onClick={onToggleLyrics}
>
<IconLrc />
</button>
</div>
</div>
);
Expand Down
20 changes: 19 additions & 1 deletion src/components/list.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef } from "react";
import { clsx } from "clsx";
import { defaultThemeColor } from "../constants";
import type { ArtistInfo, AudioInfo } from "../types";
import { useCallback } from "react";

type PlaylistProps = {
open: boolean;
Expand Down Expand Up @@ -29,8 +29,26 @@ export function Playlist({
return artist.name ?? "Audio artist";
}, []);

const listRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (listRef.current) {
const listElement = listRef.current;

listElement.style.maxHeight = `${Math.min(
listElement.scrollHeight,
listMaxHeight ?? Infinity
)}px`;

return () => {
listElement.removeAttribute("style");
};
}
}, [listMaxHeight]);

return (
<div
ref={listRef}
className={clsx("aplayer-list", {
"aplayer-list-hide": !open,
})}
Expand Down
9 changes: 7 additions & 2 deletions src/components/lyrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import React, { useMemo } from "react";
import { clsx } from "clsx";

type LyricsProps = {
show: boolean;
lrcText?: string;
currentTime: number;
};

export function Lyrics({ lrcText, currentTime }: LyricsProps) {
export function Lyrics({ show, lrcText, currentTime }: LyricsProps) {
const lines = useMemo(() => parseLrc(lrcText), [lrcText]);

const currentLineIndex = useMemo(() => {
Expand All @@ -29,7 +30,11 @@ export function Lyrics({ lrcText, currentTime }: LyricsProps) {
}, [currentLineIndex]);

return (
<div className="aplayer-lrc">
<div
className={clsx("aplayer-lrc", {
"aplayer-lrc-hide": !show,
})}
>
{lrcText ? (
<div className="aplayer-lrc-contents" style={transformStyle}>
{lines.map(([, text], index) => (
Expand Down
78 changes: 70 additions & 8 deletions src/components/player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { clsx } from "clsx";

import { ReactComponent as IconPlay } from "../assets/play.svg";
import { ReactComponent as IconPause } from "../assets/pause.svg";
import { ReactComponent as IconRight } from "../assets/right.svg";
import type { ArtistInfo, AudioInfo } from "../types";
import { Playlist } from "./list";
import { PlaybackControls } from "./controller";
Expand Down Expand Up @@ -31,6 +32,11 @@ type APlayerProps = {
*/
volume?: number;

/**
* @default "normal"
*/
appearance?: "normal" | "fixed";

/**
* @default "all"
*/
Expand All @@ -52,6 +58,7 @@ type APlayerProps = {
export function APlayer({
theme = defaultThemeColor,
audio,
appearance = "normal",
volume = 0.7,
initialLoop,
initialOrder,
Expand Down Expand Up @@ -160,17 +167,42 @@ export function APlayer({
);
}, []);

const [mini, setMini] = useState(false);

const [displayLyrics, setDisplayLyrics] = useState(true);

const bodyRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (appearance === "fixed") {
if (bodyRef.current) {
const bodyElement = bodyRef.current;
// Explicitly set width on the body element
// to ensure the width transition works
bodyElement.style.width = bodyElement.offsetWidth - 18 + "px";

return () => {
bodyElement.removeAttribute("style");
};
}
}
}, [appearance]);

return (
<div
className={clsx("aplayer", {
"aplayer-fixed": appearance === "fixed",
"aplayer-loading": audioControl.isLoading,
"aplayer-withlist": hasPlaylist,
"aplayer-withlrc": Boolean(playlist.currentSong.lrc),
"aplayer-withlrc":
Boolean(playlist.currentSong.lrc) && appearance !== "fixed",
"aplayer-narrow": mini,
})}
>
<div className="aplayer-body">
<div ref={bodyRef} className="aplayer-body">
<div
className="aplayer-pic"
onClick={handlePlayButtonClick}
style={{
backgroundImage: `url("${playlist.currentSong?.cover}")`,
}}
Expand All @@ -180,7 +212,6 @@ export function APlayer({
"aplayer-button",
audioControl.isPlaying ? "aplayer-pause" : "aplayer-play"
)}
onClick={handlePlayButtonClick}
>
{audioControl.isPlaying ? <IconPause /> : <IconPlay />}
</div>
Expand All @@ -195,10 +226,13 @@ export function APlayer({
- {renderArtist(playlist.currentSong?.artist)}
</span>
</div>
<Lyrics
lrcText={playlist.currentSong.lrc}
currentTime={audioControl.currentTime ?? 0}
/>
{appearance === "fixed" ? null : (
<Lyrics
show={displayLyrics}
lrcText={playlist.currentSong.lrc}
currentTime={audioControl.currentTime ?? 0}
/>
)}
<PlaybackControls
volume={audioControl.volume ?? volume}
onChangeVolume={audioControl.setVolume}
Expand All @@ -214,12 +248,33 @@ export function APlayer({
onOrderChange={playlist.setOrder}
loop={playlist.loop}
onLoopChange={playlist.setLoop}
isPlaying={audioControl.isPlaying ?? false}
onTogglePlay={handlePlayButtonClick}
onSkipForward={() => {
if (playlist.hasNextSong) {
playlist.next();
}
}}
onSkipBack={() => {
playlist.previous();
}}
showLyrics={displayLyrics}
onToggleLyrics={() => {
setDisplayLyrics((prev) => !prev);
}}
/>
</div>
<div className="aplayer-notice" style={notice.style}>
{notice.text}
</div>
<div className="aplayer-miniswitcher"></div>
<div
className="aplayer-miniswitcher"
onClick={() => setMini((prev) => !prev)}
>
<button className="aplayer-icon">
<IconRight />
</button>
</div>
</div>
{hasPlaylist ? (
<Playlist
Expand All @@ -231,6 +286,13 @@ export function APlayer({
listMaxHeight={listMaxHeight}
/>
) : null}
{appearance === "fixed" && (
<Lyrics
show={displayLyrics}
lrcText={playlist.currentSong.lrc}
currentTime={audioControl.currentTime ?? 0}
/>
)}
</div>
);
}
14 changes: 14 additions & 0 deletions src/hooks/usePlaylist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type PlaylistState<T> = Readonly<{
currentSong: T;
hasNextSong: boolean;
next: () => void;
previous: () => void;
prioritize: (song: T) => void;
order: PlaylistOrder;
setOrder: (order: PlaylistOrder) => void;
Expand Down Expand Up @@ -79,6 +80,18 @@ export function usePlaylist<T, K>(
}
}, [nextSong]);

const previous = useCallback(() => {
setCurrentSong((prev) => {
const currentSongIndex = list.indexOf(prev);

if (currentSongIndex > 0) {
return list[currentSongIndex - 1];
}

return prev;
});
}, [list]);

const prioritize = useCallback((song: T) => {
setCurrentSong(song);
}, []);
Expand All @@ -87,6 +100,7 @@ export function usePlaylist<T, K>(
currentSong,
hasNextSong: typeof nextSong !== "undefined",
next,
previous,
prioritize,
order,
setOrder,
Expand Down

1 comment on commit 307c9bb

@vercel
Copy link

@vercel vercel bot commented on 307c9bb Aug 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.