Skip to content

Commit

Permalink
feat(player): implement volume adjusting feature (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
async3619 authored Sep 18, 2023
1 parent 8fd9b47 commit 90d97cb
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 0 deletions.
2 changes: 2 additions & 0 deletions apps/renderer/src/components/Player/Panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { usePlayer } from "@components/Player/context";
import { RepeatMode } from "@components/Player/Provider";

import { AlbumArtView, Description, NowPlaying, Root, Section } from "@components/Player/Panel.styles";
import { VolumeControl } from "@components/VolumeControl";

export interface PlayerPanelProps {}

Expand Down Expand Up @@ -97,6 +98,7 @@ export function PlayerPanel({}: PlayerPanelProps) {
<Section>
<Box width="100%" display="flex" justifyContent="flex-end" alignItems="center" pr={1}>
<Stack direction="row" spacing={1}>
<VolumeControl />
<IconButton size="small" onClick={handleRepeatClick}>
{repeatModeIcon}
</IconButton>
Expand Down
41 changes: 41 additions & 0 deletions apps/renderer/src/components/Player/Provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export interface Player {
canSeekBackward(): boolean;
canSeekForward(): boolean;

setMuted(muted: boolean): void;
setVolume(volume: number, persist?: boolean): void;
volume: number;
muted: boolean;

setRepeatMode(mode: RepeatMode): void;
repeatMode: RepeatMode;

Expand Down Expand Up @@ -55,6 +60,11 @@ export class PlayerProvider extends React.Component<React.PropsWithChildren, Pla
setRepeatMode: this.setRepeatMode.bind(this),
repeatMode: RepeatMode.None,

setMuted: this.setMuted.bind(this),
setVolume: this.setVolume.bind(this),
volume: Number(localStorage.getItem("volume")) || 0.5,
muted: localStorage.getItem("muted") === "true",

canSeekBackward: this.canSeekBackward.bind(this),
canSeekForward: this.canSeekForward.bind(this),

Expand All @@ -81,6 +91,13 @@ export class PlayerProvider extends React.Component<React.PropsWithChildren, Pla
return this.audioRef.current;
}

public componentDidMount() {
const { volume, muted } = this.state;

this.audio.volume = volume;
this.audio.muted = muted;
}

public setRepeatMode(mode: RepeatMode) {
this.setState({ repeatMode: mode });
}
Expand Down Expand Up @@ -170,6 +187,30 @@ export class PlayerProvider extends React.Component<React.PropsWithChildren, Pla
);
}

public setMuted(muted: boolean) {
this.audio.muted = muted;

localStorage.setItem("muted", String(muted));
this.setState({ muted });

if (!muted) {
this.setVolume(this.state.volume);
}
}
public setVolume(volume: number, persist = false) {
if (!volume && persist) {
this.setMuted(true);
return;
}

this.audio.volume = volume;

if (persist) {
localStorage.setItem("volume", String(volume));
this.setState({ volume });
}
}

public canSeekBackward() {
const { repeatMode, currentIndex } = this.state;
if (repeatMode === RepeatMode.All) {
Expand Down
34 changes: 34 additions & 0 deletions apps/renderer/src/components/VolumeControl.styles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import styled from "@emotion/styled";

export const Control = styled.div`
max-width: 0;
width: ${({ theme }) => theme.spacing(10)};
margin-right: ${({ theme }) => theme.spacing(1.5)};
box-sizing: border-box;
opacity: 0;
transition: ${({ theme }) =>
theme.transitions.create(["opacity"], {
duration: theme.transitions.duration.shortest,
})};
&:has(:active) {
opacity: 1;
max-width: ${({ theme }) => theme.spacing(10)};
}
`;

export const Root = styled.div`
display: flex;
align-items: center;
&:hover {
${Control} {
max-width: ${({ theme }) => theme.spacing(10)};
opacity: 1;
}
}
`;
73 changes: 73 additions & 0 deletions apps/renderer/src/components/VolumeControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from "react";

import { Slider } from "ui";

import { IconButton } from "@mui/material";
import VolumeUpRoundedIcon from "@mui/icons-material/VolumeUpRounded";
import VolumeOffIcon from "@mui/icons-material/VolumeOff";

import { Control, Root } from "@components/VolumeControl.styles";
import { usePlayer } from "@components/Player/context";

export function VolumeControl() {
const player = usePlayer();
const [muted, setMuted] = React.useState(player.muted);
const [volume, setVolume] = React.useState(player.volume);

React.useEffect(() => {
setVolume(player.volume);
}, [player.volume]);

const handleChange = React.useCallback(
(value: number) => {
player.setVolume(value);
setVolume(value);

if (value === 0) {
setMuted(true);
} else if (muted) {
setMuted(false);
}
},
[player, muted],
);
const handleChangeEnd = React.useCallback(
(value: number) => {
player.setVolume(value, true);
setVolume(value);

if (value === 0) {
setMuted(true);
} else if (muted) {
setMuted(false);
}
},
[player, muted],
);

const handleClick = React.useCallback(() => {
const isMuted = player.muted;

player.setMuted(!isMuted);
setMuted(!isMuted);
setVolume(isMuted ? player.volume : 0);
}, [player]);

return (
<Root>
<Control>
<Slider
value={muted ? 0 : volume}
min={0}
max={1}
onValueChange={handleChange}
onValueChangeEnd={handleChangeEnd}
/>
</Control>
<IconButton size="small" onClick={handleClick}>
{!muted && <VolumeUpRoundedIcon fontSize="small" />}
{muted && <VolumeOffIcon fontSize="small" />}
</IconButton>
</Root>
);
}

0 comments on commit 90d97cb

Please sign in to comment.