use-read-aloud 0.1.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -16
- package/dist/test/setupTests.d.ts +1 -0
- package/dist/test/useReadAloud.test.d.ts +1 -0
- package/dist/use-read-aloud.cjs.js +1 -1
- package/dist/use-read-aloud.es.js +47 -43
- package/dist/useReadAloud.d.ts +6 -6
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -18,21 +18,47 @@ npm install use-read-aloud
|
|
|
18
18
|
|
|
19
19
|
```tsx
|
|
20
20
|
import { useReadAloud } from "use-read-aloud";
|
|
21
|
+
import { useState } from "react";
|
|
22
|
+
import {
|
|
23
|
+
FastForward,
|
|
24
|
+
Minus,
|
|
25
|
+
Pause,
|
|
26
|
+
Play,
|
|
27
|
+
Plus,
|
|
28
|
+
Rewind,
|
|
29
|
+
RotateCcw,
|
|
30
|
+
Volume2,
|
|
31
|
+
} from "lucide-react";
|
|
21
32
|
|
|
22
33
|
function AudioPlayer({ text }: { text: string }) {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
const [playBackSpeed, setPlayBackSpeed] = useState<number>(1);
|
|
35
|
+
|
|
36
|
+
const { isPaused, togglePlay, fastForward, seekBackward, replay } =
|
|
37
|
+
useReadAloud(text, {
|
|
38
|
+
rate: playBackSpeed,
|
|
39
|
+
});
|
|
27
40
|
|
|
28
41
|
return (
|
|
29
42
|
<div>
|
|
30
|
-
<button onClick={
|
|
31
|
-
|
|
43
|
+
<button onClick={seekBackward}>
|
|
44
|
+
<Rewind />
|
|
32
45
|
</button>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
<button onClick={togglePlay}>{isPaused ? <Play /> : <Pause />}</button>
|
|
47
|
+
<button onClick={fastForward}>
|
|
48
|
+
<FastForward />
|
|
49
|
+
</button>
|
|
50
|
+
<button onClick={replay}>
|
|
51
|
+
<RotateCcw />
|
|
52
|
+
</button>
|
|
53
|
+
<span>
|
|
54
|
+
<button onClick={() => setPlayBackSpeed(playBackSpeed - 0.25)}>
|
|
55
|
+
<Minus />
|
|
56
|
+
</button>
|
|
57
|
+
{`${playBackSpeed}x`}
|
|
58
|
+
<button onClick={() => setPlayBackSpeed(playBackSpeed + 0.25)}>
|
|
59
|
+
<Plus />
|
|
60
|
+
</button>
|
|
61
|
+
</span>
|
|
36
62
|
</div>
|
|
37
63
|
);
|
|
38
64
|
}
|
|
@@ -41,14 +67,14 @@ function AudioPlayer({ text }: { text: string }) {
|
|
|
41
67
|
## API
|
|
42
68
|
|
|
43
69
|
```
|
|
44
|
-
useReadAloud(
|
|
70
|
+
useReadAloud(text, options?)
|
|
45
71
|
```
|
|
46
72
|
|
|
47
73
|
### Parameters
|
|
48
74
|
|
|
49
|
-
-
|
|
75
|
+
- text - the text to be read.
|
|
50
76
|
|
|
51
|
-
- options
|
|
77
|
+
- options - options to specify rate and pitch of the speech
|
|
52
78
|
|
|
53
79
|
```ts
|
|
54
80
|
type Options = {
|
|
@@ -61,11 +87,13 @@ type Options = {
|
|
|
61
87
|
|
|
62
88
|
```ts
|
|
63
89
|
{
|
|
64
|
-
isReading: boolean;
|
|
65
90
|
isPaused: boolean;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
91
|
+
play: () => void;
|
|
92
|
+
pause: () => void;
|
|
93
|
+
replay: () => void;
|
|
94
|
+
seekBackward: () => void;
|
|
95
|
+
fastForward: () => void;
|
|
96
|
+
togglePlay: () => void;
|
|
69
97
|
}
|
|
70
98
|
```
|
|
71
99
|
|
|
@@ -93,6 +121,10 @@ Not supported:
|
|
|
93
121
|
|
|
94
122
|
- Firefox
|
|
95
123
|
|
|
124
|
+
## Changelog
|
|
125
|
+
|
|
126
|
+
See [CHANGELOG.md](./CHANGELOG.md)
|
|
127
|
+
|
|
96
128
|
## License
|
|
97
129
|
|
|
98
130
|
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react"),m=220;function P(a){const l=[];let t="";for(const c of a.split("."))(t+c+".").length>m?(l.push(t.trim()),t=c+"."):t+=c+".";return t.trim()&&l.push(t.trim()),l}function A(a,l={}){const{rate:t=1,pitch:c=1}=l,[k,h]=e.useState(!0),u=e.useRef(!0),i=e.useRef([]),r=e.useRef(0),b=e.useCallback(()=>{i.current=[],r.current=0},[]),d=e.useCallback(()=>{if(!a.trim())return;const n=a.split(/\n+/).map(g=>g.trim()).filter(Boolean);i.current=n.flatMap(P),r.current=0},[a]),o=e.useCallback(()=>{u.current=!0,h(!0)},[]),f=e.useCallback(()=>{speechSynthesis.cancel(),b(),o()},[b,o]),p=e.useCallback(()=>{if(r.current>=i.current.length){o();return}const n=new SpeechSynthesisUtterance(i.current[r.current]);n.rate=t,n.pitch=c,speechSynthesis.speak(n),n.onend=()=>{r.current+=1,p()},n.onerror=()=>{}},[o,c,t]),C=e.useCallback(()=>{h(!0),u.current=!0,speechSynthesis.pause()},[]),s=e.useCallback(()=>{h(!1),u.current=!1,i.current.length===0&&d(),speechSynthesis.cancel(),p()},[p,d]),y=e.useCallback(()=>{f(),s()},[f,s]),S=e.useCallback(()=>{r.current=Math.max(r.current-1,0),u.current||s()},[s]),R=e.useCallback(()=>{r.current=r.current+1,u.current||s()},[s]);return e.useEffect(()=>{u.current?speechSynthesis.cancel():s()},[t,s]),e.useEffect(()=>()=>f(),[f]),{isPaused:k,play:s,pause:C,replay:y,seekBackward:S,fastForward:R,togglePlay:k?s:C}}exports.useReadAloud=A;
|
|
@@ -1,55 +1,59 @@
|
|
|
1
|
-
import { useState as A, useRef as d, useCallback as
|
|
1
|
+
import { useState as A, useRef as d, useCallback as r, useEffect as g } from "react";
|
|
2
2
|
const M = 220;
|
|
3
|
-
function
|
|
4
|
-
const
|
|
3
|
+
function b(a) {
|
|
4
|
+
const i = [];
|
|
5
5
|
let e = "";
|
|
6
|
-
for (const
|
|
7
|
-
(e +
|
|
8
|
-
return e.trim() &&
|
|
6
|
+
for (const c of a.split("."))
|
|
7
|
+
(e + c + ".").length > M ? (i.push(e.trim()), e = c + ".") : e += c + ".";
|
|
8
|
+
return e.trim() && i.push(e.trim()), i;
|
|
9
9
|
}
|
|
10
|
-
function
|
|
11
|
-
const { rate: e = 1, pitch:
|
|
12
|
-
|
|
13
|
-
}, []),
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const s = a.split(/\n+/).map((
|
|
17
|
-
|
|
18
|
-
}, [
|
|
19
|
-
|
|
20
|
-
}, []),
|
|
21
|
-
speechSynthesis.cancel(),
|
|
22
|
-
}, [
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
h();
|
|
10
|
+
function I(a, i = {}) {
|
|
11
|
+
const { rate: e = 1, pitch: c = 1 } = i, [m, l] = A(!0), u = d(!0), o = d([]), n = d(0), y = r(() => {
|
|
12
|
+
o.current = [], n.current = 0;
|
|
13
|
+
}, []), S = r(() => {
|
|
14
|
+
if (!a.trim())
|
|
15
|
+
return;
|
|
16
|
+
const s = a.split(/\n+/).map((w) => w.trim()).filter(Boolean);
|
|
17
|
+
o.current = s.flatMap(b), n.current = 0;
|
|
18
|
+
}, [a]), f = r(() => {
|
|
19
|
+
u.current = !0, l(!0);
|
|
20
|
+
}, []), h = r(() => {
|
|
21
|
+
speechSynthesis.cancel(), y(), f();
|
|
22
|
+
}, [y, f]), p = r(() => {
|
|
23
|
+
if (n.current >= o.current.length) {
|
|
24
|
+
f();
|
|
26
25
|
return;
|
|
27
26
|
}
|
|
28
27
|
const s = new SpeechSynthesisUtterance(
|
|
29
|
-
|
|
28
|
+
o.current[n.current]
|
|
30
29
|
);
|
|
31
|
-
s.rate = e, s.pitch =
|
|
32
|
-
|
|
30
|
+
s.rate = e, s.pitch = c, speechSynthesis.speak(s), s.onend = () => {
|
|
31
|
+
n.current += 1, p();
|
|
33
32
|
}, s.onerror = () => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
33
|
+
};
|
|
34
|
+
}, [f, c, e]), k = r(() => {
|
|
35
|
+
l(!0), u.current = !0, speechSynthesis.pause();
|
|
36
|
+
}, []), t = r(() => {
|
|
37
|
+
l(!1), u.current = !1, o.current.length === 0 && S(), speechSynthesis.cancel(), p();
|
|
38
|
+
}, [p, S]), R = r(() => {
|
|
39
|
+
h(), t();
|
|
40
|
+
}, [h, t]), C = r(() => {
|
|
41
|
+
n.current = Math.max(n.current - 1, 0), u.current || t();
|
|
42
|
+
}, [t]), P = r(() => {
|
|
43
|
+
n.current = n.current + 1, u.current || t();
|
|
44
|
+
}, [t]);
|
|
45
|
+
return g(() => {
|
|
46
|
+
u.current ? speechSynthesis.cancel() : t();
|
|
47
|
+
}, [e, t]), g(() => () => h(), [h]), {
|
|
48
|
+
isPaused: m,
|
|
49
|
+
play: t,
|
|
50
|
+
pause: k,
|
|
51
|
+
replay: R,
|
|
52
|
+
seekBackward: C,
|
|
53
|
+
fastForward: P,
|
|
54
|
+
togglePlay: m ? t : k
|
|
51
55
|
};
|
|
52
56
|
}
|
|
53
57
|
export {
|
|
54
|
-
|
|
58
|
+
I as useReadAloud
|
|
55
59
|
};
|
package/dist/useReadAloud.d.ts
CHANGED
|
@@ -2,12 +2,12 @@ export type Options = {
|
|
|
2
2
|
rate?: number;
|
|
3
3
|
pitch?: number;
|
|
4
4
|
};
|
|
5
|
-
export declare function useReadAloud(
|
|
6
|
-
isReading: boolean;
|
|
5
|
+
export declare function useReadAloud(text: string, options?: Options): {
|
|
7
6
|
isPaused: boolean;
|
|
8
|
-
|
|
9
|
-
reset: () => void;
|
|
7
|
+
play: () => void;
|
|
10
8
|
pause: () => void;
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
replay: () => void;
|
|
10
|
+
seekBackward: () => void;
|
|
11
|
+
fastForward: () => void;
|
|
12
|
+
togglePlay: () => void;
|
|
13
13
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "use-read-aloud",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A React hook for reading text aloud using the Web Speech API",
|
|
6
6
|
"author": "Siva Murugan",
|
|
@@ -23,13 +23,16 @@
|
|
|
23
23
|
"dev": "vite",
|
|
24
24
|
"types": "tsc --emitDeclarationOnly",
|
|
25
25
|
"build": "vite build && tsc --emitDeclarationOnly",
|
|
26
|
-
"lint": "eslint ."
|
|
26
|
+
"lint": "eslint .",
|
|
27
|
+
"test": "vitest"
|
|
27
28
|
},
|
|
28
29
|
"peerDependencies": {
|
|
29
30
|
"react": ">=18"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@eslint/js": "^9.39.1",
|
|
34
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
35
|
+
"@testing-library/react": "^16.3.1",
|
|
33
36
|
"@types/node": "^24.10.1",
|
|
34
37
|
"@types/react": "^19.2.5",
|
|
35
38
|
"@types/react-dom": "^19.2.3",
|
|
@@ -38,9 +41,11 @@
|
|
|
38
41
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
39
42
|
"eslint-plugin-react-refresh": "^0.4.24",
|
|
40
43
|
"globals": "^16.5.0",
|
|
44
|
+
"happy-dom": "^20.0.11",
|
|
41
45
|
"typescript": "~5.9.3",
|
|
42
46
|
"typescript-eslint": "^8.46.4",
|
|
43
|
-
"vite": "^7.2.4"
|
|
47
|
+
"vite": "^7.2.4",
|
|
48
|
+
"vitest": "^4.0.16"
|
|
44
49
|
},
|
|
45
50
|
"repository": {
|
|
46
51
|
"type": "git",
|