{"id":37223,"date":"2025-03-17T07:00:00","date_gmt":"2025-03-17T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=37223"},"modified":"2025-11-26T18:15:25","modified_gmt":"2025-11-27T02:15:25","slug":"automatically-generate-adaptive-video-transcripts-ai","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai","title":{"rendered":"Automatically Generate Adaptive Video Transcripts With AI"},"content":{"rendered":"\n<p>Ensuring videos perform at different internet speeds and meet accessibility needs is conducive to a great video  experience. There are two boxes you should tick to do so: adaptive video streaming to deliver a smooth viewing experience and transcripts and subtitles (younger generations overwhelmingly prefer to <a href=\"https:\/\/www.bbc.com\/news\/entertainment-arts-59259964\" target=\"_blank\" rel=\"noreferrer noopener\">watch content with subtitles on<\/a>).<\/p>\n\n\n\n<p>Adding transcriptions and subtitles with <a href=\"https:\/\/cloudinary.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudinary<\/a>, the image and <a href=\"https:\/\/cloudinary.com\/guides\/video\/vpaas\">video platform <\/a>for storing, transforming, uploading, and delivering visual media, provides AI-powered tools to automate transcriptions and subtitles for video, improving accessibility and the viewing experience. This blog post shows you how create a flexible, adaptive video solution with automatic AI-generated transcripts using Next.js and Cloudinary.<\/p>\n\n\n\n<p>You can find the GitHub repository <a href=\"https:\/\/github.com\/Olanetsoft\/cloudinary-adaptive-video-with-ai-transcripts\">here<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<p>You should have:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Node.js installed.<\/li>\n\n\n\n<li>Knowledge of <a href=\"https:\/\/nextjs.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Next.js<\/a> and an understanding of <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript#:~:text=JavaScript%20(JS)%20is%20a%20lightweight,Apache%20CouchDB%20and%20Adobe%20Acrobat.\" target=\"_blank\" rel=\"noreferrer noopener\">JavaScript<\/a>.<\/li>\n\n\n\n<li>A free <a href=\"https:\/\/cloudinary.com\/users\/register\/free?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudinary account<\/a>.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Set Up the Project and Install Dependencies&nbsp;<\/h2>\n\n\n\n<p>First, clone the <a target=\"_blank\" href=\"https:\/\/github.com\/Olanetsoft\/cloudinary-adaptive-video-with-ai-transcripts\" rel=\"noreferrer noopener\">starter project<\/a> into your preferred folder and <strong>checkout<\/strong> to the starter branch using the Git commands below to quickly get started. This starter project is a Next.js application using the app directory configuration with Tailwind CSS for styling.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">git clone https:<span class=\"hljs-comment\">\/\/github.com\/Olanetsoft\/cloudinary-adaptive-video-with-ai-transcripts.git<\/span>\n\ncd cloudinary-adaptive-video-<span class=\"hljs-keyword\">with<\/span>-ai-transcripts\n\ngit checkout starter<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The starter project comes pre-configured with several essential packages that you&#8217;ll use throughout this tutorial. When you run npm install, the following packages and their dependencies will be installed, as specified in the package.json:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Dependencies:\n<ul class=\"wp-block-list\">\n<li>Next.js (^14)<\/li>\n\n\n\n<li>React (^18) and React DOM (^18)<\/li>\n\n\n\n<li>Cloudinary (^2.5.1)<\/li>\n\n\n\n<li>React Toastify (^10.0.6)<\/li>\n\n\n\n<li>Lucide React (^0.453.0)<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Dev dependencies:\n<ul class=\"wp-block-list\">\n<li>Tailwind CSS (^3.4.1)<\/li>\n\n\n\n<li>ESLint (^8) and ESLint Config Next (14.2.16)<\/li>\n\n\n\n<li>PostCSS (^8)<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p>Next, run the following command to install all dependencies using the <code>npm<\/code> package manager and start the project on <code>http:\/\/localhost:3000<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">code<\/span>&gt;<\/span>npm install &amp;&amp; npm run dev<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">code<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>When you run the starter project, you&#8217;ll see the basic UI for the upload component on the home page. The upload area is displayed, but the functionality to handle uploads is yet to be implemented.<\/p>\n\n\n<div class='c-callout  c-callout--inline-title c-callout--note'><strong class='c-callout__title'>Note:<\/strong> <p>The upload input is disabled, and there\u2019s no functionality connected to it yet. Throughout this tutorial, you\u2019ll implement the functionality step by step.<\/p>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\">Get Environment Variables From Cloudinary&nbsp;<\/h2>\n\n\n\n<p>You need environment credentials from your Cloudinary dashboard to use <a target=\"_blank\" href=\"https:\/\/cloudinary.com\/documentation\/image_transformations\" rel=\"noreferrer noopener\">Cloudinary image transformation<\/a> and create an adaptive video solution with automatic AI-generated transcripts.&nbsp;<\/p>\n\n\n\n<p>Log in to your <a target=\"_blank\" href=\"https:\/\/cloudinary.com\" rel=\"noreferrer noopener\">Cloudinary dashboard<\/a> to retrieve environment credentials such as the <strong>c<\/strong><strong>loud name<\/strong>, <strong>API key<\/strong>, and <strong>API secret<\/strong>.&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764209595\/blog-Automatically_Generate_Adaptive_Video_Transcripts_With_AI-1.png\" alt=\"Cloudinary dashboard\"\/><figcaption class=\"wp-element-caption\">Get product environment credentials on Cloudinary<\/figcaption><\/figure>\n\n\n\n<p>Then, create a <code>.env.local<\/code> file in the project\u2019s root folder using the following command:<\/p>\n\n\n\n<p>For macOS and Linux:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">code<\/span>&gt;<\/span>touch .env.local<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">code<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>For Windows(Command Prompt):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">code<\/span>&gt;<\/span>type NUL &gt; .env.local<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">code<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>For Windows(PowerShell):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">code<\/span>&gt;<\/span>New-Item -Path .env.local -ItemType File<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">code<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Add your environment credentials:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=<span class=\"hljs-string\">\"*********************\"<\/span>\n\nNEXT_PUBLIC_CLOUDINARY_API_KEY=<span class=\"hljs-string\">\"*********************\"<\/span>\n\nCLOUDINARY_API_SECRET=<span class=\"hljs-string\">\"*********************\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Replace ********************* with your actual Cloudinary credentials.<\/p>\n\n\n<div class='c-callout  c-callout--inline-title c-callout--note'><strong class='c-callout__title'>Note:<\/strong> <p>The NEXT_PUBLIC_ prefix exposes the variable to the browser. Do not expose sensitive information like API_SECRET with this prefix.<\/p>\n<\/div>\n\n\n<p>Your project structure should look like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">cloudinary-adaptive-video-<span class=\"hljs-keyword\">with<\/span>-ai-transcripts\/\n\n\u251c\u2500\u2500 node_modules\/\n\n\u251c\u2500\u2500 public\/\n\n\u251c\u2500\u2500 pages\/\n\n\u251c\u2500\u2500 styles\/\n\n\u251c\u2500\u2500 .env.local\n\n\u251c\u2500\u2500 package.json\n\n\u2514\u2500\u2500 next.config.js<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Next, you&#8217;ll activate the <strong>Google Video transcription Add-on<\/strong> in the following section.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Activating Google Video Transcription Add-On<\/h2>\n\n\n\n<p>Before creating a transcript that will be used for the video subtitle in this guide, you\u2019ll need to activate the Cloudinary Google AI Video Transcription add-on. The <a target=\"_blank\" href=\"https:\/\/cloudinary.com\/documentation\/google_ai_video_transcription_addon\" rel=\"noreferrer noopener\">Google AI Video Transcription add-on<\/a> automatically provides video transcription, generating speech-to-text transcripts of videos. It also supports the transcription of videos in any language.<\/p>\n\n\n\n<p>Navigate to the <strong>Add-ons<\/strong> section on your Cloudinary dashboard, and click the <a target=\"_blank\" href=\"https:\/\/cloudinary.com\/documentation\/google_ai_video_transcription_addon\" rel=\"noreferrer noopener\">Google AI Video Transcription<\/a> add-on.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764209597\/blog-Automatically_Generate_Adaptive_Video_Transcripts_With_AI-2.png\" alt=\"Cloudinary add-ons with Google Ai Video Transcription add-on outlined in red\"\/><figcaption class=\"wp-element-caption\">Navigate to the Add-ons section on your Cloudinary<\/figcaption><\/figure>\n\n\n\n<p>Next, click to subscribe to the free plan or any plan of your choice.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764209599\/blog-Automatically_Generate_Adaptive_Video_Transcripts_With_AI-3.png\" alt=\"\"\/><figcaption class=\"wp-element-caption\">Subscribe to the Google AI Video Transcription free plan<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Implementing the Upload API Route<\/h2>\n\n\n\n<p>Now that you\u2019ve activated the <strong>Google Video Transcription add-on<\/strong>, you&#8217;ll create an API route that handles video uploads to Cloudinary and requests automatic subtitle generation.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Create the API Route<\/h3>\n\n\n\n<p>In the <code>app\/ <\/code>directory, create a new folder named <code>api\/<\/code>, then add an <code>upload\/<\/code> folder within it, and finally, create a <code>route.js<\/code> file inside <code>upload\/<\/code> folder.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">mkdir -p app\/api\/upload\n\ntouch app\/api\/upload\/route.js<\/code><\/span><\/pre>\n\n\n<p>Add the following code to <code>app\/api\/upload\/route.js<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ app\/api\/upload\/route.js<\/span>\n\n<span class=\"hljs-string\">\"use server\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { NextResponse } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/server\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { v2 <span class=\"hljs-keyword\">as<\/span> cloudinary } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cloudinary\"<\/span>;\n\ncloudinary.config({\n\n\u00a0 <span class=\"hljs-attr\">cloud_name<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,\n\n\u00a0 <span class=\"hljs-attr\">api_key<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY,\n\n\u00a0 <span class=\"hljs-attr\">api_secret<\/span>: process.env.CLOUDINARY_API_SECRET,\n\n});\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">POST<\/span>(<span class=\"hljs-params\">req<\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-keyword\">try<\/span> {\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> formData = <span class=\"hljs-keyword\">await<\/span> req.formData();\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> file = formData.get(<span class=\"hljs-string\">\"video_file\"<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> buffer = Buffer.from(<span class=\"hljs-keyword\">await<\/span> file.arrayBuffer());\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> base64Video = <span class=\"hljs-string\">`data:<span class=\"hljs-subst\">${file.type}<\/span>;base64,<span class=\"hljs-subst\">${buffer.toString(<span class=\"hljs-string\">\"base64\"<\/span>)}<\/span>`<\/span>;\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Upload with VTT generation<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> uploadResult = <span class=\"hljs-keyword\">await<\/span> cloudinary.uploader.upload(base64Video, {\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">resource_type<\/span>: <span class=\"hljs-string\">\"video\"<\/span>,\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">public_id<\/span>: <span class=\"hljs-string\">`videos\/<span class=\"hljs-subst\">${<span class=\"hljs-built_in\">Date<\/span>.now()}<\/span>`<\/span>,\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">raw_convert<\/span>: <span class=\"hljs-string\">\"google_speech:vtt\"<\/span>, <span class=\"hljs-comment\">\/\/ Request VTT format<\/span>\n\n\u00a0 \u00a0 });\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> cloudName = process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME;\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Construct URLs following the documentation pattern<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> videoUrl = uploadResult.secure_url;\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> vttUrl = <span class=\"hljs-string\">`https:\/\/res.cloudinary.com\/<span class=\"hljs-subst\">${cloudName}<\/span>\/raw\/upload\/v<span class=\"hljs-subst\">${\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-built_in\">parseInt<\/span>(uploadResult.version) + <span class=\"hljs-number\">1<\/span>\n\n\u00a0 \u00a0 }<\/span>\/<span class=\"hljs-subst\">${uploadResult.public_id}<\/span>.vtt`<\/span>;\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">return<\/span> NextResponse.json({\n\n\u00a0 \u00a0 \u00a0 videoUrl,\n\n\u00a0 \u00a0 \u00a0 vttUrl,\n\n\u00a0 \u00a0 });\n\n\u00a0 } <span class=\"hljs-keyword\">catch<\/span> (error) {\n\n\u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Upload error:\"<\/span>, error);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">return<\/span> NextResponse.json(\n\n\u00a0 \u00a0 \u00a0 { <span class=\"hljs-attr\">error<\/span>: <span class=\"hljs-string\">\"Failed to upload video\"<\/span> },\n\n\u00a0 \u00a0 \u00a0 { <span class=\"hljs-attr\">status<\/span>: <span class=\"hljs-number\">500<\/span> }\n\n\u00a0 \u00a0 );\n\n\u00a0 }\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the code above, you:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Configured Cloudinary with the credentials from the environment variables.<\/li>\n\n\n\n<li>Extracted the video file from the form data.<\/li>\n\n\n\n<li>Converted the video file to a base64-encoded string.<\/li>\n\n\n\n<li>Uploaded the video to Cloudinary, requesting automatic subtitle generation (<code>raw_convert: 'google_speech:vtt'<\/code>).<\/li>\n\n\n\n<li>Constructed the URLs for the uploaded video and the generated VTT subtitle file and returned a JSON response with the <code>videoUrl<\/code> and <code>vttUrl<\/code>.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Video Upload Component<\/h2>\n\n\n\n<p>Now, you&#8217;ll implement the frontend component that allows users to upload videos.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Enabling the Upload Input and Import Necessary Packages<\/h3>\n\n\n\n<p>In the page.js file inside the app\/ folder, you must enable the upload input and implement the functionality. At the top of page.js file, import the following packages that were installed earlier in the starter project:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ app\/page.js<\/span>\n\n<span class=\"hljs-string\">\"use client\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { useState, useEffect, useRef } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { Upload, Loader2 } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"lucide-react\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { ToastContainer, toast } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-toastify\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">\"react-toastify\/dist\/ReactToastify.css\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-comment\">\/\/...<\/span>\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Implement State Variables<\/h3>\n\n\n\n<p>Inside the Home component, define the following state variables:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ app\/page.js<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> &#91;videoUrl, setVideoUrl] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> &#91;vttUrl, setVttUrl] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> &#91;isLoading, setIsLoading] = useState(<span class=\"hljs-literal\">false<\/span>);\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Your code continues...<\/span>\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Implement the handleUpload Function<\/h3>\n\n\n\n<p>Add the <code>handleUpload<\/code> function inside the Home component:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ app\/page.js<\/span>\n\n<span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n<span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> handleUpload = <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> file = e.target?.files?.&#91;<span class=\"hljs-number\">0<\/span>];\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (!file) <span class=\"hljs-keyword\">return<\/span>;\n\n\u00a0 setIsLoading(<span class=\"hljs-literal\">true<\/span>);\n\n\u00a0 setVideoUrl(<span class=\"hljs-string\">\"\"<\/span>);\n\n\u00a0 setVttUrl(<span class=\"hljs-string\">\"\"<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> formData = <span class=\"hljs-keyword\">new<\/span> FormData();\n\n\u00a0 formData.append(<span class=\"hljs-string\">\"video_file\"<\/span>, file);\n\n\u00a0 <span class=\"hljs-keyword\">try<\/span> {\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/upload\"<\/span>, {\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">body<\/span>: formData,\n\n\u00a0 \u00a0 });\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (!response.ok) <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(<span class=\"hljs-string\">\"Upload failed\"<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> response.json();\n\n\u00a0 \u00a0 setVideoUrl(data.videoUrl);\n\n\u00a0 \u00a0 setVttUrl(data.vttUrl);\n\n\u00a0 \u00a0 toast.success(<span class=\"hljs-string\">\"Video uploaded successfully!\"<\/span>);\n\n\u00a0 } <span class=\"hljs-keyword\">catch<\/span> (err) {\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> message =\n\n\u00a0 \u00a0 \u00a0 err <span class=\"hljs-keyword\">instanceof<\/span> <span class=\"hljs-built_in\">Error<\/span> ? err.message : <span class=\"hljs-string\">\"Failed to upload video\"<\/span>;\n\n\u00a0 \u00a0 toast.error(message);\n\n\u00a0 } <span class=\"hljs-keyword\">finally<\/span> {\n\n\u00a0 \u00a0 setIsLoading(<span class=\"hljs-literal\">false<\/span>);\n\n\u00a0 }\n\n};\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the code above, you:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Defined the <code>handleUpload<\/code> function to retrieve the selected file from the Input Event.<\/li>\n\n\n\n<li>Checked if a file was selected; exit if not.<\/li>\n\n\n\n<li>Set the loading state to true, reset URLs, created a <code>FormData Object<\/code>, and appended the video file.<\/li>\n\n\n\n<li>Made a POST request to the <code>\/api\/upload<\/code> endpoint with <code>FormData<\/code>.<\/li>\n\n\n\n<li>Parsed the JSON response to obtain <code>videoUrl<\/code> and <code>vttUrl<\/code>.<\/li>\n\n\n\n<li>Updated state variables with the received URLs and displayed a success notification to the user.<\/li>\n\n\n\n<li>Caught and handled any errors during the upload process and reset the loading state after completion.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Update the Upload Input JSX<\/strong><\/h3>\n\n\n\n<p>Modify the upload input in the JSX to use the handleUpload function:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">\/\/ app\/page.js\n\n\/\/...\n\nexport default function Home() {\n\n\/\/...\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"border-2 border-dashed border-gray-700 rounded-lg p-8 text-center\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">label<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"cursor-pointer group\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"file\"<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">accept<\/span>=<span class=\"hljs-string\">\"video\/*\"<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{handleUpload}<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"hidden\"<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">disabled<\/span>=<span class=\"hljs-string\">{isLoading}<\/span>\n\n\u00a0 \u00a0 \u00a0 \/&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"space-y-4\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 {\/* ... *\/}\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">label<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Remove the disabled attribute from the input when you enable the functionality, and at the bottom of your JSX, include the ToastContainer for toast notification in the application:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ app\/page.js<\/span>\n\n<span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-keyword\">return<\/span> (\n\n\u00a0 \u00a0 <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"min-h-screen bg-gray-900 text-white p-4 md:p-8\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"max-w-5xl mx-auto\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 {\/* ... *\/}\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ToastContainer<\/span> <span class=\"hljs-attr\">theme<\/span>=<span class=\"hljs-string\">\"dark\"<\/span> \/&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n\n\u00a0 );\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>To test the upload functionality, you can start your development server if it&#8217;s not already running, navigate to <code>http:\/\/localhost:3000<\/code> in your browser, and try uploading a video file. If the upload is successful, you should see a success notification.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Displaying the Video Player<\/h2>\n\n\n\n<p>Now that you can upload videos and receive URLs, let&#8217;s add the video player. Below the upload component, add the following code snippet to:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Display the video player when a <code>videoUrl<\/code> is available.<\/li>\n\n\n\n<li>Add a <code>handleVideoPlay<\/code> function that shows subtitle generation notifications.<\/li>\n<\/ul>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ app\/page.js<\/span>\n\n<span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-comment\">\/\/...<\/span>\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Handle video play<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> handleVideoPlay = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (vttUrl) {\n\n\u00a0 \u00a0 \u00a0 toast.info(\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-string\">\"Generating subtitles. They will appear automatically when ready.\"<\/span>\n\n\u00a0 \u00a0 \u00a0 );\n\n\u00a0 \u00a0 }\n\n\u00a0 };\n\n\u00a0 <span class=\"hljs-keyword\">return<\/span> (\n\n\u00a0 \u00a0 <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"min-h-screen bg-gray-900 text-white p-4 md:p-8\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"max-w-5xl mx-auto\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 {\/* ... *\/}\n\n\u00a0 \u00a0 \u00a0 \u00a0 {!videoUrl ? (\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"border-2 border-dashed border-gray-700 rounded-lg p-8 text-center\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 {\/* ... *\/}\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 ) : (\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"space-y-6\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"relative rounded-lg overflow-hidden bg-black\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">ref<\/span>=<span class=\"hljs-string\">{videoRef}<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full aspect-video\"<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">controls<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">crossOrigin<\/span>=<span class=\"hljs-string\">\"anonymous\"<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">playsInline<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">autoBuffer<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">muted<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">onPlay<\/span>=<span class=\"hljs-string\">{handleVideoPlay}<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 &gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{videoUrl}<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/mp4\"<\/span> \/&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">video<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 )}\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ToastContainer<\/span> <span class=\"hljs-attr\">theme<\/span>=<span class=\"hljs-string\">\"dark\"<\/span> \/&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n\n\u00a0 );\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Adding Automatic Subtitles<\/h2>\n\n\n\n<p>Next, you&#8217;ll add the subtitles to the video player. The URL returned by Cloudinary via the <a target=\"_blank\" href=\"https:\/\/cloudinary.com\/documentation\/google_ai_video_transcription_addon\" rel=\"noreferrer noopener\">Google AI Video Transcription add-on <\/a>is the subtitle.<\/p>\n\n\n\n<p>Modify the <code>&lt;video&gt;<\/code> element to include the subtitles with the following code snippet:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ app\/page.js<\/span>\n\n<span class=\"hljs-comment\">\/\/...<\/span>\n\nexport <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n\n\u00a0 <span class=\"hljs-comment\">\/\/...<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">return<\/span> (\n\n\u00a0 \u00a0 \u00a0 {<span class=\"hljs-comment\">\/* ... *\/<\/span>}\n\n\u00a0 \u00a0 \u00a0 &lt;video\n\n\u00a0 \u00a0 \u00a0 \u00a0 className=<span class=\"hljs-string\">\"w-full aspect-video\"<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 controls\n\n\u00a0 \u00a0 \u00a0 \u00a0 crossOrigin=<span class=\"hljs-string\">\"anonymous\"<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 playsInline\n\n\u00a0 \u00a0 \u00a0 \u00a0 autoBuffer\n\n\u00a0 \u00a0 \u00a0 \u00a0 muted\n\n\u00a0 \u00a0 \u00a0 \u00a0 onPlay={handleVideoPlay}\n\n\u00a0 \u00a0 \u00a0 &gt;\n\n\u00a0 \u00a0 \u00a0 \u00a0 &lt;source src={videoUrl} type=<span class=\"hljs-string\">\"video\/mp4\"<\/span> \/&gt;\n\n\u00a0 \u00a0 \u00a0 \u00a0 {vttUrl &amp;&amp; (\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 &lt;track\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 label=<span class=\"hljs-string\">\"English\"<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 kind=<span class=\"hljs-string\">\"subtitles\"<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 srcLang=<span class=\"hljs-string\">\"en\"<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 src={vttUrl}\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">default<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \/&gt;\n\n\u00a0 \u00a0 \u00a0 \u00a0 )}\n\n\u00a0 \u00a0 \u00a0 &lt;\/video&gt;\n\n\u00a0 )\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the following step, you will implement adaptive streaming simulations.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Simulating Adaptive Streaming<\/h2>\n\n\n\n<p>You can simulate adaptive streaming by toggling the video quality based on user interaction. Add the following state variables for throttling at the top of your Home component.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ app\/page.js<\/span>\n\n<span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-comment\">\/\/...<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> &#91;isThrottled, setIsThrottled] = useState(<span class=\"hljs-literal\">false<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> &#91;currentQuality, setCurrentQuality] = useState(<span class=\"hljs-string\">\"auto\"<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">return<\/span> (\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ ...<\/span>\n\n\u00a0 )\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Implement the simulateThrottling Function<\/h3>\n\n\n\n<p>To simulate how the video player adapts to different bandwidth conditions, you&#8217;ll implement the <code>simulateThrottling<\/code> function. This function toggles between normal and throttled bandwidth states. When throttling is enabled, you&#8217;ll modify the video URL to request a lower-quality video version by adding transformation parameters to the Cloudinary URL (<code>q_auto:low<\/code>). This simulates a low-bandwidth environment, causing the video player to load a lower-quality video stream.&nbsp;<\/p>\n\n\n\n<p>You&#8217;ll also update the UI to reflect the current quality and provide feedback to the user using notifications. To do that, add the following function inside the Home component:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ app\/page.js<\/span>\n\n<span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-comment\">\/\/...<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> simulateThrottling = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n\n\u00a0 \u00a0 setIsThrottled(!isThrottled);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (!isThrottled) {\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> throttledUrl = videoUrl.replace(<span class=\"hljs-string\">\"\/upload\/\"<\/span>, <span class=\"hljs-string\">\"\/upload\/q_auto:low\/\"<\/span>);\n\n\u00a0 \u00a0 \u00a0 setCurrentQuality(<span class=\"hljs-string\">\"360p\"<\/span>);\n\n\u00a0 \u00a0 \u00a0 videoRef.current.src = throttledUrl;\n\n\u00a0 \u00a0 } <span class=\"hljs-keyword\">else<\/span> {\n\n\u00a0 \u00a0 \u00a0 setCurrentQuality(<span class=\"hljs-string\">\"auto\"<\/span>);\n\n\u00a0 \u00a0 \u00a0 videoRef.current.src = videoUrl;\n\n\u00a0 \u00a0 }\n\n\u00a0 \u00a0 toast.info(\n\n\u00a0 \u00a0 \u00a0 isThrottled ? <span class=\"hljs-string\">\"Restored normal bandwidth\"<\/span> : <span class=\"hljs-string\">\"Simulating low bandwidth\"<\/span>\n\n\u00a0 \u00a0 );\n\n\u00a0 };\n\n\u00a0 <span class=\"hljs-keyword\">return<\/span> (\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/...<\/span>\n\n\u00a0 )\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Next, add the throttling button with an <code>onClick<\/code> functionality referencing the <code>simulateThrottling<\/code> function and the <strong>Quality<\/strong> dropdown.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ app\/page.js<\/span>\n\n<span class=\"hljs-string\">\"use client\"<\/span>;\n\n<span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-comment\">\/\/...<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">return<\/span> (\n\n\u00a0 \u00a0 <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"min-h-screen bg-gray-900 text-white p-4 md:p-8\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"max-w-5xl mx-auto\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 {\/* ... *\/}\n\n\u00a0 \u00a0 \u00a0 \u00a0 {!videoUrl ? (\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 {\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \/* ... *\/\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 }\n\n\u00a0 \u00a0 \u00a0 \u00a0 ) : (\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"space-y-6\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 {\/* ... *\/}\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex justify-end gap-4\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{simulateThrottling}<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{<\/span>`<span class=\"hljs-attr\">px-4<\/span> <span class=\"hljs-attr\">py-2<\/span> <span class=\"hljs-attr\">rounded-md<\/span> <span class=\"hljs-attr\">text-sm<\/span> <span class=\"hljs-attr\">flex<\/span> <span class=\"hljs-attr\">items-center<\/span> <span class=\"hljs-attr\">gap-2<\/span> <span class=\"hljs-attr\">transition-colors<\/span> ${\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">isThrottled<\/span> ? \"<span class=\"hljs-attr\">bg-red-500<\/span>\/<span class=\"hljs-attr\">70<\/span>\" <span class=\"hljs-attr\">:<\/span> \"<span class=\"hljs-attr\">bg-black<\/span>\/<span class=\"hljs-attr\">70<\/span>\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 }`}\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 &gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 {isThrottled ? \"Throttled\" : \"Normal\"} Bandwidth\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">select<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{currentQuality}<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(e)<\/span> =&gt;<\/span> setCurrentQuality(e.target.value)}\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 className=\"bg-black\/70 text-white px-4 py-2 rounded-md text-sm\"\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 &gt;\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">option<\/span> <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">\"auto\"<\/span>&gt;<\/span>Auto Quality<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">option<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">option<\/span> <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">\"1080p\"<\/span>&gt;<\/span>1080p<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">option<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">option<\/span> <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">\"720p\"<\/span>&gt;<\/span>720p<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">option<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">option<\/span> <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">\"480p\"<\/span>&gt;<\/span>480p<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">option<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">option<\/span> <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">\"360p\"<\/span>&gt;<\/span>360p<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">option<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">select<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 )}\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ToastContainer<\/span> <span class=\"hljs-attr\">theme<\/span>=<span class=\"hljs-string\">\"dark\"<\/span> \/&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n\n\u00a0 );\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Monitoring Bandwidth and Displaying Statistics<\/h2>\n\n\n\n<p>Next, you\u2019ll implement bandwidth monitoring to display playback statistics such as current video quality, connection status, and estimated bandwidth. This feature provides real-time feedback to the user about their playback experience and demonstrates how the video player adapts to different network conditions.<\/p>\n\n\n\n<p>Add state variables and <code>useEffect<\/code> for bandwidth monitoring:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ app\/page.js<\/span>\n\n<span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-comment\">\/\/...<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> &#91;bandwidth, setBandwidth] = useState(<span class=\"hljs-literal\">null<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> videoRef = useRef(<span class=\"hljs-literal\">null<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">return<\/span> (\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/...<\/span>\n\n\u00a0 )\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Next, add the following <code>useEffect<\/code> hook inside the Home component:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ app\/page.js<\/span>\n\n<span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-comment\">\/\/...<\/span>\n\n\u00a0 useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (!videoRef.current) <span class=\"hljs-keyword\">return<\/span>;\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> video = videoRef.current;\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">let<\/span> lastLoadedBytes = <span class=\"hljs-number\">0<\/span>;\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">let<\/span> lastLoadedTime = <span class=\"hljs-built_in\">Date<\/span>.now();\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> updateBandwidth = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (!video.buffered.length) <span class=\"hljs-keyword\">return<\/span>;\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> loadedBytes =\n\n\u00a0 \u00a0 \u00a0 \u00a0 video.buffered.end(video.buffered.length - <span class=\"hljs-number\">1<\/span>) * <span class=\"hljs-number\">1024<\/span> * <span class=\"hljs-number\">1024<\/span>;\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> currentTime = <span class=\"hljs-built_in\">Date<\/span>.now();\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> timeDiff = (currentTime - lastLoadedTime) \/ <span class=\"hljs-number\">1000<\/span>;\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> bytesDiff = loadedBytes - lastLoadedBytes;\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (timeDiff &gt; <span class=\"hljs-number\">0<\/span>) {\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> bandwidthMbps = (bytesDiff \/ timeDiff \/ (<span class=\"hljs-number\">1024<\/span> * <span class=\"hljs-number\">1024<\/span>)).toFixed(<span class=\"hljs-number\">2<\/span>);\n\n\u00a0 \u00a0 \u00a0 \u00a0 setBandwidth(bandwidthMbps);\n\n\u00a0 \u00a0 \u00a0 }\n\n\u00a0 \u00a0 \u00a0 lastLoadedBytes = loadedBytes;\n\n\u00a0 \u00a0 \u00a0 lastLoadedTime = currentTime;\n\n\u00a0 \u00a0 };\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> intervalId = setInterval(updateBandwidth, <span class=\"hljs-number\">1000<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> clearInterval(intervalId);\n\n\u00a0 }, &#91;videoUrl]);\n\n\u00a0 <span class=\"hljs-keyword\">return<\/span> (\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/...<\/span>\n\n\u00a0 );\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the code snippet above, you:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Implemented <code>useEffect<\/code> Hook.<\/li>\n\n\n\n<li>Checked if the video element exists and initialized variables for loaded bytes and time.<\/li>\n\n\n\n<li>Defined a function <code>updateBandwidth<\/code> to calculate bandwidth.<\/li>\n\n\n\n<li>Set up an interval to call <code>updateBandwidth<\/code> every second.<\/li>\n\n\n\n<li>Cleaned up the interval when the component unmounts or <code>videoUrl<\/code> changes.<\/li>\n<\/ul>\n\n\n\n<p>Next, attach the ref to the Video Element by updating your <code>&lt;video&gt;<\/code> element to include the ref attribute:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">ref<\/span>=<span class=\"hljs-string\">{videoRef}<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full aspect-video\"<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">controls<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">crossOrigin<\/span>=<span class=\"hljs-string\">\"anonymous\"<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">playsInline<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">autoBuffer<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">muted<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">onPlay<\/span>=<span class=\"hljs-string\">{handleVideoPlay}<\/span>\n\n\u00a0 &gt;<\/span>\n\n\u00a0 \u00a0 {\/* ... *\/}\n\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">video<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Display Playback Statistics<\/h2>\n\n\n\n<p>Below the throttling button, add the following JSX code to display the playback statistics:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ app\/page.js<\/span>\n\n<span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-comment\">\/\/...<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">return<\/span> (\n\n\u00a0 \u00a0 \u00a0 <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"min-h-screen bg-gray-900 text-white p-4 md:p-8\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"max-w-5xl mx-auto\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mb-8\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0{\/* ... *\/}\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 {!videoUrl ? (\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 {\/* ... *\/}\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 ) : (\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 {\/* ... *\/}\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-gray-800\/50 rounded-lg p-4\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h3<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-sm font-semibold mb-2\"<\/span>&gt;<\/span>Playback Stats<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h3<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-sm text-gray-400 space-y-2\"<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>Current Quality: {currentQuality}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>Connection: {isThrottled ? \"Throttled\" : \"Normal\"}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 Bandwidth: {bandwidth ? `${bandwidth} Mbps` : \"Measuring...\"}\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 )}\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ToastContainer<\/span> <span class=\"hljs-attr\">theme<\/span>=<span class=\"hljs-string\">\"dark\"<\/span> \/&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n\n\u00a0 );\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Congratulations! You&#8217;ve successfully implemented an adaptive video player with automatic subtitles using Next.js and Cloudinary.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Testing the Application<\/h2>\n\n\n\n<p>You can now test the entire application by uploading, playing the video, throttling to simulate low bandwidth, and observing the bandwidth measurement and playback statistics. Here is a link to the full video of the demo:<\/p>\n\n\n<cld-video-player\n      cloud-name='cloudinary-marketing'\n      public-id='cloudinary-adaptive-demo_t1suzs'\n      js-config='{\"playbackRates\":[0.5,1,1.5,2]}'\n      style='max-width: ;'\n      class='c-video-player'\n      \n      core-version='2.12.3'\n      player-version='1.7.0'\n      >\n      <video\n        id='_video-player69dd80b12ad88'\n        data-cld-big-play-button='init'\n        data-cld-source-types='[\"hls\",\"webm\\\/vp9\",\"mp4\\\/h265\",\"mp4\"]'\n        controls\n        muted\n        class='cld-video-player cld-fluid wp-block-cloudinary-video-player  cld-video-player-skin-dark'\n      ><\/video>\n    <\/cld-video-player>\n\n<div class='c-callout  c-callout--inline-title c-callout--note'><strong class='c-callout__title'>Note:<\/strong> <p>After implementation, if the subtitle for your video doesn\u2019t show up, wait for a few seconds or minutes and retry. Don\u2019t refresh the page.<\/p>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>You now have a fully functional adaptive video player with AI-generated subtitles. This blog post showed you how to use <a target=\"_blank\" href=\"https:\/\/nextjs.org\/\" rel=\"noreferrer noopener\">Next.js<\/a> and <a target=\"_blank\" href=\"https:\/\/cloudinary.com\/\" rel=\"noreferrer noopener\">Cloudinary<\/a> to create a flexible, adaptive video solution with automatic AI-generated transcripts.<\/p>\n\n\n\n<p>To learn more about how to build accessible video experiences that work well across different network conditions with Cloudinary, <a target=\"_blank\" href=\"https:\/\/www.dropbox.com\/scl\/fi\/v4bozmdt8yjfjy3p3kw60\/Automatically-Generate-Adaptive-Video-Transcripts-With-AI.paper?rlkey=dwzdpe7818tlpclbo5mhdhdn4&amp;dl=0\" rel=\"noreferrer noopener\">contact us today<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Additional Resources<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/cloudinary.com\/documentation\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudinary Documentation<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/cloudinary.com\/documentation\/google_ai_video_transcription_addon#displaying_transcripts_as_subtitle_overlays\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudinary Google AI Video Transcription<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/cloudinary.com\/documentation\/video_manipulation_and_delivery\" target=\"_blank\" rel=\"noreferrer noopener\">Video Transformations<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Ensuring videos perform at different internet speeds and meet accessibility needs is conducive to a great video experience. There are two boxes you should tick to do so: adaptive video streaming to deliver a smooth viewing experience and transcripts and subtitles (younger generations overwhelmingly prefer to watch content with subtitles on). Adding transcriptions and subtitles [&hellip;]<\/p>\n","protected":false},"author":87,"featured_media":37224,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[336,303,310],"class_list":["post-37223","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-ai","tag-video","tag-video-player"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Automatically Generate Adaptive Video Transcripts With AI<\/title>\n<meta name=\"description\" content=\"Build accessible video experiences with AI-generated transcripts and adaptive streaming using Cloudinary and Next.js.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Automatically Generate Adaptive Video Transcripts With AI\" \/>\n<meta property=\"og:description\" content=\"Build accessible video experiences with AI-generated transcripts and adaptive streaming using Cloudinary and Next.js.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2025-03-17T14:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-11-27T02:15:25+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1736542468\/Automatically_generate_adaptive_video_with_AI_transcripts\/Automatically_generate_adaptive_video_with_AI_transcripts.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"2000\" \/>\n\t<meta property=\"og:image:height\" content=\"1100\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"melindapham\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Automatically Generate Adaptive Video Transcripts With AI\",\"datePublished\":\"2025-03-17T14:00:00+00:00\",\"dateModified\":\"2025-11-27T02:15:25+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai\"},\"wordCount\":1411,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1736542468\/Automatically_generate_adaptive_video_with_AI_transcripts\/Automatically_generate_adaptive_video_with_AI_transcripts.jpg?_i=AA\",\"keywords\":[\"AI\",\"Video\",\"Video Player\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2025\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai\",\"url\":\"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai\",\"name\":\"Automatically Generate Adaptive Video Transcripts With AI\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1736542468\/Automatically_generate_adaptive_video_with_AI_transcripts\/Automatically_generate_adaptive_video_with_AI_transcripts.jpg?_i=AA\",\"datePublished\":\"2025-03-17T14:00:00+00:00\",\"dateModified\":\"2025-11-27T02:15:25+00:00\",\"description\":\"Build accessible video experiences with AI-generated transcripts and adaptive streaming using Cloudinary and Next.js.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1736542468\/Automatically_generate_adaptive_video_with_AI_transcripts\/Automatically_generate_adaptive_video_with_AI_transcripts.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1736542468\/Automatically_generate_adaptive_video_with_AI_transcripts\/Automatically_generate_adaptive_video_with_AI_transcripts.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Automatically Generate Adaptive Video Transcripts With AI\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\",\"name\":\"melindapham\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"caption\":\"melindapham\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Automatically Generate Adaptive Video Transcripts With AI","description":"Build accessible video experiences with AI-generated transcripts and adaptive streaming using Cloudinary and Next.js.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai","og_locale":"en_US","og_type":"article","og_title":"Automatically Generate Adaptive Video Transcripts With AI","og_description":"Build accessible video experiences with AI-generated transcripts and adaptive streaming using Cloudinary and Next.js.","og_url":"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai","og_site_name":"Cloudinary Blog","article_published_time":"2025-03-17T14:00:00+00:00","article_modified_time":"2025-11-27T02:15:25+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1736542468\/Automatically_generate_adaptive_video_with_AI_transcripts\/Automatically_generate_adaptive_video_with_AI_transcripts.jpg?_i=AA","type":"image\/jpeg"}],"author":"melindapham","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Automatically Generate Adaptive Video Transcripts With AI","datePublished":"2025-03-17T14:00:00+00:00","dateModified":"2025-11-27T02:15:25+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai"},"wordCount":1411,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1736542468\/Automatically_generate_adaptive_video_with_AI_transcripts\/Automatically_generate_adaptive_video_with_AI_transcripts.jpg?_i=AA","keywords":["AI","Video","Video Player"],"inLanguage":"en-US","copyrightYear":"2025","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai","url":"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai","name":"Automatically Generate Adaptive Video Transcripts With AI","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1736542468\/Automatically_generate_adaptive_video_with_AI_transcripts\/Automatically_generate_adaptive_video_with_AI_transcripts.jpg?_i=AA","datePublished":"2025-03-17T14:00:00+00:00","dateModified":"2025-11-27T02:15:25+00:00","description":"Build accessible video experiences with AI-generated transcripts and adaptive streaming using Cloudinary and Next.js.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1736542468\/Automatically_generate_adaptive_video_with_AI_transcripts\/Automatically_generate_adaptive_video_with_AI_transcripts.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1736542468\/Automatically_generate_adaptive_video_with_AI_transcripts\/Automatically_generate_adaptive_video_with_AI_transcripts.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/automatically-generate-adaptive-video-transcripts-ai#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Automatically Generate Adaptive Video Transcripts With AI"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9","name":"melindapham","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","caption":"melindapham"}}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1736542468\/Automatically_generate_adaptive_video_with_AI_transcripts\/Automatically_generate_adaptive_video_with_AI_transcripts.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/37223","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/87"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=37223"}],"version-history":[{"count":5,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/37223\/revisions"}],"predecessor-version":[{"id":39444,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/37223\/revisions\/39444"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/37224"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=37223"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=37223"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=37223"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}