├── .eslintrc.json ├── .gitignore ├── README.md ├── app ├── favicon.ico ├── globals.css ├── layout.tsx ├── page.tsx └── views │ └── HomeView.tsx ├── components.json ├── lib └── utils.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── next.svg └── vercel.svg ├── tailwind.config.js └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Voice-to-Text Next.js App 3 | 4 | This application allows users to record their voice and receive a real-time transcript displayed on the screen. It's built using Next.js and utilizes Web Speech API for recording and speech recognition. 5 | 6 | ![App Demo](https://tyhgectxutilszaayoua.supabase.co/storage/v1/object/public/misc/voice-gif.gif) 7 | 8 | ## Table of Contents 9 | 10 | - [Demo](#demo) 11 | - [Features](#features) 12 | - [Getting Started](#getting-started) 13 | - [Prerequisites](#prerequisites) 14 | - [Installation](#installation) 15 | - [Usage](#usage) 16 | - [Built with](#built-with) 17 | - [Contributing](#contributing) 18 | - [License](#license) 19 | 20 | ## Demo 21 | 22 | ![App Demo](https://tyhgectxutilszaayoua.supabase.co/storage/v1/object/public/misc/voice-gif.gif) 23 | 24 | ## Features 25 | 26 | - Record your voice using the built-in microphone. 27 | - See real-time transcription of your speech on the screen. 28 | - Minimalistic user interface for easy interaction. 29 | - Built with responsive design in mind, works well on different devices. 30 | 31 | ## Getting Started 32 | 33 | Follow these instructions to get the project up and running on your local machine. 34 | 35 | ### Prerequisites 36 | 37 | - Node.js: Make sure you have Node.js installed. You can download it from [nodejs.org](https://nodejs.org/). 38 | 39 | ### Installation 40 | 41 | 1. Clone the repository: 42 | 43 | ```bash 44 | git clone https://github.com/your-username/voice-to-text-nextjs.git 45 | ``` 46 | 47 | 2. Navigate to the project directory: 48 | 49 | ```bash 50 | cd voice-to-text-nextjs 51 | ``` 52 | 53 | 3. Install the dependencies: 54 | 55 | ```bash 56 | npm install 57 | ``` 58 | 59 | ### Usage 60 | 61 | 1. Start the development server: 62 | 63 | ```bash 64 | npm run dev 65 | ``` 66 | 67 | 2. Open your browser and navigate to `http://localhost:3000`. 68 | 69 | 3. Allow microphone access when prompted. 70 | 71 | 4. Click the "Start Recording" button to begin recording your voice. 72 | 73 | 5. As you speak, you should see the transcription displayed on the screen in real-time. 74 | 75 | 6. Click the "Stop Recording" button to stop recording. 76 | 77 | ## Built with 78 | 79 | - [Next.js](https://nextjs.org/) - The React framework for building the app. 80 | - [Web Speech API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API) - Used for recording and speech recognition. 81 | - [Tailwind CSS](https://tailwindcss.com/) - Utility-first CSS framework for styling. 82 | 83 | ## Contributing 84 | 85 | Contributions are welcome! If you find any issues or want to enhance the app, feel free to submit a pull request. 86 | 87 | 1. Fork the repository. 88 | 2. Create a new branch. 89 | 3. Make your changes and commit them. 90 | 4. Push the changes to your fork. 91 | 5. Submit a pull request describing your changes. 92 | 93 | ## License 94 | 95 | This project is licensed under the [MIT License](LICENSE). 96 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sambowenhughes/voice-recording-with-nextjs/55080d9e9f0abd2a76f9df830364cf8d56e478be/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css' 2 | import type { Metadata } from 'next' 3 | import { Inter } from 'next/font/google' 4 | 5 | const inter = Inter({ subsets: ['latin'] }) 6 | 7 | export const metadata: Metadata = { 8 | title: 'Create Next App', 9 | description: 'Generated by create next app', 10 | } 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: { 15 | children: React.ReactNode 16 | }) { 17 | return ( 18 | 19 | {children} 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import HomeView from "./views/HomeView"; 2 | 3 | export default function Home() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /app/views/HomeView.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | // Import necessary modules and components 4 | import { useEffect, useState, useRef } from "react"; 5 | 6 | // Declare a global interface to add the webkitSpeechRecognition property to the Window object 7 | declare global { 8 | interface Window { 9 | webkitSpeechRecognition: any; 10 | } 11 | } 12 | 13 | // Export the MicrophoneComponent function component 14 | export default function MicrophoneComponent() { 15 | // State variables to manage recording status, completion, and transcript 16 | const [isRecording, setIsRecording] = useState(false); 17 | const [recordingComplete, setRecordingComplete] = useState(false); 18 | const [transcript, setTranscript] = useState(""); 19 | 20 | // Reference to store the SpeechRecognition instance 21 | const recognitionRef = useRef(null); 22 | 23 | // Function to start recording 24 | const startRecording = () => { 25 | setIsRecording(true); 26 | // Create a new SpeechRecognition instance and configure it 27 | recognitionRef.current = new window.webkitSpeechRecognition(); 28 | recognitionRef.current.continuous = true; 29 | recognitionRef.current.interimResults = true; 30 | 31 | // Event handler for speech recognition results 32 | recognitionRef.current.onresult = (event: any) => { 33 | const { transcript } = event.results[event.results.length - 1][0]; 34 | 35 | // Log the recognition results and update the transcript state 36 | console.log(event.results); 37 | setTranscript(transcript); 38 | }; 39 | 40 | // Start the speech recognition 41 | recognitionRef.current.start(); 42 | }; 43 | 44 | // Cleanup effect when the component unmounts 45 | useEffect(() => { 46 | return () => { 47 | // Stop the speech recognition if it's active 48 | if (recognitionRef.current) { 49 | recognitionRef.current.stop(); 50 | } 51 | }; 52 | }, []); 53 | 54 | // Function to stop recording 55 | const stopRecording = () => { 56 | if (recognitionRef.current) { 57 | // Stop the speech recognition and mark recording as complete 58 | recognitionRef.current.stop(); 59 | setRecordingComplete(true); 60 | } 61 | }; 62 | 63 | // Toggle recording state and manage recording actions 64 | const handleToggleRecording = () => { 65 | setIsRecording(!isRecording); 66 | if (!isRecording) { 67 | startRecording(); 68 | } else { 69 | stopRecording(); 70 | } 71 | }; 72 | 73 | // Render the microphone component with appropriate UI based on recording state 74 | return ( 75 |
76 |
77 | {(isRecording || transcript) && ( 78 |
79 |
80 |
81 |

82 | {recordingComplete ? "Recorded" : "Recording"} 83 |

84 |

85 | {recordingComplete 86 | ? "Thanks for talking." 87 | : "Start speaking..."} 88 |

89 |
90 | {isRecording && ( 91 |
92 | )} 93 |
94 | 95 | {transcript && ( 96 |
97 |

{transcript}

98 |
99 | )} 100 |
101 | )} 102 | 103 |
104 | {isRecording ? ( 105 | // Button for stopping recording 106 | 118 | ) : ( 119 | // Button for starting recording 120 | 135 | )} 136 |
137 |
138 |
139 | ); 140 | } 141 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voice-to-text", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@types/node": "20.4.1", 13 | "@types/react": "18.2.14", 14 | "@types/react-dom": "18.2.6", 15 | "autoprefixer": "10.4.14", 16 | "eslint": "8.44.0", 17 | "eslint-config-next": "13.4.9", 18 | "mic-recorder-to-mp3": "^2.2.2", 19 | "next": "13.4.9", 20 | "postcss": "8.4.25", 21 | "react": "18.2.0", 22 | "react-dom": "18.2.0", 23 | "tailwindcss": "3.3.2", 24 | "tailwindcss-animate": "^1.0.6", 25 | "typescript": "5.1.6", 26 | "wavesurfer.js": "^7.1.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | './pages/**/*.{ts,tsx}', 6 | './components/**/*.{ts,tsx}', 7 | './app/**/*.{ts,tsx}', 8 | './src/**/*.{ts,tsx}', 9 | ], 10 | theme: { 11 | container: { 12 | center: true, 13 | padding: "2rem", 14 | screens: { 15 | "2xl": "1400px", 16 | }, 17 | }, 18 | extend: { 19 | colors: { 20 | border: "hsl(var(--border))", 21 | input: "hsl(var(--input))", 22 | ring: "hsl(var(--ring))", 23 | background: "hsl(var(--background))", 24 | foreground: "hsl(var(--foreground))", 25 | primary: { 26 | DEFAULT: "hsl(var(--primary))", 27 | foreground: "hsl(var(--primary-foreground))", 28 | }, 29 | secondary: { 30 | DEFAULT: "hsl(var(--secondary))", 31 | foreground: "hsl(var(--secondary-foreground))", 32 | }, 33 | destructive: { 34 | DEFAULT: "hsl(var(--destructive))", 35 | foreground: "hsl(var(--destructive-foreground))", 36 | }, 37 | muted: { 38 | DEFAULT: "hsl(var(--muted))", 39 | foreground: "hsl(var(--muted-foreground))", 40 | }, 41 | accent: { 42 | DEFAULT: "hsl(var(--accent))", 43 | foreground: "hsl(var(--accent-foreground))", 44 | }, 45 | popover: { 46 | DEFAULT: "hsl(var(--popover))", 47 | foreground: "hsl(var(--popover-foreground))", 48 | }, 49 | card: { 50 | DEFAULT: "hsl(var(--card))", 51 | foreground: "hsl(var(--card-foreground))", 52 | }, 53 | }, 54 | borderRadius: { 55 | lg: "var(--radius)", 56 | md: "calc(var(--radius) - 2px)", 57 | sm: "calc(var(--radius) - 4px)", 58 | }, 59 | keyframes: { 60 | "accordion-down": { 61 | from: { height: 0 }, 62 | to: { height: "var(--radix-accordion-content-height)" }, 63 | }, 64 | "accordion-up": { 65 | from: { height: "var(--radix-accordion-content-height)" }, 66 | to: { height: 0 }, 67 | }, 68 | }, 69 | animation: { 70 | "accordion-down": "accordion-down 0.2s ease-out", 71 | "accordion-up": "accordion-up 0.2s ease-out", 72 | }, 73 | }, 74 | }, 75 | plugins: [require("tailwindcss-animate")], 76 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | --------------------------------------------------------------------------------