{"id":1444,"date":"2025-01-17T14:40:42","date_gmt":"2025-01-17T14:40:42","guid":{"rendered":"https:\/\/zahiralam.com\/blog\/?p=1444"},"modified":"2025-01-20T12:08:17","modified_gmt":"2025-01-20T12:08:17","slug":"building-a-real-time-speech-to-text-mobile-app-with-react-native-and-node-js","status":"publish","type":"post","link":"https:\/\/zahiralam.com\/blog\/building-a-real-time-speech-to-text-mobile-app-with-react-native-and-node-js\/","title":{"rendered":"Building a Real-Time Speech-to-Text Mobile App with React Native and Node.js"},"content":{"rendered":"\n<p>This article will guide you through building a mobile app using React Native for the frontend and Node.js for the backend, where users can record their voice, convert the audio to text using Google\u2019s Speech-to-Text API, and display the transcription in the app. We\u2019ll walk through the steps that made the project functional.\n\n\n\n<h3 class=\"wp-block-heading\">Prerequisites<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>React Native Setup<\/strong>: Ensure React Native is installed and working on your system. Follow&nbsp;<a href=\"https:\/\/zahiralam.com\/blog\/react-native-build-ios-android-mac\/\">this guide<\/a>&nbsp;to set up React Native for iOS and Android on macOS.<\/li>\n\n\n\n<li><strong>Node.js Setup<\/strong>: Install Node.js 20.x by following&nbsp;<a href=\"https:\/\/zahiralam.com\/blog\/installing-node-js-20-x-on-macos-with-apple-silicon-m1-m2-m3\/\">this guide<\/a>.<\/li>\n\n\n\n<li><strong>Google Speech-to-Text API Key<\/strong>: Obtain an API key from the Google Cloud Console and ensure the Speech-to-Text API is enabled.<\/li>\n\n\n\n<li><strong>FFmpeg<\/strong>: Install FFmpeg for audio conversion:<\/li>\n<\/ol>\n\n\n\n<div class=\"code-block-container\">\n                        <pre class=\"wp-block-code\"><code id=\"code-1\">brew install ffmpeg<\/code><\/pre>\n                        <amp-iframe sandbox=\"allow-scripts\" width=\"94\" height=\"72\" frameborder=\"0\" \n                                    src=\"https:\/\/zahiralam.com\/blog\/wp-content\/plugins\/amp-copy-code-button\/copier.html#brew%20install%20ffmpeg\">\n                            <button class=\"copy-button\" data-label=\"brew install ffmpeg\"  placeholder disabled>Copy<\/button>\n                        <\/amp-iframe>\n                    <\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1: Create a Project Structure<\/h3>\n\n\n\n<p>1. Create a folder named&nbsp;<code>VoiceNote<\/code>&nbsp;and navigate to it:\n\n\n\n<div class=\"code-block-container\">\n                        <pre class=\"wp-block-code\"><code id=\"code-2\">mkdir VoiceNote &amp;&amp; cd VoiceNote<\/code><\/pre>\n                        <amp-iframe sandbox=\"allow-scripts\" width=\"94\" height=\"72\" frameborder=\"0\" \n                                    src=\"https:\/\/zahiralam.com\/blog\/wp-content\/plugins\/amp-copy-code-button\/copier.html#mkdir%20VoiceNote%20%26%26%20cd%20VoiceNote\">\n                            <button class=\"copy-button\" data-label=\"mkdir VoiceNote &amp;&amp; cd VoiceNote\"  placeholder disabled>Copy<\/button>\n                        <\/amp-iframe>\n                    <\/div>\n\n\n\n<p>2. Initialize two subprojects:\n\n\n\n<p>a) <strong>React Native App<\/strong>: \n\n\n\n<div class=\"code-block-container\">\n                        <pre class=\"wp-block-code\"><code id=\"code-3\">npx @react-native-community\/cli init VoiceNoteApp<\/code><\/pre>\n                        <amp-iframe sandbox=\"allow-scripts\" width=\"94\" height=\"72\" frameborder=\"0\" \n                                    src=\"https:\/\/zahiralam.com\/blog\/wp-content\/plugins\/amp-copy-code-button\/copier.html#npx%20%40react-native-community%2Fcli%20init%20VoiceNoteApp\">\n                            <button class=\"copy-button\" data-label=\"npx @react-native-community\/cli init VoiceNoteApp\"  placeholder disabled>Copy<\/button>\n                        <\/amp-iframe>\n                    <\/div>\n\n\n\n<p>b) <strong>Node.js Backend<\/strong>:\n\n\n\n<div class=\"code-block-container\">\n                        <pre class=\"wp-block-code\"><code id=\"code-4\">mkdir backend &amp;&amp; cd backend npm init -y<\/code><\/pre>\n                        <amp-iframe sandbox=\"allow-scripts\" width=\"94\" height=\"72\" frameborder=\"0\" \n                                    src=\"https:\/\/zahiralam.com\/blog\/wp-content\/plugins\/amp-copy-code-button\/copier.html#mkdir%20backend%20%26%26%20cd%20backend%20npm%20init%20-y\">\n                            <button class=\"copy-button\" data-label=\"mkdir backend &amp;&amp; cd backend npm init -y\"  placeholder disabled>Copy<\/button>\n                        <\/amp-iframe>\n                    <\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Step 2: Set Up the Node.js Backend<\/h3>\n\n\n\n<p>1. Install required dependencies in the&nbsp;<code>backend<\/code>&nbsp;folder:\n\n\n\n<div class=\"code-block-container\">\n                        <pre class=\"wp-block-code\"><code id=\"code-5\">npm install express body-parser multer dotenv axios<\/code><\/pre>\n                        <amp-iframe sandbox=\"allow-scripts\" width=\"94\" height=\"72\" frameborder=\"0\" \n                                    src=\"https:\/\/zahiralam.com\/blog\/wp-content\/plugins\/amp-copy-code-button\/copier.html#npm%20install%20express%20body-parser%20multer%20dotenv%20axios\">\n                            <button class=\"copy-button\" data-label=\"npm install express body-parser multer dotenv axios\"  placeholder disabled>Copy<\/button>\n                        <\/amp-iframe>\n                    <\/div>\n\n\n\n<p>2. Install and configure FFmpeg for audio conversion:\n\n\n\n<div class=\"code-block-container\">\n                        <pre class=\"wp-block-code\"><code id=\"code-6\">brew install ffmpeg<\/code><\/pre>\n                        <amp-iframe sandbox=\"allow-scripts\" width=\"94\" height=\"72\" frameborder=\"0\" \n                                    src=\"https:\/\/zahiralam.com\/blog\/wp-content\/plugins\/amp-copy-code-button\/copier.html#brew%20install%20ffmpeg\">\n                            <button class=\"copy-button\" data-label=\"brew install ffmpeg\"  placeholder disabled>Copy<\/button>\n                        <\/amp-iframe>\n                    <\/div>\n\n\n\n<p>3. Create an&nbsp;<code>.env<\/code>&nbsp;file in the&nbsp;<code>backend<\/code>&nbsp;folder and add your Google Speech-to-Text API key:<code>GOOGLE_API_KEY=YOUR_GOOGLE_API_KEY<\/code>\n\n\n\n<p>4. Create a file named&nbsp;<code>server.js<\/code>&nbsp;in the&nbsp;<code>backend<\/code>&nbsp;folder and add the following code:\n\n\n\n<div class=\"code-block-container\">\n                        <pre class=\"wp-block-code\"><code id=\"code-7\">require(&quot;dotenv&quot;).config();\nconst express = require(&quot;express&quot;);\nconst bodyParser = require(&quot;body-parser&quot;);\nconst multer = require(&quot;multer&quot;);\nconst fs = require(&quot;fs&quot;);\nconst axios = require(&quot;axios&quot;);\nconst { exec } = require(&quot;child_process&quot;);\n\nconst app = express();\nconst port = 5019;\n\n\/\/ Middleware\napp.use(bodyParser.json());\n\n\/\/ Multer configuration for file uploads\nconst upload = multer({ dest: &quot;uploads\/&quot; });\n\n\/\/ Endpoint to handle audio upload\napp.post(&quot;\/upload&quot;, upload.single(&quot;audio&quot;), async (req, res) =&gt; {\n    try {\n      const inputPath = req.file.path;\n      const outputPath = `${req.file.path}.wav`;\n  \n      \/\/ Convert the audio file to LINEAR16 with 16000 Hz sample rate\n      const ffmpegCommand = `ffmpeg -i ${inputPath} -ar 16000 -ac 1 -f wav ${outputPath}`;\n      exec(ffmpegCommand, async (error, stdout, stderr) =&gt; {\n        if (error) {\n          console.error(&quot;Error during audio conversion:&quot;, stderr);\n          return res.status(500).json({ error: &quot;Error converting audio file&quot; });\n        }\n  \n        \/\/ Read the converted audio file\n        const audioFile = fs.readFileSync(outputPath);\n        const audioBytes = audioFile.toString(&quot;base64&quot;);\n  \n        \/\/ Google Speech-to-Text API request payload\n        const requestPayload = {\n          config: {\n            encoding: &quot;LINEAR16&quot;,\n            sampleRateHertz: 16000,\n            languageCode: &quot;en-US&quot;,\n          },\n          audio: {\n            content: audioBytes,\n          },\n        };\n  \n        \/\/ Send the audio to Google Speech-to-Text API\n        try {\n          const response = await axios.post(\n            `https:\/\/speech.googleapis.com\/v1\/speech:recognize?key=${process.env.GOOGLE_API_KEY}`,\n            requestPayload,\n            { headers: { &quot;Content-Type&quot;: &quot;application\/json&quot; } }\n          );\n  \n          \/\/ Extract transcription from the API response\n          const transcription = response.data.results\n            .map((result) =&gt; result.alternatives[0].transcript)\n            .join(&quot;\\n&quot;);\n          \n            console.log(&#039;transcription : &#039;, transcription);\n  \n          \/\/ Cleanup temporary files\n          fs.unlinkSync(inputPath);\n          fs.unlinkSync(outputPath);\n  \n          \/\/ Respond with transcription\n          res.json({ transcription });\n        } catch (apiError) {\n          console.error(&quot;Error during transcription:&quot;, apiError.response?.data || apiError.message);\n          res.status(500).json({ error: &quot;Error during transcription&quot; });\n        }\n      });\n    } catch (error) {\n      console.error(&quot;Error processing request:&quot;, error);\n      res.status(500).json({ error: &quot;Error processing audio file&quot; });\n    }\n  });\n\napp.listen(port, () =&gt; {\n  console.log(`Server running at http:\/\/localhost:${port}`);\n});\n<\/code><\/pre>\n                        <amp-iframe sandbox=\"allow-scripts\" width=\"94\" height=\"72\" frameborder=\"0\" \n                                    src=\"https:\/\/zahiralam.com\/blog\/wp-content\/plugins\/amp-copy-code-button\/copier.html#require%28%22dotenv%22%29.config%28%29%3B%0Aconst%20express%20%3D%20require%28%22express%22%29%3B%0Aconst%20bodyParser%20%3D%20require%28%22body-parser%22%29%3B%0Aconst%20multer%20%3D%20require%28%22multer%22%29%3B%0Aconst%20fs%20%3D%20require%28%22fs%22%29%3B%0Aconst%20axios%20%3D%20require%28%22axios%22%29%3B%0Aconst%20%7B%20exec%20%7D%20%3D%20require%28%22child_process%22%29%3B%0A%0Aconst%20app%20%3D%20express%28%29%3B%0Aconst%20port%20%3D%205019%3B%0A%0A%2F%2F%20Middleware%0Aapp.use%28bodyParser.json%28%29%29%3B%0A%0A%2F%2F%20Multer%20configuration%20for%20file%20uploads%0Aconst%20upload%20%3D%20multer%28%7B%20dest%3A%20%22uploads%2F%22%20%7D%29%3B%0A%0A%2F%2F%20Endpoint%20to%20handle%20audio%20upload%0Aapp.post%28%22%2Fupload%22%2C%20upload.single%28%22audio%22%29%2C%20async%20%28req%2C%20res%29%20%3D%3E%20%7B%0A%20%20%20%20try%20%7B%0A%20%20%20%20%20%20const%20inputPath%20%3D%20req.file.path%3B%0A%20%20%20%20%20%20const%20outputPath%20%3D%20%60%24%7Breq.file.path%7D.wav%60%3B%0A%20%20%0A%20%20%20%20%20%20%2F%2F%20Convert%20the%20audio%20file%20to%20LINEAR16%20with%2016000%20Hz%20sample%20rate%0A%20%20%20%20%20%20const%20ffmpegCommand%20%3D%20%60ffmpeg%20-i%20%24%7BinputPath%7D%20-ar%2016000%20-ac%201%20-f%20wav%20%24%7BoutputPath%7D%60%3B%0A%20%20%20%20%20%20exec%28ffmpegCommand%2C%20async%20%28error%2C%20stdout%2C%20stderr%29%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20if%20%28error%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20console.error%28%22Error%20during%20audio%20conversion%3A%22%2C%20stderr%29%3B%0A%20%20%20%20%20%20%20%20%20%20return%20res.status%28500%29.json%28%7B%20error%3A%20%22Error%20converting%20audio%20file%22%20%7D%29%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%0A%20%20%20%20%20%20%20%20%2F%2F%20Read%20the%20converted%20audio%20file%0A%20%20%20%20%20%20%20%20const%20audioFile%20%3D%20fs.readFileSync%28outputPath%29%3B%0A%20%20%20%20%20%20%20%20const%20audioBytes%20%3D%20audioFile.toString%28%22base64%22%29%3B%0A%20%20%0A%20%20%20%20%20%20%20%20%2F%2F%20Google%20Speech-to-Text%20API%20request%20payload%0A%20%20%20%20%20%20%20%20const%20requestPayload%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20config%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20encoding%3A%20%22LINEAR16%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20sampleRateHertz%3A%2016000%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20languageCode%3A%20%22en-US%22%2C%0A%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20audio%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20content%3A%20audioBytes%2C%0A%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%0A%20%20%20%20%20%20%20%20%2F%2F%20Send%20the%20audio%20to%20Google%20Speech-to-Text%20API%0A%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20const%20response%20%3D%20await%20axios.post%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%60https%3A%2F%2Fspeech.googleapis.com%2Fv1%2Fspeech%3Arecognize%3Fkey%3D%24%7Bprocess.env.GOOGLE_API_KEY%7D%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20requestPayload%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20headers%3A%20%7B%20%22Content-Type%22%3A%20%22application%2Fjson%22%20%7D%20%7D%0A%20%20%20%20%20%20%20%20%20%20%29%3B%0A%20%20%0A%20%20%20%20%20%20%20%20%20%20%2F%2F%20Extract%20transcription%20from%20the%20API%20response%0A%20%20%20%20%20%20%20%20%20%20const%20transcription%20%3D%20response.data.results%0A%20%20%20%20%20%20%20%20%20%20%20%20.map%28%28result%29%20%3D%3E%20result.alternatives%5B0%5D.transcript%29%0A%20%20%20%20%20%20%20%20%20%20%20%20.join%28%22%5Cn%22%29%3B%0A%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20console.log%28%27transcription%20%3A%20%27%2C%20transcription%29%3B%0A%20%20%0A%20%20%20%20%20%20%20%20%20%20%2F%2F%20Cleanup%20temporary%20files%0A%20%20%20%20%20%20%20%20%20%20fs.unlinkSync%28inputPath%29%3B%0A%20%20%20%20%20%20%20%20%20%20fs.unlinkSync%28outputPath%29%3B%0A%20%20%0A%20%20%20%20%20%20%20%20%20%20%2F%2F%20Respond%20with%20transcription%0A%20%20%20%20%20%20%20%20%20%20res.json%28%7B%20transcription%20%7D%29%3B%0A%20%20%20%20%20%20%20%20%7D%20catch%20%28apiError%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20console.error%28%22Error%20during%20transcription%3A%22%2C%20apiError.response%3F.data%20%7C%7C%20apiError.message%29%3B%0A%20%20%20%20%20%20%20%20%20%20res.status%28500%29.json%28%7B%20error%3A%20%22Error%20during%20transcription%22%20%7D%29%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%29%3B%0A%20%20%20%20%7D%20catch%20%28error%29%20%7B%0A%20%20%20%20%20%20console.error%28%22Error%20processing%20request%3A%22%2C%20error%29%3B%0A%20%20%20%20%20%20res.status%28500%29.json%28%7B%20error%3A%20%22Error%20processing%20audio%20file%22%20%7D%29%3B%0A%20%20%20%20%7D%0A%20%20%7D%29%3B%0A%0Aapp.listen%28port%2C%20%28%29%20%3D%3E%20%7B%0A%20%20console.log%28%60Server%20running%20at%20http%3A%2F%2Flocalhost%3A%24%7Bport%7D%60%29%3B%0A%7D%29%3B%0A\">\n                            <button class=\"copy-button\" data-label=\"require(&quot;dotenv&quot;).config();\nconst express = require(&quot;express&quot;);\nconst bodyParser = require(&quot;body-parser&quot;);\nconst multer = require(&quot;multer&quot;);\nconst fs = require(&quot;fs&quot;);\nconst axios = require(&quot;axios&quot;);\nconst { exec } = require(&quot;child_process&quot;);\n\nconst app = express();\nconst port = 5019;\n\n\/\/ Middleware\napp.use(bodyParser.json());\n\n\/\/ Multer configuration for file uploads\nconst upload = multer({ dest: &quot;uploads\/&quot; });\n\n\/\/ Endpoint to handle audio upload\napp.post(&quot;\/upload&quot;, upload.single(&quot;audio&quot;), async (req, res) =&gt; {\n    try {\n      const inputPath = req.file.path;\n      const outputPath = `${req.file.path}.wav`;\n  \n      \/\/ Convert the audio file to LINEAR16 with 16000 Hz sample rate\n      const ffmpegCommand = `ffmpeg -i ${inputPath} -ar 16000 -ac 1 -f wav ${outputPath}`;\n      exec(ffmpegCommand, async (error, stdout, stderr) =&gt; {\n        if (error) {\n          console.error(&quot;Error during audio conversion:&quot;, stderr);\n          return res.status(500).json({ error: &quot;Error converting audio file&quot; });\n        }\n  \n        \/\/ Read the converted audio file\n        const audioFile = fs.readFileSync(outputPath);\n        const audioBytes = audioFile.toString(&quot;base64&quot;);\n  \n        \/\/ Google Speech-to-Text API request payload\n        const requestPayload = {\n          config: {\n            encoding: &quot;LINEAR16&quot;,\n            sampleRateHertz: 16000,\n            languageCode: &quot;en-US&quot;,\n          },\n          audio: {\n            content: audioBytes,\n          },\n        };\n  \n        \/\/ Send the audio to Google Speech-to-Text API\n        try {\n          const response = await axios.post(\n            `https:\/\/speech.googleapis.com\/v1\/speech:recognize?key=${process.env.GOOGLE_API_KEY}`,\n            requestPayload,\n            { headers: { &quot;Content-Type&quot;: &quot;application\/json&quot; } }\n          );\n  \n          \/\/ Extract transcription from the API response\n          const transcription = response.data.results\n            .map((result) =&gt; result.alternatives[0].transcript)\n            .join(&quot;\\n&quot;);\n          \n            console.log(&#039;transcription : &#039;, transcription);\n  \n          \/\/ Cleanup temporary files\n          fs.unlinkSync(inputPath);\n          fs.unlinkSync(outputPath);\n  \n          \/\/ Respond with transcription\n          res.json({ transcription });\n        } catch (apiError) {\n          console.error(&quot;Error during transcription:&quot;, apiError.response?.data || apiError.message);\n          res.status(500).json({ error: &quot;Error during transcription&quot; });\n        }\n      });\n    } catch (error) {\n      console.error(&quot;Error processing request:&quot;, error);\n      res.status(500).json({ error: &quot;Error processing audio file&quot; });\n    }\n  });\n\napp.listen(port, () =&gt; {\n  console.log(`Server running at http:\/\/localhost:${port}`);\n});\n\"  placeholder disabled>Copy<\/button>\n                        <\/amp-iframe>\n                    <\/div>\n\n\n\n<p>5. Start the backend server:\n\n\n\n<div class=\"code-block-container\">\n                        <pre class=\"wp-block-code\"><code id=\"code-8\">node server.js<\/code><\/pre>\n                        <amp-iframe sandbox=\"allow-scripts\" width=\"94\" height=\"72\" frameborder=\"0\" \n                                    src=\"https:\/\/zahiralam.com\/blog\/wp-content\/plugins\/amp-copy-code-button\/copier.html#node%20server.js\">\n                            <button class=\"copy-button\" data-label=\"node server.js\"  placeholder disabled>Copy<\/button>\n                        <\/amp-iframe>\n                    <\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Step 3: Configure React Native<\/h3>\n\n\n\n<p>1. Install dependencies for audio recording in the&nbsp;<code>VoiceNoteApp<\/code>&nbsp;folder:\n\n\n\n<div class=\"code-block-container\">\n                        <pre class=\"wp-block-code\"><code id=\"code-9\">npm install react-native-audio-recorder-player react-native-permissions<\/code><\/pre>\n                        <amp-iframe sandbox=\"allow-scripts\" width=\"94\" height=\"72\" frameborder=\"0\" \n                                    src=\"https:\/\/zahiralam.com\/blog\/wp-content\/plugins\/amp-copy-code-button\/copier.html#npm%20install%20react-native-audio-recorder-player%20react-native-permissions\">\n                            <button class=\"copy-button\" data-label=\"npm install react-native-audio-recorder-player react-native-permissions\"  placeholder disabled>Copy<\/button>\n                        <\/amp-iframe>\n                    <\/div>\n\n\n\n<p>2. Update the&nbsp;<code>VoiceNoteApp\/ios\/VoiceNoteApp\/Info.plist<\/code>&nbsp;file for iOS permissions. Add the following:\n\n\n\n<div class=\"code-block-container\">\n                        <pre class=\"wp-block-code\"><code id=\"code-10\">&lt;key&gt;NSMicrophoneUsageDescription&lt;\/key&gt; \n&lt;string&gt;We need microphone access to record audio for transcription.&lt;\/string&gt;<\/code><\/pre>\n                        <amp-iframe sandbox=\"allow-scripts\" width=\"94\" height=\"72\" frameborder=\"0\" \n                                    src=\"https:\/\/zahiralam.com\/blog\/wp-content\/plugins\/amp-copy-code-button\/copier.html#%3Ckey%3ENSMicrophoneUsageDescription%3C%2Fkey%3E%20%0A%3Cstring%3EWe%20need%20microphone%20access%20to%20record%20audio%20for%20transcription.%3C%2Fstring%3E\">\n                            <button class=\"copy-button\" data-label=\"&lt;key&gt;NSMicrophoneUsageDescription&lt;\/key&gt; \n&lt;string&gt;We need microphone access to record audio for transcription.&lt;\/string&gt;\"  placeholder disabled>Copy<\/button>\n                        <\/amp-iframe>\n                    <\/div>\n\n\n\n<p>3. Modify the&nbsp;<code>App.tsx<\/code>&nbsp;file to include the following code:\n\n\n\n<div class=\"code-block-container\">\n                        <pre class=\"wp-block-code\"><code id=\"code-11\">import React, { useState } from &quot;react&quot;;\nimport { View, Text, Button, StyleSheet, ActivityIndicator } from &quot;react-native&quot;;\nimport AudioRecorderPlayer from &quot;react-native-audio-recorder-player&quot;;\n\nconst audioRecorderPlayer = new AudioRecorderPlayer();\n\nconst App = () =&gt; {\n  const [recording, setRecording] = useState(false);\n  const [transcription, setTranscription] = useState(&quot;&quot;);\n  const [loading, setLoading] = useState(false); \/\/ State for the spinner loader\n\n  const startRecording = async () =&gt; {\n    try {\n      setRecording(true);\n      const path = &quot;recording.m4a&quot;; \/\/ Recorded file path\n      await audioRecorderPlayer.startRecorder(path);\n      console.log(&quot;Recording started&quot;);\n    } catch (error) {\n      console.error(&quot;Error starting recording:&quot;, error);\n    }\n  };\n\n  const stopRecording = async () =&gt; {\n    try {\n      const result = await audioRecorderPlayer.stopRecorder();\n      setRecording(false);\n      console.log(&quot;Recording stopped:&quot;, result);\n\n      \/\/ Show the loader while sending the audio file\n      setLoading(true);\n\n      \/\/ Send audio file to backend\n      const formData = new FormData();\n      formData.append(&quot;audio&quot;, {\n        uri: `file:\/\/${result}`,\n        type: &quot;audio\/mpeg&quot;,\n        name: &quot;recording.mp4&quot;,\n      });\n\n      const response = await fetch(&quot;http:\/\/localhost:5019\/upload&quot;, {\n        method: &quot;POST&quot;,\n        body: formData,\n        headers: {\n          &quot;Content-Type&quot;: &quot;multipart\/form-data&quot;,\n        },\n      });\n\n      const data = await response.json();\n      console.log(&#039;data.transcription: &#039;, data.transcription);\n      setTranscription(data.transcription);\n\n      \/\/ Hide the loader after API call is complete\n      setLoading(false);\n\n    } catch (error) {\n      console.error(&quot;Error stopping recording:&quot;, error);\n      setLoading(false); \/\/ Ensure loader is hidden even if there&#039;s an error\n    }\n  };\n\n  return (\n    &lt;View style={styles.container}&gt;\n      &lt;Button\n        title={recording ? &quot;Stop Recording&quot; : &quot;Start Recording&quot;}\n        onPress={recording ? stopRecording : startRecording}\n      \/&gt;\n      &lt;Text style={styles.text}&gt;\n        {transcription ? `Transcription: ${transcription}` : &quot;&quot;}\n      &lt;\/Text&gt;\n      {loading &amp;&amp; (\n        &lt;View style={styles.loaderContainer}&gt;\n          &lt;ActivityIndicator size=&quot;large&quot; color=&quot;#0000ff&quot; \/&gt;\n        &lt;\/View&gt;\n      )}\n    &lt;\/View&gt;\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    justifyContent: &quot;center&quot;,\n    alignItems: &quot;center&quot;,\n  },\n  text: {\n    marginTop: 20,\n    fontSize: 16,\n  },\n  loaderContainer: {\n    position: &quot;absolute&quot;,\n    top: 0,\n    left: 0,\n    right: 0,\n    bottom: 0,\n    justifyContent: &quot;center&quot;,\n    alignItems: &quot;center&quot;,\n    backgroundColor: &quot;rgba(255, 255, 255, 0.6)&quot;, \/\/ Optional: dim the background\n    zIndex: 10, \/\/ Ensure it stays above other components\n  },\n});\n\nexport default App;\n<\/code><\/pre>\n                        <amp-iframe sandbox=\"allow-scripts\" width=\"94\" height=\"72\" frameborder=\"0\" \n                                    src=\"https:\/\/zahiralam.com\/blog\/wp-content\/plugins\/amp-copy-code-button\/copier.html#import%20React%2C%20%7B%20useState%20%7D%20from%20%22react%22%3B%0Aimport%20%7B%20View%2C%20Text%2C%20Button%2C%20StyleSheet%2C%20ActivityIndicator%20%7D%20from%20%22react-native%22%3B%0Aimport%20AudioRecorderPlayer%20from%20%22react-native-audio-recorder-player%22%3B%0A%0Aconst%20audioRecorderPlayer%20%3D%20new%20AudioRecorderPlayer%28%29%3B%0A%0Aconst%20App%20%3D%20%28%29%20%3D%3E%20%7B%0A%20%20const%20%5Brecording%2C%20setRecording%5D%20%3D%20useState%28false%29%3B%0A%20%20const%20%5Btranscription%2C%20setTranscription%5D%20%3D%20useState%28%22%22%29%3B%0A%20%20const%20%5Bloading%2C%20setLoading%5D%20%3D%20useState%28false%29%3B%20%2F%2F%20State%20for%20the%20spinner%20loader%0A%0A%20%20const%20startRecording%20%3D%20async%20%28%29%20%3D%3E%20%7B%0A%20%20%20%20try%20%7B%0A%20%20%20%20%20%20setRecording%28true%29%3B%0A%20%20%20%20%20%20const%20path%20%3D%20%22recording.m4a%22%3B%20%2F%2F%20Recorded%20file%20path%0A%20%20%20%20%20%20await%20audioRecorderPlayer.startRecorder%28path%29%3B%0A%20%20%20%20%20%20console.log%28%22Recording%20started%22%29%3B%0A%20%20%20%20%7D%20catch%20%28error%29%20%7B%0A%20%20%20%20%20%20console.error%28%22Error%20starting%20recording%3A%22%2C%20error%29%3B%0A%20%20%20%20%7D%0A%20%20%7D%3B%0A%0A%20%20const%20stopRecording%20%3D%20async%20%28%29%20%3D%3E%20%7B%0A%20%20%20%20try%20%7B%0A%20%20%20%20%20%20const%20result%20%3D%20await%20audioRecorderPlayer.stopRecorder%28%29%3B%0A%20%20%20%20%20%20setRecording%28false%29%3B%0A%20%20%20%20%20%20console.log%28%22Recording%20stopped%3A%22%2C%20result%29%3B%0A%0A%20%20%20%20%20%20%2F%2F%20Show%20the%20loader%20while%20sending%20the%20audio%20file%0A%20%20%20%20%20%20setLoading%28true%29%3B%0A%0A%20%20%20%20%20%20%2F%2F%20Send%20audio%20file%20to%20backend%0A%20%20%20%20%20%20const%20formData%20%3D%20new%20FormData%28%29%3B%0A%20%20%20%20%20%20formData.append%28%22audio%22%2C%20%7B%0A%20%20%20%20%20%20%20%20uri%3A%20%60file%3A%2F%2F%24%7Bresult%7D%60%2C%0A%20%20%20%20%20%20%20%20type%3A%20%22audio%2Fmpeg%22%2C%0A%20%20%20%20%20%20%20%20name%3A%20%22recording.mp4%22%2C%0A%20%20%20%20%20%20%7D%29%3B%0A%0A%20%20%20%20%20%20const%20response%20%3D%20await%20fetch%28%22http%3A%2F%2Flocalhost%3A5019%2Fupload%22%2C%20%7B%0A%20%20%20%20%20%20%20%20method%3A%20%22POST%22%2C%0A%20%20%20%20%20%20%20%20body%3A%20formData%2C%0A%20%20%20%20%20%20%20%20headers%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%22Content-Type%22%3A%20%22multipart%2Fform-data%22%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%7D%29%3B%0A%0A%20%20%20%20%20%20const%20data%20%3D%20await%20response.json%28%29%3B%0A%20%20%20%20%20%20console.log%28%27data.transcription%3A%20%27%2C%20data.transcription%29%3B%0A%20%20%20%20%20%20setTranscription%28data.transcription%29%3B%0A%0A%20%20%20%20%20%20%2F%2F%20Hide%20the%20loader%20after%20API%20call%20is%20complete%0A%20%20%20%20%20%20setLoading%28false%29%3B%0A%0A%20%20%20%20%7D%20catch%20%28error%29%20%7B%0A%20%20%20%20%20%20console.error%28%22Error%20stopping%20recording%3A%22%2C%20error%29%3B%0A%20%20%20%20%20%20setLoading%28false%29%3B%20%2F%2F%20Ensure%20loader%20is%20hidden%20even%20if%20there%27s%20an%20error%0A%20%20%20%20%7D%0A%20%20%7D%3B%0A%0A%20%20return%20%28%0A%20%20%20%20%3CView%20style%3D%7Bstyles.container%7D%3E%0A%20%20%20%20%20%20%3CButton%0A%20%20%20%20%20%20%20%20title%3D%7Brecording%20%3F%20%22Stop%20Recording%22%20%3A%20%22Start%20Recording%22%7D%0A%20%20%20%20%20%20%20%20onPress%3D%7Brecording%20%3F%20stopRecording%20%3A%20startRecording%7D%0A%20%20%20%20%20%20%2F%3E%0A%20%20%20%20%20%20%3CText%20style%3D%7Bstyles.text%7D%3E%0A%20%20%20%20%20%20%20%20%7Btranscription%20%3F%20%60Transcription%3A%20%24%7Btranscription%7D%60%20%3A%20%22%22%7D%0A%20%20%20%20%20%20%3C%2FText%3E%0A%20%20%20%20%20%20%7Bloading%20%26%26%20%28%0A%20%20%20%20%20%20%20%20%3CView%20style%3D%7Bstyles.loaderContainer%7D%3E%0A%20%20%20%20%20%20%20%20%20%20%3CActivityIndicator%20size%3D%22large%22%20color%3D%22%230000ff%22%20%2F%3E%0A%20%20%20%20%20%20%20%20%3C%2FView%3E%0A%20%20%20%20%20%20%29%7D%0A%20%20%20%20%3C%2FView%3E%0A%20%20%29%3B%0A%7D%3B%0A%0Aconst%20styles%20%3D%20StyleSheet.create%28%7B%0A%20%20container%3A%20%7B%0A%20%20%20%20flex%3A%201%2C%0A%20%20%20%20justifyContent%3A%20%22center%22%2C%0A%20%20%20%20alignItems%3A%20%22center%22%2C%0A%20%20%7D%2C%0A%20%20text%3A%20%7B%0A%20%20%20%20marginTop%3A%2020%2C%0A%20%20%20%20fontSize%3A%2016%2C%0A%20%20%7D%2C%0A%20%20loaderContainer%3A%20%7B%0A%20%20%20%20position%3A%20%22absolute%22%2C%0A%20%20%20%20top%3A%200%2C%0A%20%20%20%20left%3A%200%2C%0A%20%20%20%20right%3A%200%2C%0A%20%20%20%20bottom%3A%200%2C%0A%20%20%20%20justifyContent%3A%20%22center%22%2C%0A%20%20%20%20alignItems%3A%20%22center%22%2C%0A%20%20%20%20backgroundColor%3A%20%22rgba%28255%2C%20255%2C%20255%2C%200.6%29%22%2C%20%2F%2F%20Optional%3A%20dim%20the%20background%0A%20%20%20%20zIndex%3A%2010%2C%20%2F%2F%20Ensure%20it%20stays%20above%20other%20components%0A%20%20%7D%2C%0A%7D%29%3B%0A%0Aexport%20default%20App%3B%0A\">\n                            <button class=\"copy-button\" data-label=\"import React, { useState } from &quot;react&quot;;\nimport { View, Text, Button, StyleSheet, ActivityIndicator } from &quot;react-native&quot;;\nimport AudioRecorderPlayer from &quot;react-native-audio-recorder-player&quot;;\n\nconst audioRecorderPlayer = new AudioRecorderPlayer();\n\nconst App = () =&gt; {\n  const [recording, setRecording] = useState(false);\n  const [transcription, setTranscription] = useState(&quot;&quot;);\n  const [loading, setLoading] = useState(false); \/\/ State for the spinner loader\n\n  const startRecording = async () =&gt; {\n    try {\n      setRecording(true);\n      const path = &quot;recording.m4a&quot;; \/\/ Recorded file path\n      await audioRecorderPlayer.startRecorder(path);\n      console.log(&quot;Recording started&quot;);\n    } catch (error) {\n      console.error(&quot;Error starting recording:&quot;, error);\n    }\n  };\n\n  const stopRecording = async () =&gt; {\n    try {\n      const result = await audioRecorderPlayer.stopRecorder();\n      setRecording(false);\n      console.log(&quot;Recording stopped:&quot;, result);\n\n      \/\/ Show the loader while sending the audio file\n      setLoading(true);\n\n      \/\/ Send audio file to backend\n      const formData = new FormData();\n      formData.append(&quot;audio&quot;, {\n        uri: `file:\/\/${result}`,\n        type: &quot;audio\/mpeg&quot;,\n        name: &quot;recording.mp4&quot;,\n      });\n\n      const response = await fetch(&quot;http:\/\/localhost:5019\/upload&quot;, {\n        method: &quot;POST&quot;,\n        body: formData,\n        headers: {\n          &quot;Content-Type&quot;: &quot;multipart\/form-data&quot;,\n        },\n      });\n\n      const data = await response.json();\n      console.log(&#039;data.transcription: &#039;, data.transcription);\n      setTranscription(data.transcription);\n\n      \/\/ Hide the loader after API call is complete\n      setLoading(false);\n\n    } catch (error) {\n      console.error(&quot;Error stopping recording:&quot;, error);\n      setLoading(false); \/\/ Ensure loader is hidden even if there&#039;s an error\n    }\n  };\n\n  return (\n    &lt;View style={styles.container}&gt;\n      &lt;Button\n        title={recording ? &quot;Stop Recording&quot; : &quot;Start Recording&quot;}\n        onPress={recording ? stopRecording : startRecording}\n      \/&gt;\n      &lt;Text style={styles.text}&gt;\n        {transcription ? `Transcription: ${transcription}` : &quot;&quot;}\n      &lt;\/Text&gt;\n      {loading &amp;&amp; (\n        &lt;View style={styles.loaderContainer}&gt;\n          &lt;ActivityIndicator size=&quot;large&quot; color=&quot;#0000ff&quot; \/&gt;\n        &lt;\/View&gt;\n      )}\n    &lt;\/View&gt;\n  );\n};\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    justifyContent: &quot;center&quot;,\n    alignItems: &quot;center&quot;,\n  },\n  text: {\n    marginTop: 20,\n    fontSize: 16,\n  },\n  loaderContainer: {\n    position: &quot;absolute&quot;,\n    top: 0,\n    left: 0,\n    right: 0,\n    bottom: 0,\n    justifyContent: &quot;center&quot;,\n    alignItems: &quot;center&quot;,\n    backgroundColor: &quot;rgba(255, 255, 255, 0.6)&quot;, \/\/ Optional: dim the background\n    zIndex: 10, \/\/ Ensure it stays above other components\n  },\n});\n\nexport default App;\n\"  placeholder disabled>Copy<\/button>\n                        <\/amp-iframe>\n                    <\/div>\n\n\n\n<p>4. Run the React Native app:\n\n\n\n<div class=\"code-block-container\">\n                        <pre class=\"wp-block-code\"><code id=\"code-12\">npx react-native run-ios<\/code><\/pre>\n                        <amp-iframe sandbox=\"allow-scripts\" width=\"94\" height=\"72\" frameborder=\"0\" \n                                    src=\"https:\/\/zahiralam.com\/blog\/wp-content\/plugins\/amp-copy-code-button\/copier.html#npx%20react-native%20run-ios\">\n                            <button class=\"copy-button\" data-label=\"npx react-native run-ios\"  placeholder disabled>Copy<\/button>\n                        <\/amp-iframe>\n                    <\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Step 4: Test the App<\/h3>\n\n\n\n<p>1. Start the Node.js backend:\n\n\n\n<div class=\"code-block-container\">\n                        <pre class=\"wp-block-code\"><code id=\"code-13\">cd backend &amp;&amp; node server.js<\/code><\/pre>\n                        <amp-iframe sandbox=\"allow-scripts\" width=\"94\" height=\"72\" frameborder=\"0\" \n                                    src=\"https:\/\/zahiralam.com\/blog\/wp-content\/plugins\/amp-copy-code-button\/copier.html#cd%20backend%20%26%26%20node%20server.js\">\n                            <button class=\"copy-button\" data-label=\"cd backend &amp;&amp; node server.js\"  placeholder disabled>Copy<\/button>\n                        <\/amp-iframe>\n                    <\/div>\n\n\n\n<p>2. Run the React Native app and test by recording your voice.\n\n\n\n<p>3. The transcription should appear in the app after you stop the recording.\n\n\n\n<p>\n\n\n\n<h3 class=\"wp-block-heading\">Summary<\/h3>\n\n\n\n<p>By following these steps, you\u2019ve successfully created a React Native app with a Node.js backend that records audio, converts it to text using Google\u2019s Speech-to-Text API, and displays the transcription. This process involved configuring React Native, setting up a Node.js backend with FFmpeg for audio conversion, and leveraging Google\u2019s powerful speech recognition capabilities.\n","protected":false},"excerpt":{"rendered":"<p>This article will guide you through building a mobile app using React Native for the frontend and Node.js for the backend, where users can record [&#8230;]<\/p>\n","protected":false},"author":1,"featured_media":1454,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[77],"tags":[55,54,254],"class_list":["post-1444","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-mobile-development","tag-nodejs","tag-react-native","tag-voice-to-text-app"],"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/zahiralam.com\/blog\/wp-json\/wp\/v2\/posts\/1444","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/zahiralam.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/zahiralam.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/zahiralam.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/zahiralam.com\/blog\/wp-json\/wp\/v2\/comments?post=1444"}],"version-history":[{"count":22,"href":"https:\/\/zahiralam.com\/blog\/wp-json\/wp\/v2\/posts\/1444\/revisions"}],"predecessor-version":[{"id":1479,"href":"https:\/\/zahiralam.com\/blog\/wp-json\/wp\/v2\/posts\/1444\/revisions\/1479"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/zahiralam.com\/blog\/wp-json\/wp\/v2\/media\/1454"}],"wp:attachment":[{"href":"https:\/\/zahiralam.com\/blog\/wp-json\/wp\/v2\/media?parent=1444"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/zahiralam.com\/blog\/wp-json\/wp\/v2\/categories?post=1444"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/zahiralam.com\/blog\/wp-json\/wp\/v2\/tags?post=1444"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}