<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Consuo Dev Blog</title>
    <description>The latest articles on DEV Community by Consuo Dev Blog (@consuo).</description>
    <link>https://dev.to/consuo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2397%2F4dddbeb6-611b-4c3b-8ca5-5c60f028dc6b.png</url>
      <title>DEV Community: Consuo Dev Blog</title>
      <link>https://dev.to/consuo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/consuo"/>
    <language>en</language>
    <item>
      <title>Lambda Function to Insert Ads with Consuo</title>
      <dc:creator>Jonas Birmé</dc:creator>
      <pubDate>Fri, 05 Jun 2020 07:34:51 +0000</pubDate>
      <link>https://dev.to/consuo/lambda-function-to-insert-ads-with-consuo-3eem</link>
      <guid>https://dev.to/consuo/lambda-function-to-insert-ads-with-consuo-3eem</guid>
      <description>&lt;p&gt;With Consuo you can create virtual linear TV channels from the video on demand files that you already have on your CDN and in this post we will describe how you can write a simple Lambda-function to insert ads between the programs in the channel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sLBT2q7R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ip3zau3738ii155s40sn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sLBT2q7R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ip3zau3738ii155s40sn.png" alt="Alt Text" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A Lambda function in this case is a code snippet that is running in the cloud without you having to think about servers. Both Amazon Web Service, Azure and Heroku offers this functionality and in this blog post we will be using the service provided by AWS that is called AWS Lambda.&lt;/p&gt;

&lt;p&gt;We will create a code snippet that will be given a URI to an HLS packaged video on demand file on the CDN and will rewrite the manifest of this HLS file and insert markers and ad segments. This modified manifest will then be scheduled in Consuo. Consuo preserves these ad markers in the virtual linear TV stream which means that by using a server-side ad replacer we can replace these ads with more targeted ads for a specific user.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z3nFGQOB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2u5ggrcfwy62ffb5q923.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z3nFGQOB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2u5ggrcfwy62ffb5q923.png" alt="Alt Text" width="880" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;We will create an endpoint that will respond with the modified HLS manifest and what will be served to a video player, or in our case, Consuo. As a query parameter you provide a base64 encoded payload with the location of the source VOD and the locations of the ads. Actually we will need two endpoints. One to handle the master manifest and another for each media manifest.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wNo1V-Kn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cocdm39vlgvisrc6ya65.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wNo1V-Kn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cocdm39vlgvisrc6ya65.png" alt="Alt Text" width="810" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The process will be as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Consuo (or a general video player) will be given an URI to the Lambda endpoint together with the base64 encoded JSON payload.&lt;/li&gt;
&lt;li&gt;Consuo fetches the master manifest from the Lambda endpoint. The Lambda endpoint will return a master manifest where the media manifest are pointing to the Lambda endpoint together with the base64 encoded payload.&lt;/li&gt;
&lt;li&gt;To start playing the media manifests are now fetched from this Lambda endpoint.&lt;/li&gt;
&lt;li&gt;When a media manifest request is handled the Lambda function will retrieve the original media manifest from the VOD CDN and parse it. It will then manipulate this media manifest and insert the ad segments, adding ad markers and other necessary HLS tags.&lt;/li&gt;
&lt;li&gt;It then returns this manipulated media manifest to Consuo or the video player. As it is only the manifest files that are manipulated the video segments will be still fetched directly from the CDN and not through this Lambda.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;The code to handle a master manifest request is shown below. Full &lt;a href="https://github.com/Eyevinn/lambda-stitch"&gt;Source Code&lt;/a&gt; is available on GitHub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleMasterManifestRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encodedPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryStringParameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Received request /master.m3u8 (payload=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;encodedPayload&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getMasterManifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encodedPayload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rewrittenManifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rewriteMasterManifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;encodedPayload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;generateManifestResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rewrittenManifest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;generateErrorResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to generate master manifest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And as previously mentioned it returns a rewritten master manifest where the media manifest locations from the original manifest has just been replaced to point to this Lambda endpoint instead.&lt;/p&gt;

&lt;p&gt;To handle the media manifest request we will have these lines of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleMediaManifestRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryStringParameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encodedPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryStringParameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Received request /media.m3u8 (bw=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bw&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, payload=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;encodedPayload&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hlsVod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createVodFromPayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encodedPayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;baseUrlFromSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;subdir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryStringParameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subdir&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mediaManifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hlsVod&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;getMediaManifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;generateManifestResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mediaManifest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;generateErrorResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to generate media manifest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the interesting function here is the &lt;code&gt;createVodFromPayload()&lt;/code&gt; which we can have a look into.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createVodFromPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encodedPayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;deserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encodedPayload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;vodOpts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrlFromSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;^(.*)/.*?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;vodOpts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subdir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;vodOpts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subdir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hlsVod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;HLSSpliceVod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vodOpts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hlsVod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;adpromises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;breaks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;breaks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;adpromises&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;hlsVod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insertAdAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;promiseFn&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;adpromises&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;promiseFn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;hlsVod&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It utilizes the open source library &lt;a href="https://www.npmjs.com/package/@eyevinn/hls-splice"&gt;@eyevinn/hls-splice&lt;/a&gt; to actually do the manifest manipulation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it available
&lt;/h2&gt;

&lt;p&gt;We now have the code for the Lambda function and once we have created a Lambda function in AWS we now need to make this available.&lt;/p&gt;

&lt;p&gt;To do that we need to add an Application Load Balancer where HTTP requests on port 80 are forwarded to a target group of type Lambda.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DTNtXiiW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rkww2rxdqibqx74yudub.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DTNtXiiW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rkww2rxdqibqx74yudub.png" alt="Alt Text" width="880" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Requests that are coming from the ALB are then handled in the main entry function of the Lambda.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/stitch/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;httpMethod&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;handleCreateRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/stitch/master.m3u8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;handleMasterManifestRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/stitch/media.m3u8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;handleMediaManifestRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;generateErrorResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Here is an example with a pre-roll ad: &lt;code&gt;http://lambda.eyevinn.technology/stitch/master.m3u8?payload=eyJ1cmkiOiJodHRwczovL21haXR2LXZvZC5sYWIuZXlldmlubi50ZWNobm9sb2d5L3N0c3dlMTctd2lsbGxhdy5tNHYvbWFzdGVyLm0zdTgiLCJicmVha3MiOlt7InBvcyI6MCwiZHVyYXRpb24iOjE2MDAwLCJ1cmwiOiJodHRwczovL21haXR2LXZvZC5sYWIuZXlldmlubi50ZWNobm9sb2d5L2Fkcy82Y2Q3ZDc2OC1lMjE0LTRlYmMtOWYxNC03ZWQ4OTcxMDExNWUubXA0L21hc3Rlci5tM3U4In1dfQ==&lt;/code&gt; and decoding the base64 show you the payload.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"https://maitv-vod.lab.eyevinn.technology/stswe17-willlaw.m4v/master.m3u8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"breaks"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"pos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;16000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"https://maitv-vod.lab.eyevinn.technology/ads/6cd7d768-e214-4ebc-9f14-7ed89710115e.mp4/master.m3u8"&lt;/span&gt;&lt;span class="p"&gt;}]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can use the example above and put it in a schedule in Consuo which could look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"channelId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyevinn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"assetId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:uuid:ee16c6bf-70b9-4246-9b70-b132b706beda"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"eventId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dd02e9ea-0ec4-4d26-9e6c-12a85e762c65"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:uuid:ee16c6bf-70b9-4246-9b70-b132b706beda"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STSWE17 Will Law"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1591335742455&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"end_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1591337431455&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2020-06-05T05:42:22.455Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2020-06-05T06:10:31.455Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://lambda.eyevinn.technology/stitch/master.m3u8?payload=eyJ1cmkiOiJodHRwczovL21haXR2LXZvZC5sYWIuZXlldmlubi50ZWNobm9sb2d5L3N0c3dlMTctd2lsbGxhdy5tNHYvbWFzdGVyLm0zdTgiLCJicmVha3MiOlt7InBvcyI6MCwiZHVyYXRpb24iOjE2MDAwLCJ1cmwiOiJodHRwczovL21haXR2LXZvZC5sYWIuZXlldmlubi50ZWNobm9sb2d5L2Fkcy9iYzU1ZmZkYy1kMDcxLTQ4NTgtYTk3ZC1jMjI5M2YwNTlmMTkubXA0L21hc3Rlci5tM3U4In1dfQ=="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1689&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Adding this Lambda function you can generate virtual linear TV channels with ads and using this in combination with a server-side ad inserter you can have linear TV channels with individually targeted ads. If you want to know more about Consuo visit &lt;a href="https://www.consuo.tv"&gt;www.consuo.tv&lt;/a&gt; and request a free 30-days trial.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>javascript</category>
      <category>hlssplice</category>
      <category>streaming</category>
    </item>
    <item>
      <title>Launching Virtual Channels on AWS with Consuo</title>
      <dc:creator>Jonas Birmé</dc:creator>
      <pubDate>Wed, 27 May 2020 20:37:33 +0000</pubDate>
      <link>https://dev.to/consuo/launching-virtual-channels-on-aws-with-consuo-32en</link>
      <guid>https://dev.to/consuo/launching-virtual-channels-on-aws-with-consuo-32en</guid>
      <description>&lt;p&gt;&lt;a href="//www.consuo.tv"&gt;Consuo&lt;/a&gt; is a cloud-agnostic software component to generate virtual linear TV channels, and in this blog post we will describe how you can deploy this component to AWS and not having to manage any server instances.&lt;/p&gt;

&lt;h1&gt;
  
  
  Elastic Container Service and Fargate
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html"&gt;Amazon ECS (Elastic Container Service)&lt;/a&gt; is a container management and orchestrator of Docker containers running on a cluster. With the Fargate launch type you run your cluster on a serverless infrastructure, and this is what we will use in the example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Task Definitions
&lt;/h2&gt;

&lt;p&gt;Before we create the ECS cluster we will start by defining two task definitions. One task definition for the Consuo Schedule container, and another for the Consuo Engine container.&lt;/p&gt;

&lt;p&gt;Let us begin with the task definition for the Consuo Schedule task. Some parts are left out to highlight the Consuo specifics.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"containerDefinitions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"entryPoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"portMappings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"hostPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"containerPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8001&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CORS_WHITELIST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com,https://www.example.com"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;LICENSEKEY&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyevinntechnology/consuo-schedule:v1.1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"consuo-schedule"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requiresCompatibilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We define the port mappings for the default port that is 8001 for Consuo Schedule and provide the environment variables &lt;code&gt;CORS_WHITELIST&lt;/code&gt; and the license key. For a full list of available environment variables and their purpose read the &lt;a href="//docs.consuo.tv"&gt;Consuo documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We then define a similar task definition for the Consuo Engine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"containerDefinitions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"entryPoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"portMappings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"hostPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"containerPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CHANNELMGRAPI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://&amp;lt;CONSUO_SCHEDULE_IP&amp;gt;:8001"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;LICENSEKEY&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyevinntechnology/consuo-engine:v1.1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"consuo-schedule"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requiresCompatibilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the moment we have a placeholder for the &lt;code&gt;CHANNELMGRAPI&lt;/code&gt; variable and we will later replace it with the URI that the Consuo Schedule API will be accessible on. In both task definitions we define that we want to use Fargate as the launch type for these tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cluster and Services
&lt;/h2&gt;

&lt;p&gt;Then we will create the cluster for Consuo services in ECS. Once the cluster is created we will then create two services. One for the Consuo Schedule service and another for the Consuo Engine service.&lt;/p&gt;

&lt;p&gt;When creating the services we will use each of the task definitions defined above and service type &lt;code&gt;REPLICA&lt;/code&gt; and launch type &lt;code&gt;FARGATE&lt;/code&gt;. For the Schedule service you need to ensure that the security group allows TCP traffic on port 8001 and for the Engine service open up port 8000, as this is what we specified in the task definitions. Number of replicas we set initially to 1.&lt;/p&gt;

&lt;h2&gt;
  
  
  Load Balancing
&lt;/h2&gt;

&lt;p&gt;To be able to access these services we need to setup a load balancer. We will use the AWS Application Load Balancer so we can reuse the same instance for both services. We will have one target group for each of the services. The ALB we configure with two listeners: HTTP 8000 and HTTP 8001 where port 8000 forwards to the Consuo engine target group and port 8001 forwards traffic to the Consuo schedule target group.&lt;/p&gt;

&lt;p&gt;Once everything is setup you will then be able to access the Consuo schedule service on for example: &lt;code&gt;http://alb-consuo-486438318.eu-north-1.elb.amazonaws.com:8001/api/docs/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And now you can update the &lt;code&gt;CHANNELMGRAPI&lt;/code&gt; variable to point to for example &lt;code&gt;http://alb-consuo-486438318.eu-north-1.elb.amazonaws.com:8001&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you don't intend to have the schedule accessible outside of the cluster we could use the service discovery functionality and have the Consuo schedule container only accessible by the Consuo engine container. That is left as an exercise for the reader.&lt;/p&gt;

&lt;h2&gt;
  
  
  CDN
&lt;/h2&gt;

&lt;p&gt;To off-load the traffic to the Consuo engine service it is recommended to place a CDN in front of it. With AWS we can use their CDN service Cloudfront to do this. This also gives a possibility to have SSL termination which is today needed to be able to play the stream in a browser. You create a distribution where you specify the ALB as the origin and origin port 8000. Important to configure the distribution to &lt;code&gt;Use Origin Cache Headers&lt;/code&gt; and query string whitelist &lt;code&gt;channel&lt;/code&gt;. You might also want to override the caching TTLs for error codes such as 404 and 500, to avoid getting 404s stuck in the caches.&lt;/p&gt;

&lt;p&gt;When everything is up and running you will now have a system setup that looks similar to this drawing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j1uqLLkg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/i2drh1xe20pek7o57mmr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j1uqLLkg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/i2drh1xe20pek7o57mmr.png" alt="Alt Text" width="880" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>videostreaming</category>
      <category>docker</category>
      <category>ecs</category>
    </item>
    <item>
      <title>To build the Consuo component ecosystem</title>
      <dc:creator>Erik Hoffman</dc:creator>
      <pubDate>Tue, 26 May 2020 04:54:46 +0000</pubDate>
      <link>https://dev.to/consuo/to-build-the-consuo-component-ecosystem-4p46</link>
      <guid>https://dev.to/consuo/to-build-the-consuo-component-ecosystem-4p46</guid>
      <description>&lt;p&gt;At Eyevinn we’ve always valued platform and product independence as well as taking a neutral stand when it comes to languages and frameworks. Therefore, when building components for &lt;a href="https://www.consuo.tv/"&gt;Consuo&lt;/a&gt;, our vod-to-live stitching product, it was a quite obvious choice for us to not only build a React component, which may have been the simplest decision, but also a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components"&gt;web component for vanilla JavaScript&lt;/a&gt; projects with no framework being used.&lt;/p&gt;

&lt;p&gt;A lot of JavaScript developers these days thinks that you need a framework such as React or Vue to be able to create encapsulated components to be shareable. Though over the last few years standards as been moving quite fast and the web component standard as such give us all the features that we expects and wants out of modern JavaScript solution for building a component to act isolated as well as being shared in a simple way, both internally as well as externally (e.g. &lt;a href="https://www.webcomponents.org/"&gt;webcomponents.org&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Since before we already had an &lt;a href="https://www.npmjs.com/package/@consuo/react-up-next"&gt;Up Next component for Consuo, built and shared as a React component&lt;/a&gt;. As soon as it had been shared internally I took the challenge to create an &lt;a href="https://www.npmjs.com/package/@consuo/web-component-up-next"&gt;exact replica as an web component&lt;/a&gt;, as I as a developer loves standard vanilla, no framework, solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do we need an ecosystem for Consuo?
&lt;/h2&gt;

&lt;p&gt;Consuo as a product, or rather as a base platform together with &lt;a href="https://github.com/Eyevinn/channel-engine"&gt;its open source engine&lt;/a&gt;, is the core of a wider end user experience. We want to bring the laid back experience of classic linear tv into the new era of streaming, as well as bringing the unused potential back to the streaming companies backlog of clips and vod videos in terms of consumption and monetization.&lt;/p&gt;

&lt;p&gt;Consuo as a core handles the scheduling as well as the stitching of the channel, creating the core experience. Though we want to help you evolve that experience by bringing more functionality to the table by building a base plate of open source components to be used. This starts with our schedule related components to show what’s currently on air and what’s next up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework decisions and framework neutrality?
&lt;/h2&gt;

&lt;p&gt;We started to build this component as a React component, as this is currently the framework of choice for our product web as well as demo solutions. But as mentioned earlier, we have a history and pride of being language and framework agnostic at Eyevinn. And we do really want this experience and these components to be available for everyone to implement and to use.&lt;/p&gt;

&lt;p&gt;With the evolvement of the quite recent web component standard it was a simple decision to take, to develop such alternative as well. As this can be used in all frameworks, not isolated to a single one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The state of web components
&lt;/h2&gt;

&lt;p&gt;As mentioned earlier The state of web components in style JavaScript standard is quite exciting these days. The api as such might not be as simplified and fine tuned as in React or Polymer, but all the functionality as expected is there, and to avoid the overhead of an framework as well as creating a shareable component not dependent on the framework being used by the implementing part, is a quite exciting thing.&lt;/p&gt;

&lt;p&gt;So what can we do?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose to be reachable by outer JavaScript or to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM"&gt;entirely encapsulate&lt;/a&gt; the functionality.&lt;/li&gt;
&lt;li&gt;Choose to be reachable by outer styling or to entirely encapsulate our styles.&lt;/li&gt;
&lt;li&gt;Act on being attached, as well as detached, from a document. (See &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#Using_the_lifecycle_callbacks"&gt;Life Cycle Callbacks&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Act on the attributes of the element being changed during runtime.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means that we can choose whether we want to build an ecosystem by contributed functionality only, or by encapsulated visually designed components that matches our product’s graphical profile.&lt;/p&gt;

&lt;p&gt;For this specific component we just wanted to deliver the functionality of a simple schedule visualizer and do not encapsulate any specified styling.&lt;/p&gt;

&lt;p&gt;So let’s go to the functionality of the Up Next component as such and to compare a React component to what we can build in native JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  The feature set
&lt;/h2&gt;

&lt;p&gt;The component as such solves a rather basic scenario which can be seen as a base plate functionality in any linear channel experience - Showing what’s currently playing and what’s Up Next.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9uuSG8Za--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/2k1WTiI.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9uuSG8Za--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/2k1WTiI.png" alt="" width="880" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is implemented by pulling the schedule from which the linear experience is created out of the video files that already exists in the archive.&lt;/p&gt;

&lt;p&gt;An example of such schedule json&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"channelId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyevinn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"assetId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:uuid:da80b21b-2e6e-42ae-82b8-3b1b5581b59a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"eventId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"735fecc8-cade-410d-993f-9860e4de9efe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:uuid:da80b21b-2e6e-42ae-82b8-3b1b5581b59a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TV Plus Joachim"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1590226668810&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"end_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1590226741810&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2020-05-23T09:37:48.810Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2020-05-23T09:39:01.810Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://lambda.eyevinn.technology/stitch/master.m3u8?payload=eyJ1cmkiOiJodHRwczovL21haXR2LXZvZC5sYWIuZXlldmlubi50ZWNobm9sb2d5L3R2cGx1cy1hZC1qb2FjaGltLm1vdi9tYXN0ZXIubTN1OCIsImJyZWFrcyI6W3sicG9zIjowLCJkdXJhdGlvbiI6MTYwMDAsInVybCI6Imh0dHBzOi8vbWFpdHYtdm9kLmxhYi5leWV2aW5uLnRlY2hub2xvZ3kvYWRzL3NmZi0xNXMubXA0L21hc3Rlci5tM3U4In1dfQ=="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;73&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"channelId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyevinn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"assetId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:uuid:b8ff551a-6da3-485a-8a53-b11c5d28753f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"eventId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"c25f531a-3bf5-4645-b3f0-cbaf4c7f459d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:uuid:b8ff551a-6da3-485a-8a53-b11c5d28753f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TV Plus Johanna"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1590226741810&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"end_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1590226816810&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2020-05-23T09:39:01.810Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2020-05-23T09:40:16.810Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://lambda.eyevinn.technology/stitch/master.m3u8?payload=eyJ1cmkiOiJodHRwczovL21haXR2LXZvZC5sYWIuZXlldmlubi50ZWNobm9sb2d5L3R2cGx1cy1hZC1qb2hhbm5hLm1vdi9tYXN0ZXIubTN1OCIsImJyZWFrcyI6W3sicG9zIjowLCJkdXJhdGlvbiI6MTYwMDAsInVybCI6Imh0dHBzOi8vbWFpdHYtdm9kLmxhYi5leWV2aW5uLnRlY2hub2xvZ3kvYWRzLzZjZDdkNzY4LWUyMTQtNGViYy05ZjE0LTdlZDg5NzEwMTE1ZS5tcDQvbWFzdGVyLm0zdTgifV19"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  React component vs Web Component
&lt;/h2&gt;

&lt;p&gt;Both these components are built in quite a similar way as the feature set is quite similar, as mentioned earlier on in this post. So the things that really differs is the markup as such, the state handling and the rendering language implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Life cycle handling
&lt;/h3&gt;

&lt;p&gt;Life cycles in &lt;a href="https://programmingwithmosh.com/javascript/react-lifecycle-methods/"&gt;React&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#Using_the_lifecycle_callbacks"&gt;Web Components&lt;/a&gt; exposes the same possibilities, though with different terminology and minor limitations.&lt;/p&gt;

&lt;p&gt;A simple table of the most common life cycle events can be shown as this&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Event&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;React Component&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Web Component&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Added to DOM&lt;/td&gt;
&lt;td&gt;componentDidMount&lt;/td&gt;
&lt;td&gt;connectedCallback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rendering&lt;/td&gt;
&lt;td&gt;render&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attributes being changed&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;attributeChangedCallback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Removed from DOM&lt;/td&gt;
&lt;td&gt;componentWillUnmount&lt;/td&gt;
&lt;td&gt;disconnectedCallback&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As seen the main structure is the same between the solutions, though the missing part doesn't mean that the functionality is missed as such.&lt;/p&gt;

&lt;p&gt;React handles the changed attributes by simply changing the values on the &lt;em&gt;props&lt;/em&gt; of the component, and re-render the component with those values in mind. In the web component you will have to trigger the render function yourself, both when added to DOM (the &lt;code&gt;connectedCallback&lt;/code&gt; event) as well when you get the event for changed attribute values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rendering
&lt;/h3&gt;

&lt;p&gt;The other major difference is how the rendering is handled. In React you render your inner html with a markup language called &lt;a href="https://reactjs.org/docs/introducing-jsx.html"&gt;JSX&lt;/a&gt;. This gives us the ability to get great autocompletion in your code editor as well as evaluating expressions and values of properties to take visualisation decision based on those. For Web Components on the other hand, we will rather create the inner html elements in JavaScript and append them as childs or, as I prefer to do, write the html as such in a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals"&gt;template literal&lt;/a&gt;. Even though this doesn't bring the excellent editor functionality as JSX might give us, we will have the same ability to handle expressions and property values in the rendition.&lt;/p&gt;

&lt;p&gt;React Example&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y4ZNmuNa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/rpS7NGo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y4ZNmuNa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/rpS7NGo.png" alt="" width="880" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Web Component Example&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---VQxCui4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/npqGOk4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---VQxCui4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/npqGOk4.png" alt="" width="880" height="719"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;To take the extra step and deliver a cross framework component is, as seen, quite a small job - bringing not only a value to the ecosystem that we want to build, but also letting the customer utilise our knowledge and development undependent on their framework choices.&lt;/p&gt;

&lt;p&gt;The components can be downloaded through npm (&lt;a href="https://www.npmjs.com/package/@consuo/react-up-next"&gt;React component&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/@consuo/web-component-up-next"&gt;web component&lt;/a&gt;) and the source code can be found on our Github (&lt;a href="https://github.com/Eyevinn/consuo-react-up-next"&gt;React component&lt;/a&gt; and &lt;a href="https://github.com/Eyevinn/consuo-web-component-up-next"&gt;web component&lt;/a&gt;). Please feel free to contribute, either to the libraries and components that already exists or to the ecosystem around the &lt;a href="https://github.com/Eyevinn/channel-engine"&gt;Channel engine&lt;/a&gt; and Consuo by building open source functionality around them.&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Using the in-built Auto Scheduler</title>
      <dc:creator>Jonas Birmé</dc:creator>
      <pubDate>Sat, 23 May 2020 11:22:07 +0000</pubDate>
      <link>https://dev.to/consuo/using-the-in-built-auto-scheduler-4mln</link>
      <guid>https://dev.to/consuo/using-the-in-built-auto-scheduler-4mln</guid>
      <description>&lt;p&gt;The quickest way to get started and creating a virtual channel is to use the in-built auto-scheduler. In this blog post we will walk you through how this is done.&lt;/p&gt;

&lt;p&gt;In the Consuo Schedule micro service, an automatic scheduler is included that can automatically add events to a channel's schedule. The assets for the events are randomly chosen and four events at the time are added back-to-back in the schedule. This can be used to create numerous virtual channel based on a search query in the VOD library. For example a virtual channel for all news clips about a news event that is unfolding.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T8Xs2kfq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lcg0kdw4ktblp3de623w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T8Xs2kfq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lcg0kdw4ktblp3de623w.png" alt="Using auto-scheduler" width="880" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When creating a new channel you define that this channel should use the automatic scheduler and you provide an URI to an Atom RSS feed containing the assets the automatic scheduler can choose from. Between your video asset search you build an adapter that translates the response into an Atom RSS xml. The mandatory fields are &lt;code&gt;&amp;lt;id&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; but a title is recommended to also be provided.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;feed xmlns="http://www.w3.org/2005/Atom"&amp;gt;
  &amp;lt;title&amp;gt;Content from Eyevinn&amp;lt;/title&amp;gt;
  &amp;lt;id&amp;gt;urn:uuid:883e6f00-783e-46d0-bb6c-0e15e6ac4e33&amp;lt;/id&amp;gt;
  &amp;lt;updated&amp;gt;2020-05-22T08:48:00Z&amp;lt;/updated&amp;gt;
  &amp;lt;entry&amp;gt;
    &amp;lt;id&amp;gt;urn:uuid:23d1d228-2305-4bc8-a937-1280a0d7e33a&amp;lt;/id&amp;gt;
    &amp;lt;title&amp;gt;Tears of Steel&amp;lt;/title&amp;gt;
    &amp;lt;link&amp;gt;https://maitv-vod.lab.eyevinn.technology/tearsofsteel_4k.mov/master.m3u8&amp;lt;/link&amp;gt;
  &amp;lt;/entry&amp;gt;
  &amp;lt;entry&amp;gt;
    &amp;lt;id&amp;gt;urn:uuid:1555b26b-ecdd-4658-b527-e35b6429ef58&amp;lt;/id&amp;gt;
    &amp;lt;title&amp;gt;VINN&amp;lt;/title&amp;gt;
    &amp;lt;link&amp;gt;https://maitv-vod.lab.eyevinn.technology/VINN.mp4/master.m3u8&amp;lt;/link&amp;gt;
  &amp;lt;/entry&amp;gt;
&amp;lt;/feed&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To create a channel with automatic scheduling enabled you issue an HTTP POST with the following JSON to the &lt;code&gt;/channels&lt;/code&gt; endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "id": "mychannel",
  "name": "My Automatic Channel",
  "config": {
    "auto_populate": true,
    "auto_populate_content": &amp;lt;RSS_URI&amp;gt;,   
    "auto_cleanup": true
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The config parameter &lt;code&gt;auto_populate&lt;/code&gt; when set to true enables the automatic scheduler and the &lt;code&gt;auto_populate_content&lt;/code&gt; parameter points to the RSS feed.&lt;/p&gt;

&lt;p&gt;To verify that the automatic scheduler is working you can fetch the schedule for this channel with an HTTP GET of &lt;code&gt;/channels/mychannel/schedule&lt;/code&gt; and you would get something similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  {
    "channelId": "eyevinn",
    "assetId": "urn:uuid:da80b21b-2e6e-42ae-82b8-3b1b5581b59a",
    "eventId": "735fecc8-cade-410d-993f-9860e4de9efe",
    "id": "urn:uuid:da80b21b-2e6e-42ae-82b8-3b1b5581b59a",
    "title": "TV Plus Joachim",
    "start_time": 1590226668810,
    "end_time": 1590226741810,
    "start": "2020-05-23T09:37:48.810Z",
    "end": "2020-05-23T09:39:01.810Z",
    "uri": "http://lambda.eyevinn.technology/stitch/master.m3u8?payload=eyJ1cmkiOiJodHRwczovL21haXR2LXZvZC5sYWIuZXlldmlubi50ZWNobm9sb2d5L3R2cGx1cy1hZC1qb2FjaGltLm1vdi9tYXN0ZXIubTN1OCIsImJyZWFrcyI6W3sicG9zIjowLCJkdXJhdGlvbiI6MTYwMDAsInVybCI6Imh0dHBzOi8vbWFpdHYtdm9kLmxhYi5leWV2aW5uLnRlY2hub2xvZ3kvYWRzL3NmZi0xNXMubXA0L21hc3Rlci5tM3U4In1dfQ==",
    "duration": 73
  },
  {
    "channelId": "eyevinn",
    "assetId": "urn:uuid:b8ff551a-6da3-485a-8a53-b11c5d28753f",
    "eventId": "c25f531a-3bf5-4645-b3f0-cbaf4c7f459d",
    "id": "urn:uuid:b8ff551a-6da3-485a-8a53-b11c5d28753f",
    "title": "TV Plus Johanna",
    "start_time": 1590226741810,
    "end_time": 1590226816810,
    "start": "2020-05-23T09:39:01.810Z",
    "end": "2020-05-23T09:40:16.810Z",
    "uri": "http://lambda.eyevinn.technology/stitch/master.m3u8?payload=eyJ1cmkiOiJodHRwczovL21haXR2LXZvZC5sYWIuZXlldmlubi50ZWNobm9sb2d5L3R2cGx1cy1hZC1qb2hhbm5hLm1vdi9tYXN0ZXIubTN1OCIsImJyZWFrcyI6W3sicG9zIjowLCJkdXJhdGlvbiI6MTYwMDAsInVybCI6Imh0dHBzOi8vbWFpdHYtdm9kLmxhYi5leWV2aW5uLnRlY2hub2xvZ3kvYWRzLzZjZDdkNzY4LWUyMTQtNGViYy05ZjE0LTdlZDg5NzEwMTE1ZS5tcDQvbWFzdGVyLm0zdTgifV19",
    "duration": 75
  }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you see in the example above the schedule events are all back-to-back and that is taken care of by the automatic scheduler.&lt;/p&gt;

&lt;p&gt;The config parameter &lt;code&gt;auto_cleanup&lt;/code&gt; specifies whether old events should be automatically removed.&lt;/p&gt;

&lt;p&gt;This automatic scheduler is at the moment very basic and does not prevent that same assets in a row are scheduled and if you intend more sophisticated logic you can build your own automatic scheduler using the APIs provided. &lt;/p&gt;

&lt;p&gt;And how to do that, we leave room for in another blog post. Stay tune!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Schedule events for your first virtual channel</title>
      <dc:creator>Jonas Birmé</dc:creator>
      <pubDate>Sat, 16 May 2020 12:00:10 +0000</pubDate>
      <link>https://dev.to/consuo/schedule-events-for-your-first-virtual-channel-265a</link>
      <guid>https://dev.to/consuo/schedule-events-for-your-first-virtual-channel-265a</guid>
      <description>&lt;p&gt;In this blog we will walk you through how you by using the Consuo Schedule API can create a virtual channel and add events to the schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-requisities
&lt;/h2&gt;

&lt;p&gt;You have already an instance of Consuo up and running and you replace &lt;code&gt;&amp;lt;CHANNELMGR_IP&amp;gt;&lt;/code&gt; in this post with the IP-address to where your Consuo Schedule manager is running. For instructions on how to setup and install Consuo you can read the &lt;a href="https://docs.consuo.tv/GET-STARTED.html"&gt;Quick Start guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create channel
&lt;/h2&gt;

&lt;p&gt;We will use the Consuo Schedule API to create a channel. You find the API documentation at &lt;code&gt;http://&amp;lt;CHANNELMGR_IP&amp;gt;:8001/api/docs/&lt;/code&gt;. In the examples in this post we will use Node JS but other programming languages can be used as the API is HTTP based. What we will do first is to create a channel.&lt;/p&gt;

&lt;p&gt;In this example we use &lt;code&gt;fetch&lt;/code&gt; as HTTP client and to create a channel we do an HTTP POST with the following body to the endpoint &lt;code&gt;/channels&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  id: "one",
  name: "Channel One"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const fetch = require('node-fetch');
const API = "http://&amp;lt;CHANNELMGR_IP&amp;gt;:8001";

async function run() {
  const res = await fetch(API + '/channels', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      id: "one",
      name: "Channel One"
    })
  });
  const json = await res.json();
  console.log(json);
}

run();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you have a channel created that has the ID &lt;code&gt;one&lt;/code&gt; and the name &lt;code&gt;Channel One&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add an event
&lt;/h2&gt;

&lt;p&gt;Next, let us schedule an event. An event has a start, end and a URI to the video file to be played out. The format of the video file must be HLS as that is what Consuo supports today. The start time of the event is provided as a Unix Timestamp in milliseconds and all timestamps are in UTC timezone.&lt;/p&gt;

&lt;p&gt;As an example, we want to create an event that starts on May 16 at 14:00 CET (UTC+02:00).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const start_time = Date.parse("16 May 2020 14:00:00+02:00");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example &lt;code&gt;start_time&lt;/code&gt; is then &lt;code&gt;1589630400000&lt;/code&gt;. To calculate the &lt;code&gt;end_time&lt;/code&gt; we need to add the duration of the video file that we want to schedule. The video file we want to schedule is &lt;code&gt;https://maitv-vod.lab.eyevinn.technology/VINN.mp4/master.m3u8&lt;/code&gt; and it has a duration of 106 seconds. To calculate the &lt;code&gt;end_time&lt;/code&gt; we need to multiply the duration with 1000 as the timestamps are in milliseconds and then we add this to the &lt;code&gt;start_time&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const end_time = start_time + (106 * 1000);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The event we want to schedule then looks as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const event = {
  assetId: "VINN",
  title: "VINN",
  start_time: Date.parse("16 May 2020 14:00:00+02:00"),
  end_time: start_time + (106 * 1000),
  uri: "https://maitv-vod.lab.eyevinn.technology/VINN.mp4/master.m3u8",
  duration: 106
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we make an HTTP POST with the above JSON to &lt;code&gt;/channels/one/schedule&lt;/code&gt;. The schedule for this channel will now include this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  {
    "assetId": "VINN",
    "eventId": "4c605714-a3db-48e5-a5af-d0ece30246de",
    "id": "VINN",
    "title": "VINN",
    "start_time": 1589630400000,
    "end_time": 1589630506000,
    "start": "2020-05-16T12:00:00.000Z",
    "end": "2020-05-16T12:01:46.000Z",
    "uri": "https://maitv-vod.lab.eyevinn.technology/VINN.mp4/master.m3u8",
    "duration": 106
  }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code example for the above can then look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const uri = "https://maitv-vod.lab.eyevinn.technology/VINN.mp4/master.m3u8";
  const start_time = Date.parse("16 May 2020 14:00:00+02:00");
  const end_time = start_time + (106 * 1000);
  const event = {
    assetId: 'VINN',
    title: 'VINN',
    start_time: start_time,
    end_time: end_time,
    uri: uri,
    duration: 106
  };
  const res = await fetch(API + '/channels/one/schedule', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(event)
  });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Playing the channel
&lt;/h2&gt;

&lt;p&gt;To start playing the channel that you just created and added events to you need an HLS capable video player. If you just want to test you can use the HTML player at &lt;a href="http://player.eyevinn.technology/"&gt;http://player.eyevinn.technology/&lt;/a&gt; and enter the URI &lt;code&gt;http://&amp;lt;ENGINE_IP&amp;gt;:8000/live/master.m3u8?channel=one&lt;/code&gt;. The &lt;code&gt;&amp;lt;ENGINE_IP&amp;gt;&lt;/code&gt; is the IP to the Consuo Engine service.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>streaming</category>
      <category>vodtolive</category>
      <category>node</category>
    </item>
  </channel>
</rss>
