├── .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