use-read-aloud 1.0.1 → 1.0.2

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 CHANGED
@@ -1,8 +1,15 @@
1
1
  # use-read-aloud
2
2
 
3
- A lightweight React hook that adds text-to-speech (read aloud) functionality using the Web Speech API.
3
+ A lightweight React hook for reliable text-to-speech (read aloud) in the browser. Designed for long-form content like blogs and articles using the Web Speech API.
4
4
 
5
- This hook is designed for blogs, articles, and reading-focused applications.
5
+ This hook is designed to work around common Web Speech API issues such as:
6
+
7
+ - Random failures when reading long text
8
+ - Unreliable behavior when resuming after long pauses
9
+ - Missing controls like fast-forward, rewind, etc.
10
+
11
+ > Note : This works fully on the client side using the browser’s built-in text-to-speech engines.
12
+ > No external APIs or dependencies are required.
6
13
 
7
14
  ## Installation
8
15
 
@@ -10,60 +17,26 @@ This hook is designed for blogs, articles, and reading-focused applications.
10
17
  npm install use-read-aloud
11
18
  ```
12
19
 
13
- ## Peer dependency
14
-
15
- - react >= 18
16
-
17
20
  ## Usage
18
21
 
19
22
  ```tsx
20
23
  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";
32
-
33
- function AudioPlayer({ text }: { text: string }) {
34
- const [playBackSpeed, setPlayBackSpeed] = useState<number>(1);
35
24
 
36
- const { isPaused, togglePlay, fastForward, seekBackward, replay } =
37
- useReadAloud(text, {
38
- rate: playBackSpeed,
39
- });
25
+ function AudioPlayer() {
26
+ const { isPaused, togglePlay } = useReadAloud("Text to be read");
40
27
 
41
28
  return (
42
- <div>
43
- <button onClick={seekBackward}>
44
- <Rewind />
45
- </button>
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>
62
- </div>
29
+ <>
30
+ <button onClick={togglePlay}>{isPaused ? "Play" : "Pause"}</button>
31
+ </>
63
32
  );
64
33
  }
65
34
  ```
66
35
 
36
+ ## Demo
37
+
38
+ A live demo can be found in [this blog](https://pragmaticswe.com/post/escaping-the-new-year-resolutions-matrix), which uses this library under the hood.
39
+
67
40
  ## API
68
41
 
69
42
  ```
@@ -72,9 +45,9 @@ useReadAloud(text, options?)
72
45
 
73
46
  ### Parameters
74
47
 
75
- - text - the text to be read.
48
+ - text - the text to be read
76
49
 
77
- - options - options to specify rate and pitch of the speech
50
+ - options - additional options to change the rate and pitch of the speech
78
51
 
79
52
  ```ts
80
53
  type Options = {
@@ -97,17 +70,86 @@ type Options = {
97
70
  }
98
71
  ```
99
72
 
73
+ ## Examples
74
+
75
+ 1. Fast forward / seek backward
76
+
77
+ ```tsx
78
+ import { useReadAloud } from "use-read-aloud";
79
+
80
+ function AudioPlayer() {
81
+ const { isPaused, togglePlay, fastForward, seekBackward } =
82
+ useReadAloud("Text to be read");
83
+
84
+ return (
85
+ <>
86
+ <button onClick={seekBackward}>Rewind</button>
87
+ <button onClick={togglePlay}>{isPaused ? "Play" : "Pause"}</button>
88
+ <button onClick={fastForward}>FastForward</button>
89
+ </>
90
+ );
91
+ }
92
+ ```
93
+
94
+ 2. Speed up / slow down
95
+
96
+ ```tsx
97
+ import { useReadAloud } from "use-read-aloud";
98
+ import { useState } from "react";
99
+
100
+ function AudioPlayer() {
101
+ const [playBackSpeed, setPlayBackSpeed] = useState<number>(1);
102
+
103
+ const { isPaused, togglePlay } = useReadAloud("Text to be read", {
104
+ rate: playBackSpeed,
105
+ });
106
+
107
+ return (
108
+ <>
109
+ <button onClick={togglePlay}>{isPaused ? "Play" : "Pause"}</button>
110
+ <span>
111
+ <button onClick={() => setPlayBackSpeed(playBackSpeed - 0.25)}>
112
+ Minus
113
+ </button>
114
+ {`${playBackSpeed}x`}
115
+ <button onClick={() => setPlayBackSpeed(playBackSpeed + 0.25)}>
116
+ Plus
117
+ </button>
118
+ </span>
119
+ </>
120
+ );
121
+ }
122
+ ```
123
+
124
+ 3. Replay from the start
125
+
126
+ ```tsx
127
+ import { useReadAloud } from "use-read-aloud";
128
+
129
+ function AudioPlayer() {
130
+ const { isPaused, togglePlay, replay } = useReadAloud("Text to be read");
131
+
132
+ return (
133
+ <>
134
+ <button onClick={togglePlay}>{isPaused ? "Play" : "Pause"}</button>
135
+ <button onClick={replay}>Replay</button>
136
+ </>
137
+ );
138
+ }
139
+ ```
140
+
100
141
  ## Behavior
101
142
 
102
- - Long text is automatically split into smaller chunks for reliability
143
+ - Long text is automatically split into smaller chunks of sentences for reliability.
144
+ - A workaround is used as a replacement for the inconsistent `speechSynthesis.resume()` function. (This may restart the current sentence, which is generally acceptable and far more reliable.)
103
145
 
104
- - Speech is cancelled automatically when the component unmounts
146
+ ## Peer dependency
105
147
 
106
- - Pause and resume are handled safely using internal session tracking
148
+ - react >= 18
107
149
 
108
150
  ## Browser support
109
151
 
110
- This hook uses the Web Speech API.
152
+ This hook uses the browser built-in Web Speech API, which is supported by most modern browsers. But, the voice quality varies by browser.
111
153
 
112
154
  Supported:
113
155
 
@@ -119,7 +161,7 @@ Supported:
119
161
 
120
162
  Not supported:
121
163
 
122
- - Firefox
164
+ - Firefox (does not support the Web Speech API)
123
165
 
124
166
  ## Changelog
125
167
 
@@ -1 +1 @@
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
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react"),R=220;function M(l){const i=[];let t="";for(const c of l.split("."))(t+c+".").length>R?(i.push(t.trim()),t=c+"."):t+=c+".";return t.trim()&&i.push(t.trim()),i}function P(l,i={}){const{rate:t=1,pitch:c=1}=i,[k,h]=e.useState(!0),u=e.useRef(!0),a=e.useRef([]),r=e.useRef(0),b=e.useCallback(()=>{a.current=[],r.current=0},[]),d=e.useCallback(()=>{if(!l.trim())return;const n=l.split(/\n+/).map(m=>m.trim()).filter(Boolean);a.current=n.flatMap(M),r.current=0},[l]),o=e.useCallback(()=>{u.current=!0,h(!0),r.current=0},[]),f=e.useCallback(()=>{speechSynthesis.cancel(),b(),o()},[b,o]),p=e.useCallback(()=>{if(r.current>=a.current.length){o();return}const n=new SpeechSynthesisUtterance(a.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,a.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]),g=e.useCallback(()=>{r.current=Math.min(r.current+1,a.current.length-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:g,togglePlay:k?s:C}}exports.useReadAloud=P;
@@ -1,57 +1,60 @@
1
- import { useState as A, useRef as d, useCallback as r, useEffect as g } from "react";
2
- const M = 220;
3
- function b(a) {
4
- const i = [];
1
+ import { useState as w, useRef as m, useCallback as n, useEffect as g } from "react";
2
+ const A = 220;
3
+ function b(i) {
4
+ const o = [];
5
5
  let e = "";
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;
6
+ for (const c of i.split("."))
7
+ (e + c + ".").length > A ? (o.push(e.trim()), e = c + ".") : e += c + ".";
8
+ return e.trim() && o.push(e.trim()), o;
9
9
  }
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())
10
+ function I(i, o = {}) {
11
+ const { rate: e = 1, pitch: c = 1 } = o, [d, l] = w(!0), u = m(!0), a = m([]), t = m(0), y = n(() => {
12
+ a.current = [], t.current = 0;
13
+ }, []), S = n(() => {
14
+ if (!i.trim())
15
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(() => {
16
+ const s = i.split(/\n+/).map((P) => P.trim()).filter(Boolean);
17
+ a.current = s.flatMap(b), t.current = 0;
18
+ }, [i]), f = n(() => {
19
+ u.current = !0, l(!0), t.current = 0;
20
+ }, []), h = n(() => {
21
21
  speechSynthesis.cancel(), y(), f();
22
- }, [y, f]), p = r(() => {
23
- if (n.current >= o.current.length) {
22
+ }, [y, f]), p = n(() => {
23
+ if (t.current >= a.current.length) {
24
24
  f();
25
25
  return;
26
26
  }
27
27
  const s = new SpeechSynthesisUtterance(
28
- o.current[n.current]
28
+ a.current[t.current]
29
29
  );
30
30
  s.rate = e, s.pitch = c, speechSynthesis.speak(s), s.onend = () => {
31
- n.current += 1, p();
31
+ t.current += 1, p();
32
32
  }, s.onerror = () => {
33
33
  };
34
- }, [f, c, e]), k = r(() => {
34
+ }, [f, c, e]), k = n(() => {
35
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]);
36
+ }, []), r = n(() => {
37
+ l(!1), u.current = !1, a.current.length === 0 && S(), speechSynthesis.cancel(), p();
38
+ }, [p, S]), R = n(() => {
39
+ h(), r();
40
+ }, [h, r]), C = n(() => {
41
+ t.current = Math.max(t.current - 1, 0), u.current || r();
42
+ }, [r]), M = n(() => {
43
+ t.current = Math.min(
44
+ t.current + 1,
45
+ a.current.length - 1
46
+ ), u.current || r();
47
+ }, [r]);
45
48
  return g(() => {
46
- u.current ? speechSynthesis.cancel() : t();
47
- }, [e, t]), g(() => () => h(), [h]), {
48
- isPaused: m,
49
- play: t,
49
+ u.current ? speechSynthesis.cancel() : r();
50
+ }, [e, r]), g(() => () => h(), [h]), {
51
+ isPaused: d,
52
+ play: r,
50
53
  pause: k,
51
54
  replay: R,
52
55
  seekBackward: C,
53
- fastForward: P,
54
- togglePlay: m ? t : k
56
+ fastForward: M,
57
+ togglePlay: d ? r : k
55
58
  };
56
59
  }
57
60
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-read-aloud",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "description": "A React hook for reading text aloud using the Web Speech API",
6
6
  "author": "Siva Murugan",
@@ -9,6 +9,7 @@
9
9
  "react",
10
10
  "react-hook",
11
11
  "text-to-speech",
12
+ "tts",
12
13
  "speech-synthesis",
13
14
  "read-aloud",
14
15
  "web-speech-api"