Add live captions to a video call app with daily-js

This page summarizes the projects mentioned and recommended in the original post on dev.to

Our great sponsors
  • SurveyJS - Open-Source JSON Form Builder to Create Dynamic Forms Right in Your App
  • InfluxDB - Power Real-Time Data Analytics at Scale
  • WorkOS - The modern identity platform for B2B SaaS
  • prebuilt-transcription

  • If you are like spoilers and want to see what we’re building today, you can jump straight into the prebuilt-transcription code and also try a live demo

  • react-window

    React components for efficiently rendering large lists and tabular data

  • Without this styling for the and its container, Daily Prebuilt defaults to taking up only a small amount of space on the page. You can change the styling however you like and Daily Prebuilt will fit within those constraints.

    Note: We are using wrap() to add Daily Prebuilt to an existing </code>, but you could also use the <a href="https://docs.daily.co/reference/daily-js/factory-methods/create-frame"><code>createFrame</code></a> method to make a new <code><iframe></code>, style that frame, and add it to the page.</em></p> <p><a href="https://res.cloudinary.com/practicaldev/image/fetch/s--U9jJGgBM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.daily.co/blog/content/images/2022/04/transcription2.png" class="article-body-image-wrapper"><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U9jJGgBM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.daily.co/blog/content/images/2022/04/transcription2.png" alt="Video call UI when a token or call owner is not present and transcription can't be used" loading="lazy" width="880" height="499"></a></p> <h2> <a name="transcription-component" href="#transcription-component"> </a> Transcription component </h2> <p>Now that we have Daily Prebuilt loaded on the page, let’s start implementing transcription by adding a component to store our buttons and transcript. </p> <p>From our <code>[room].tsx</code>, we reference the <code>Transcription</code> component and pass some managed state to the component: </p> <ul> <li> <code>callFrame</code>: passes the Daily call frame object, which allows the component to start and stop transcription</li> <li> <code>newMsg</code>: sends each new transcripted message to the component for showing the text in the transcript window</li> <li> <code>owner</code>: this boolean tells the component whether the current user is or isn’t a <a href="https://docs.daily.co/reference/rest-api/meeting-tokens#participant-details-meeting-permissions-and-available-features">room owner</a> </li> <li> <code>isTranscribing</code>: this boolean tells the component that Daily is or isn’t currently transcribing. </li> </ul> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="o"><</span><span class="nx">Transcription</span> <span class="nx">callFrame</span><span class="o">=</span><span class="p">{</span><span class="nx">callFrame</span><span class="p">}</span> <span class="nx">newMsg</span><span class="o">=</span><span class="p">{</span><span class="nx">newMsg</span><span class="p">}</span> <span class="nx">owner</span><span class="o">=</span><span class="p">{</span><span class="nx">isOwner</span><span class="p">}</span> <span class="nx">isTranscribing</span><span class="o">=</span><span class="p">{</span><span class="nx">isTranscribing</span><span class="p">}</span> <span class="sr">/</span><span class="err">> </span></code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>In our <code>Transcription</code> component (defined in <code>components/Transcription.tsx</code>), we have a button that toggles the option to start or stop transcription based on whether transcription is currently active according to Daily (we’ll come back to that in a second):<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="o"><</span><span class="nx">button</span> <span class="nx">disabled</span><span class="o">=</span><span class="p">{</span><span class="o">!</span><span class="nx">owner</span><span class="p">}</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">isTranscribing</span> <span class="p">?</span> <span class="nx">stopTranscription</span><span class="p">()</span> <span class="p">:</span> <span class="nx">startTranscription</span><span class="p">();</span> <span class="p">}}</span> <span class="o">></span> <span class="p">{</span><span class="nx">isTranscribing</span> <span class="p">?</span> <span class="dl">"</span><span class="s2">Stop transcribing</span><span class="dl">"</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">Start transcribing</span><span class="dl">"</span><span class="p">}</span> <span class="o"><</span><span class="sr">/button</span><span class="err">> </span></code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>If the meeting participant is not an owner, this button will be disabled along with a message explaining that only meeting room owners can start transcription.</p> <p>This button utilizes these two simple functions:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="k">async</span> <span class="kd">function</span> <span class="nx">startTranscription</span><span class="p">()</span> <span class="p">{</span> <span class="nx">callFrame</span><span class="p">?.</span><span class="nx">startTranscription</span><span class="p">();</span> <span class="p">}</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">stopTranscription</span><span class="p">()</span> <span class="p">{</span> <span class="nx">callFrame</span><span class="p">?.</span><span class="nx">stopTranscription</span><span class="p">();</span> <span class="p">}</span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>How do these functions know if transcription is happening or not? For that, we jump back to <code>[room].tsx</code>. Earlier in the post, we looked at the basic structure of the <code>startCall</code> function. In our demo, this function also has a few lines dedicated to Daily event listeners. We are listening to a few Daily-emitted events that help us shape the video call experience. Two of these events are <a href="https://docs.daily.co/reference/daily-js/events/transcription-events#transcription-started"><code>transcription-started</code></a> and <a href="https://docs.daily.co/reference/daily-js/events/transcription-events#transcription-stopped"><code>transcription-stopped</code></a> events.</p> <p>When those events are emitted, we know to update the React state to set <code>isTranscribing</code> to its correct boolean value.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="nx">newCallFrame</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">transcription-started</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">setIsTranscribing</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span> <span class="p">});</span> <span class="nx">newCallFrame</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">transcription-stopped</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">setIsTranscribing</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span> <span class="p">});</span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p><em>Note: You can also use our new <a href="https://www.daily.co/blog/introducing-the-new-daily-react-hooks-library/">Daily React Hooks</a> library to more quickly connect your React-based app with Daily’s JavaScript API!</em></p> <h2> <a name="adding-transcription" href="#adding-transcription"> </a> Adding transcription </h2> <p>Now that we are able to start and stop transcription, we need to add the transcripts to the page. Our transcripts come in from Daily via an <a href=""><code>”app-message”</code></a> event. For that, we need another event listener within our <code>startCall</code> function. This checks whether each <code>”app-message”</code> came from the ID of “transcription” and whether it is a full sentence (that’s what <code>data.is_final</code> is doing below). When we have a message, we save the message as an object with the author’s username, the text transcription, and a timestamp.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="nx">newCallFrame</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span> <span class="dl">"</span><span class="s2">app-message</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">msg</span><span class="p">:</span> <span class="nx">DailyEventObjectAppMessage</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">msg</span><span class="p">?.</span><span class="nx">data</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="nx">msg</span><span class="p">?.</span><span class="nx">fromId</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">transcription</span><span class="dl">"</span> <span class="o">&&</span> <span class="nx">data</span><span class="p">?.</span><span class="nx">is_final</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">local</span> <span class="o">=</span> <span class="nx">newCallFrame</span><span class="p">.</span><span class="nx">participants</span><span class="p">().</span><span class="nx">local</span><span class="p">;</span> <span class="kd">const</span> <span class="na">name</span><span class="p">:</span> <span class="nx">string</span> <span class="o">=</span> <span class="nx">local</span><span class="p">.</span><span class="nx">session_id</span> <span class="o">===</span> <span class="nx">data</span><span class="p">.</span><span class="nx">session_id</span> <span class="p">?</span> <span class="nx">local</span><span class="p">.</span><span class="nx">user_name</span> <span class="p">:</span> <span class="nx">newCallFrame</span><span class="p">.</span><span class="nx">participants</span><span class="p">()[</span><span class="nx">data</span><span class="p">.</span><span class="nx">session_id</span><span class="p">].</span><span class="nx">user_name</span><span class="p">;</span> <span class="kd">const</span> <span class="na">text</span><span class="p">:</span> <span class="nx">string</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">text</span><span class="p">;</span> <span class="kd">const</span> <span class="na">timestamp</span><span class="p">:</span> <span class="nx">string</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">timestamp</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="nx">name</span><span class="p">.</span><span class="nx">length</span> <span class="o">&&</span> <span class="nx">text</span><span class="p">.</span><span class="nx">length</span> <span class="o">&&</span> <span class="nx">timestamp</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span> <span class="nx">setNewMsg</span><span class="p">({</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">text</span><span class="p">,</span> <span class="nx">timestamp</span> <span class="p">});</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">);</span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>We need some React state to hold messages, so we set up a <code>const</code> where we instantiate this state as an empty array to hold incoming message objects.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="p">[</span><span class="nx">messages</span><span class="p">,</span> <span class="nx">setMessage</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="o"><</span><span class="nb">Array</span><span class="o"><</span><span class="nx">transcriptMsg</span><span class="o">>></span><span class="p">([]);</span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>This is essentially all that needs to be done to get transcription on the page. You can loop through this array of messages and add them to the screen, or you can add each new message to the screen as it arrives. However, there’s one extra step worth taking to optimize your app for all of these messages, and we’ll see how that works in the next section.</p> <p><em>Note: Transcript messages are ephemeral. They are only available for the message the user has received while they are in the room. If you refresh your page, you’ll lose the transcripts. Similarly, new users will only see a transcript for conversations that have taken place since they’ve joined and not a history.</em></p> <p><a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OFCHaJHp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://www.daily.co/blog/content/images/2022/04/transcription3.gif" class="article-body-image-wrapper"><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OFCHaJHp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://www.daily.co/blog/content/images/2022/04/transcription3.gif" alt="Transcribed text scrolling up and down" loading="lazy" width="430" height="157" data-animated="true"></a></p> <h2> <a name="optimize-your-window" href="#optimize-your-window"> </a> Optimize your window </h2> <p>Seeing transcripts appear on the screen is super fun, but it can quickly slow down browser windows with the addition of so many DOM elements getting added to the screen. Below, we’ll cover not just how we add transcript messages to our page, but also how to do it in a way that is efficient and not overwhelming to anyone’s browser.</p> <p>To help with this, we need to add two dependencies to our app: <a href="https://www.npmjs.com/package/react-window"><code>react-window</code></a> and <a href="https://www.npmjs.com/package/react-virtualized-auto-sizer"><code>react-virtualized-auto-sizer</code></a>. These libraries help us by loading only the most recent messages. Instead of loading the entire array of message objects as HTML, the DOM only loads the small part of the data set visible in the window. This virtualization technique prevents poor performance caused by an overloaded browser tab holding too much data in memory. Users can still scroll up and see previous messages which are loaded as needed when requested.</p> <p>We have established <code>const</code>s for the transcript list and rows that instantiate as empty objects.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="kd">const</span> <span class="nx">listRef</span> <span class="o">=</span> <span class="nx">useRef</span><span class="o"><</span><span class="nx">any</span><span class="o">></span><span class="p">({});</span> <span class="kd">const</span> <span class="nx">rowRef</span> <span class="o">=</span> <span class="nx">useRef</span><span class="o"><</span><span class="nx">any</span><span class="o">></span><span class="p">({});</span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>We add new messages received from the parent <code>[room]</code> page to an array. We also have a small function that keeps the array of messages moving to the bottom (most recent) element every time a message is received.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">setMessage</span><span class="p">((</span><span class="na">messages</span><span class="p">:</span> <span class="nb">Array</span><span class="o"><</span><span class="nx">transcriptMsg</span><span class="o">></span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="k">return</span> <span class="p">[...</span><span class="nx">messages</span><span class="p">,</span> <span class="nx">newMsg</span><span class="p">];</span> <span class="p">});</span> <span class="p">},</span> <span class="p">[</span><span class="nx">newMsg</span><span class="p">]);</span> <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">messages</span> <span class="o">&&</span> <span class="nx">messages</span><span class="p">.</span><span class="nx">length</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">scrollToBottom</span><span class="p">();</span> <span class="p">};</span> <span class="p">}</span> <span class="p">},</span> <span class="p">[</span><span class="nx">messages</span><span class="p">]);</span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>For each row, we call a formatting function. It structures the transcript in the style of “Message Author: Message Text” on the left and Timestamp trimmed to a local time only on the right (styled with CSS, the handy-dandy <code>float:right</code>).<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="kd">function</span> <span class="nx">Row</span><span class="p">({</span> <span class="nx">index</span><span class="p">,</span> <span class="nx">style</span> <span class="p">}:</span> <span class="nx">rowProps</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">rowRef</span> <span class="o">=</span> <span class="nx">useRef</span><span class="o"><</span><span class="nx">any</span><span class="o">></span><span class="p">({});</span> <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">rowRef</span><span class="p">.</span><span class="nx">current</span><span class="p">)</span> <span class="p">{</span> <span class="nx">setRowHeight</span><span class="p">(</span><span class="nx">index</span><span class="p">,</span> <span class="nx">rowRef</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">clientHeight</span><span class="p">);</span> <span class="p">}</span> <span class="p">},</span> <span class="p">[</span><span class="nx">rowRef</span><span class="p">]);</span> <span class="k">return</span> <span class="p">(</span> <span class="o"><</span><span class="nx">div</span> <span class="nx">style</span><span class="o">=</span><span class="p">{</span><span class="nx">style</span><span class="p">}</span><span class="o">></span> <span class="p">{</span><span class="nx">messages</span><span class="p">[</span><span class="nx">index</span><span class="p">].</span><span class="nx">name</span> <span class="o">&&</span> <span class="p">(</span> <span class="o"><</span><span class="nx">div</span> <span class="nx">ref</span><span class="o">=</span><span class="p">{</span><span class="nx">rowRef</span><span class="p">}</span><span class="o">></span> <span class="p">{</span><span class="nx">messages</span><span class="p">[</span><span class="nx">index</span><span class="p">].</span><span class="nx">name</span><span class="p">}:</span> <span class="p">{</span><span class="nx">messages</span><span class="p">[</span><span class="nx">index</span><span class="p">].</span><span class="nx">text</span><span class="p">}</span> <span class="o"><</span><span class="nx">span</span> <span class="nx">className</span><span class="o">=</span><span class="p">{</span><span class="nx">styles</span><span class="p">.</span><span class="nx">timestamp</span><span class="p">}</span><span class="o">></span> <span class="p">{</span><span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nx">messages</span><span class="p">[</span><span class="nx">index</span><span class="p">].</span><span class="nx">timestamp</span><span class="p">).</span><span class="nx">toLocaleTimeString</span><span class="p">()}</span> <span class="o"><</span><span class="sr">/span</span><span class="err">> </span> <span class="o"><</span><span class="sr">/div</span><span class="err">> </span> <span class="p">)}</span> <span class="o"><</span><span class="sr">/div</span><span class="err">> </span> <span class="p">);</span> <span class="p">}</span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>Our rendered transcript block then looks like this, with each loaded row wrapped in the <code>react-window</code> List and <code>react-virtualized-auto-sizer</code> AutoSizer elements.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="o"><</span><span class="nx">AutoSizer</span><span class="o">></span> <span class="p">{({</span> <span class="nx">height</span><span class="p">,</span> <span class="nx">width</span> <span class="p">})</span> <span class="o">=></span> <span class="p">(</span> <span class="o"><</span><span class="nx">List</span> <span class="nx">height</span><span class="o">=</span><span class="p">{</span><span class="nx">height</span><span class="p">}</span> <span class="nx">width</span><span class="o">=</span><span class="p">{</span><span class="nx">width</span><span class="p">}</span> <span class="nx">itemCount</span><span class="o">=</span><span class="p">{</span><span class="nx">messages</span><span class="p">.</span><span class="nx">length</span><span class="p">}</span> <span class="nx">itemSize</span><span class="o">=</span><span class="p">{</span><span class="nx">getRowHeight</span><span class="p">}</span> <span class="nx">ref</span><span class="o">=</span><span class="p">{</span><span class="nx">listRef</span><span class="p">}</span> <span class="o">></span> <span class="p">{</span><span class="nx">Row</span><span class="p">}</span> <span class="o"><</span><span class="sr">/List</span><span class="err">> </span> <span class="p">)}</span> <span class="o"><</span><span class="sr">/AutoSizer</span><span class="err">> </span></code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <h2> <a name="download" href="#download"> </a> Download </h2> <p>The transcripts collected in this app are not available after the call concludes, so downloading them is helpful if you want to use them later.</p> <p>To do that, we need to prepare a chat file with all of the text, not just the text currently virtualized on the screen.</p> <p>We have already seen that we are using React state to collect and set messages. For preparing a plain text file with the transcript inside, we will add a <code>transcriptFile</code> state that instantiates as an empty string.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="p">[</span><span class="nx">transcriptFile</span><span class="p">,</span> <span class="nx">setTranscriptFile</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="o"><</span><span class="nx">string</span><span class="o">></span><span class="p">(</span><span class="dl">""</span><span class="p">);</span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>Next, let’s set up a <code>useEffect</code> to style the transcript in a way that works best for reviewing later. Unlike the live transcript where we have the timestamp on the right and set to local time only, this includes the full timestamp and date for every message.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="cm">/* Allow user to download most recent full transcript text */</span> <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">setTranscriptFile</span><span class="p">(</span> <span class="nb">window</span><span class="p">.</span><span class="nx">URL</span><span class="p">.</span><span class="nx">createObjectURL</span><span class="p">(</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">(</span> <span class="nx">messages</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">msg</span><span class="p">)</span> <span class="o">=></span> <span class="nx">msg</span><span class="p">.</span><span class="nx">name</span> <span class="p">?</span> <span class="s2">`</span><span class="p">${</span><span class="nx">msg</span><span class="p">.</span><span class="nx">timestamp</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="nx">msg</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2">: </span><span class="p">${</span><span class="nx">msg</span><span class="p">.</span><span class="nx">text</span><span class="p">}</span><span class="s2">\n`</span> <span class="p">:</span> <span class="s2">`Transcript\n`</span> <span class="p">),</span> <span class="p">{</span> <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">octet/stream</span><span class="dl">"</span> <span class="p">}</span> <span class="p">)</span> <span class="p">)</span> <span class="p">);</span> <span class="p">},</span> <span class="p">[</span><span class="nx">messages</span><span class="p">]);</span> </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>This link will get the most recent full transcript and by default save it as a file called <code>transcript.txt</code>, although this can be changed later by the user.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="o"><</span><span class="nx">a</span> <span class="nx">href</span><span class="o">=</span><span class="p">{</span><span class="nx">transcriptFile</span><span class="p">}</span> <span class="nx">download</span><span class="o">=</span><span class="dl">"</span><span class="s2">transcript.txt</span><span class="dl">"</span><span class="o">></span> <span class="nx">Download</span> <span class="nx">Transcript</span> <span class="o"><</span><span class="sr">/a</span><span class="err">> </span></code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <h2> <a name="conclusion" href="#conclusion"> </a> Conclusion </h2> <p>And there you have it! Using Daily Prebuilt and our new Transcription API with Deepgram, it’s not too much work to add a live transcript to your meetings. From what we’ve shown in this demo, you can easily add different styles (including to the Daily Prebuilt window itself by <a href="https://docs.daily.co/guides/daily-prebuilt-features/customizing-daily-prebuilt-calls-with-color-themes#main">customizing with your own color themes</a>)</p> <p>We would love to see what you’ve built using Daily. Reach out to us anytime at <a href="//mailto:[email protected]">[email protected]</a>!</p>

  • SurveyJS

    Open-Source JSON Form Builder to Create Dynamic Forms Right in Your App. With SurveyJS form UI libraries, you can build and style forms in a fully-integrated drag & drop form builder, render them in your JS app, and store form submission data in any backend, inc. PHP, ASP.NET Core, and Node.js.

    SurveyJS logo
  • Next.js

    The React Framework

  • This demo is based on the Next.js React framework, starting with the create-next-app template builder. This tutorial also uses TypeScript. If you are new to TypeScript, no worries! Because it’s built on top of JavaScript, it looks very similar with a few additional features and syntax.

NOTE: The number of mentions on this list indicates mentions on common posts plus user suggested alternatives. Hence, a higher number means a more popular project.

Suggest a related project

Related posts