Skip to content

Commit f2f7cf6

Browse files
authored
Modify the way volume controls work (#1173)
1 parent cd50dab commit f2f7cf6

File tree

2 files changed

+102
-15
lines changed

2 files changed

+102
-15
lines changed

src/components/VolumeControl.vue

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,10 @@
161161
</v-list-item>
162162
<!-- mute btn + volume slider + volume level text-->
163163
<v-list-item
164-
v-if="childPlayer.volume_control != PLAYER_CONTROL_NONE"
164+
v-if="
165+
childPlayer.volume_control != PLAYER_CONTROL_NONE &&
166+
player.group_members.includes(childPlayer.player_id)
167+
"
165168
class="volumesliderrow"
166169
:link="false"
167170
:style="
@@ -224,10 +227,10 @@ import { getPlayerName, truncateString } from "@/helpers/utils";
224227
import PlayerVolume from "@/layouts/default/PlayerOSD/PlayerVolume.vue";
225228
import { api } from "@/plugins/api";
226229
import {
230+
PlaybackState,
227231
Player,
228232
PLAYER_CONTROL_NONE,
229233
PlayerFeature,
230-
PlaybackState,
231234
PlayerType,
232235
} from "@/plugins/api/interfaces";
233236
import { computed, ref } from "vue";
@@ -301,7 +304,13 @@ const getVolumePlayers = function (player: Player) {
301304
}
302305
}
303306
}
304-
items.sort((a, b) => (a.name.toUpperCase() > b.name.toUpperCase() ? 1 : -1));
307+
// Sort: selected (in group) first, then by name
308+
items.sort((a, b) => {
309+
const aSelected = player.group_members.includes(a.player_id);
310+
const bSelected = player.group_members.includes(b.player_id);
311+
if (aSelected !== bSelected) return aSelected ? -1 : 1;
312+
return a.name.toUpperCase() > b.name.toUpperCase() ? 1 : -1;
313+
});
305314
return items;
306315
};
307316

src/layouts/default/PlayerOSD/PlayerVolume.vue

Lines changed: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
<template>
22
<v-slider
3+
ref="sliderRef"
34
v-bind="playerVolumeProps"
4-
@touchstart="isThumbHidden = false"
5-
@touchend="isThumbHidden = true"
6-
@mouseenter="isThumbHidden = false"
7-
@mouseleave="isThumbHidden = true"
5+
:model-value="displayValue"
86
@wheel.prevent="onWheel"
9-
@end="(value) => $emit('update:model-value', value)"
7+
@click.stop
8+
@start="onDragStart"
9+
@update:model-value="onUpdateModelValue"
10+
@end="onDragEnd"
1011
>
1112
<!-- Dynamically inherit slots from parent -->
1213
<!-- @vue-ignore -->
@@ -17,9 +18,10 @@
1718
</template>
1819

1920
<script lang="ts">
20-
import { computed, ref } from "vue";
21+
import { computed, ref, watch } from "vue";
2122
2223
export default {
24+
inheritAttrs: false,
2325
props: {
2426
// eslint-disable-next-line vue/require-default-prop
2527
width: String,
@@ -31,34 +33,110 @@ export default {
3133
},
3234
emits: ["update:model-value"],
3335
setup(props, ctx) {
34-
const isThumbHidden = ref(true);
36+
const sliderRef = ref(null);
37+
const startValue = ref(0);
38+
const updateCount = ref(0);
39+
const lastEnd = ref(0);
40+
const displayValue = ref<number>(
41+
typeof ctx.attrs["model-value"] === "number"
42+
? (ctx.attrs["model-value"] as number)
43+
: 0,
44+
);
3545
3646
const playerVolumeDefaults = computed(() => ({
3747
class: "player-volume",
3848
hideDetails: true,
3949
trackSize: 2,
40-
thumbSize: isThumbHidden.value ? 0 : 10,
50+
thumbSize: 12,
4151
step: 2,
4252
elevation: 0,
4353
style: `width: ${props.width}; height:${props.height}; display: inline-grid; ${props.style}`,
4454
}));
4555
56+
const sliderAttrs = computed(() => {
57+
const attrs = ctx.attrs as Record<string, unknown>;
58+
const { "model-value": _mv, modelValue: _mv2, ...rest } = attrs || {};
59+
60+
return rest;
61+
});
62+
4663
const playerVolumeProps = computed(() => ({
4764
...playerVolumeDefaults.value,
48-
...ctx,
65+
...sliderAttrs.value,
4966
}));
5067
5168
const onWheel = ({ deltaY }: WheelEvent) => {
5269
if (!props.allowWheel) return;
5370
const step = playerVolumeProps.value.step;
5471
55-
const volumeValue = ctx.attrs["model-value"] as number;
72+
const volumeValue =
73+
(ctx.attrs["model-value"] as number) ?? displayValue.value;
5674
const volumeDelta = deltaY < 0 ? step : -step;
5775
58-
ctx.emit("update:model-value", volumeValue + volumeDelta);
76+
const newValue = Math.max(0, Math.min(100, volumeValue + volumeDelta));
77+
displayValue.value = newValue;
78+
ctx.emit("update:model-value", newValue);
79+
};
80+
81+
const onDragStart = (value: number) => {
82+
startValue.value = value;
83+
updateCount.value = 0;
84+
displayValue.value = value;
5985
};
6086
61-
return { isThumbHidden, playerVolumeProps, onWheel };
87+
const onUpdateModelValue = (newValue: number) => {
88+
updateCount.value++;
89+
90+
if (updateCount.value > 2) {
91+
displayValue.value = newValue;
92+
}
93+
};
94+
95+
const onDragEnd = (endValue: number) => {
96+
// Cooldown to avoid duplicate emits only for moible click (otherwise it fires 2 be calls)
97+
const now = Date.now();
98+
if (now - lastEnd.value < 250) {
99+
return;
100+
}
101+
lastEnd.value = now;
102+
103+
const step = playerVolumeProps.value.step;
104+
105+
// If we had many updates, that means it was a drag so we emit only the final value
106+
if (updateCount.value > 3) {
107+
displayValue.value = endValue;
108+
ctx.emit("update:model-value", endValue);
109+
} else {
110+
const volumeDelta = endValue > startValue.value ? step : -step;
111+
const newVolume = Math.max(
112+
0,
113+
Math.min(100, startValue.value + volumeDelta),
114+
);
115+
displayValue.value = newVolume;
116+
ctx.emit("update:model-value", newVolume);
117+
}
118+
119+
updateCount.value = 0;
120+
};
121+
122+
watch(
123+
() => ctx.attrs["model-value"] as number,
124+
(val) => {
125+
if (typeof val === "number" && updateCount.value === 0) {
126+
displayValue.value = val;
127+
}
128+
},
129+
);
130+
131+
return {
132+
playerVolumeProps,
133+
onWheel,
134+
sliderRef,
135+
onDragStart,
136+
onUpdateModelValue,
137+
onDragEnd,
138+
displayValue,
139+
};
62140
},
63141
};
64142
</script>

0 commit comments

Comments
 (0)