useSound问题

背景

还记的那天,产品经理给我提了个需求,要我做一个组件。要求也是蛮常规的,就是做一个声音选择的下拉单选框,选中啥放啥声音(这里还有一个,就是需要上上传功能,但后续讨论只需要配置默认声音,提供选择,就没做了。做起来也不麻烦,只需要上传之后,返回一个可下载的链接使用useObjectUrl()钩子)。心想这不so easy分分钟就给你搞定。当然,你觉得一件事很简单的时候,他就越容易出问题。

问题阐述

由于我非常喜欢vueuse,所以一接到这个需求的时候,我就想到了vueuse的useSound。既然如此那就开干。

  • setup 部分
1
2
const selectValue = ref<number | undefined>()
const { play } = useSound(selectValue)
  • template部分
1
<a-select v-model="selectValue" />

结果我把网页开打一看,好家伙,啥效果都没有,我寻思不可能吧,这么大个库也能出bug?

让我来瞅瞅哪里出问题了, 先来看看这个函数的ts类型

1
declare function useSound(url: MaybeRef<string>, { volume, playbackRate, soundEnabled, interrupt, autoplay, onload, ...delegated }?: ComposableOptions): ReturnedValue;

嗯….只能传非空的url,然后他的源码并没有对非空url进行处理。我们实际开发者,肯定会遇到空值的情况,所以对我来说这个函数不太能符合我的需求。

怎么办呢。。。我灵机一动,我把它的源码拿下来自己改改不就行了。

具体实现

由于我现在都使用vue3来写,所以就没考虑兼容vue2,需要的童鞋自己使用vue-demi实现下把

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<!-- index.js -->
import howler from 'howler'
function useSound(url, { volume = 1, playbackRate = 1, soundEnabled = true, interrupt = false, autoplay = false, onload, ...delegated } = {}) {
const HowlConstructor = ref(null);
const isPlaying = ref(false);
const duration = ref(null);
const sound = ref(null);
if(unref(url)) {
sound.value = new HowlConstructor.value({
src: unref(url),
volume: unref(volume),
rate: unref(playbackRate),
onload: handleLoad,
...delegated
});
}
function handleLoad() {
if (typeof onload === "function")
onload.call(this);
duration.value = (duration.value || sound.value?.duration() || 0) * 1e3;
if (autoplay === true) {
isPlaying.value = true;
}
}
watch(
() => [unref(url)],
() => {
if (unref(url)) {
sound.value = new HowlConstructor.value({
src: unref(url),
volume: unref(volume),
rate: unref(playbackRate),
onload: handleLoad,
...delegated
});
} else {
sound.value = void 0
}
}
);
watch(
() => [unref(volume), unref(playbackRate)],
() => {
if (sound.value) {
sound.value.volume(unref(volume));
sound.value.rate(unref(playbackRate));
}
}
);
const play = (options) => {
if (typeof options === "undefined") {
options = {};
}
if (!sound.value || !soundEnabled && !options.forceSoundEnabled) {
return;
}
if (interrupt) {
sound.value.stop();
}
if (options.playbackRate) {
sound.value.rate(options.playbackRate);
}
sound.value.play(options.id);
sound.value.once("end", () => {
if (sound.value && sound.value && !sound.value.playing()) {
isPlaying.value = false;
}
});
isPlaying.value = true;
};
const stop = (id) => {
if (!sound.value) {
return;
}
sound.value.stop(typeof id === "number" ? id : void 0);
isPlaying.value = false;
};
const pause = (id) => {
if (!sound.value) {
return;
}
sound.value.pause(typeof id === "number" ? id : void 0);
isPlaying.value = false;
};
const returnedValue = {
play,
sound,
isPlaying,
duration,
pause,
stop
};
return returnedValue;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!-- index.d.ts -->
import { HowlOptions, Howl } from 'howler';
import { Ref, ComputedRef } from 'vue';

type MaybeRef<T> = T | Ref<T> | ComputedRef<T>;
interface SpriteMap {
[key: string]: [number, number];
}
type ComposableOptions = {
volume?: MaybeRef<number>;
playbackRate?: MaybeRef<number>;
interrupt?: boolean;
soundEnabled?: boolean;
autoplay?: boolean;
sprite?: SpriteMap;
onload?: () => void;
} & Omit<HowlOptions, 'src'>;
interface PlayOptions {
id?: number;
forceSoundEnabled?: boolean;
playbackRate?: number;
}
type PlayFunction = (options?: PlayOptions) => void;
interface ReturnedValue {
play: PlayFunction;
sound: Ref<Howl | null>;
stop: (id?: number) => void;
pause: (id?: number) => void;
isPlaying: Ref<boolean>;
duration: Ref<number | null>;
}

declare function useSound(url: MaybeRef<string | undefined>, { volume, playbackRate, soundEnabled, interrupt, autoplay, onload, ...delegated }?: ComposableOptions): ReturnedValue;

export { useSound };

把这两段代码自己封装下hook,就可以在项目中使用啦,亲测好用

总结

虽然有些需求看上去简单,实现起来也简单,但里面还是有坑的,必须小心别踩坑了。

最后想感叹句,站在巨人的肩膀上,真的舒服啊。

代码参考: vueuse useSound


useSound问题
https://garlandqian.github.io/2023/09/01/vueuse/useSound踩坑记/
作者
Garland Qian
发布于
2023年9月1日
许可协议