Skip to content

Commit

Permalink
Merge pull request #421 from bcc-code/feature/314_volume-control
Browse files Browse the repository at this point in the history
Volume control & draggable sliders
  • Loading branch information
kkuepper committed May 16, 2024
2 parents 3eceae8 + 00dc16e commit 9b2b8d8
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 48 deletions.
3 changes: 3 additions & 0 deletions assets/icons/icon.audio.off.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/icons/icon.audio.on.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 7 additions & 47 deletions components/media-player/MediaPlayerOpen.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,10 @@ const {
fastForward,
repeatStatus,
} = useNuxtApp().$mediaPlayer;
const onPointerUpProgressBar = (event: PointerEvent) => {
const rect = (event.currentTarget as Element)?.getBoundingClientRect();
currentPosition.value =
((event.clientX - rect.left) / rect.width) * currentTrackDuration.value;
};
const onPointerDownProgressBar = () => {
// TODO: let user drag the progress-bar on mouse-down,
// update the time while keeping the song playing,
// and update the players position only on mouse-up.
};
</script>

<template>
<div>
<div class="flex max-h-full flex-col">
<div class="px-3 py-6">
<div class="flex h-60 items-center justify-center">
<div class="relative z-10 overflow-hidden">
Expand Down Expand Up @@ -88,41 +77,9 @@ const onPointerDownProgressBar = () => {
</TextMarquee>
</div>
</div>
<div class="px-4 py-2">
<div class="group flex h-3 items-center py-2">
<svg
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="width-full h-2 w-full overflow-hidden rounded-full transition-all duration-200 ease-out group-hover:h-3"
@pointerdown="onPointerDownProgressBar"
@pointerup="onPointerUpProgressBar"
@click.stop
>
<rect width="100%" height="100%" class="fill-background-2" />
<rect
v-if="
Number.isFinite(currentPosition) &&
Number.isFinite(currentTrackDuration)
"
:width="(currentPosition / currentTrackDuration) * 100 + '%'"
height="100%"
class="fill-label-1"
/>
</svg>
</div>
<div class="flex justify-between py-0.5 text-sm">
<span>
<TimeDuration :duration="currentPosition"></TimeDuration
></span>
<span>
<TimeDuration
:duration="
Math.max(Math.floor(currentTrackDuration) - currentPosition, 0)
"
></TimeDuration
></span>
</div>
</div>
<MediaPlayerPositionSlider />
<div class="flex justify-evenly px-4 py-2">
<button
:class="isLoading ? 'text-label-4 ' : 'border hover:text-3xl'"
Expand Down Expand Up @@ -187,6 +144,9 @@ const onPointerDownProgressBar = () => {
<NuxtIcon name="icon.skip.large" filled />
</button>
</div>
<MediaPlayerVolumeSlider />
<div class="absolute left-4 right-4 top-4 z-10 flex justify-between">
<div>
<div
Expand Down
90 changes: 90 additions & 0 deletions components/media-player/PositionSlider.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<script setup lang="ts">
import * as slider from "@zag-js/slider";
import { normalizeProps, useMachine } from "@zag-js/vue";
const { currentPosition, currentTrackDuration } = useNuxtApp().$mediaPlayer;
const newPosition = ref<number | null>(null);
const [positionState, positionSend] = useMachine(
slider.machine({
id: "position",
value: [0],
step: 0.01,
onValueChange(details) {
const [value] = details.value;
if (value) newPosition.value = value;
},
onValueChangeEnd(details) {
const [value] = details.value;
if (value) {
currentPosition.value = (value / 100) * currentTrackDuration.value;
newPosition.value = null;
}
},
}),
);
const positionSlider = computed(() =>
slider.connect(positionState.value, positionSend, normalizeProps),
);
const currentOrNewPosition = computed(() =>
newPosition.value === null
? currentPosition.value
: (newPosition.value / 100) * currentTrackDuration.value,
);
watch([currentPosition, currentTrackDuration], () => {
if (newPosition.value === null && currentTrackDuration.value > 0) {
const value = (currentPosition.value / currentTrackDuration.value) * 100;
positionSlider.value.setValue([value]);
newPosition.value = null;
}
});
</script>

<template>
<div class="group/position px-4 py-2" v-bind="positionSlider.rootProps">
<div class="flex items-center">
<div class="h-7"></div>
<div
class="h-6 w-full transition-all duration-200 group-hover/position:h-7"
>
<div
v-bind="positionSlider.controlProps"
class="h-full cursor-pointer py-2"
>
<div class="h-full overflow-hidden rounded-full">
<div
v-bind="positionSlider.trackProps"
class="h-full cursor-pointer bg-background-2"
>
<div
v-bind="positionSlider.rangeProps"
class="h-full cursor-pointer bg-label-1"
/>
</div>
</div>
<div
v-for="(_, index) in positionSlider.value"
:key="index"
v-bind="positionSlider.getThumbProps({ index })"
>
<input v-bind="positionSlider.getHiddenInputProps({ index })" />
</div>
</div>
</div>
</div>
<div class="flex justify-between py-0.5 text-sm">
<span>
<TimeDuration :duration="currentOrNewPosition"></TimeDuration
></span>
<span>
<TimeDuration
:duration="
Math.max(Math.floor(currentTrackDuration) - currentOrNewPosition, 0)
"
></TimeDuration
></span>
</div>
</div>
</template>
69 changes: 69 additions & 0 deletions components/media-player/VolumeSlider.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<script setup lang="ts">
import * as slider from "@zag-js/slider";
import { normalizeProps, useMachine } from "@zag-js/vue";
const { volume } = useNuxtApp().$mediaPlayer;
const [volumeState, volumeSend] = useMachine(
slider.machine({
id: "volume",
value: [volume.value * 100],
onValueChange(details) {
const [value] = details.value;
if (value) volume.value = value / 100;
},
}),
);
const volumeSlider = computed(() =>
slider.connect(volumeState.value, volumeSend, normalizeProps),
);
const setVolume = (value: number) => {
volume.value = value;
volumeSlider.value.setValue([value * 100]);
};
</script>
<template>
<div class="px-4 pb-2 pt-5" v-bind="volumeSlider.rootProps">
<div
class="group/volume flex items-center gap-3 rounded-3xl border border-label-separator px-[16px] py-[1px]"
>
<NuxtIcon
name="icon.audio.off"
class="my-1.5 cursor-pointer text-2xl"
@click.stop="setVolume(0)"
/>
<div
class="h-8 w-full transition-all duration-200 group-hover/volume:h-9"
>
<div
v-bind="volumeSlider.controlProps"
class="h-full cursor-pointer py-3"
>
<div class="h-full overflow-hidden rounded-full">
<div
v-bind="volumeSlider.trackProps"
class="h-full cursor-pointer bg-background-2"
>
<div
v-bind="volumeSlider.rangeProps"
class="h-full cursor-pointer bg-label-1"
/>
</div>
</div>
<div
v-for="(_, index) in volumeSlider.value"
:key="index"
v-bind="volumeSlider.getThumbProps({ index })"
>
<input v-bind="volumeSlider.getHiddenInputProps({ index })" />
</div>
</div>
</div>
<NuxtIcon
name="icon.audio.on"
class="cursor-pointer text-2xl"
@click.stop="setVolume(1)"
/>
</div>
</div>
</template>
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"@pinia/nuxt": "^0.5.1",
"@vueuse/core": "^10.7.2",
"@vueuse/math": "^10.9.0",
"@zag-js/slider": "^0.50.0",
"@zag-js/vue": "^0.50.0",
"class-variance-authority": "^0.7.0",
"cross-env": "^7.0.3",
"electron-store": "^8.1.0",
Expand Down
4 changes: 4 additions & 0 deletions plugins/mediaPlayer/MediaTrack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ export default class MediaTrack {
this.audioElement.pause();
}

setVolume(volume: number) {
this.audioElement.volume = volume;
}

endPortion() {
const time = this.audioElement.currentTime;
const now = new Date();
Expand Down
15 changes: 15 additions & 0 deletions plugins/mediaPlayer/mediaPlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface MediaPlayer {
currentTrack: ComputedRef<UnwrapRef<TrackModel> | undefined>;
currentPosition: Ref<number>;
currentTrackDuration: ComputedRef<number>;
volume: Ref<number>;
setQueue: (
queue: TrackModel[],
index?: number,
Expand Down Expand Up @@ -67,6 +68,8 @@ export const initMediaPlayer = (

let nextStartPosition = 0;

const volume = ref(1);

function stop() {
if (activeMedia.value) {
activeMedia.value.destroy();
Expand All @@ -89,6 +92,7 @@ export const initMediaPlayer = (
nextStartPosition = 0;
}
activeMedia.value = createMediaTrack(url, track);
activeMedia.value.setVolume(volume.value);
activeMedia.value.registerSource();
activeMedia.value.registerEvents();
}
Expand Down Expand Up @@ -193,6 +197,16 @@ export const initMediaPlayer = (
},
});

const volumeComputed = computed({
get: () => volume.value,
set: (value: number) => {
volume.value = value;
if (activeMedia.value) {
activeMedia.value.setVolume(value);
}
},
});

const hasPrevious = computed(() => queue.value.length > 0);

const jumpToPreviousThreshold = 5; // BMM Mobile & YouTube Music have 5s. Spotify has 3s.
Expand Down Expand Up @@ -288,6 +302,7 @@ export const initMediaPlayer = (
currentTrackDuration: computed(() =>
activeMedia.value ? activeMedia.value.duration : NaN,
),
volume: volumeComputed,
queue: computed(() => queue.value),
setQueue,
setQueueShuffled,
Expand Down

0 comments on commit 9b2b8d8

Please sign in to comment.