<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Dyte]]></title><description><![CDATA[Learn about why and how we are building the most developer friendly video and audio SDKs.]]></description><link>https://dyte.io/blog/</link><image><url>https://dyte.io/blog/favicon.png</url><title>Dyte</title><link>https://dyte.io/blog/</link></image><generator>Ghost 5.78</generator><lastBuildDate>Thu, 16 Apr 2026 05:40:11 GMT</lastBuildDate><atom:link href="https://dyte.io/blog/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Bridging Your Telephony Calls to a Video Call]]></title><description><![CDATA[The blog explores merging telephony with video via SIP. Highlights benefits and use cases of Dyte's SIP Interconnect for seamless VOIP and WebRTC bridging.]]></description><link>https://dyte.io/blog/bridging-your-telephony-calls-video-call/</link><guid isPermaLink="false">65b121f73df14600014b3f01</guid><category><![CDATA[Announcement]]></category><dc:creator><![CDATA[Palash Golecha]]></dc:creator><pubDate>Tue, 03 Dec 2024 08:48:00 GMT</pubDate><media:content url="https://dyte.io/blog/content/images/2024/02/SIP-integration---2.png" medium="image"/><content:encoded><![CDATA[<img src="https://dyte.io/blog/content/images/2024/02/SIP-integration---2.png" alt="Bridging Your Telephony Calls to a Video Call"><p>This blog post offers an in-depth look at the world of telecommunication technologies, focusing on how traditional telephony and modern video conferencing are being integrated together. We will clarify complex concepts like VOIP, SIP, PSTN, and WebRTC, examining their interactions and the methods used to combine these technologies.</p><p>Bridging telephony calls with video calls is not just a technical feat; it serves practical and impactful use cases in various sectors. Here are some reasons why this integration is crucial:</p><ul><li><strong>Telehealth Services</strong>: In healthcare, integrating telephony with video conferencing enables doctors to offer more comprehensive telehealth services. Patients can initially contact healthcare providers through regular phone calls and, if necessary, switch to a video call for a more thorough consultation.</li><li><strong>Network Restrictions: </strong>Let&#x2019;s be honest, not every place has great internet, especially mobile internet, which is far more unstable indoors or while on the move. Network restrictions shouldn&#x2019;t block your customers from accessing your services.</li><li><strong>Customer Service</strong>: Customer support centers can benefit significantly from this integration. A customer calling through a traditional phone line can be quickly transferred to a WebRTC voice call with a support agent, allowing for more personalized and effective problem-solving.</li></ul><p>Now, let&#x2019;s get to understand what technologies we are dealing with to make all of this possible</p><h2 id="pstn-public-switched-telephone-network"><strong>PSTN (Public Switched Telephone Network):</strong></h2><p>PSTN is the traditional telephone network that has been in use for many years. It is a circuit-switched network that relies on copper wires, fiber optic cables, microwave transmission links, satellites, and undersea telephone cables</p><p>Most of your regular phone calls use PSTN to connect with each other</p><h2 id="voip"><strong>VOIP</strong></h2><p>VoIP (Voice over Internet Protocol) is a technology that allows voice communication to be transmitted over the Internet or other IP-based networks. It represents a significant shift from traditional telephony, which relies on circuit-switched networks such as the Public Switched Telephone Network (PSTN).</p><p>VoIP is an umbrella term that encompasses many different protocols for voice communications over the Internet, including voice calls</p><ul><li>VoIP service providers typically use gateways to connect VoIP networks with the PSTN, allowing seamless communication between internet-based and traditional telephone systems.</li><li>The integration of VoIP with PSTN enables users to enjoy the benefits of internet-based calling while still being able to connect with users on the conventional telephone network</li></ul><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/01/SIP-in-video-calls.png" class="kg-image" alt="Bridging Your Telephony Calls to a Video Call" loading="lazy" width="2000" height="710" srcset="https://dyte.io/blog/content/images/size/w600/2024/01/SIP-in-video-calls.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/01/SIP-in-video-calls.png 1000w, https://dyte.io/blog/content/images/size/w1600/2024/01/SIP-in-video-calls.png 1600w, https://dyte.io/blog/content/images/2024/01/SIP-in-video-calls.png 2237w" sizes="(min-width: 720px) 720px"></figure><h2 id="webrtc"><strong>WebRTC</strong></h2><p>This is the technology that is commonly used for video and audio communication on the web. WebRTC is an umbrella of protocols that enables web browsers and mobile applications to communicate directly with each other in real-time without the need for intermediaries.</p><p><strong>Everyone from Conferencing applications like Google Meet to SDK vendors like Dyte uses WebRTC to deliver real-time audio and video on the internet</strong></p><blockquote>Google Meet is popular, we are aware of it, but its latency and connectivity issues just doesn&apos;t work for modern teams - so here&apos;s a list of <a href="https://feta.io/blog/meet-alternatives" rel="noreferrer">Google Meet alternatives</a>.</blockquote><h2 id="sip-and-sip-trunking"><strong>SIP and SIP Trunking</strong></h2><p>SIP, or Session Initiation Protocol, is a set of rules governing the initiation, maintenance, and termination of VoIP calls. It acts as a supporting protocol that facilitates the functioning of VoIP technology i.e. it is a part of the VOIP protocols</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/01/SIP.png" class="kg-image" alt="Bridging Your Telephony Calls to a Video Call" loading="lazy" width="2000" height="710" srcset="https://dyte.io/blog/content/images/size/w600/2024/01/SIP.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/01/SIP.png 1000w, https://dyte.io/blog/content/images/size/w1600/2024/01/SIP.png 1600w, https://dyte.io/blog/content/images/2024/01/SIP.png 2237w" sizes="(min-width: 720px) 720px"></figure><h2 id="bridging-webrtc-with-telephony"><strong>Bridging WebRTC with Telephony</strong></h2><p>Session Initiation Protocol (SIP) Interconnect refers to the setup where two or more different SIP-based networks or systems are connected to enable the flow of voice traffic between them.</p><p>Dyte&apos;s SIP Interconnect allows you to bridge VOIP calls from an external third-party service to Dyte&apos;s WebRTC meetings. That means you can use SIP methodologies to connect with our SIP Servers and have it bridged to participants who might be connected through Dyte Client SDKs (WebRTC)</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/01/Webrtc-with-telephony.png" class="kg-image" alt="Bridging Your Telephony Calls to a Video Call" loading="lazy" width="2000" height="890" srcset="https://dyte.io/blog/content/images/size/w600/2024/01/Webrtc-with-telephony.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/01/Webrtc-with-telephony.png 1000w, https://dyte.io/blog/content/images/size/w1600/2024/01/Webrtc-with-telephony.png 1600w, https://dyte.io/blog/content/images/2024/01/Webrtc-with-telephony.png 2237w" sizes="(min-width: 720px) 720px"></figure><h3 id="integration-guide">Integration Guide</h3><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/01/image.png" class="kg-image" alt="Bridging Your Telephony Calls to a Video Call" loading="lazy" width="2000" height="1214" srcset="https://dyte.io/blog/content/images/size/w600/2024/01/image.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/01/image.png 1000w, https://dyte.io/blog/content/images/size/w1600/2024/01/image.png 1600w, https://dyte.io/blog/content/images/2024/01/image.png 2040w" sizes="(min-width: 720px) 720px"></figure><p>Get your SIP credentials from the <a href="https://dev.dyte.io/apikeys" rel="noopener noreferrer">Developer Portal</a> in the <code>API Keys</code> section<br>(You will have to contact support to enable the feature)</p><p>Our SIP server provides unique SIP user information with credentials. With this information, an SIP URL can be formed to connect to a meeting. The convention is <code>sip:&lt;meetingid&gt;@sip.dyte.io</code>.</p><p>Once you have the credentials, the simplest way to test the SIP Endpoint is using a SIP Client, you can use clients like Zoiper, Telephone(Mac only), etc.</p><p>Now, to connect to a specific Dyte <code>meetingId</code>, you can dial in using SIP with the given username and password and a URI in the format <code>sip:&lt;meetingId&gt;@sip.dyte.io</code></p><p>&#x1F389; That is it, once you dial with the above credentials your SIP call should be bridged with Dyte&apos;s WebRTC meeting.</p><h2 id="examples"><strong>Examples</strong></h2><h3 id="integration-with-twilio-voice"><strong>Integration with Twilio Voice</strong></h3><p>To connect with Dyte, we are going to use TwiML to perform the SIP dialin.</p><h3 id="guide">Guide</h3><p>Steps to follow</p><ol><li>Get a Twilio account. You can go to <a href="https://www.twilio.com/try-twilio">https://www.twilio.com/try-twilio</a> and create an account</li><li>Buy a VOIP number</li><li>Configure the VOIP number to <a href="https://www.twilio.com/docs/usage/webhooks/voice-webhooks#incoming-voice-call">use webhook to handle any incoming call</a></li></ol><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/01/image-4.png" class="kg-image" alt="Bridging Your Telephony Calls to a Video Call" loading="lazy" width="2000" height="884" srcset="https://dyte.io/blog/content/images/size/w600/2024/01/image-4.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/01/image-4.png 1000w, https://dyte.io/blog/content/images/size/w1600/2024/01/image-4.png 1600w, https://dyte.io/blog/content/images/size/w2400/2024/01/image-4.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Now, when you get a webhook, you can respond with a <a href="https://www.twilio.com/docs/voice/twiml/sip">TwiML SIP Dial</a> verb with Dyte&apos;s SIP configuration</p><pre><code class="language-XML">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;Response&gt;
&lt;Dial&gt;
    &lt;Sip username=&quot;&lt;DyteSIPUsername&gt;&quot; password=&quot;&lt;DyteSIPPassword&gt;&quot;&gt;sip:meetingId@sip.dyte.io&lt;/Sip&gt;
&lt;/Dial&gt;
&lt;/Response&gt;</code></pre><p><strong>Express Example</strong></p><pre><code class="language-Plain Text">const express = require(&apos;express&apos;);
const VoiceResponse = require(&apos;twilio&apos;).twiml.VoiceResponse;
const urlencoded = require(&apos;body-parser&apos;).urlencoded;

const app = express();

// Parse incoming POST params with Express middleware
app.use(urlencoded({ extended: false }));

// Create a route that will handle Twilio webhook requests, sent as an
// HTTP POST to /voice in our application
app.post(&apos;/voice&apos;, (request, response) =&gt; {
  console.log({ request });
// Use the Twilio Node.js SDK to build an XML response
  const twiml = new VoiceResponse();

  const dial = twiml.dial();
  dial.sip(
    {
      username: &apos;&lt;DyteSIPUsername&gt;&apos;,
      password: &apos;&lt;DyteSIPPassword&gt;&apos;,
    },
    &apos;sip:&lt;meetingId&gt;@sip.dyte.io&apos;
  );

// Render the response as XML in reply to the webhook request
  response.type(&apos;text/xml&apos;);
  console.log({ twiml: twiml.toString() });
  response.send(twiml.toString());
});
</code></pre><p>In conclusion, integrating telephony with video conferencing represents a is a practical development in communication technology, combining the reliability and widespread use of PSTN with the flexibility and modern capabilities of VoIP, WebRTC, and SIP. By bridging these technologies, services like Dyte are enabling more seamless and versatile communication experiences across various sectors, from telehealth to customer service. </p><p>This blend of old and new technology not only enhances existing communication methods but also opens the door for innovative applications in the future.</p>]]></content:encoded></item><item><title><![CDATA[Hiring Challenge: Smallest Golang Websocket Client]]></title><description><![CDATA[Learn to create a compact Go program for a websocket server, optimizing the binary size. Insights from a Dyte hiring challenge.]]></description><link>https://dyte.io/blog/hiring-challenge-smallest-golang-websocket-client/</link><guid isPermaLink="false">65afa55c3df14600014b3deb</guid><category><![CDATA[Engineering]]></category><dc:creator><![CDATA[Pratham K]]></dc:creator><pubDate>Fri, 29 Nov 2024 16:00:00 GMT</pubDate><media:content url="https://dyte.io/blog/content/images/2024/01/HIRING-CHALLANGE.png" medium="image"/><content:encoded><![CDATA[<h2 id="adventures-in-making-small-go-binaries"><strong>Adventures in making small Go binaries</strong></h2><img src="https://dyte.io/blog/content/images/2024/01/HIRING-CHALLANGE.png" alt="Hiring Challenge: Smallest Golang Websocket Client"><p>In this post, we&apos;ll write a small Go program to talk with a websocket server while trying to make the generated binary as small as possible. This was performed as part of one of Dyte&apos;s <a href="https://hacktofinale.dyte.live/challenges/golf">hiring challenges</a>, but the methods discussed here can be applied to any Go program in general. Do note that this is just for fun and not something you should try in production!</p><h2 id="problem-statement"><strong>Problem statement</strong></h2><p>So, we have a basic websocket server that accepts connections from a client and checks if it sent a <code>hello</code> message, whereas the client has to print out the server&apos;s response. The server-side code is written using the <a href="https://github.com/gorilla/websocket"><code>gorilla/websocket</code></a> package and can be found <a href="https://github.com/git-bruh/wscodegolf/blob/main/server/main.go">here</a>, but we won&apos;t really go through it here as our focus is on making the client-side binary small.</p><p>We&apos;ll be covering various methods throughout this post, ranging from swapping out the Go compiler, using an ELF packer, and tweaking linker flags to using raw syscalls instead of the standard library.</p><h2 id="humble-beginnings"><strong>Humble Beginnings</strong></h2><p>Let&apos;s start out by writing an obvious Go program using the <code>x/net/websocket</code> package:</p><pre><code class="language-Go">package main

import (
	&quot;fmt&quot;
	&quot;log&quot;

	&quot;golang.org/x/net/websocket&quot;
)

func main() {
	url := &quot;ws://localhost:8080/&quot;
	ws, err := websocket.Dial(url, &quot;&quot;, url)

	if err != nil {
		log.Fatal(err)
	}

	defer ws.Close()

	// Write the `hello` message
	if _, err := ws.Write([]byte(&quot;hello&quot;)); err != nil {
		log.Fatal(err)
	}

	// 512 byte buffer for storing the response
	var response = make([]byte, 512)

	// No. of bytes received
	var received int

	if received, err = ws.Read(response); err != nil {
		log.Fatal(err)
	}

	fmt.Printf(&quot;Received: %s\n&quot;, response[:received])
}</code></pre><p>Building and running it, we get a ~5.8 MiB binary (6084899 bytes), which is far from our goal:</p><pre><code class="language-shell">$ go build -o main &amp;&amp; ./main
Received: dyte
$ wc -c main
6084899 main</code></pre><p>The <code>go build</code> command allows us to tweak the flags passed to various components like the assembler ( <code>go tool asm</code>, <code>-asmflags</code>), the linker ( <code>go tool link</code>, <code>-ldflags</code>) and the compiler itself ( <code>go tool compile</code>, <code>-gcflags</code>). But only the linker flags are relevant to us for reducing the binary size, and this is quite widely known. In <code>ldflags</code>, <code>-s</code> disables the symbol table and <code>-w</code> omits debug information, while the <code>-trimpath</code> flag converts absolute file paths to relative ones, further reducing the size to ~3.9 MiB:</p><pre><code class="language-shell">$ go build -trimpath -ldflags &apos;-s -w&apos; -o main &amp;&amp; wc -c main
4128768 main</code></pre><h2 id="reinventing-the-wheel"><strong>Reinventing the wheel</strong></h2><p>Now, we&apos;ll start moving into the more esoteric side of things while still sticking with our trusty Go compiler. For starters, let&apos;s abandon the <code>net/websocket</code> package and talk over the TCP socket directly, crafting the HTTP and websocket payload by hand.</p><p>Refer to <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers">this</a> MDN document about writing websocket servers, as we won&apos;t be covering the payload in-depth here, though it is extensively commented on in the code below:</p><pre><code class="language-Go">package main

import (
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;net&quot;
)

func main() {
	httpInitMsg := []byte(&quot;GET / HTTP/1.1\r\nHost:dyte.io\r\nUpgrade:websocket\r\nConnection:Upgrade\r\nSec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version:13\r\nConnection:Upgrade\r\n\r\n&quot;)
	wsPayload := []byte{
		// FIN Bit (Final fragment), OpCode (1 for text payload)
		0b10000001,
		// Mask Bit (Required), followed by 7 bits for length (0b0000101 == 5)
		0b10000101,
		// We don&apos;t set the extended payload bits as our payload is only 5 bytes
		// Mask (can be any arbritary 32 bit integer)
		0b00000001,
		0b00000010,
		0b00000011,
		0b00000100,
		// Payload, the string &quot;hello&quot; with each character XOR&apos;d with the
		// corresponding mask bits
		0b01101001, // &apos;h&apos; ^ 0b00000001
		0b01100111, // &apos;e&apos; ^ 0b00000010
		0b01101111, // &apos;l&apos; ^ 0b00000011
		0b01101000, // &apos;l&apos; ^ 0b00000100
		0b01101110, // &apos;o&apos; ^ 0b00000001
	}

	// Establish a TCP connection to the server
	conn, err := net.Dial(&quot;tcp&quot;, &quot;localhost:8080&quot;)

	if err != nil {
		log.Fatal(err)
	}

	defer conn.Close()

	// Send the initial HTTP message to start talking over the WebSocket protocol
	_, err = conn.Write(httpInitMsg)

	if err != nil {
		log.Fatal(err)
	}

	response := make([]byte, 512)

	// Receive the initial HTTP response
	received, err := conn.Read(response)

	if err != nil {
		log.Fatal(err)
	}

	// Write the websocket frame
	_, err = conn.Write(wsPayload)

	if err != nil {
		log.Fatal(err)
	}

	// Read the reply into the existing buffer
	_, err = conn.Read(response[received:])

	fmt.Println(string(response))
}</code></pre><p>We&apos;ve made quite some progress, down to ~1.7 MiB!</p><pre><code class="language-Shell">$ go build -trimpath -ldflags &apos;-s -w&apos; -o main &amp;&amp; ./main
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

dyte
$ wc -c main
1814528 main</code></pre><p>Now, we&apos;ll use <a href="https://upx.github.io/" rel="noopener noreferrer">UPX</a>, an executable packer that compresses the binary and strips unneeded ELF sections. Do note that this impacts cold start times a bit due to the decompression overhead. This takes us down to ~710 KiB!</p><pre><code class="language-Shell">$ upx -9 main # Max compression level
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2024
UPX 4.2.2       Markus Oberhumer, Laszlo Molnar &amp; John Reiser    Jan 3rd 2024

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
   1814528 -&gt;    727684   40.10%   linux/amd64   main

Packed 1 file.</code></pre><h2 id="one-step-closer-to-insanity"><strong>One step closer to insanity</strong></h2><p>Till now, we&apos;ve just switched to the standard library for talking to the server. We can go one step further and use raw syscalls to handle all the socket interactions, becoming our own standard library in a sense :p</p><p>Note that syscalls are a lower level of abstraction than libc, as the libc functions, such as <code>recv</code> internally wrap the corresponding system calls. This might not make much sense if you&apos;ve never done socket programming in C, but the comments should give you enough of an idea of what&apos;s going on.</p><p>Essentially, a socket is a file descriptor that we create via the <code>socket()</code> syscall (which is identified by <code>SYS_SOCKET</code> here, referring to syscall no. <code>41</code>), and we further use this in subsequent syscalls to connect to the server and exchange data. The <a href="https://github.com/torvalds/linux/blob/9d1694dc91ce7b80bc96d6d8eaf1a1eca668d847/include/uapi/linux/in.h#L256"><code>sockaddr_in</code></a> structure is used to describe the address &amp; port we want to connect to, which we encode by hand here:</p><pre><code class="language-Go">func main() {
	httpInitMsg := []byte(...)
	wsPayload := []byte{...}
	// Connects to an IPv4 server at 127.0.0.1 on port 8080
	sockaddr := []byte{
		// family - AF_INET (0x2), padded to 16 bits
		0b00000010,
		0b00000000,
		// port - 8080, padded to 16 bits
		0b00011111,
		0b10010000,
		// addr - 127.0.0.1, 32 bits
		// 127 &lt;&lt; 0 | 0 &lt;&lt; 8 | 0 &lt;&lt; 16 | 1 &lt;&lt; 24
		0b01111111,
		0b00000000,
		0b00000000,
		0b00000001,
		// 64 bits of padding
		0b00000000, 0b00000000, 0b00000000, 0b00000000,
		0b00000000, 0b00000000, 0b00000000, 0b00000000,
	}
	// The response buffer for receiving server responses
	var response [135]byte

	// Create a IPv4 (AF_INET), TCP (SOCK_STREAM) socket FD
	// __NR_socket, AF_INET, SOCK_STREAM
	var sock, _, _ = syscall.Syscall(syscall.SYS_SOCKET, 0x2, 0x1, 0)

	// Connect to the server using the `sockaddr_in` structure
	// __NR_connect, fd, sockaddr_in, len(sockaddr_in)
	syscall.Syscall6(syscall.SYS_CONNECT, sock, uintptr(unsafe.Pointer(&amp;sockaddr[0])), uintptr(len(sockaddr)), 0, 0, 0)

	// Send the HTTP message over the socket
	// __NR_sendto, fd, buf, len(buf), flags, addr, addr_len
	syscall.Syscall6(syscall.SYS_SENDTO, sock, uintptr(unsafe.Pointer(&amp;httpInitMsg[0])), uintptr(len(httpInitMsg)), 0, 0, 0)

	// Receive the response
	// __NR_recvfrom, fd, buf, len(buf), flags, addr, addr_len
	var n, _, _ = syscall.Syscall6(syscall.SYS_RECVFROM, sock, uintptr(unsafe.Pointer(&amp;response[0])), uintptr(len(response)), 0, 0, 0)

	// Send the WebSocket frame
	// __NR_sendto
	syscall.Syscall6(syscall.SYS_SENDTO, sock, uintptr(unsafe.Pointer(&amp;wsPayload[0])), uintptr(len(wsPayload)), 0, 0, 0)

	// Receive the response
	// __NR_recvfrom
	syscall.Syscall6(syscall.SYS_RECVFROM, sock, uintptr(unsafe.Pointer(&amp;response[n])), uintptr(len(response))-n, 0, 0, 0)

	// Close the socket FD
	// __NR_close
	syscall.Syscall(syscall.SYS_CLOSE, sock, 0, 0)

	// Write the response string to standard output
	// __NR_write, STDOUT_FILENO
	syscall.Syscall(syscall.SYS_WRITE, 1, uintptr(unsafe.Pointer(&amp;response[0])), uintptr(len(response)))
}</code></pre><p>Now, the stock binary is ~828 KiB, and with UPX, it goes down to a not-so measly ~352 KiB:</p><pre><code class="language-Shell">$ go build -trimpath -ldflags &apos;-s -w&apos; -o main &amp;&amp; upx -9 main
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2024
UPX 4.2.2       Markus Oberhumer, Laszlo Molnar &amp; John Reiser    Jan 3rd 2024

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
    847872 -&gt;    360692   42.54%   linux/amd64   main

Packed 1 file.</code></pre><h2 id="swapping-the-go-compiler"><strong>Swapping the Go compiler</strong></h2><p>Unfortunately, that&apos;s a dead-end for how far the vanilla Go compiler can take us. We can now start experimenting with <a href="https://tinygo.org/">TinyGo</a>, an alternative LLVM-based Go compiler that produces significantly smaller binaries. Spoiler alert: Our binaries will fall below the minimum size accepted by UPX for compression!</p><p>One nifty feature TinyGo provides is the <code>-size</code> flag, which shows the various packages that make up our final binary. This is what it shows in the 2nd example that uses the standard library&apos;s <code>net</code> package:</p><pre><code class="language-Shell">$ tinygo build -o main -size full
   code  rodata    data     bss |   flash     ram | package
------------------------------- | --------------- | -------
      0      45       4      18 |      49      22 | (padding)
     45   20330      18      78 |   20393      96 | (unknown)
   1647    3627       0      72 |    5274      72 | /usr/lib/go/src/syscall
   3870      58      12     536 |    3940     548 | C musl
    401       0       0       0 |     401       0 | Go interface assert
    477       0       0       0 |     477       0 | Go interface method
      0    5816       0       0 |    5816       0 | Go types
     50       0       0       0 |      50       0 | errors
   7780     161      40       0 |    7981      40 | fmt
     26       0       0       0 |      26       0 | internal/bytealg
   1690      21       0       0 |    1711       0 | internal/fmtsort
    443      51       0      48 |     494      48 | internal/godebug
    157     369    1280       0 |    1806    1280 | internal/godebugs
     31      12      48      88 |      91     136 | internal/intern
    155       2       0       0 |     157       0 | internal/itoa
      0      57      48       0 |     105      48 | internal/oserror
    486      24       0      16 |     510      16 | internal/task
    336      22       0       0 |     358       0 | io/fs
   2767       3      40      64 |    2810     104 | log
    127       0       0       0 |     127       0 | main
     27       0       0       0 |      27       0 | math
    122       0       0       0 |     122       0 | math/bits
      0      25      16     160 |      41     176 | net
    298      16      56      24 |     370      80 | os
   6272     715      96       0 |    7083      96 | reflect
   8993     258      12      95 |    9263     107 | runtime
    822       0       0       0 |     822       0 | sort
   7280   16705    1338       0 |   25323    1338 | strconv
   1822     200       0       0 |    2022       0 | sync
    141      75       0       1 |     216       1 | sync/atomic
    193    1455       0       8 |    1648       8 | syscall
  20700    1029     184     128 |   21913     312 | time
   1132     288       0       0 |    1420       0 | unicode/utf8
------------------------------- | --------------- | -------
  68290   51364    3192    1336 |  122846    4528 | total</code></pre><p>Meanwhile, for the syscalls-only example:</p><pre><code class="language-Shell">$ tinygo build -o main -size full
   code  rodata    data     bss |   flash     ram | package
------------------------------- | --------------- | -------
      0       1       4      21 |       5      25 | (padding)
     25    2494       8      31 |    2527      39 | (unknown)
     92       0       0      40 |      92      40 | /usr/lib/go/src/syscall
   2894      27       4     536 |    2925     540 | C musl
      0     208       0       0 |     208       0 | Go types
    365      24       0      16 |     389      16 | internal/task
    268     162       0       0 |     430       0 | main
   3020     135       8      91 |    3163      99 | runtime
     80      75       0       1 |     155       1 | sync/atomic
------------------------------- | --------------- | -------
   6744    3126      24     736 |    9894     760 | total</code></pre><p>This makes sense as we skip over a ton of abstractions by using raw syscalls, but we can reduce this even further with the control that TinyGo gives us! We can disable goroutines &amp; channels, swap out the GC, pass arbritary linker flags, etc. as can be seen in the <a href="https://tinygo.org/docs/reference/usage/important-options/">documentation</a>.</p><p>First off, let&apos;s get a baseline for how much TinyGo can help us:</p><pre><code class="language-Plain">$ tinygo build -o main -no-debug &amp;&amp; wc -c main
18160 main</code></pre><p>17.7 KiB, that&apos;s already 1/20th the size of our previous attempt! Let&apos;s go ahead and disable goroutines ( <code>with -scheduler none</code>), switch to a <a href="https://github.com/tinygo-org/tinygo/blob/release/src/runtime/gc_leaking.go" rel="noopener noreferrer">smaller GC implementation</a> that just leaks memory ( <code>-gc leaking</code>), and just execute a trap instruction instead of printing the panic message in-case of panics ( <code>-panic trap</code>) - 12.75 KiB:</p><pre><code class="language-Shell">$ tinygo build -o main -no-debug -scheduler none -gc leaking -panic trap &amp;&amp; wc -c main
13056 main</code></pre><h2 id="ripping-out-the-gc"><strong>Ripping out the GC</strong></h2><p>The leaking GC, while quite small, still includes code to request memory via syscalls, so we can just provide our own allocator that gives out addresses from a fixed-size buffer on the stack, which is initialized at program startup. We can use a small buffer for this purpose as only a few allocations are made in our program, such as initializing the variables we declared (due to Go&apos;s escape analysis, as we take pointers to these variables) and the <a href="https://github.com/tinygo-org/tinygo/blob/731532cd2b6353b60b443343b51296ec0fafae09/src/runtime/runtime_unix.go#L138" rel="noopener noreferrer">runtime startup code</a>:</p><pre><code class="language-Diff">--- a/main.go
+++ b/main.go
@@ -5,6 +5,26 @@ import (
        &quot;unsafe&quot;
 )

+var buffer [1024]byte
+var used uintptr = 0
++// We disable the go GC entirely and provide this stub for handling
+// allocations, giving out addresses from a static buffer on the stack
+// This saves many bytes over using the &quot;leaking&quot; GC, it is more or less
+// used exclusively by the runtime&apos;s startup code for tasks like setting up
+// the processe&apos;s environment variables
+// If it crashes, run it with a clean environment (env -i ./main)
++//go:linkname alloc runtime.alloc
+func alloc(size uintptr, layoutPtr unsafe.Pointer) unsafe.Pointer {
+       var ptr = unsafe.Pointer(&amp;buffer[used])
++       // Align for x64
+       used += ((size + 15) &amp;^ 15)
++       return ptr
+}
+
 func main() {</code></pre><p>Now, building with <code>-gc none</code> - 12.42 KiB:</p><pre><code class="language-Plain">$ tinygo build -o main -no-debug -scheduler none -gc none -panic trap &amp;&amp; wc -c main
12720 main</code></pre><h2 id="linker-flags"><strong>Linker flags</strong></h2><p>As mentioned before, TinyGo allows us to pass arbitrary flags to the linker at compile time. This can be done via spec files, which tell TinyGo some information about the target architecture; some examples can be seen here. The format is not documented as such in the documentation, but all the possible keys with their defaults can be found in <a href="https://github.com/tinygo-org/tinygo/blob/release/compileopts/target.go">target.go</a>, which we use as a reference for creating our own.</p><p>This is what our <code>spec.json</code> looks like all the values are at their defaults except for <code>ldflags</code>, which we will now go through:</p><pre><code class="language-Shell">{
  &quot;llvm-target&quot;: &quot;x86_64-unknown-linux-musl&quot;,
  &quot;cpu&quot;: &quot;x86-64&quot;,
  &quot;goos&quot;: &quot;linux&quot;,
  &quot;goarch&quot;: &quot;amd64&quot;,
  &quot;build-tags&quot;: [
    &quot;amd64&quot;,
    &quot;linux&quot;
  ],
  &quot;linker&quot;: &quot;ld.lld&quot;,
  &quot;rtlib&quot;: &quot;compiler-rt&quot;,
  &quot;libc&quot;: &quot;musl&quot;,
  &quot;defaultstacksize&quot;: 65536,
  &quot;ldflags&quot;: [
    &quot;--gc-sections&quot;,
    &quot;--discard-all&quot;,
    &quot;--strip-all&quot;,
    &quot;--no-rosegment&quot;,
    &quot;-znorelro&quot;,
    &quot;-znognustack&quot;
  ]
}</code></pre><p>Let&apos;s refer to the <code>lld</code> linker&apos;s <a href="https://man.archlinux.org/man/extra/lld/ld.lld.1.en">man-page</a> for these flags:</p><ul><li><code>-gc-sections</code>: Enables garbage collection of unused sections, explained more in detail in <a href="https://maskray.me/blog/2021-02-28-linker-garbage-collection">this</a> blog</li><li><code>-discard-all</code>: Deletes all local symbols</li><li><code>-strip-all</code>: Removes the symbol table and debug information</li><li><code>-no-rosegment</code>: Allows the linker to combine read-only and read-execute segments of the binary</li><li><code>znorelro</code>: Disables emitting the <code>PT_GNU_RELRO</code> segment, used to specify certain regions of the binary that should be marked as read-only after performing relocations. Good security measure, but we just care about trimming bytes in this post :p</li><li><code>znognustack</code>: Disables emitting the <code>PT_GNU_STACK</code> segment, used to determine whether the stack should be executable or not, again, security</li></ul><p>On top of this, we can further strip more sections from the compiled binary with the <code>strip</code> command <code>strip --strip-section-headers -R .comment -R .note -R .eh_frame main</code>. This removes the section headers (used by tools like <code>objdump</code> to locate sections), along with the <code>.comment</code> section (which contains toolchain-related info) and the <code>.eh_frame</code> section (used for stack unwinding, which we don&apos;t need here)</p><p>Finally, our binary is down to 6.44 KiB:</p><pre><code class="language-Plain">$ tinygo build -o main -no-debug -scheduler none -gc none -panic trap -target spec.json
$ strip --strip-section-headers -R .comment -R .note -R .eh_frame main
$ wc -c main
6600 main</code></pre><h2 id="ripping-out-the-standard-library"><strong>Ripping out the standard library</strong></h2><p>6.44 KiB is still too big for a program that basically just makes a few syscalls (technically, every program fits this definition, but you get the intent), and this part gets its own section as it is basically cheating in the context of this challenge :p</p><p>So, we&apos;re still pulling in quite a bit of code from the standard library, mainly around the startup code that sets up the program&apos;s execution environment before our <code>main</code> function is actually called, look at <a href="https://github.com/tinygo-org/tinygo/blob/731532cd2b6353b60b443343b51296ec0fafae09/src/runtime/runtime_unix.go#L70">runtime_unix.go</a> and <a href="https://github.com/tinygo-org/tinygo/blob/731532cd2b6353b60b443343b51296ec0fafae09/src/runtime/scheduler_none.go#L22">scheduler_none.go</a> for more clarity.</p><p>All we have to do is export our <code>main</code> function with a different name (eg. <code>smol_main</code>), and tell the linker to treat that as the actual entry point, which would prevent the standard library startup code from making its way into our binary.</p><ul><li>In <code>spec.json</code>, we pass the <code>entry</code> flag to the linker, and drop libc completely, as it is only needed by TinyGo&apos;s standard library for certain functions</li></ul><pre><code class="language-diff">--- a/spec.json
+++ b/spec.json
@@ -9,7 +9,6 @@
   ],
   &quot;linker&quot;: &quot;ld.lld&quot;,
   &quot;rtlib&quot;: &quot;compiler-rt&quot;,
-  &quot;libc&quot;: &quot;musl&quot;,
   &quot;defaultstacksize&quot;: 65536,
   &quot;ldflags&quot;: [
     &quot;--gc-sections&quot;,
@@ -17,6 +16,7 @@
     &quot;--strip-all&quot;,
     &quot;--no-rosegment&quot;,
     &quot;-znorelro&quot;,
-    &quot;-znognustack&quot;
+    &quot;-znognustack&quot;,
+    &quot;-entry=smol_main&quot;
   ]
 }</code></pre><ul><li>In <code>main.go</code>, we make our local variables global, allowing them to be placed on the stack rather than the heap (remember the escape analysis mentioned earlier?), which further allows us to get rid of our dummy GC implementation. We annotate the <code>main</code> function with directives to export it as <code>smol_main</code>, and disable <a href="https://github.com/tinygo-org/tinygo/blob/731532cd2b6353b60b443343b51296ec0fafae09/compiler/asserts.go#L19" rel="noopener noreferrer">bounds checking</a>, as the <a href="https://github.com/tinygo-org/tinygo/blob/731532cd2b6353b60b443343b51296ec0fafae09/src/runtime/panic.go#L143" rel="noopener noreferrer">panic handler</a> for it indirectly pulls in <a href="https://github.com/tinygo-org/tinygo/blob/731532cd2b6353b60b443343b51296ec0fafae09/src/runtime/runtime_unix.go#L155" rel="noopener noreferrer">some libc symbols</a>.</li></ul><pre><code class="language-Diff">--- a/main.go
+++ b/main.go
@@ -5,29 +5,9 @@ import (
        &quot;unsafe&quot;
 )

-var buffer [1024]byte
-var used uintptr = 0
--// We disable the go GC entirely and provide this stub for handling
-// allocations, giving out addresses from a static buffer on the stack
-// This saves many bytes over using the &quot;leaking&quot; GC, it is more or less
-// used exclusively by the runtime&apos;s startup code for tasks like setting up
-// the processe&apos;s environment variables
-// If it crashes, run it with a clean environment (env -i ./main)
--//go:linkname alloc runtime.alloc
-func alloc(size uintptr, layoutPtr unsafe.Pointer) unsafe.Pointer {
-       var ptr = unsafe.Pointer(&amp;buffer[used])
--       // Align for x64
-       used += ((size + 15) &amp;^ 15)
--       return ptr
-}
--func main() {
-       httpInitMsg := []byte(&quot;GET / HTTP/1.1\r\nHost:dyte.io\r\nUpgrade:websocket\r\nConnection:Upgrade\r\nSec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version:13\r\nConnection:Upgrade\r\n\r\n&quot;)
-       wsPayload := []byte{
+var (
+       httpInitMsg = []byte(&quot;GET / HTTP/1.1\r\nHost:dyte.io\r\nUpgrade:websocket\r\nConnection:Upgrade\r\nSec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version:13\r\nConnection:Upgrade\r\n\r\n&quot;)
+       wsPayload   = []byte{
                // FIN Bit (Final fragment), OpCode (1 for text payload)
                0b10000001,
                // Mask Bit (Required), followed by 7 bits for length (0b0000101 == 5)
@@ -47,7 +27,7 @@ func main() {
                0b01101110, // &apos;o&apos; ^ 0b00000001
        }
        // Connects to an IPv4 server at 127.0.0.1 on port 8080
-       sockaddr := []byte{
+       sockaddr = []byte{
                // family - AF_INET (0x2), padded to 16 bits
                0b00000010,
                0b00000000,
@@ -65,8 +45,12 @@ func main() {
                0b00000000, 0b00000000, 0b00000000, 0b00000000,
        }
        // The response buffer for receiving server responses
-       var response [135]byte
+       response [135]byte
+)

+//export smol_main
+//go:nobounds
+func main() {
        // Create a IPv4 (AF_INET), TCP (SOCK_STREAM) socket FD
        // __NR_socket, AF_INET, SOCK_STREAM
        var sock, _, _ = syscall.Syscall(syscall.SYS_SOCKET, 0x2, 0x1, 0)
@@ -98,4 +82,11 @@ func main() {
        // Write the response string to standard output
        // __NR_write, STDOUT_FILENO
        syscall.Syscall(syscall.SYS_WRITE, 1, uintptr(unsafe.Pointer(&amp;response[0])), uintptr(len(response)))
++       // Cleanly exit the program with status code 0
+       // The libc does this for us in the usual flow, that goes like so:
+       //   __libc_start_main (libc) -&gt; main (runtime_unix.go) -&gt; main (main.go)
+       // But here, the entrypoint is in main.go itself
+       // __NR_exit, EXIT_SUCCESS
+       syscall.Syscall(syscall.SYS_EXIT, 0, 0, 0)
 }</code></pre><p>Now, we&apos;re down to just 810 bytes:</p><pre><code class="language-Plain Text">$ tinygo build -o main -scheduler none -gc none -panic trap -target spec.json \
    &amp;&amp; strip --strip-section-headers -R .comment -R .note -R .eh_frame main \
    &amp;&amp; wc -c main
810 main</code></pre><h2 id="compiling-for-32-bits"><strong>Compiling for 32-bits</strong></h2><p>One last trick up our sleeves is to compile the binary for 32-bits (<code>i386</code>) rather than <code>amd64</code>, as 32-bit binaries are significantly smaller in comparison. However, we&apos;ll still be able to run this binary on most 64-bit Linux systems (given that <code>CONFIG_IA32_EMULATION</code> is enabled in the kernel)</p><p>To do this, all we need to do is flip the target-related switches in <code>spec.json</code>. Note that we don&apos;t need to update syscalls to reflect <code>i386</code> as we&apos;re using constants like <code>syscall.SYS_SOCKET</code> rather than hardcoding the syscall numbers:</p><p><code>spec.json</code></p><pre><code class="language-Diff">--- a/spec.json
+++ b/spec.json
@@ -1,10 +1,10 @@
 {
-  &quot;llvm-target&quot;: &quot;x86_64-unknown-linux-musl&quot;,
-  &quot;cpu&quot;: &quot;x86-64&quot;,
+  &quot;llvm-target&quot;: &quot;i386-unknown-linux-musl&quot;,
+  &quot;cpu&quot;: &quot;i386&quot;,
   &quot;goos&quot;: &quot;linux&quot;,
-  &quot;goarch&quot;: &quot;amd64&quot;,
+  &quot;goarch&quot;: &quot;386&quot;,
   &quot;build-tags&quot;: [
-    &quot;amd64&quot;,
+    &quot;386&quot;,
     &quot;linux&quot;
   ],
   &quot;linker&quot;: &quot;ld.lld&quot;,</code></pre><p>Now, our binary is just 538 bytes, and it still works!</p><pre><code class="language-Shell">$ tinygo build -o main -scheduler none -gc none -panic trap -target spec.json \
    &amp;&amp; strip --strip-section-headers -R .comment -R .note -R .eh_frame main \
    &amp;&amp; wc -c main
538 main
$ file main
main: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, no section header
$ ./main
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

dyte</code></pre><h2 id="conclusion"><strong>Conclusion</strong></h2>
<!--kg-card-begin: html-->
<table id="942bd83b-72a9-4b89-a8e2-2e4dd4592907" class="simple-table"><tbody><tr id="5bf0cf9e-752c-4219-8d05-a5556219ed01"><td id="iWl{" class>Attempt</td><td id="Z{ut" class>Size (in bytes)</td><td id="~aIO" class>Compiler</td></tr><tr id="904f4830-175d-4fde-9e36-06ad158d53ad"><td id="iWl{" class><a href="https://github.com/git-bruh/wscodegolf/tree/main/client/net_websocket">Using&#xA0;</a><a href="https://github.com/git-bruh/wscodegolf/tree/main/client/net_websocket"><code>x/net/websocket</code></a></td><td id="Z{ut" class>4128768 (stripped)</td><td id="~aIO" class>Go</td></tr><tr id="26c7fb85-5336-4866-ae1f-04a44fde5433"><td id="iWl{" class><a href="https://github.com/git-bruh/wscodegolf/tree/main/client/stdlib_only">Pure standard library</a></td><td id="Z{ut" class>727684 (1814528 without UPX)</td><td id="~aIO" class>Go</td></tr><tr id="50e73202-cfdd-4fe4-8568-d6f584413e2e"><td id="iWl{" class><a href="https://github.com/git-bruh/wscodegolf/tree/main/client/syscalls_only">Syscalls only</a></td><td id="Z{ut" class>360692 (847872 without UPX)</td><td id="~aIO" class>Go</td></tr><tr id="fd63a173-e0cb-4740-b3bb-1013791e82e3"><td id="iWl{" class><a href="https://github.com/git-bruh/wscodegolf/tree/main/client/syscalls_only">Syscalls only</a></td><td id="Z{ut" class>13056</td><td id="~aIO" class>TinyGo</td></tr><tr id="165bc2e5-b6e3-44a4-bd11-fe6288cd4f87"><td id="iWl{" class><a href="https://github.com/git-bruh/wscodegolf/tree/main/client/syscalls_no_gc">Syscalls with dummy GC</a></td><td id="Z{ut" class>12720</td><td id="~aIO" class>TinyGo</td></tr><tr id="d447cec1-3653-4030-b0d5-6884ae6a5abf"><td id="iWl{" class><a href="https://github.com/git-bruh/wscodegolf/tree/main/client/syscalls_no_gc_ldflags">Syscalls with dummy GC, custom ldflags</a></td><td id="Z{ut" class>6600</td><td id="~aIO" class>TinyGo</td></tr><tr id="3890fec7-b9df-4439-a88f-61e59678c4c6"><td id="iWl{" class><a href="https://github.com/git-bruh/wscodegolf/tree/main/client/syscalls_no_gc_custom_entry">Syscalls with no GC, custom ldflags, custom entrypoint</a></td><td id="Z{ut" class>810</td><td id="~aIO" class>TinyGo</td></tr><tr id="283c566c-1ef0-4fa4-a352-d62786f243bc"><td id="iWl{" class><a href="https://github.com/git-bruh/wscodegolf/tree/main/client/syscalls_no_gc_custom_entry_32bit">Syscalls with no GC, custom ldflags, custom entrypoint, 32-bit</a></td><td id="Z{ut" class>538</td><td id="~aIO" class>TinyGo</td></tr></tbody></table>
<!--kg-card-end: html-->
<p>As we did not cover each topic in a lot of depth in this post, here are some handy resources:</p><ul><li><a href="https://tinygo.org/docs">TinyGo docs</a></li><li><a href="https://man.archlinux.org/man/extra/lld/ld.lld.1.en">ld.lld man-page</a></li><li><a href="https://jameshfisher.com/2018/02/19/how-to-syscall-in-c">Using raw syscalls in C</a></li><li>Misc. linker-related blogs</li><li><a href="https://maskray.me/blog/2021-02-28-linker-garbage-collection">Linker garbage collection</a></li><li><a href="https://maskray.me/blog/2020-11-15-explain-gnu-linker-options">Explain GNU style linker options</a></li></ul><p>Checkout the full solution here <a href="https://github.com/git-bruh/wscodegolf/">https://github.com/git-bruh/wscodegolf/</a> and <strong>if you want to look at some of our other challenges, checkout </strong><a href="https://hacktofinale.dyte.io/"><strong>https://hacktofinale.dyte.io/</strong></a><strong> </strong></p>]]></content:encoded></item><item><title><![CDATA[Open Sourcing Dyte’s Device Emulator]]></title><description><![CDATA[Open sourcing Dyte's device emulator that helps you ship well tested products by writing media related integration tests with ease.]]></description><link>https://dyte.io/blog/open-sourcing-dytes-device-emulator/</link><guid isPermaLink="false">64e7266cd0c964000195204b</guid><category><![CDATA[Announcement]]></category><dc:creator><![CDATA[Ravindra Singh Rathor]]></dc:creator><pubDate>Tue, 26 Nov 2024 10:45:00 GMT</pubDate><media:content url="https://dyte.io/blog/content/images/2023/08/Open-Sourcing-Dyte-s-Device-Emulator.png" medium="image"/><content:encoded><![CDATA[<img src="https://dyte.io/blog/content/images/2023/08/Open-Sourcing-Dyte-s-Device-Emulator.png" alt="Open Sourcing Dyte&#x2019;s Device Emulator"><p>For a product, integration tests are one of the crucial parts that improve quality &amp; stability. This is true for WebRTC applications as well. However, challenges arise when we try to create integration tests around user media in a browser.</p><p>For an end user, sharing a camera &amp; mic is straightforward. For this, browsers expose APIs such as <code>enumerateDevices</code> &amp; <code>getUserMedia</code> on the <code>MediaDevices</code> interface, on which user interfaces can be built easily.</p><pre><code class="language-jsx">navigator.mediaDevices.getUserMedia({audio: true, video: true})
</code></pre><p>Try pasting the above line in Developer Console in Chrome &amp; you will see that it asks for permission from you to share the camera &amp; mic. Once you permit the browser to access the camera &amp; mic, you will see your camera LED turning on, indicating that your camera is in use.</p><h4 id="media-device-unavailability-in-test-environments">Media Device Unavailability in Test Environments</h4><p>In a virtualised test environment, access to actual media devices is often unavailable. The <code>getUserMedia</code> interface is designed to interact with real hardware connected to a device. Therefore, when these environments try to run tests that invoke such interfaces, they fail to replicate real-world scenarios.</p><p>In such a scenario one possible solution a developer can resort to is by using fake media streams interfaces provided by the browser</p><p>For example, you could start Chromium with the following command line arguments to add microphone and webcam devices which would use the provided static video &amp; audio files as media source</p><pre><code>--use-fake-ui-for-media-stream
--use-fake-device-for-media-stream
--use-file-for-fake-video-capture=ABSOLUTE_PATH_TO_VIDEO_FILE
--use-file-for-fake-audio-capture=ABSOLUTE_PATH_TO_AUDIO_FILE
</code></pre><p>This is better than having no media, but isn&apos;t really useful for testing</p><h4 id="limited-testing-capabilities">Limited Testing Capabilities</h4><p>Even in the above situation or situations where physical hardware devices are available for testing, automating the test scenarios can be an uphill battle. Let&apos;s consider some instances where these challenges come into play:</p><ul><li><strong>Device Plug-in Scenario</strong>: Imagine a user plugs in a new microphone while a WebRTC application is running. An ideal application should seamlessly switch the audio input to the new device without requiring a manual switch or causing interruptions. Automating this test is challenging as it necessitates actual hardware manipulations, which are difficult to enact in a software-only test setup.</li><li><strong>Hardware Failure Handling</strong>: Another crucial test case is how the application responds to hardware failures. For instance, what happens if a camera is unable to provide media in required constraints</li></ul><h3 id="introducing-device-emulator">Introducing Device Emulator</h3><p>To solve all these pain points we are open-sourcing our device emulator - <a href="https://github.com/dyte-io/device-emulator">https://github.com/dyte-io/device-emulator</a> that can be used to mimic devices across browsers. By simulating various hardware states and events, the toolkit can provide a nuanced and comprehensive evaluation of a WebRTC application&apos;s robustness, reliability, and user experience.</p><p>Dyte&apos;s device emulator currently supports:</p><ul><li>Adding and removing virtual media devices</li><li>Simulating a failure by silencing a track</li><li>Simulating a faulty device (getUserMedia failure)</li></ul><p>How easy is it to integrate the Dyte&apos;s device emulator, you might ask? In Playwright, the integration test solution that <a href="https://dyte.io">Dyte</a> uses, all you have to do is add the below 1-liner ( or 3?) code snippet.</p><pre><code class="language-jsx">await page.addScriptTag({
      url: &apos;&lt;https://cdn.jsdelivr.net/npm/@dytesdk/device-emulator/dist/index.iife.js&gt;&apos;,
});
</code></pre><p><strong>Not using Playwright?</strong></p><p>No worries. Just figure out a way to add a script tag to your choice of tool.</p><pre><code class="language-jsx">&lt;script src=&quot;&lt;https://cdn.jsdelivr.net/npm/@dytesdk/device-emulator/dist/index.iife.js&gt;&quot;&gt;&lt;/script&gt;
</code></pre><h2 id="how-does-this-work">How does this work?</h2><p>The script tag loads the device emulator library into your page which patches the <code>navigator.MediaDevices</code> interface with the toolkit&apos;s modified version providing the same API  signatures. Internally it uses <code>Web Audio API</code> for generating virtual AudioTracks and <code>Canvas API</code> for generating VideoTracks and simulates different media behaviour and states</p><h3 id="adding-a-virtual-device">Adding a virtual device</h3><p>Once the device emulator is loaded, Use the below code snippet. <code>addEmulatedDevice</code> is the extra method exposed by Dyte&apos;s device emulator to help you with the addition of devices.</p><pre><code class="language-jsx">window.addEventListener(&apos;dyte.deviceEmulatorLoaded&apos;, () =&gt; {
	navigator.mediaDevices.addEmulatedDevice(&apos;videoinput&apos;);
	navigator.mediaDevices.addEmulatedDevice(&apos;audioinput&apos;);
});</code></pre><h3 id="removing-a-virtual-device">Removing a virtual device</h3><p>It is a two-step process. Figure out the emulated device id using the code snippet below:</p><pre><code class="language-jsx">navigator.mediaDevices.enumerateDevices()
</code></pre><p>Filter the device that you want to use and retrieve the device Id. Once you have the device id, remove the device using the code snippet below.</p><pre><code class="language-jsx">navigator.mediaDevices.removeEmulatedDevice(&apos;PUT_EMULATED_DEVICE_ID_HERE&apos;);
</code></pre><p>That&apos;s it. Now, you can add as many devices as you want and play around with the addition/removal of devices.</p><h3 id="writing-a-test">Writing a Test</h3><p>Using the above example, writing automated tests because straightforward</p><pre><code class="language-js">// Simulate a new camera plugging in
await this.page.evaluate(() =&gt; {
            navigator.mediaDevices.addEmulatedDevice(&apos;videoinput&apos;);
        });

// Verify your application&apos;s expected behaviour
await expect(..)</code></pre><h2 id="quick-demo">Quick Demo</h2><p>Go to any web based video conferencing app like Google Meet or go to <a href="https://demo.dyte.io">https://demo.dyte.io</a> and create a meeting</p><p>Once inside the meeting, Open the developer console by right clicking on the webpage and then inspect.</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2023/08/device-emulator-image-1.png" class="kg-image" alt="Open Sourcing Dyte&#x2019;s Device Emulator" loading="lazy" width="1174" height="808" srcset="https://dyte.io/blog/content/images/size/w600/2023/08/device-emulator-image-1.png 600w, https://dyte.io/blog/content/images/size/w1000/2023/08/device-emulator-image-1.png 1000w, https://dyte.io/blog/content/images/2023/08/device-emulator-image-1.png 1174w" sizes="(min-width: 720px) 720px"></figure><p>Dev tools would open. Go to the Console tab and, paste the code below, and hit the enter/return key.</p><pre><code class="language-jsx">window.addEventListener(&apos;dyte.deviceEmulatorLoaded&apos;, () =&gt; {
     navigator.mediaDevices.addEmulatedDevice(&apos;videoinput&apos;);   
});

var script = document.createElement(&apos;script&apos;);
script.type = &apos;text/javascript&apos;;
script.src = &apos;&lt;https://cdn.jsdelivr.net/npm/@dytesdk/device-emulator/dist/index.iife.js&gt;&apos;;
document.head.appendChild(script);
</code></pre><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2023/08/Open-Sourcing-Dyte-s-Device-Emulator-image-2.png" class="kg-image" alt="Open Sourcing Dyte&#x2019;s Device Emulator" loading="lazy" width="2000" height="1188" srcset="https://dyte.io/blog/content/images/size/w600/2023/08/Open-Sourcing-Dyte-s-Device-Emulator-image-2.png 600w, https://dyte.io/blog/content/images/size/w1000/2023/08/Open-Sourcing-Dyte-s-Device-Emulator-image-2.png 1000w, https://dyte.io/blog/content/images/size/w1600/2023/08/Open-Sourcing-Dyte-s-Device-Emulator-image-2.png 1600w, https://dyte.io/blog/content/images/size/w2400/2023/08/Open-Sourcing-Dyte-s-Device-Emulator-image-2.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>It is that easy. Now, you can join a meeting with a fake video</p><h3 id="conclusion">Conclusion</h3><p>Checkout the complete guide and examples at  <a href="https://docs.dyte.io/community-packages/device-emulator">https://docs.dyte.io/community-packages/device-emulator</a> </p><p>Or try an full demo at  <a href="https://device-emulator.vercel.app/">https://device-emulator.vercel.app/</a></p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2023/08/Screenshot-2023-08-28-at-10.29.54-PM.png" class="kg-image" alt="Open Sourcing Dyte&#x2019;s Device Emulator" loading="lazy" width="2000" height="909" srcset="https://dyte.io/blog/content/images/size/w600/2023/08/Screenshot-2023-08-28-at-10.29.54-PM.png 600w, https://dyte.io/blog/content/images/size/w1000/2023/08/Screenshot-2023-08-28-at-10.29.54-PM.png 1000w, https://dyte.io/blog/content/images/size/w1600/2023/08/Screenshot-2023-08-28-at-10.29.54-PM.png 1600w, https://dyte.io/blog/content/images/2023/08/Screenshot-2023-08-28-at-10.29.54-PM.png 2000w" sizes="(min-width: 720px) 720px"></figure><p>Shipping a product with proper testing is paramount for any company, and what&apos;s better than writing integration tests to test end-user scenarios?</p><p>We know how tricky writing media-related integration tests could be! We hope this toolkit will ease some pain while writing media-related integration tests for you, as it did for us.</p><p>Feel free to raise feature requests, pull requests, or fork the repo (<a href="https://github.com/dyte-io/device-emulator">https://github.com/dyte-io/device-emulator</a>) and customize it to your liking.</p><p>I hope you found this post informative and engaging. If you have any thoughts or feedback, please get in touch with me on <a href="https://twitter.com/rsr_thedarklord">Twitter</a> or <a href="https://www.linkedin.com/in/softwareprovider/">LinkedIn</a>. Stay tuned for more related blog posts in the future!</p><p><em>If you haven&apos;t heard about Dyte yet, head over to </em><a href="https://dyte.io/"><em>dyte.io</em></a><em> to learn how we are revolutionizing communication through our SDKs and libraries and how you can </em><a href="https://accounts.dyte.in/auth/register"><em>get started</em></a><em> quickly on your 10,000 free minutes, which renew every month. If you have any questions, you can reach us at </em><a href="mailto:support@dyte.io"><em>support@dyte.io</em></a><em> or ask our </em><a href="https://community.dyte.io/"><em>developer community</em></a><em>.</em></p>]]></content:encoded></item><item><title><![CDATA[Building a Live Auction Platform With React and Dyte]]></title><description><![CDATA[Learn how to build a live auction platform using React and Dyte to actively engage in live auctions, interact with auctioneers, and place real-time bids.]]></description><link>https://dyte.io/blog/live-auction-platform/</link><guid isPermaLink="false">651ffda7d0c9640001952d04</guid><category><![CDATA[Demos]]></category><dc:creator><![CDATA[Ishita Kabra]]></dc:creator><pubDate>Wed, 20 Nov 2024 14:21:00 GMT</pubDate><media:content url="https://dyte.io/blog/content/images/2023/10/live-auction--header.png" medium="image"/><content:encoded><![CDATA[<h2 id></h2><img src="https://dyte.io/blog/content/images/2023/10/live-auction--header.png" alt="Building a Live Auction Platform With React and Dyte"><p>In today&#x2019;s digital landscape, the art of buying and selling has taken a remarkable leap forward with the advent of real-time online marketplaces and auctions. A live auction is an interactive bidding process in real-time, facilitated through digital platforms. Participants place live bids on goods or services within a defined timeframe, and the highest bid at the end of the auction wins the item or service being auctioned.<br><br>We want to help people create engaging live experiences with Dyte, and live auctions just happened to be on the market. By blending technology with real-time bidding, in this blog, we&#x2019;ll delve into the essentials and take you through the application flow of building and running your own live auction platform before the fall of the hammer.<br><br>Let&#x2019;s discover the products up for sale before the bidding starts!</p><h2 id="why-build-a-live-auction-platform">Why build a live auction platform?</h2><ul><li><strong>Convenient:</strong> Participate in live auctions from anywhere, at any time.</li><li><strong>Real-time bidding:</strong> Experience the thrill of bidding against other enthusiasts in real-time.</li><li><strong>Wide range of items:</strong> Discover diverse items, from collectibles to artwork and more.</li><li><strong>Seamless integration:</strong> The live auction app provides users with a smooth and uninterrupted auction experience with Dyte&apos;s reliable audio/video conferencing and customizable UI.</li></ul><h2 id="before-you-start">Before you start</h2><ul><li>Basic knowledge of <a href="https://react.dev/">React.js</a> is required to build this application.</li><li>Please ensure that <a href="https://nodejs.org/">Node.js</a> is installed on your machine. We will use it to run our application.</li><li>Lastly, you will need the API Key and organization ID from the <a href="https://dev.dyte.io/">developer portal</a> to authenticate yourself when you call Dyte&apos;s REST APIs.</li></ul><h2 id="building-the-live-bidding-platform">Building the live bidding platform</h2><h3 id="installation">Installation</h3><p>You need to install Dyte&apos;s React UI Kit and Core packages to get started. You can do so by using npm or Yarn.</p><pre><code class="language-jsx">npm install @dytesdk/react-ui-kit @dytesdk/react-web-core
</code></pre><h3 id="getting-started">Getting started</h3><p>We will first fetch the organization ID and API Key from the <a href="https://dev.dyte.io/">developer portal</a>. Then, we&apos;ll create an account and navigate to the API Keys page.</p><p><strong>Please ensure that you do not upload your API Key anywhere.</strong></p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2023/10/live-auction--asset-1.png" class="kg-image" alt="Building a Live Auction Platform With React and Dyte" loading="lazy" width="1433" height="821" srcset="https://dyte.io/blog/content/images/size/w600/2023/10/live-auction--asset-1.png 600w, https://dyte.io/blog/content/images/size/w1000/2023/10/live-auction--asset-1.png 1000w, https://dyte.io/blog/content/images/2023/10/live-auction--asset-1.png 1433w" sizes="(min-width: 720px) 720px"></figure><p>Next, we will create a meeting using the following <a href="https://www.notion.so/demo-docs-dyte-io-6f55d86117c84c1b8bcba43f97917da4?pvs=21">Rest API</a>. Here is a sample response.</p><pre><code class="language-jsx">{
  &quot;success&quot;: true,
  &quot;data&quot;: {
    &quot;id&quot;: &quot;497f6eca-6276-4993-bfeb-53cbbbbaxxxx&quot;,
    &quot;name&quot;: &quot;string&quot;,
    &quot;picture&quot;: &quot;&lt;http://example.com&gt;&quot;,
    &quot;custom_participant_id&quot;: &quot;string&quot;,
    &quot;preset_name&quot;: &quot;string&quot;,
    &quot;created_at&quot;: &quot;2019-08-24T14:15:22Z&quot;,
    &quot;updated_at&quot;: &quot;2019-08-24T14:15:22Z&quot;,
    &quot;token&quot;: &quot;string&quot;
  }
}
</code></pre><p>We use the ID from the payload to generate an auth token using the <a href="https://docs.dyte.io/api#/operations/add_participant">Add Participants API</a>. This auth token is used to initialize a Dyte client. Let&apos;s start setting up the project.</p><h3 id="build-your-custom-ui">Build your custom UI</h3><p>Create a file <code>src/App.tsx</code>. We will use the <code>DyteMeeting</code> hook to initialize a new Dyte meeting. The <code>DyteProvider</code> is used to pass the meeting object to all child components inside this application. We will also set up event listeners for joining and leaving the room.</p><pre><code class="language-tsx">import { useEffect, useState } from &apos;react&apos;;
import { DyteProvider, useDyteClient } from &apos;@dytesdk/react-web-core&apos;;
import { LoadingScreen } from &apos;./pages&apos;;
import { Meeting, SetupScreen } from &apos;./pages&apos;;

function App() {
  const [meeting, initMeeting] = useDyteClient();
  const [roomJoined, setRoomJoined] = useState&lt;boolean&gt;(false);

  useEffect(() =&gt; {
    const searchParams = new URL(window.location.href).searchParams;
    const authToken = searchParams.get(&apos;authToken&apos;);

    if (!authToken) {
      alert(
        &quot;An authToken wasn&apos;t passed, please pass an authToken in the URL query to join a meeting.&quot;
      );
      return;
    }

    initMeeting({
      authToken,
      defaults: {
        audio: false,
        video: false,
      },
    });
  }, []);

  useEffect(() =&gt; {
    if (!meeting) return;

    const roomJoinedListener = () =&gt; {
      setRoomJoined(true);
    };
    const roomLeftListener = () =&gt; {
      setRoomJoined(false);
    };
    meeting.self.on(&apos;roomJoined&apos;, roomJoinedListener);
    meeting.self.on(&apos;roomLeft&apos;, roomLeftListener);

    return () =&gt; {
      meeting.self.removeListener(&apos;roomJoined&apos;, roomJoinedListener);
      meeting.self.removeListener(&apos;roomLeft&apos;, roomLeftListener);
    }

  }, [meeting])

  return (
    &lt;DyteProvider value={meeting} fallback={&lt;LoadingScreen /&gt;}&gt;
      {
        !roomJoined ? &lt;SetupScreen /&gt; : &lt;Meeting /&gt;
      }
    &lt;/DyteProvider&gt;
  )
}
</code></pre><p>Now, we will build a setup screen; this is the first page that the user will see.</p><p>Create a file <code>src/pages/setupScreen/setupScreen.tsx</code>. We will update the user&apos;s display name and join the Dyte meeting from this page.</p><pre><code class="language-tsx">import { useEffect, useState } from &apos;react&apos;
import &apos;./setupScreen.css&apos;
import { useDyteMeeting } from &apos;@dytesdk/react-web-core&apos;;
import {
  DyteAudioVisualizer,
  DyteAvatar,
  DyteCameraToggle,
  DyteMicToggle,
  DyteNameTag,
  DyteParticipantTile,
} from &apos;@dytesdk/react-ui-kit&apos;;

const SetupScreen = () =&gt; {
  const { meeting } = useDyteMeeting();
  const [isHost, setIsHost] = useState&lt;boolean&gt;(false);
  const [name, setName] = useState&lt;string&gt;(&apos;&apos;);

  useEffect(() =&gt; {
    if (!meeting) return;
    const preset = meeting.self.presetName;
    const name = meeting.self.name;
    setName(name);

    if (preset.includes(&apos;host&apos;)) {
      setIsHost(true);
    }
  }, [meeting])

  const joinMeeting = () =&gt; {
    meeting?.self.setName(name);
    meeting.joinRoom();
  }

  return (
    &lt;div className=&apos;setup-screen&apos;&gt;
      &lt;div className=&quot;setup-media&quot;&gt;
        &lt;div className=&quot;video-container&quot;&gt;
          &lt;DyteParticipantTile meeting={meeting} participant={meeting.self}&gt;
            &lt;DyteAvatar size=&quot;md&quot; participant={meeting.self}/&gt;
            &lt;DyteNameTag meeting={meeting} participant={meeting.self}&gt;
              &lt;DyteAudioVisualizer size=&apos;sm&apos; slot=&quot;start&quot; participant={meeting.self} /&gt;
            &lt;/DyteNameTag&gt;
            &lt;div className=&apos;setup-media-controls&apos;&gt;
              &lt;DyteMicToggle size=&quot;sm&quot; meeting={meeting}/&gt;
              &amp;ensp;
              &lt;DyteCameraToggle size=&quot;sm&quot; meeting={meeting}/&gt;
            &lt;/div&gt;
          &lt;/DyteParticipantTile&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div className=&quot;setup-information&quot;&gt;
        &lt;div className=&quot;setup-content&quot;&gt;
          &lt;h2&gt;Welcome! {name}&lt;/h2&gt;
          &lt;p&gt;{isHost ? &apos;You are joining as a Host&apos; : &apos;You are joining as a bidder&apos;}&lt;/p&gt;
          &lt;input disabled={!meeting.self.permissions.canEditDisplayName ?? false} className=&apos;setup-name&apos; value={name} onChange={(e) =&gt; {
            setName(e.target.value)
          }} /&gt;
          &lt;button className=&apos;setup-join&apos; onClick={joinMeeting}&gt;
            Join Meeting
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  )
}

export default SetupScreen
</code></pre><p>Now that we have the basic setup let&apos;s build the live auction platform.</p><p>Create a file <code>src/pages/meeting/Meeting.tsx</code>.</p><p><strong>Our live auction app will implement the following functionality:</strong></p><ul><li>Give hosts an option to start/stop the auction.</li><li>Give hosts an option to navigate between different auction products.</li><li>Allow users to make real-time bids for each product.</li><li>Show the highest bid to all users.</li></ul><pre><code class="language-tsx">import { useEffect, useState } from &apos;react&apos;
import &apos;./meeting.css&apos;
import {
  DyteCameraToggle,
  DyteChatToggle,
  DyteGrid,
  DyteHeader,
  DyteLeaveButton,
  DyteMicToggle,
  DyteNotifications,
  DyteParticipantsAudio,
  DyteSidebar,
  sendNotification,
} from &apos;@dytesdk/react-ui-kit&apos;
import { useDyteMeeting } from &apos;@dytesdk/react-web-core&apos;;
import { AuctionControlBar, Icon } from &apos;../../components&apos;;
import { bidItems } from &apos;../../constants&apos;;

interface Bid {
  bid: number;
  user: string;
}

const Meeting = () =&gt; {
  const { meeting } = useDyteMeeting();

  const [item, setItem] = useState(0);
  const [isHost, setIsHost] = useState&lt;boolean&gt;(false);
  const [showPopup, setShowPopup] = useState&lt;boolean&gt;(true);
  const [auctionStarted, setAuctionStarted] = useState&lt;boolean&gt;(false);
  const [activeSidebar, setActiveSidebar] = useState&lt;boolean&gt;(false);
  const [highestBid, setHighestBid] = useState&lt;Bid&gt;({ bid: 100, user: &apos;default&apos; });

  const handlePrev = () =&gt; {
    if (item - 1 &lt; 0) return;
    setItem(item - 1)
    meeting.participants.broadcastMessage(&apos;item-changed&apos;, { item: item - 1 })
  }
  const handleNext = () =&gt; {
    if ( item + 1 &gt;= bidItems.length) return;
    setItem(item + 1)
    meeting.participants.broadcastMessage(&apos;item-changed&apos;, { item: item + 1 })
  }

  useEffect(() =&gt; {
    setHighestBid({
      bid: bidItems[item].startingBid,
      user: &apos;default&apos;
    })
  }, [item])

  useEffect(() =&gt; {
    if (!meeting) return;

    const preset = meeting.self.presetName;
    if (preset.includes(&apos;host&apos;)) {
      setIsHost(true);
    }

    const handleBroadcastedMessage = ({ type, payload }: { type: string, payload: any }) =&gt; {
      switch(type) {
        case &apos;auction-toggle&apos;: {
          setAuctionStarted(payload.started);
          break;
        }
        case &apos;item-changed&apos;: {
          setItem(payload.item);
          break;
        }
        case &apos;new-bid&apos;: {
          sendNotification({
            id: &apos;new-bid&apos;,
            message: `${payload.user} just made a bid of $ ${payload.bid}!`,
            duration: 2000,
          })
          if (parseFloat(payload.bid) &gt; highestBid.bid) setHighestBid(payload)
          break;
        }
        default:
          break;
      }
    }
    meeting.participants.on(&apos;broadcastedMessage&apos;, handleBroadcastedMessage);

    const handleDyteStateUpdate = ({detail}: any) =&gt; {
        if (detail.activeSidebar) {
         setActiveSidebar(true);
        } else {
          setActiveSidebar(false);
        }
    }

    document.body.addEventListener(&apos;dyteStateUpdate&apos;, handleDyteStateUpdate);

    return () =&gt; {
      document.body.removeEventListener(&apos;dyteStateUpdate&apos;, handleDyteStateUpdate);
      meeting.participants.removeListener(&apos;broadcastedMessage&apos;, handleBroadcastedMessage);
    }
  }, [meeting])

  useEffect(() =&gt; {
    const participantJoinedListener = () =&gt; {
      if (!auctionStarted) return;
      setTimeout(() =&gt; {
        meeting.participants.broadcastMessage(&apos;auction-toggle&apos;, {
          started: auctionStarted
        })
      }, 500)
    
    }
    meeting.participants.joined.on(&apos;participantJoined&apos;, participantJoinedListener);
    return () =&gt; {
      meeting.participants.joined.removeListener(&apos;participantJoined&apos;, participantJoinedListener);
    }
  }, [meeting, auctionStarted])

  const toggleAuction = () =&gt; {
    if (!isHost) return;
    meeting.participants.broadcastMessage(&apos;auction-toggle&apos;, {
      started: !auctionStarted
    })
    if (!auctionStarted) {
      meeting.self.pin();
    } else {
      meeting.self.unpin();
    }
    setAuctionStarted(!auctionStarted);
  }

  return (
    &lt;div className=&apos;meeting-container&apos;&gt;
      &lt;DyteParticipantsAudio meeting={meeting} /&gt;
      &lt;DyteNotifications meeting={meeting} /&gt;

      &lt;DyteHeader meeting={meeting} size=&apos;lg&apos;&gt;
        &lt;div className=&quot;meeting-header&quot;&gt;
          {
            auctionStarted &amp;&amp; (
              &lt;div className=&quot;show-auction-popup&quot; onClick={() =&gt; setShowPopup(() =&gt; !showPopup)}&gt;
                &lt;Icon size=&apos;sm&apos; icon={showPopup ? &apos;close&apos; : &apos;next&apos;} /&gt;
              &lt;/div&gt;
            )
          }
        &lt;/div&gt;
      &lt;/DyteHeader&gt;

      &lt;div className=&apos;meeting-grid&apos;&gt;
        {
          auctionStarted &amp;&amp; (
            &lt;div className={`auction-container ${!showPopup ? &apos;hide-auction-popup&apos; : &apos;&apos;}`}&gt;
              &lt;img className=&apos;auction-img&apos; src={bidItems[item].link} /&gt;
              &lt;div className=&apos;auction-desc&apos;&gt;
                {bidItems[item].description}
              &lt;/div&gt;
              &lt;AuctionControlBar
                item={item}
                highestBid={highestBid}
                handleNext={handleNext}
                handlePrev={handlePrev}
                isHost={isHost}
              /&gt;
          &lt;/div&gt;
          )
        }
        &lt;DyteGrid layout=&apos;column&apos; meeting={meeting} style={{ height: &apos;100%&apos; }}/&gt;
        {activeSidebar &amp;&amp; &lt;DyteSidebar meeting={meeting} /&gt;}
      &lt;/div&gt;

      &lt;div className=&apos;meeting-controlbar&apos;&gt;
        &lt;DyteMicToggle size=&apos;md&apos; meeting={meeting} /&gt;
        &lt;DyteCameraToggle size=&apos;md&apos;  meeting={meeting} /&gt;
        &lt;DyteLeaveButton size=&apos;md&apos; /&gt;
        &lt;DyteChatToggle size=&apos;md&apos; meeting={meeting} /&gt;
        {
          isHost &amp;&amp; (
            &lt;button className=&apos;auction-toggle-button&apos; onClick={toggleAuction}&gt;
              &lt;Icon size=&apos;lg&apos; icon=&apos;auction&apos; /&gt;
              {auctionStarted ? &apos;Stop&apos; : &apos;Start&apos;} Auction
            &lt;/button&gt;
          )
        }
      &lt;/div&gt;
    &lt;/div&gt;
  )
}

export default Meeting
</code></pre><p>Et voila! Our live auction app is ready. <br><br>You can play around with this live bidding app to get you going with some real-time bidding at - <a href="https://dyte-live-bidding.vercel.app/">https://dyte-live-bidding.vercel.app/</a>! Dyte going once, going twice...</p><p>You can check out the complete code for the project <a href="https://github.com/dyte-io/react-samples/tree/main/samples/live-auction">here</a>.</p><p>Are you <em>sold</em> yet, or do we need to say more?</p><h2 id="conclusion">Conclusion</h2><p>With this, we have built our live auction platform. All you have to do is pull out your gavel and shout at the top of your lungs. We look forward to you outbidding us!</p><p>If you have any thoughts or feedback, please reach out to us on <a href="https://www.linkedin.com/company/dyteio/mycompany/">LinkedIn</a> and <a href="https://twitter.com/dyte_io">Twitter</a>. Stay tuned for more related blog posts in the future!</p><p><em>Get better insights on leveraging Dyte&apos;s technology and discover how it can revolutionize your app&apos;s communication capabilities with its </em><a href="https://dyte.io/video-sdk"><em>SDKs</em></a><em>. Head over to </em><a href="https://dyte.io/"><em>dyte.io</em></a><em> to learn how to </em><a href="https://accounts.dyte.in/auth/register"><em>start</em></a><em> quickly on your 10,000 free minutes, which renew every month. You can reach us at </em><a href="mailto:support@dyte.io"><em>support@dyte.io</em></a><em> or ask our </em><a href="https://community.dyte.io/"><em>developer community</em></a><em> if you have any questions.</em></p>]]></content:encoded></item><item><title><![CDATA[AI Generated Background Images for Video Calls]]></title><description><![CDATA[Revolutionise your virtual meetings with this comprehensive guide on building a video-calling app using Dyte SDK. Learn how to integrate Stability AI's model to generate stunning AI images as custom backgrounds.]]></description><link>https://dyte.io/blog/ai-generated-background/</link><guid isPermaLink="false">6594fd533df14600014b3a89</guid><category><![CDATA[AI]]></category><category><![CDATA[Demos]]></category><dc:creator><![CDATA[Vaibhav Shinde]]></dc:creator><pubDate>Tue, 19 Nov 2024 10:30:00 GMT</pubDate><media:content url="https://dyte.io/blog/content/images/2024/01/AI-generated-background-image--1-.png" medium="image"/><content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2><img src="https://dyte.io/blog/content/images/2024/01/AI-generated-background-image--1-.png" alt="AI Generated Background Images for Video Calls"><p>In this tutorial, we will create a video-calling app using the Dyte SDK. This app will enable users to input prompts, generate AI-generated images, and set them as background during video calls. &#x2728;</p><h2 id="introduction">Introduction</h2><p>With the advent of text-to-image models like DALL.E 3 and Midjourney, there has been a rise in different use cases for them.</p><p>In this tutorial, we&apos;re going to create a video calling app that uses a similar model from <a href="https://stability.ai/" rel="nofollow">Stability AI</a>. Users can input prompts and have AI-generated images as their backgrounds during video calls. &#x2728;</p><p>So, let&apos;s get going! &#x1F680;</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/01/chart.png" class="kg-image" alt="AI Generated Background Images for Video Calls" loading="lazy" width="2000" height="1180" srcset="https://dyte.io/blog/content/images/size/w600/2024/01/chart.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/01/chart.png 1000w, https://dyte.io/blog/content/images/size/w1600/2024/01/chart.png 1600w, https://dyte.io/blog/content/images/2024/01/chart.png 2000w" sizes="(min-width: 720px) 720px"></figure><h2 id="high-level-design-of-the-application">High-Level Design of the application</h2><p>Our aim is to create a seamless and engaging video-calling experience that goes beyond the ordinary. When users click the &quot;Create Meeting&quot; button in our app, they see a staging area with the option to input prompts for AI image generation. The generated image will be set as the background of your video.</p><ul><li>In this project, we will use React with <a href="https://dyte.io/blog/custom-ui-kit-sdk/" rel="nofollow">Dyte UI kit</a> and <a href="https://www.npmjs.com/package/@dytesdk/react-web-core" rel="nofollow">Dyte React Web Core</a> packages for the frontend.</li><li>For the backend, we will use NodeJS with Express</li><li>For image generation, we will use <a href="https://stability.ai/" rel="nofollow">Stability AI</a></li><li>Lastly, we will use <a href="https://apidocs.imgur.com/" rel="nofollow">Imgur</a> for storing screenshots.</li></ul><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/01/aiImage.png" class="kg-image" alt="AI Generated Background Images for Video Calls" loading="lazy" width="1650" height="662" srcset="https://dyte.io/blog/content/images/size/w600/2024/01/aiImage.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/01/aiImage.png 1000w, https://dyte.io/blog/content/images/size/w1600/2024/01/aiImage.png 1600w, https://dyte.io/blog/content/images/2024/01/aiImage.png 1650w" sizes="(min-width: 720px) 720px"></figure><h2 id="folder-structure">Folder Structure</h2><p>We will keep our client code in <code>frontend</code>  folder meanwhile our backend code will reside in the root folder itself. After completing the tutorial, the folder structure will look like this. &#x1F447;</p><pre><code class="language-bash">&#x251C;&#x2500;&#x2500; frontend
&#x2502;   &#x251C;&#x2500;&#x2500; public
&#x2502;   &#x2514;&#x2500;&#x2500; src
&#x2502;       &#x251C;&#x2500;&#x2500; components
&#x2502;       &#x2502;   &#x251C;&#x2500;&#x2500; Home.js
&#x2502;       &#x2502;   &#x251C;&#x2500;&#x2500; Meet.js
&#x2502;       &#x2502;   &#x2514;&#x2500;&#x2500; Stage.js
&#x2502;       &#x251C;&#x2500;&#x2500; App.css
&#x2502;       &#x251C;&#x2500;&#x2500; App.js
&#x2502;       &#x251C;&#x2500;&#x2500; App.test.js
&#x2502;       &#x251C;&#x2500;&#x2500; index.css
&#x2502;       &#x251C;&#x2500;&#x2500; index.js
&#x2502;       &#x251C;&#x2500;&#x2500; logo.svg
&#x2502;       &#x251C;&#x2500;&#x2500; reportWebVitals.js
&#x2502;       &#x2514;&#x2500;&#x2500; setupTests.js
&#x251C;&#x2500;&#x2500; package.json
&#x2514;&#x2500;&#x2500; src
    &#x251C;&#x2500;&#x2500; api
    &#x2502;   &#x251C;&#x2500;&#x2500; dyte.js
    &#x2502;   &#x2514;&#x2500;&#x2500; stability.js
    &#x2514;&#x2500;&#x2500; index.js

</code></pre>
<h2 id="step-0-configurations-and-setup">Step 0: Configurations and Setup</h2><p>&#x200D;&#x1F4BB; Before building our application, we must set up a Dyte account.</p><p>We can create a free account by clicking the &quot;Start Building&quot; button on <a href="https://dyte.io/" rel="noreferrer">Dyte.io</a> and signing up using Google or GitHub .</p><p>Once signed up, we can access our <a href="https://dev.dyte.io/apikeys" rel="nofollow">Dyte API keys</a> from the &quot;API Keys&quot; tab in the left sidebar. We will keep these keys secure as we will use them later. &#x1F92B;</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/01/gifAI.gif" class="kg-image" alt="AI Generated Background Images for Video Calls" loading="lazy" width="864" height="460" srcset="https://dyte.io/blog/content/images/size/w600/2024/01/gifAI.gif 600w, https://dyte.io/blog/content/images/2024/01/gifAI.gif 864w" sizes="(min-width: 720px) 720px"></figure><p>We will begin by creating a new directory for our project, and navigating into it using the following commands:</p><pre><code class="language-bash">mkdir dyte
cd dyte
</code></pre>
<p><strong>Please note:</strong></p><p>We will also require accounts on the following platforms:</p><ul><li>Imgur: Create an account on Imgur and create an API key. Here is a <a href="https://apidocs.imgur.com/" rel="nofollow">step-by-step guide</a></li><li>Stability AI : Here is a <a href="https://platform.stability.ai/docs/getting-started" rel="nofollow">step-by-step guide</a></li></ul><p>Now back to the tutorial.</p><h2 id="step-1-setting-up-the-frontend">Step 1: Setting up the frontend</h2><p>Let&apos;s start setting up our front-end project using React and Dyte! &#x2728;</p><p><code>create-react-app</code>We will create a boilerplate React app using . We can do this with the following command:</p><pre><code class="language-bash">npx create-react-app frontend
</code></pre>
<p>This will initialize a new React app in the <code>frontend</code> directory. &#x1F4C1;</p><p><code>react-router</code>Then, we will go ahead and install the <code>dyte react-web-core</code>, <code>dyte react-ui-kit</code> and  packages in this project using the following command &#x1F447;</p><pre><code class="language-bash">cd frontend 
npm install @dytesdk/react-web-core @dytesdk/react-ui-kit react-router react-router-dom @dytesdk/video-background-transformer dotenv
</code></pre>
<figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="https://dyte.io/blog/content/media/2024/03/terminal_thumb.jpg" data-kg-custom-thumbnail>
            <div class="kg-video-container">
                <video src="https://dyte.io/blog/content/media/2024/03/terminal.mp4" poster="https://img.spacergif.org/v1/1236x810/0a/spacer.png" width="1236" height="810" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://dyte.io/blog/content/media/2024/03/terminal_thumb.jpg&apos;) 50% 50% / cover no-repeat;"></video>
                <div class="kg-video-overlay">
                    <button class="kg-video-large-play-icon" aria-label="Play video">
                        <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                            <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/>
                        </svg>
                    </button>
                </div>
                <div class="kg-video-player-container kg-video-hide">
                    <div class="kg-video-player">
                        <button class="kg-video-play-icon" aria-label="Play video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/>
                            </svg>
                        </button>
                        <button class="kg-video-pause-icon kg-video-hide" aria-label="Pause video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/>
                                <rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/>
                            </svg>
                        </button>
                        <span class="kg-video-current-time">0:00</span>
                        <div class="kg-video-time">
                            /<span class="kg-video-duration">0:07</span>
                        </div>
                        <input type="range" class="kg-video-seek-slider" max="100" value="0">
                        <button class="kg-video-playback-rate" aria-label="Adjust playback speed">1&#xD7;</button>
                        <button class="kg-video-unmute-icon" aria-label="Unmute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/>
                            </svg>
                        </button>
                        <button class="kg-video-mute-icon kg-video-hide" aria-label="Mute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/>
                            </svg>
                        </button>
                        <input type="range" class="kg-video-volume-slider" max="100" value="100">
                    </div>
                </div>
            </div>
            
        </figure><h2 id="step-2-setting-up-the-backend">Step 2: Setting up the backend</h2><p>Let&apos;s get started with setting up our NodeJs with express backend now. &#x1F64C;</p><p>We will go back to the root directory of our project and initiate our backend here itself for the ease of hosting:</p><pre><code class="language-bash">npm init -y
</code></pre>
<p>Now let&apos;s install our dependencies</p><pre><code class="language-bash">npm install express cors axios dotenv
npm install -g nodemon
</code></pre>
<figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/01/gif2ai.gif" class="kg-image" alt="AI Generated Background Images for Video Calls" loading="lazy" width="1237" height="810" srcset="https://dyte.io/blog/content/images/size/w600/2024/01/gif2ai.gif 600w, https://dyte.io/blog/content/images/size/w1000/2024/01/gif2ai.gif 1000w, https://dyte.io/blog/content/images/2024/01/gif2ai.gif 1237w" sizes="(min-width: 720px) 720px"></figure><h2 id="step-3-setting-up-our-backend-application">Step 3: Setting up our backend application</h2><p></p><p>First let us start by defining our <code>.env</code> file to store our 3rd party API keys. &#x1F511;</p><pre><code class="language-yaml">DYTE_ORG_ID=&lt;ORG_ID&gt;
IMGUR_CLIENT_ID=&lt;IMGUR_KEY&gt;
DYTE_API_KEY=&lt;DYTE_KEY&gt;
STABILITY_API_KEY=&lt;SATBILITY_KEY&gt;
</code></pre>
<p>Now we would need to write code for using Dyte and Stability APIs.</p><p>First we will write a module that provides a function to interact with the Stability API to generate an image based on our text prompt.</p><p><code>src/api/stability.js</code></p><pre><code class="language-javascript">// Description: This file contains the functions that interact with the Stability API
const axios = require(&quot;axios&quot;);
const dotenv = require(&quot;dotenv&quot;);
const path = require(&quot;path&quot;);

// Create an absolute path to the .env file located one directory above
const dotenvPath = path.join(__dirname, &quot;../..&quot;, &quot;.env&quot;);

// Load the environment variables from the .env file
dotenv.config({ path: dotenvPath });

const STABILITY_API_KEY = process.env.STABILITY_API_KEY;
const textToImage = async (prompt) =&gt; {
	const apiUrl =
		&quot;https://api.stability.ai/v1/generation/stable-diffusion-512-v2-1/text-to-image&quot;;

	const headers = {
		Accept: &quot;application/json&quot;,
		Authorization: STABILITY_API_KEY, // Replace with your actual API key
	};

	const body = {
		steps: 10,
		width: 512,
		height: 512,
		seed: 0,
		cfg_scale: 5,
		samples: 1,
		text_prompts: [
			{
				text: prompt,
				weight: 1,
			},
			{
				text: &quot;blurry, bad&quot;,
				weight: -1,
			},
		],
	};

	try {
		const response = await axios.post(apiUrl, body, {
			headers,
		});

		if (response.status !== 200) {
			throw new Error(`Non-200 response: ${response.status}`);
		}

		const responseJSON = response.data;
		console.log(response);
		console.log(responseJSON);
		const base64Images = responseJSON.artifacts.map((image) =&gt; image.base64);

		console.log(base64Images);
		return base64Images[0];
	} catch (error) {
		throw new Error(`Error generating image: ${error.message}`);
	}
};

module.exports = { textToImage };
</code></pre>
<p>Now lets write code for using Dyte API.</p><p><code>src/api/dyte.js</code></p><pre><code class="language-javascript">const axios = require(&quot;axios&quot;);
const path = require(&quot;path&quot;);
const dotenv = require(&quot;dotenv&quot;);

// Create an absolute path to the .env file located one directory above
const dotenvPath = path.join(__dirname, &quot;../..&quot;, &quot;.env&quot;);

// Load the environment variables from the .env file
dotenv.config({ path: dotenvPath });

const DYTE_API_KEY = process.env.DYTE_API_KEY;
const DYTE_ORG_ID = process.env.DYTE_ORG_ID;

console.log(DYTE_API_KEY, DYTE_ORG_ID);

const API_HASH = Buffer.from(
  `${DYTE_ORG_ID}:${DYTE_API_KEY}`,
  &quot;utf-8&quot;
).toString(&quot;base64&quot;);

console.log(API_HASH);
const DyteAPI = axios.create({
  baseURL: &quot;https://api.dyte.io/v2&quot;,
  headers: {
    Authorization: `Basic ${API_HASH}`,
  },
});

module.exports = DyteAPI;
</code></pre>
<p>Next, we will start with our <code>index.js</code> file, we would need to create the following routes:</p><p><code>POST /meetings</code> - Create a new meeting</p><p><code>POST /meetings/{meetingId}/participants</code> - This route is responsible for adding a participant to a specific meeting identified by <code>meetingId</code></p><p><code>POST /upload</code> - Responsible for generating the AI image from the prompt, uploading to imgur and returning the imgur link</p><hr><p>So let&apos;s get started &#x1F447;</p><p><code>src/index.js</code></p><pre><code class="language-javascript">const express = require(&quot;express&quot;);
const cors = require(&quot;cors&quot;);
const DyteAPI = require(&quot;./api/dyte&quot;);
const axios = require(&quot;axios&quot;);
const bodyParser = require(&quot;body-parser&quot;);
const { textToImage } = require(&quot;./api/stability&quot;); // Update the path accordingly

const PORT = process.env.PORT || 3000;
const app = express();

app.use(cors());
app.use(express.json());
app.use(bodyParser.json({ limit: &quot;10mb&quot; }));

app.post(&quot;/meetings&quot;, async (req, res) =&gt; {
	const { title } = req.body;
	const response = await DyteAPI.post(&quot;/meetings&quot;, {
		title,
	});
	return res.status(response.status).json(response.data);
});

app.post(&quot;/meetings/:meetingId/participants&quot;, async (req, res) =&gt; {
	const meetingId = req.params.meetingId;
	const { name, preset_name } = req.body;
	const client_specific_id = `react-samples::${name.replaceAll(
		&quot; &quot;,
		&quot;-&quot;
	)}-${Math.random().toString(36).substring(2, 7)}`;
	const response = await DyteAPI.post(`/meetings/${meetingId}/participants`, {
		name,
		preset_name,
		client_specific_id,
	});

	return res.status(response.status).json(response.data);
});

app.post(&quot;/upload&quot;, async (req, res) =&gt; {
	try {
		const { prompt } = req.body;
		console.log(prompt);

		const generatedImageBase64 = await textToImage(prompt);

		// Upload the generated image to Imgur
		const imgurClientId = process.env.IMGUR_CLIENT_ID;

		const response = await axios.post(
			&quot;https://api.imgur.com/3/image&quot;,
			{
				image: generatedImageBase64,
			},
			{
				headers: {
					Authorization: `Client-ID ${imgurClientId}`,
					&quot;Content-Type&quot;: &quot;application/json&quot;,
				},
			}
		);

		const imgurLink = response.data.data.link;
		return res.status(200).json({ imgurLink });
	} catch (error) {
		console.error(&quot;Error uploading image:&quot;, error.message);
		if (error.response) {
			console.error(&quot;Imgur API response:&quot;, error.response.data);
		}
		return res.status(500).json({ error: &quot;Could not upload image.&quot; });
	}
});

app.listen(PORT, () =&gt; {
	console.log(`Started listening on ${PORT}...`);
});
</code></pre>
<p></p><h2 id="step-4-creating-the-frontend">Step 4: Creating the frontend</h2><p>This React application provides functionality to automatically create a meeting when the main route (/) is accessed and doesn&apos;t contain a meeting ID in the URL.</p><p>The application displays different views/components based on the route: a Home view when at the root and a Stage view when accessing a specific meeting.</p><p><code>App.js</code></p><pre><code class="language-javascript">import { useEffect, useState } from &quot;react&quot;;
import Home from &quot;./components/Home&quot;;
import { BrowserRouter, Routes, Route, Link } from &quot;react-router-dom&quot;;
import &quot;./App.css&quot;;
import Stage from &quot;./components/Stage&quot;;

const SERVER_URL = process.env.SERVER_URL || &quot;http://localhost:3000&quot;;

function App() {
	const [meetingId, setMeetingId] = useState();

	const createMeeting = async () =&gt; {
		try {
			const res = await fetch(`${SERVER_URL}/meetings`, {
				method: &quot;POST&quot;,
				body: JSON.stringify({ title: &quot;AI generated image background&quot; }),
				headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
			});

			if (!res.ok) {
				throw new Error(&quot;Failed to create meeting&quot;); // You can customize the error message
			}

			const resJson = await res.json();
			setMeetingId(resJson.data.id);
		} catch (error) {
			console.error(&quot;Error creating meeting:&quot;, error);
		}
	};

	useEffect(() =&gt; {
		const id = window.location.pathname.split(&quot;/&quot;)[2];
		if (!!!id) {
			createMeeting();
		}
	}, []);

	return (
		&lt;BrowserRouter&gt;
			&lt;Routes&gt;
				&lt;Route path=&quot;/&quot; element={&lt;Home meetingId={meetingId} /&gt;}&gt;&lt;/Route&gt;
				&lt;Route path=&quot;/meeting/:meetingId&quot; element={&lt;Stage /&gt;}&gt;&lt;/Route&gt;
			&lt;/Routes&gt;
		&lt;/BrowserRouter&gt;
	);
}

export default App;
</code></pre>
<p>Now let&apos;s come to our Home component.</p><p>&#x1F680; The Home component is the heart of the user interface, serving as a minimal entry point for the application. Here&apos;s what it does:</p><p><strong>Prompt Handling:</strong> Users can input a prompt in a text field. This is managed using useState, making the input reactive and interactive.</p><p><strong>Meeting Creation:</strong> The core feature here is the ability to create a meeting. Upon clicking the <code>Create and join meeting</code> button, it triggers <code>handleCreateMeeting</code> function which further calls <code>handleUpload</code>. This sends the prompt to a server and navigates the user to a meeting page.</p><p><code>src/components/Home.js</code></p><pre><code class="language-javascript">import { useNavigate } from &quot;react-router-dom&quot;;
import { useState } from &quot;react&quot;;
import { useAIImage } from &quot;../SharedDataContext&quot;;

function Home({ meetingId }) {
	const [prompt, setPrompt] = useState(&quot;&quot;);
	const [loading, setLoading] = useState(false);
	const { updateAIImageUrl } = useAIImage();
	const navigate = useNavigate();

	const REACT_APP_SERVER_URL =
		process.env.REACT_APP_SERVER_URL || &quot;http://localhost:3000&quot;;

	const handleUpload = async () =&gt; {
		try {
			const response = await fetch(REACT_APP_SERVER_URL + &quot;/upload&quot;, {
				method: &quot;POST&quot;,
				headers: {
					&quot;Content-Type&quot;: &quot;application/json&quot;,
				},
				body: JSON.stringify({ prompt: prompt }),
			});

			if (response.ok) {
				const data = await response.json();
				console.log(data);
				updateAIImageUrl(data?.imgurLink);
				setLoading(false);
				navigate(`/meeting/${meetingId}`);
			} else {
				console.log(&quot;error&quot; + response);
			}
		} catch (error) {
			console.log(error);
		}
	};

	const handleCreateMeeting = async () =&gt; {
		setLoading(true);
		try {
			updateAIImageUrl(prompt);
			handleUpload();
		} catch (error) {
			console.error(&quot;Error generating image:&quot;, error);
		}
	};

	return (
		&lt;div
			style={{
				height: &quot;100vh&quot;,
				width: &quot;100vw&quot;,
				fontSize: &quot;x-large&quot;,
				display: &quot;flex&quot;,
				flexDirection: &quot;column&quot;,
				justifyContent: &quot;center&quot;,
				alignItems: &quot;center&quot;,
			}}
		&gt;
			&lt;h2
				style={{
					color: &quot;#00000&quot;,
					fontWeight: &quot;bold&quot;,
					fontSize: &quot;1.5rem&quot;,
					marginBottom: &quot;20px&quot;,
				}}
			&gt;
				Enter AI prompt
			&lt;/h2&gt;
			&lt;input
				type=&quot;text&quot;
				value={prompt}
				onChange={(e) =&gt; setPrompt(e.target.value)}
				style={{
					paddingTop: &quot;8px&quot;,
					paddingBottom: &quot;8px&quot;,
					paddingLeft: &quot;4px&quot;,
					paddingRight: &quot;4px&quot;,
					border: &quot;2px #2260FD solid&quot;,
					borderRadius: &quot;4px&quot;,
					width: &quot;300px&quot;,
					marginBottom: &quot;20px&quot;,
				}}
			/&gt;
			&lt;button
				onClick={handleCreateMeeting}
				style={{
					backgroundColor: &quot;#2260FD&quot;,
					color: &quot;white&quot;,
					padding: &quot;10px 20px&quot;,
					borderRadius: &quot;4px&quot;,
					fontWeight: &quot;bold&quot;,
					alignItems: &quot;center&quot;,
					border: &quot;none&quot;,
					cursor: &quot;pointer&quot;,
					width: &quot;310px&quot;,
					display: &quot;flex&quot;,
					justifyContent: &quot;center&quot;,
				}}
				disabled={loading}
			&gt;
				{loading ? (
					&lt;div className=&quot;spinner-border text-light&quot; role=&quot;status&quot;&gt;
						&lt;span className=&quot;visually-hidden&quot;&gt;Loading...&lt;/span&gt;
					&lt;/div&gt;
				) : (
					&quot;Create and join meeting&quot;
				)}
			&lt;/button&gt;
		&lt;/div&gt;
	);
}

export default Home;
</code></pre>
<p>&#x1F4F8; Here&apos;s how our root page looks after adding the <code>Home</code> component :</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/01/gif3ai.png" class="kg-image" alt="AI Generated Background Images for Video Calls" loading="lazy" width="2000" height="1030" srcset="https://dyte.io/blog/content/images/size/w600/2024/01/gif3ai.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/01/gif3ai.png 1000w, https://dyte.io/blog/content/images/size/w1600/2024/01/gif3ai.png 1600w, https://dyte.io/blog/content/images/2024/01/gif3ai.png 2000w" sizes="(min-width: 720px) 720px"></figure><p>Now, let&apos;s delve into the <code>Stage</code> component that renders on route <code>/meeting/:meetingId</code> which acts as a container component, orchestrating the meeting stage of the application.</p><p>When the admin clicks on the link provided on the <code>/</code> route, he gets redirected to the Stage page</p><p><code>src/components/Stage.js</code></p><pre><code class="language-javascript">import Meet from &quot;./Meet&quot;;

const Stage = () =&gt; {
	return (
		&lt;div
			style={{
				height: &quot;100vh&quot;,
				width: &quot;100vw&quot;,
				display: &quot;flex&quot;,
				justifyContent: &quot;center&quot;,
				alignItems: &quot;center&quot;,
				color: &quot;white&quot;,
			}}
		&gt;
			&lt;&gt;
				&lt;Meet /&gt;
			&lt;/&gt;
		&lt;/div&gt;
	);
};

export default Stage;
</code></pre>
<p>And the last one is the Meet component. The Meet component is where we utilize the <code>DyteMeeting</code> component from the Dyte SDK, to set up and manage a meeting environment.</p><p><code>src/components/Meet.js</code></p><pre><code class="language-javascript">
import { useState, useEffect, useRef } from &quot;react&quot;;
import { DyteMeeting, provideDyteDesignSystem } from &quot;@dytesdk/react-ui-kit&quot;;
import { useDyteClient } from &quot;@dytesdk/react-web-core&quot;;
import DyteVideoBackgroundTransformer from &quot;@dytesdk/video-background-transformer&quot;;
import { useAIImage } from &quot;../SharedDataContext&quot;;

// Constants

const REACT_APP_SERVER_URL =
	process.env.REACT_APP_SERVER_URL || &quot;http://localhost:3000&quot;;

const Meet = () =&gt; {
	const meetingEl = useRef();
	const [meeting, initMeeting] = useDyteClient();
	const [userToken, setUserToken] = useState();
	const [hasInitializedBackground, setHasInitializedBackground] =
		useState(false);

	const { AIImageUrl } = useAIImage();

	const meetingId = window.location.pathname.split(&quot;/&quot;)[2];

	const initializeVideoBackground = async () =&gt; {
		try {
			if (!meeting) {
				return; // No need to proceed if the meeting is not available
			}

			const videoBackgroundTransformer =
				await DyteVideoBackgroundTransformer.init();
			const videoMiddleware =
				await videoBackgroundTransformer.createStaticBackgroundVideoMiddleware(
					AIImageUrl
				);

			meeting.self.addVideoMiddleware(videoMiddleware);
			console.log(&quot;Video background initialized&quot;);
		} catch (error) {
			console.error(&quot;Error initializing video background:&quot;, error);
		}
	};

	const joinMeeting = async (id) =&gt; {
		try {
			const res = await fetch(
				`${REACT_APP_SERVER_URL}/meetings/${id}/participants`,
				{
					method: &quot;POST&quot;,
					body: JSON.stringify({
						name: &quot;new user&quot;,
						preset_name: &quot;group_call_host&quot;,
						meeting_id: meetingId,
					}),
					headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
				}
			);

			if (!res.ok) {
				throw new Error(&quot;Failed to join meeting&quot;); // Customize the error message
			}

			const resJson = await res.json();
			return resJson.data.token;
		} catch (error) {
			console.error(&quot;Error joining meeting:&quot;, error.message);
		}
	};

	const joinMeetingId = async () =&gt; {
		if (meetingId) {
			const authToken = await joinMeeting(meetingId);
			await initMeeting({
				authToken,
			});
			setUserToken(authToken);
		}
	};

	useEffect(() =&gt; {
		if (meetingId &amp;&amp; !userToken) joinMeetingId();
	}, []);

	useEffect(() =&gt; {
		if (meeting &amp;&amp; !hasInitializedBackground) {
			initializeVideoBackground();
			setHasInitializedBackground(true);
		}
	}, [meeting, hasInitializedBackground]);

	useEffect(() =&gt; {
		if (userToken) {
			provideDyteDesignSystem(meetingEl.current, {
				theme: &quot;light&quot;,
			});
		}
	}, [userToken]);

	return (
		&lt;div style={{ height: &quot;100vh&quot;, width: &quot;100vw&quot;, display: &quot;flex&quot; }}&gt;
			{userToken &amp;&amp; (
				&lt;&gt;
					&lt;div style={{ width: &quot;100vw&quot;, height: &quot;100vh&quot; }}&gt;
						&lt;DyteMeeting mode=&quot;fill&quot; meeting={meeting} ref={meetingEl} /&gt;
					&lt;/div&gt;
				&lt;/&gt;
			)}
		&lt;/div&gt;
	);
};

export default Meet;
</code></pre>
<p>This component will handle the meeting setup. It will take user prompt and apply an AI-generatedted image from that prompt as the background.nd.</p><h2 id="step-5-trying-out-our-application">Step 5: Trying out our application</h2><p>Ta-da! &#x2728; It&apos;s time to put our application to the test and see it in action!</p><ul><li>First click on create meeting &#x1F9D1;&#x200D;&#x1F4BB;</li><li>Then we give a prompt for the AI to generate an image from. Voila! Your background is set</li><li>We then join the meeting with our new customised AI generated background!</li></ul><figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="https://dyte.io/blog/content/media/2024/03/ai_bg_thumb.jpg" data-kg-custom-thumbnail>
            <div class="kg-video-container">
                <video src="https://dyte.io/blog/content/media/2024/03/ai_bg.mp4" poster="https://img.spacergif.org/v1/1440x810/0a/spacer.png" width="1440" height="810" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://dyte.io/blog/content/media/2024/03/ai_bg_thumb.jpg&apos;) 50% 50% / cover no-repeat;"></video>
                <div class="kg-video-overlay">
                    <button class="kg-video-large-play-icon" aria-label="Play video">
                        <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                            <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/>
                        </svg>
                    </button>
                </div>
                <div class="kg-video-player-container kg-video-hide">
                    <div class="kg-video-player">
                        <button class="kg-video-play-icon" aria-label="Play video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/>
                            </svg>
                        </button>
                        <button class="kg-video-pause-icon kg-video-hide" aria-label="Pause video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/>
                                <rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/>
                            </svg>
                        </button>
                        <span class="kg-video-current-time">0:00</span>
                        <div class="kg-video-time">
                            /<span class="kg-video-duration">0:06</span>
                        </div>
                        <input type="range" class="kg-video-seek-slider" max="100" value="0">
                        <button class="kg-video-playback-rate" aria-label="Adjust playback speed">1&#xD7;</button>
                        <button class="kg-video-unmute-icon" aria-label="Unmute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/>
                            </svg>
                        </button>
                        <button class="kg-video-mute-icon kg-video-hide" aria-label="Mute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/>
                            </svg>
                        </button>
                        <input type="range" class="kg-video-volume-slider" max="100" value="100">
                    </div>
                </div>
            </div>
            
        </figure><p>You can try the live demo here: <a href="https://main--background-image-gen.netlify.app/" rel="nofollow">Live Demo Link</a></p><h2 id="to-run-locally">To run locally:</h2><p>n the <code>/</code> folder   </p><pre><code class="language-bash">npm i
nodemon index.js
</code></pre>
<p>In <code>frontend/</code></p><pre><code class="language-bash">npm i
npm run start
</code></pre>
<p><a href="https://github.com/vishal-sarg/dyte-background-generator">Link to GitHub Repo</a></p><h2 id="conclusion">Conclusion</h2><p>In conclusion, we&apos;ve harnessed recent image generation AI&apos;s power to create our video call backgrounds, opening up exciting possibilities. Now, your virtual meetings, classes, or gatherings can be infused with vibrant visuals, from scenic landscapes to dynamic artwork.</p><p>You may go ahead and start creating your video calling applications with <a href="https://dyte.io/" rel="nofollow">Dyte</a>. &#x1F31F;</p>]]></content:encoded></item><item><title><![CDATA[Packaging Libraries in iOS: A Comprehensive Guide]]></title><description><![CDATA[In this blog, learn the nuances of the creation and distribution of iOS SDKs to enhance the efficiency and reliability of your projects.]]></description><link>https://dyte.io/blog/packaging-libraries-ios/</link><guid isPermaLink="false">650b1d46d0c96400019525cc</guid><category><![CDATA[Engineering]]></category><dc:creator><![CDATA[Shaunak Jagtap]]></dc:creator><pubDate>Mon, 18 Nov 2024 17:37:00 GMT</pubDate><media:content url="https://dyte.io/blog/content/images/2023/09/Packaging-Libraries-in-iOS.png" medium="image"/><content:encoded><![CDATA[<h2 id="understanding-the-premise"><strong>Understanding the premise</strong></h2><img src="https://dyte.io/blog/content/images/2023/09/Packaging-Libraries-in-iOS.png" alt="Packaging Libraries in iOS: A Comprehensive Guide"><p>Software Development Kits (<a href="https://dyte.io/ios-video-sdk">SDKs</a>) are the lifeblood of development, offering a treasure of pre-packaged libraries, tools, and resources that empower developers to craft rich and feature-packed applications. However, for those new to distributing SDKs in iOS, the path can be filled with uncertainty.</p><p>We have shipped 100s of <a href="https://dyte.io/ios-video-sdk">iOS SDK</a> builds in the form of our WebRTC clients for customers looking to integrate real time communications within their applications. With that experience, this technical blog post is tailored for iOS developers and aims to illuminate the nuances of SDK distribution, including key concepts, best practices, and tools. With this knowledge, developers can unlock the full potential of their SDKs and ensure seamless integration experiences.</p><h3 id="static-vs-dynamic-libraries-and-frameworks-in-ios"><strong>Static vs. dynamic libraries and frameworks in iOS</strong></h3><p>Seasoned iOS developers often grapple with crucial decisions regarding system frameworks, packaging their code, and integrating third-party components. Among these decisions, choosing between static and dynamic libraries or frameworks is pivotal, making profound implications for application performance and resource management.</p><h3 id="implications-on-app-size-and-launch-time"><strong>Implications on app size and launch time</strong></h3><p>The choice between static and dynamic libraries or frameworks carries significant weight regarding your app&apos;s binary size and launch time.</p><h3 id="summary-of-static-vs-dynamic-linking">Summary of static Vs. dynamic linking</h3><p>Here&apos;s a concise summary of how static and dynamic linking impacts various facets of your application:</p>
<!--kg-card-begin: html-->
<style>
    tr td:first-child {
      min-width: 8em;
      width: 8em;
    }
</style>
<table class="first-col-highlight">
<thead>
<tr>
<th>Facets</th>
<th>Static Linking</th>
<th>Dynamic Linking</th>
</tr>
</thead>
<tbody>
<tr>
<td>App Size</td>
<td>Large app bundle size</td>
<td>Smaller app bundle size</td>
</tr>
<tr>
<td>App Launch Time</td>
<td>Faster load time</td>
<td>Slower load time</td>
</tr>
<tr>
<td>Safety</td>
<td>Scrutinised and copied at build time</td>
<td>Risk of runtime glitches, potential for runtime crashes</td>
</tr>
<tr>
<td>Deployment</td>
<td>In static linking, the SDK or library is bundled within the single app binary, and the entire library&apos;s code becomes an integral part of the app&apos;s binary executable.</td>
<td>App references the library at runtime, and the operating system is responsible for loading the required library when the app is launched or when the specific library functions are first called</td>
</tr>
<tr>
<td>Debugging</td>
<td>Easier to debug as all code is available</td>
<td>Harder to debug as code may not be available at runtime</td>
</tr>
<tr>
<td>Memory Usage</td>
<td>apps that are statically linked tend to have less memory usage during runtime.</td>
<td>may have slightly more memory usage during runtime</td>
</tr>
<tr>
<td>Flexibility</td>
<td>Updates to the SDK or library require app recompilation and release because the entire library is bundled with the app&apos;s binary during compilation, preventing separate updates.</td>
<td>Library updates can be made independently of the app, offering flexibility and allowing users to benefit from updates without app recompilation.</td>
</tr>
</tbody>
</table>
<!--kg-card-end: html-->
<h3 id="when-to-use-dynamic-linking"><strong>When to use dynamic linking</strong></h3><p>While statically linked modules proffer a smaller app size and accelerated loading times, dynamic linking has its own set of compelling use cases:</p><ul><li><strong>Multiple static modules depending on the same module:</strong> When your app comprises multiple static modules that lean on a common module, you might encounter warnings about duplicate symbols at runtime. Transitioning the shared module to a dynamic one can effectively mitigate this issue.</li><li><strong>iOS increased app launch time with many dynamic libraries/frameworks:</strong> Loading numerous third-party dynamic libraries or frameworks on <a href="https://dyte.io/ios-video-sdk">iOS</a> can lead to prolonged app launch times. Vigilant monitoring and optimisation efforts are essential to upholding app launch performance.</li></ul><h3 id="staticdynamic-with-different-integration-techniques"><strong>Static/dynamic with different integration techniques</strong></h3><p>To make well-informed decisions about linking, a profound understanding of how various dependency managers handle linking behaviour is imperative:</p><ul><li><strong>Own targets or projects linked directly:</strong> Exert precise control over the linking behaviour of targets within your repository or external repositories by fine-tuning Build Settings. A simple adjustment to the <strong><code>MACH_O_TYPE</code></strong> Build Setting allows you to toggle between static library and dynamic library.</li><li><strong>CocoaPods:</strong> By default, CocoaPods constructs and links dependencies as static libraries. The introduction of <strong><code>use_frameworks!</code></strong> in your Podfile enables the construction of dynamic frameworks. Moreover, <strong><code>:linkage =&gt; :static</code></strong> can be employed to shape dependencies as static frameworks.</li><li><strong>Swift Package Manager (SPM):</strong> SPM, by default, fabricates dependencies as static libraries, offering minimal control over linking behaviour. However, if you are the custodian of a package, you can specify <strong><code>type: .dynamic</code></strong> within your <strong><code>Package.swift</code></strong> file to fashion a dynamic package.</li><li><strong>Carthage:</strong> In its default configuration, Carthage leans towards using dynamic frameworks for dependencies. Nevertheless, you retain the flexibility to configure it to construct and link them statically when circumstances dictate such an arrangement.</li></ul><h2 id="understanding-different-formats"><strong>Understanding different formats</strong></h2><p>In the realm of iOS development, many formats for packaging libraries and resources exist. Each format serves distinct purposes, and as an expert iOS developer, comprehending their nuances is indispensable. Here&apos;s an exploration of some essential formats:</p><h3 id="xcframework"><strong>xcframework</strong></h3><p><strong>XCFramework</strong> is a relatively recent addition to Apple&apos;s arsenal of formats. It is a versatile container meticulously designed for packaging frameworks for diverse platforms and architectures into a single, harmonious bundle. Embracing XCFrameworks streamlines the distribution of binary frameworks, ensuring harmonious coexistence across various Apple devices and processor architectures. Adopting XCFrameworks bestows developers with the gift of streamlined development, reduced integration complexities, and enhanced application performance.</p><h3 id="framework"><strong>Framework</strong></h3><p>The classic <strong>framework</strong> format remains a stalwart choice for packaging code and resources in<a href="https://dyte.io/ios-video-sdk"> iOS development</a>. Frameworks offer a structured habitat for your codebase, gracefully accommodating header files, binaries, and resources. They advocate the virtues of encapsulation and modularity, simplifying the process of integration and maintenance of your code. Frameworks wear the dual hats of either static or dynamic, contingent upon whether the code binds at compile time (static) or runtime (dynamic).</p><h3 id="a-static-library-and-o-object-file"><strong>.a (Static Library) and .o (Object File)</strong></h3><p><strong>.a files</strong>, heralded as static libraries, house compiled code snugly woven into the fabric of your application at compile time. These libraries accompany your app&apos;s binary, bestowing it with a petite binary size and promising accelerated startup times. <strong>.o files</strong>, or object files, inhabit the realm of intermediate compilation units, wielding the potential to unite and give birth to static libraries or dynamically linked frameworks. Distinguishing between the use cases of static libraries and object files is pivotal for optimising your app&apos;s performance and memory footprint.</p><h3 id="dylib-dynamic-library"><strong>.dylib (Dynamic Library)</strong></h3><p>The world of <strong>.dylib files</strong> beckons dynamic libraries, extending an invitation for their ad hoc arrival at runtime as your app springs into existence. Dynamic libraries usher in a measure of flexibility in code sharing but might nudge your app&apos;s binary size northward. Dynamic libraries traditionally find their calling in system frameworks and shared system components. Handling them with care and vigilance is a requisite, for any slip-up in configuration or inclusion can usher in the Spector of runtime crashes.</p><h3 id="universal-framework"><strong>Universal Framework</strong></h3><p>Universal frameworks, the &quot;Jack of all trades&quot; among formats, don the cloak of versatility. They are a specialised framework format engineered to accommodate multiple architectures and platforms under one sprawling roof. This format performs an invaluable service, simplifying the distribution of cross-platform libraries. <a href="https://dyte.io/ios-video-sdk">Developers</a> can present a single binary, an embodiment of unity, capable of functioning seamlessly across an array of iOS devices and processor architectures.</p><h2 id="swift-package-manager"><strong>Swift Package Manager</strong></h2><p>As a seasoned <a href="https://dyte.io/ios-video-sdk">iOS</a> developer, you&apos;re no stranger to the capabilities wielded by Swift Package Manager (SPM) when it comes to dependency management. Within the labyrinthine corridors of SPM, two pivotal concepts demand your attention: <strong><code>.binaryTarget</code></strong>, <strong><code>.target</code></strong>, and the strategic utilization of <strong><code>linkerSettings</code></strong>.</p><h3 id="binarytarget"><strong><code>.binaryTarget</code></strong></h3><p>Introduced as a breath of fresh air, <strong><code>.binaryTarget</code></strong> assumes the mantle of a feature within Swift Package Manager designed explicitly to streamline the integration of binary dependencies. Binary dependencies are pre-compiled libraries or frameworks presented by third-party sources, emerging as swift and efficient companions for integration. Here&apos;s the lowdown:</p><ul><li><strong>Efficient integration</strong>: With the declaration of a <strong><code>.binaryTarget</code></strong>, Swift Package Manager orchestrates retrieving a pre-compiled binary from a designated source, whether a Git repository or a URL. This streamlined approach expedites integration, ushering inconvenience.</li><li><strong>Platform-agnostic brilliance</strong>: Binary targets shine as platform-agnostic stars, casting their glow across diverse platforms and architectures, including iOS, macOS, and beyond. This trait is especially beneficial for those engaged in cross-platform development endeavors.</li><li><strong>Version control vigilance</strong>: Binary targets, operating in the realm of pre-compiled artifacts, dwell outside the confines of direct version control within your package. Instead, the version or tag of the binary dependency takes center stage within your <strong><code>Package.swift</code></strong> file.</li><li><strong>Swift harmony</strong>: Ensure the selected binary target aligns with your project&apos;s Swift version for harmony and compatibility. A mismatch in Swift versions can sow the seeds of discord.</li></ul><h3 id="target"><strong><code>.target</code></strong></h3><p>In stark contrast to the pragmatic elegance of <strong><code>.binaryTarget</code></strong>, <strong><code>.target</code></strong> emerges as the go-to directive for source-based dependencies. Source-based packages carry their source code, ready to undergo the rites of compilation upon integration into your project. Vital insights into this concept include:</p><ul><li><strong>Source code integration</strong>: Invocation of a <strong><code>.target</code></strong> commands Swift Package Manager to embark on a journey of source code retrieval, cloning the package&apos;s source code and forging it into your project. This process offers the boon of customization, enabling you to mold or modify the package as per your requirements.</li><li><strong>Version control ascendancy</strong>: Source-based packages ascend to prominence as the champions of direct version control within your project&apos;s repository. This translates into the power to wield influence over the package&apos;s code, affording the liberty to make modifications as needed.</li><li><strong>Swift compatibility</strong>: As with binary targets, extending a cordial handshake of compatibility between your project&apos;s Swift version and the chosen source-based package is paramount. Avoidance of mismatched Swift versions can be your shield against compatibility conundrums.</li><li><strong>The web of dependency</strong>: Source-based packages often weave a web of dependencies, crafting a sprawling tapestry of interconnected packages. Swift Package Manager rises to the challenge, orchestrating the management of this intricate dependency graph, ensuring that all required packages partake in the grand symphony of integration.</li></ul><h3 id="linkersettings"><strong><code>linkerSettings</code></strong></h3><p><strong><code>linkerSettings</code></strong>, an entity of significance within the grand configuration of Swift Package Manager, entrusts you with the reins of control over linker flags and settings that govern the orchestration of your targets. Its importance is not to be underestimated:</p><ul><li><strong>Fine-grained command</strong>:  <strong><code>linkerSettings</code></strong> stands as your herald, bearing the mandate of fine-grained control over the linking process. It paves the way for the specification of linker flags, search paths, and sundry settings, exerting a profound influence on how the package melds with your project.</li><li><strong>Integration tailoring</strong>: Instances may arise where a package exhibits a penchant for specific linker flags or settings. Enter <strong><code>linkerSettings</code></strong>; this savior allows you to fashion an integration that gracefully accommodates the package&apos;s unique requirements.</li><li><strong>Mitigating linking conflicts</strong>: In the integration, where multiple packages jostle for space, conflicts on the frontiers of linking can rear their heads. <strong><code>linkerSettings</code></strong> steps in as the peacemaker, offering the means to navigate these conflicts with poise and elegance, ensuring a harmonious integration experience.</li><li><strong>Compatibility crusade</strong>: To preserve the sanctity of your project, heed the call of compatibility. Scrutinize the compatibility of linker flags and settings with your project&apos;s Swift version and platform. Mismatched configurations have the power to disrupt the tranquil flow of runtime.</li></ul><h2 id="the-role-of-checksums-in-spm"><strong>The role of checksums in SPM</strong></h2><p>Checksums, those guardians of integrity and security, play a pivotal role within the domain of Swift Package Manager (SPM), specifically in the realm of package management. They serve as sentinels tasked with verifying the authenticity and integrity of external <a href="https://dyte.io/ios-video-sdk">dependencies</a> before ushering them into your project.</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/03/iOS.webp" class="kg-image" alt="Packaging Libraries in iOS: A Comprehensive Guide" loading="lazy" width="2000" height="1125" srcset="https://dyte.io/blog/content/images/size/w600/2024/03/iOS.webp 600w, https://dyte.io/blog/content/images/size/w1000/2024/03/iOS.webp 1000w, https://dyte.io/blog/content/images/size/w1600/2024/03/iOS.webp 1600w, https://dyte.io/blog/content/images/2024/03/iOS.webp 2000w" sizes="(min-width: 720px) 720px"></figure><ol><li><strong>Package resolution</strong>: Your project&apos;s <strong><code>Package.swift</code></strong> file hosts declarations of dependencies. When summoned, the Swift Package Manager embarks on a mission to retrieve the package manifest, an essential dossier housing information about the package and its version.</li><li><strong>Download and check</strong>: The saga continues with downloading the package&apos;s source code or binary artifact. Simultaneously, Swift Package Manager, armed with diligence, fetches the checksum linked to the package&apos;s version from a trustworthy source, often the package repository.</li><li><strong>Verification</strong>: As the download completes, Swift Package Manager commences an expedition into checksum calculation. The calculated checksum stands face-to-face with its repository-forged counterpart, and only harmony, represented by a perfect match, rings true. A matching checksum signifies that the downloaded package mirrors the precise form and content expected by the package repository.</li></ol><h2 id="mergeable-libraries-new-in-town"><strong>Mergeable libraries (New in Town!)</strong></h2><p>Apple announced Mergeable libraries in WWDC23, the unsung heroes sometimes cloaked in the monikers of &quot;umbrella frameworks&quot; or &quot;universals,&quot; which is the fusion of multiple frameworks or libraries into a singular, cohesive framework. This gives a leaner, meaner, and more efficient application binary. Let&apos;s shine a light on the critical facets of mergeable libraries:</p><h3 id="1-reduction-in-binary-size"><strong>1. Reduction in binary size</strong></h3><p>The most prominent jewel in the crown of mergeable libraries is the substantial reduction in the size of your application binary. By amalgamating multiple frameworks into a singular entity, developers surgically excise redundant code and resources, rendering the binary trim and sleek. This trimness finds its true value in mobile applications, where app size directly influences download times and device storage.</p><h3 id="2-streamlined-maintenance"><strong>2. Streamlined maintenance</strong></h3><p>Mergeable libraries don the mantle of the custodian of dependencies, simplifying the labyrinthine maintenance process. Instead of juggling several individual libraries, each with its own versioning and update cycle, developers are entrusted with the guardianship of a solitary, consolidated library. This harmonisation streamlines the update process and curtails the risk of version conflicts and compatibility conundrums.</p><h3 id="3-improved-loadlaunch-times"><strong>3. Improved load/launch times</strong></h3><p>Diminished binary sizes usher in improved app launch times. With fewer resources to load into memory, the app leaps to life more swiftly, embellishing the user experience with the gift of alacrity. Reduced load times are particularly invaluable in scenarios where instant access to the application is not just a luxury but an expectation.</p><h3 id="4-cross-platform-compatibility"><strong>4. Cross-platform compatibility</strong></h3><p>Mergeable libraries possess the unique capability of accommodating multiple platforms and architectures, transforming them into the darlings of cross-platform development. In the hands of adept developers, a single library can extend its benevolent embrace across iOS, macOS, watchOS, tvOS, and many other platforms, fostering an ecosystem of harmonious coexistence.</p><h2 id="dyld-vs-dyld3-dynamic-linker-in-ios"><strong>Dyld vs. Dyld3: dynamic linker in iOS</strong></h2><p>In the vast landscape of <a href="https://dyte.io/ios-video-sdk">iOS development</a>, two linkers shine brightly: Dyld and its evolutionary offspring, Dyld3. These dynamic linkers perform the critical role of managing the loading and linking of libraries during an app&apos;s launch. For expert iOS developers, a deep understanding of their inner workings is essential.</p><h3 id="dyld"><strong>Dyld</strong></h3><ol><li><strong>Startup performance</strong>: Dyld, a stalwart of <a href="https://dyte.io/ios-video-sdk">iOS</a>, has been meticulously designed to deliver efficient startup performance. It employs an arsenal of optimisations to minimise the time required for loading and linking libraries when an app takes its first breath. This efficiency is paramount in ensuring a seamless user experience.</li><li><strong>Lazy binding</strong>: Dyld employs the ingenious strategy of lazy binding, which defers symbol resolution until the precise moment when a symbol is first utilized. This mechanism trims the startup overhead by avoiding unnecessary work during the initial stages of the app launch.</li><li><strong>Shared caches</strong>: In its quest to enhance startup performance, Dyld harnesses the power of shared caches. These caches store pre-processed libraries, allowing multiple applications to share them. This shared resource optimises resource utilisation and further expedites the launch process.</li></ol><h3 id="dyld3-the-evolutionary-leap"><strong>Dyld3: The evolutionary leap</strong></h3><p>As <a href="https://dyte.io/ios-video-sdk">iOS</a> and macOS continued to evolve, the demands on dynamic linking also grew. This prompted the emergence of Dyld3, representing a substantial leap forward in dynamic linking technology. Dyld3 introduced several key advancements aimed at optimising app performance and resource management.</p><h3 id="key-advancements-in-dyld3"><strong>Key advancements in Dyld3:</strong></h3><ol><li><strong>Reduced memory overhead</strong>: Dyld3 was engineered with a focus on minimising memory overhead. It employs a more efficient data structure for managing loaded libraries, precious in resource-constrained environments such as <a href="https://dyte.io/ios-video-sdk">mobile devices</a>.</li><li><strong>Parallel loading</strong>: Dyld3 introduces the paradigm of parallel loading, enabling it to load multiple libraries concurrently. This parallelism takes full advantage of multi-core processors, resulting in faster app launch times.</li><li><strong>On-demand loading</strong>: Dyld3 adopts the strategy of on-demand loading, loading only the portions of libraries that are required at runtime. This &quot;just-in-time&quot; approach conserves memory and accelerates startup times.</li><li><strong>Improved symbol binding</strong>: Enhancing symbol binding performance, ensuring that symbols are resolved efficiently as an app runs. This is crucial for maintaining smooth app performance during execution.</li></ol><p>Creating and distributing an <a href="https://dyte.io/ios-video-sdk">iOS SDK</a> demands careful consideration of various elements. To embark on this journey, you must make critical decisions regarding the type of library (static or dynamic) that best suits your needs. </p><p>Dependency management tools like CocoaPods and Swift Package Manager (SPM) are crucial in linking and integrating your <a href="https://dyte.io/ios-video-sdk">SDK</a> into other projects. Understanding library formats, such as frameworks and xcframeworks, is essential for packaging your code effectively. Don&apos;t forget the importance of checksums in ensuring the security of your SDK. </p><p>Additionally, exploring the advantages of mergeable libraries can help reduce app size and simplify maintenance. Lastly, delve into the world of dynamic linkers like Dyld and Dyld3 to optimise app startup performance and memory usage. By mastering these components, you&apos;ll be well-prepared to create and distribute <a href="https://dyte.io/ios-video-sdk" rel="noreferrer">iOS SDKs</a> that enhance the efficiency and reliability of your development projects.</p><p><em>If you haven&apos;t heard about Dyte yet, head over to </em><a href="https://dyte.io/"><em>dyte.io</em></a><em> to learn how we are revolutionizing communication through our </em><a href="https://dyte.io/video-sdk"><em>SDKs</em></a><em> and libraries and how you can </em><a href="https://accounts.dyte.in/auth/register" rel="noreferrer noopener"><em>get started</em></a><em> quickly on your 10,000 free minutes, which renew every month. You can reach us at </em><a href="mailto:support@dyte.io" rel="noreferrer noopener"><em>support@dyte.io</em></a><em> or ask our </em><a href="https://community.dyte.io/" rel="noreferrer noopener"><em>developer community</em></a><em> if you have any questions.</em></p>]]></content:encoded></item><item><title><![CDATA[Render Video Tracks From WebRTC Using Flutter PlatformViews]]></title><description><![CDATA[The blog explores how to render WebRTC video tracks in Flutter using PlatformViews and solves an interesting challenge.]]></description><link>https://dyte.io/blog/render-video-tracks-webrtc-flutter-platformviews/</link><guid isPermaLink="false">64f094a9d0c964000195239b</guid><category><![CDATA[Engineering]]></category><dc:creator><![CDATA[Aman Kumar]]></dc:creator><pubDate>Mon, 18 Nov 2024 16:22:00 GMT</pubDate><media:content url="https://dyte.io/blog/content/images/2023/08/Render-Video-Tracks-From-WebRTC-Using-Flutter-PlatformViews--1-.png" medium="image"/><content:encoded><![CDATA[<img src="https://dyte.io/blog/content/images/2023/08/Render-Video-Tracks-From-WebRTC-Using-Flutter-PlatformViews--1-.png" alt="Render Video Tracks From WebRTC Using Flutter PlatformViews"><p>Flutter, unlike native Android, iOS, or even React Native apps, does not use system drawing primitives for rendering your application. This blog will give you all the theory of how, what, and why of using Flutter PlatformViews and how WebRTC(lib) VideoTracks can be rendered in your Flutter applications using PlatformViews.</p><h1 id="background">Background</h1><p>Before we start our discussion on how <code>PlatformViews</code> are rendered, let&apos;s discuss how Flutter generally draws its UI.</p><p>Flutter paints its UI from scratch every time using its graphics engine, <code>Impeller</code>. Flutter draws every pixel on the screen, giving developers a high degree of control over the UI.<br><br>Flutter uses 3 threads to render its UIs.</p><ul><li>UI thread</li><li>Platform thread</li><li>Raster thread</li></ul><p>The UI thread is where your Dart code runs. The Platform thread is responsible for creating widgets, calculating the layout, and other layout-related tasks. Once this is done, the layout tree is delegated to the Raster thread, which converts the tree into actual pixels on the screen.</p><pre><code class="language-dart">// Dart code runs on the UI thread
void main() {
  runApp(MyApp());
}
</code></pre><p>Let&#x2019;s see how this fits with our use case of making video available from the native side to Flutter.</p><h1 id="webrtc-connection"><strong>WebRTC connection</strong></h1><p>There are multiple ways through which you can render your <code>libwebrtc</code> video tracks on Flutter, like for Android, you can</p><ol><li>Use <code>SurfaceTextureRenderer</code> to render the VideoTrack, make the Texture available to Flutter using TextureRegistry API, and <a href="https://api.flutter.dev/flutter/widgets/Texture-class.html">render the Texture in Flutter</a>.</li><li>Use <code>SurfaceViewRenderer</code> to render the VideoTrack as an AndroidView, and use PlatformViews to use the native view in Flutter.</li></ol><p>In this blog are going to talk about how the 2nd approach works under the hood. Specifically, what and why of <code>PlatformViews</code>.</p><h1 id="what-are-flutter-platformviews">What are Flutter PlatformViews?</h1><p><code>PlatformViews</code> are used when we want to use native views as a Flutter widget. There are two ways we can create PlatformView in Flutter.</p><h2 id="1-hybrid-composition">1. <strong>Hybrid composition</strong></h2><p>In Hybrid composition, Flutter creates a special type of view and asks Android/iOS to create a corresponding view and embeds the native view into its own widget tree.</p><p>Below is how you can implement <code>PlatformViews</code> by using the Hybrid composition method -</p><pre><code class="language-dart">Widget build(BuildContext context) {
  // This is used in the platform side to register the view.
  const String viewType = &apos;&lt;platform-view-type&gt;&apos;;
  // Pass parameters to the platform side.
  const Map&lt;String, dynamic&gt; creationParams = &lt;String, dynamic&gt;{};

  return PlatformViewLink(
    viewType: viewType,
    surfaceFactory:
        (context, controller) {
      return AndroidViewSurface(
        controller: controller as AndroidViewController,
        gestureRecognizers: const &lt;Factory&lt;OneSequenceGestureRecognizer&gt;&gt;{},
        hitTestBehavior: PlatformViewHitTestBehavior.opaque,
      );
    },
    onCreatePlatformView: (params) {
      return PlatformViewsService.initSurfaceAndroidView(
        id: params.id,
        viewType: viewType,
        layoutDirection: TextDirection.ltr,
        creationParams: creationParams,
        creationParamsCodec: const StandardMessageCodec(),
        onFocus: () {
          params.onFocusChanged(true);
        },
      )
        ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
        ..create();
    },
  );
}
</code></pre><p>Here&apos;s a breakdown of what each part does:</p><p><strong>1. Variables initialization</strong>:</p><ul><li><code>viewType</code> is a unique identifier for the native view. This should match the identifier used in the native Android code.</li><li><code>creationParams</code> is a map that can hold any parameters you want to pass to the native view for its initialization.</li></ul><p><strong>2. PlatformViewLink widget</strong>:</p><ul><li>This widget serves as a bridge between the Flutter framework and the native view. It takes in the <code>viewType</code> and two factory functions: <code>surfaceFactory</code> and <code>onCreatePlatformView</code>.</li></ul><p><strong>3. surfaceFactory function</strong>:</p><ul><li>This function returns an <code>AndroidViewSurface</code> widget, which is responsible for displaying the native Android view.</li><li>It takes a <code>controller</code> argument, an instance of <code>AndroidViewController</code>, which is used to control the native Android view.</li><li><code>gestureRecognizers</code> specifies which gestures the native view should consume. In this example, it&apos;s set to an empty set, meaning the native view won&apos;t consume any gestures.</li><li><code>hitTestBehavior</code> is set to <code>PlatformViewHitTestBehavior.opaque</code>, which means the native view will block touches to underlying Flutter widgets.</li></ul><p><strong>4. onCreatePlatformView function</strong>:</p><ul><li>This function initializes the native Android view and returns an instance of it.</li><li>It uses <code>PlatformViewsService.initSurfaceAndroidView</code> to initialize the native view, passing in various parameters like <code>id</code>, <code>viewType</code>, and <code>creationParams</code>.</li><li>An <code>onFocus</code> callback is also defined, which is triggered when the native view gains focus.</li><li>A listener is added to notify when the Android view is created successfully.</li></ul><p>By using this <code>build</code> method in your Flutter app, you can seamlessly integrate a native Android view into your Flutter widget tree.</p><h2 id="2-virtual-display-mode">2. <strong>Virtual display mode</strong></h2><p>In virtual display mode, Flutter creates an offscreen <code>android.view.Surface</code>, which is a drawing surface to render graphics. It&#x2019;s like a blank canvas. Then the native view renders its content onto this surface. Flutter then takes the content of that surface and uses it as a texture within its rendering pipeline. This texture is drawn where the platform view should appear.</p><p><code>iOS</code> only supports Hybrid composition.</p><p>Below is how you can implement virtual display mode.</p><pre><code class="language-dart">Widget build(BuildContext context) {
  // This is used in the platform side to register the view.
  const String viewType = &apos;&lt;platform-view-type&gt;&apos;;
  // Pass parameters to the platform side.
  final Map&lt;String, dynamic&gt; creationParams = &lt;String, dynamic&gt;{};

  return AndroidView(
    viewType: viewType,
    layoutDirection: TextDirection.ltr,
    creationParams: creationParams,
    creationParamsCodec: const StandardMessageCodec(),
  );
}
</code></pre><h3 id="androidview-widget"><strong>AndroidView widget</strong>:</h3><ul><li>This widget is used to display the native Android view within the Flutter app.</li><li>It takes several parameters:</li><li><code>viewType</code> specifies the type of Android view to create.</li><li><code>layoutDirection</code> sets the text direction, which is left-to-right (<code>TextDirection.ltr</code>) in this example.</li><li><code>creationParams</code> are the initial parameters to pass to the Android view.</li><li><code>creationParamsCodec</code> specifies how to encode <code>creationParams</code>. The <code>StandardMessageCodec</code> is used for encoding basic types like strings, numbers, and collections.</li></ul><h3 id="native-implementation">Native implementation</h3><p>On the native side, we need to create the view that we need to serve, create a factory, and register it so that the native platform can create views whenever it is asked by Flutter.</p><p>To do this in Android we do -</p><pre><code class="language-kotlin">class NativeViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
    override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
        val creationParams = args as Map&lt;String?, Any?&gt;?
        return NativeView(context, viewId, creationParams)
    }
}
</code></pre><p>Next, we have to register this factory in the flutter engine -</p><pre><code class="language-kotlin">binding.platformViewRegistry
                .registerViewFactory(&quot;&lt;platform-view-type&gt;&quot;, NativeViewFactory())
</code></pre><h3 id="performance-impact">Performance Impact</h3><p>Before Android 10, hybrid composition required a lot of to and fro between main memory and GPU to compose native views with flutter widgets, after Android 10, copying is done only once, which makes it very performant.</p><p><strong>Before Android 10</strong></p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2023/08/Flutter-Platviews-I.png" class="kg-image" alt="Render Video Tracks From WebRTC Using Flutter PlatformViews" loading="lazy" width="1920" height="1047" srcset="https://dyte.io/blog/content/images/size/w600/2023/08/Flutter-Platviews-I.png 600w, https://dyte.io/blog/content/images/size/w1000/2023/08/Flutter-Platviews-I.png 1000w, https://dyte.io/blog/content/images/size/w1600/2023/08/Flutter-Platviews-I.png 1600w, https://dyte.io/blog/content/images/2023/08/Flutter-Platviews-I.png 1920w" sizes="(min-width: 720px) 720px"></figure><p><strong>After Android 10:</strong></p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2023/09/Flutter-Platfoem-Views-II.png" class="kg-image" alt="Render Video Tracks From WebRTC Using Flutter PlatformViews" loading="lazy" width="1920" height="1047" srcset="https://dyte.io/blog/content/images/size/w600/2023/09/Flutter-Platfoem-Views-II.png 600w, https://dyte.io/blog/content/images/size/w1000/2023/09/Flutter-Platfoem-Views-II.png 1000w, https://dyte.io/blog/content/images/size/w1600/2023/09/Flutter-Platfoem-Views-II.png 1600w, https://dyte.io/blog/content/images/2023/09/Flutter-Platfoem-Views-II.png 1920w" sizes="(min-width: 720px) 720px"></figure><p>Here is a small table that can help you decide which method to choose:</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2023/08/Screenshot-2023-08-31-at-9.50.13-PM.png" class="kg-image" alt="Render Video Tracks From WebRTC Using Flutter PlatformViews" loading="lazy" width="1496" height="924" srcset="https://dyte.io/blog/content/images/size/w600/2023/08/Screenshot-2023-08-31-at-9.50.13-PM.png 600w, https://dyte.io/blog/content/images/size/w1000/2023/08/Screenshot-2023-08-31-at-9.50.13-PM.png 1000w, https://dyte.io/blog/content/images/2023/08/Screenshot-2023-08-31-at-9.50.13-PM.png 1496w" sizes="(min-width: 720px) 720px"></figure><h1 id="what-did-we-choose-at-dyte"><strong>What did we choose at Dyte?</strong></h1><p>At Dyte, our Flutter SDK is a wrapper over the Android and iOS SDKs, and we don&apos;t load any WebRTC video/audio streams directly. We use <a href="https://docs.dyte.io/android-core/local-user/introduction#get-local-user-video-view"><code>VideoView</code></a> from our Android SDK to display the video, which is then served as a PlatformView. We prefer a hybrid composition due to its performance benefits.</p><h2 id="challenges">Challenges</h2><p>We have two Flutter SDKs: dyte core and dyte uikit. Dyte core has no knowledge of where the <code>VideoView</code> (which is a <code>PlatformView</code>) is going to be used. It might be possible that the <code>VideoView</code> for a single participant is used on multiple screens. Let&#x2019;s call this <code>VideoView</code> as <code>FlutterVideoView</code> since <code>VideoView</code> is also present in our <a href="https://docs.dyte.io/android-core">Android SDK</a> and is a crucial part of this discussion.</p><p>We wrote a <code>PlatformView</code>, which serves <code>VideoView</code>. Let&#x2019;s call it <code>AndroidVideoView</code>. Dyte core is dependent on our core mobile SDKs and <code>VideoView</code> is a <code>View</code> in Android.</p><blockquote>We know that one <code>View</code> can be part of only one <code>ViewGroup</code> at any moment of time.</blockquote><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2023/08/Flutter-Platform-III.png" class="kg-image" alt="Render Video Tracks From WebRTC Using Flutter PlatformViews" loading="lazy" width="1920" height="1047" srcset="https://dyte.io/blog/content/images/size/w600/2023/08/Flutter-Platform-III.png 600w, https://dyte.io/blog/content/images/size/w1000/2023/08/Flutter-Platform-III.png 1000w, https://dyte.io/blog/content/images/size/w1600/2023/08/Flutter-Platform-III.png 1600w, https://dyte.io/blog/content/images/2023/08/Flutter-Platform-III.png 1920w" sizes="(min-width: 720px) 720px"></figure><blockquote>Let&#x2019;s keep this in the back of the mind that Flutter has no direct support to detect the visibility of a widget.</blockquote><p>The problem we were facing was when we used <code>FlutterVideoView</code> for the same participant on two different screens. For the sake of simplicity, let&#x2019;s call it screen A and screen B. Now when the user navigates from screen A to screen B everything was working smoothly as screen B was creating a fresh <code>FlutterVideoView</code>, which internally created a new instance of <code>AndroidVideoView</code>, so the <code>VideoView</code> is now attached to a new <code>ViewGroup</code>.</p><p>The problem was when the user navigated back from screen B to screen A, there was no way to detect if <code>FlutterVideoView</code> widget had gone from background to foreground so we could call <code>render()</code> on the <code>AndroidVideoView</code>, which is a PlatformView in our Flutter SDK. <code>render()</code> method calls the <code>render()</code> of <code>VideoView</code>, which internally takes care of removing the view from the old <code>ViewGroup</code> and renders the video track.</p><p>On the Dyte UI kit, we can get the lifecycle methods if the screen has been changed, but that would not solve the actual problem of the view not getting refreshed on its own. To solve this, we used a community plugin, <a href="https://pub.dev/packages/visibility_detector"><code>visibility_detector</code></a> in Dyte core flutter SDK. It gave us a widget that had callbacks that get triggered when its child widget&#x2019;s visibility is changed.</p><p>Now the second part of the problem was to find out the <code>PlatformView</code>, which was associated with the widget and call <code>render()</code> on it. To tackle this we had to retrieve the <code>View</code> from the Flutter engine since all the <code>PlatformView</code>s are created by the Flutter engine itself.</p><p>To tackle this, we cached the <code>FlutterEngine</code> using <a href="https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngineCache.html"><code>FlutterEngineCache</code></a>. This allowed us to access the <code>PlatformView</code> by its <code>viewId</code>.</p><pre><code class="language-dart">FlutterEngineCache.getInstance().put(&quot;DyteFlutterEngine&quot;, flutterPluginBinding.flutterEngine)
</code></pre><p>Through the engine, we can access the <code>PlatformView</code> with the help of <code>viewId</code>, which is assigned to every <code>PlatformView</code> when it is created.</p><pre><code class="language-dart">onCreatePlatformView: (params) {
        return PlatformViewsService.initSurfaceAndroidView(
         id: params.id,
         viewType: viewType,
         layoutDirection: TextDirection.ltr,
         creationParams: creationParams,
         reationParamsCodec: const StandardMessageCodec(),
         onFocus: () {
		     params.onFocusChanged(true);
          },
        )
				// Here we assign the view id.
          ..addOnPlatformViewCreatedListener(setNativeViewId)
          ..create();
      },
</code></pre><p>To access the native view, we did</p><pre><code class="language-kotlin">val targetView = FlutterEngineCache.getInstance()[&quot;DyteFlutterEngine&quot;]!!
	            .platformViewsController.getPlatformViewById(viewId) as AndroidVideoView?
</code></pre><p>Finally, we call the <code>render()</code> method on this view.</p><pre><code class="language-kotlin">targetView?.render()
</code></pre><p>After all these steps, we clear the cached <code>FlutterEngine</code> to free up the memory.  This helped us solve a critical issue in our Flutter video SDK.</p><h1 id="conclusion">Conclusion</h1><p>This concludes our take on Flutter PlatformViews and how we use it at <code>Dyte</code>. Video is the most essential part of our <a href="https://docs.dyte.io/flutter">Flutter SDK</a>, and the Flutter ecosystem has provided us with great utilities to deal with it.</p><p>On top of it, we have made it seamless for you to have feature-rich <a href="https://dyte.io/video-sdk">audio video conferencing</a> in your app with the least hassle. Stay tuned for more engineering blogs, we talk about behind the scenes, WebRTC, and cool things that can be built upon Dyte. Check out our blog <a href="https://dyte.io/blog">here</a>.</p><p>If you have any thoughts or feedback feel free to connect with me on <a href="https://www.linkedin.com/in/thisisamank/">LinkedIn</a> or <a href="https://www.linkedin.com/in/thisisamank/">X(Twitter)</a>.</p>]]></content:encoded></item><item><title><![CDATA[Kotlin MPP: Basics]]></title><description><![CDATA[Learn about Kotlin Multiplatform's vast ecosystem, enabling you to build cross-platform solutions from a single, shared codebase.]]></description><link>https://dyte.io/blog/kotlin-mpp-basics/</link><guid isPermaLink="false">65ee072dfc3f7000017e9485</guid><category><![CDATA[Engineering]]></category><dc:creator><![CDATA[Harsh Shandilya]]></dc:creator><pubDate>Fri, 15 Nov 2024 09:54:00 GMT</pubDate><media:content url="https://dyte.io/blog/content/images/2024/03/Kotlin-Multiplatform-2.png" medium="image"/><content:encoded><![CDATA[<img src="https://dyte.io/blog/content/images/2024/03/Kotlin-Multiplatform-2.png" alt="Kotlin MPP: Basics"><p>When we set out to build our mobile SDKs at Dyte, we decided early on that we wanted to reuse as much code between platforms as possible. It would save us a lot of time and effort as a small team not to have to implement everything twice &#x2014; once for iOS and then again for Android.</p><h2 id="taking-cues-from-dropbox">Taking cues from Dropbox</h2><p>There simply weren&apos;t many options available to achieve what we had set our minds to. We could write C++ (or Rust) and then use FFI bindings from them to iOS and Android, but the overhead of learning a completely new and unfamiliar language made it quite a risky option. Dropbox had been a <a href="https://oleb.net/blog/2014/05/how-dropbox-uses-cplusplus-cross-platform-development/">big champion</a> of this strategy, and it worked for them for a long time.</p><p>Still, ultimately, the friction of forcing mobile developers to write C++ ended up with them <a href="https://dropbox.tech/mobile/the-not-so-hidden-cost-of-sharing-code-between-ios-and-android">ditching the idea</a> and just opting to build stuff twice in the platform&apos;s respective native languages.</p><h2 id="the-blended-alternative-of-kotlin-mpp">The blended alternative of Kotlin MPP</h2><p>Kotlin offered us an optimally blended alternative way to do this. It was a language already familiar to our Android developers and was relatively easy for iOS engineers to pick up due to its syntactic familiarity with Swift.</p><p>It is easier to write than C++, and the excellent Kotlin Multiplatform toolchain ensured we were not taking on avoidable FFI overhead by being able to compile our code down to platform-native formats.</p><p>Kotlin Multiplatform was announced at KotlinConf 2017 under the name Kotlin Multiplatform Projects (MPP) and initially supported JVM, Native, and JavaScript. It enabled the Kotlin toolchain to compile executables and libraries for different platforms from a single, shared Kotlin codebase. The powerful <code>expect</code>/<code>actual</code> system of bridging platform-specific APIs into standard code allows for common business logic to be written once but still be able to lean into the target platform for specific things.</p><h2 id="leveraging-bidirectional-interoperability">Leveraging bidirectional interoperability</h2><p>Unlike other cross-platform solutions, which tend to invent everything themselves and treat native interoperability as an afterthought, Kotlin strongly focuses on bidirectional interoperability. Kotlin Multiplatform can consume dependencies written in the platform&apos;s target language, expose them within Kotlin, and compile them to the same binary format as the target language.</p><p>For example, Kotlin code for Apple platforms can consume <a href="https://cocoapods.org/">Cocoapods</a> libraries through Kotlin&apos;s first-party Gradle integration, and developers can use the libraries like any other Kotlin code. This also goes in the other direction &#x2014; the Kotlin Gradle plugin enables building Kotlin libraries into <a href="https://developer.apple.com/documentation/xcode/creating-a-multi-platform-binary-framework-bundle">XCFramework bundles</a> that can be imported into Xcode projects.</p><p>At <a href="https://dyte.io/">Dyte</a>, we leverage Kotlin Multiplatform&apos;s vast ecosystem to build all our mobile SDKs from a single shared codebase written entirely in Kotlin. It enables us to deliver a consistent API and feature set across iOS and Android without compromising quality for either platform.</p><p>Its powerful publishing tooling ensures that our usage of Kotlin Multiplatform remains transparent to our clients. Clients using our SDK on Android see a regular <a href="https://developer.android.com/studio/projects/android-library#aar-contents">AAR file</a>; on iOS, they get an <a href="https://developer.apple.com/documentation/xcode/creating-a-multi-platform-binary-framework-bundle">XCFramework</a>. Thanks to this, our SDKs integrate painlessly into existing mobile codebases like any other dependency.</p><h2 id="final-thoughts">Final thoughts</h2><p>In conclusion, Kotlin MPP is an excellent tool for efficient cross-platform development. Allowing for shared code between mobile platforms and prioritizing bidirectional interoperability makes delivering a consistent experience across platforms easier without compromising quality. At Dyte, we&apos;ve seen firsthand the benefits of Kotlin Multiplatform, and we&apos;re excited to see where it goes in the future.</p><p>We hope you found this post informative and engaging. If you have any thoughts or feedback, please reach out to us on&#xA0;<a href="https://www.linkedin.com/company/dyteio/mycompany/" rel="noreferrer noopener">LinkedIn</a>&#xA0;and&#xA0;<a href="https://twitter.com/dyte_io" rel="noreferrer noopener">Twitter</a>. Stay tuned for more related blog posts in the future!</p><p><em>If you haven&apos;t heard about Dyte yet, head over to&#xA0;</em><a href="https://dyte.io/"><em>dyte.io</em></a><em>&#xA0;to learn how we are revolutionizing communication through our SDKs and libraries and how you can&#xA0;</em><a href="https://accounts.dyte.in/auth/register" rel="noreferrer noopener"><em>get started</em></a><em>&#xA0;quickly on your 10,000 free minutes, which renew every month. You can reach us at&#xA0;</em><a href="mailto:support@dyte.io"><em>support@dyte.io</em></a><em>&#xA0;or ask our&#xA0;</em><a href="https://community.dyte.io/"><em>developer community</em></a><em>.</em></p>]]></content:encoded></item><item><title><![CDATA[Announcing End-to-End Encryption in Dyte]]></title><description><![CDATA[Explore the mechanisms behind end-to-end encryption for audio/video calls, focusing on its standards, implementation, and impact on performance.]]></description><link>https://dyte.io/blog/end-to-end-encryption/</link><guid isPermaLink="false">660c35c8fc3f7000017e96c0</guid><category><![CDATA[Announcement]]></category><dc:creator><![CDATA[Palash Golecha]]></dc:creator><pubDate>Fri, 15 Nov 2024 08:07:00 GMT</pubDate><media:content url="https://dyte.io/blog/content/images/2024/04/end-to-end-encryption--header.png" medium="image"/><content:encoded><![CDATA[<img src="https://dyte.io/blog/content/images/2024/04/end-to-end-encryption--header.png" alt="Announcing End-to-End Encryption in Dyte"><p><strong>True</strong> end-to-end encryption for audio and video calls is in beta in Dyte SDKs!</p><p>In this blog, we&apos;ll explore the mechanisms behind end-to-end encryption (E2EE) in Web Real-Time Communication (WebRTC), focusing on its standards, implementation strategies, impact on performance, and how it works within our SDK. </p><h2 id="inbuilt-security">Inbuilt security</h2><p><a href="https://dyte.io/blog/webrtc/" rel="noreferrer">WebRTC</a> communication is already encrypted. It uses DTLS combined with SRTP to secure both data and media communications.</p><p>SRTP is an extension of the Real-time Transport Protocol (RTP) to deliver audio and video over the Internet. While RTP handles the delivery, it lacks built-in security features, which is where SRTP comes into play.</p><h3 id="key-features-of-srtp"><strong>Key Features of SRTP:</strong></h3><ul><li><strong>Encryption:</strong> SRTP encrypts the payload of RTP packets, which contains the actual media data (voice, video, etc.), using symmetric encryption algorithms (AES). This ensures that the content of the communication cannot be easily eavesdropped or intercepted by unauthorized parties.</li><li><strong>Message Authentication:</strong> SRTP provides a mechanism to verify the authenticity of messages, ensuring that the data has not been tampered with in transit. This is typically achieved using a Message Authentication Code (MAC), a small piece of information, or a tag derived from the packet content and a secret key.</li><li><strong>Replay Protection:</strong> SRTP implements replay protection to ensure that attackers cannot capture and re-send packets in an attempt to disrupt the communication. This is done by keeping track of the sequence numbers of packets and rejecting any that are out of order or duplicated.</li><li><strong>Integrity Protection:</strong> SRTP also ensures the integrity of the data transmitted using MACs, confirming that it has not been altered from its original form during transit.</li></ul><h2 id="the-man-in-the-middle-sfu">The Man-in-the-middle: SFU</h2><p>While WebRTC was designed as a peer-to-peer protocol, most common implementations involve a centralized server that routes media to different parties. WebRTC connections are made from Client &#x2192; SFU and then SFU &#x2192; Client, which means all the inbuilt encryptions stop at the server. Data is decrypted on the server and re-encrypted.</p><p>While being secure in transit is acceptable in most security threat models, there are use cases where you want mathematical guarantees against tampering or eavesdropping.</p><h3 id="implementing-end-to-end-encryption">Implementing End-to-End Encryption</h3><p>As video encoding is lossy, ideally, you would want to apply encryption to the encoded frames before they are transmitted (RTP packetizer).</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/04/end-to-end-encryption--asset-1.png" class="kg-image" alt="Announcing End-to-End Encryption in Dyte" loading="lazy" width="1920" height="430" srcset="https://dyte.io/blog/content/images/size/w600/2024/04/end-to-end-encryption--asset-1.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/04/end-to-end-encryption--asset-1.png 1000w, https://dyte.io/blog/content/images/size/w1600/2024/04/end-to-end-encryption--asset-1.png 1600w, https://dyte.io/blog/content/images/2024/04/end-to-end-encryption--asset-1.png 1920w" sizes="(min-width: 720px) 720px"></figure><p>Now, there are two competing standards on how to do this on web browsers:</p><ul><li><strong>Insertable Streams API</strong> - Introduced in 2020, supported only on Chromium browsers. This is a more general-purpose API that not only allows modification post-encoding and pre-RTP packetization but also allows you to modify frames pre-encoding.</li><li><strong>RTCRtpScriptTransform - </strong>The current standard, not supported by Chromium but supported by Firefox and Safari, is much more limited and designed for cases like end-to-end encryption.</li></ul><p>The good news is that since they both come at the same stage of the pipeline, they are interoperable, i.e., media encrypted using Insertable Streams can be decrypted using RTCRtpScriptTransform.</p><p>Also, since these encryption steps would be computationally heavy, we don&apos;t want to do them on the main thread. We will use Web Workers to offload the encryption/decryption to a different thread.</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/04/end-to-end-encryption--asset-2.png" class="kg-image" alt="Announcing End-to-End Encryption in Dyte" loading="lazy" width="1920" height="724" srcset="https://dyte.io/blog/content/images/size/w600/2024/04/end-to-end-encryption--asset-2.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/04/end-to-end-encryption--asset-2.png 1000w, https://dyte.io/blog/content/images/size/w1600/2024/04/end-to-end-encryption--asset-2.png 1600w, https://dyte.io/blog/content/images/2024/04/end-to-end-encryption--asset-2.png 1920w" sizes="(min-width: 720px) 720px"></figure><h2 id="under-the-hood"><strong>Under the hood</strong></h2><p>Now, we have a place where we can encrypt/decrypt media, but what about the actual encryption process? Technically, you can encrypt it using XORing with a static bit or something naive, but that wouldn&apos;t be secure.</p><p>We chose AES-GCM to encrypt the media frames/samples. LiveKit&apos;s implementation of the same feature inspires our encryption algorithm implementation.</p><h3 id="iv-generation"><strong>IV Generation</strong></h3><p>The IV is used in AES-GCM encryption to provide uniqueness to the encryption process. This ensures that the same payload (e.g., video frame) encrypted multiple times with the same key will result in different ciphertexts. For IV, you just need to make sure that an adversary cannot predict the IV in advance, and for that, we use a combination of time and WebRTC metadata around the stream, which guarantees this to be unique.</p><h3 id="key-derivation-and-key-ratcheting"><strong>Key Derivation and Key Ratcheting</strong></h3><p>If your app users set the encryption key as &quot;12345678,&quot; you don&apos;t want AES to use this weak key directly. PBKDF2 puts the password and the salt through a&#xA0;<a href="https://open.oregonstate.education/cryptographyOEfirst/chapter/chapter-6-pseudorandom-functions/">pseudo-random function</a>&#xA0;a set number of times, according to the value for iteration count. The final output is a strong key. Therefore, we use PBKDF2 to derive strong keys from weak keys.</p><p>The same PBKDF2 mechanism can support key ratcheting, which involves periodically updating the encryption keys used in a communication session. This ensures that the compromise of one key does not compromise past or future communications.</p><ul><li>The current encryption key is used to derive a new key at regular intervals or based on specific conditions (e.g., the number of messages sent).</li><li>The new key replaces the old key for subsequent encryptions, effectively &quot;ratcheting&quot; forward the key material.</li><li>Participants in the communication must synchronize the ratcheting process to ensure they can decrypt received messages with the correct key.</li></ul><p><strong>Encrypting the frame</strong></p><p>The media frame payload sometimes carries metadata that the SFU requires to function, such as keyframes; therefore, part of the RTP payload must be kept unencrypted.</p><p>This differs for each codec (VP8/VP9/OPUS) and each frame type. Dyte SDK provides end-to-end encryption support for all the codecs we support &#x2014; VP8 and VP9 for video and OPUS for audio.</p><h3 id="enable-end-to-end-encryption-in-your-dyte-setups"><strong>Enable end-to-end encryption in your Dyte setups</strong></h3><p>We are rolling this out gradually, and therefore, you will need to contact <a href="mailto:support@dyte.io">support@dyte.io</a> to have this enabled.</p><p>However, once this is enabled, the integration is relatively straightforward.</p><p>Let&apos;s first see how a typical Dyte SDK initialization works.</p><pre><code class="language-jsx">import DyteClient from &apos;@dytesdk/web-core&apos;;

const meeting = await DyteClient.init({
      authToken,
});

// use meeting object
</code></pre><p>To implement end-to-end encryption,</p><pre><code class="language-tsx">import DyteClient from &quot;@dytesdk/web-core&quot;;
import DyteE2EEManager from &quot;@dytesdk/web-core/modules/e2ee&quot;;

const sharedKeyProvider = new DyteE2EEManager.SharedKeyProvider();
sharedKeyProvider.setKey(&quot;meeting-password&quot;);

const e2eeManager = new DyteE2EEManager({ keyProvider: sharedKeyProvider });

const meeting = await DyteClient.init({
  authToken,
  modules: {
	  e2ee: {
		  enabled: true,
		  manager: e2eeManager
	  }
  }
});
</code></pre><p>The above example uses a shared key provider, which, in simple words, is a single key that is used for all encryption of all participant&apos;s media. You can also set a different key per participant using <code>DyteE2EEManager.ParticipantKeyProvider();</code> but you will have to coordinate passing the correct key on every participant join.</p><p>The <em>key</em> takeaway is that you handle the movement of keys, ensuring all participants use the correct key. This key should ideally be transported outside of Dyte-provided communication channels and your own trusted communication channels. Dyte will handle the encryption and media delivery.</p><p><strong>Can I use the X feature while end-to-end encryption is enabled?</strong></p><p>Generally, all features should be available except Cloud Recording/AI/ Transcription features when end-to-end encryption is enabled (since we can&apos;t decrypt media on our servers).</p><p><strong>Are chat, data track, and plugins also end-to-end encrypted?</strong></p><p>Not right now, but this should be available in the (very) near future.</p><h2 id="final-thoughts">Final thoughts</h2><p>As we adopt end-to-end encryption, we take a big step towards better privacy and security in our digital lives. It&apos;s about ensuring our conversations stay private, reinforcing that our digital spaces should be safe for everyone. Moving forward with this technology means we&apos;re all playing a part in protecting our online communications.</p><p>Reach out to <a href="mailto:support@dyte.io">support@dyte.io</a> to experiment and use this feature.</p><p>If you have any thoughts or feedback, please reach out to us on <a href="https://www.linkedin.com/company/dyteio/mycompany/" rel="noreferrer noopener">LinkedIn</a> and <a href="https://twitter.com/dyte_io" rel="noreferrer noopener">Twitter</a>. Stay tuned for more related blog posts in the future!</p><p><em>Get better insights on leveraging Dyte&#x2019;s technology and discover how it can revolutionize your app&#x2019;s communication capabilities with its&#xA0;</em><a href="https://dyte.io/video-sdk" rel="noopener noreferrer"><em>SDKs</em></a><em>.</em></p>]]></content:encoded></item><item><title><![CDATA[Launching New Media Regions]]></title><description><![CDATA[Dyte is launching additional media regions that users can connect to and achieve lower latency, improving the experience for everyone connected.]]></description><link>https://dyte.io/blog/new-media-regions/</link><guid isPermaLink="false">66228323fc3f7000017e98e9</guid><category><![CDATA[Announcement]]></category><dc:creator><![CDATA[Ninad Pundalik]]></dc:creator><pubDate>Thu, 14 Nov 2024 15:13:00 GMT</pubDate><media:content url="https://dyte.io/blog/content/images/2024/04/New_Media_Regions--Header.png" medium="image"/><content:encoded><![CDATA[<img src="https://dyte.io/blog/content/images/2024/04/New_Media_Regions--Header.png" alt="Launching New Media Regions"><p>Dyte provides tools to developers and organizations for integrating meetings, webinars, and live streams on mobile and web apps that are used globally. Live audio and video experiences are affected significantly by the time it takes to transmit data from one participant to another, i.e., latency.</p><p>To support our globally increasing base of customers, Dyte is launching additional regions that users can connect to and achieve lower latency, drastically improving the experience of everyone connected.</p><p>Previously, Dyte&#x2019;s calls were serviced globally from four regions: North America - East, India, Singapore, and Europe - Frankfurt.</p><p>Starting today, we are adding four more media regions,</p><ul><li>North America - West</li><li>South America</li><li>Africa</li><li>Europe - London</li></ul><p>bringing Dyte&#x2019;s presence to eight regions across various parts of the globe.</p><p>Meeting sessions are automatically routed to the closest region when the first user joins, and starting today, these new media regions will automatically be included in the routing process. Dyte also allows developers to choose a region for a particular meeting explicitly, and Dyte&#x2019;s APIs and associated documentation will be updated shortly to support this additional set of regions.</p><h3 id="data-residency">Data Residency</h3><p>Dyte provides data residency options in India, the US, and Europe. The addition of new regions will not change this list; the new regions will only route media, and the data processing regions will remain the same as before.</p><h3 id="custom-regions">Custom Regions</h3><p>We remain open to adding more media and data processing regions. Our customers can contact us for dedicated infrastructure in any specific AWS or Google Cloud region for a small fee.</p><p>We&#x2019;re excited to help our customers better serve their users&apos; needs and to continue to grow and enhance our services in various ways.</p><p><em>If you haven&apos;t heard about Dyte yet, head over to&#xA0;</em><a href="https://dyte.io/"><em>dyte.io</em></a><em>&#xA0;to learn how we are revolutionizing communication through our SDKs and libraries and how you can&#xA0;</em><a href="https://accounts.dyte.in/auth/register" rel="noreferrer noopener"><em>get started</em></a><em>&#xA0;quickly on your 10,000 free minutes, which renew every month. If you have any questions, you can reach us at&#xA0;</em><a href="mailto:support@dyte.io"><em>support@dyte.io</em></a><em>&#xA0;or ask our&#xA0;</em><a href="https://community.dyte.io/"><em>developer community</em></a><em>.</em></p>]]></content:encoded></item><item><title><![CDATA[LL-HLS in Depth]]></title><description><![CDATA[Learn how to optimize HLS for latency by understanding Apple's low-latency HLS and another independent low-latency solution, Community HLS.]]></description><link>https://dyte.io/blog/ll-hls-in-depth/</link><guid isPermaLink="false">6557349ad0c9640001953ec0</guid><category><![CDATA[Engineering]]></category><dc:creator><![CDATA[Fenil Jain]]></dc:creator><pubDate>Thu, 14 Nov 2024 12:32:00 GMT</pubDate><media:content url="https://dyte.io/blog/content/images/2023/11/HLS--3---LL-HLS-in-depth.png" medium="image"/><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2><img src="https://dyte.io/blog/content/images/2023/11/HLS--3---LL-HLS-in-depth.png" alt="LL-HLS in Depth"><p>In the <a href="https://dyte.io/blog/hls-in-depth/">HLS in depth</a> blog, we explored the core HLS protocol, how it works, its benefits, and its limitations. While discussing limitations, one notable point was higher latency. Reducing it is a tricky problem, and so efforts began in this direction. Today, we will explore how they led to the final evolution of LL-HLS and CL-HLS.</p><h2 id="terminology">Terminology</h2><p>Before we start, let&apos;s get our terminology straight. In this article, CL-HLS refers to Community HLS, also known as L-HLS. LL-HLS refers to Apple&apos;s low-latency HLS or AL-HLS. I have seen a lot of confusion around these (I had much more). We will use CL-HLS and LL-HLS for both of these independent low-latency solutions.</p><h2 id="hls">HLS</h2><p>With traditional HLS (HTTP Live Streaming), we wait for a segment to be produced. Then, we copy it to some web-server/edge location, which then gets polled by the client to fetch them. For a 6 second segment, this would mean 6 second input wait + encoder costs + CDN fetch costs + client buffer, which is 3 segments, so 4 segments to start playing, i.e., 24 seconds. A notable point would be how this latency begins to add up in traditional HLS.</p><p>Before even starting to optimize, let&apos;s get a mental model around this:</p><h2 id="what-we-dont-focus-to-optimize">What we don&apos;t focus to optimize</h2><p>One would say we can start optimizing from source to encoder delivery. The catch is that the HLS Spec does not discuss this part of delivery so that it can be done in any way open to the implementor. The most famous route is RTMP, but we have seen WebRTC emerging as an excellent alternative. With OBS getting first-class support for it using WHIP, it is exciting to see how the ingest side of things evolves.</p><p>But for optimizations in HLS, we don&apos;t focus on ingest from a spec perspective. So, the next part is the encoder. Again, one can get the best encoder, and it&apos;s a complete field of its own to optimize them, which is also the reason the spec doesn&apos;t talk about them explicitly. So that is out of the picture.</p><p>But now everything after segments, leaving the encoder, is HLS territory (including the client) and is open for optimizations. So our problem statement is getting output from the encoder to glass (viewer&apos;s screen) as fast as possible.</p><h2 id="segment-size-optimization">Segment size optimization</h2><p>If we revisit the segment flow mentioned above, we could just segment size smaller, less than a second maybe, and then keep sending them. But there&apos;s a catch &#x2014; we always want each segment to be independently decodable. What do I mean by independently decodable? I will give a small, just enough primer on how the encoder works; everything above that is homework for you!</p><p>When talking about still pictures, we know one way they can be represented in memory &#x2014; RGB. So, we store the RGB value for each pixel, and a nested array forms a picture. That&apos;s a lot of data points! Well, now let&apos;s say we are dealing with a stream of pictures and storing this representation for each of them. This would require a lot of memory. So, what we do is choose a base representation. We can call it I-Frame (keyframes). These are not directly RGB representations but with some smart compression. Now, whatever changes in the next frame, we take a smart difference from the I-Frame and store it as a P-Frame. P-Frames, hence, are dependent frames, and I-Frames are independent frames as they don&apos;t depend on anyone else. In <a href="https://en.wikipedia.org/wiki/Video_compression_picture_types">Wikipedia&apos;s</a> words:</p><pre><code>I&#x2011;frames are the least compressible but don&apos;t require other video frames to decode. P&#x2011;frames can use data from previous frames to decompress and are more compressible than I&#x2011;frames.

</code></pre><p>Back to our topic, each segment needs at least one I-Frame, a simple reason being that a viewer can join the stream at any time, and a peer starts getting the segment from that point in time; hence, that segment should be independent of the start of the stream. We discussed that I-Frames are heavy; thus, reducing segment size would cause keyframe generation to increase and the size of each segment to grow. Segment size directly corresponds to bandwidth usage, which would start choking on lower bandwidth devices.</p><p>We now understand why we can&apos;t just reduce the segment size mindlessly. But we do need to deliver smaller chunks of data to the client. What if we break this segment into smaller &quot;parts,&quot; where each part is not required to have a keyframe, but if it does, we can mention it as &quot;INDEPENDENT&quot;.</p><p>This way, the client only looks for parts that are &quot;INDEPENDENT&quot; and starts decoding from that point. It does not need to wait for the complete segment to begin playing. This also became possible due to Chunked CMAF and Partial TS. We mentioned these in our <a href="https://dyte.io/blog/hls-in-depth/">previous article</a>. They are container-type formats, a way to store video/audio data. Getting support chunks/smaller parts allowed us to shrink normal container sizes into <em>n</em> number of chunks. The ideal size of these parts is 300-400ms.</p><p>This is the same method CL-HLS and LL-HLS use to reduce latency. The tag used to identify a partial segment is EXT-X-PART. If we check the spec, we find another interesting attribute, BYTERANGE, which is a way to indicate that the delivered part will be in this byte range of the complete segment.</p><h2 id="delivery">Delivery</h2><p>Okay, this seems reasonable, but how does the client get these parts? The client will have to fetch manifest to understand how to find them. However, as we keep generating more parts, we keep changing the manifest file really fast, so we would also have to fetch new changes quickly. To optimize this step, CL-HLS and LL-HLS took two different routes.</p><p>CL-HLS optimized it by pre-announcing segments, and when they were fetched, it used HTTP Chunked Transfer Encoding (CTE) to deliver parts continuously.</p><p>CTE is a method of sending data in chunks, so instead of letting the client know the total size of the payload, we keep sending chunks with the mentioned size, and when we are done, we send an empty chunk. This is usually employed in cases where the actual payload size is unknown.</p><p>One can understand why this is a neat technique. We are announcing yet-to-be-formed segments and leveraging the network round trip time to make parts and continuously deliver them using CTE. One point about using CTE is it makes bandwidth estimation harder.</p><p>LL-HLS took a different route. In their initial 2019 announcement, it seemed their strategy was to write segments to the manifest file if and only if generated. To compensate for low latency, they started pushing segments using HTTP/2 Push when the client requested a newer manifest. This saves a round trip time of the client reading the manifest, understanding it, and then requesting segments/parts.</p><p>Later, after taking feedback from the community, they modified the spec in 2020 to include a prefetch tag, which, like the CL-HLS version, allowed to pre-announce segments. So, HTTP/2 Push isn&apos;t a mandatory requirement anymore.</p><p>But the client still has to fetch these after getting the manifest; no CTE is involved. This does make a few things more bandwidth-consuming. A protocol with all Apple&apos;s changes and CTE for continuous delivery would be a perfect middle-ground, and maybe the community&apos;s ultimate goal was planning for low-latency HLS.</p><p>The tag used to pre-announce a segment is EXT-X-PRELOAD-HINT.</p><h2 id="playlist-delta-updates">Playlist delta updates</h2><p>In HLS, another problem we discussed was around big playlists. Let&apos;s say we have a long livestream going on, and fetching manifest listing segments from the very start until the end would make round-trip super heavy. Instead, we could get only the updates and deltas from the playlist at one point. Playlist delta updates enable this. It is an exciting feature, and hence, also ported back to HLS specification.</p><p>The EXT-X-SKIP tag is used as a marker to skip a manifest section. To request a delta update from the server, the client uses_HLS_skip=YES|v2 query param.</p><h2 id="blocking-playlist-updates">Blocking playlist updates</h2><p>For CDNs, let&apos;s say we set cache TTL to 2 seconds. This would mean CDN will not serve newer segments produced every 300-400 ms till 2 seconds. We may need some kind of cache-busting and precise segment/part fetch mechanism. LL-HLS also provides this feature in the form of blocking playlist updates. We can tell the server not to break the connection and send a response until a particular segment/part is ready to be served.</p><p>This also helps client blocking until their set buffer size is not fulfilled; at this point, they can start decoding and displaying output instantly.</p><p>Blocking is obtained by usage of two query params in sync:</p><ul><li>_HLS_msn=&lt;M&gt;: Do not resolve the request until the M-numbered media sequence number is ready.</li><li>_HLS_part=&lt;N&gt;: Do not resolve the request until the N-part of the M segment is ready. This tag needs the msn tag to be present.</li></ul><h2 id="rendition-reports">Rendition reports</h2><p>Another great feature added by LL-HLS is a faster way to do ABR using rendition reports. These reports contain the last media sequence number and the latest part. A rendition report is needed for each of the defined bitrates individually. EXT-X-RENDITION-REPORT is used to identify these reports.</p><h2 id="ll-hls-example-playlists">LL-HLS example playlists</h2><p><a href="https://developer.apple.com/documentation/http-live-streaming/enabling-low-latency-http-live-streaming-hls#Utilize-New-Media-Playlist-Tags-for-Low-Latency-HLS">Apple documentation</a> around LL-HLS includes examples of different playlist requests and their responses.</p><p>General low-latency playlist example for request style:</p><pre><code>GET &lt;https://example.com/2M/waitForMSN.php?_HLS_msn=273&amp;_HLS_part=2&gt;

</code></pre><p>Is this:</p><pre><code>#EXTM3U
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:6
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=1.0,CAN-SKIP-UNTIL=12.0
#EXT-X-PART-INF:PART-TARGET=0.33334
#EXT-X-MEDIA-SEQUENCE:266
#EXT-X-PROGRAM-DATE-TIME:2019-02-14T02:13:36.106Z
#EXT-X-MAP:URI=&quot;init.mp4&quot;
#EXTINF:4.00008,
fileSequence266.mp4
#EXTINF:4.00008,
fileSequence267.mp4
#EXTINF:4.00008,
fileSequence268.mp4
#EXTINF:4.00008,
fileSequence269.mp4
#EXTINF:4.00008,
fileSequence270.mp4
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.0.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.1.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.2.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.3.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.4.mp4&quot;,INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.5.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.6.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.7.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.8.mp4&quot;,INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.9.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.10.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.11.mp4&quot;
#EXTINF:4.00008,
fileSequence271.mp4
#EXT-X-PROGRAM-DATE-TIME:2019-02-14T02:14:00.106Z
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.a.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.b.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.c.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.d.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.e.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.f.mp4&quot;,INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.g.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.h.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.i.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.j.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.k.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.l.mp4&quot;
#EXTINF:4.00008,
fileSequence272.mp4
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart273.0.mp4&quot;,INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart273.1.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart273.2.mp4&quot;
#EXT-X-PRELOAD-HINT:TYPE=PART,URI=&quot;filePart273.3.mp4&quot;

#EXT-X-RENDITION-REPORT:URI=&quot;../1M/waitForMSN.php&quot;,LAST-MSN=273,LAST-PART=2
#EXT-X-RENDITION-REPORT:URI=&quot;../4M/waitForMSN.php&quot;,LAST-MSN=273,LAST-PART=1

</code></pre><p>We can see some familiar tags from the HLS post and some new ones we learned in this article. Part duration is defined using tag #EXT-X-PART-INF:PART-TARGET=0.33334. For server control params, we see #EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=1.0,CAN-SKIP-UNTIL=12.0. This has a couple of instructions. Let&apos;s understand them one by one:</p><ul><li>CAN-BLOCK-RELOAD: To block reload until 273 media sequence number and its 2nd part is not ready.</li><li>Then PART-HOLD-BACK, do not play until &apos;n&apos;, 1.0 here; parts are unavailable.</li><li>And finally, CAN-SKIP-UNTIL, the server can skip a part of the playlist if the client requests it. The value for the last tag is in seconds and must be at least six times the target duration; hence, we have it as 12 here.</li></ul><p>Actual parts are listed using #EXT-X-PART. It has DURATION and URI fields, which are pretty self-explanatory. There&apos;s also an INDEPENDENT tag at the end of some parts. These represent the I-Frames we discussed earlier and hence can be used as a point from which the client can start decoding.</p><p>Near the bottom, we can see EXT-X-PRELOAD-HINT. It mentions it&apos;s hinting for a part using TYPE=PART and then the exact URI to fetch it.</p><p>And lastly, we have rendition reports mentioning the last MSN and part index along with URI.</p><p>There&apos;s also an example of playlist delta update, which is requested using a URL like:</p><pre><code>GET &lt;https://example.com/2M/waitForMSN.php?_HLS_msn=273&amp;_HLS_part=3&gt; &amp;_HLS_skip=YES

</code></pre><pre><code>#EXTM3U
# Following the example above, this Playlist is a response to: GET &lt;https://example.com/2M/waitForMSN.php?_HLS_msn=273&amp;_HLS_part=3&gt; &amp;_HLS_skip=YES
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:9
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=1.0,CAN-SKIP-UNTIL=12.0
#EXT-X-PART-INF:PART-TARGET=0.33334
#EXT-X-MEDIA-SEQUENCE:266
#EXT-X-SKIP:SKIPPED-SEGMENTS=3
#EXTINF:4.00008,
fileSequence269.mp4
#EXTINF:4.00008,
fileSequence270.mp4
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.0.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.1.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.2.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.3.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.4.mp4&quot;,INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.5.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.6.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.7.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.8.mp4&quot;,INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.9.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.10.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart271.11.mp4&quot;
#EXTINF:4.00008,
fileSequence271.mp4
#EXT-X-PROGRAM-DATE-TIME:2019-02-14T02:14:00.106Z
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.a.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.b.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.c.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.d.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.e.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.f.mp4&quot;,INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.g.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.h.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.i.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.j.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.k.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart272.l.mp4&quot;
#EXTINF:4.00008,
fileSequence272.mp4
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart273.0.mp4&quot;,INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart273.1.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart273.2.mp4&quot;
#EXT-X-PART:DURATION=0.33334,URI=&quot;filePart273.3.mp4&quot;
#EXT-X-PRELOAD-HINT:TYPE=PART,URI=&quot;filePart273.4.mp4&quot;

#EXT-X-RENDITION-REPORT:URI=&quot;../1M/waitForMSN.php&quot;,LAST-MSN=273,LAST-PART=3
#EXT-X-RENDITION-REPORT:URI=&quot;../4M/waitForMSN.php&quot;,LAST-MSN=273,LAST-PART=3

</code></pre><p>We have a new query param of _HLS_SKIP to indicate that part of the playlist can be skipped. Then we have #EXT-X-SKIP:SKIPPED-SEGMENTS=3 to mention how many segments we have skipped in this playlist update.</p><p>They also have an example of a playlist that contains byterange-addressed parts:</p><pre><code># In these examples only the end of the Playlist is shown.
# This is Playlist update 1
#EXTINF:4.08,
fs270.mp4
#EXT-X-PART:DURATION=1.02,URI=&quot;fs271.mp4&quot;,BYTERANGE=&quot;20000@0&quot;
#EXT-X-PART:DURATION=1.02,URI=&quot;fs271.mp4&quot;,BYTERANGE=&quot;23000@20000&quot;
#EXT-X-PART:DURATION=1.02,URI=&quot;fs271.mp4&quot;,BYTERANGE=&quot;18000@43000&quot;
#EXT-X-PRELOAD-HINT:TYPE=PART,URI=&quot;fs271.mp4&quot;,BYTERANGE-START=61000

# This is Playlist update 2
#EXTINF:4.08,
fs270.mp4
#EXT-X-PART:DURATION=1.02,URI=&quot;fs271.mp4&quot;,BYTERANGE=&quot;20000@0&quot;
#EXT-X-PART:DURATION=1.02,URI=&quot;fs271.mp4&quot;,BYTERANGE=&quot;23000@20000&quot;
#EXT-X-PART:DURATION=1.02,URI=&quot;fs271.mp4&quot;,BYTERANGE=&quot;18000@43000&quot;
#EXT-X-PART:DURATION=1.02,URI=&quot;fs271.mp4&quot;,BYTERANGE=&quot;19000@61000&quot;
#EXTINF:4.08,
fs271.mp4
#EXT-X-PRELOAD-HINT:TYPE=PART,URI=&quot;fs272.mp4&quot;,BYTERANGE-START=0

# This is Playlist update 3
#EXTINF:4.08,
fs270.mp4
#EXT-X-PART:DURATION=1.02,URI=&quot;fs271.mp4&quot;,BYTERANGE=&quot;20000@0&quot;
#EXT-X-PART:DURATION=1.02,URI=&quot;fs271.mp4&quot;,BYTERANGE=&quot;23000@20000&quot;
#EXT-X-PART:DURATION=1.02,URI=&quot;fs271.mp4&quot;,BYTERANGE=&quot;18000@43000&quot;
#EXT-X-PART:DURATION=1.02,URI=&quot;fs271.mp4&quot;,BYTERANGE=&quot;19000@61000&quot;
#EXTINF:4.08,
fs271.mp4
#EXT-X-PART:DURATION=1.02,URI=&quot;fs272.mp4&quot;,BYTERANGE=&quot;21000@0&quot;
#EXT-X-PRELOAD-HINT:TYPE=PART,URI=&quot;fs272.mp4&quot;,BYTERANGE-START=21000

</code></pre><p>Notice using the BYTERANGE attribute at the end of each EXT-X-PART tag. Another attractive attribute is BYTERANGE-START in EXT-X-PRELOAD-HINT tag, that is, to mark the start of the byte range of part in the segment.</p><h2 id="final-thoughts">Final thoughts</h2><p>The need for low-latency solutions arose soon after the release of HLS, and we went from no solutions to a couple of competing solutions in no time. Watching different approaches and thought processes behind the same problem optimization is undoubtedly fascinating. Today, we have LL-HLS as the leading standard for low-latency live streaming. Amazon/Twitch evolved CL-HLS independently and used a proprietary implementation that seems to be performing really well!</p><p>Scaling low-latency solutions is definitely challenging, and that&apos;s why we at Dyte handle that for you. Feel at home with that sweet DX and leave all the complexity to us; try our <a href="https://dyte.io/live-streaming-sdk">Livestreaming SDK</a> today!</p><p><em>Get better insights on leveraging Dyte&#x2019;s technology and discover how it can revolutionize your app&#x2019;s communication capabilities with its </em><a href="https://dyte.io/video-sdk" rel="noopener noreferrer"><em>SDKs</em></a><em>.</em></p>]]></content:encoded></item><item><title><![CDATA[Building a Fast IP Location Service]]></title><description><![CDATA[Learn to create your own location service with extremely low latency and lower costs with zero downtimes to bypass the chaos of third-party services.]]></description><link>https://dyte.io/blog/location-service/</link><guid isPermaLink="false">65e45f10fc3f7000017e90c0</guid><category><![CDATA[Engineering]]></category><dc:creator><![CDATA[Ravindra Singh Rathor]]></dc:creator><pubDate>Mon, 11 Nov 2024 06:30:00 GMT</pubDate><media:content url="https://dyte.io/blog/content/images/2024/03/Dyte_Location_Service_Header-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://dyte.io/blog/content/images/2024/03/Dyte_Location_Service_Header-1.png" alt="Building a Fast IP Location Service"><p>At Dyte, we provide white-labeled audio/video SDKs to facilitate meetings, webinars, and live streams for our clients to integrate into their mobile and web apps to be used globally.</p><p>When participants join a meeting, Dyte allocates a media server to the meeting in the region nearest to these participants to ensure low latency and a smoother meeting experience. To figure out the closest region to use, we need to determine the location of these participants. The location need not be point precise; even if we have a rough idea of state/county/province/district, we can make a good enough decision.</p><p>To figure out the location, aka latitude and longitude, we were earlier receiving the IP of the caller from the network request from the client and passing this IP to third-party providers that keep track of IPs against latitude and longitude. Since these IPs keep changing, we went ahead with one such third-party location service provider to focus on our core offering of audio/video SDK rather than maintaining one more side project.</p><p><strong>Pros of using third-party service:</strong></p><ol><li><strong>In-depth details of IP:</strong> These location services gave not just the position but also the city, country, company name, autonomous system number (ASN), and many more.</li><li><strong>Near Zero maintenance solution:</strong> We wouldn&apos;t have to maintain the database, scale, and frequent DB updates. API was uncomplicated and simple to upgrade if needed.</li></ol><p><strong>Cons of using third-party services:</strong></p><ol><li><strong>Latency:</strong> Most of the time, this extra information causes the latency to reach beyond 500ms.</li><li><strong>Third-party downtimes:</strong> Since fetching location was crucial, any downtime in the location service would mean downtime at Dyte, which was unacceptable.</li><li><strong>Cost:</strong> We didn&apos;t need most of the data, but we were still getting it, resulting in extra cost.</li><li><strong>Poor performance for some edge locations:</strong> Some edge locations were more prone to slow responses than others.</li></ol><p>So, after months of facing these issues, we decided to take matters into our own hands and make our location service &#x2014; that would have extremely low latency and lower costs with zero downtimes.</p><p><strong>We had two approaches to create a new location service:</strong></p><ol><li>Purchase or download free IP details, store them in DB and expose an endpoint to query.</li><li>Think outside the box.</li></ol><p>Since purchasing IP details and storing and maintaining them was going to be a lot of pain, we returned to our drawing board to see what else could be done if we were to think outside the box.</p><h3 id="introducing-cloudfront-headers"><strong>Introducing Cloudfront headers</strong></h3><p>After looking for alternate solutions everywhere, we came across <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/adding-cloudfront-headers.html" rel="noreferrer">CloudFront&apos;s request headers</a>. </p><p>Since CloudFront already has the database and uses it to populate request headers, we wondered why not somehow use these headers to retrieve city, country, and location, among other things. Whatever we needed was there virtually free of cost, and latency was under 50ms compared to 500ms - 1 second.</p><p>The next step was to make the code return these headers work with edge locations without hitting any origin server. Since CloudFront, by its nature, supports edge locations and CloudFront functions, this step was already solved. All we needed was a small piece of code in CloudFront functions.</p><h2 id="steps-to-create-location-service-cloudfront-distribution"><strong>Steps to create location service CloudFront distribution</strong></h2><p>Here are the detailed steps to create a location service distribution yourself.</p><h3 id="create-a-fake-origin">Create a Fake origin</h3><p>We need to create a Fake AWS S3 bucket as an origin to keep CloudFront happy. Since CloudFront must have an origin, we had to give it one. This AWS S3 bucket will never be hit, so don&apos;t worry about S3 hits and the associated costs. You can go to AWS S3 and create a new S3 bucket manually.</p><h3 id="create-a-cloudfront-function">Create a CloudFront function</h3><p>Create a CloudFront function, put this simple code, and publish it.</p><pre><code class="language-html">function handler(event) {
    var headers = event &amp;&amp; event.request &amp;&amp; event.request.headers || {};
    function getHeaderValue(headerName){
        return headers &amp;&amp; headers[headerName] &amp;&amp; headers[headerName].value;
    }
    var response = {
        statusCode: 200,
        statusDescription: &apos;OK&apos;,
        headers: {
            &apos;cloudfront-functions&apos;: { value: &apos;generated-by-CloudFront-Functions&apos; },
            &apos;access-control-allow-origin&apos;: { value: &apos;*&apos;},
            &apos;access-control-allow-headers&apos;: { value: &apos;*&apos; },
            &apos;access-control-allow-methods&apos;: { value: &apos;GET,OPTIONS&apos;},
            &apos;timing-allow-origin&apos;: { value: &apos;*&apos;}
        },
        body: JSON.stringify({
              city: getHeaderValue(&apos;cloudfront-viewer-city&apos;),
              country: getHeaderValue(&apos;cloudfront-viewer-country&apos;),
              region: getHeaderValue(&apos;cloudfront-viewer-country-region-name&apos;),
              loc: (getHeaderValue(&apos;cloudfront-viewer-latitude&apos;) || &apos;&apos;) + &quot;,&quot; + (getHeaderValue(&apos;cloudfront-viewer-longitude&apos;) || &apos;&apos;),
              timezone: getHeaderValue(&apos;cloudfront-viewer-time-zone&apos;),
              ip: (getHeaderValue(&apos;cloudfront-viewer-address&apos;) || &apos;&apos;).split(&quot;:&quot;).slice(0, -1).join(&quot;:&quot;),
              postal: getHeaderValue(&apos;cloudfront-viewer-postal-code&apos;),
        })
    };
    return response;
}
</code></pre><h3 id="create-a-distribution">Create a Distribution</h3><p>While creating a CloudFront distribution, link the S3 bucket to it as the origin. Select the path pattern of your liking or leave it as default (*), and select desired HTTP methods. In Caching policies, set the following,</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/03/Dyte_Location_Service_Asset_5.png" class="kg-image" alt="Building a Fast IP Location Service" loading="lazy" width="1642" height="1112" srcset="https://dyte.io/blog/content/images/size/w600/2024/03/Dyte_Location_Service_Asset_5.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/03/Dyte_Location_Service_Asset_5.png 1000w, https://dyte.io/blog/content/images/size/w1600/2024/03/Dyte_Location_Service_Asset_5.png 1600w, https://dyte.io/blog/content/images/2024/03/Dyte_Location_Service_Asset_5.png 1642w" sizes="(min-width: 720px) 720px"></figure><p>Map the previously created CloudFront functions as Viewer Request in function associations.</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/03/Dyte_Location_Service_Asset_2.png" class="kg-image" alt="Building a Fast IP Location Service" loading="lazy" width="1642" height="684" srcset="https://dyte.io/blog/content/images/size/w600/2024/03/Dyte_Location_Service_Asset_2.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/03/Dyte_Location_Service_Asset_2.png 1000w, https://dyte.io/blog/content/images/size/w1600/2024/03/Dyte_Location_Service_Asset_2.png 1600w, https://dyte.io/blog/content/images/2024/03/Dyte_Location_Service_Asset_2.png 1642w" sizes="(min-width: 720px) 720px"></figure><p>Once you are done making this distribution, test it out. You would get a URL similar to <code>somerandomcharacters.cloudfront.net</code>. Opening this link will show your IP details as follows.</p><pre><code class="language-jsx">{
  &quot;city&quot;: &quot;Gunzenhausen&quot;,
  &quot;country&quot;: &quot;DE&quot;,
  &quot;region&quot;: &quot;Bavaria&quot;,
  &quot;loc&quot;: &quot;49.11560, 10.75110&quot;,
  &quot;timezone&quot;: &quot;Europe/Berlin&quot;,
  &quot;ip&quot;: &quot;2a01:4f8:c0c:c129::1&quot;,
  &quot;postal&quot;: &quot;91710&quot;
}
</code></pre><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/03/Dyte_Location_Service_Asset_3.png" class="kg-image" alt="Building a Fast IP Location Service" loading="lazy" width="2000" height="77" srcset="https://dyte.io/blog/content/images/size/w600/2024/03/Dyte_Location_Service_Asset_3.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/03/Dyte_Location_Service_Asset_3.png 1000w, https://dyte.io/blog/content/images/size/w1600/2024/03/Dyte_Location_Service_Asset_3.png 1600w, https://dyte.io/blog/content/images/size/w2400/2024/03/Dyte_Location_Service_Asset_3.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>If you have come so far, the next step is to link Route 53 to this CloudFront distribution to have a sane-looking domain name such as <code>ipdetails.yourwebsite.com</code>. If you are not using Route 53, use the tool of your choice. It is not a must.</p><h2 id="user-flow">User Flow</h2><p>Overall, the user flow will look like the following.</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/03/Dyte_Location_Service_Asset_4-1.png" class="kg-image" alt="Building a Fast IP Location Service" loading="lazy" width="1140" height="1164" srcset="https://dyte.io/blog/content/images/size/w600/2024/03/Dyte_Location_Service_Asset_4-1.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/03/Dyte_Location_Service_Asset_4-1.png 1000w, https://dyte.io/blog/content/images/2024/03/Dyte_Location_Service_Asset_4-1.png 1140w" sizes="(min-width: 720px) 720px"></figure><p>Voila, you now have the IP info solution that gives results under 10ms. Below is an instance of it delivering IP details in 6ms!</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/03/Dyte_Location_Service_Asset_1.png" class="kg-image" alt="Building a Fast IP Location Service" loading="lazy" width="2000" height="235" srcset="https://dyte.io/blog/content/images/size/w600/2024/03/Dyte_Location_Service_Asset_1.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/03/Dyte_Location_Service_Asset_1.png 1000w, https://dyte.io/blog/content/images/size/w1600/2024/03/Dyte_Location_Service_Asset_1.png 1600w, https://dyte.io/blog/content/images/2024/03/Dyte_Location_Service_Asset_1.png 2094w" sizes="(min-width: 720px) 720px"></figure><p>There is no need to maintain and scale DBs or purchase anything. You now have a simple near-zero maintenance location service distribution system that is extremely fast. It is not going to go down easily; AWS can attest to that.</p><p>Though these are minimal improvements in terms of cost for many organizations to even take up, they are huge in reducing latency. Collectively, they make Dyte&apos;s customer experience better with time.</p><p>I hope you found this post informative and engaging. If you have any thoughts or feedback, please contact me on&#xA0;<a href="https://twitter.com/rsr_thedarklord">Twitter</a>&#xA0;or&#xA0;<a href="https://www.linkedin.com/in/softwareprovider/">LinkedIn</a>. Stay tuned for more related blog posts in the future!</p><p><em>If you haven&apos;t heard about Dyte yet, head over to&#xA0;</em><a href="https://dyte.io/"><em>dyte.io</em></a><em>&#xA0;to learn how we are revolutionizing communication through our SDKs and libraries and how you can&#xA0;</em><a href="https://accounts.dyte.in/auth/register"><em>get started</em></a><em>&#xA0;quickly on your 10,000 free minutes, which renew every month. You can reach us at&#xA0;</em><a href="mailto:support@dyte.io"><em>support@dyte.io</em></a><em>&#xA0;or ask our&#xA0;</em><a href="https://community.dyte.io/"><em>developer community</em></a><em>.</em></p>]]></content:encoded></item><item><title><![CDATA[AI-Powered Audio Transcriptions]]></title><description><![CDATA[Explore Dyte's beta AI-powered audio transcription feature, converting spoken words into text with real-time accuracy. Benefit from speaker identification and searchable transcripts.]]></description><link>https://dyte.io/blog/ai-powered-audio-transcriptions/</link><guid isPermaLink="false">6576de673df14600014b366e</guid><category><![CDATA[AI]]></category><dc:creator><![CDATA[Rohan Mukherjee]]></dc:creator><pubDate>Fri, 08 Nov 2024 10:28:00 GMT</pubDate><media:content url="https://dyte.io/blog/content/images/2023/12/aiblog--1--3.png" medium="image"/><content:encoded><![CDATA[<img src="https://dyte.io/blog/content/images/2023/12/aiblog--1--3.png" alt="AI-Powered Audio Transcriptions"><p><strong>Update: This feature is out of beta and in general availability.</strong> </p><p></p><p>At Dyte, we are committed to revolutionizing real-time communication, and we&apos;re thrilled to announce an exciting addition to our platform: AI-powered audio transcriptions. This feature is designed to enhance your communication experience by effortlessly converting spoken words into written text.</p><p>We&apos;re introducing an efficient and accurate way to transcribe your conversations in meetings. We provide transcriptions in 2 forms:</p><ol><li><strong>Live transcriptions</strong>: The transcripts can be consumed on the client side using the Dyte SDK that&apos;s suitable for your platform. These transcripts are generated on the server in real-time.</li><li><strong>Post-meeting webhooks</strong>: The meeting transcript can be consumed via a <a href="https://docs.dyte.io/guides/capabilities/ai/meeting-transcription#consume-transcript-via-a-post-meeting-webhook">webhook after the meeting ends</a>.</li></ol><p>This release marks the step in a journey toward providing you with cutting-edge AI capabilities. We&#x2019;re also in the process of developing features to support other AI features like meeting agenda generation and meeting summarization.</p><h2 id="usage">Usage</h2><p>Dyte&apos;s transcription APIs likely offer a programmatic way for developers to integrate transcription capabilities into their applications or services. To know more about the transcription APIs in detail, check out <a href="https://docs.dyte.io/guides/capabilities/ai/meeting-transcription">this guide</a>.</p><p>As always, making sure that our features are developer-friendly is our top priority. Thus, we provide a very simple-to-use API in our client SDKs for you to be able to consume real-time audio transcriptions. Here&#x2019;s an example of how to use it in our web core SDK.</p><pre><code class="language-jsx">meeting.ai.on(&apos;transcript&apos;, (transcriptionData) =&gt; {
    console.log(transcriptionData);
});</code></pre><p>The <code>transcriptionData</code> object consists of the following information:</p><ul><li>An ID to uniquely identify the transcript</li><li>The name of the speaker</li><li>The ID of the speaker</li><li>The transcribed speech</li><li>A timestamp of when the speaker had spoken</li></ul><p>The <code>transcriptionData</code> object can be represented with the help of the following interface.</p>
<pre><code class="language-jsx">export interface Transcript {
  id: string;
  name: string;
  peerId: string;
  transcript: string;
  date: Date;
}</code></pre><p>The <code>meeting.ai</code> object emits transcripts only when it&#x2019;s enabled in the preset of the participant who is speaking. To learn more about how to enable this feature for a participant, check out <a href="https://docs.dyte.io/guides/capabilities/ai/meeting-transcription#control-transcriptions-for-participants-using-presets"> the transcription guide</a>.</p>
<h2 id="key-features">Key Features</h2><p>Dyte&apos;s transcriptions offer a robust suite of features, as mentioned below.</p><ul><li><strong>Real-Time Accuracy:</strong> Our AI engine provides instant and accurate audio transcriptions, ensuring you stay in sync with the conversation.</li><li><strong>Speaker Identification:</strong> Easily identify speakers with our speaker attribution feature, making it clearer who said what.</li><li><strong>Searchable Transcripts:</strong> Search through the transcript to quickly locate specific points in the conversation, streamlining post-meeting analysis.</li></ul><h2 id="pricing">Pricing</h2><p>Our AI-driven audio transcription service is priced at $0.015 per minute, offering precise and rapid transcription for any volume of content. This straightforward rate ensures transparent billing for all your transcription needs.</p>]]></content:encoded></item><item><title><![CDATA[Kotlin MPP: Concurrency]]></title><description><![CDATA[Learn how to manage concurrency in Kotlin Multiplatform (KMP) projects. This guide covers best practices and techniques for cross-platform development.]]></description><link>https://dyte.io/blog/kotlin-kmp-concurrency/</link><guid isPermaLink="false">664b3be31e73890001d4a985</guid><category><![CDATA[Engineering]]></category><dc:creator><![CDATA[Yash Garg]]></dc:creator><pubDate>Thu, 07 Nov 2024 08:56:00 GMT</pubDate><media:content url="https://dyte.io/blog/content/images/2024/05/kotlinmpp-concurrency--1-.png" medium="image"/><content:encoded><![CDATA[<img src="https://dyte.io/blog/content/images/2024/05/kotlinmpp-concurrency--1-.png" alt="Kotlin MPP: Concurrency"><p>At <a href="https://dyte.io/">Dyte</a>, we leverage Kotlin Multiplatform across our products to keep our codebase consistent and maintainable. It allows us to share code across platforms, including Android, iOS, and the web, while still providing the flexibility to write platform-specific code when needed.</p><h2 id="the-need-for-structured-concurrency">The need for structured concurrency</h2><p>Structured concurrency allows doing multiple computations outside the UI-thread to keep the app as responsive as possible. It differs from concurrency in the sense that a task can only run within the scope of its parent, which cannot end before all of its children. This ensures that all tasks are properly managed and cleaned up, preventing memory leaks and other issues.</p><p><a href="https://kotlinlang.org/docs/multiplatform.html">Kotlin Multiplatform</a> provides an easier way to handle concurrency using it&apos;s <code>kotlinx.coroutines</code> library. It allows developers to write asynchronous code in a more readable and maintainable way, making it easier to handle complex scenarios.</p><h2 id="the-problem">The problem</h2><p>Let us launch two-hundred coroutines all doing the same action two-thousand times. The task is to increment a shared counter. The counter is a simple integer variable.</p><p>The code is as follows:</p><pre><code class="language-kotlin">suspend fun hugeRun(task: suspend () -&gt; Unit) {
    val i = 200  // number of coroutines to launch
    val k = 2000 // times an action is repeated by each coroutine

    coroutineScope { // scope for coroutines
        repeat(i) {
            launch {
                repeat(k) { task() }
            }
        }
    }

    println(&quot;Completed ${i * k} actions&quot;)
}
</code></pre><p>What does it print at the end? It is highly unlikely to ever print &quot;Counter = 400000&quot;, because two hundred coroutines increment the counter concurrently from multiple threads without any synchronization.</p><h2 id="approaches-to-handle-concurrency">Approaches to handle concurrency</h2><p>There is a common misconception that making a variable <code>volatile</code> solves concurrency problem. However, that only guarantees visibility of changes to other threads, but does not provide <strong>atomicity</strong>.</p><p>This means that if two threads read the value of a volatile variable at the same time, they may both see the same value, and both increment it, leading to a lost update.</p><h2 id="so-how-do-we-solve-this-problem">So, how do we solve this problem?</h2><p>We will explore three different approaches to handle this:</p><h3 id="using-atomic-primitives-for-shared-state-concurrency">Using Atomic Primitives for Shared State Concurrency</h3><p>The general solution that works both for threads and for coroutines is to use a thread-safe (aka synchronized, linearizable, or atomic) data structure that provides all the necessary synchronization for the corresponding operations that needs to be performed on a shared state. In the case of a simple counter we can use <code>AtomicInteger</code> class which has atomic <code>incrementAndGet</code> operations:</p><pre><code class="language-kotlin">import kotlinx.atomicfu.*

fun main() {
    // Create an atomic integer
    val counter = atomic(0)

    // Launch multiple coroutines to increment the counter concurrently
    repeat(100) {
        GlobalScope.launch {
            // Atomically increment the counter
            counter.incrementAndGet()
        }
    }

    // Wait for all coroutines to complete
    Thread.sleep(100)

    // Print the final value of the counter
    println(&quot;Counter value: ${counter.value}&quot;)
}
</code></pre><h3 id="using-threads-for-shared-state-concurrency">Using Threads for Shared State Concurrency</h3><p>To handle concurrency using threads, you can create multiple threads and synchronize access to shared state using locks or other synchronization mechanisms. Here&apos;s an example of how to increment a shared counter using threads:</p><pre><code class="language-kotlin">fun main() {
    // Create a shared counter
    var counter = 0

    // Launch multiple threads to increment the counter concurrently
    repeat(100) {
        Thread {
            // Increment the counter
            counter++
        }.start()
    }

    // Wait for all threads to complete
    Thread.sleep(100)

    // Print the final value of the counter
    println(&quot;Counter value: $counter&quot;)
}
</code></pre><h3 id="using-coroutines-for-asynchronous-operations">Using Coroutines for Asynchronous Operations</h3><p>We can also use <code>CoroutineScope</code> to launch multiple coroutines to perform asynchronous operations concurrently. Here&apos;s an example of how to increment a shared counter using coroutines:</p><pre><code class="language-kotlin">import kotlinx.coroutines.*

fun main() {
    // Define a coroutine scope
    runBlocking {
        // Launch a coroutine to perform a background task
        val job = launch {
            val result = async {
                // Simulate a long-running operation
                delay(1000)
                &quot;Hello, KMP!&quot;
            }
            // Wait for the result and print it
            println(result.await())
        }
        // Do other work concurrently
        println(&quot;Loading...&quot;)
        // Wait for the coroutine to complete
        job.join()
    }
}
</code></pre><h2 id="platform-specific-threading-models">Platform-Specific Threading Models</h2><p>Kotlin Multiplatform allows you to write platform-specific code using the <code>expect</code> and <code>actual</code> keywords. This enables you to leverage platform-specific threading models to handle concurrency in a platform-agnostic way.</p><p>Here&apos;s an example of how to use platform-specific threading models to perform tasks on the main UI thread in Android and iOS:</p><blockquote>Note: We can also use <code>kotlinx.coroutines</code> library to handle concurrency in a platform-agnostic way. It is not necessary to use platform-specific threading models, but they can be useful in certain scenarios.</blockquote><h3 id="platform-specific-threading-models-android">Platform-Specific Threading Models (Android)</h3><pre><code class="language-kotlin">import android.os.Handler
import android.os.Looper

fun main() {
    // Create a handler associated with the main UI thread
    val mainHandler = Handler(Looper.getMainLooper())

    // Post a task to the main UI thread
    mainHandler.post {
        // Update UI or perform other operations on the main thread
        println(&quot;Task executed on the main UI thread (Android)&quot;)
    }
}
</code></pre><h3 id="platform-specific-threading-models-ios">Platform-Specific Threading Models (iOS)</h3><pre><code class="language-swift">import Foundation

func main() {
    // Perform a task on the main UI thread (iOS)
    DispatchQueue.main.async {
        // Update UI or perform other operations on the main thread
        print(&quot;Task executed on the main UI thread (iOS)&quot;)
    }
}
</code></pre><h2 id="thread-safe-lists">Thread Safe Lists</h2><p>Apart from the above approaches, we at Dyte use custom implementations of lists with thread safety to handle concurrency in our applications.  We use two types of lists: <code>WriteHeavyMutableList&lt;T&gt;</code> and <code>ReadHeavyMutableList&lt;T&gt;</code> with the help of <a href="https://github.com/Kotlin/kotlinx-atomicfu?tab=readme-ov-file#locks">Locks</a> provided by <code>kotlinx.atomicfu</code> like <code>ReentrantLock</code>.</p><pre><code class="language-kotlin">// Short implementation of WriteHeavyMutableList
import kotlinx.atomicfu.*

internal class WriteHeavyMutableList&lt;T&gt; {
  val lock = reentrantLock()
  val intList = mutableListOf&lt;T&gt;()

  ...

  // The lock is used to synchronize access to the list
  // ensuring that only one thread can read or write to the list at a time.
  fun get(index: Int): T = lock.withLock { intList[index] }

  ...
}
</code></pre><p>This allows us to safely access and modify the list from multiple threads without the risk of data corruption or issues like concurrent modification exceptions.</p><h2 id="final-thoughts">Final Thoughts</h2><p>Concurrency is a complex topic that requires careful consideration when designing and implementing multiplatform applications. By understanding the strengths and weaknesses of each approach, you can choose the best solution for your specific use case, as we at Dyte do.</p><p>We hope you found this post informative and engaging. If you have any thoughts or feedback, please reach out to us on <a href="https://www.linkedin.com/company/dyteio/mycompany/">LinkedIn</a> and <a href="https://twitter.com/dyte_io">Twitter</a>. Stay tuned for more related blog posts in the future!</p><p><em>If you haven&apos;t heard about Dyte yet, head over to </em><a href="http://dyte.io/"><em>dyte.io</em></a><em> to learn how we are revolutionizing communication through our SDKs and libraries and how you can </em><a href="https://accounts.dyte.in/auth/register"><em>get started</em></a><em> quickly on your 10,000 free minutes, which renew every month. You can reach us at </em><a href="mailto:support@dyte.io"><em>support@dyte.io</em></a><em> or ask our </em><a href="https://community.dyte.io/"><em>developer community</em></a><em>.</em></p>]]></content:encoded></item><item><title><![CDATA[Monitoring Web API Performance]]></title><description><![CDATA[Learn how to monitor API performance in real-time, from the user's perspective, to identify and resolve real-world slowdowns.]]></description><link>https://dyte.io/blog/web-api-performance-monitoring/</link><guid isPermaLink="false">6617ec72fc3f7000017e977a</guid><category><![CDATA[Engineering]]></category><dc:creator><![CDATA[Ravindra Singh Rathor]]></dc:creator><pubDate>Thu, 07 Nov 2024 06:58:00 GMT</pubDate><media:content url="https://dyte.io/blog/content/images/2024/04/Web_API_Performance_Monitoring--Header-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://dyte.io/blog/content/images/2024/04/Web_API_Performance_Monitoring--Header-1.png" alt="Monitoring Web API Performance"><p>Optimal performance is crucial in the realm of web development. Users demand a smooth and responsive experience, and any latency can lead to frustration and abandonment. Thankfully, modern browsers equip developers with powerful tools to monitor and enhance performance.</p><p>One such tool is the PerformanceObserver API, a JavaScript interface enabling the observation of performance-related events. This guide comprehensively explores the PerformanceObserver API, specifically focusing on its application in tracking API performance.</p><p>While monitoring API performance on the server side is essential, it doesn&apos;t tell the whole story. What matters most is how long it takes for users to see results. Server metrics might show everything running smoothly, but if there&apos;s network lag or delays in processing user requests, the user experience suffers.</p><p>Tracking performance from the user&apos;s perspective helps identify real-world slowdowns that can lead to frustration and, ultimately, user abandonment of your application.</p><h2 id="understanding-the-performanceobserver-api"><strong>Understanding the PerformanceObserver API</strong></h2><p>The PerformanceObserver API builds upon the foundation of the broader Performance API. While the Performance API offers access to valuable performance-related information like timing metrics and navigation data, it often presents this data as a snapshot. The PerformanceObserver API elevates performance monitoring by enabling real-time observation and response to performance events. This empowers developers to delve deeper into various performance metrics, including resource timing, user navigation patterns, and server response times, all with a focus on providing a more granular and user-centric view of website performance.</p><h3 id="basic-usage"><strong>Basic usage</strong></h3><p>Using the PerformanceObserver API typically involves creating an observer object and specifying which performance entry types to observe. Here&apos;s a basic example.</p><pre><code class="language-jsx">const apiPerformanceObserver = new PerformanceObserver((list) =&gt; {
  list.getEntries()?.forEach((entry) =&gt; {
        console.log(&apos;performanceEntry:: &apos;, entry);
  });
});
apiPerformanceObserver.observe({ type: &apos;resource&apos;, buffered: true });
</code></pre><p>To try this out, head over to <a href="https://app.dyte.io/v2/meeting?demo=Default">Dyte Demo</a>. Once the page is loaded, paste the above code snippet into the developer console and hit enter. You will instantly see the buffered entries.</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/04/Web_API_Performance_Monitoring--Asset_1.png" class="kg-image" alt="Monitoring Web API Performance" loading="lazy" width="2000" height="1053" srcset="https://dyte.io/blog/content/images/size/w600/2024/04/Web_API_Performance_Monitoring--Asset_1.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/04/Web_API_Performance_Monitoring--Asset_1.png 1000w, https://dyte.io/blog/content/images/size/w1600/2024/04/Web_API_Performance_Monitoring--Asset_1.png 1600w, https://dyte.io/blog/content/images/2024/04/Web_API_Performance_Monitoring--Asset_1.png 2000w" sizes="(min-width: 720px) 720px"></figure><p>Provide any username and random meeting name and click on Start Meeting. You will see even more performance entries being console-logged.</p><h3 id="filtering-out-xhr-fetch"><strong>Filtering out XHR &amp; Fetch</strong></h3><p>If you only care about XHR &amp; Fetch, you can filter those initiatorType out.</p><pre><code class="language-jsx">const apiPerformanceObserver = new PerformanceObserver((list) =&gt; {
  list.getEntries()?.forEach((entry) =&gt; {
    const performanceEntry = entry.toJSON();
    if (
      [&apos;xmlhttprequest&apos;, &apos;fetch&apos;].includes(performanceEntry.initiatorType)
    ) {
        console.log(&apos;performanceEntry Filtered:: &apos;, entry);
    }
  });
});
apiPerformanceObserver.observe({ type: &apos;resource&apos;, buffered: true });
</code></pre><p>Notice the <code>if</code> condition, to filter out entries, based on the initiatorType. To learn more about initiatorType, please refer to <a href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/initiatorType">this</a>.</p><p>If you expand some of these entries, you will realize that nearly all have 0 as values for their keys, except the duration key, which is not ideal since we want the actual values. Let&apos;s discuss why this is happening and how to fix it.</p><h3 id="fixing-data-for-cross-origin-calls"><strong>Fixing data for cross-origin calls</strong></h3><p>Many resource timing properties are restricted to return&#xA0;<code>0</code>&#xA0;or an empty string when the resource is a cross-origin request. This is one of the security practices while dealing with CORS.</p><p>Since on <a href="https://app.dyte.io/v2/meeting?demo=Default">Dyte Demo</a>, Dyte is NOT sending the Timing-Allow-Origin response Header, you see most values as 0. This can be verified using the Network Tab.</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/04/Web_API_Performance_Monitoring--Asset_2.png" class="kg-image" alt="Monitoring Web API Performance" loading="lazy" width="1712" height="704" srcset="https://dyte.io/blog/content/images/size/w600/2024/04/Web_API_Performance_Monitoring--Asset_2.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/04/Web_API_Performance_Monitoring--Asset_2.png 1000w, https://dyte.io/blog/content/images/size/w1600/2024/04/Web_API_Performance_Monitoring--Asset_2.png 1600w, https://dyte.io/blog/content/images/2024/04/Web_API_Performance_Monitoring--Asset_2.png 1712w" sizes="(min-width: 720px) 720px"></figure><p>We are sending a Timing-Allow-Origin response header for one of the endpoints, <a href="https://location.dyte.io" rel="noreferrer">location.dyte.io</a>. The Performance Entry for this endpoint will have proper values. </p><p>(To learn more about this, check out our blog on <a href="https://dyte.io/blog/location-service/">building a fast IP location service</a>.)</p><figure class="kg-card kg-image-card"><img src="https://dyte.io/blog/content/images/2024/04/Web_API_Performance_Monitoring--Asset_3.png" class="kg-image" alt="Monitoring Web API Performance" loading="lazy" width="1552" height="790" srcset="https://dyte.io/blog/content/images/size/w600/2024/04/Web_API_Performance_Monitoring--Asset_3.png 600w, https://dyte.io/blog/content/images/size/w1000/2024/04/Web_API_Performance_Monitoring--Asset_3.png 1000w, https://dyte.io/blog/content/images/2024/04/Web_API_Performance_Monitoring--Asset_3.png 1552w" sizes="(min-width: 720px) 720px"></figure><p>Since this happens for CORS requests only, To expose cross-origin timing information, we need to send the&#xA0;<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin">Timing-Allow-Origin</a>&#xA0;HTTP response header using the backend codebase; only then can FE see the timing in the FE code.</p><p>You can add the Timing-Allow-Origin response header for your project in the following ways.</p><p>A NextJS config in the backend to return CORS response headers and Timing-Allow-Origin to every incoming call looks like this.</p><pre><code class="language-jsx">const nextConfig = {
    async headers() {
        return [
            {
                // matching all API routes
                source: &quot;/api/:path*&quot;,
                headers: [
                    { key: &quot;Access-Control-Allow-Credentials&quot;, value: &quot;true&quot; },
                    { key: &quot;Access-Control-Allow-Origin&quot;, value: &quot;*&quot; },
                    { key: &quot;Access-Control-Allow-Methods&quot;, value: &quot;GET,DELETE,PATCH,POST,PUT&quot; },
                    { key: &quot;Access-Control-Allow-Headers&quot;, value: &quot;X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version&quot; },
                    { key: &quot;Timing-Allow-Origin&quot;, value: &quot;*&quot; },
                ]
            }
        ]
    }
};
</code></pre><p>For ExpressJS, it could be something like the following code snippet in a middleware.</p><pre><code class="language-jsx">app.use(function(req, res, next) {
    res.header(&quot;Access-Control-Allow-Credentials&quot;, &quot;true&quot;);
    res.header(&quot;Access-Control-Allow-Origin&quot;, &quot;*&quot;);
    res.header(&quot;Access-Control-Allow-Methods&quot;, &quot;GET,DELETE,PATCH,POST,PUT&quot;);
    res.header(&quot;Access-Control-Allow-Headers&quot;, &quot;Origin, X-Requested-With, Content-Type, Accept&quot;);
    res.header(&quot;Timing-Allow-Origin&quot;, &quot;*&quot;);
    next();
});
</code></pre><p>Please refer to the security requirements <a href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming#security_requirements">here</a> to learn more about this security constraint.</p><p>To send even more timing information (BE timing information such as DB fetch and cache Hit) to FE, you can pass the timing information using the <a href="https://web.dev/articles/custom-metrics?utm_source=devtools#server-timing-api">Server Timing API</a>.</p><h3 id="sample-performanceresourcetiming-entry">Sample PerformanceResourceTiming Entry </h3><p>If you have configured the response headers properly, you will see proper values for all the keys.</p><p>Here is one such sample.</p><pre><code class="language-jsx">{
    &quot;name&quot;: &quot;&lt;https://location.dyte.io/&gt;&quot;,
    &quot;entryType&quot;: &quot;resource&quot;,
    &quot;startTime&quot;: 1041.699999999255,
    &quot;duration&quot;: 61,
    &quot;initiatorType&quot;: &quot;fetch&quot;,
    &quot;deliveryType&quot;: &quot;&quot;,
    &quot;nextHopProtocol&quot;: &quot;h2&quot;,
    &quot;renderBlockingStatus&quot;: &quot;non-blocking&quot;,
    &quot;workerStart&quot;: 0,
    &quot;redirectStart&quot;: 0,
    &quot;redirectEnd&quot;: 0,
    &quot;fetchStart&quot;: 1041.699999999255,
    &quot;domainLookupStart&quot;: 1071.199999999255,
    &quot;domainLookupEnd&quot;: 1071.199999999255,
    &quot;connectStart&quot;: 1071.199999999255,
    &quot;secureConnectionStart&quot;: 1081.199999999255,
    &quot;connectEnd&quot;: 1091.6000000014901,
    &quot;requestStart&quot;: 1091.699999999255,
    &quot;responseStart&quot;: 1102.3999999985099,
    &quot;firstInterimResponseStart&quot;: 0,
    &quot;responseEnd&quot;: 1102.699999999255,
    &quot;transferSize&quot;: 444,
    &quot;encodedBodySize&quot;: 144,
    &quot;decodedBodySize&quot;: 144,
    &quot;responseStatus&quot;: 200,
    &quot;serverTiming&quot;: []
}
</code></pre><h3 id="making-sense-of-the-performance-timing-data">Making sense of the Performance Timing data</h3><p>The above performance timing data is a snapshot for a resource. You can get <a href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming#typical_resource_timing_metrics">typical resource timing metrics</a> out of these entries/snapshots.</p><pre><code class="language-jsx">Measuring TCP handshake time (connectEnd - connectStart)
Measuring DNS lookup time (domainLookupEnd - domainLookupStart)
Measuring redirection time (redirectEnd - redirectStart)
Measuring interim request time (firstInterimResponseStart - requestStart)
Measuring request time (responseStart - requestStart)
Measuring TLS negotiation time (requestStart - secureConnectionStart)
Measuring time to fetch (without redirects) (responseEnd - fetchStart)
Measuring ServiceWorker processing time (fetchStart - workerStart)
Checking if content was compressed (decodedBodySize should not be encodedBodySize)
Checking if local caches were hit (transferSize should be 0)
Checking if modern and fast protocols are used (nextHopProtocol should be HTTP/2 or HTTP/3)
Checking if the correct resources are render-blocking (renderBlockingStatus)
</code></pre><p>Finally, you have this data in frontend. The next step is to send it to your BE endpoint to store it somewhere, e.g., NewRelic, DataDog, or DB, which we leave to you.</p><p>Performance API helped Dyte determine the timings of its new services, such as <a href="http://location.dyte.io">location.dyte.io</a>, to see how well they were performing globally.</p><p>You can learn more about PerformanceResourceTiming <a href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming">here</a>. To learn more about Performance API, please refer to <a href="https://developer.mozilla.org/en-US/docs/Web/API/Performance_API">these docs</a>.</p><p>We believe these Timing metrics will also help you test and improve your services.</p><p>Lastly, I hope you found this post informative and engaging. If you have any thoughts or feedback, please get in touch with me on&#xA0;<a href="https://twitter.com/rsr_thedarklord">Twitter</a>&#xA0;or&#xA0;<a href="https://www.linkedin.com/in/softwareprovider/">LinkedIn</a>. Stay tuned for more related blog posts in the future!</p><p><em>If you haven&apos;t heard about Dyte yet, head over to&#xA0;</em><a href="https://dyte.io/"><em>dyte.io</em></a><em>&#xA0;to learn how we are revolutionizing communication through our SDKs and libraries and how you can&#xA0;</em><a href="https://accounts.dyte.in/auth/register"><em>get started</em></a><em>&#xA0;quickly on your 10,000 free minutes, which renew every month. You can reach us at&#xA0;</em><a href="mailto:support@dyte.io"><em>support@dyte.io</em></a><em>&#xA0;or ask our&#xA0;</em><a href="https://community.dyte.io/"><em>developer community</em></a><em>.</em></p>]]></content:encoded></item></channel></rss>