From 3609c1c9a0139f5527ae84eb36681b584734e111 Mon Sep 17 00:00:00 2001 From: emmacodes Date: Thu, 26 Sep 2024 09:24:53 -0700 Subject: [PATCH 1/5] added voice to speech api, able to input item name by voice --- src/components/VoiceToText.jsx | 3 +++ src/views/ManageList.jsx | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/components/VoiceToText.jsx diff --git a/src/components/VoiceToText.jsx b/src/components/VoiceToText.jsx new file mode 100644 index 0000000..ee48db0 --- /dev/null +++ b/src/components/VoiceToText.jsx @@ -0,0 +1,3 @@ +export default function VoiceToText() { + return <>pass; +} diff --git a/src/views/ManageList.jsx b/src/views/ManageList.jsx index fef0356..6aa370a 100644 --- a/src/views/ManageList.jsx +++ b/src/views/ManageList.jsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { addItem, shareList } from '../api'; +import VoiceToText from '../components/VoiceToText'; export function ManageList({ userId, list }) { const [formData, setFormData] = useState({ @@ -90,6 +91,25 @@ export function ManageList({ userId, list }) { } catch (error) {} } + function handleVoiceTransform() { + const recognition = new (window.SpeechRecognition || + window.webkitSpeechRecognition || + window.mozSpeechRecognition || + window.msSpeechRecognition)(); + recognition.lang = 'en-US'; + recognition.interimResults = false; + recognition.maxAlternatives = 1; + recognition.start(); + recognition.onresult = (event) => { + const transcript = event.results[0][0].transcript; + setFormData((prev) => ({ ...prev, name: transcript })); + + recognition.onend = () => { + console.log('Speech recognition ended.'); + }; + }; + } + //需要audioend吗? return ( <>

@@ -108,6 +128,10 @@ export function ManageList({ userId, list }) { required > + + +

+ From bdc0fd981484283079fd9fec0ff039062e6f83f8 Mon Sep 17 00:00:00 2001 From: emmacodes Date: Sat, 28 Sep 2024 19:08:37 -0700 Subject: [PATCH 2/5] created a voiceToText custom hook and use this to update input value --- src/components/VoiceToText.jsx | 3 -- src/utils/hooks.js | 60 +++++++++++++++++++++++++++++++++- src/views/ManageList.jsx | 38 ++++++++++----------- 3 files changed, 77 insertions(+), 24 deletions(-) delete mode 100644 src/components/VoiceToText.jsx diff --git a/src/components/VoiceToText.jsx b/src/components/VoiceToText.jsx deleted file mode 100644 index ee48db0..0000000 --- a/src/components/VoiceToText.jsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function VoiceToText() { - return <>pass; -} diff --git a/src/utils/hooks.js b/src/utils/hooks.js index 92a06ca..1e31aad 100644 --- a/src/utils/hooks.js +++ b/src/utils/hooks.js @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; /** * Set some state in React, and also persist that value in localStorage. @@ -19,3 +19,61 @@ export function useStateWithStorage(storageKey, initialValue) { return [value, setValue]; } + +export function useVoiceToText() { + const [text, setText] = useState(''); + const [isListening, setIsListening] = useState(false); + const recognitionRef = useRef(null); + + useEffect(() => { + if ( + !('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) + ) { + console.error('SpeechRecognition is not supported by this browser.'); + return; + } + + recognitionRef.current = new (window.SpeechRecognition || + window.webkitSpeechRecognition || + window.mozSpeechRecognition || + window.msSpeechRecognition)(); + + recognitionRef.current.lang = 'en-US'; + recognitionRef.current.interimResults = false; + recognitionRef.current.maxAlternatives = 1; + + recognitionRef.current.onresult = (event) => { + const transcript = event.results[0][0].transcript; + setText(transcript); + }; + recognitionRef.current.onend = () => { + setIsListening(false); + }; + + recognitionRef.current.onerror = (error) => { + console.error('Speech recognition error:', error); + setIsListening(false); + }; + + return () => { + recognitionRef.current.stop(); + recognitionRef.current = null; + }; + }, []); + + const startListening = () => { + if (!isListening && recognitionRef.current) { + recognitionRef.current.start(); + setIsListening(true); + } + }; + + const stopListening = () => { + if (isListening && recognitionRef.current) { + recognitionRef.current.stop(); + setIsListening(false); + } + }; + + return { text, isListening, startListening, stopListening }; +} diff --git a/src/views/ManageList.jsx b/src/views/ManageList.jsx index 6aa370a..ddce693 100644 --- a/src/views/ManageList.jsx +++ b/src/views/ManageList.jsx @@ -1,6 +1,6 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { addItem, shareList } from '../api'; -import VoiceToText from '../components/VoiceToText'; +import { useVoiceToText } from '../utils'; export function ManageList({ userId, list }) { const [formData, setFormData] = useState({ @@ -9,6 +9,15 @@ export function ManageList({ userId, list }) { }); const [email, setEmail] = useState(''); + + const { text, isListening, startListening } = useVoiceToText(); + + useEffect(() => { + if (text) { + setFormData((prev) => ({ ...prev, name: text })); + } + }, [text]); + function handleChange(e) { e.preventDefault(); setFormData((prev) => ({ @@ -92,24 +101,11 @@ export function ManageList({ userId, list }) { } function handleVoiceTransform() { - const recognition = new (window.SpeechRecognition || - window.webkitSpeechRecognition || - window.mozSpeechRecognition || - window.msSpeechRecognition)(); - recognition.lang = 'en-US'; - recognition.interimResults = false; - recognition.maxAlternatives = 1; - recognition.start(); - recognition.onresult = (event) => { - const transcript = event.results[0][0].transcript; - setFormData((prev) => ({ ...prev, name: transcript })); - - recognition.onend = () => { - console.log('Speech recognition ended.'); - }; - }; + if (!isListening) { + startListening(); + } } - //需要audioend吗? + return ( <>

@@ -128,7 +124,9 @@ export function ManageList({ userId, list }) { required > - +

From c36032ba3d6828f7820f6d7a029d54536db5db03 Mon Sep 17 00:00:00 2001 From: emmacodes Date: Sat, 28 Sep 2024 19:20:43 -0700 Subject: [PATCH 3/5] implemented same custom hook of voiceToText to home page --- src/views/Home.jsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/views/Home.jsx b/src/views/Home.jsx index adb5b3a..e34ac75 100644 --- a/src/views/Home.jsx +++ b/src/views/Home.jsx @@ -1,8 +1,9 @@ import './Home.css'; import { SingleList } from '../components'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { createList, useAuth } from '../api'; import { useNavigate } from 'react-router-dom'; +import { useVoiceToText } from '../utils'; export function Home({ data, setListPath }) { const [listName, setListName] = useState(''); @@ -11,6 +12,13 @@ export function Home({ data, setListPath }) { const userId = user?.uid; const userEmail = user?.email; const navigate = useNavigate(); + const { text, isListening, startListening } = useVoiceToText(); + + useEffect(() => { + if (text) { + setListName(text); + } + }, [text]); async function handleSubmit(e) { e.preventDefault(); @@ -60,6 +68,9 @@ export function Home({ data, setListPath }) { value={listName} onChange={(e) => setListName(e.target.value)} /> +

{error}

From 816c6f43d7ee2ad1172f59781d95dad1d8edf7d1 Mon Sep 17 00:00:00 2001 From: emmacodes Date: Sun, 29 Sep 2024 22:43:15 -0700 Subject: [PATCH 4/5] added request mic access check when start listening --- src/utils/hooks.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/utils/hooks.js b/src/utils/hooks.js index 1e31aad..66eb0c2 100644 --- a/src/utils/hooks.js +++ b/src/utils/hooks.js @@ -61,10 +61,19 @@ export function useVoiceToText() { }; }, []); - const startListening = () => { - if (!isListening && recognitionRef.current) { - recognitionRef.current.start(); - setIsListening(true); + const startListening = async () => { + try { + // Request microphone access + await navigator.mediaDevices.getUserMedia({ audio: true }); + console.log('Microphone access granted. Starting speech recognition...'); + + if (!isListening && recognitionRef.current) { + recognitionRef.current.start(); + setIsListening(true); + } + } catch (err) { + console.error('Error accessing microphone:', err); + setError('Failed to access microphone. Please enable mic permissions.'); } }; From c42e43bbd20024bd8924c05c16a8953f66501d69 Mon Sep 17 00:00:00 2001 From: emmacodes Date: Mon, 30 Sep 2024 10:59:55 -0700 Subject: [PATCH 5/5] added a detectBrowser function, if safari, disable voiceToText feature with an alert msg --- src/utils/hooks.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/utils/hooks.js b/src/utils/hooks.js index 66eb0c2..10edf25 100644 --- a/src/utils/hooks.js +++ b/src/utils/hooks.js @@ -61,12 +61,40 @@ export function useVoiceToText() { }; }, []); + function detectBrowser() { + let userAgent = navigator.userAgent; + if (userAgent.indexOf('Edg') > -1) { + return 'Microsoft Edge'; + } else if (userAgent.indexOf('Chrome') > -1) { + return 'Chrome'; + } else if (userAgent.indexOf('Firefox') > -1) { + return 'Firefox'; + } else if (userAgent.indexOf('Safari') > -1) { + return 'Safari'; + } else if (userAgent.indexOf('Opera') > -1) { + return 'Opera'; + } else if ( + userAgent.indexOf('Trident') > -1 || + userAgent.indexOf('MSIE') > -1 + ) { + return 'Internet Explorer'; + } + + return 'Unknown'; + } + const startListening = async () => { try { // Request microphone access await navigator.mediaDevices.getUserMedia({ audio: true }); console.log('Microphone access granted. Starting speech recognition...'); + const browserName = detectBrowser(); + if (browserName === 'Safari') { + window.alert('Start Voice Input is not working in your browser.'); + return; + } + if (!isListening && recognitionRef.current) { recognitionRef.current.start(); setIsListening(true);