├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── google-services.json │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── it │ │ │ │ └── maionemiky │ │ │ │ └── flutter_firebase_video_call_webrtc │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── 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-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── buildWeb.bat ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── 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 │ ├── Info.plist │ └── Runner-Bridging-Header.h └── firebase_app_id_file.json ├── lib ├── firebase_options.dart ├── main.dart ├── signaling.dart ├── snack_msg.dart ├── turn.dart └── turn_web.dart ├── pubspec.lock ├── pubspec.yaml ├── runServer.bat ├── test └── widget_test.dart └── web ├── favicon.png ├── icons ├── Icon-192.png ├── Icon-512.png ├── Icon-maskable-192.png └── Icon-maskable-512.png ├── index.html └── manifest.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 77d935af4db863f6abd0b9c31c7e6df2a13de57b 8 | channel: @u 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Michele Maione 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlutterFirebaseVideoCallWebRTC 2 | a Flutter Video Call Demo using WebRTC, Coturn and Firebase 3 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /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 plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = '1.8' 38 | } 39 | 40 | sourceSets { 41 | main.java.srcDirs += 'src/main/kotlin' 42 | } 43 | 44 | defaultConfig { 45 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 46 | applicationId "it.maionemiky.flutter_firebase_video_call_webrtc" 47 | minSdkVersion flutter.minSdkVersion 48 | targetSdkVersion flutter.targetSdkVersion 49 | versionCode flutterVersionCode.toInteger() 50 | versionName flutterVersionName 51 | } 52 | 53 | buildTypes { 54 | release { 55 | // TODO: Add your own signing config for the release build. 56 | // Signing with the debug keys for now, so `flutter run --release` works. 57 | signingConfig signingConfigs.debug 58 | } 59 | } 60 | } 61 | 62 | flutter { 63 | source '../..' 64 | } 65 | 66 | dependencies { 67 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 68 | } 69 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "340047693711", 4 | "project_id": "firebase-video-call-webrtc", 5 | "storage_bucket": "firebase-video-call-webrtc.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:340047693711:android:65c14573347575a15a6dfa", 11 | "android_client_info": { 12 | "package_name": "it.maionemiky.flutter_firebase_video_call_webrtc" 13 | } 14 | }, 15 | "oauth_client": [ 16 | { 17 | "client_id": "340047693711-mm95gqnaiev9dcemm48ls099sfiq2ons.apps.googleusercontent.com", 18 | "client_type": 3 19 | } 20 | ], 21 | "api_key": [ 22 | { 23 | "current_key": "AIzaSyA05KPMeRCE4V8xnT2YeLpPlPLQm5gtxgM" 24 | } 25 | ], 26 | "services": { 27 | "appinvite_service": { 28 | "other_platform_oauth_client": [ 29 | { 30 | "client_id": "340047693711-mm95gqnaiev9dcemm48ls099sfiq2ons.apps.googleusercontent.com", 31 | "client_type": 3 32 | } 33 | ] 34 | } 35 | } 36 | } 37 | ], 38 | "configuration_version": "1" 39 | } -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/it/maionemiky/flutter_firebase_video_call_webrtc/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package it.maionemiky.flutter_firebase_video_call_webrtc 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 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-6.7-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /buildWeb.bat: -------------------------------------------------------------------------------- 1 | flutter build web --base-href "/webrtc.github.io/" -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /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 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 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 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 97 | ); 98 | path = Runner; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 97C146ED1CF9000F007C117D /* Runner */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 107 | buildPhases = ( 108 | 9740EEB61CF901F6004384FC /* Run Script */, 109 | 97C146EA1CF9000F007C117D /* Sources */, 110 | 97C146EB1CF9000F007C117D /* Frameworks */, 111 | 97C146EC1CF9000F007C117D /* Resources */, 112 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = Runner; 120 | productName = Runner; 121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | 97C146E61CF9000F007C117D /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastUpgradeCheck = 1300; 131 | ORGANIZATIONNAME = ""; 132 | TargetAttributes = { 133 | 97C146ED1CF9000F007C117D = { 134 | CreatedOnToolsVersion = 7.3.1; 135 | LastSwiftMigration = 1100; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 140 | compatibilityVersion = "Xcode 9.3"; 141 | developmentRegion = en; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = 97C146E51CF9000F007C117D; 148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | 97C146ED1CF9000F007C117D /* Runner */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 97C146EC1CF9000F007C117D /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXShellScriptBuildPhase section */ 172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 173 | isa = PBXShellScriptBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | ); 177 | inputPaths = ( 178 | ); 179 | name = "Thin Binary"; 180 | outputPaths = ( 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | shellPath = /bin/sh; 184 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 185 | }; 186 | 9740EEB61CF901F6004384FC /* Run Script */ = { 187 | isa = PBXShellScriptBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | ); 191 | inputPaths = ( 192 | ); 193 | name = "Run Script"; 194 | outputPaths = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | shellPath = /bin/sh; 198 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 199 | }; 200 | /* End PBXShellScriptBuildPhase section */ 201 | 202 | /* Begin PBXSourcesBuildPhase section */ 203 | 97C146EA1CF9000F007C117D /* Sources */ = { 204 | isa = PBXSourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 208 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXSourcesBuildPhase section */ 213 | 214 | /* Begin PBXVariantGroup section */ 215 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 216 | isa = PBXVariantGroup; 217 | children = ( 218 | 97C146FB1CF9000F007C117D /* Base */, 219 | ); 220 | name = Main.storyboard; 221 | sourceTree = ""; 222 | }; 223 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 97C147001CF9000F007C117D /* Base */, 227 | ); 228 | name = LaunchScreen.storyboard; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXVariantGroup section */ 232 | 233 | /* Begin XCBuildConfiguration section */ 234 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 240 | CLANG_CXX_LIBRARY = "libc++"; 241 | CLANG_ENABLE_MODULES = YES; 242 | CLANG_ENABLE_OBJC_ARC = YES; 243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 244 | CLANG_WARN_BOOL_CONVERSION = YES; 245 | CLANG_WARN_COMMA = YES; 246 | CLANG_WARN_CONSTANT_CONVERSION = YES; 247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 249 | CLANG_WARN_EMPTY_BODY = YES; 250 | CLANG_WARN_ENUM_CONVERSION = YES; 251 | CLANG_WARN_INFINITE_RECURSION = YES; 252 | CLANG_WARN_INT_CONVERSION = YES; 253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 258 | CLANG_WARN_STRICT_PROTOTYPES = YES; 259 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 263 | COPY_PHASE_STRIP = NO; 264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 265 | ENABLE_NS_ASSERTIONS = NO; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | GCC_C_LANGUAGE_STANDARD = gnu99; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 271 | GCC_WARN_UNDECLARED_SELECTOR = YES; 272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 273 | GCC_WARN_UNUSED_FUNCTION = YES; 274 | GCC_WARN_UNUSED_VARIABLE = YES; 275 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 276 | MTL_ENABLE_DEBUG_INFO = NO; 277 | SDKROOT = iphoneos; 278 | SUPPORTED_PLATFORMS = iphoneos; 279 | TARGETED_DEVICE_FAMILY = "1,2"; 280 | VALIDATE_PRODUCT = YES; 281 | }; 282 | name = Profile; 283 | }; 284 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 285 | isa = XCBuildConfiguration; 286 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | CLANG_ENABLE_MODULES = YES; 290 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 291 | ENABLE_BITCODE = NO; 292 | INFOPLIST_FILE = Runner/Info.plist; 293 | LD_RUNPATH_SEARCH_PATHS = ( 294 | "$(inherited)", 295 | "@executable_path/Frameworks", 296 | ); 297 | PRODUCT_BUNDLE_IDENTIFIER = it.maionemiky.flutterFirebaseVideoCallWebrtc; 298 | PRODUCT_NAME = "$(TARGET_NAME)"; 299 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 300 | SWIFT_VERSION = 5.0; 301 | VERSIONING_SYSTEM = "apple-generic"; 302 | }; 303 | name = Profile; 304 | }; 305 | 97C147031CF9000F007C117D /* Debug */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ALWAYS_SEARCH_USER_PATHS = NO; 309 | CLANG_ANALYZER_NONNULL = YES; 310 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 311 | CLANG_CXX_LIBRARY = "libc++"; 312 | CLANG_ENABLE_MODULES = YES; 313 | CLANG_ENABLE_OBJC_ARC = YES; 314 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 315 | CLANG_WARN_BOOL_CONVERSION = YES; 316 | CLANG_WARN_COMMA = YES; 317 | CLANG_WARN_CONSTANT_CONVERSION = YES; 318 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 319 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 320 | CLANG_WARN_EMPTY_BODY = YES; 321 | CLANG_WARN_ENUM_CONVERSION = YES; 322 | CLANG_WARN_INFINITE_RECURSION = YES; 323 | CLANG_WARN_INT_CONVERSION = YES; 324 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 325 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 326 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 327 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 328 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 329 | CLANG_WARN_STRICT_PROTOTYPES = YES; 330 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 331 | CLANG_WARN_UNREACHABLE_CODE = YES; 332 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 333 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 334 | COPY_PHASE_STRIP = NO; 335 | DEBUG_INFORMATION_FORMAT = dwarf; 336 | ENABLE_STRICT_OBJC_MSGSEND = YES; 337 | ENABLE_TESTABILITY = YES; 338 | GCC_C_LANGUAGE_STANDARD = gnu99; 339 | GCC_DYNAMIC_NO_PIC = NO; 340 | GCC_NO_COMMON_BLOCKS = YES; 341 | GCC_OPTIMIZATION_LEVEL = 0; 342 | GCC_PREPROCESSOR_DEFINITIONS = ( 343 | "DEBUG=1", 344 | "$(inherited)", 345 | ); 346 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 347 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 348 | GCC_WARN_UNDECLARED_SELECTOR = YES; 349 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 350 | GCC_WARN_UNUSED_FUNCTION = YES; 351 | GCC_WARN_UNUSED_VARIABLE = YES; 352 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 353 | MTL_ENABLE_DEBUG_INFO = YES; 354 | ONLY_ACTIVE_ARCH = YES; 355 | SDKROOT = iphoneos; 356 | TARGETED_DEVICE_FAMILY = "1,2"; 357 | }; 358 | name = Debug; 359 | }; 360 | 97C147041CF9000F007C117D /* Release */ = { 361 | isa = XCBuildConfiguration; 362 | buildSettings = { 363 | ALWAYS_SEARCH_USER_PATHS = NO; 364 | CLANG_ANALYZER_NONNULL = YES; 365 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 366 | CLANG_CXX_LIBRARY = "libc++"; 367 | CLANG_ENABLE_MODULES = YES; 368 | CLANG_ENABLE_OBJC_ARC = YES; 369 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 370 | CLANG_WARN_BOOL_CONVERSION = YES; 371 | CLANG_WARN_COMMA = YES; 372 | CLANG_WARN_CONSTANT_CONVERSION = YES; 373 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 374 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 375 | CLANG_WARN_EMPTY_BODY = YES; 376 | CLANG_WARN_ENUM_CONVERSION = YES; 377 | CLANG_WARN_INFINITE_RECURSION = YES; 378 | CLANG_WARN_INT_CONVERSION = YES; 379 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 380 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 381 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 382 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 383 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 384 | CLANG_WARN_STRICT_PROTOTYPES = YES; 385 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 386 | CLANG_WARN_UNREACHABLE_CODE = YES; 387 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 388 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 389 | COPY_PHASE_STRIP = NO; 390 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 391 | ENABLE_NS_ASSERTIONS = NO; 392 | ENABLE_STRICT_OBJC_MSGSEND = YES; 393 | GCC_C_LANGUAGE_STANDARD = gnu99; 394 | GCC_NO_COMMON_BLOCKS = YES; 395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 396 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 397 | GCC_WARN_UNDECLARED_SELECTOR = YES; 398 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 399 | GCC_WARN_UNUSED_FUNCTION = YES; 400 | GCC_WARN_UNUSED_VARIABLE = YES; 401 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 402 | MTL_ENABLE_DEBUG_INFO = NO; 403 | SDKROOT = iphoneos; 404 | SUPPORTED_PLATFORMS = iphoneos; 405 | SWIFT_COMPILATION_MODE = wholemodule; 406 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 407 | TARGETED_DEVICE_FAMILY = "1,2"; 408 | VALIDATE_PRODUCT = YES; 409 | }; 410 | name = Release; 411 | }; 412 | 97C147061CF9000F007C117D /* Debug */ = { 413 | isa = XCBuildConfiguration; 414 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 415 | buildSettings = { 416 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 417 | CLANG_ENABLE_MODULES = YES; 418 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 419 | ENABLE_BITCODE = NO; 420 | INFOPLIST_FILE = Runner/Info.plist; 421 | LD_RUNPATH_SEARCH_PATHS = ( 422 | "$(inherited)", 423 | "@executable_path/Frameworks", 424 | ); 425 | PRODUCT_BUNDLE_IDENTIFIER = it.maionemiky.flutterFirebaseVideoCallWebrtc; 426 | PRODUCT_NAME = "$(TARGET_NAME)"; 427 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 428 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 429 | SWIFT_VERSION = 5.0; 430 | VERSIONING_SYSTEM = "apple-generic"; 431 | }; 432 | name = Debug; 433 | }; 434 | 97C147071CF9000F007C117D /* Release */ = { 435 | isa = XCBuildConfiguration; 436 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 437 | buildSettings = { 438 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 439 | CLANG_ENABLE_MODULES = YES; 440 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 441 | ENABLE_BITCODE = NO; 442 | INFOPLIST_FILE = Runner/Info.plist; 443 | LD_RUNPATH_SEARCH_PATHS = ( 444 | "$(inherited)", 445 | "@executable_path/Frameworks", 446 | ); 447 | PRODUCT_BUNDLE_IDENTIFIER = it.maionemiky.flutterFirebaseVideoCallWebrtc; 448 | PRODUCT_NAME = "$(TARGET_NAME)"; 449 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 450 | SWIFT_VERSION = 5.0; 451 | VERSIONING_SYSTEM = "apple-generic"; 452 | }; 453 | name = Release; 454 | }; 455 | /* End XCBuildConfiguration section */ 456 | 457 | /* Begin XCConfigurationList section */ 458 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 459 | isa = XCConfigurationList; 460 | buildConfigurations = ( 461 | 97C147031CF9000F007C117D /* Debug */, 462 | 97C147041CF9000F007C117D /* Release */, 463 | 249021D3217E4FDB00AE95B9 /* Profile */, 464 | ); 465 | defaultConfigurationIsVisible = 0; 466 | defaultConfigurationName = Release; 467 | }; 468 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 469 | isa = XCConfigurationList; 470 | buildConfigurations = ( 471 | 97C147061CF9000F007C117D /* Debug */, 472 | 97C147071CF9000F007C117D /* Release */, 473 | 249021D4217E4FDB00AE95B9 /* Profile */, 474 | ); 475 | defaultConfigurationIsVisible = 0; 476 | defaultConfigurationName = Release; 477 | }; 478 | /* End XCConfigurationList section */ 479 | }; 480 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 481 | } 482 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/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/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/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/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Flutter Firebase Video Call Webrtc 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | flutter_firebase_video_call_webrtc 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/firebase_app_id_file.json: -------------------------------------------------------------------------------- 1 | { 2 | "file_generated_by": "FlutterFire CLI", 3 | "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", 4 | "GOOGLE_APP_ID": "1:340047693711:ios:3fc08586602f0f4d5a6dfa", 5 | "FIREBASE_PROJECT_ID": "firebase-video-call-webrtc", 6 | "GCM_SENDER_ID": "340047693711" 7 | } -------------------------------------------------------------------------------- /lib/firebase_options.dart: -------------------------------------------------------------------------------- 1 | // File generated by FlutterFire CLI. 2 | // ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members 3 | import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; 4 | import 'package:flutter/foundation.dart' 5 | show defaultTargetPlatform, kIsWeb, TargetPlatform; 6 | 7 | /// Default [FirebaseOptions] for use with your Firebase apps. 8 | /// 9 | /// Example: 10 | /// ```dart 11 | /// import 'firebase_options.dart'; 12 | /// // ... 13 | /// await Firebase.initializeApp( 14 | /// options: DefaultFirebaseOptions.currentPlatform, 15 | /// ); 16 | /// ``` 17 | class DefaultFirebaseOptions { 18 | static FirebaseOptions get currentPlatform { 19 | if (kIsWeb) { 20 | return web; 21 | } 22 | switch (defaultTargetPlatform) { 23 | case TargetPlatform.android: 24 | return android; 25 | case TargetPlatform.iOS: 26 | return ios; 27 | case TargetPlatform.macOS: 28 | throw UnsupportedError( 29 | 'DefaultFirebaseOptions have not been configured for macos - ' 30 | 'you can reconfigure this by running the FlutterFire CLI again.', 31 | ); 32 | case TargetPlatform.windows: 33 | throw UnsupportedError( 34 | 'DefaultFirebaseOptions have not been configured for windows - ' 35 | 'you can reconfigure this by running the FlutterFire CLI again.', 36 | ); 37 | case TargetPlatform.linux: 38 | throw UnsupportedError( 39 | 'DefaultFirebaseOptions have not been configured for linux - ' 40 | 'you can reconfigure this by running the FlutterFire CLI again.', 41 | ); 42 | default: 43 | throw UnsupportedError( 44 | 'DefaultFirebaseOptions are not supported for this platform.', 45 | ); 46 | } 47 | } 48 | 49 | static const FirebaseOptions web = FirebaseOptions( 50 | apiKey: 'AIzaSyCTREJETzcup7ELYF9uYBHQrz0qUvqRs44', 51 | appId: '1:340047693711:web:d78f9dc9a2dad9ba5a6dfa', 52 | messagingSenderId: '340047693711', 53 | projectId: 'firebase-video-call-webrtc', 54 | authDomain: 'fir-video-call-webrtc.firebaseapp.com', 55 | storageBucket: 'firebase-video-call-webrtc.appspot.com', 56 | ); 57 | 58 | static const FirebaseOptions android = FirebaseOptions( 59 | apiKey: 'AIzaSyA05KPMeRCE4V8xnT2YeLpPlPLQm5gtxgM', 60 | appId: '1:340047693711:android:65c14573347575a15a6dfa', 61 | messagingSenderId: '340047693711', 62 | projectId: 'firebase-video-call-webrtc', 63 | storageBucket: 'firebase-video-call-webrtc.appspot.com', 64 | ); 65 | 66 | static const FirebaseOptions ios = FirebaseOptions( 67 | apiKey: 'AIzaSyBaUePsfg75I-tkLW_r8c-a1vIyjYcsApo', 68 | appId: '1:340047693711:ios:3fc08586602f0f4d5a6dfa', 69 | messagingSenderId: '340047693711', 70 | projectId: 'firebase-video-call-webrtc', 71 | storageBucket: 'firebase-video-call-webrtc.appspot.com', 72 | iosClientId: '340047693711-kn33imgei727fd4fsrnn8qc536rhuiep.apps.googleusercontent.com', 73 | iosBundleId: 'it.maionemiky.flutterFirebaseVideoCallWebrtc', 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:firebase_core/firebase_core.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_firebase_video_call_webrtc/firebase_options.dart'; 6 | import 'package:flutter_firebase_video_call_webrtc/signaling.dart'; 7 | import 'package:flutter_firebase_video_call_webrtc/snack_msg.dart'; 8 | import 'package:flutter_webrtc/flutter_webrtc.dart'; 9 | 10 | typedef ExecuteCallback = void Function(); 11 | typedef ExecuteFutureCallback = Future Function(); 12 | 13 | Future main() async { 14 | WidgetsFlutterBinding.ensureInitialized(); 15 | 16 | await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); 17 | 18 | runApp(const MyApp()); 19 | } 20 | 21 | class MyApp extends StatelessWidget { 22 | const MyApp({Key? key}) : super(key: key); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return MaterialApp( 27 | title: "WebRTC", 28 | theme: ThemeData(primarySwatch: Colors.orange), 29 | home: const MyHomePage(), 30 | ); 31 | } 32 | } 33 | 34 | class MyHomePage extends StatefulWidget { 35 | const MyHomePage({Key? key}) : super(key: key); 36 | 37 | @override 38 | _MyHomePageState createState() => _MyHomePageState(); 39 | } 40 | 41 | class _MyHomePageState extends State { 42 | static const _chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890'; 43 | static final _rnd = Random(); 44 | 45 | static String getRandomString(int length) => String.fromCharCodes(Iterable.generate(length, (index) => _chars.codeUnitAt(_rnd.nextInt(_chars.length)))); 46 | 47 | final signaling = Signaling(localDisplayName: getRandomString(20)); 48 | 49 | final localRenderer = RTCVideoRenderer(); 50 | final Map remoteRenderers = {}; 51 | final Map remoteRenderersLoading = {}; 52 | 53 | String roomId = ''; 54 | 55 | bool localRenderOk = false; 56 | bool error = false; 57 | 58 | @override 59 | void initState() { 60 | super.initState(); 61 | 62 | signaling.onAddLocalStream = (peerUuid, displayName, stream) { 63 | setState(() { 64 | localRenderer.srcObject = stream; 65 | localRenderOk = stream != null; 66 | }); 67 | }; 68 | 69 | signaling.onAddRemoteStream = (peerUuid, displayName, stream) async { 70 | final remoteRenderer = RTCVideoRenderer(); 71 | await remoteRenderer.initialize(); 72 | remoteRenderer.srcObject = stream; 73 | 74 | setState(() => remoteRenderers[peerUuid] = remoteRenderer); 75 | }; 76 | 77 | signaling.onRemoveRemoteStream = (peerUuid, displayName) { 78 | if (remoteRenderers.containsKey(peerUuid)) { 79 | remoteRenderers[peerUuid]!.srcObject = null; 80 | remoteRenderers[peerUuid]!.dispose(); 81 | 82 | setState(() { 83 | remoteRenderers.remove(peerUuid); 84 | remoteRenderersLoading.remove(peerUuid); 85 | }); 86 | } 87 | }; 88 | 89 | signaling.onConnectionConnected = (peerUuid, displayName) { 90 | setState(() => remoteRenderersLoading[peerUuid] = false); 91 | }; 92 | 93 | signaling.onConnectionLoading = (peerUuid, displayName) { 94 | setState(() => remoteRenderersLoading[peerUuid] = true); 95 | }; 96 | 97 | signaling.onConnectionError = (peerUuid, displayName) { 98 | SnackMsg.showError(context, 'Connection failed with $displayName'); 99 | error = true; 100 | }; 101 | 102 | signaling.onGenericError = (errorText) { 103 | SnackMsg.showError(context, errorText); 104 | error = true; 105 | }; 106 | 107 | initCamera(); 108 | } 109 | 110 | @override 111 | void dispose() { 112 | localRenderer.dispose(); 113 | 114 | disposeRemoteRenderers(); 115 | 116 | super.dispose(); 117 | } 118 | 119 | Future initCamera() async { 120 | await localRenderer.initialize(); 121 | await doTry(runAsync: () => signaling.openUserMedia()); 122 | } 123 | 124 | void disposeRemoteRenderers() { 125 | for (final remoteRenderer in remoteRenderers.values) { 126 | remoteRenderer.dispose(); 127 | } 128 | 129 | remoteRenderers.clear(); 130 | } 131 | 132 | Flex view({required List children}) { 133 | final isLandscape = MediaQuery.of(context).size.width > MediaQuery.of(context).size.height; 134 | return isLandscape ? Row(children: children) : Column(children: children); 135 | } 136 | 137 | Future doTry({ExecuteCallback? runSync, ExecuteFutureCallback? runAsync, ExecuteCallback? onError}) async { 138 | try { 139 | runSync?.call(); 140 | await runAsync?.call(); 141 | } catch (e) { 142 | SnackMsg.showError(context, 'Error: $e'); 143 | onError?.call(); 144 | } 145 | } 146 | 147 | Future reJoin() async { 148 | await hangUp(false); 149 | await join(); 150 | } 151 | 152 | Future join() async { 153 | setState(() => error = false); 154 | 155 | await signaling.reOpenUserMedia(); 156 | await signaling.join(roomId); 157 | } 158 | 159 | Future hangUp(bool exit) async { 160 | setState(() { 161 | error = false; 162 | 163 | if (exit) { 164 | roomId = ''; 165 | } 166 | }); 167 | 168 | await signaling.hangUp(exit); 169 | 170 | setState(() { 171 | disposeRemoteRenderers(); 172 | }); 173 | } 174 | 175 | bool isMicMuted() { 176 | try { 177 | return signaling.isMicMuted(); 178 | } catch (e) { 179 | SnackMsg.showError(context, 'Error: $e'); 180 | return true; 181 | } 182 | } 183 | 184 | @override 185 | Widget build(BuildContext context) { 186 | return Scaffold( 187 | appBar: AppBar(title: Text('WebRTC - ${signaling.localDisplayName}')), 188 | floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, 189 | floatingActionButton: FutureBuilder( 190 | future: signaling.cameraCount(), 191 | initialData: 0, 192 | builder: (context, cameraCountSnap) => Wrap( 193 | spacing: 15, 194 | children: [ 195 | if (!localRenderOk) ...[ 196 | FloatingActionButton( 197 | tooltip: 'Open camera', 198 | backgroundColor: Colors.redAccent, 199 | child: const Icon(Icons.videocam_off_outlined), 200 | onPressed: () async => await doTry( 201 | runAsync: () => signaling.reOpenUserMedia(), 202 | ), 203 | ), 204 | ], 205 | if (roomId.length > 2) ...[ 206 | if (error) ...[ 207 | FloatingActionButton( 208 | tooltip: 'Retry call', 209 | child: const Icon(Icons.add_call), 210 | backgroundColor: Colors.green, 211 | onPressed: () async => await doTry( 212 | runAsync: () => join(), 213 | onError: () => hangUp(false), 214 | ), 215 | ), 216 | ], 217 | if (localRenderOk && signaling.isJoined()) ...[ 218 | FloatingActionButton( 219 | tooltip: signaling.isScreenSharing() ? 'Change screen sharing' : 'Start screen sharing', 220 | backgroundColor: signaling.isScreenSharing() ? Colors.amber : Colors.grey, 221 | child: const Icon(Icons.screen_share_outlined), 222 | onPressed: () async => await doTry( 223 | runAsync: () => signaling.screenSharing(), 224 | ), 225 | ), 226 | if (signaling.isScreenSharing()) ...[ 227 | FloatingActionButton( 228 | tooltip: 'Stop screen sharing', 229 | backgroundColor: Colors.redAccent, 230 | child: const Icon(Icons.stop_screen_share_outlined), 231 | onPressed: () => signaling.stopScreenSharing(), 232 | ), 233 | ], 234 | if (cameraCountSnap.hasData && cameraCountSnap.requireData > 1) ...[ 235 | FloatingActionButton( 236 | tooltip: 'Switch camera', 237 | backgroundColor: Colors.grey, 238 | child: const Icon(Icons.switch_camera), 239 | onPressed: () async => await doTry( 240 | runAsync: () => signaling.switchCamera(), 241 | ), 242 | ) 243 | ], 244 | FloatingActionButton( 245 | tooltip: isMicMuted() ? 'Un-mute mic' : 'Mute mic', 246 | backgroundColor: isMicMuted() ? Colors.redAccent : Colors.grey, 247 | child: isMicMuted() ? const Icon(Icons.mic_off) : const Icon(Icons.mic_outlined), 248 | onPressed: () => doTry( 249 | runSync: () => setState(() => signaling.muteMic()), 250 | ), 251 | ), 252 | FloatingActionButton( 253 | tooltip: 'Hangup', 254 | backgroundColor: Colors.red, 255 | child: const Icon(Icons.call_end), 256 | onPressed: () => hangUp(false), 257 | ), 258 | FloatingActionButton( 259 | tooltip: 'Exit', 260 | backgroundColor: Colors.red, 261 | child: const Icon(Icons.exit_to_app), 262 | onPressed: () => hangUp(true), 263 | ), 264 | ] else ...[ 265 | FloatingActionButton( 266 | tooltip: 'Start call', 267 | child: const Icon(Icons.call), 268 | backgroundColor: Colors.green, 269 | onPressed: () async => await doTry( 270 | runAsync: () => join(), 271 | onError: () => hangUp(false), 272 | ), 273 | ), 274 | ], 275 | ], 276 | ], 277 | ), 278 | ), 279 | body: Container( 280 | margin: const EdgeInsets.all(8.0), 281 | child: Column( 282 | children: [ 283 | // room 284 | Container( 285 | margin: const EdgeInsets.all(8.0), 286 | child: Row( 287 | mainAxisAlignment: MainAxisAlignment.center, 288 | children: [ 289 | const Text("Room ID: "), 290 | Flexible( 291 | child: TextFormField( 292 | initialValue: roomId, 293 | onChanged: (value) => setState(() => roomId = value), 294 | ), 295 | ) 296 | ], 297 | ), 298 | ), 299 | 300 | // streaming 301 | Expanded( 302 | child: view( 303 | children: [ 304 | if (localRenderOk) ...[ 305 | Expanded( 306 | child: Container( 307 | margin: const EdgeInsets.all(4), 308 | padding: const EdgeInsets.all(4), 309 | decoration: BoxDecoration( 310 | border: Border.all( 311 | color: const Color(0XFF2493FB), 312 | ), 313 | ), 314 | child: RTCVideoView(localRenderer, mirror: !signaling.isScreenSharing()), 315 | ), 316 | ), 317 | ], 318 | for (final remoteRenderer in remoteRenderers.entries) ...[ 319 | Expanded( 320 | child: Container( 321 | margin: const EdgeInsets.all(4), 322 | padding: const EdgeInsets.all(4), 323 | decoration: BoxDecoration( 324 | border: Border.all( 325 | color: const Color(0XFF2493FB), 326 | ), 327 | ), 328 | child: false == remoteRenderersLoading[remoteRenderer.key] // && true == remoteRenderer.value.srcObject?.active 329 | ? RTCVideoView(remoteRenderer.value) 330 | : const Center( 331 | child: CircularProgressIndicator(), 332 | ), 333 | ), 334 | ), 335 | ], 336 | ], 337 | ), 338 | ), 339 | ], 340 | ), 341 | ), 342 | ); 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /lib/signaling.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:collection'; 3 | import 'dart:math'; 4 | 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:flutter/foundation.dart'; 7 | import 'package:flutter_webrtc/flutter_webrtc.dart'; 8 | import 'package:uuid/uuid.dart'; 9 | 10 | typedef ErrorCallback = void Function(String error); 11 | typedef PeerCallback = void Function(String peerUuid, String displayName); 12 | typedef StreamStateCallback = void Function(String peerUuid, String displayName, MediaStream? stream); 13 | typedef ConnectionClosedCallback = RTCVideoRenderer Function(); 14 | 15 | class Signaling { 16 | MediaStream? _localStream, _shareStream; 17 | StreamStateCallback? onAddRemoteStream, onAddLocalStream; 18 | PeerCallback? onRemoveRemoteStream, onConnectionLoading, onConnectionConnected, onConnectionError; 19 | ErrorCallback? onGenericError; 20 | 21 | // key is uuid, values are peer connection object and user defined display name string 22 | final Map _peerConnections = {}; 23 | final _peerBanned = HashSet(); 24 | 25 | static const collectionVideoCall = 'videoCall'; 26 | static const tableConnectionParamsFor = 'connectionParamsFor'; 27 | static const tablePeers = 'peers'; 28 | static const tableSdp = 'sdp'; 29 | static const tableIce = 'ice'; 30 | 31 | final String localDisplayName; 32 | String? _localUuid, _appointmentId; 33 | 34 | StreamSubscription? _listenerSdp, _listenerIce; 35 | 36 | Signaling({required this.localDisplayName}); 37 | 38 | final _iceServers = { 39 | 'iceServers': [ 40 | { 41 | // coturn server 42 | 'urls': ['turn:80.211.89.209:3478'], 43 | 'username': 'coturn', 44 | 'credential': 'coturn', 45 | }, 46 | ] 47 | }; 48 | 49 | Future cameraCount() async { 50 | if (isScreenSharing()) { 51 | return 0; 52 | } else { 53 | try { 54 | final cams = await Helper.cameras; 55 | 56 | return kIsWeb ? min(cams.length, 1) : cams.length; 57 | } catch (e) { 58 | // camera not accessible, like for screen sharing or other problems 59 | onGenericError?.call('Error: $e'); 60 | return 0; 61 | } 62 | } 63 | } 64 | 65 | bool isJoined() { 66 | return _appointmentId != null; 67 | } 68 | 69 | Future switchCamera() async { 70 | if (!kIsWeb && _localStream != null) { 71 | await Helper.switchCamera(_localStream!.getVideoTracks().first); 72 | } 73 | } 74 | 75 | bool isMicMuted() { 76 | return !isMicEnabled(); 77 | } 78 | 79 | bool isMicEnabled() { 80 | if (_localStream != null) { 81 | return _localStream!.getAudioTracks().first.enabled; 82 | } 83 | 84 | return true; 85 | } 86 | 87 | bool isScreenSharing() { 88 | return _shareStream != null; 89 | } 90 | 91 | Future stopScreenSharing() async { 92 | if (_shareStream != null) { 93 | for (final track in _shareStream!.getTracks()) { 94 | await track.stop(); 95 | } 96 | 97 | await _shareStream?.dispose(); 98 | _shareStream = null; 99 | } 100 | 101 | await _replaceStream(_localStream!); 102 | } 103 | 104 | Future screenSharing() async { 105 | _shareStream = await navigator.mediaDevices.getDisplayMedia({ 106 | 'audio': false, 107 | 'video': { 108 | 'cursor': 'always', 109 | }, 110 | }); 111 | 112 | await _replaceStream(_shareStream!); 113 | } 114 | 115 | void muteMic() { 116 | if (_localStream != null) { 117 | _localStream!.getAudioTracks()[0].enabled = !isMicEnabled(); 118 | } 119 | } 120 | 121 | Future hangUp(bool updateLocalVideo) async { 122 | await _listenerSdp?.cancel(); 123 | await _listenerIce?.cancel(); 124 | 125 | _listenerSdp = null; 126 | _listenerIce = null; 127 | 128 | await stopScreenSharing(); 129 | 130 | if (updateLocalVideo) { 131 | onAddLocalStream?.call('', localDisplayName, null); 132 | 133 | if (_localStream != null) { 134 | for (final track in _localStream!.getTracks()) { 135 | await track.stop(); 136 | } 137 | 138 | await _localStream!.dispose(); 139 | _localStream = null; 140 | } 141 | } 142 | 143 | for (final pc in _peerConnections.values) { 144 | await _closePeerConnection(pc); 145 | } 146 | 147 | _peerConnections.clear(); 148 | _peerBanned.clear(); 149 | 150 | await _clearAllFirebaseData(); 151 | 152 | _appointmentId = null; 153 | } 154 | 155 | Future join(String appointmentId) async { 156 | _appointmentId = appointmentId; 157 | _localUuid = const Uuid().v1(); 158 | 159 | if (_localStream == null) { 160 | throw Exception('You can not start a call without the webcam opened'); 161 | } 162 | 163 | final peers = await FirebaseFirestore.instance 164 | .collection( 165 | collectionVideoCall, 166 | ) 167 | .doc(_appointmentId) 168 | .collection(tablePeers) 169 | .where( 170 | 'uuid', 171 | isNotEqualTo: _localUuid, // exclude my self 172 | ) 173 | .get(); 174 | 175 | for (final peer in peers.docs) { 176 | await _helloEveryone(peer.data()); 177 | } 178 | 179 | // add my self to peers list 180 | await _writePeer({ 181 | 'uuid': _localUuid, 182 | 'displayName': localDisplayName, 183 | 'created': FieldValue.serverTimestamp(), 184 | }); 185 | 186 | _startListenSdp(); 187 | } 188 | 189 | void _startListenIce() { 190 | // WebRTC ICE 191 | _listenerIce ??= FirebaseFirestore.instance 192 | .collection( 193 | collectionVideoCall, 194 | ) 195 | .doc(_appointmentId) 196 | .collection(tableConnectionParamsFor) 197 | .doc(_localUuid) 198 | .collection(tableIce) 199 | .orderBy('created') 200 | .snapshots() 201 | .listen( 202 | (snapshot) { 203 | for (final ice in snapshot.docs) { 204 | _manageIce(ice.data()); 205 | } 206 | }, 207 | ); 208 | } 209 | 210 | void _startListenSdp() { 211 | // WebRTC SDP 212 | _listenerSdp ??= FirebaseFirestore.instance 213 | .collection( 214 | collectionVideoCall, 215 | ) 216 | .doc(_appointmentId) 217 | .collection(tableConnectionParamsFor) 218 | .doc(_localUuid) 219 | .collection(tableSdp) 220 | .orderBy('created') 221 | .snapshots() 222 | .listen( 223 | (snapshot) { 224 | for (final sdp in snapshot.docs) { 225 | _manageSdp(sdp.data()); 226 | } 227 | }, 228 | ); 229 | } 230 | 231 | Future _helloEveryone(Map peerData) async { 232 | final String peerId = peerData['uuid']; 233 | final String displayName = peerData['displayName']; 234 | 235 | if (_peerConnections.containsKey(peerId)) { 236 | throw Exception('Peer $peerId already exists!'); 237 | } else { 238 | final pc = await _createPeerConnection(peerId, displayName); 239 | await _createdDescription(pc, await pc.createOffer(), peerId, 'offer'); 240 | } 241 | } 242 | 243 | Future _createPeerConnection(String fromPeerId, String displayName) async { 244 | if (kDebugMode) { 245 | print('Create PeerConnection with configuration: $_iceServers'); 246 | } 247 | 248 | final pc = await createPeerConnection(_iceServers); 249 | 250 | _peerConnections.putIfAbsent(fromPeerId, () => pc); 251 | 252 | pc.onIceCandidate = (event) => _gotIceCandidate(event, fromPeerId, displayName); 253 | pc.onTrack = (event) => _gotRemoteStream(event, fromPeerId, displayName); 254 | pc.onIceConnectionState = (event) => _checkConnectionState(event, fromPeerId, displayName); 255 | 256 | for (final track in _localStream!.getTracks()) { 257 | await pc.addTrack(track, _localStream!); 258 | } 259 | 260 | return pc; 261 | } 262 | 263 | Future _manageSdp(Map receivedMsg) async { 264 | if (receivedMsg.containsKey('sdp')) { 265 | final String fromPeerId = receivedMsg['uuid']; 266 | final String displayName = receivedMsg['displayName']; 267 | final sdp = receivedMsg['sdp']; 268 | final sdpType = receivedMsg['sdpType']; 269 | 270 | if (!_peerBanned.contains(fromPeerId)) { 271 | try { 272 | if ('offer' == sdpType) { 273 | // peer A that was in room, receive an Offer from peer B, and send an Answer 274 | 275 | if (!_peerConnections.containsKey(fromPeerId)) { 276 | final pc = await _createPeerConnection(fromPeerId, displayName); 277 | 278 | await pc.setRemoteDescription(RTCSessionDescription(sdp['sdp'], sdp['type'])); 279 | 280 | await _createdDescription(pc, await pc.createAnswer(), fromPeerId, 'answer'); 281 | _startListenIce(); 282 | } 283 | } else if ('answer' == sdpType) { 284 | // peer B enter room, that sent an Offer to peer A receive an Answer from peer A 285 | final pc = _peerConnections[fromPeerId]; 286 | 287 | if (pc?.getRemoteDescription() != null) { 288 | switch (pc?.signalingState) { 289 | case RTCSignalingState.RTCSignalingStateStable: 290 | case RTCSignalingState.RTCSignalingStateClosed: 291 | break; 292 | 293 | default: 294 | await pc?.setRemoteDescription(RTCSessionDescription(sdp['sdp'], sdp['type'])); 295 | _startListenIce(); 296 | break; 297 | } 298 | } 299 | } 300 | } catch (sdpE) { 301 | onGenericError?.call('Error in SDP routine from $displayName: $sdpE'); 302 | } 303 | } 304 | } 305 | } 306 | 307 | Future _manageIce(Map receivedMsg) async { 308 | if (receivedMsg.containsKey('ice')) { 309 | final String fromPeerId = receivedMsg['uuid']; 310 | final String displayName = receivedMsg['displayName']; 311 | final ice = receivedMsg['ice']; 312 | 313 | if (!_peerBanned.contains(fromPeerId)) { 314 | try { 315 | if (_peerConnections.containsKey(fromPeerId)) { 316 | final pc = _peerConnections[fromPeerId]!; 317 | 318 | await pc.addCandidate(RTCIceCandidate(ice['candidate'], ice['sdpMid'], ice['sdpMLineIndex'])); 319 | } else { 320 | throw Exception('Received ICE candidate, but $displayName have not a peer connection'); 321 | } 322 | } catch (iceE) { 323 | onGenericError?.call('Error in ICE routine from $displayName: $iceE'); 324 | } 325 | } 326 | } 327 | } 328 | 329 | Future _createdDescription(RTCPeerConnection pc, RTCSessionDescription description, String destinationPeerId, String sdpType) async { 330 | if (kDebugMode) { 331 | print('create description $sdpType, for peer $destinationPeerId'); 332 | } 333 | 334 | await pc.setLocalDescription(description); 335 | 336 | final sdp = { 337 | 'uuid': _localUuid, 338 | 'displayName': localDisplayName, 339 | 'created': FieldValue.serverTimestamp(), 340 | 'sdpType': sdpType, 341 | 'sdp': description.toMap(), 342 | }; 343 | 344 | await FirebaseFirestore.instance 345 | .collection( 346 | collectionVideoCall, 347 | ) 348 | .doc(_appointmentId) 349 | .collection(tableConnectionParamsFor) 350 | .doc(destinationPeerId) 351 | .collection(tableSdp) 352 | .add(sdp); 353 | } 354 | 355 | void _checkConnectionState(RTCIceConnectionState state, String peerUuid, String displayName) { 356 | if (!_peerBanned.contains(peerUuid)) { 357 | if (kDebugMode) { 358 | print('connection with peer $displayName: $state'); 359 | } 360 | 361 | switch (state) { 362 | case RTCIceConnectionState.RTCIceConnectionStateNew: 363 | case RTCIceConnectionState.RTCIceConnectionStateChecking: 364 | onConnectionLoading?.call(peerUuid, displayName); 365 | break; 366 | 367 | case RTCIceConnectionState.RTCIceConnectionStateConnected: 368 | case RTCIceConnectionState.RTCIceConnectionStateCompleted: 369 | onConnectionConnected?.call(peerUuid, displayName); 370 | break; 371 | 372 | case RTCIceConnectionState.RTCIceConnectionStateFailed: 373 | _peerBanned.add(peerUuid); 374 | 375 | onConnectionError?.call(peerUuid, displayName); 376 | 377 | _removePeer(peerUuid, displayName); 378 | break; 379 | 380 | case RTCIceConnectionState.RTCIceConnectionStateDisconnected: 381 | case RTCIceConnectionState.RTCIceConnectionStateClosed: 382 | _removePeer(peerUuid, displayName); 383 | break; 384 | 385 | default: 386 | break; 387 | } 388 | } 389 | } 390 | 391 | Future _closePeerConnection(RTCPeerConnection pc) async { 392 | await pc.close(); 393 | await pc.dispose(); 394 | } 395 | 396 | Future _removePeer(String peerUuid, String displayName) async { 397 | if (_peerConnections.containsKey(peerUuid)) { 398 | await _closePeerConnection(_peerConnections[peerUuid]!); 399 | 400 | _peerConnections.remove(peerUuid); 401 | 402 | onRemoveRemoteStream?.call(peerUuid, displayName); 403 | } 404 | } 405 | 406 | void _gotRemoteStream(RTCTrackEvent event, String peerUuid, String displayName) { 407 | if (!_peerBanned.contains(peerUuid)) { 408 | if (kDebugMode) { 409 | print('got remote stream, peer $displayName'); 410 | } 411 | 412 | onAddRemoteStream?.call(peerUuid, displayName, event.streams[0]); 413 | } 414 | } 415 | 416 | Future _gotIceCandidate(RTCIceCandidate iceCandidate, String peerUuid, String displayName) async { 417 | if (!_peerBanned.contains(peerUuid)) { 418 | if (kDebugMode) { 419 | print('got ice candidate, peer $displayName'); 420 | } 421 | 422 | if (iceCandidate.candidate?.isNotEmpty ?? false) { 423 | final ice = { 424 | 'uuid': _localUuid, 425 | 'displayName': localDisplayName, 426 | 'created': FieldValue.serverTimestamp(), 427 | 'ice': iceCandidate.toMap(), 428 | }; 429 | 430 | await FirebaseFirestore.instance 431 | .collection( 432 | collectionVideoCall, 433 | ) 434 | .doc(_appointmentId) 435 | .collection(tableConnectionParamsFor) 436 | .doc(peerUuid) 437 | .collection(tableIce) 438 | .add(ice); 439 | } 440 | } 441 | } 442 | 443 | Future reOpenUserMedia() async { 444 | if (_localStream == null) { 445 | await openUserMedia(); 446 | } 447 | } 448 | 449 | Future openUserMedia() async { 450 | _localStream = await navigator.mediaDevices.getUserMedia({ 451 | 'audio': true, 452 | 'video': { 453 | 'facingMode': 'user', // front camera 454 | } 455 | }); 456 | 457 | if ((_localStream?.getVideoTracks().length ?? 0) == 0) { 458 | throw Exception('There are no video tracks'); 459 | } else { 460 | if (kDebugMode) { 461 | print('Video tracks: ${_localStream?.getVideoTracks().length}'); 462 | } 463 | } 464 | 465 | if ((_localStream?.getAudioTracks().length ?? 0) == 0) { 466 | throw Exception('There are no audio tracks'); 467 | } else { 468 | if (kDebugMode) { 469 | print('Audio tracks: ${_localStream?.getAudioTracks().length}'); 470 | } 471 | } 472 | 473 | onAddLocalStream?.call('', localDisplayName, _localStream!); 474 | } 475 | 476 | Future _writePeer(Map msg) async { 477 | await FirebaseFirestore.instance 478 | .collection( 479 | collectionVideoCall, 480 | ) 481 | .doc(_appointmentId) 482 | .collection(tablePeers) 483 | .add(msg); 484 | } 485 | 486 | Future _clearAllFirebaseData() async { 487 | // remove me from peers 488 | await FirebaseFirestore.instance 489 | .collection(collectionVideoCall) 490 | .doc(_appointmentId) 491 | .collection(tablePeers) 492 | .where( 493 | 'uuid', 494 | isEqualTo: _localUuid, 495 | ) 496 | .get() 497 | .then( 498 | (snapshot) async { 499 | for (final peer in snapshot.docs) { 500 | await peer.reference.delete(); 501 | } 502 | }, 503 | ); 504 | 505 | // remove all params for me 506 | final docRef = await FirebaseFirestore.instance 507 | .collection( 508 | collectionVideoCall, 509 | ) 510 | .doc(_appointmentId) 511 | .collection(tableConnectionParamsFor) 512 | .doc(_localUuid) 513 | .get(); 514 | 515 | await docRef.reference.delete(); 516 | } 517 | 518 | Future _replaceStream(MediaStream stream) async { 519 | final track = stream.getVideoTracks().first; 520 | 521 | for (final pc in _peerConnections.values) { 522 | final senders = await pc.getSenders(); 523 | 524 | for (final s in senders) { 525 | if ('video' == s.track?.kind) { 526 | await s.replaceTrack(track); 527 | } 528 | } 529 | } 530 | 531 | if (kDebugMode) { 532 | print('Video tracks: ${stream.getVideoTracks().length}'); 533 | print('Audio tracks: ${stream.getAudioTracks().length}'); 534 | } 535 | 536 | onAddLocalStream?.call(_localUuid!, localDisplayName, stream); 537 | } 538 | } 539 | -------------------------------------------------------------------------------- /lib/snack_msg.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | enum TypeOfMsg { ok, info, error } 4 | 5 | class SnackMsg { 6 | static void showOk(BuildContext context, String text) { 7 | show(context, text, TypeOfMsg.ok); 8 | } 9 | 10 | static void showInfo(BuildContext context, String text) { 11 | show(context, text, TypeOfMsg.info); 12 | } 13 | 14 | static void showError(BuildContext context, String text) { 15 | show(context, text, TypeOfMsg.error); 16 | } 17 | 18 | static Color _colorByTypeOfMsg(TypeOfMsg typeOfMsg) { 19 | switch (typeOfMsg) { 20 | case TypeOfMsg.ok: 21 | return const Color(0XFF16F28B); 22 | case TypeOfMsg.info: 23 | return const Color(0XFF2493FB); 24 | case TypeOfMsg.error: 25 | return const Color(0XFFFF4D4F); 26 | } 27 | } 28 | 29 | static void show(BuildContext context, String text, TypeOfMsg typeOfMsg, {SnackBarAction? snackBarAction}) { 30 | final snackBar = SnackBar( 31 | backgroundColor: _colorByTypeOfMsg(typeOfMsg), 32 | content: Row( 33 | children: [ 34 | if (typeOfMsg == TypeOfMsg.ok) ...[ 35 | const Icon(Icons.done, color: Colors.white), 36 | ] else if (typeOfMsg == TypeOfMsg.info) ...[ 37 | const Icon(Icons.info_outline, color: Colors.white), 38 | ] else if (typeOfMsg == TypeOfMsg.error) ...[ 39 | const Icon(Icons.dangerous, color: Colors.white), 40 | ], 41 | const SizedBox(width: 20), 42 | Expanded( 43 | child: Text(text), 44 | ), 45 | ], 46 | ), 47 | action: snackBarAction ?? 48 | SnackBarAction( 49 | label: 'Dismiss', 50 | textColor: Colors.white, 51 | onPressed: () => ScaffoldMessenger.of(context).hideCurrentSnackBar(), 52 | ), 53 | ); 54 | 55 | ScaffoldMessenger.of(context).showSnackBar(snackBar); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/turn.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | 5 | import 'package:flutter/foundation.dart'; 6 | 7 | Future> getTurnCredential(String host, int port) async { 8 | final client = HttpClient(context: SecurityContext()); 9 | 10 | client.badCertificateCallback = (cert, host, port) { 11 | if (kDebugMode) { 12 | print('getTurnCredential: Allow self-signed certificate => $host:$port. '); 13 | } 14 | 15 | return true; 16 | }; 17 | 18 | final url = 'https://$host:$port/api/turn?service=turn&username=flutter-webrtc'; 19 | final request = await client.getUrl(Uri.parse(url)); 20 | final response = await request.close(); 21 | final responseBody = await response.transform(const Utf8Decoder()).join(); 22 | 23 | if (kDebugMode) { 24 | print('getTurnCredential:response => $responseBody.'); 25 | } 26 | 27 | return const JsonDecoder().convert(responseBody); 28 | } 29 | -------------------------------------------------------------------------------- /lib/turn_web.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:http/http.dart' as http; 5 | 6 | Future getTurnCredential(String host, int port) async { 7 | final url = 'https://$host:$port/api/turn?service=turn&username=flutter-webrtc'; 8 | final res = await http.get(Uri.parse(url)); 9 | 10 | if (res.statusCode == 200) { 11 | final data = json.decode(res.body); 12 | 13 | if (kDebugMode) { 14 | print('getTurnCredential:response => $data.'); 15 | } 16 | 17 | return data; 18 | } 19 | 20 | return {}; 21 | } 22 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.8.2" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.2.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | cloud_firestore: 40 | dependency: "direct main" 41 | description: 42 | name: cloud_firestore 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "3.1.8" 46 | cloud_firestore_platform_interface: 47 | dependency: "direct main" 48 | description: 49 | name: cloud_firestore_platform_interface 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "5.4.13" 53 | cloud_firestore_web: 54 | dependency: "direct main" 55 | description: 56 | name: cloud_firestore_web 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.6.8" 60 | collection: 61 | dependency: transitive 62 | description: 63 | name: collection 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.15.0" 67 | crypto: 68 | dependency: transitive 69 | description: 70 | name: crypto 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "3.0.2" 74 | cupertino_icons: 75 | dependency: "direct main" 76 | description: 77 | name: cupertino_icons 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "1.0.4" 81 | dart_webrtc: 82 | dependency: transitive 83 | description: 84 | name: dart_webrtc 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "1.0.6" 88 | fake_async: 89 | dependency: transitive 90 | description: 91 | name: fake_async 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "1.2.0" 95 | ffi: 96 | dependency: transitive 97 | description: 98 | name: ffi 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "1.2.1" 102 | file: 103 | dependency: transitive 104 | description: 105 | name: file 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "6.1.2" 109 | firebase_core: 110 | dependency: "direct main" 111 | description: 112 | name: firebase_core 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "1.12.0" 116 | firebase_core_platform_interface: 117 | dependency: transitive 118 | description: 119 | name: firebase_core_platform_interface 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "4.2.4" 123 | firebase_core_web: 124 | dependency: transitive 125 | description: 126 | name: firebase_core_web 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "1.6.0" 130 | flutter: 131 | dependency: "direct main" 132 | description: flutter 133 | source: sdk 134 | version: "0.0.0" 135 | flutter_lints: 136 | dependency: "direct dev" 137 | description: 138 | name: flutter_lints 139 | url: "https://pub.dartlang.org" 140 | source: hosted 141 | version: "1.0.4" 142 | flutter_test: 143 | dependency: "direct dev" 144 | description: flutter 145 | source: sdk 146 | version: "0.0.0" 147 | flutter_web_plugins: 148 | dependency: transitive 149 | description: flutter 150 | source: sdk 151 | version: "0.0.0" 152 | flutter_webrtc: 153 | dependency: "direct main" 154 | description: 155 | name: flutter_webrtc 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "0.8.7" 159 | http: 160 | dependency: "direct main" 161 | description: 162 | name: http 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "0.13.4" 166 | http_parser: 167 | dependency: transitive 168 | description: 169 | name: http_parser 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "4.0.1" 173 | js: 174 | dependency: transitive 175 | description: 176 | name: js 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "0.6.3" 180 | lints: 181 | dependency: transitive 182 | description: 183 | name: lints 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "1.0.1" 187 | matcher: 188 | dependency: transitive 189 | description: 190 | name: matcher 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "0.12.11" 194 | meta: 195 | dependency: transitive 196 | description: 197 | name: meta 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "1.7.0" 201 | path: 202 | dependency: transitive 203 | description: 204 | name: path 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "1.8.0" 208 | path_provider: 209 | dependency: transitive 210 | description: 211 | name: path_provider 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "2.0.10" 215 | path_provider_android: 216 | dependency: transitive 217 | description: 218 | name: path_provider_android 219 | url: "https://pub.dartlang.org" 220 | source: hosted 221 | version: "2.0.14" 222 | path_provider_ios: 223 | dependency: transitive 224 | description: 225 | name: path_provider_ios 226 | url: "https://pub.dartlang.org" 227 | source: hosted 228 | version: "2.0.9" 229 | path_provider_linux: 230 | dependency: transitive 231 | description: 232 | name: path_provider_linux 233 | url: "https://pub.dartlang.org" 234 | source: hosted 235 | version: "2.1.6" 236 | path_provider_macos: 237 | dependency: transitive 238 | description: 239 | name: path_provider_macos 240 | url: "https://pub.dartlang.org" 241 | source: hosted 242 | version: "2.0.6" 243 | path_provider_platform_interface: 244 | dependency: transitive 245 | description: 246 | name: path_provider_platform_interface 247 | url: "https://pub.dartlang.org" 248 | source: hosted 249 | version: "2.0.4" 250 | path_provider_windows: 251 | dependency: transitive 252 | description: 253 | name: path_provider_windows 254 | url: "https://pub.dartlang.org" 255 | source: hosted 256 | version: "2.0.6" 257 | platform: 258 | dependency: transitive 259 | description: 260 | name: platform 261 | url: "https://pub.dartlang.org" 262 | source: hosted 263 | version: "3.1.0" 264 | plugin_platform_interface: 265 | dependency: transitive 266 | description: 267 | name: plugin_platform_interface 268 | url: "https://pub.dartlang.org" 269 | source: hosted 270 | version: "2.1.2" 271 | process: 272 | dependency: transitive 273 | description: 274 | name: process 275 | url: "https://pub.dartlang.org" 276 | source: hosted 277 | version: "4.2.4" 278 | sky_engine: 279 | dependency: transitive 280 | description: flutter 281 | source: sdk 282 | version: "0.0.99" 283 | source_span: 284 | dependency: transitive 285 | description: 286 | name: source_span 287 | url: "https://pub.dartlang.org" 288 | source: hosted 289 | version: "1.8.1" 290 | stack_trace: 291 | dependency: transitive 292 | description: 293 | name: stack_trace 294 | url: "https://pub.dartlang.org" 295 | source: hosted 296 | version: "1.10.0" 297 | stream_channel: 298 | dependency: transitive 299 | description: 300 | name: stream_channel 301 | url: "https://pub.dartlang.org" 302 | source: hosted 303 | version: "2.1.0" 304 | string_scanner: 305 | dependency: transitive 306 | description: 307 | name: string_scanner 308 | url: "https://pub.dartlang.org" 309 | source: hosted 310 | version: "1.1.0" 311 | term_glyph: 312 | dependency: transitive 313 | description: 314 | name: term_glyph 315 | url: "https://pub.dartlang.org" 316 | source: hosted 317 | version: "1.2.0" 318 | test_api: 319 | dependency: transitive 320 | description: 321 | name: test_api 322 | url: "https://pub.dartlang.org" 323 | source: hosted 324 | version: "0.4.3" 325 | typed_data: 326 | dependency: transitive 327 | description: 328 | name: typed_data 329 | url: "https://pub.dartlang.org" 330 | source: hosted 331 | version: "1.3.0" 332 | uuid: 333 | dependency: "direct main" 334 | description: 335 | name: uuid 336 | url: "https://pub.dartlang.org" 337 | source: hosted 338 | version: "3.0.6" 339 | vector_math: 340 | dependency: transitive 341 | description: 342 | name: vector_math 343 | url: "https://pub.dartlang.org" 344 | source: hosted 345 | version: "2.1.1" 346 | webrtc_interface: 347 | dependency: transitive 348 | description: 349 | name: webrtc_interface 350 | url: "https://pub.dartlang.org" 351 | source: hosted 352 | version: "1.0.4" 353 | win32: 354 | dependency: transitive 355 | description: 356 | name: win32 357 | url: "https://pub.dartlang.org" 358 | source: hosted 359 | version: "2.5.2" 360 | xdg_directories: 361 | dependency: transitive 362 | description: 363 | name: xdg_directories 364 | url: "https://pub.dartlang.org" 365 | source: hosted 366 | version: "0.2.0+1" 367 | sdks: 368 | dart: ">=2.15.1 <3.0.0" 369 | flutter: ">=2.8.1" 370 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_firebase_video_call_webrtc 2 | description: a Flutter Video Call Demo using WebRTC, Coturn and Firebase 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter 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.15.1 <3.0.0" 22 | 23 | # Dependencies specify other packages that your package needs in order to work. 24 | # To automatically upgrade your package dependencies to the latest versions 25 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 26 | # dependencies can be manually updated by changing the version numbers below to 27 | # the latest version available on pub.dev. To see which dependencies have newer 28 | # versions available, run `flutter pub outdated`. 29 | dependencies: 30 | flutter: 31 | sdk: flutter 32 | 33 | 34 | # The following adds the Cupertino Icons font to your application. 35 | # Use with the CupertinoIcons class for iOS style icons. 36 | cupertino_icons: ^1.0.2 37 | http: ^0.13.4 38 | flutter_webrtc: ^0.8.7 39 | firebase_core: ^1.12.0 40 | cloud_firestore: ^3.1.8 41 | cloud_firestore_web: 2.6.8 42 | cloud_firestore_platform_interface: 5.4.13 43 | uuid: ^3.0.6 44 | 45 | dev_dependencies: 46 | flutter_test: 47 | sdk: flutter 48 | 49 | # The "flutter_lints" package below contains a set of recommended lints to 50 | # encourage good coding practices. The lint set provided by the package is 51 | # activated in the `analysis_options.yaml` file located at the root of your 52 | # package. See that file for information about deactivating specific lint 53 | # rules and activating additional ones. 54 | flutter_lints: ^1.0.0 55 | 56 | # For information on the generic Dart part of this file, see the 57 | # following page: https://dart.dev/tools/pub/pubspec 58 | 59 | # The following section is specific to Flutter. 60 | flutter: 61 | 62 | # The following line ensures that the Material Icons font is 63 | # included with your application, so that you can use the icons in 64 | # the material Icons class. 65 | uses-material-design: true 66 | 67 | # To add assets to your application, add an assets section, like this: 68 | # assets: 69 | # - images/a_dot_burr.jpeg 70 | # - images/a_dot_ham.jpeg 71 | 72 | # An image asset can refer to one or more resolution-specific "variants", see 73 | # https://flutter.dev/assets-and-images/#resolution-aware. 74 | 75 | # For details regarding adding assets from package dependencies, see 76 | # https://flutter.dev/assets-and-images/#from-packages 77 | 78 | # To add custom fonts to your application, add a fonts section here, 79 | # in this "flutter" section. Each entry in this list should have a 80 | # "family" key with the font family name, and a "fonts" key with a 81 | # list giving the asset and other descriptors for the font. For 82 | # example: 83 | # fonts: 84 | # - family: Schyler 85 | # fonts: 86 | # - asset: fonts/Schyler-Regular.ttf 87 | # - asset: fonts/Schyler-Italic.ttf 88 | # style: italic 89 | # - family: Trajan Pro 90 | # fonts: 91 | # - asset: fonts/TrajanPro.ttf 92 | # - asset: fonts/TrajanPro_Bold.ttf 93 | # weight: 700 94 | # 95 | # For details regarding fonts from package dependencies, 96 | # see https://flutter.dev/custom-fonts/#from-packages 97 | -------------------------------------------------------------------------------- /runServer.bat: -------------------------------------------------------------------------------- 1 | flutter run -d web-server --web-port 8080 -------------------------------------------------------------------------------- /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_firebase_video_call_webrtc/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(const MyApp()); 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 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikymaione/FlutterFirebaseVideoCallWebRTC/adf9bcef1ac2db78a6ab10b150df648611378e3d/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | flutter_firebase_video_call_webrtc 33 | 34 | 35 | 36 | 39 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutter_firebase_video_call_webrtc", 3 | "short_name": "flutter_firebase_video_call_webrtc", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "a Flutter Video Call Demo using WebRTC, Coturn and Firebase", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | --------------------------------------------------------------------------------