├── LICENSE ├── README.md ├── android ├── android.iml ├── app │ ├── app.iml │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ ├── com │ │ │ └── cybercode │ │ │ │ └── flutter_openvidu_demo │ │ │ │ └── MainActivity.java │ │ └── io │ │ │ └── flutter │ │ │ └── plugins │ │ │ └── GeneratedPluginRegistrant.java │ │ └── res │ │ ├── drawable │ │ └── launch_background.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── settings_aar.gradle ├── assets └── imgs │ ├── offline_icon.png │ └── openvidu_logo.png ├── flutter_openvidu_demo.iml ├── gif └── flutter_openvidu_demo.gif ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ ├── Flutter.podspec │ └── Release.xcconfig ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── ivan.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── Runner │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── GeneratedPluginRegistrant.h │ ├── GeneratedPluginRegistrant.m │ ├── Info.plist │ └── main.m └── ServiceDefinitions.json ├── lib ├── call_sample.dart ├── main.dart └── utils │ ├── ApiClient.dart │ ├── RequestConfig.dart │ ├── rtc_ice_candidate.dart │ └── signaling.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 cyb3rcod3 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter-openvidu-demo 2 | 3 | Simple flutter mobile app that demonstrates how to make a webrtc video call with the openVidu server. 4 | 5 | Requirements are [Flutter SDK](https://flutter.dev/docs/get-started/install) and [openVidu](https://openvidu.io/) 6 | ```bash 7 | $ git clone https://github.com/cyb3rcod3/flutter-openvidu-demo.git 8 | $ cd flutter-openvidu-demo 9 | $ flutter pub get 10 | $ flutter run 11 | ``` 12 | Tested on Android and iOS 13 | 14 | ![flutter-openvidu-demo animated gif](gif/flutter_openvidu_demo.gif) 15 | -------------------------------------------------------------------------------- /android/android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.cybercode.flutter_openvidu_demo" 37 | minSdkVersion 18 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 42 | //consumerProguardFiles 'proguard-rules.pro' 43 | } 44 | 45 | compileOptions { 46 | sourceCompatibility JavaVersion.VERSION_1_8 47 | targetCompatibility JavaVersion.VERSION_1_8 48 | } 49 | 50 | buildTypes { 51 | release { 52 | // TODO: Add your own signing config for the release build. 53 | // Signing with the debug keys for now, so `flutter run --release` works. 54 | signingConfig signingConfigs.debug 55 | useProguard true 56 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 57 | } 58 | } 59 | packagingOptions { 60 | exclude 'META-INF/proguard/androidx-annotations.pro' 61 | } 62 | } 63 | 64 | flutter { 65 | source '../..' 66 | } 67 | 68 | dependencies { 69 | testImplementation 'junit:junit:4.12' 70 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 71 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 72 | } 73 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Flutter WebRTC 2 | -keep class com.cloudwebrtc.webrtc.** { *; } 3 | -keep class org.webrtc.** { *; } -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 28 | 35 | 39 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/cybercode/flutter_openvidu_demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.cybercode.flutter_openvidu_demo; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java: -------------------------------------------------------------------------------- 1 | package io.flutter.plugins; 2 | 3 | import io.flutter.plugin.common.PluginRegistry; 4 | import com.cloudwebrtc.webrtc.FlutterWebRTCPlugin; 5 | import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin; 6 | import io.flutter.plugins.urllauncher.UrlLauncherPlugin; 7 | 8 | /** 9 | * Generated file. Do not edit. 10 | */ 11 | public final class GeneratedPluginRegistrant { 12 | public static void registerWith(PluginRegistry registry) { 13 | if (alreadyRegisteredWith(registry)) { 14 | return; 15 | } 16 | FlutterWebRTCPlugin.registerWith(registry.registrarFor("com.cloudwebrtc.webrtc.FlutterWebRTCPlugin")); 17 | SharedPreferencesPlugin.registerWith(registry.registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")); 18 | UrlLauncherPlugin.registerWith(registry.registrarFor("io.flutter.plugins.urllauncher.UrlLauncherPlugin")); 19 | } 20 | 21 | private static boolean alreadyRegisteredWith(PluginRegistry registry) { 22 | final String key = GeneratedPluginRegistrant.class.getCanonicalName(); 23 | if (registry.hasPlugin(key)) { 24 | return true; 25 | } 26 | registry.registrarFor(key); 27 | return false; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.6.3' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu May 21 03:23:13 CEST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /assets/imgs/offline_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/assets/imgs/offline_icon.png -------------------------------------------------------------------------------- /assets/imgs/openvidu_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/assets/imgs/openvidu_logo.png -------------------------------------------------------------------------------- /flutter_openvidu_demo.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /gif/flutter_openvidu_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/gif/flutter_openvidu_demo.gif -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Flutter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # NOTE: This podspec is NOT to be published. It is only used as a local source! 3 | # 4 | 5 | Pod::Spec.new do |s| 6 | s.name = 'Flutter' 7 | s.version = '1.0.0' 8 | s.summary = 'High-performance, high-fidelity mobile apps.' 9 | s.description = <<-DESC 10 | Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. 11 | DESC 12 | s.homepage = 'https://flutter.io' 13 | s.license = { :type => 'MIT' } 14 | s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } 15 | s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } 16 | s.ios.deployment_target = '8.0' 17 | s.vendored_frameworks = 'Flutter.framework' 18 | end 19 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '10.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | def parse_KV_file(file, separator='=') 8 | file_abs_path = File.expand_path(file) 9 | if !File.exists? file_abs_path 10 | return []; 11 | end 12 | pods_ary = [] 13 | skip_line_start_symbols = ["#", "/"] 14 | File.foreach(file_abs_path) { |line| 15 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 16 | plugin = line.split(pattern=separator) 17 | if plugin.length == 2 18 | podname = plugin[0].strip() 19 | path = plugin[1].strip() 20 | podpath = File.expand_path("#{path}", file_abs_path) 21 | pods_ary.push({:name => podname, :path => podpath}); 22 | else 23 | puts "Invalid plugin specification: #{line}" 24 | end 25 | } 26 | return pods_ary 27 | end 28 | 29 | target 'Runner' do 30 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 31 | # referring to absolute paths on developers' machines. 32 | system('rm -rf .symlinks') 33 | system('mkdir -p .symlinks/plugins') 34 | 35 | # Flutter Pods 36 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 37 | if generated_xcode_build_settings.empty? 38 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 39 | end 40 | generated_xcode_build_settings.map { |p| 41 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 42 | symlink = File.join('.symlinks', 'flutter') 43 | File.symlink(File.dirname(p[:path]), symlink) 44 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 45 | end 46 | } 47 | 48 | # Plugin Pods 49 | plugin_pods = parse_KV_file('../.flutter-plugins') 50 | plugin_pods.map { |p| 51 | symlink = File.join('.symlinks', 'plugins', p[:name]) 52 | File.symlink(p[:path], symlink) 53 | pod p[:name], :path => File.join(symlink, 'ios') 54 | } 55 | end 56 | 57 | post_install do |installer| 58 | installer.pods_project.targets.each do |target| 59 | target.build_configurations.each do |config| 60 | config.build_settings['ENABLE_BITCODE'] = 'NO' 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 8EF76F2A20E6D1B6DE7482C7 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 15BC1E059AB8D58D6F31A853 /* libPods-Runner.a */; }; 13 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 14 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 15 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 16 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 17 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 18 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXCopyFilesBuildPhase section */ 22 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 23 | isa = PBXCopyFilesBuildPhase; 24 | buildActionMask = 2147483647; 25 | dstPath = ""; 26 | dstSubfolderSpec = 10; 27 | files = ( 28 | ); 29 | name = "Embed Frameworks"; 30 | runOnlyForDeploymentPostprocessing = 0; 31 | }; 32 | /* End PBXCopyFilesBuildPhase section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 36 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 37 | 15BC1E059AB8D58D6F31A853 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 39 | 5B36A50EF4CD1A93237318D6 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 40 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 41 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 42 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 43 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 44 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 45 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 47 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 48 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 49 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 50 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | A8D196773125AAB1E136A063 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 52 | /* End PBXFileReference section */ 53 | 54 | /* Begin PBXFrameworksBuildPhase section */ 55 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | 8EF76F2A20E6D1B6DE7482C7 /* libPods-Runner.a in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | /* End PBXFrameworksBuildPhase section */ 64 | 65 | /* Begin PBXGroup section */ 66 | 7FCCAE0EF0328EC3D4877350 /* Frameworks */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 15BC1E059AB8D58D6F31A853 /* libPods-Runner.a */, 70 | ); 71 | name = Frameworks; 72 | sourceTree = ""; 73 | }; 74 | 9740EEB11CF90186004384FC /* Flutter */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 78 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 79 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 80 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 81 | ); 82 | name = Flutter; 83 | sourceTree = ""; 84 | }; 85 | 97C146E51CF9000F007C117D = { 86 | isa = PBXGroup; 87 | children = ( 88 | 9740EEB11CF90186004384FC /* Flutter */, 89 | 97C146F01CF9000F007C117D /* Runner */, 90 | 97C146EF1CF9000F007C117D /* Products */, 91 | D19CFC9EBE768E9F44363DDE /* Pods */, 92 | 7FCCAE0EF0328EC3D4877350 /* Frameworks */, 93 | ); 94 | sourceTree = ""; 95 | }; 96 | 97C146EF1CF9000F007C117D /* Products */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 97C146EE1CF9000F007C117D /* Runner.app */, 100 | ); 101 | name = Products; 102 | sourceTree = ""; 103 | }; 104 | 97C146F01CF9000F007C117D /* Runner */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 108 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 109 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 110 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 111 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 112 | 97C147021CF9000F007C117D /* Info.plist */, 113 | 97C146F11CF9000F007C117D /* Supporting Files */, 114 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 115 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 116 | ); 117 | path = Runner; 118 | sourceTree = ""; 119 | }; 120 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 97C146F21CF9000F007C117D /* main.m */, 124 | ); 125 | name = "Supporting Files"; 126 | sourceTree = ""; 127 | }; 128 | D19CFC9EBE768E9F44363DDE /* Pods */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 5B36A50EF4CD1A93237318D6 /* Pods-Runner.debug.xcconfig */, 132 | A8D196773125AAB1E136A063 /* Pods-Runner.release.xcconfig */, 133 | ); 134 | name = Pods; 135 | sourceTree = ""; 136 | }; 137 | /* End PBXGroup section */ 138 | 139 | /* Begin PBXNativeTarget section */ 140 | 97C146ED1CF9000F007C117D /* Runner */ = { 141 | isa = PBXNativeTarget; 142 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 143 | buildPhases = ( 144 | 79DB657BBAA4C66FD2F9F610 /* [CP] Check Pods Manifest.lock */, 145 | 9740EEB61CF901F6004384FC /* Run Script */, 146 | 97C146EA1CF9000F007C117D /* Sources */, 147 | 97C146EB1CF9000F007C117D /* Frameworks */, 148 | 97C146EC1CF9000F007C117D /* Resources */, 149 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 150 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 151 | 5BC80851079CF347B338D4EE /* [CP] Embed Pods Frameworks */, 152 | ); 153 | buildRules = ( 154 | ); 155 | dependencies = ( 156 | ); 157 | name = Runner; 158 | productName = Runner; 159 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 160 | productType = "com.apple.product-type.application"; 161 | }; 162 | /* End PBXNativeTarget section */ 163 | 164 | /* Begin PBXProject section */ 165 | 97C146E61CF9000F007C117D /* Project object */ = { 166 | isa = PBXProject; 167 | attributes = { 168 | LastUpgradeCheck = 0910; 169 | ORGANIZATIONNAME = "The Chromium Authors"; 170 | TargetAttributes = { 171 | 97C146ED1CF9000F007C117D = { 172 | CreatedOnToolsVersion = 7.3.1; 173 | DevelopmentTeam = K33SK8FW66; 174 | ProvisioningStyle = Automatic; 175 | }; 176 | }; 177 | }; 178 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 179 | compatibilityVersion = "Xcode 3.2"; 180 | developmentRegion = English; 181 | hasScannedForEncodings = 0; 182 | knownRegions = ( 183 | English, 184 | en, 185 | Base, 186 | ); 187 | mainGroup = 97C146E51CF9000F007C117D; 188 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 189 | projectDirPath = ""; 190 | projectRoot = ""; 191 | targets = ( 192 | 97C146ED1CF9000F007C117D /* Runner */, 193 | ); 194 | }; 195 | /* End PBXProject section */ 196 | 197 | /* Begin PBXResourcesBuildPhase section */ 198 | 97C146EC1CF9000F007C117D /* Resources */ = { 199 | isa = PBXResourcesBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 203 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 204 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 205 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 206 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 207 | ); 208 | runOnlyForDeploymentPostprocessing = 0; 209 | }; 210 | /* End PBXResourcesBuildPhase section */ 211 | 212 | /* Begin PBXShellScriptBuildPhase section */ 213 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 214 | isa = PBXShellScriptBuildPhase; 215 | buildActionMask = 2147483647; 216 | files = ( 217 | ); 218 | inputPaths = ( 219 | ); 220 | name = "Thin Binary"; 221 | outputPaths = ( 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | shellPath = /bin/sh; 225 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 226 | }; 227 | 5BC80851079CF347B338D4EE /* [CP] Embed Pods Frameworks */ = { 228 | isa = PBXShellScriptBuildPhase; 229 | buildActionMask = 2147483647; 230 | files = ( 231 | ); 232 | inputPaths = ( 233 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 234 | "${PODS_ROOT}/../.symlinks/flutter/ios-release/Flutter.framework", 235 | "${PODS_ROOT}/GoogleWebRTC/Frameworks/frameworks/WebRTC.framework", 236 | ); 237 | name = "[CP] Embed Pods Frameworks"; 238 | outputPaths = ( 239 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", 240 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework", 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | shellPath = /bin/sh; 244 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 245 | showEnvVarsInLog = 0; 246 | }; 247 | 79DB657BBAA4C66FD2F9F610 /* [CP] Check Pods Manifest.lock */ = { 248 | isa = PBXShellScriptBuildPhase; 249 | buildActionMask = 2147483647; 250 | files = ( 251 | ); 252 | inputPaths = ( 253 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 254 | "${PODS_ROOT}/Manifest.lock", 255 | ); 256 | name = "[CP] Check Pods Manifest.lock"; 257 | outputPaths = ( 258 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | shellPath = /bin/sh; 262 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 263 | showEnvVarsInLog = 0; 264 | }; 265 | 9740EEB61CF901F6004384FC /* Run Script */ = { 266 | isa = PBXShellScriptBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | ); 270 | inputPaths = ( 271 | ); 272 | name = "Run Script"; 273 | outputPaths = ( 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | shellPath = /bin/sh; 277 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 278 | }; 279 | /* End PBXShellScriptBuildPhase section */ 280 | 281 | /* Begin PBXSourcesBuildPhase section */ 282 | 97C146EA1CF9000F007C117D /* Sources */ = { 283 | isa = PBXSourcesBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 287 | 97C146F31CF9000F007C117D /* main.m in Sources */, 288 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | }; 292 | /* End PBXSourcesBuildPhase section */ 293 | 294 | /* Begin PBXVariantGroup section */ 295 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 296 | isa = PBXVariantGroup; 297 | children = ( 298 | 97C146FB1CF9000F007C117D /* Base */, 299 | ); 300 | name = Main.storyboard; 301 | sourceTree = ""; 302 | }; 303 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 304 | isa = PBXVariantGroup; 305 | children = ( 306 | 97C147001CF9000F007C117D /* Base */, 307 | ); 308 | name = LaunchScreen.storyboard; 309 | sourceTree = ""; 310 | }; 311 | /* End PBXVariantGroup section */ 312 | 313 | /* Begin XCBuildConfiguration section */ 314 | 97C147031CF9000F007C117D /* Debug */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | ALWAYS_SEARCH_USER_PATHS = NO; 318 | CLANG_ANALYZER_NONNULL = YES; 319 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 320 | CLANG_CXX_LIBRARY = "libc++"; 321 | CLANG_ENABLE_MODULES = YES; 322 | CLANG_ENABLE_OBJC_ARC = YES; 323 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 324 | CLANG_WARN_BOOL_CONVERSION = YES; 325 | CLANG_WARN_COMMA = YES; 326 | CLANG_WARN_CONSTANT_CONVERSION = YES; 327 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 328 | CLANG_WARN_EMPTY_BODY = YES; 329 | CLANG_WARN_ENUM_CONVERSION = YES; 330 | CLANG_WARN_INFINITE_RECURSION = YES; 331 | CLANG_WARN_INT_CONVERSION = YES; 332 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 333 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 334 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 335 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 336 | CLANG_WARN_STRICT_PROTOTYPES = YES; 337 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 338 | CLANG_WARN_UNREACHABLE_CODE = YES; 339 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 340 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 341 | COPY_PHASE_STRIP = NO; 342 | DEBUG_INFORMATION_FORMAT = dwarf; 343 | ENABLE_STRICT_OBJC_MSGSEND = YES; 344 | ENABLE_TESTABILITY = YES; 345 | GCC_C_LANGUAGE_STANDARD = gnu99; 346 | GCC_DYNAMIC_NO_PIC = NO; 347 | GCC_NO_COMMON_BLOCKS = YES; 348 | GCC_OPTIMIZATION_LEVEL = 0; 349 | GCC_PREPROCESSOR_DEFINITIONS = ( 350 | "DEBUG=1", 351 | "$(inherited)", 352 | ); 353 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 354 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 355 | GCC_WARN_UNDECLARED_SELECTOR = YES; 356 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 357 | GCC_WARN_UNUSED_FUNCTION = YES; 358 | GCC_WARN_UNUSED_VARIABLE = YES; 359 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 360 | MTL_ENABLE_DEBUG_INFO = YES; 361 | ONLY_ACTIVE_ARCH = YES; 362 | SDKROOT = iphoneos; 363 | TARGETED_DEVICE_FAMILY = "1,2"; 364 | }; 365 | name = Debug; 366 | }; 367 | 97C147041CF9000F007C117D /* Release */ = { 368 | isa = XCBuildConfiguration; 369 | buildSettings = { 370 | ALWAYS_SEARCH_USER_PATHS = NO; 371 | CLANG_ANALYZER_NONNULL = YES; 372 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 373 | CLANG_CXX_LIBRARY = "libc++"; 374 | CLANG_ENABLE_MODULES = YES; 375 | CLANG_ENABLE_OBJC_ARC = YES; 376 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 377 | CLANG_WARN_BOOL_CONVERSION = YES; 378 | CLANG_WARN_COMMA = YES; 379 | CLANG_WARN_CONSTANT_CONVERSION = YES; 380 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 381 | CLANG_WARN_EMPTY_BODY = YES; 382 | CLANG_WARN_ENUM_CONVERSION = YES; 383 | CLANG_WARN_INFINITE_RECURSION = YES; 384 | CLANG_WARN_INT_CONVERSION = YES; 385 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 386 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 387 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 388 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 389 | CLANG_WARN_STRICT_PROTOTYPES = YES; 390 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 391 | CLANG_WARN_UNREACHABLE_CODE = YES; 392 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 393 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 394 | COPY_PHASE_STRIP = NO; 395 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 396 | ENABLE_NS_ASSERTIONS = NO; 397 | ENABLE_STRICT_OBJC_MSGSEND = YES; 398 | GCC_C_LANGUAGE_STANDARD = gnu99; 399 | GCC_NO_COMMON_BLOCKS = YES; 400 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 401 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 402 | GCC_WARN_UNDECLARED_SELECTOR = YES; 403 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 404 | GCC_WARN_UNUSED_FUNCTION = YES; 405 | GCC_WARN_UNUSED_VARIABLE = YES; 406 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 407 | MTL_ENABLE_DEBUG_INFO = NO; 408 | SDKROOT = iphoneos; 409 | TARGETED_DEVICE_FAMILY = "1,2"; 410 | VALIDATE_PRODUCT = YES; 411 | }; 412 | name = Release; 413 | }; 414 | 97C147061CF9000F007C117D /* Debug */ = { 415 | isa = XCBuildConfiguration; 416 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 417 | buildSettings = { 418 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 419 | CODE_SIGN_IDENTITY = "Apple Development"; 420 | CODE_SIGN_STYLE = Automatic; 421 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 422 | DEVELOPMENT_TEAM = K33SK8FW66; 423 | ENABLE_BITCODE = NO; 424 | FRAMEWORK_SEARCH_PATHS = ( 425 | "$(inherited)", 426 | "$(PROJECT_DIR)/Flutter", 427 | ); 428 | INFOPLIST_FILE = Runner/Info.plist; 429 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 430 | LIBRARY_SEARCH_PATHS = ( 431 | "$(inherited)", 432 | "$(PROJECT_DIR)/Flutter", 433 | ); 434 | PRODUCT_BUNDLE_IDENTIFIER = "com.cybercode.flutter-openvidu"; 435 | PRODUCT_NAME = "$(TARGET_NAME)"; 436 | PROVISIONING_PROFILE_SPECIFIER = ""; 437 | VERSIONING_SYSTEM = "apple-generic"; 438 | }; 439 | name = Debug; 440 | }; 441 | 97C147071CF9000F007C117D /* Release */ = { 442 | isa = XCBuildConfiguration; 443 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 444 | buildSettings = { 445 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 446 | CODE_SIGN_IDENTITY = "Apple Development"; 447 | CODE_SIGN_STYLE = Automatic; 448 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 449 | DEVELOPMENT_TEAM = K33SK8FW66; 450 | ENABLE_BITCODE = NO; 451 | FRAMEWORK_SEARCH_PATHS = ( 452 | "$(inherited)", 453 | "$(PROJECT_DIR)/Flutter", 454 | ); 455 | INFOPLIST_FILE = Runner/Info.plist; 456 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 457 | LIBRARY_SEARCH_PATHS = ( 458 | "$(inherited)", 459 | "$(PROJECT_DIR)/Flutter", 460 | ); 461 | PRODUCT_BUNDLE_IDENTIFIER = "com.cybercode.flutter-openvidu"; 462 | PRODUCT_NAME = "$(TARGET_NAME)"; 463 | PROVISIONING_PROFILE_SPECIFIER = ""; 464 | VERSIONING_SYSTEM = "apple-generic"; 465 | }; 466 | name = Release; 467 | }; 468 | /* End XCBuildConfiguration section */ 469 | 470 | /* Begin XCConfigurationList section */ 471 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 472 | isa = XCConfigurationList; 473 | buildConfigurations = ( 474 | 97C147031CF9000F007C117D /* Debug */, 475 | 97C147041CF9000F007C117D /* Release */, 476 | ); 477 | defaultConfigurationIsVisible = 0; 478 | defaultConfigurationName = Release; 479 | }; 480 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 481 | isa = XCConfigurationList; 482 | buildConfigurations = ( 483 | 97C147061CF9000F007C117D /* Debug */, 484 | 97C147071CF9000F007C117D /* Release */, 485 | ); 486 | defaultConfigurationIsVisible = 0; 487 | defaultConfigurationName = Release; 488 | }; 489 | /* End XCConfigurationList section */ 490 | }; 491 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 492 | } 493 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcuserdata/ivan.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner.xcworkspace/xcuserdata/ivan.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb3rcod3/flutter-openvidu-demo/f0263a0a53add7d10f0501b43b71efd8bb438ef4/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/GeneratedPluginRegistrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | #ifndef GeneratedPluginRegistrant_h 6 | #define GeneratedPluginRegistrant_h 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface GeneratedPluginRegistrant : NSObject 13 | + (void)registerWithRegistry:(NSObject*)registry; 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | #endif /* GeneratedPluginRegistrant_h */ 18 | -------------------------------------------------------------------------------- /ios/Runner/GeneratedPluginRegistrant.m: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | #import "GeneratedPluginRegistrant.h" 6 | 7 | #if __has_include() 8 | #import 9 | #else 10 | @import flutter_webrtc; 11 | #endif 12 | 13 | #if __has_include() 14 | #import 15 | #else 16 | @import shared_preferences; 17 | #endif 18 | 19 | #if __has_include() 20 | #import 21 | #else 22 | @import url_launcher; 23 | #endif 24 | 25 | @implementation GeneratedPluginRegistrant 26 | 27 | + (void)registerWithRegistry:(NSObject*)registry { 28 | [FlutterWebRTCPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterWebRTCPlugin"]]; 29 | [FLTSharedPreferencesPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTSharedPreferencesPlugin"]]; 30 | [FLTURLLauncherPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTURLLauncherPlugin"]]; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_openvidu_demo 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | NSCameraUsageDescription 26 | $(PRODUCT_NAME) Camera Usage! 27 | NSMicrophoneUsageDescription 28 | $(PRODUCT_NAME) Microphone Usage! 29 | NSPhotoLibraryUsageDescription 30 | $(PRODUCT_NAME) PhotoLibrary Usage! 31 | UIBackgroundModes 32 | 33 | fetch 34 | remote-notification 35 | 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIMainStoryboardFile 39 | Main 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | UIViewControllerBasedStatusBarAppearance 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ios/ServiceDefinitions.json: -------------------------------------------------------------------------------- 1 | {"services":[]} -------------------------------------------------------------------------------- /lib/call_sample.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_webrtc/webrtc.dart'; 4 | import 'package:flutter_openvidu_demo/utils/signaling.dart'; 5 | 6 | class CallSampleWidget extends StatefulWidget { 7 | CallSampleWidget({Key key, @required this.server, this.sessionName, this.userName, this.secret, this.iceServer}) : super(key: key); 8 | 9 | final String server; 10 | final String sessionName; 11 | final String userName; 12 | final String secret; 13 | final String iceServer; 14 | 15 | @override 16 | _CallSampleWidgetState createState() => _CallSampleWidgetState(); 17 | } 18 | 19 | class _CallSampleWidgetState extends State { 20 | 21 | final _localRenderer = new RTCVideoRenderer(); 22 | final _remoteRenderer = new RTCVideoRenderer(); 23 | 24 | Signaling _signaling; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | 30 | initRenderers(); 31 | } 32 | 33 | Future initRenderers() async { 34 | await _localRenderer.initialize(); 35 | await _remoteRenderer.initialize(); 36 | _connect(); 37 | } 38 | 39 | void _hangUp() { 40 | if (_signaling != null) { 41 | Navigator.of(context).pop(); 42 | } 43 | } 44 | 45 | void _switchCamera() { 46 | _signaling.switchCamera(); 47 | } 48 | 49 | void _muteMic() { 50 | _signaling.muteMic(); 51 | } 52 | 53 | @override 54 | Widget build(BuildContext context) { 55 | return new Scaffold( 56 | body: OrientationBuilder(builder: (context, orientation) { 57 | return new Container( 58 | child: new Stack(children: [ 59 | new Positioned( 60 | left: 0.0, 61 | right: 0.0, 62 | top: 0.0, 63 | bottom: 0.0, 64 | child: new Container( 65 | margin: new EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0), 66 | width: MediaQuery.of(context).size.width, 67 | height: MediaQuery.of(context).size.height, 68 | child: new RTCVideoView(_remoteRenderer), 69 | decoration: new BoxDecoration(color: Colors.black54), 70 | ) 71 | ), 72 | new Positioned( 73 | left: 20.0, 74 | top: 40.0, 75 | child: new Container( 76 | width: orientation == Orientation.portrait ? 90.0 : 120.0, 77 | height: orientation == Orientation.portrait ? 120.0 : 90.0, 78 | child: new RTCVideoView(_localRenderer), 79 | decoration: new BoxDecoration(color: Colors.black54), 80 | ), 81 | ), 82 | ]), 83 | ); 84 | }), 85 | floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, 86 | floatingActionButton: new SizedBox( 87 | width: 200.0, 88 | child: new Row( 89 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 90 | children: [ 91 | FloatingActionButton( 92 | child: const Icon(Icons.switch_camera), 93 | onPressed: _switchCamera, 94 | heroTag: "btn_switchCamera", 95 | ), 96 | FloatingActionButton( 97 | onPressed: _hangUp, 98 | heroTag: "btn_hangUp", 99 | tooltip: 'Hangup', 100 | child: new Icon(Icons.call_end), 101 | backgroundColor: Colors.pink, 102 | ), 103 | FloatingActionButton( 104 | child: const Icon(Icons.mic_off), 105 | onPressed: _muteMic, 106 | heroTag: "btn_muteMic", 107 | ) 108 | ] 109 | ) 110 | ), 111 | ); 112 | } 113 | 114 | @override 115 | void dispose() { 116 | super.dispose(); 117 | _signaling?.close(); 118 | _localRenderer?.dispose(); 119 | _remoteRenderer?.dispose(); 120 | print('◤◢◤◢◤◢◤◢◤◢◤ dispose() ALL ◤◢◤◢◤◢◤◢◤◢◤'); 121 | } 122 | 123 | Future _connect() async { 124 | 125 | if (_signaling == null) { 126 | 127 | //_Init Signaling 128 | _signaling = new Signaling(widget.server, widget.secret, widget.userName, widget.iceServer); 129 | 130 | //_Create session 131 | final String sessionId = await _signaling.createWebRtcSession(sessionId: widget.sessionName); 132 | print('◤◢◤◢◤◢◤◢◤◢◤ sessionId: $sessionId ◤◢◤◢◤◢◤◢◤◢◤ '); 133 | 134 | //_Create_token 135 | final String token = await _signaling.createWebRtcToken(sessionId: sessionId); 136 | print('◤◢◤◢◤◢◤◢◤◢◤ token: $token ◤◢◤◢◤◢◤◢◤◢◤ '); 137 | 138 | //_Connect_socket //ADDED AWAIT 139 | await _signaling.connect(); 140 | 141 | _signaling.onStateChange = (SignalingState state) { 142 | print('_signaling.onStateChange: $state'); 143 | switch (state) { 144 | case SignalingState.CallStateNew: 145 | break; 146 | case SignalingState.CallStateBye: 147 | break; 148 | default: 149 | break; 150 | } 151 | }; 152 | 153 | _signaling.onLocalStream = ((stream) { 154 | print('onLocalStream: ${stream.id}'); 155 | _localRenderer.srcObject = stream; 156 | }); 157 | 158 | _signaling.onAddRemoteStream = ((stream) { 159 | print('onAddRemoteStream: ${stream.id}'); 160 | _remoteRenderer.srcObject = stream; 161 | }); 162 | 163 | _signaling.onRemoveRemoteStream = ((stream) { 164 | print('onRemoveRemoteStream'); 165 | _remoteRenderer.srcObject = null; 166 | }); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:math'; 3 | import 'dart:io'; 4 | import 'dart:async'; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | import 'package:flutter_openvidu_demo/call_sample.dart'; 7 | import 'package:url_launcher/url_launcher.dart'; 8 | 9 | void main() => runApp(MaterialApp(home: MyHome())); 10 | 11 | class MyHome extends StatefulWidget { 12 | @override 13 | _MyHomeState createState() => new _MyHomeState(); 14 | } 15 | 16 | class _MyHomeState extends State { 17 | 18 | bool isOnline = false; 19 | TextEditingController _textSessionController; 20 | TextEditingController _textUserNameController; 21 | TextEditingController _textUrlController; 22 | TextEditingController _textSecretController; 23 | TextEditingController _textPortController; 24 | TextEditingController _textIceServersController; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | 30 | _textSessionController = TextEditingController(text: 'Session-flutter-test-${Random().nextInt(1000)}'); 31 | _textUserNameController = TextEditingController(text: 'FlutterUser${Random().nextInt(1000)}'); 32 | _textUrlController = TextEditingController(text: 'demos.openvidu.io'); 33 | _textSecretController = TextEditingController(text: 'MY_SECRET'); 34 | _textPortController = TextEditingController(text: '443'); 35 | _textIceServersController = TextEditingController(text: 'stun.l.google.com:19302'); 36 | 37 | _loadSharedPref(); 38 | _liveConn(); 39 | } 40 | 41 | Future _loadSharedPref() async { 42 | SharedPreferences prefs = await SharedPreferences.getInstance(); 43 | _textUrlController.text = prefs.getString('textUrl') ?? _textUrlController.text; 44 | _textSecretController.text = prefs.getString('textSecret') ?? _textSecretController.text; 45 | _textPortController.text = prefs.getString('textPort') ?? _textPortController.text; 46 | _textIceServersController.text = prefs.getString('textIceServers') ?? _textIceServersController.text; 47 | print('Loaded user inputs value.'); 48 | } 49 | 50 | Future _saveSharedPref() async { 51 | SharedPreferences prefs = await SharedPreferences.getInstance(); 52 | await prefs.setString('textUrl', _textUrlController.text); 53 | await prefs.setString('textSecret', _textSecretController.text); 54 | await prefs.setString('textPort', _textPortController.text); 55 | await prefs.setString('textIceServers', _textIceServersController.text); 56 | print('Saved user inputs values.'); 57 | } 58 | 59 | Future _liveConn() async{ 60 | await _checkOnline(); 61 | Timer.periodic(Duration(seconds: 5), (timer) async{ 62 | await _checkOnline(); 63 | }); 64 | } 65 | Future _checkOnline() async { 66 | try { 67 | final result = await InternetAddress.lookup('google.com'); 68 | if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) { 69 | if (!isOnline) { 70 | isOnline = true; 71 | setState(() {}); 72 | print('Online..'); 73 | } 74 | } 75 | } on SocketException catch (_) { 76 | if (isOnline) { 77 | isOnline = false; 78 | setState(() {}); 79 | print('..Offline'); 80 | } 81 | } 82 | } 83 | 84 | @override 85 | Widget build(BuildContext context) { 86 | return new Scaffold( 87 | appBar: new AppBar( 88 | title: const Text('Flutter openVidu demo'), 89 | actions: [ 90 | Row(children: [ 91 | isOnline ? Image(image: AssetImage('assets/openvidu_logo.png'),fit: BoxFit.fill, width: 35,) : 92 | Image(image: AssetImage('assets/offline_icon.png'),fit: BoxFit.fill, width: 35,), 93 | ]), 94 | ] 95 | ), 96 | drawer: Drawer( 97 | child: ListView( 98 | children: [ 99 | ListTile( 100 | leading: CircleAvatar(backgroundImage: AssetImage('assets/openvidu_logo.png')), 101 | title: Text("Flutter openVidu demo"), 102 | subtitle: Text("v 1.0.0"), 103 | ), 104 | ListTile(leading: Icon(Icons.home), title: Text("Home"),onTap:(){Navigator.of(context).pop();}), 105 | InkWell( 106 | child: new ListTile(leading: Icon(Icons.insert_link), title: Text("GitHub")), 107 | onTap: () => launch('https://github.com/cyb3rcod3/flutter-openvidu-demo') 108 | ), 109 | InkWell( 110 | child: new ListTile(leading: Icon(Icons.insert_link), title: Text("openVidu")), 111 | onTap: () => launch('https://openvidu.io') 112 | ), 113 | 114 | ], 115 | ) 116 | ), 117 | body: Container( 118 | child: SingleChildScrollView( 119 | padding: const EdgeInsets.all(10.0), 120 | child: Center( 121 | child: Column( 122 | mainAxisAlignment: MainAxisAlignment.center, 123 | crossAxisAlignment: CrossAxisAlignment.center, 124 | children: [ 125 | SizedBox(height: 10), 126 | TextField( 127 | controller: _textSessionController, 128 | decoration: InputDecoration( 129 | contentPadding: EdgeInsets.all(5), 130 | border: OutlineInputBorder(), 131 | labelText: 'Session room name', 132 | hintText: 'Enter session room name' 133 | ), 134 | ), 135 | SizedBox(height: 10), 136 | TextField( 137 | controller: _textUserNameController, 138 | decoration: InputDecoration( 139 | contentPadding: EdgeInsets.all(5), 140 | border: OutlineInputBorder(), 141 | labelText: 'Session username', 142 | hintText: 'Enter username' 143 | ), 144 | ), 145 | SizedBox(height: 40), 146 | TextField( 147 | controller: _textUrlController, 148 | decoration: InputDecoration( 149 | contentPadding: EdgeInsets.all(5), 150 | border: OutlineInputBorder(), 151 | labelText: 'openVidu server url', 152 | hintText: 'Enter openVidu server url' 153 | ), 154 | ), 155 | SizedBox(height: 10), 156 | TextField( 157 | controller: _textPortController, 158 | decoration: InputDecoration( 159 | contentPadding: EdgeInsets.all(5), 160 | border: OutlineInputBorder(), 161 | labelText: 'openVidu server port', 162 | hintText: 'Enter openVidu server port' 163 | ), 164 | ), 165 | SizedBox(height: 10), 166 | TextField( 167 | controller: _textSecretController, 168 | decoration: InputDecoration( 169 | contentPadding: EdgeInsets.all(5), 170 | border: OutlineInputBorder(), 171 | labelText: 'openVidu server secret', 172 | hintText: 'Enter openVidu server secret' 173 | ), 174 | ), 175 | SizedBox(height: 10), 176 | TextField( 177 | controller: _textIceServersController, 178 | decoration: InputDecoration( 179 | contentPadding: EdgeInsets.all(5), 180 | border: OutlineInputBorder(), 181 | labelText: 'Ice server', 182 | hintText: 'Enter ice server url' 183 | ), 184 | ), 185 | SizedBox( 186 | height: 30, 187 | ), 188 | FlatButton( 189 | child: Text(isOnline ? 'JoinRoom' : ' Offline ', style: TextStyle(fontSize: 20.0),), 190 | textColor: Colors.white, 191 | padding: EdgeInsets.all(15.0), 192 | color: Colors.green[400], 193 | disabledColor: Colors.grey, 194 | onPressed: isOnline ? () => 195 | Navigator.push(context, MaterialPageRoute(builder: (context) { 196 | _saveSharedPref(); 197 | return CallSampleWidget( server: '${_textUrlController.text}:${_textPortController.text}', 198 | sessionName: _textSessionController.text, 199 | userName: _textUserNameController.text, 200 | secret: _textSecretController.text, 201 | iceServer: _textIceServersController.text ); 202 | }) 203 | ) : null, 204 | ), 205 | ], 206 | ), 207 | ), 208 | ), 209 | ), 210 | ); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /lib/utils/ApiClient.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'dart:io'; 5 | 6 | import 'package:flutter_openvidu_demo/utils/RequestConfig.dart'; 7 | 8 | class ApiClient{ 9 | final HttpClient client = HttpClient(); 10 | 11 | Map get getDefaultHeaders{ 12 | final Map defaultHeaders = {}; 13 | return defaultHeaders; 14 | } 15 | 16 | /// Use this instead of [getAction], [postAction] and [putAction] 17 | Future request(Config config, {bool autoLogin = false}) async { 18 | print('[${config.method}] Sending request: ${config.uri.toString()}'); 19 | 20 | final HttpClientRequest _request = await client.openUrl(config.method, config.uri) 21 | .then((HttpClientRequest request) => _addHeaders(request, config)) 22 | .then((HttpClientRequest request) => _addCookies(request, config)) 23 | .then((HttpClientRequest request) => _addBody(request, config)); 24 | 25 | final HttpClientResponse _response = await _request.close(); 26 | 27 | print('[${config.method}] Received: ${_response.reasonPhrase} [${_response.statusCode}] - ${config.uri.toString()}'); 28 | 29 | if(_response.statusCode == HttpStatus.ok){ 30 | return config.hasResponse ? Future.value(config.responseType.parse(_response)) : Future.value(_response); 31 | } 32 | 33 | return await _processError(_response, config, onAutoLoginSuccess: () => request(config)); 34 | } 35 | 36 | HttpClientRequest _addBody(HttpClientRequest request, Config config) { 37 | if (config.hasBody) { 38 | request.headers.contentType = config.body.getContentType(); 39 | request.contentLength = const Utf8Encoder().convert(config.body.getBody()).length; 40 | request.write(config.body.getBody()); 41 | } 42 | 43 | return request; 44 | } 45 | 46 | HttpClientRequest _addCookies(HttpClientRequest request, Config config) { 47 | config.cookies.forEach((String key, dynamic value) => 48 | value is Cookie ? request.cookies.add(value) : request.cookies.add(new Cookie(key, value))); 49 | 50 | return request; 51 | } 52 | 53 | HttpClientRequest _addHeaders(HttpClientRequest request, Config config) { 54 | // Add default headers 55 | getDefaultHeaders.forEach((String key, dynamic value) => request.headers.add(key, value)); 56 | 57 | // Add config headers 58 | config.headers.forEach((String key, dynamic value)=> request.headers.add(key, value)); 59 | 60 | return request; 61 | } 62 | 63 | Future _processError(HttpClientResponse response, Config config, {Future Function() onAutoLoginSuccess}) async { 64 | final PenkalaError penkalaError = await PenkalaError.parseError(response, config); 65 | 66 | return Future.error(penkalaError); 67 | } 68 | } 69 | 70 | class PenkalaError{ 71 | final HttpClientResponse response; 72 | final Config config; 73 | 74 | bool shouldShow = true; 75 | ErrorType errorType; 76 | final StringBuffer _presentableError = StringBuffer(); 77 | 78 | PenkalaError(this.response, this.config, {this.errorType}); 79 | 80 | String get errorString => _presentableError.isEmpty ? null : _presentableError.toString(); 81 | 82 | @override 83 | String toString(){ 84 | return 'PenkalaError :: $errorString'; 85 | } 86 | 87 | /// Get more info about Request error 88 | /// Will set up error type and string for specific error 89 | /// Toggles [shouldShow] flag to false if error dialog is not needed to pop up for this error 90 | Future _processError() async { 91 | print('Processing error : [${response.statusCode}] - ${response.reasonPhrase}'); 92 | final String _responseData = await utf8.decodeStream(response); 93 | final Map errorJson = jsonDecode(_responseData); 94 | 95 | switch(response.statusCode){ 96 | /// Start auto-login procedure if we receive status code 498 97 | /// 498 Invalid Token (Esri) 98 | /// Returned by ArcGIS for Server. Code 498 indicates an expired or otherwise invalid token. 99 | case 498: 100 | continue unknown; 101 | 102 | /// Error 401 is thrown when user is unauthorized to access this endpoint. 103 | /// App should never call endpoint that will receive '401' if user is logged in 104 | case 401: 105 | errorType = ErrorType.unauthorized; 106 | break; 107 | 108 | /// Error 409 is thrown when an already openVidu named session is alive. 109 | case 409: 110 | errorType = ErrorType.sessionAlready; 111 | break; 112 | 113 | /// Bad gateway. Usually means there is server fix/deploy on the way. 114 | case 502: 115 | errorType = ErrorType.badGateway; 116 | break; 117 | 118 | /// Bad request. Get error code from response data JSON saved in field 119 | /// 'error_code'. This will give us detailed info about error defined by server for this app. 120 | /// 121 | /// Codes are defined here: https://docs.hot-soup.com/penkala-api/index.html#response-codes 122 | case 400: 123 | switch(errorJson['error_code'] ?? -1){ 124 | case 186: 125 | errorType = ErrorType.missingSignatureImages; 126 | break; 127 | case 187: 128 | errorType = ErrorType.signatureApprovalNeeded; 129 | break; 130 | default: 131 | errorType = ErrorType.badRequest; 132 | break; 133 | } 134 | break; 135 | unknown: 136 | default: 137 | errorType = ErrorType.unknown; 138 | 139 | print('UNKNOWN ERROR! ${response.statusCode} - [${response.reasonPhrase}]'); 140 | print('URL: ${config.uri.toString()}'); 141 | print('Headers: ${config.headers.toString()}'); 142 | print('Body: ${config.body?.getBody()}'); 143 | print('Data: ${_responseData ?? ''}'); 144 | break; 145 | } 146 | 147 | if(errorType == ErrorType.sessionAlready){ 148 | _presentableError.writeln('409 - Session already created. Join in..'); 149 | print(_presentableError); 150 | } 151 | else if(errorType == ErrorType.badGateway){ 152 | _presentableError.writeln('502 - Bad Gateway (deploy is on the way?)'); 153 | }else{ 154 | try{ 155 | if(errorJson.containsKey('errors')){ 156 | final List errors = errorJson['errors']; 157 | }else{ 158 | _presentableError.writeln(errorJson['error_msg'] ?? 'Something went wrong!'); 159 | } 160 | 161 | print('Error: ${errorType.toString()}'); 162 | }catch(exception){ 163 | print('Exception proccessing error: $exception'); 164 | } 165 | } 166 | } 167 | 168 | static Future parseError(HttpClientResponse response, Config config) async { 169 | final PenkalaError error = PenkalaError(response, config); 170 | await error._processError(); 171 | return Future.value(error); 172 | } 173 | } 174 | 175 | enum ErrorType{ 176 | tokenExpired, 177 | badGateway, 178 | badRequest, 179 | unauthorized, 180 | unknown, 181 | noConnection, 182 | signatureApprovalNeeded, 183 | missingSignatureImages, 184 | sessionAlready 185 | } 186 | -------------------------------------------------------------------------------- /lib/utils/RequestConfig.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async' show Future, Stream; 2 | import 'dart:convert' show jsonEncode, jsonDecode, utf8; 3 | import 'dart:io' show HttpClientResponse, ContentType; 4 | 5 | import 'package:html/dom.dart' show Document; 6 | import 'package:html/parser.dart' as parser show parse; 7 | 8 | enum RequestMethod { 9 | get, 10 | post, 11 | put, 12 | delete, 13 | patch 14 | } 15 | 16 | class RequestBody { 17 | static _JsonRequest json(Map data) => new _JsonRequest(data); 18 | static _FormDataRequest formData(Map data) => new _FormDataRequest(data); 19 | } 20 | 21 | class ResponseBody { 22 | static _JsonResponse json() => new _JsonResponse(); 23 | static _DocumentResponse document() => new _DocumentResponse(); 24 | } 25 | 26 | class Config { 27 | const Config({ 28 | RequestMethod method, 29 | this.uri, 30 | this.body, 31 | this.responseType, 32 | this.headers = const {}, 33 | this.cookies = const {}, 34 | }) : _method = method; 35 | 36 | final RequestMethod _method; 37 | final Uri uri; 38 | final _RequestBodyType body; 39 | final _ResponseBodyType responseType; 40 | final Map headers; 41 | final Map cookies; 42 | 43 | void addHeader(String name, Object value) => 44 | headers['$name'] = value; 45 | 46 | void addCookie(String name, Object value) => 47 | cookies['$name'] = value; 48 | 49 | String get method => _getMethod(); 50 | bool get hasResponse => responseType != null; 51 | bool get hasBody => body != null; 52 | bool get hasHeader => headers?.isNotEmpty == true; 53 | 54 | String _getMethod(){ 55 | switch(_method){ 56 | case RequestMethod.post: 57 | return 'POST'; 58 | case RequestMethod.put: 59 | return 'PUT'; 60 | case RequestMethod.get: 61 | return 'GET'; 62 | case RequestMethod.delete: 63 | return 'DELETE'; 64 | case RequestMethod.patch: 65 | return 'PATCH'; 66 | default: 67 | return 'UNKNOWN'; 68 | } 69 | } 70 | } 71 | 72 | abstract class _RequestBodyType { 73 | ContentType getContentType() => 74 | new ContentType('application', _type(), charset: 'utf-8'); 75 | String getBody(); 76 | String _type(); 77 | } 78 | 79 | class _JsonRequest extends _RequestBodyType { 80 | final Map json; 81 | 82 | _JsonRequest(this.json); 83 | @override 84 | String _type() => 'json'; 85 | 86 | @override 87 | String getBody() => jsonEncode(json); 88 | } 89 | 90 | class _FormDataRequest extends _RequestBodyType { 91 | final Map formData; 92 | 93 | _FormDataRequest(this.formData); 94 | 95 | @override 96 | String _type() => 'x-www-form-urlencoded'; 97 | 98 | @override 99 | String getBody() => 100 | formData.keys.map((dynamic key) => '$key=${formData[key]}').join('&'); 101 | } 102 | 103 | abstract class _ResponseBodyType { 104 | String getAcceptHeader() => 'Accept'; 105 | String getAcceptValue(); 106 | dynamic parse(HttpClientResponse response); 107 | } 108 | 109 | class _JsonResponse extends _ResponseBodyType { 110 | @override 111 | String getAcceptValue() => 'application/json'; 112 | 113 | @override 114 | Future> parse(HttpClientResponse response) async => 115 | jsonDecode(await utf8.decodeStream(response));//response.transform(utf8.decoder).join()); 116 | } 117 | 118 | class _DocumentResponse extends _ResponseBodyType { 119 | @override 120 | String getAcceptValue() => 'text/html'; 121 | 122 | @override 123 | Future parse(HttpClientResponse response) async => 124 | parser.parse(await response.asyncExpand((List bytes) => new Stream.fromIterable(bytes)).toList()); 125 | } 126 | -------------------------------------------------------------------------------- /lib/utils/rtc_ice_candidate.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_webrtc/webrtc.dart'; 2 | class CustomRTCIceCandidate extends RTCIceCandidate { 3 | String candidate; 4 | String sdpMid; 5 | int sdpMlineIndex; 6 | String endpointName; 7 | 8 | CustomRTCIceCandidate(this.candidate, this.sdpMid, this.sdpMlineIndex, this.endpointName) : super('', '', 0); 9 | 10 | dynamic toMap() { 11 | return { 12 | "candidate": candidate, 13 | "sdpMid": sdpMid, 14 | "sdpMLineIndex": sdpMlineIndex, 15 | "endpointName": endpointName, 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/utils/signaling.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:async'; 3 | import 'dart:io'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter_webrtc/webrtc.dart'; 6 | import 'package:flutter_openvidu_demo/utils/ApiClient.dart'; 7 | import 'package:flutter_openvidu_demo/utils/RequestConfig.dart'; 8 | import 'package:flutter_openvidu_demo/utils/rtc_ice_candidate.dart'; 9 | 10 | enum SignalingState { 11 | CallStateNew, 12 | CallStateRinging, 13 | CallStateInvite, 14 | CallStateConnected, 15 | CallStateBye, 16 | ConnectionOpen, 17 | ConnectionClosed, 18 | ConnectionError, 19 | CallStateWaiting 20 | } 21 | 22 | class JsonConstants { 23 | static const String value = 'value'; 24 | static const String params = 'params'; 25 | static const String method = 'method'; 26 | static const String id = 'id'; 27 | static const String result = 'result'; 28 | static const String iceCandidate = 'iceCandidate'; 29 | static const String participantJoined = "participantJoined"; 30 | static const String participantEvicted = "participantEvicted"; 31 | static const String participantUnpublished = "participantUnpublished"; 32 | static const String streamPropertyChanged = "streamPropertyChanged"; 33 | static const String sendMessage = "sendMessage"; 34 | static const String participantPublished = "participantPublished"; 35 | static const String participantLeft = "participantLeft"; 36 | static const String sessionId = "sessionId"; 37 | static const String sdpAnswer = "sdpAnswer"; 38 | static const String joinRoom = "joinRoom"; 39 | static const String metadata = "metadata"; 40 | static const String publishVideo = "publishVideo"; 41 | static const String onIceCandidate = 'onIceCandidate'; 42 | static const String receiveVideoFrom = 'receiveVideoFrom'; 43 | static const String endpointName = 'endpointName'; 44 | static const String senderConnectionId = 'senderConnectionId'; 45 | static const String connectionId = 'connectionId'; 46 | static const String leaveRoom = 'leaveRoom'; 47 | static const String streams = 'streams'; 48 | } 49 | 50 | class RemoteParticipant { 51 | RemoteParticipant({this.id, this.streamId, this.mediaStream, this.peerConnection, this.metadata}); 52 | 53 | String id; 54 | String streamId; 55 | MediaStream mediaStream; 56 | RTCPeerConnection peerConnection; 57 | String metadata; 58 | } 59 | 60 | typedef void SignalingStateCallback(SignalingState state); 61 | typedef void StreamStateCallback(MediaStream stream); 62 | typedef void OtherEventCallback(dynamic event); 63 | 64 | 65 | class Signaling { 66 | 67 | Signaling(this._url, this._secret, this._userName, this._iceServer): 68 | _iceServers = { 69 | 'iceServers': [ 70 | { 71 | 'url': 'stun:$_iceServer', 72 | //'username': _turnUsername, 73 | //'credential': _turnCredential 74 | }, 75 | ] 76 | }; // Signaling 77 | 78 | 79 | WebSocket _socket; 80 | String _url; 81 | String _secret; 82 | String token; 83 | String session; 84 | String _userName; 85 | String _iceServer; 86 | String _userId; 87 | String _endpointName; 88 | 89 | Map _iceServers = {}; 90 | Map _participants = {}; 91 | Map _participantEndpoints = {}; 92 | Map _idsReceiveVideo = {}; 93 | 94 | int _internalId = 1; 95 | int _idPublishVideo; 96 | int _idJoinRoom; 97 | 98 | Timer _timer; 99 | 100 | MediaStream _localStream; 101 | RTCPeerConnection _localPeerConnection; 102 | bool _localPeerConnectionHasRemoteDescription = false; 103 | 104 | SignalingStateCallback onStateChange; 105 | StreamStateCallback onLocalStream; 106 | StreamStateCallback onAddRemoteStream; 107 | StreamStateCallback onRemoveRemoteStream; 108 | 109 | OtherEventCallback onParticipantsRemove; 110 | OtherEventCallback onParticipantsJoined; 111 | OtherEventCallback onParticipantsStreamUpdate; 112 | OtherEventCallback onMessageReceive; 113 | OtherEventCallback onUserId; 114 | OtherEventCallback onSelfEvict; 115 | 116 | List> _iceCandidatesParams = >[]; 117 | List _remoteAlreadyInRoomValues; 118 | 119 | bool _haveRemoteAlreadyInRoom = false; 120 | 121 | 122 | final Map _constraints = { 123 | 'mandatory': { 124 | 'OfferToReceiveAudio': true, 125 | 'OfferToReceiveVideo': true, 126 | }, 127 | 'optional': [], 128 | }; 129 | 130 | void updateInternalId() { 131 | _internalId++; 132 | } 133 | 134 | void close() { 135 | _localStream?.dispose(); 136 | _localStream = null; 137 | _timer?.cancel(); 138 | _sendJson(JsonConstants.leaveRoom, null); 139 | _localPeerConnection?.close(); 140 | _participants.forEach((String id, RemoteParticipant remoteParticipant) { 141 | remoteParticipant.peerConnection?.close(); 142 | }); 143 | _participants.clear(); 144 | _socket?.close(); 145 | } 146 | 147 | void switchCamera() { 148 | if (_localStream != null) { 149 | _localStream.getVideoTracks()[0].switchCamera(); 150 | } 151 | } 152 | void muteMic() { 153 | if (_localStream != null) { 154 | //_FIX_IT 155 | _localStream.getVideoTracks()[0].setMicrophoneMute(true); 156 | } 157 | } 158 | 159 | Future createWebRtcSession({String sessionId}) { 160 | final Map bodyMap = {'customSessionId': sessionId}; 161 | final Map headersMap = 162 | { 163 | 'Authorization': 'Basic ${base64Encode(utf8.encode('OPENVIDUAPP:$_secret'))}' 164 | }; 165 | return ApiClient().request>(Config( 166 | uri: Uri.parse('https://$_url/api/sessions'), 167 | headers: headersMap, 168 | body: RequestBody.json(bodyMap), 169 | method: RequestMethod.post, 170 | responseType: ResponseBody.json() 171 | )).then((Map jsonResponse) { 172 | print('◤◢◤◢◤◢◤◢◤◢◤ Create WebRTC session POST response: $jsonResponse ◤◢◤◢◤◢◤◢◤◢◤'); 173 | session = jsonResponse['id']; 174 | return session; 175 | }).catchError((error) { 176 | //__FIX_IT_IF_ERROR_409 177 | session = sessionId; 178 | print('createWebRtcSession error: $error'); 179 | return sessionId; 180 | }); 181 | } 182 | 183 | Future createWebRtcToken({@required String sessionId, String role = 'PUBLISHER'}){ 184 | final Map bodyMap = 185 | {'session': sessionId, 'role': role, 'data': ''}; 186 | final Map headersMap = 187 | { 188 | 'Authorization': 'Basic ${base64Encode(utf8.encode('OPENVIDUAPP:$_secret'))}' 189 | }; 190 | return ApiClient().request>(Config( 191 | uri: Uri.parse('https://$_url/api/tokens'), 192 | headers: headersMap, 193 | body: RequestBody.json(bodyMap), 194 | method: RequestMethod.post, 195 | responseType: ResponseBody.json() 196 | )).then((Map jsonResponse) { 197 | print('◤◢◤◢◤◢◤◢◤◢◤ Create WebRTC token POST response: $jsonResponse ◤◢◤◢◤◢◤◢◤◢◤'); 198 | token = jsonResponse['token']; 199 | return token; 200 | }); 201 | } 202 | 203 | 204 | Future connect() async { 205 | try { 206 | _socket = await WebSocket.connect('wss://$_url/openvidu'); 207 | 208 | if (this.onStateChange != null) { 209 | this.onStateChange(SignalingState.ConnectionOpen); 210 | print('◤◢◤◢◤◢◤◢◤◢◤ Socket connected <----> ◤◢◤◢◤◢◤◢◤◢◤'); 211 | } 212 | 213 | _socket.listen((data) { 214 | this.onMessage(json.decode(data)); 215 | }, onDone: () { 216 | if (this.onStateChange != null) { 217 | this.onStateChange(SignalingState.ConnectionClosed); 218 | print('◤◢◤◢◤◢◤◢◤◢◤ Socket disconnected <-/--> ◤◢◤◢◤◢◤◢◤◢◤'); 219 | } 220 | }); 221 | 222 | _createLocalPeerConnection().then((_) { 223 | _idJoinRoom = _sendJson(JsonConstants.joinRoom, { 224 | JsonConstants.metadata: '{\"clientData\": \"$_userName\"}', 225 | 'secret': '', 226 | 'platform': Platform.isAndroid ? 'Android' : 'iOS', 227 | // 'dataChannels': 'false', 228 | 'session': session, 229 | 'token': token, 230 | }); 231 | _createLocalOffer(); 232 | }); 233 | 234 | _startPingTimer(); 235 | 236 | }catch(e){ 237 | print('◤◢!◤◢◤!◢◤◢!◤◢◤ connect error: $e ◤◢!◤◢◤!◢◤◢!◤◢◤'); 238 | if(this.onStateChange != null){ 239 | this.onStateChange(SignalingState.ConnectionError); 240 | } 241 | } 242 | } 243 | 244 | Future onMessage(message) async { 245 | Map mapData = message; 246 | print('◤◢◤◢◤◢◤◢◤◢◤ onMessage_json_from_socket ---> | $message | ◤◢◤◢◤◢◤◢◤◢◤'); 247 | if (mapData.containsKey(JsonConstants.result)) { 248 | if (mapData['id'] == _idPublishVideo) { 249 | _endpointName = mapData[JsonConstants.result]['id']; 250 | print('◤◢◤◢◤◢(_1_)◤◢◤◢◤ '); 251 | } else if (mapData['id'] == _idJoinRoom) { 252 | _createLocalOffer();//?????????????????????? 253 | print('◤◢◤◢◤◢(_2_)◤◢◤◢◤ '); 254 | } 255 | handleResult(mapData[JsonConstants.result], mapData[JsonConstants.id]); 256 | print('◤◢◤◢◤◢(_3 - handleResult_)◤◢◤◢◤ '); 257 | } else { 258 | handleMethod(mapData); 259 | print('◤◢◤◢◤◢(_3 - handleMethod)◤◢◤◢◤ '); 260 | 261 | } 262 | } 263 | 264 | void handleResult(Map data, int id) { 265 | if (data.containsKey(JsonConstants.sdpAnswer)) { 266 | _saveAnswer(data, id); 267 | } else if (data.containsKey(JsonConstants.sessionId)) { 268 | if (data.containsKey(JsonConstants.value)) { // Join room reply 269 | List values = data[JsonConstants.value]; 270 | _userId = data['id']; 271 | if (this.onUserId != null){ 272 | this.onUserId(data['id']); 273 | } 274 | for (Map iceCandidate in _iceCandidatesParams) { 275 | iceCandidate[JsonConstants.endpointName] = data['endpointName'] ?? data['id']; 276 | iceCandidate[JsonConstants.id] = data['id'] ?? data['endpointName']; 277 | _sendJson(JsonConstants.onIceCandidate, iceCandidate); 278 | } 279 | if (values.length > 0) { 280 | _addParticipantsAlreadyInRoom(values); 281 | } 282 | } 283 | } else if (data.containsKey(JsonConstants.value)) { 284 | print('|◤◢◤◢◤pong|'); 285 | } else { 286 | print('Unrecognized: $data'); 287 | } 288 | } 289 | 290 | void handleMethod(Map data) { 291 | print('◤◢◤◢◤◢◤◢◤◢◤ HANDLE_METHOD ◤◢◤◢◤◢◤◢◤◢◤ : $data'); 292 | if (data.containsKey(JsonConstants.params)) { 293 | Map params = data[JsonConstants.params]; 294 | String method = data[JsonConstants.method]; 295 | switch (method) { 296 | case JsonConstants.iceCandidate: 297 | _iceCandidateMethod(params); 298 | print('◤◢◤◢◤◢◤◢◤◢◤ iceCandidate: $params ◤◢◤◢◤◢◤◢◤◢◤'); 299 | break; 300 | case JsonConstants.participantJoined: 301 | _participantJoinedMethod(params); 302 | print('◤◢◤◢◤◢◤◢◤◢◤ participantJoined: $params ◤◢◤◢◤◢◤◢◤◢◤'); 303 | break; 304 | case JsonConstants.sendMessage: 305 | _messageReceive(params); 306 | print('◤◢◤◢◤◢◤◢◤◢◤ sendMessage: $params ◤◢◤◢◤◢◤◢◤◢◤'); 307 | break; 308 | case JsonConstants.participantPublished: 309 | _participantPublishedMethod(params); 310 | print('◤◢◤◢◤◢◤◢◤◢◤ participantPublished: $params ◤◢◤◢◤◢◤◢◤◢◤'); 311 | break; 312 | case JsonConstants.participantLeft: 313 | print('◤◢◤◢◤◢◤◢◤◢◤ participantLeft: $params ◤◢◤◢◤◢◤◢◤◢◤'); 314 | break; 315 | case JsonConstants.participantEvicted: 316 | _participantLeftMethod(params); 317 | print('◤◢◤◢◤◢◤◢◤◢◤ participantEvicted: $params ◤◢◤◢◤◢◤◢◤◢◤'); 318 | break; 319 | default: 320 | print('handleMethod: can\'t understand method: $method'); 321 | break; 322 | } 323 | } else { 324 | print('handleMethod, no params'); 325 | } 326 | } 327 | 328 | Future _addParticipantsAlreadyInRoom(List values) async { 329 | for (Map value in values) { 330 | String _remoteParticipantId = value[JsonConstants.id]; 331 | RemoteParticipant remoteParticipant = RemoteParticipant(id: _remoteParticipantId, metadata: value[JsonConstants.metadata]); 332 | if (value.containsKey(JsonConstants.streams)) { 333 | List streamsList = value[JsonConstants.streams]; 334 | Map stream = streamsList?.first; 335 | if (stream != null && stream.containsKey(JsonConstants.id)) { 336 | remoteParticipant.streamId = stream[JsonConstants.id]; 337 | } 338 | } 339 | _participants[_remoteParticipantId] = remoteParticipant; 340 | _createRemotePeerConnection(remoteParticipant).then((RTCPeerConnection remotePeerConnection) async { 341 | _receiveVideoFromParticipant(remoteParticipant); 342 | }); 343 | if (this.onStateChange != null) { 344 | this.onStateChange(SignalingState.CallStateConnected); 345 | } 346 | } 347 | } 348 | 349 | void _participantJoinedMethod(Map params) { 350 | RemoteParticipant remoteParticipant = RemoteParticipant(id: params[JsonConstants.id], metadata: params[JsonConstants.metadata]); 351 | _participants[remoteParticipant.id] = remoteParticipant; 352 | this.onParticipantsJoined(remoteParticipant); 353 | _createRemotePeerConnection(remoteParticipant); 354 | if (this.onStateChange != null) { 355 | this.onStateChange(SignalingState.CallStateConnected); 356 | } 357 | } 358 | 359 | void _messageReceive(Map params) { 360 | print('_message $params'); 361 | if (this.onMessageReceive != null){ 362 | this.onMessageReceive(params); 363 | } 364 | } 365 | 366 | void sendMessage(List to, String signalType, dynamic data) { 367 | Map _msg = { 368 | 'to': to, 369 | 'data': data, 370 | 'type': signalType 371 | }; 372 | _sendJson(JsonConstants.sendMessage, { 373 | 'message': jsonEncode(_msg) 374 | }); 375 | } 376 | 377 | Future _receiveVideoFromParticipant(RemoteParticipant remoteParticipant) async { 378 | if (remoteParticipant.peerConnection == null) { 379 | return; 380 | } 381 | try { 382 | RTCSessionDescription sessionDescription = await remoteParticipant.peerConnection.createOffer(_constraints); 383 | remoteParticipant.peerConnection.setLocalDescription(sessionDescription); 384 | int id = _sendJson(JsonConstants.receiveVideoFrom, { 385 | 'sender': remoteParticipant.streamId ?? remoteParticipant.id, 386 | 'sdpOffer': sessionDescription.sdp 387 | }); 388 | _idsReceiveVideo[id] = remoteParticipant.id; 389 | } catch(e) { 390 | print(e.toString()); 391 | } 392 | } 393 | 394 | void _participantLeftMethod(Map params) { 395 | String participantId = params['connectionId']; 396 | if (participantId == _userId) { 397 | this.onSelfEvict(_userId); 398 | } 399 | else if (_participants.containsKey(participantId)) { 400 | this.onParticipantsRemove(_participants[participantId]); 401 | _participants[participantId].peerConnection?.close(); 402 | _participants.remove(participantId); 403 | if (_participants.length == 0) { 404 | if (this.onStateChange != null) { 405 | this.onStateChange(SignalingState.CallStateWaiting); 406 | } 407 | } 408 | } 409 | } 410 | 411 | void _iceCandidateMethod(Map params) { 412 | bool isLocal = params[JsonConstants.senderConnectionId] == _userId; 413 | _saveIceCandidate(params, params[JsonConstants.endpointName], params[JsonConstants.senderConnectionId], isLocal); 414 | } 415 | 416 | void _saveIceCandidate(Map params, String endpointName, String senderConnectionId, bool isLocal) { 417 | CustomRTCIceCandidate iceCandidate = CustomRTCIceCandidate(params['candidate'], params['sdpMid'], params['sdpMLineIndex'], endpointName); 418 | if (isLocal && _localPeerConnection != null) { 419 | _localPeerConnection.addCandidate(iceCandidate); 420 | } else if (_participants.containsKey(senderConnectionId)) { 421 | _participants[senderConnectionId].peerConnection.addCandidate(iceCandidate); 422 | _participantEndpoints[senderConnectionId] = endpointName; 423 | } 424 | } 425 | 426 | void _saveAnswer(Map data, int id) { 427 | RTCSessionDescription sessionDescription = RTCSessionDescription(data['sdpAnswer'], 'answer'); 428 | if (!_localPeerConnectionHasRemoteDescription) { 429 | _localPeerConnection.setRemoteDescription(sessionDescription); 430 | _localPeerConnectionHasRemoteDescription = true; 431 | } else if (_idsReceiveVideo.containsKey(id)) { 432 | _participants[_idsReceiveVideo[id]].peerConnection.setRemoteDescription(sessionDescription); 433 | } 434 | } 435 | 436 | void _startPingTimer() { 437 | _timer = Timer.periodic(const Duration(seconds: 3), (Timer timer) { 438 | _sendJson('ping', { 439 | 'interval': '3000' 440 | }); 441 | print('|ping◤◢◤◢◤|'); 442 | }); 443 | } 444 | 445 | Future createStream({isLocalStream = false}) async { 446 | final Map mediaConstraints = { 447 | 'audio': true, 448 | 'video': { 449 | 'mandatory': { 450 | 'minWidth': '320', 451 | 'minHeight': '240', 452 | 'minFrameRate': '30', 453 | }, 454 | 'facingMode': 'user', 455 | 'optional': [], 456 | } 457 | }; 458 | MediaStream stream = await navigator.getUserMedia(mediaConstraints); 459 | if (isLocalStream && this.onLocalStream != null) { 460 | this.onLocalStream(stream); 461 | } 462 | return stream; 463 | } 464 | 465 | Future _createLocalPeerConnection() async { 466 | _localStream = await createStream(isLocalStream: true); 467 | _localPeerConnection = await createPeerConnection(_iceServers, _constraints); 468 | _localPeerConnection.onSignalingState = ((state) { 469 | if (state == RTCSignalingState.RTCSignalingStateStable) { 470 | 471 | } 472 | }); 473 | _localPeerConnection.onIceGatheringState = ((state) { 474 | if (state == RTCIceGatheringState.RTCIceGatheringStateComplete) { 475 | if (_haveRemoteAlreadyInRoom) { 476 | _addParticipantsAlreadyInRoom(_remoteAlreadyInRoomValues); 477 | _remoteAlreadyInRoomValues.clear(); 478 | _haveRemoteAlreadyInRoom = false; 479 | } 480 | } 481 | }); 482 | _localPeerConnection.onIceCandidate = (candidate) { 483 | Map iceCandidateParams = { 484 | 'sdpMid': candidate.sdpMid, 485 | 'sdpMLineIndex': candidate.sdpMlineIndex, 486 | 'candidate': candidate.candidate, 487 | }; 488 | if (_userId != null){ 489 | iceCandidateParams[JsonConstants.endpointName] = _endpointName ?? _userId; 490 | _sendJson(JsonConstants.onIceCandidate, iceCandidateParams); 491 | } else { 492 | _iceCandidatesParams.add(iceCandidateParams); 493 | } 494 | }; 495 | _localPeerConnection.addStream(_localStream); 496 | } 497 | 498 | Future _createRemotePeerConnection(RemoteParticipant remoteParticipant) async { 499 | RTCPeerConnection remotePeerConnection = await createPeerConnection(_iceServers, _constraints); 500 | remotePeerConnection.onIceCandidate = (candidate) { 501 | Map iceCandidateParams = { 502 | 'sdpMid': candidate.sdpMid, 503 | 'sdpMLineIndex': candidate.sdpMlineIndex, 504 | 'endpointName': remoteParticipant.streamId ?? _participantEndpoints[remoteParticipant.id] ?? remoteParticipant.id, 505 | 'candidate': candidate.candidate, 506 | }; 507 | _sendJson(JsonConstants.onIceCandidate, iceCandidateParams); 508 | }; 509 | 510 | remotePeerConnection.onAddStream = ((stream) { 511 | remoteParticipant.mediaStream = stream; 512 | if (this.onParticipantsStreamUpdate != null){ 513 | this.onParticipantsStreamUpdate(remoteParticipant); 514 | } 515 | if (this.onAddRemoteStream != null){ 516 | this.onAddRemoteStream(stream); 517 | } 518 | }); 519 | 520 | remotePeerConnection.onSignalingState = ((state) { 521 | print('remotePeerConnection.onSignalingState: $state'); 522 | /* 523 | if (state == RTCSignalingState.RTCSignalingStateStable) { 524 | // 525 | } 526 | */ 527 | }); 528 | 529 | remotePeerConnection.onIceGatheringState = ((state) { 530 | print('icestate: $state'); 531 | }); 532 | 533 | remotePeerConnection.onRemoveStream = (stream) { 534 | remoteParticipant.mediaStream = stream; 535 | if (this.onParticipantsStreamUpdate != null){ 536 | this.onParticipantsStreamUpdate(remoteParticipant); 537 | } 538 | if(this.onRemoveRemoteStream != null){ 539 | this.onRemoveRemoteStream(stream); 540 | } 541 | }; 542 | 543 | remotePeerConnection.onDataChannel = (channel) { 544 | print('onDataChannel: $channel'); 545 | }; 546 | 547 | remoteParticipant.peerConnection = remotePeerConnection; 548 | return remotePeerConnection; 549 | } 550 | 551 | Future _createLocalOffer() async { 552 | try { 553 | RTCSessionDescription s = await _localPeerConnection.createOffer(_constraints); 554 | await _localPeerConnection.setLocalDescription(s); 555 | _idPublishVideo = _sendJson(JsonConstants.publishVideo, { 556 | 'audioOnly': 'false', 557 | 'hasAudio': 'true', 558 | 'doLoopback': 'false', 559 | 'hasVideo': 'true', 560 | 'audioActive': 'true', 561 | 'videoActive': 'true', 562 | 'typeOfVideo': 'CAMERA', 563 | 'frameRate': '30', 564 | 'videoDimensions': '{\"width\": 320, \"height\": 240}', 565 | 'sdpOffer': s.sdp 566 | }); 567 | } catch (e) { 568 | print(e.toString()); 569 | } 570 | } 571 | 572 | Future _participantPublishedMethod(Map params) async { 573 | String _remoteParticipantId = params[JsonConstants.id]; 574 | if (_participants.containsKey(_remoteParticipantId)) { 575 | RemoteParticipant remoteParticipantPublished = _participants[_remoteParticipantId]; 576 | if (params.containsKey(JsonConstants.streams)) { 577 | List streamsList = params[JsonConstants.streams]; 578 | Map stream = streamsList?.first; 579 | if (stream != null && stream.containsKey(JsonConstants.id)) { 580 | remoteParticipantPublished.streamId = stream[JsonConstants.id]; 581 | } 582 | } 583 | if (remoteParticipantPublished.peerConnection != null){ 584 | _receiveVideoFromParticipant(remoteParticipantPublished); 585 | } else { 586 | _createRemotePeerConnection(remoteParticipantPublished).then((RTCPeerConnection remotePeerConnection) async { 587 | _receiveVideoFromParticipant(remoteParticipantPublished); 588 | }); 589 | 590 | } 591 | } 592 | } 593 | 594 | int _sendJson(String method, Map params) { 595 | Map dict = {}; 596 | dict[JsonConstants.method] = method; 597 | dict[JsonConstants.id] = _internalId; 598 | dict['jsonrpc'] = '2.0'; 599 | if ((params?.length ?? 0) > 0) { 600 | dict[JsonConstants.params] = params; 601 | } 602 | updateInternalId(); 603 | String jsonString = json.encode(dict); 604 | _socket?.add(jsonString); 605 | print('◤◢◤◢◤◢◤◢◤◢◤ send_json_to_socket ---> | $jsonString | ◤◢◤◢◤◢◤◢◤◢◤'); 606 | return _internalId - 1; 607 | } 608 | 609 | } 610 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.13" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.6.0" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.4.1" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.0.0" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.3" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.12" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.1" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.1.4" 60 | csslib: 61 | dependency: transitive 62 | description: 63 | name: csslib 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.16.1" 67 | cupertino_icons: 68 | dependency: "direct main" 69 | description: 70 | name: cupertino_icons 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "0.1.3" 74 | flutter: 75 | dependency: "direct main" 76 | description: flutter 77 | source: sdk 78 | version: "0.0.0" 79 | flutter_test: 80 | dependency: "direct dev" 81 | description: flutter 82 | source: sdk 83 | version: "0.0.0" 84 | flutter_web_plugins: 85 | dependency: transitive 86 | description: flutter 87 | source: sdk 88 | version: "0.0.0" 89 | flutter_webrtc: 90 | dependency: "direct main" 91 | description: 92 | name: flutter_webrtc 93 | url: "https://pub.dartlang.org" 94 | source: hosted 95 | version: "0.2.8" 96 | html: 97 | dependency: "direct main" 98 | description: 99 | name: html 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "0.14.0+3" 103 | image: 104 | dependency: transitive 105 | description: 106 | name: image 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "2.1.12" 110 | matcher: 111 | dependency: transitive 112 | description: 113 | name: matcher 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "0.12.6" 117 | meta: 118 | dependency: transitive 119 | description: 120 | name: meta 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.1.8" 124 | path: 125 | dependency: transitive 126 | description: 127 | name: path 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "1.6.4" 131 | petitparser: 132 | dependency: transitive 133 | description: 134 | name: petitparser 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "2.4.0" 138 | platform_detect: 139 | dependency: transitive 140 | description: 141 | name: platform_detect 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "1.4.0" 145 | plugin_platform_interface: 146 | dependency: transitive 147 | description: 148 | name: plugin_platform_interface 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.0.2" 152 | pub_semver: 153 | dependency: transitive 154 | description: 155 | name: pub_semver 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "1.4.4" 159 | quiver: 160 | dependency: transitive 161 | description: 162 | name: quiver 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "2.1.3" 166 | shared_preferences: 167 | dependency: "direct main" 168 | description: 169 | name: shared_preferences 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "0.5.7+3" 173 | shared_preferences_macos: 174 | dependency: transitive 175 | description: 176 | name: shared_preferences_macos 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "0.0.1+9" 180 | shared_preferences_platform_interface: 181 | dependency: transitive 182 | description: 183 | name: shared_preferences_platform_interface 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "1.0.4" 187 | shared_preferences_web: 188 | dependency: transitive 189 | description: 190 | name: shared_preferences_web 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "0.1.2+7" 194 | sky_engine: 195 | dependency: transitive 196 | description: flutter 197 | source: sdk 198 | version: "0.0.99" 199 | source_span: 200 | dependency: transitive 201 | description: 202 | name: source_span 203 | url: "https://pub.dartlang.org" 204 | source: hosted 205 | version: "1.7.0" 206 | stack_trace: 207 | dependency: transitive 208 | description: 209 | name: stack_trace 210 | url: "https://pub.dartlang.org" 211 | source: hosted 212 | version: "1.9.3" 213 | stream_channel: 214 | dependency: transitive 215 | description: 216 | name: stream_channel 217 | url: "https://pub.dartlang.org" 218 | source: hosted 219 | version: "2.0.0" 220 | string_scanner: 221 | dependency: transitive 222 | description: 223 | name: string_scanner 224 | url: "https://pub.dartlang.org" 225 | source: hosted 226 | version: "1.0.5" 227 | term_glyph: 228 | dependency: transitive 229 | description: 230 | name: term_glyph 231 | url: "https://pub.dartlang.org" 232 | source: hosted 233 | version: "1.1.0" 234 | test_api: 235 | dependency: transitive 236 | description: 237 | name: test_api 238 | url: "https://pub.dartlang.org" 239 | source: hosted 240 | version: "0.2.15" 241 | typed_data: 242 | dependency: transitive 243 | description: 244 | name: typed_data 245 | url: "https://pub.dartlang.org" 246 | source: hosted 247 | version: "1.1.6" 248 | url_launcher: 249 | dependency: "direct main" 250 | description: 251 | name: url_launcher 252 | url: "https://pub.dartlang.org" 253 | source: hosted 254 | version: "5.4.10" 255 | url_launcher_macos: 256 | dependency: transitive 257 | description: 258 | name: url_launcher_macos 259 | url: "https://pub.dartlang.org" 260 | source: hosted 261 | version: "0.0.1+7" 262 | url_launcher_platform_interface: 263 | dependency: transitive 264 | description: 265 | name: url_launcher_platform_interface 266 | url: "https://pub.dartlang.org" 267 | source: hosted 268 | version: "1.0.7" 269 | url_launcher_web: 270 | dependency: transitive 271 | description: 272 | name: url_launcher_web 273 | url: "https://pub.dartlang.org" 274 | source: hosted 275 | version: "0.1.1+6" 276 | vector_math: 277 | dependency: transitive 278 | description: 279 | name: vector_math 280 | url: "https://pub.dartlang.org" 281 | source: hosted 282 | version: "2.0.8" 283 | xml: 284 | dependency: transitive 285 | description: 286 | name: xml 287 | url: "https://pub.dartlang.org" 288 | source: hosted 289 | version: "3.6.1" 290 | sdks: 291 | dart: ">=2.7.0 <3.0.0" 292 | flutter: ">=1.12.13+hotfix.5 <2.0.0" 293 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_openvidu_demo 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.7.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | 28 | # The following adds the Cupertino Icons font to your application. 29 | # Use with the CupertinoIcons class for iOS style icons. 30 | cupertino_icons: ^0.1.3 31 | html: 0.14.0+3 32 | flutter_webrtc: ^0.2.8 33 | url_launcher: ^5.4.10 34 | shared_preferences: ^0.5.7+3 35 | 36 | dev_dependencies: 37 | flutter_test: 38 | sdk: flutter 39 | 40 | # For information on the generic Dart part of this file, see the 41 | # following page: https://dart.dev/tools/pub/pubspec 42 | 43 | # The following section is specific to Flutter. 44 | flutter: 45 | 46 | # The following line ensures that the Material Icons font is 47 | # included with your application, so that you can use the icons in 48 | # the material Icons class. 49 | uses-material-design: true 50 | 51 | # To add assets to your application, add an assets section, like this: 52 | # assets: 53 | # - images/a_dot_burr.jpeg 54 | # - images/a_dot_ham.jpeg 55 | assets: 56 | - assets/offline_icon.png 57 | - assets/openvidu_logo.png 58 | 59 | # An image asset can refer to one or more resolution-specific "variants", see 60 | # https://flutter.dev/assets-and-images/#resolution-aware. 61 | 62 | # For details regarding adding assets from package dependencies, see 63 | # https://flutter.dev/assets-and-images/#from-packages 64 | 65 | # To add custom fonts to your application, add a fonts section here, 66 | # in this "flutter" section. Each entry in this list should have a 67 | # "family" key with the font family name, and a "fonts" key with a 68 | # list giving the asset and other descriptors for the font. For 69 | # example: 70 | # fonts: 71 | # - family: Schyler 72 | # fonts: 73 | # - asset: fonts/Schyler-Regular.ttf 74 | # - asset: fonts/Schyler-Italic.ttf 75 | # style: italic 76 | # - family: Trajan Pro 77 | # fonts: 78 | # - asset: fonts/TrajanPro.ttf 79 | # - asset: fonts/TrajanPro_Bold.ttf 80 | # weight: 700 81 | # 82 | # For details regarding fonts from package dependencies, 83 | # see https://flutter.dev/custom-fonts/#from-packages 84 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_openvidu_demo/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyHome()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------