{"id":28020,"date":"2022-06-28T09:07:59","date_gmt":"2022-06-28T09:07:59","guid":{"rendered":"http:\/\/audio-platform-for-managing-twitter-spaces"},"modified":"2022-06-28T09:07:59","modified_gmt":"2022-06-28T09:07:59","slug":"audio-platform-for-managing-twitter-spaces","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/","title":{"rendered":"Audio Platform for Managing Twitter Spaces"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>One of the best ways to engage on Social Media today is Twitter Spaces &#8211; a relatively new feature on Twitter that allows users to host audio-based conversations to talk about whatever topics seem interesting to them.<\/p>\n<p>It has seen a lot of traction since its inception and the momentum doesn\u2019t seem to be getting lower. Like every other technology, users have already started recommending updates to the feature, like, adding comments to spaces, more reaction emojis etc.<\/p>\n<p>While we wait for those much-needed features, <a href=\"https:\/\/twitter.com\/alex__luong\">Alex<\/a> and <a href=\"https:\/\/twitter.com\/kenny_io\">myself<\/a> decided to work on a platform that will help Twitter space organizers aggregate, manage and reuse their recorded conversations.<\/p>\n<p>One of the biggest challenges we faced was the ability to download the recorded audio file, seeing as some spaces conversations last a very long time, the file size can get really big. However, Twitter recently made it possible to download the audio, and we figured now would be a good time to make this project.<\/p>\n<h3>Technologies we used:<\/h3>\n<ul>\n<li>Firebase &#8211; Database<\/li>\n<li>Cloudinary &#8211; Media hosting and storage<\/li>\n<li>Nextjs &#8211; Frontend framework<\/li>\n<li>TailwindCSS &#8211; Styling framework<\/li>\n<\/ul>\n<p>I should probably write a separate post that will piece together the process of combining all these technologies to achieve the project, but for the scope of this post, I\u2019ll limit my writing to how we used Cloudinary to handle the audio files and render it to the client for user consumption.<\/p>\n<h3>Cloudinary Integration<\/h3>\n<p>First, we thought of a service that could accommodate really large audio files without jeopardizing performance. We considered a few other technologies but ended up going with Cloudinary.<\/p>\n<p>To add Cloudinary to the Next.js project we had to first install it into the project via the CLI with<\/p>\n<pre class=\"js-syntax-highlighted\"><code>npm i cloudinary\n\n<\/code><\/pre>\n<p>Next, we added the Cloudinary CDN script to the project in <code>_document.js<\/code> file like so:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> Document, { Html, Head, Main, NextScript } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/document\"<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">MyDocument<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Document<\/span> <\/span>{\n  <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">async<\/span> getInitialProps(ctx) {\n    <span class=\"hljs-keyword\">const<\/span> initialProps = <span class=\"hljs-keyword\">await<\/span> Document.getInitialProps(ctx);\n    <span class=\"hljs-keyword\">return<\/span> { ...initialProps };\n  }\n  render() {\n    <span class=\"hljs-keyword\">return<\/span> (\n      <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Html<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Head<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span>\n            <span class=\"hljs-attr\">defer<\/span>\n            <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/widget.cloudinary.com\/v2.0\/global\/all.js\"<\/span>\n            <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\/javascript\"<\/span>\n          &gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Head<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Main<\/span> \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">NextScript<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Html<\/span>&gt;<\/span><\/span>\n    );\n  }\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> MyDocument;\n\n<\/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<p>The next thing we did was set environment variables to hold our Cloudinary credentials. We built the project with the Netlify CLI, so to create environment variables we did:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">ntl env:<span class=\"hljs-keyword\">set<\/span> NEXT_PUBLIC_CLOUDINARY_API_KEY \"OUR_API_KEY\"\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><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<p>We repeated the same procedure for other variables like the <code>API_SECRET<\/code>, <code>CLOUD_NAME<\/code>, <code>UPLOAD_PRESET<\/code> etc.<\/p>\n<p>After we set those variables, we needed to install some more Cloudinary packages to help with the audio file upload. For this, we decided to go with the Cloudinary Upload Widget. It is a handy tool that made it possible for us to maintain the UI design of our project without much overhead.<\/p>\n<p>To install the widget, we ran the command:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm i cloudinary\/widget, \n\n<\/code><\/span><\/pre>\n<p>When a user wants to upload a new space, we render a form that allows them to select both a banner for their space and the audio file containing the recording.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/allspaces\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1656017890\/upload-space_oelpcc.png\" alt=\"upload twitter space\" loading=\"lazy\" class=\"c-transformed-asset\" \/><\/p>\n<p>I\u2019m going off on a limb to guess that you\u2019re familiar with Cloudinary image uploads so I\u2019ll maintain focus on the audio file. Uploading audio files to Cloudinary is the same as uploading videos. The only difference is the file extension.<\/p>\n<p>For instance, if you have a file <code>my-recording.mp4<\/code> Cloudinary will treat this as a video file, however, if you change the file extension to <code>my-recording.mp3<\/code> Cloudinary will automatically convert it to an audio file.<\/p>\n<p>As a result, we configured our audio file upload logic just the same way  we would a video file:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">VideoUpload<\/span>(<span class=\"hljs-params\">{ userId, spaceId }<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> &#91;isAudioUploaded, setIsAudioUploaded] = useState(<span class=\"hljs-literal\">false<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> randomId = useMemo(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> createRandomId(), &#91;]);\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handleWidgetClick<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n    <span class=\"hljs-keyword\">const<\/span> widget = <span class=\"hljs-built_in\">window<\/span>.cloudinary.createUploadWidget(\n      {\n        <span class=\"hljs-attr\">cloudName<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,\n        <span class=\"hljs-attr\">uploadPreset<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET,\n        <span class=\"hljs-attr\">apiKey<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY,\n        <span class=\"hljs-attr\">publicId<\/span>: createAudioId(userId, spaceId, randomId),\n        <span class=\"hljs-attr\">resourceType<\/span>: <span class=\"hljs-string\">\"video\"<\/span>,\n      },\n      (error, result) =&gt; {\n        <span class=\"hljs-keyword\">if<\/span> (!error &amp;&amp; result &amp;&amp; result.event === <span class=\"hljs-string\">\"success\"<\/span>) {\n          setIsAudioUploaded(<span class=\"hljs-literal\">true<\/span>);\n        }\n      }\n    );\n\n    widget.open();\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> ();\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><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<p>The next thing we wanted to account for was security. The default Cloudinary upload method is <code>unsigned<\/code> which would allow us to upload files with an upload preset. I highlighted some security concerns of that approach in a <a href=\"https:\/\/mediajams.dev\/post\/signed-uploads-in-cloudinary-with-next.js\">separate article<\/a>. So to curb that, we used the <code>signed<\/code> upload method and set up our file upload logic like so:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { generateSignature } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/utils\/generateSignature\"<\/span>;\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">uploadAsset<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n    <span class=\"hljs-keyword\">const<\/span> widget = <span class=\"hljs-built_in\">window<\/span>.cloudinary.createUploadWidget(\n      {\n        <span class=\"hljs-attr\">cloudName<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,\n        <span class=\"hljs-attr\">uploadPreset<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET,\n        <span class=\"hljs-attr\">apiKey<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY,\n        <span class=\"hljs-attr\">uploadSignature<\/span>: generateSignature,\n        <span class=\"hljs-attr\">publicId<\/span>: createAudioId(userId, spaceId, randomId),\n      },\n      (error, result) =&gt; {\n        <span class=\"hljs-keyword\">if<\/span> (!error &amp;&amp; result &amp;&amp; result.event === <span class=\"hljs-string\">\"success\"<\/span>) {\n          setIsAudioUploaded(<span class=\"hljs-literal\">true<\/span>);\n        }\n      }\n    );\n\n    widget.open();\n  }\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><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<p>The addition of an <code>uploadSignature<\/code> parameter on the upload widget means that we are using a signed upload method and the upload details will be signed on a server using the <code>API KEY<\/code> to authorize the upload request.<\/p>\n<p>When we take a look at the <code>utils\/uploadSignature.js<\/code> file, we should see that it makes the request to our server and returns the signed upload signature:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">generateSignature<\/span>(<span class=\"hljs-params\">callback, paramsToSign<\/span>) <\/span>{\n  fetch(<span class=\"hljs-string\">`\/api\/sign`<\/span>, {\n    <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n    <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n      paramsToSign,\n    }),\n  })\n    .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">r<\/span>) =&gt;<\/span> r.json())\n    .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">{ signature }<\/span>) =&gt;<\/span> {\n      callback(signature);\n    });\n}\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><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<h3>Displaying audio files<\/h3>\n<p>After handling file uploads to Cloudinary with the signed upload method, we needed a way to render the files on our application so users can listen to it. For this, we used the Cloudinary <code>AdvancedVideo<\/code> component exported via the <code>@cloudinary\/react<\/code> package.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { AdvancedVideo } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@cloudinary\/react\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">AudioPlayer<\/span>(<span class=\"hljs-params\">{ video }<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">AdvancedVideo<\/span> <span class=\"hljs-attr\">cldVid<\/span>=<span class=\"hljs-string\">{video}<\/span> <span class=\"hljs-attr\">controls<\/span> \/&gt;<\/span><\/span>;\n}\n\n<\/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<p>And finally, we pass our Twitter space audio ID (from Cloudinary) into the <code>AudioPlayer<\/code> component above to render it for users to listen:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" 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\">AudioPlayer<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"player\"<\/span> <span class=\"hljs-attr\">video<\/span>=<span class=\"hljs-string\">{getVideo(space.audioId)}<\/span> \/&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><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<p>This is what renders the audio file in our application for users to interact with. It looks like this:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/allspaces\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1655910549\/space_xztygs.png\" alt=\"listen to space audio\" loading=\"lazy\" class=\"c-transformed-asset\" \/><\/p>\n<p>It is worthy to mention that while implementing this part of the application, we tried a couple of things that didn\u2019t quite work:<\/p>\n<ul>\n<li>HLS Streaming for the audio files and<\/li>\n<li>Cloudinary waveforms<\/li>\n<\/ul>\n<p>Might investigate it again to see how we can get these to work but If you\u2019d like to check out the web app, you can visit the <a href=\"https:\/\/twitterspaces.netlify.app\/\">staging version here<\/a> with the password <code>spaces123<\/code>. Feel free to <a href=\"https:\/\/github.com\/kenny-io\/TwitterSpaces\/pulls\">open a PR<\/a> if something catches your interest!<\/p>\n<p>In a later post, I\u2019ll dive deeper into how we structured the app to handle authentication with Firebase Firestore and organized the user data and content in Cloudinary.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28021,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[389,134,371],"class_list":["post-28020","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-audio","tag-guest-post","tag-under-review"],"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>Audio Platform for Managing Twitter Spaces<\/title>\n<meta name=\"description\" content=\"Learn how we were able to build an audio hub for Twitter spaces using Cloudinary and a host of other technologies.\" \/>\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\/guest_post\/audio-platform-for-managing-twitter-spaces\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Audio Platform for Managing Twitter Spaces\" \/>\n<meta property=\"og:description\" content=\"Learn how we were able to build an audio hub for Twitter spaces using Cloudinary and a host of other technologies.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-06-28T09:07:59+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923988\/Web_Assets\/blog\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"5184\" \/>\n\t<meta property=\"og:image:height\" content=\"3240\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\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\/guest_post\/audio-platform-for-managing-twitter-spaces\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Audio Platform for Managing Twitter Spaces\",\"datePublished\":\"2022-06-28T09:07:59+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/\"},\"wordCount\":6,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923988\/Web_Assets\/blog\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc.jpg?_i=AA\",\"keywords\":[\"Audio\",\"Guest Post\",\"Under Review\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/\",\"name\":\"Audio Platform for Managing Twitter Spaces\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923988\/Web_Assets\/blog\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc.jpg?_i=AA\",\"datePublished\":\"2022-06-28T09:07:59+00:00\",\"description\":\"Learn how we were able to build an audio hub for Twitter spaces using Cloudinary and a host of other technologies.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923988\/Web_Assets\/blog\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923988\/Web_Assets\/blog\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc.jpg?_i=AA\",\"width\":5184,\"height\":3240},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Audio Platform for Managing Twitter Spaces\"}]},{\"@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\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Audio Platform for Managing Twitter Spaces","description":"Learn how we were able to build an audio hub for Twitter spaces using Cloudinary and a host of other technologies.","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\/guest_post\/audio-platform-for-managing-twitter-spaces\/","og_locale":"en_US","og_type":"article","og_title":"Audio Platform for Managing Twitter Spaces","og_description":"Learn how we were able to build an audio hub for Twitter spaces using Cloudinary and a host of other technologies.","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-06-28T09:07:59+00:00","og_image":[{"width":5184,"height":3240,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923988\/Web_Assets\/blog\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc.jpg?_i=AA","type":"image\/jpeg"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/"},"author":{"name":"","@id":""},"headline":"Audio Platform for Managing Twitter Spaces","datePublished":"2022-06-28T09:07:59+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/"},"wordCount":6,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923988\/Web_Assets\/blog\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc.jpg?_i=AA","keywords":["Audio","Guest Post","Under Review"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/","name":"Audio Platform for Managing Twitter Spaces","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923988\/Web_Assets\/blog\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc.jpg?_i=AA","datePublished":"2022-06-28T09:07:59+00:00","description":"Learn how we were able to build an audio hub for Twitter spaces using Cloudinary and a host of other technologies.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923988\/Web_Assets\/blog\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923988\/Web_Assets\/blog\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc.jpg?_i=AA","width":5184,"height":3240},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/audio-platform-for-managing-twitter-spaces\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Audio Platform for Managing Twitter Spaces"}]},{"@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":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681923988\/Web_Assets\/blog\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc\/b75dcc48a91ea3b49582a90c9c11cb9708fe9fa1-5184x3240-1_2802165dbc.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28020","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\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28020"}],"version-history":[{"count":0,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28020\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28021"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28020"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28020"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28020"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}