<?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: Evgeny Kot</title>
    <description>The latest articles on DEV Community by Evgeny Kot (@bunopus).</description>
    <link>https://dev.to/bunopus</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F721159%2F9f655afb-1235-4fdb-9684-8a28f206f63b.jpeg</url>
      <title>DEV Community: Evgeny Kot</title>
      <link>https://dev.to/bunopus</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bunopus"/>
    <language>en</language>
    <item>
      <title>Saving videos to cloud in Flutter</title>
      <dc:creator>Evgeny Kot</dc:creator>
      <pubDate>Fri, 08 Oct 2021 11:42:16 +0000</pubDate>
      <link>https://dev.to/bunopus/saving-videos-to-cloud-in-flutter-57ld</link>
      <guid>https://dev.to/bunopus/saving-videos-to-cloud-in-flutter-57ld</guid>
      <description>&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;If you ever thought: &lt;em&gt;"Video-calls, VoIP, codecs, all these things, it's so complicated!&lt;/em&gt; 🤯 &lt;em&gt;I can't code it myself"&lt;/em&gt; – you're right. This area can be quite challenging for beginners (like me). Some time ago I got an idea for an app: &lt;em&gt;what if in dangerous situations I can press one button on my phone and record a video directly to the cloud?&lt;/em&gt; But preparing servers, understanding protocols, codecs and the whole world of telephony – that's not the thing I want (and can) do in one evening.&lt;br&gt;
Turns out it's much easier than I thought! In this article I will show you how to create an uber-simple Flutter app that can save video from the camera to the cloud server.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 This article describes how to use &lt;strong&gt;VoxImplant&lt;/strong&gt; platform. You need to bear in mind that this is a paid service, so please check their &lt;a href="https://voximplant.com/pricing"&gt;pricelist&lt;/a&gt; before jumping in.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Content
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How to prepare platform&lt;/li&gt;
&lt;li&gt;
Client

&lt;ul&gt;
&lt;li&gt;Preparations&lt;/li&gt;
&lt;li&gt;Camera permissions and debugging&lt;/li&gt;
&lt;li&gt;Client code&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;li&gt;Links&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Preparing platform &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Before we get our fingers dirty, we need to prepare the platform. VoxImplant provides a user-friendly interface and rich API for their services, so let's cook the backend part of our app.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new account on &lt;a href="//voximplant.com"&gt;voximplant.com&lt;/a&gt; and login into the dashboard.
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h4_RdTTS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/50v5yw4rjkaj9acfbxiw.png" alt="Dashboard"&gt;
&lt;/li&gt;
&lt;li&gt;Open left menu -&amp;gt; Applications -&amp;gt; Create application
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--S-gc9MFb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0jpgu94jkrj6p7rcf8hf.png" alt="Create application"&gt;
Name it as you wish (in my case - &lt;code&gt;recorder&lt;/code&gt;). Click at the created application to go to its settings.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the app dashboard go to Users -&amp;gt; Create user&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cXIrdTaN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qik4mkqvmu739epcy4qz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cXIrdTaN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qik4mkqvmu739epcy4qz.png" alt="Create user"&gt;&lt;/a&gt;Choose name and password.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 I used &lt;em&gt;"Separate account balance"&lt;/em&gt; checkbox, so this user would have separate balance from main account. You can call me paranoid, but it's never too much security!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally we can add some code. Go to Scenarios -&amp;gt; New Scenario. I called mine &lt;code&gt;video-recorder&lt;/code&gt;. Copy-Paste this code to the editor:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//subscribe to the events&lt;/span&gt;
&lt;span class="nx"&gt;VoxEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AppEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CallAlerting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CallEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Connected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleCallConnected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CallEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RecordStarted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleRecordStarted&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CallEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;VoxEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terminate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CallEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Disconnected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;VoxEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terminate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleCallConnected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Record call including video&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;video&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleRecordStarted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Send video URL to client&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&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;/code&gt;&lt;/pre&gt;



&lt;p&gt;It's self-explanatory, but in short: we subscribe to &lt;code&gt;Connected&lt;/code&gt; event and record incoming video to the cloud. When recording is started (&lt;code&gt;RecordStarted&lt;/code&gt; event), we send message with its link to the client. For the sake of simplicity we will not add video player to the client, so this code is here just to show how to do that.&lt;/p&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And the last thing! We should add a router that will handle our calls. Go to Routing -&amp;gt; Create rule. In my case the pattern accepts all incoming calls, but in a production environment you should change it to something meaningful. Choose our scenario from the box.&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q8ztPzoZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4hi682hydbj752q7795b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q8ztPzoZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4hi682hydbj752q7795b.png" alt="Create rule"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that's it with the platform. So simple! Now we can switch to client part.&lt;/p&gt;




&lt;h2&gt;
  
  
  Client &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Preparations &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 If you don't want to code client yourself - just clone it:&lt;br&gt;
&lt;code&gt;git clone git@github.com:bunopus/video_recorder.git&lt;/code&gt;&lt;br&gt;
Don't forget to add your &lt;em&gt;USERNAME&lt;/em&gt; and &lt;em&gt;PASSWORD&lt;/em&gt;. Create .env file in root folder with following content and run an app.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  USER=USERNAME@YOUR_APP_URL
  PASSWORD=ACTUAL_PASSWORD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's start preparing our client. I assume that you already have Flutter SDK up and running. If not - check their &lt;a href="https://flutter.dev/docs/get-started/install"&gt;docs&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create empty project&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter create video_recorder
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add &lt;code&gt;flutter_voximplant&lt;/code&gt; package with&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter pub add flutter_voximplant
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We don't need any other packages, at least for our super-simple app. You can check that it's up and running with &lt;code&gt;flutter run&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Camera access and debugging &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Before we continue I would like to say a couple of words about debugging apps that use camera. Even though it should be straightforward – it's not 🤦‍♀️. At least for Android.  &lt;/p&gt;

&lt;p&gt;For VoxImplant plugin to work – add this into your project (&lt;a href="https://github.com/voximplant/flutter_voximplant/blob/master/README.md"&gt;docs&lt;/a&gt;)&lt;/p&gt;

&lt;h4&gt;
  
  
  iOS
&lt;/h4&gt;

&lt;p&gt;Add the following entry to your &lt;code&gt;Info.plist&lt;/code&gt; file, located in &lt;code&gt;&amp;lt;project root&amp;gt;/ios/Runner/Info.plist&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;&amp;lt;key&amp;gt;NSMicrophoneUsageDescription&amp;lt;/key&amp;gt;
&amp;lt;string&amp;gt;Microphone is required to make audio calls&amp;lt;/string&amp;gt;
&amp;lt;key&amp;gt;NSCameraUsageDescription&amp;lt;/key&amp;gt;
&amp;lt;string&amp;gt;Camera is required to make video calls&amp;lt;/string&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This entry allows your app to access the microphone and cameras.&lt;/p&gt;

&lt;h4&gt;
  
  
  Android
&lt;/h4&gt;

&lt;p&gt;It is required to add Java 8 support.&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;&amp;lt;project root&amp;gt;android/app/build.gradle&lt;/code&gt; file and add the following lines to ‘android’ section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that's not enough (check &lt;a href="https://www.oreilly.com/library/view/flutter-for-beginners/9781788996082/378b452d-3b50-4de5-9e98-8571b813a924.xhtml"&gt;this article&lt;/a&gt;)!&lt;br&gt;
For Android, go to &lt;code&gt;android/app/src/(main|debug|profile)/AndroidManifest.xml&lt;/code&gt; and add&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;manifest&lt;/span&gt; &lt;span class="na"&gt;xmlns:android=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/apk/res/android"&lt;/span&gt; &lt;span class="na"&gt;package=&lt;/span&gt;&lt;span class="s"&gt;"com.example.handson"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...  
  &lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.CAMERA"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.RECORD_AUDIO"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/manifest&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open the emulator and give access to an app.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tnWSaHJe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gi2eaedeyxoqn5xfddy1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tnWSaHJe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gi2eaedeyxoqn5xfddy1.png" alt="Access for app"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Best cross-platform solution for Flutter to handle permissions is &lt;a href="https://pub.dev/packages/permission_handler"&gt;permission_handler&lt;/a&gt; package. It removes neccesity to dance around your phone.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And if you want to use your &lt;strong&gt;actual web camera&lt;/strong&gt; and not jumping square that comes with Android emulator - you should change setting as advised &lt;a href="https://youtu.be/B4JVqKHsYzk"&gt;here&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/B4JVqKHsYzk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BUT&lt;/strong&gt; 🤦‍♀️ in my case, i've got one problem: i have several cams, connected to my computer, and in emulator settings i had only first one!&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mxJbzjU9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7c1exu546lxkku2yyqol.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mxJbzjU9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7c1exu546lxkku2yyqol.png" alt="Emulator settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hopefully it can be solved if you run your emulator with &lt;a href="https://stackoverflow.com/questions/63951011/how-to-access-webcam-connected-using-usb-in-android-avd"&gt;this command&lt;/a&gt; (on Mac)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/Library/Android/sdk/emulator
emulator @YOUR_DEVICE_NAME &lt;span class="nt"&gt;-camera-back&lt;/span&gt; webcam1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 For some reason you can't use &lt;code&gt;-camera-back webcam1 -camera-front webcam1&lt;/code&gt; &lt;a href="https://stackoverflow.com/questions/11857006/android-webcam-enable-in-emulator/11857261"&gt;at the same time&lt;/a&gt; 🤷‍♀️&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To check if the camera is working – open the camera app in your emulator and see face of a person who spent their life trying to run this $h1t 👁👄👁&lt;/p&gt;




&lt;h3&gt;
  
  
  Client code &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Finally we have done everything to start coding our client. Let's take a look at &lt;code&gt;lib/main.dart&lt;/code&gt;. At the init phase we're creating a client for the platform calling &lt;code&gt;Voximplant().getClient()&lt;/code&gt;.&lt;br&gt;
Then we login &lt;code&gt;_client.login()&lt;/code&gt; using the username and password created previously. To simplify code I store them in a .env file (and use &lt;a href="https://pub.dev/packages/flutter_dotenv"&gt;flutter_dotenv&lt;/a&gt; package). For your prouction app you can implement login screen, and use auth token (&lt;a href="https://github.com/voximplant/flutter_demos/blob/master/video_call/lib/services/auth_service.dart"&gt;example&lt;/a&gt;)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Use &lt;a href="mailto:user@application.account.voximplant.com"&gt;user@application.account.voximplant.com&lt;/a&gt; format to log in.&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_MyHomePageState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyHomePage&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;VIClient&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Voximplant&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getClient&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;VICall&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_call&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;AppState&lt;/span&gt; &lt;span class="n"&gt;_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialising&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;initState&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initState&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;_login&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;_login&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connect&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
      &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;login&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'USER'&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'PASSWORD'&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
      &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;{});&lt;/span&gt;
      &lt;span class="n"&gt;_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ready&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
      &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
      &lt;span class="o"&gt;});&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we press "Record", our app starts a call to the platform with &lt;code&gt;_client.call()&lt;/code&gt;. You can pass the &lt;code&gt;VICallSettings&lt;/code&gt; structure to specify codec and other parameters. Pay attention to the empty string passed as the first argument to &lt;code&gt;call&lt;/code&gt;: this is the route name for your call. If you remember, we put &lt;code&gt;.*&lt;/code&gt; in our &lt;code&gt;pattern&lt;/code&gt; field, so any name will be accepted. For production you can add a meaningful route here and then check it on the platform.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VICallSettings&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;_settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;videoFlags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VIVideoFlags&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;sendVideo:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;_call&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;settings:&lt;/span&gt; &lt;span class="n"&gt;_settings&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;_call&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;onCallConnected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_onCallConnected&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;_call&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;onCallDisconnected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_onCallDisconneced&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;_call&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;onMessageReceived&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_onMessage&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;_call?.onCallConnected&lt;/code&gt; and other callbacks are used to subscribe to events and change the UI of our app accordingly. &lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;_onMessage&lt;/code&gt; function we just log the video url that platform sends to us. You can then show it to the user with the help of &lt;a href="https://pub.dev/packages/video_player"&gt;video_player&lt;/a&gt; package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;_onMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VICall&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 There is a &lt;a href="https://github.com/voximplant/flutter_voximplant/issues/24"&gt;bug&lt;/a&gt; in &lt;code&gt;flutter_voximplant&lt;/code&gt; package that makes messages unusable on Android. Fix is already merged into master, but not published to pub. Hopefully it will be fixed soon.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When we need to end up the call, we're just calling method &lt;code&gt;_call?.hangup()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And the last thing we need to do is to close the connection when we don't need it. I'm doing that inside the &lt;code&gt;dispose&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="nd"&gt;@override&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;_logout&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logout&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getClientState&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;VIClientState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Disconnected&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;disconnect&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 &lt;code&gt;dispose&lt;/code&gt; method in Flutter widgets has couple of issues, for example &lt;a href="https://github.com/flutter/flutter/issues/28134"&gt;not called when app quit&lt;/a&gt; or &lt;a href="https://github.com/flutter/flutter/issues/64935"&gt;doesn't work as async&lt;/a&gt;. In a production environment you will need some workarounds to make sure that connection is closed properly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Conclusion &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Aaaand that's it 🎉! Our client is ready, and when we press the Record button (and then Stop), we can go to App -&amp;gt; Call history and see the happy face of a happy developer! 😃&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DFlZMJ_P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phqq6be21u0wj7g9fqkg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DFlZMJ_P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phqq6be21u0wj7g9fqkg.png" alt="Happy face"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Links &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/voximplant/flutter_voximplant"&gt;Voximplant flutter package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/voximplant/videorecorder"&gt;Typescript implementation of video recorder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://voximplant.com/docs/tutorials/recording/video-recording"&gt;Video recording tutorial&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;sup&gt;Illustration created by stories - &lt;a href="https://www.freepik.com/vectors/mobile"&gt;&lt;/a&gt;&lt;a href="http://www.freepik.com"&gt;www.freepik.com&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>video</category>
      <category>voximplant</category>
    </item>
  </channel>
</rss>
