├── .github
├── stale.yml
└── workflows
│ ├── deploy-web.yaml
│ └── flutter.yml
├── .gitignore
├── LICENSE
├── README.md
├── android
├── app
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── cloudwebrtc
│ │ │ └── flutterwebrtcdemo
│ │ │ └── MainActivity.java
│ │ └── res
│ │ ├── drawable
│ │ └── launch_background.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ └── values
│ │ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── flutter_webrtc_demo.iml
├── flutter_webrtc_demo_android.iml
├── ios
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ └── contents.xcworkspacedata
└── Runner
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-1024x1024@1x.png
│ │ ├── Icon-App-20x20@1x.png
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@1x.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ └── Icon-App-83.5x83.5@2x.png
│ └── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage.png
│ │ ├── LaunchImage@2x.png
│ │ ├── LaunchImage@3x.png
│ │ └── README.md
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ └── main.m
├── lib
├── main.dart
└── src
│ ├── call_sample
│ ├── call_sample.dart
│ ├── data_channel_sample.dart
│ ├── random_string.dart
│ ├── settings.dart
│ └── signaling.dart
│ ├── route_item.dart
│ ├── utils
│ ├── device_info.dart
│ ├── device_info_web.dart
│ ├── screen_select_dialog.dart
│ ├── turn.dart
│ ├── turn_web.dart
│ ├── websocket.dart
│ └── websocket_web.dart
│ └── widgets
│ └── screen_select_dialog.dart
├── macos
├── .gitignore
├── Flutter
│ ├── Flutter-Debug.xcconfig
│ ├── Flutter-Release.xcconfig
│ └── GeneratedPluginRegistrant.swift
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── app_icon_1024.png
│ │ ├── app_icon_128.png
│ │ ├── app_icon_16.png
│ │ ├── app_icon_256.png
│ │ ├── app_icon_32.png
│ │ ├── app_icon_512.png
│ │ └── app_icon_64.png
│ ├── Base.lproj
│ └── MainMenu.xib
│ ├── Configs
│ ├── AppInfo.xcconfig
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── Warnings.xcconfig
│ ├── DebugProfile.entitlements
│ ├── Info.plist
│ ├── MainFlutterWindow.swift
│ └── Release.entitlements
├── pubspec.lock
├── pubspec.yaml
├── renovate.json
├── screenshots
├── android-01.png
├── android-02.png
├── flutter-webrtc-android-example.png
├── flutter-webrtc-desktop-01.png
├── flutter-webrtc-desktop-02.png
├── flutter-webrtc-desktop-03.png
├── flutter-webrtc-ios-example.png
├── ios-01.jpeg
└── ios-02.jpeg
├── test
└── widget_test.dart
├── web
└── index.html
└── windows
├── .gitignore
├── CMakeLists.txt
├── flutter
├── CMakeLists.txt
├── generated_plugin_registrant.cc
├── generated_plugin_registrant.h
└── generated_plugins.cmake
└── runner
├── CMakeLists.txt
├── Runner.rc
├── flutter_window.cpp
├── flutter_window.h
├── main.cpp
├── resource.h
├── resources
└── app_icon.ico
├── run_loop.cpp
├── run_loop.h
├── runner.exe.manifest
├── utils.cpp
├── utils.h
├── win32_window.cpp
└── win32_window.h
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pinned
8 | - security
9 | # Label to use when marking an issue as stale
10 | staleLabel: wontfix
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. It will be closed if no further activity occurs. Thank you
15 | for your contributions.
16 | # Comment to post when closing a stale issue. Set to `false` to disable
17 | closeComment: false
18 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-web.yaml:
--------------------------------------------------------------------------------
1 | name: deploy web on github-page
2 | on:
3 | push:
4 | branches:
5 | - master
6 | jobs:
7 | build:
8 | name: Build Web
9 | env:
10 | my_secret: ${{secrets.commit_secret}}
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v1
14 | - uses: subosito/flutter-action@v1
15 | with:
16 | channel: "stable"
17 | - run: flutter config --enable-web
18 | - run: flutter clean
19 | - run: flutter pub get
20 | - run: flutter build web --release --web-renderer html --base-href /flutter-webrtc-demo/
21 | - run: |
22 | cd build/web
23 | git init
24 | git config --global user.email duanweiwei1982@gmail.com
25 | git config --global user.name cloudwebrtc
26 | git status
27 | git remote add origin https://${{secrets.commit_secret}}@github.com/flutter-webrtc/flutter-webrtc-demo.git
28 | git checkout -b gh-pages
29 | git add --all
30 | git commit -m "update"
31 | git push origin gh-pages -f
32 |
--------------------------------------------------------------------------------
/.github/workflows/flutter.yml:
--------------------------------------------------------------------------------
1 | name: Flutter CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - dev
7 | - master
8 | - action_test
9 |
10 | jobs:
11 | test:
12 | name: Test on ${{ matrix.os }}
13 | runs-on: ${{ matrix.os }}
14 | strategy:
15 | matrix:
16 | os: [ubuntu-latest]
17 | # os: [ubuntu-latest, windows-latest, macos-latest]
18 | steps:
19 | - uses: actions/checkout@v2
20 | - uses: actions/setup-java@v1
21 | with:
22 | java-version: '12.x'
23 | - uses: subosito/flutter-action@v1
24 | with:
25 | flutter-version: '3.3.2'
26 | channel: 'stable'
27 | - run: flutter packages get
28 | - run: flutter test
29 | - run: flutter build apk --target-platform android-arm,android-arm64 --split-per-abi
30 |
31 | # - run: curl
32 |
--------------------------------------------------------------------------------
/.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 | # Visual Studio Code related
19 | .vscode/
20 |
21 | # Flutter/Dart/Pub related
22 | **/doc/api/
23 | .dart_tool/
24 | .flutter-plugins
25 | .packages
26 | .pub-cache/
27 | .pub/
28 | /build/
29 |
30 | # Android related
31 | **/android/**/gradle-wrapper.jar
32 | **/android/.gradle
33 | **/android/captures/
34 | **/android/gradlew
35 | **/android/gradlew.bat
36 | **/android/local.properties
37 | **/android/**/GeneratedPluginRegistrant.java
38 |
39 | # iOS/XCode related
40 | **/ios/**/*.mode1v3
41 | **/ios/**/*.mode2v3
42 | **/ios/**/*.moved-aside
43 | **/ios/**/*.pbxuser
44 | **/ios/**/*.perspectivev3
45 | **/ios/**/*sync/
46 | **/ios/**/.sconsign.dblite
47 | **/ios/**/.tags*
48 | **/ios/**/.vagrant/
49 | **/ios/**/DerivedData/
50 | **/ios/**/Icon?
51 | **/ios/**/Pods/
52 | **/ios/**/.symlinks/
53 | **/ios/**/profile
54 | **/ios/**/xcuserdata
55 | **/ios/.generated/
56 | **/ios/Flutter/App.framework
57 | **/ios/Flutter/Flutter.framework
58 | **/ios/Flutter/Generated.xcconfig
59 | **/ios/Flutter/app.flx
60 | **/ios/Flutter/app.zip
61 | **/ios/Flutter/flutter_assets/
62 | **/ios/ServiceDefinitions.json
63 | **/ios/Runner/GeneratedPluginRegistrant.*
64 |
65 | # Exceptions to above rules.
66 | !**/ios/**/default.mode1v3
67 | !**/ios/**/default.mode2v3
68 | !**/ios/**/default.pbxuser
69 | !**/ios/**/default.perspectivev3
70 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
71 | .flutter-plugins-dependencies
72 |
73 | ios/Flutter/flutter_export_environment.sh
74 | lib/generated_plugin_registrant.dart
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 湖北捷智云技术有限公司
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # flutter-webrtc-demo
2 | [](https://join.slack.com/t/flutterwebrtc/shared_invite/zt-q83o7y1s-FExGLWEvtkPKM8ku_F8cEQ)
3 |
4 | Flutter WebRTC plugin Demo
5 |
6 | Online Demo: https://flutter-webrtc.github.io/flutter-webrtc-demo/
7 |
8 | ## Usage
9 | - `git clone https://github.com/cloudwebrtc/flutter-webrtc-demo`
10 | - `cd flutter-webrtc-demo`
11 | - `flutter packages get`
12 | - `flutter run`
13 | ## Note
14 | - If you want to test `P2P Call Sample`, please use the [webrtc-flutter-server](https://github.com/cloudwebrtc/flutter-webrtc-server), and enter your server address into the example app.
15 |
16 | ## screenshots
17 | # iOS
18 |
19 | # Android
20 |
21 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | id "dev.flutter.flutter-gradle-plugin"
5 | }
6 |
7 | def localProperties = new Properties()
8 | def localPropertiesFile = rootProject.file('local.properties')
9 | if (localPropertiesFile.exists()) {
10 | localPropertiesFile.withReader('UTF-8') { reader ->
11 | localProperties.load(reader)
12 | }
13 | }
14 |
15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
16 | if (flutterVersionCode == null) {
17 | flutterVersionCode = '1'
18 | }
19 |
20 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
21 | if (flutterVersionName == null) {
22 | flutterVersionName = '1.0'
23 | }
24 |
25 | android {
26 | namespace "com.cloudwebrtc.flutterwebrtcdemo"
27 | compileSdkVersion flutter.compileSdkVersion
28 | ndkVersion flutter.ndkVersion
29 |
30 |
31 | lintOptions {
32 | disable 'InvalidPackage'
33 | }
34 |
35 | defaultConfig {
36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
37 | applicationId "com.cloudwebrtc.flutterwebrtcdemo"
38 | minSdkVersion 23
39 | targetSdkVersion flutter.targetSdkVersion
40 | versionCode flutterVersionCode.toInteger()
41 | versionName flutterVersionName
42 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
43 | }
44 |
45 | compileOptions {
46 | sourceCompatibility JavaVersion.VERSION_1_8
47 | targetCompatibility JavaVersion.VERSION_1_8
48 | }
49 |
50 | buildTypes {
51 | release {
52 | // TODO: Add your own signing config for the release build.
53 | // Signing with the debug keys for now, so `flutter run --release` works.
54 | signingConfig signingConfigs.debug
55 | }
56 | }
57 | packagingOptions {
58 | exclude 'META-INF/proguard/androidx-annotations.pro'
59 | }
60 | }
61 |
62 | flutter {
63 | source '../..'
64 | }
65 |
66 | dependencies {}
67 |
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | ## Flutter WebRTC
2 | -keep class com.cloudwebrtc.webrtc.** { *; }
3 | -keep class org.webrtc.** { *; }
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
24 |
27 |
30 |
38 |
42 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/cloudwebrtc/flutterwebrtcdemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.cloudwebrtc.flutterwebrtcdemo;
2 |
3 | import io.flutter.embedding.android.FlutterActivity;
4 |
5 | public class MainActivity extends FlutterActivity {
6 | }
7 |
--------------------------------------------------------------------------------
/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/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | allprojects {
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 | }
8 |
9 | rootProject.buildDir = '../build'
10 | subprojects {
11 | project.buildDir = "${rootProject.buildDir}/${project.name}"
12 | }
13 | subprojects {
14 | project.evaluationDependsOn(':app')
15 | }
16 |
17 | tasks.register("clean", Delete) {
18 | delete rootProject.buildDir
19 | }
20 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Jul 05 17:31:51 EDT 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
7 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 |
2 | pluginManagement {
3 | def flutterSdkPath = {
4 | def properties = new Properties()
5 | file("local.properties").withInputStream { properties.load(it) }
6 | def flutterSdkPath = properties.getProperty("flutter.sdk")
7 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
8 | return flutterSdkPath
9 | }()
10 |
11 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
12 |
13 | repositories {
14 | google()
15 | mavenCentral()
16 | gradlePluginPortal()
17 | }
18 | }
19 |
20 | plugins {
21 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
22 | id "com.android.application" version '8.1.1' apply false
23 | id "org.jetbrains.kotlin.android" version "2.0.0" apply false
24 | }
25 |
26 | include ":app"
27 |
28 |
--------------------------------------------------------------------------------
/flutter_webrtc_demo.iml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/flutter_webrtc_demo_android.iml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | platform :ios, '10.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
32 | end
33 |
34 | post_install do |installer|
35 | installer.pods_project.targets.each do |target|
36 | flutter_additional_ios_build_settings(target)
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; };
12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
13 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
14 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
15 | 8EF76F2A20E6D1B6DE7482C7 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 15BC1E059AB8D58D6F31A853 /* libPods-Runner.a */; };
16 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
17 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
18 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
19 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
20 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
21 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
22 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
23 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
24 | /* End PBXBuildFile section */
25 |
26 | /* Begin PBXCopyFilesBuildPhase section */
27 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
28 | isa = PBXCopyFilesBuildPhase;
29 | buildActionMask = 2147483647;
30 | dstPath = "";
31 | dstSubfolderSpec = 10;
32 | files = (
33 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
34 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
35 | );
36 | name = "Embed Frameworks";
37 | runOnlyForDeploymentPostprocessing = 0;
38 | };
39 | /* End PBXCopyFilesBuildPhase section */
40 |
41 | /* Begin PBXFileReference section */
42 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
43 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
44 | 15BC1E059AB8D58D6F31A853 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
45 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; };
46 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
47 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; };
48 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
49 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
50 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
51 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
52 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
53 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; };
54 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
55 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
56 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
57 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
58 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
59 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
60 | /* End PBXFileReference section */
61 |
62 | /* Begin PBXFrameworksBuildPhase section */
63 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
64 | isa = PBXFrameworksBuildPhase;
65 | buildActionMask = 2147483647;
66 | files = (
67 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
68 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
69 | 8EF76F2A20E6D1B6DE7482C7 /* libPods-Runner.a in Frameworks */,
70 | );
71 | runOnlyForDeploymentPostprocessing = 0;
72 | };
73 | /* End PBXFrameworksBuildPhase section */
74 |
75 | /* Begin PBXGroup section */
76 | 7FCCAE0EF0328EC3D4877350 /* Frameworks */ = {
77 | isa = PBXGroup;
78 | children = (
79 | 15BC1E059AB8D58D6F31A853 /* libPods-Runner.a */,
80 | );
81 | name = Frameworks;
82 | sourceTree = "";
83 | };
84 | 9740EEB11CF90186004384FC /* Flutter */ = {
85 | isa = PBXGroup;
86 | children = (
87 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */,
88 | 3B80C3931E831B6300D905FE /* App.framework */,
89 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
90 | 9740EEBA1CF902C7004384FC /* Flutter.framework */,
91 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
92 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
93 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
94 | );
95 | name = Flutter;
96 | sourceTree = "";
97 | };
98 | 97C146E51CF9000F007C117D = {
99 | isa = PBXGroup;
100 | children = (
101 | 9740EEB11CF90186004384FC /* Flutter */,
102 | 97C146F01CF9000F007C117D /* Runner */,
103 | 97C146EF1CF9000F007C117D /* Products */,
104 | D19CFC9EBE768E9F44363DDE /* Pods */,
105 | 7FCCAE0EF0328EC3D4877350 /* Frameworks */,
106 | );
107 | sourceTree = "";
108 | };
109 | 97C146EF1CF9000F007C117D /* Products */ = {
110 | isa = PBXGroup;
111 | children = (
112 | 97C146EE1CF9000F007C117D /* Runner.app */,
113 | );
114 | name = Products;
115 | sourceTree = "";
116 | };
117 | 97C146F01CF9000F007C117D /* Runner */ = {
118 | isa = PBXGroup;
119 | children = (
120 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
121 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
122 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
123 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
124 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
125 | 97C147021CF9000F007C117D /* Info.plist */,
126 | 97C146F11CF9000F007C117D /* Supporting Files */,
127 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
128 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
129 | );
130 | path = Runner;
131 | sourceTree = "";
132 | };
133 | 97C146F11CF9000F007C117D /* Supporting Files */ = {
134 | isa = PBXGroup;
135 | children = (
136 | 97C146F21CF9000F007C117D /* main.m */,
137 | );
138 | name = "Supporting Files";
139 | sourceTree = "";
140 | };
141 | D19CFC9EBE768E9F44363DDE /* Pods */ = {
142 | isa = PBXGroup;
143 | children = (
144 | );
145 | name = Pods;
146 | sourceTree = "";
147 | };
148 | /* End PBXGroup section */
149 |
150 | /* Begin PBXNativeTarget section */
151 | 97C146ED1CF9000F007C117D /* Runner */ = {
152 | isa = PBXNativeTarget;
153 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
154 | buildPhases = (
155 | 79DB657BBAA4C66FD2F9F610 /* [CP] Check Pods Manifest.lock */,
156 | 9740EEB61CF901F6004384FC /* Run Script */,
157 | 97C146EA1CF9000F007C117D /* Sources */,
158 | 97C146EB1CF9000F007C117D /* Frameworks */,
159 | 97C146EC1CF9000F007C117D /* Resources */,
160 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
161 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
162 | 5BC80851079CF347B338D4EE /* [CP] Embed Pods Frameworks */,
163 | );
164 | buildRules = (
165 | );
166 | dependencies = (
167 | );
168 | name = Runner;
169 | productName = Runner;
170 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
171 | productType = "com.apple.product-type.application";
172 | };
173 | /* End PBXNativeTarget section */
174 |
175 | /* Begin PBXProject section */
176 | 97C146E61CF9000F007C117D /* Project object */ = {
177 | isa = PBXProject;
178 | attributes = {
179 | LastUpgradeCheck = 0910;
180 | ORGANIZATIONNAME = "The Chromium Authors";
181 | TargetAttributes = {
182 | 97C146ED1CF9000F007C117D = {
183 | CreatedOnToolsVersion = 7.3.1;
184 | };
185 | };
186 | };
187 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
188 | compatibilityVersion = "Xcode 3.2";
189 | developmentRegion = English;
190 | hasScannedForEncodings = 0;
191 | knownRegions = (
192 | en,
193 | Base,
194 | );
195 | mainGroup = 97C146E51CF9000F007C117D;
196 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
197 | projectDirPath = "";
198 | projectRoot = "";
199 | targets = (
200 | 97C146ED1CF9000F007C117D /* Runner */,
201 | );
202 | };
203 | /* End PBXProject section */
204 |
205 | /* Begin PBXResourcesBuildPhase section */
206 | 97C146EC1CF9000F007C117D /* Resources */ = {
207 | isa = PBXResourcesBuildPhase;
208 | buildActionMask = 2147483647;
209 | files = (
210 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
211 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
212 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
213 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
214 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */,
215 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
216 | );
217 | runOnlyForDeploymentPostprocessing = 0;
218 | };
219 | /* End PBXResourcesBuildPhase section */
220 |
221 | /* Begin PBXShellScriptBuildPhase section */
222 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
223 | isa = PBXShellScriptBuildPhase;
224 | buildActionMask = 2147483647;
225 | files = (
226 | );
227 | inputPaths = (
228 | );
229 | name = "Thin Binary";
230 | outputPaths = (
231 | );
232 | runOnlyForDeploymentPostprocessing = 0;
233 | shellPath = /bin/sh;
234 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
235 | };
236 | 5BC80851079CF347B338D4EE /* [CP] Embed Pods Frameworks */ = {
237 | isa = PBXShellScriptBuildPhase;
238 | buildActionMask = 2147483647;
239 | files = (
240 | );
241 | inputPaths = (
242 | "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
243 | "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework",
244 | "${PODS_ROOT}/GoogleWebRTC/Frameworks/frameworks/WebRTC.framework",
245 | );
246 | name = "[CP] Embed Pods Frameworks";
247 | outputPaths = (
248 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
249 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework",
250 | );
251 | runOnlyForDeploymentPostprocessing = 0;
252 | shellPath = /bin/sh;
253 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
254 | showEnvVarsInLog = 0;
255 | };
256 | 79DB657BBAA4C66FD2F9F610 /* [CP] Check Pods Manifest.lock */ = {
257 | isa = PBXShellScriptBuildPhase;
258 | buildActionMask = 2147483647;
259 | files = (
260 | );
261 | inputPaths = (
262 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
263 | "${PODS_ROOT}/Manifest.lock",
264 | );
265 | name = "[CP] Check Pods Manifest.lock";
266 | outputPaths = (
267 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
268 | );
269 | runOnlyForDeploymentPostprocessing = 0;
270 | shellPath = /bin/sh;
271 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
272 | showEnvVarsInLog = 0;
273 | };
274 | 9740EEB61CF901F6004384FC /* Run Script */ = {
275 | isa = PBXShellScriptBuildPhase;
276 | buildActionMask = 2147483647;
277 | files = (
278 | );
279 | inputPaths = (
280 | );
281 | name = "Run Script";
282 | outputPaths = (
283 | );
284 | runOnlyForDeploymentPostprocessing = 0;
285 | shellPath = /bin/sh;
286 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
287 | };
288 | /* End PBXShellScriptBuildPhase section */
289 |
290 | /* Begin PBXSourcesBuildPhase section */
291 | 97C146EA1CF9000F007C117D /* Sources */ = {
292 | isa = PBXSourcesBuildPhase;
293 | buildActionMask = 2147483647;
294 | files = (
295 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
296 | 97C146F31CF9000F007C117D /* main.m in Sources */,
297 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
298 | );
299 | runOnlyForDeploymentPostprocessing = 0;
300 | };
301 | /* End PBXSourcesBuildPhase section */
302 |
303 | /* Begin PBXVariantGroup section */
304 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
305 | isa = PBXVariantGroup;
306 | children = (
307 | 97C146FB1CF9000F007C117D /* Base */,
308 | );
309 | name = Main.storyboard;
310 | sourceTree = "";
311 | };
312 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
313 | isa = PBXVariantGroup;
314 | children = (
315 | 97C147001CF9000F007C117D /* Base */,
316 | );
317 | name = LaunchScreen.storyboard;
318 | sourceTree = "";
319 | };
320 | /* End PBXVariantGroup section */
321 |
322 | /* Begin XCBuildConfiguration section */
323 | 97C147031CF9000F007C117D /* Debug */ = {
324 | isa = XCBuildConfiguration;
325 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
326 | buildSettings = {
327 | ALWAYS_SEARCH_USER_PATHS = NO;
328 | CLANG_ANALYZER_NONNULL = YES;
329 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
330 | CLANG_CXX_LIBRARY = "libc++";
331 | CLANG_ENABLE_MODULES = YES;
332 | CLANG_ENABLE_OBJC_ARC = YES;
333 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
334 | CLANG_WARN_BOOL_CONVERSION = YES;
335 | CLANG_WARN_COMMA = YES;
336 | CLANG_WARN_CONSTANT_CONVERSION = YES;
337 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
338 | CLANG_WARN_EMPTY_BODY = YES;
339 | CLANG_WARN_ENUM_CONVERSION = YES;
340 | CLANG_WARN_INFINITE_RECURSION = YES;
341 | CLANG_WARN_INT_CONVERSION = YES;
342 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
343 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
344 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
345 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
346 | CLANG_WARN_STRICT_PROTOTYPES = YES;
347 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
348 | CLANG_WARN_UNREACHABLE_CODE = YES;
349 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
350 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
351 | COPY_PHASE_STRIP = NO;
352 | DEBUG_INFORMATION_FORMAT = dwarf;
353 | ENABLE_STRICT_OBJC_MSGSEND = YES;
354 | ENABLE_TESTABILITY = YES;
355 | GCC_C_LANGUAGE_STANDARD = gnu99;
356 | GCC_DYNAMIC_NO_PIC = NO;
357 | GCC_NO_COMMON_BLOCKS = YES;
358 | GCC_OPTIMIZATION_LEVEL = 0;
359 | GCC_PREPROCESSOR_DEFINITIONS = (
360 | "DEBUG=1",
361 | "$(inherited)",
362 | );
363 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
364 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
365 | GCC_WARN_UNDECLARED_SELECTOR = YES;
366 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
367 | GCC_WARN_UNUSED_FUNCTION = YES;
368 | GCC_WARN_UNUSED_VARIABLE = YES;
369 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
370 | MTL_ENABLE_DEBUG_INFO = YES;
371 | ONLY_ACTIVE_ARCH = YES;
372 | SDKROOT = iphoneos;
373 | TARGETED_DEVICE_FAMILY = "1,2";
374 | };
375 | name = Debug;
376 | };
377 | 97C147041CF9000F007C117D /* Release */ = {
378 | isa = XCBuildConfiguration;
379 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
380 | buildSettings = {
381 | ALWAYS_SEARCH_USER_PATHS = NO;
382 | CLANG_ANALYZER_NONNULL = YES;
383 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
384 | CLANG_CXX_LIBRARY = "libc++";
385 | CLANG_ENABLE_MODULES = YES;
386 | CLANG_ENABLE_OBJC_ARC = YES;
387 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
388 | CLANG_WARN_BOOL_CONVERSION = YES;
389 | CLANG_WARN_COMMA = YES;
390 | CLANG_WARN_CONSTANT_CONVERSION = YES;
391 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
392 | CLANG_WARN_EMPTY_BODY = YES;
393 | CLANG_WARN_ENUM_CONVERSION = YES;
394 | CLANG_WARN_INFINITE_RECURSION = YES;
395 | CLANG_WARN_INT_CONVERSION = YES;
396 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
397 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
398 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
399 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
400 | CLANG_WARN_STRICT_PROTOTYPES = YES;
401 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
402 | CLANG_WARN_UNREACHABLE_CODE = YES;
403 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
404 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
405 | COPY_PHASE_STRIP = NO;
406 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
407 | ENABLE_NS_ASSERTIONS = NO;
408 | ENABLE_STRICT_OBJC_MSGSEND = YES;
409 | GCC_C_LANGUAGE_STANDARD = gnu99;
410 | GCC_NO_COMMON_BLOCKS = YES;
411 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
412 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
413 | GCC_WARN_UNDECLARED_SELECTOR = YES;
414 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
415 | GCC_WARN_UNUSED_FUNCTION = YES;
416 | GCC_WARN_UNUSED_VARIABLE = YES;
417 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
418 | MTL_ENABLE_DEBUG_INFO = NO;
419 | SDKROOT = iphoneos;
420 | TARGETED_DEVICE_FAMILY = "1,2";
421 | VALIDATE_PRODUCT = YES;
422 | };
423 | name = Release;
424 | };
425 | 97C147061CF9000F007C117D /* Debug */ = {
426 | isa = XCBuildConfiguration;
427 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
428 | buildSettings = {
429 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
430 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
431 | ENABLE_BITCODE = NO;
432 | FRAMEWORK_SEARCH_PATHS = (
433 | "$(inherited)",
434 | "$(PROJECT_DIR)/Flutter",
435 | );
436 | INFOPLIST_FILE = Runner/Info.plist;
437 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
438 | LIBRARY_SEARCH_PATHS = (
439 | "$(inherited)",
440 | "$(PROJECT_DIR)/Flutter",
441 | );
442 | PRODUCT_BUNDLE_IDENTIFIER = com.cloudwebrtc.flutterWebrtcDemo;
443 | PRODUCT_NAME = "$(TARGET_NAME)";
444 | VERSIONING_SYSTEM = "apple-generic";
445 | };
446 | name = Debug;
447 | };
448 | 97C147071CF9000F007C117D /* Release */ = {
449 | isa = XCBuildConfiguration;
450 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
451 | buildSettings = {
452 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
453 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
454 | ENABLE_BITCODE = NO;
455 | FRAMEWORK_SEARCH_PATHS = (
456 | "$(inherited)",
457 | "$(PROJECT_DIR)/Flutter",
458 | );
459 | INFOPLIST_FILE = Runner/Info.plist;
460 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
461 | LIBRARY_SEARCH_PATHS = (
462 | "$(inherited)",
463 | "$(PROJECT_DIR)/Flutter",
464 | );
465 | PRODUCT_BUNDLE_IDENTIFIER = com.cloudwebrtc.flutterWebrtcDemo;
466 | PRODUCT_NAME = "$(TARGET_NAME)";
467 | VERSIONING_SYSTEM = "apple-generic";
468 | };
469 | name = Release;
470 | };
471 | /* End XCBuildConfiguration section */
472 |
473 | /* Begin XCConfigurationList section */
474 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
475 | isa = XCConfigurationList;
476 | buildConfigurations = (
477 | 97C147031CF9000F007C117D /* Debug */,
478 | 97C147041CF9000F007C117D /* Release */,
479 | );
480 | defaultConfigurationIsVisible = 0;
481 | defaultConfigurationName = Release;
482 | };
483 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
484 | isa = XCConfigurationList;
485 | buildConfigurations = (
486 | 97C147061CF9000F007C117D /* Debug */,
487 | 97C147071CF9000F007C117D /* Release */,
488 | );
489 | defaultConfigurationIsVisible = 0;
490 | defaultConfigurationName = Release;
491 | };
492 | /* End XCConfigurationList section */
493 | };
494 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
495 | }
496 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
56 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
77 |
83 |
84 |
85 |
86 |
88 |
89 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : FlutterAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #include "AppDelegate.h"
2 | #include "GeneratedPluginRegistrant.h"
3 |
4 | @implementation AppDelegate
5 |
6 | - (BOOL)application:(UIApplication *)application
7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
8 | [GeneratedPluginRegistrant registerWithRegistry:self];
9 | // Override point for customization after application launch.
10 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
11 | }
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/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/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/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/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/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/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/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/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/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/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/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/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/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/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/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/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/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/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/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/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/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/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/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/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/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/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/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/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/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/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flutter-webrtc/flutter-webrtc-demo/7f3b2e5c46eac73b26fdc36cc4d5c06140a6e31a/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 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | flutter_webrtc_demo
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | NSCameraUsageDescription
26 | $(PRODUCT_NAME) Camera Usage!
27 | NSMicrophoneUsageDescription
28 | $(PRODUCT_NAME) Microphone Usage!
29 | NSPhotoLibraryUsageDescription
30 | $(PRODUCT_NAME) PhotoLibrary Usage!
31 | UIBackgroundModes
32 |
33 | fetch
34 | remote-notification
35 |
36 | UILaunchStoryboardName
37 | LaunchScreen
38 | UIMainStoryboardFile
39 | Main
40 | UISupportedInterfaceOrientations
41 |
42 | UIInterfaceOrientationPortrait
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 | UISupportedInterfaceOrientations~ipad
47 |
48 | UIInterfaceOrientationPortrait
49 | UIInterfaceOrientationPortraitUpsideDown
50 | UIInterfaceOrientationLandscapeLeft
51 | UIInterfaceOrientationLandscapeRight
52 |
53 | UIViewControllerBasedStatusBarAppearance
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/ios/Runner/main.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char* argv[]) {
6 | @autoreleasepool {
7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:core';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:shared_preferences/shared_preferences.dart';
5 |
6 | import 'src/call_sample/call_sample.dart';
7 | import 'src/call_sample/data_channel_sample.dart';
8 | import 'src/route_item.dart';
9 |
10 | void main() => runApp(new MyApp());
11 |
12 | class MyApp extends StatefulWidget {
13 | @override
14 | _MyAppState createState() => new _MyAppState();
15 | }
16 |
17 | enum DialogDemoAction {
18 | cancel,
19 | connect,
20 | }
21 |
22 | class _MyAppState extends State {
23 | List items = [];
24 | String _server = '';
25 | late SharedPreferences _prefs;
26 |
27 | bool _datachannel = false;
28 | @override
29 | initState() {
30 | super.initState();
31 | _initData();
32 | _initItems();
33 | }
34 |
35 | _buildRow(context, item) {
36 | return ListBody(children: [
37 | ListTile(
38 | title: Text(item.title),
39 | onTap: () => item.push(context),
40 | trailing: Icon(Icons.arrow_right),
41 | ),
42 | Divider()
43 | ]);
44 | }
45 |
46 | @override
47 | Widget build(BuildContext context) {
48 | return MaterialApp(
49 | home: Scaffold(
50 | appBar: AppBar(
51 | title: Text('Flutter-WebRTC example'),
52 | ),
53 | body: ListView.builder(
54 | shrinkWrap: true,
55 | padding: const EdgeInsets.all(0.0),
56 | itemCount: items.length,
57 | itemBuilder: (context, i) {
58 | return _buildRow(context, items[i]);
59 | })),
60 | );
61 | }
62 |
63 | _initData() async {
64 | _prefs = await SharedPreferences.getInstance();
65 | setState(() {
66 | _server = _prefs.getString('server') ?? 'demo.cloudwebrtc.com';
67 | });
68 | }
69 |
70 | void showDemoDialog(
71 | {required BuildContext context, required Widget child}) {
72 | showDialog(
73 | context: context,
74 | builder: (BuildContext context) => child,
75 | ).then((T? value) {
76 | // The value passed to Navigator.pop() or null.
77 | if (value != null) {
78 | if (value == DialogDemoAction.connect) {
79 | _prefs.setString('server', _server);
80 | Navigator.push(
81 | context,
82 | MaterialPageRoute(
83 | builder: (BuildContext context) => _datachannel
84 | ? DataChannelSample(host: _server)
85 | : CallSample(host: _server)));
86 | }
87 | }
88 | });
89 | }
90 |
91 | _showAddressDialog(context) {
92 | showDemoDialog(
93 | context: context,
94 | child: AlertDialog(
95 | title: const Text('Enter server address:'),
96 | content: TextField(
97 | onChanged: (String text) {
98 | setState(() {
99 | _server = text;
100 | });
101 | },
102 | decoration: InputDecoration(
103 | hintText: _server,
104 | ),
105 | textAlign: TextAlign.center,
106 | ),
107 | actions: [
108 | TextButton(
109 | child: const Text('CANCEL'),
110 | onPressed: () {
111 | Navigator.pop(context, DialogDemoAction.cancel);
112 | }),
113 | TextButton(
114 | child: const Text('CONNECT'),
115 | onPressed: () {
116 | Navigator.pop(context, DialogDemoAction.connect);
117 | })
118 | ]));
119 | }
120 |
121 | _initItems() {
122 | items = [
123 | RouteItem(
124 | title: 'P2P Call Sample',
125 | subtitle: 'P2P Call Sample.',
126 | push: (BuildContext context) {
127 | _datachannel = false;
128 | _showAddressDialog(context);
129 | }),
130 | RouteItem(
131 | title: 'Data Channel Sample',
132 | subtitle: 'P2P Data Channel.',
133 | push: (BuildContext context) {
134 | _datachannel = true;
135 | _showAddressDialog(context);
136 | }),
137 | ];
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/lib/src/call_sample/call_sample.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'dart:core';
3 | import '../widgets/screen_select_dialog.dart';
4 | import 'signaling.dart';
5 | import 'package:flutter_webrtc/flutter_webrtc.dart';
6 |
7 | class CallSample extends StatefulWidget {
8 | static String tag = 'call_sample';
9 | final String host;
10 | CallSample({required this.host});
11 |
12 | @override
13 | _CallSampleState createState() => _CallSampleState();
14 | }
15 |
16 | class _CallSampleState extends State {
17 | Signaling? _signaling;
18 | List _peers = [];
19 | String? _selfId;
20 | RTCVideoRenderer _localRenderer = RTCVideoRenderer();
21 | RTCVideoRenderer _remoteRenderer = RTCVideoRenderer();
22 | bool _inCalling = false;
23 | Session? _session;
24 | DesktopCapturerSource? selected_source_;
25 | bool _waitAccept = false;
26 |
27 | // ignore: unused_element
28 | _CallSampleState();
29 |
30 | @override
31 | initState() {
32 | super.initState();
33 | initRenderers();
34 | _connect(context);
35 | }
36 |
37 | initRenderers() async {
38 | await _localRenderer.initialize();
39 | await _remoteRenderer.initialize();
40 | }
41 |
42 | @override
43 | deactivate() {
44 | super.deactivate();
45 | _signaling?.close();
46 | _localRenderer.dispose();
47 | _remoteRenderer.dispose();
48 | }
49 |
50 | void _connect(BuildContext context) async {
51 | _signaling ??= Signaling(widget.host, context)..connect();
52 | _signaling?.onSignalingStateChange = (SignalingState state) {
53 | switch (state) {
54 | case SignalingState.ConnectionClosed:
55 | case SignalingState.ConnectionError:
56 | case SignalingState.ConnectionOpen:
57 | break;
58 | }
59 | };
60 |
61 | _signaling?.onCallStateChange = (Session session, CallState state) async {
62 | switch (state) {
63 | case CallState.CallStateNew:
64 | setState(() {
65 | _session = session;
66 | });
67 | break;
68 | case CallState.CallStateRinging:
69 | bool? accept = await _showAcceptDialog();
70 | if (accept!) {
71 | _accept();
72 | setState(() {
73 | _inCalling = true;
74 | });
75 | } else {
76 | _reject();
77 | }
78 | break;
79 | case CallState.CallStateBye:
80 | if (_waitAccept) {
81 | print('peer reject');
82 | _waitAccept = false;
83 | Navigator.of(context).pop(false);
84 | }
85 | setState(() {
86 | _localRenderer.srcObject = null;
87 | _remoteRenderer.srcObject = null;
88 | _inCalling = false;
89 | _session = null;
90 | });
91 | break;
92 | case CallState.CallStateInvite:
93 | _waitAccept = true;
94 | _showInvateDialog();
95 | break;
96 | case CallState.CallStateConnected:
97 | if (_waitAccept) {
98 | _waitAccept = false;
99 | Navigator.of(context).pop(false);
100 | }
101 | setState(() {
102 | _inCalling = true;
103 | });
104 |
105 | break;
106 | case CallState.CallStateRinging:
107 | }
108 | };
109 |
110 | _signaling?.onPeersUpdate = ((event) {
111 | setState(() {
112 | _selfId = event['self'];
113 | _peers = event['peers'];
114 | });
115 | });
116 |
117 | _signaling?.onLocalStream = ((stream) {
118 | _localRenderer.srcObject = stream;
119 | setState(() {});
120 | });
121 |
122 | _signaling?.onAddRemoteStream = ((_, stream) {
123 | _remoteRenderer.srcObject = stream;
124 | setState(() {});
125 | });
126 |
127 | _signaling?.onRemoveRemoteStream = ((_, stream) {
128 | _remoteRenderer.srcObject = null;
129 | });
130 | }
131 |
132 | Future _showAcceptDialog() {
133 | return showDialog(
134 | context: context,
135 | builder: (context) {
136 | return AlertDialog(
137 | title: Text("title"),
138 | content: Text("accept?"),
139 | actions: [
140 | MaterialButton(
141 | child: Text(
142 | 'Reject',
143 | style: TextStyle(color: Colors.red),
144 | ),
145 | onPressed: () => Navigator.of(context).pop(false),
146 | ),
147 | MaterialButton(
148 | child: Text(
149 | 'Accept',
150 | style: TextStyle(color: Colors.green),
151 | ),
152 | onPressed: () => Navigator.of(context).pop(true),
153 | ),
154 | ],
155 | );
156 | },
157 | );
158 | }
159 |
160 | Future _showInvateDialog() {
161 | return showDialog(
162 | context: context,
163 | builder: (context) {
164 | return AlertDialog(
165 | title: Text("title"),
166 | content: Text("waiting"),
167 | actions: [
168 | TextButton(
169 | child: Text("cancel"),
170 | onPressed: () {
171 | Navigator.of(context).pop(false);
172 | _hangUp();
173 | },
174 | ),
175 | ],
176 | );
177 | },
178 | );
179 | }
180 |
181 | _invitePeer(BuildContext context, String peerId, bool useScreen) async {
182 | if (_signaling != null && peerId != _selfId) {
183 | _signaling?.invite(peerId, 'video', useScreen);
184 | }
185 | }
186 |
187 | _accept() {
188 | if (_session != null) {
189 | _signaling?.accept(_session!.sid, 'video');
190 | }
191 | }
192 |
193 | _reject() {
194 | if (_session != null) {
195 | _signaling?.reject(_session!.sid);
196 | }
197 | }
198 |
199 | _hangUp() {
200 | if (_session != null) {
201 | _signaling?.bye(_session!.sid);
202 | }
203 | }
204 |
205 | _switchCamera() {
206 | _signaling?.switchCamera();
207 | }
208 |
209 | Future selectScreenSourceDialog(BuildContext context) async {
210 | MediaStream? screenStream;
211 | if (WebRTC.platformIsDesktop) {
212 | final source = await showDialog(
213 | context: context,
214 | builder: (context) => ScreenSelectDialog(),
215 | );
216 | if (source != null) {
217 | try {
218 | var stream =
219 | await navigator.mediaDevices.getDisplayMedia({
220 | 'video': {
221 | 'deviceId': {'exact': source.id},
222 | 'mandatory': {'frameRate': 30.0}
223 | }
224 | });
225 | stream.getVideoTracks()[0].onEnded = () {
226 | print(
227 | 'By adding a listener on onEnded you can: 1) catch stop video sharing on Web');
228 | };
229 | screenStream = stream;
230 | } catch (e) {
231 | print(e);
232 | }
233 | }
234 | } else if (WebRTC.platformIsWeb) {
235 | screenStream =
236 | await navigator.mediaDevices.getDisplayMedia({
237 | 'audio': false,
238 | 'video': true,
239 | });
240 | }
241 | if (screenStream != null) _signaling?.switchToScreenSharing(screenStream);
242 | }
243 |
244 | _muteMic() {
245 | _signaling?.muteMic();
246 | }
247 |
248 | _buildRow(context, peer) {
249 | var self = (peer['id'] == _selfId);
250 | return ListBody(children: [
251 | ListTile(
252 | title: Text(self
253 | ? peer['name'] + ', ID: ${peer['id']} ' + ' [Your self]'
254 | : peer['name'] + ', ID: ${peer['id']} '),
255 | onTap: null,
256 | trailing: SizedBox(
257 | width: 100.0,
258 | child: Row(
259 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
260 | children: [
261 | IconButton(
262 | icon: Icon(self ? Icons.close : Icons.videocam,
263 | color: self ? Colors.grey : Colors.black),
264 | onPressed: () => _invitePeer(context, peer['id'], false),
265 | tooltip: 'Video calling',
266 | ),
267 | IconButton(
268 | icon: Icon(self ? Icons.close : Icons.screen_share,
269 | color: self ? Colors.grey : Colors.black),
270 | onPressed: () => _invitePeer(context, peer['id'], true),
271 | tooltip: 'Screen sharing',
272 | )
273 | ])),
274 | subtitle: Text('[' + peer['user_agent'] + ']'),
275 | ),
276 | Divider()
277 | ]);
278 | }
279 |
280 | @override
281 | Widget build(BuildContext context) {
282 | return Scaffold(
283 | appBar: AppBar(
284 | title: Text('P2P Call Sample' +
285 | (_selfId != null ? ' [Your ID ($_selfId)] ' : '')),
286 | actions: [
287 | IconButton(
288 | icon: const Icon(Icons.settings),
289 | onPressed: null,
290 | tooltip: 'setup',
291 | ),
292 | ],
293 | ),
294 | floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
295 | floatingActionButton: _inCalling
296 | ? SizedBox(
297 | width: 240.0,
298 | child: Row(
299 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
300 | children: [
301 | FloatingActionButton(
302 | child: const Icon(Icons.switch_camera),
303 | tooltip: 'Camera',
304 | onPressed: _switchCamera,
305 | ),
306 | FloatingActionButton(
307 | child: const Icon(Icons.desktop_mac),
308 | tooltip: 'Screen Sharing',
309 | onPressed: () => selectScreenSourceDialog(context),
310 | ),
311 | FloatingActionButton(
312 | onPressed: _hangUp,
313 | tooltip: 'Hangup',
314 | child: Icon(Icons.call_end),
315 | backgroundColor: Colors.pink,
316 | ),
317 | FloatingActionButton(
318 | child: const Icon(Icons.mic_off),
319 | tooltip: 'Mute Mic',
320 | onPressed: _muteMic,
321 | )
322 | ]))
323 | : null,
324 | body: _inCalling
325 | ? OrientationBuilder(builder: (context, orientation) {
326 | return Container(
327 | child: Stack(children: [
328 | Positioned(
329 | left: 0.0,
330 | right: 0.0,
331 | top: 0.0,
332 | bottom: 0.0,
333 | child: Container(
334 | margin: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0),
335 | width: MediaQuery.of(context).size.width,
336 | height: MediaQuery.of(context).size.height,
337 | child: RTCVideoView(_remoteRenderer),
338 | decoration: BoxDecoration(color: Colors.black54),
339 | )),
340 | Positioned(
341 | left: 20.0,
342 | top: 20.0,
343 | child: Container(
344 | width: orientation == Orientation.portrait ? 90.0 : 120.0,
345 | height:
346 | orientation == Orientation.portrait ? 120.0 : 90.0,
347 | child: RTCVideoView(_localRenderer, mirror: true),
348 | decoration: BoxDecoration(color: Colors.black54),
349 | ),
350 | ),
351 | ]),
352 | );
353 | })
354 | : ListView.builder(
355 | shrinkWrap: true,
356 | padding: const EdgeInsets.all(0.0),
357 | itemCount: (_peers != null ? _peers.length : 0),
358 | itemBuilder: (context, i) {
359 | return _buildRow(context, _peers[i]);
360 | }),
361 | );
362 | }
363 | }
364 |
--------------------------------------------------------------------------------
/lib/src/call_sample/data_channel_sample.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'dart:core';
3 | import 'dart:async';
4 | import 'dart:typed_data';
5 | import 'signaling.dart';
6 | import 'package:flutter_webrtc/flutter_webrtc.dart';
7 |
8 | class DataChannelSample extends StatefulWidget {
9 | static String tag = 'call_sample';
10 | final String host;
11 | DataChannelSample({required this.host});
12 |
13 | @override
14 | _DataChannelSampleState createState() => _DataChannelSampleState();
15 | }
16 |
17 | class _DataChannelSampleState extends State {
18 | Signaling? _signaling;
19 | List _peers = [];
20 | String? _selfId;
21 | bool _inCalling = false;
22 | RTCDataChannel? _dataChannel;
23 | Session? _session;
24 | Timer? _timer;
25 | var _text = '';
26 | // ignore: unused_element
27 | _DataChannelSampleState();
28 | bool _waitAccept = false;
29 |
30 | @override
31 | initState() {
32 | super.initState();
33 | _connect(context);
34 | }
35 |
36 | @override
37 | deactivate() {
38 | super.deactivate();
39 | _signaling?.close();
40 | _timer?.cancel();
41 | }
42 |
43 | Future _showAcceptDialog() {
44 | return showDialog(
45 | context: context,
46 | builder: (context) {
47 | return AlertDialog(
48 | title: Text("title"),
49 | content: Text("accept?"),
50 | actions: [
51 | MaterialButton(
52 | child: Text(
53 | 'Reject',
54 | style: TextStyle(color: Colors.red),
55 | ),
56 | onPressed: () => Navigator.of(context).pop(false),
57 | ),
58 | MaterialButton(
59 | child: Text(
60 | 'Accept',
61 | style: TextStyle(color: Colors.green),
62 | ),
63 | onPressed: () => Navigator.of(context).pop(true),
64 | ),
65 | ],
66 | );
67 | },
68 | );
69 | }
70 |
71 | Future _showInvateDialog() {
72 | return showDialog(
73 | context: context,
74 | builder: (context) {
75 | return AlertDialog(
76 | title: Text("title"),
77 | content: Text("waiting"),
78 | actions: [
79 | TextButton(
80 | child: Text("cancel"),
81 | onPressed: () {
82 | Navigator.of(context).pop(false);
83 | _hangUp();
84 | },
85 | ),
86 | ],
87 | );
88 | },
89 | );
90 | }
91 |
92 | void _connect(BuildContext context) async {
93 | _signaling ??= Signaling(widget.host, context)..connect();
94 |
95 | _signaling?.onDataChannelMessage = (_, dc, RTCDataChannelMessage data) {
96 | setState(() {
97 | if (data.isBinary) {
98 | print('Got binary [' + data.binary.toString() + ']');
99 | } else {
100 | _text = data.text;
101 | }
102 | });
103 | };
104 |
105 | _signaling?.onDataChannel = (_, channel) {
106 | _dataChannel = channel;
107 | };
108 |
109 | _signaling?.onSignalingStateChange = (SignalingState state) {
110 | switch (state) {
111 | case SignalingState.ConnectionClosed:
112 | case SignalingState.ConnectionError:
113 | case SignalingState.ConnectionOpen:
114 | break;
115 | }
116 | };
117 |
118 | _signaling?.onCallStateChange = (Session session, CallState state) async {
119 | switch (state) {
120 | case CallState.CallStateNew:
121 | setState(() {
122 | _session = session;
123 | });
124 | _timer = Timer.periodic(Duration(seconds: 1), _handleDataChannelTest);
125 | break;
126 | case CallState.CallStateBye:
127 | if (_waitAccept) {
128 | print('peer reject');
129 | _waitAccept = false;
130 | Navigator.of(context).pop(false);
131 | }
132 | setState(() {
133 | _inCalling = false;
134 | });
135 | _timer?.cancel();
136 | _dataChannel = null;
137 | _inCalling = false;
138 | _session = null;
139 | _text = '';
140 | break;
141 | case CallState.CallStateInvite:
142 | _waitAccept = true;
143 | _showInvateDialog();
144 | break;
145 | case CallState.CallStateConnected:
146 | if (_waitAccept) {
147 | _waitAccept = false;
148 | Navigator.of(context).pop(false);
149 | }
150 | setState(() {
151 | _inCalling = true;
152 | });
153 | break;
154 | case CallState.CallStateRinging:
155 | bool? accept = await _showAcceptDialog();
156 | if (accept!) {
157 | _accept();
158 | setState(() {
159 | _inCalling = true;
160 | });
161 | } else {
162 | _reject();
163 | }
164 |
165 | break;
166 | }
167 | };
168 |
169 | _signaling?.onPeersUpdate = ((event) {
170 | setState(() {
171 | _selfId = event['self'];
172 | _peers = event['peers'];
173 | });
174 | });
175 | }
176 |
177 | _handleDataChannelTest(Timer timer) async {
178 | String text =
179 | 'Say hello ' + timer.tick.toString() + ' times, from [$_selfId]';
180 | _dataChannel
181 | ?.send(RTCDataChannelMessage.fromBinary(Uint8List(timer.tick + 1)));
182 | _dataChannel?.send(RTCDataChannelMessage(text));
183 | }
184 |
185 | _invitePeer(context, peerId) async {
186 | if (peerId != _selfId) {
187 | _signaling?.invite(peerId, 'data', false);
188 | }
189 | }
190 |
191 | _accept() {
192 | if (_session != null) {
193 | _signaling?.accept(_session!.sid, 'data');
194 | }
195 | }
196 |
197 | _reject() {
198 | if (_session != null) {
199 | _signaling?.reject(_session!.sid);
200 | }
201 | }
202 |
203 | _hangUp() {
204 | _signaling?.bye(_session!.sid);
205 | }
206 |
207 | _buildRow(context, peer) {
208 | var self = (peer['id'] == _selfId);
209 | return ListBody(children: [
210 | ListTile(
211 | title: Text(self
212 | ? peer['name'] + ', ID: ${peer['id']} ' + ' [Your self]'
213 | : peer['name'] + ', ID: ${peer['id']} '),
214 | onTap: () => _invitePeer(context, peer['id']),
215 | trailing: Icon(Icons.sms),
216 | subtitle: Text('[' + peer['user_agent'] + ']'),
217 | ),
218 | Divider()
219 | ]);
220 | }
221 |
222 | @override
223 | Widget build(BuildContext context) {
224 | return Scaffold(
225 | appBar: AppBar(
226 | title: Text('Data Channel Sample' +
227 | (_selfId != null ? ' [Your ID ($_selfId)] ' : '')),
228 | actions: [
229 | IconButton(
230 | icon: const Icon(Icons.settings),
231 | onPressed: null,
232 | tooltip: 'setup',
233 | ),
234 | ],
235 | ),
236 | floatingActionButton: _inCalling
237 | ? FloatingActionButton(
238 | onPressed: _hangUp,
239 | tooltip: 'Hangup',
240 | child: Icon(Icons.call_end),
241 | )
242 | : null,
243 | body: _inCalling
244 | ? Center(
245 | child: Container(
246 | child: Text('Recevied => ' + _text),
247 | ),
248 | )
249 | : ListView.builder(
250 | shrinkWrap: true,
251 | padding: const EdgeInsets.all(0.0),
252 | itemCount: (_peers != null ? _peers.length : 0),
253 | itemBuilder: (context, i) {
254 | return _buildRow(context, _peers[i]);
255 | }),
256 | );
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/lib/src/call_sample/random_string.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016, Damon Douglas. All rights reserved. Use of this source code
2 | // is governed by a BSD-style license that can be found in the LICENSE file.
3 |
4 | /// Simple library for generating random ascii strings.
5 | ///
6 | /// More dartdocs go here.
7 | ///
8 | ///
9 | /// A simple usage example:
10 | ///
11 | /// import 'package:random_string/random_string.dart' as random;
12 | /// main() {
13 | /// print(randomBetween(10,20)); // some integer between 10 and 20
14 | /// print(randomNumeric(4)); // sequence of 4 random numbers i.e. 3259
15 | /// print(randomString(10)); // random sequence of 10 characters i.e. e~f93(4l-
16 | /// print(randomAlpha(5)); // random sequence of 5 alpha characters i.e. aRztC
17 | /// print(randomAlphaNumeric(10)); // random sequence of 10 alpha numeric i.e. aRztC1y32B
18 | /// }
19 |
20 | library random_string;
21 |
22 | import 'dart:math';
23 |
24 | const ASCII_START = 33;
25 | const ASCII_END = 126;
26 | const NUMERIC_START = 48;
27 | const NUMERIC_END = 57;
28 | const LOWER_ALPHA_START = 97;
29 | const LOWER_ALPHA_END = 122;
30 | const UPPER_ALPHA_START = 65;
31 | const UPPER_ALPHA_END = 90;
32 |
33 | /// Generates a random integer where [from] <= [to].
34 | int randomBetween(int from, int to) {
35 | if (from > to) throw Exception('$from cannot be > $to');
36 | var rand = Random();
37 | return ((to - from) * rand.nextDouble()).toInt() + from;
38 | }
39 |
40 | /// Generates a random string of [length] with characters
41 | /// between ascii [from] to [to].
42 | /// Defaults to characters of ascii '!' to '~'.
43 | String randomString(int length, {int from: ASCII_START, int to: ASCII_END}) {
44 | return String.fromCharCodes(
45 | List.generate(length, (index) => randomBetween(from, to)));
46 | }
47 |
48 | /// Generates a random string of [length] with only numeric characters.
49 | String randomNumeric(int length) =>
50 | randomString(length, from: NUMERIC_START, to: NUMERIC_END);
51 | /*
52 | /// Generates a random string of [length] with only alpha characters.
53 | String randomAlpha(int length) {
54 | var lowerAlphaLength = randomBetween(0, length);
55 | var upperAlphaLength = length - lowerAlphaLength;
56 | var lowerAlpha = randomString(lowerAlphaLength,
57 | from: LOWER_ALPHA_START, to: LOWER_ALPHA_END);
58 | var upperAlpha = randomString(upperAlphaLength,
59 | from: UPPER_ALPHA_START, to: UPPER_ALPHA_END);
60 | return randomMerge(lowerAlpha, upperAlpha);
61 | }
62 |
63 | /// Generates a random string of [length] with alpha-numeric characters.
64 | String randomAlphaNumeric(int length) {
65 | var alphaLength = randomBetween(0, length);
66 | var numericLength = length - alphaLength;
67 | var alpha = randomAlpha(alphaLength);
68 | var numeric = randomNumeric(numericLength);
69 | return randomMerge(alpha, numeric);
70 | }
71 |
72 | /// Merge [a] with [b] and scramble characters.
73 | String randomMerge(String a, String b) {
74 | var mergedCodeUnits = List.from("$a$b".codeUnits);
75 | mergedCodeUnits.shuffle();
76 | return String.fromCharCodes(mergedCodeUnits);
77 | }*/
78 |
--------------------------------------------------------------------------------
/lib/src/call_sample/settings.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'dart:core';
3 |
4 | class CallSettings extends StatefulWidget {
5 | static String tag = 'call_settings';
6 |
7 | @override
8 | _CallSettingsState createState() => _CallSettingsState();
9 | }
10 |
11 | class _CallSettingsState extends State {
12 | @override
13 | initState() {
14 | super.initState();
15 | }
16 |
17 | @override
18 | deactivate() {
19 | super.deactivate();
20 | }
21 |
22 | @override
23 | Widget build(BuildContext context) {
24 | return Scaffold(
25 | appBar: AppBar(
26 | title: Text('Settings'),
27 | ),
28 | body: OrientationBuilder(
29 | builder: (context, orientation) {
30 | return Center(child: Text("settings"));
31 | },
32 | ),
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/src/call_sample/signaling.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:async';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_webrtc/flutter_webrtc.dart';
5 |
6 | import '../utils/screen_select_dialog.dart';
7 | import 'random_string.dart';
8 |
9 | import '../utils/device_info.dart'
10 | if (dart.library.js) '../utils/device_info_web.dart';
11 | import '../utils/websocket.dart'
12 | if (dart.library.js) '../utils/websocket_web.dart';
13 | import '../utils/turn.dart' if (dart.library.js) '../utils/turn_web.dart';
14 |
15 | enum SignalingState {
16 | ConnectionOpen,
17 | ConnectionClosed,
18 | ConnectionError,
19 | }
20 |
21 | enum CallState {
22 | CallStateNew,
23 | CallStateRinging,
24 | CallStateInvite,
25 | CallStateConnected,
26 | CallStateBye,
27 | }
28 |
29 | enum VideoSource {
30 | Camera,
31 | Screen,
32 | }
33 |
34 | class Session {
35 | Session({required this.sid, required this.pid});
36 | String pid;
37 | String sid;
38 | RTCPeerConnection? pc;
39 | RTCDataChannel? dc;
40 | List remoteCandidates = [];
41 | }
42 |
43 | class Signaling {
44 | Signaling(this._host, this._context);
45 |
46 | JsonEncoder _encoder = JsonEncoder();
47 | JsonDecoder _decoder = JsonDecoder();
48 | String _selfId = randomNumeric(6);
49 | SimpleWebSocket? _socket;
50 | BuildContext? _context;
51 | var _host;
52 | var _port = 8086;
53 | var _turnCredential;
54 | Map _sessions = {};
55 | MediaStream? _localStream;
56 | List _remoteStreams = [];
57 | List _senders = [];
58 | VideoSource _videoSource = VideoSource.Camera;
59 |
60 | Function(SignalingState state)? onSignalingStateChange;
61 | Function(Session session, CallState state)? onCallStateChange;
62 | Function(MediaStream stream)? onLocalStream;
63 | Function(Session session, MediaStream stream)? onAddRemoteStream;
64 | Function(Session session, MediaStream stream)? onRemoveRemoteStream;
65 | Function(dynamic event)? onPeersUpdate;
66 | Function(Session session, RTCDataChannel dc, RTCDataChannelMessage data)?
67 | onDataChannelMessage;
68 | Function(Session session, RTCDataChannel dc)? onDataChannel;
69 |
70 | String get sdpSemantics => 'unified-plan';
71 |
72 | Map _iceServers = {
73 | 'iceServers': [
74 | {'url': 'stun:stun.l.google.com:19302'},
75 | /*
76 | * turn server configuration example.
77 | {
78 | 'url': 'turn:123.45.67.89:3478',
79 | 'username': 'change_to_real_user',
80 | 'credential': 'change_to_real_secret'
81 | },
82 | */
83 | ]
84 | };
85 |
86 | final Map _config = {
87 | 'mandatory': {},
88 | 'optional': [
89 | {'DtlsSrtpKeyAgreement': true},
90 | ]
91 | };
92 |
93 | final Map _dcConstraints = {
94 | 'mandatory': {
95 | 'OfferToReceiveAudio': false,
96 | 'OfferToReceiveVideo': false,
97 | },
98 | 'optional': [],
99 | };
100 |
101 | close() async {
102 | await _cleanSessions();
103 | _socket?.close();
104 | }
105 |
106 | void switchCamera() {
107 | if (_localStream != null) {
108 | if (_videoSource != VideoSource.Camera) {
109 | _senders.forEach((sender) {
110 | if (sender.track!.kind == 'video') {
111 | sender.replaceTrack(_localStream!.getVideoTracks()[0]);
112 | }
113 | });
114 | _videoSource = VideoSource.Camera;
115 | onLocalStream?.call(_localStream!);
116 | } else {
117 | Helper.switchCamera(_localStream!.getVideoTracks()[0]);
118 | }
119 | }
120 | }
121 |
122 | void switchToScreenSharing(MediaStream stream) {
123 | if (_localStream != null && _videoSource != VideoSource.Screen) {
124 | _senders.forEach((sender) {
125 | if (sender.track!.kind == 'video') {
126 | sender.replaceTrack(stream.getVideoTracks()[0]);
127 | }
128 | });
129 | onLocalStream?.call(stream);
130 | _videoSource = VideoSource.Screen;
131 | }
132 | }
133 |
134 | void muteMic() {
135 | if (_localStream != null) {
136 | bool enabled = _localStream!.getAudioTracks()[0].enabled;
137 | _localStream!.getAudioTracks()[0].enabled = !enabled;
138 | }
139 | }
140 |
141 | void invite(String peerId, String media, bool useScreen) async {
142 | var sessionId = _selfId + '-' + peerId;
143 | Session session = await _createSession(null,
144 | peerId: peerId,
145 | sessionId: sessionId,
146 | media: media,
147 | screenSharing: useScreen);
148 | _sessions[sessionId] = session;
149 | if (media == 'data') {
150 | _createDataChannel(session);
151 | }
152 | _createOffer(session, media);
153 | onCallStateChange?.call(session, CallState.CallStateNew);
154 | onCallStateChange?.call(session, CallState.CallStateInvite);
155 | }
156 |
157 | void bye(String sessionId) {
158 | _send('bye', {
159 | 'session_id': sessionId,
160 | 'from': _selfId,
161 | });
162 | var sess = _sessions[sessionId];
163 | if (sess != null) {
164 | _closeSession(sess);
165 | }
166 | }
167 |
168 | void accept(String sessionId, String media) {
169 | var session = _sessions[sessionId];
170 | if (session == null) {
171 | return;
172 | }
173 | _createAnswer(session, media);
174 | }
175 |
176 | void reject(String sessionId) {
177 | var session = _sessions[sessionId];
178 | if (session == null) {
179 | return;
180 | }
181 | bye(session.sid);
182 | }
183 |
184 | void onMessage(message) async {
185 | Map mapData = message;
186 | var data = mapData['data'];
187 |
188 | switch (mapData['type']) {
189 | case 'peers':
190 | {
191 | List peers = data;
192 | if (onPeersUpdate != null) {
193 | Map event = Map();
194 | event['self'] = _selfId;
195 | event['peers'] = peers;
196 | onPeersUpdate?.call(event);
197 | }
198 | }
199 | break;
200 | case 'offer':
201 | {
202 | var peerId = data['from'];
203 | var description = data['description'];
204 | var media = data['media'];
205 | var sessionId = data['session_id'];
206 | var session = _sessions[sessionId];
207 | var newSession = await _createSession(session,
208 | peerId: peerId,
209 | sessionId: sessionId,
210 | media: media,
211 | screenSharing: false);
212 | _sessions[sessionId] = newSession;
213 | await newSession.pc?.setRemoteDescription(
214 | RTCSessionDescription(description['sdp'], description['type']));
215 | // await _createAnswer(newSession, media);
216 |
217 | if (newSession.remoteCandidates.length > 0) {
218 | newSession.remoteCandidates.forEach((candidate) async {
219 | await newSession.pc?.addCandidate(candidate);
220 | });
221 | newSession.remoteCandidates.clear();
222 | }
223 | onCallStateChange?.call(newSession, CallState.CallStateNew);
224 | onCallStateChange?.call(newSession, CallState.CallStateRinging);
225 | }
226 | break;
227 | case 'answer':
228 | {
229 | var description = data['description'];
230 | var sessionId = data['session_id'];
231 | var session = _sessions[sessionId];
232 | session?.pc?.setRemoteDescription(
233 | RTCSessionDescription(description['sdp'], description['type']));
234 | onCallStateChange?.call(session!, CallState.CallStateConnected);
235 | }
236 | break;
237 | case 'candidate':
238 | {
239 | var peerId = data['from'];
240 | var candidateMap = data['candidate'];
241 | var sessionId = data['session_id'];
242 | var session = _sessions[sessionId];
243 | RTCIceCandidate candidate = RTCIceCandidate(candidateMap['candidate'],
244 | candidateMap['sdpMid'], candidateMap['sdpMLineIndex']);
245 |
246 | if (session != null) {
247 | if (session.pc != null) {
248 | await session.pc?.addCandidate(candidate);
249 | } else {
250 | session.remoteCandidates.add(candidate);
251 | }
252 | } else {
253 | _sessions[sessionId] = Session(pid: peerId, sid: sessionId)
254 | ..remoteCandidates.add(candidate);
255 | }
256 | }
257 | break;
258 | case 'leave':
259 | {
260 | var peerId = data as String;
261 | _closeSessionByPeerId(peerId);
262 | }
263 | break;
264 | case 'bye':
265 | {
266 | var sessionId = data['session_id'];
267 | print('bye: ' + sessionId);
268 | var session = _sessions.remove(sessionId);
269 | if (session != null) {
270 | onCallStateChange?.call(session, CallState.CallStateBye);
271 | _closeSession(session);
272 | }
273 | }
274 | break;
275 | case 'keepalive':
276 | {
277 | print('keepalive response!');
278 | }
279 | break;
280 | default:
281 | break;
282 | }
283 | }
284 |
285 | Future connect() async {
286 | var url = 'https://$_host:$_port/ws';
287 | _socket = SimpleWebSocket(url);
288 |
289 | print('connect to $url');
290 |
291 | if (_turnCredential == null) {
292 | try {
293 | _turnCredential = await getTurnCredential(_host, _port);
294 | /*{
295 | "username": "1584195784:mbzrxpgjys",
296 | "password": "isyl6FF6nqMTB9/ig5MrMRUXqZg",
297 | "ttl": 86400,
298 | "uris": ["turn:127.0.0.1:19302?transport=udp"]
299 | }
300 | */
301 | _iceServers = {
302 | 'iceServers': [
303 | {
304 | 'urls': _turnCredential['uris'][0],
305 | 'username': _turnCredential['username'],
306 | 'credential': _turnCredential['password']
307 | },
308 | ]
309 | };
310 | } catch (e) {}
311 | }
312 |
313 | _socket?.onOpen = () {
314 | print('onOpen');
315 | onSignalingStateChange?.call(SignalingState.ConnectionOpen);
316 | _send('new', {
317 | 'name': DeviceInfo.label,
318 | 'id': _selfId,
319 | 'user_agent': DeviceInfo.userAgent
320 | });
321 | };
322 |
323 | _socket?.onMessage = (message) {
324 | print('Received data: ' + message);
325 | onMessage(_decoder.convert(message));
326 | };
327 |
328 | _socket?.onClose = (int? code, String? reason) {
329 | print('Closed by server [$code => $reason]!');
330 | onSignalingStateChange?.call(SignalingState.ConnectionClosed);
331 | };
332 |
333 | await _socket?.connect();
334 | }
335 |
336 | Future createStream(String media, bool userScreen,
337 | {BuildContext? context}) async {
338 | final Map mediaConstraints = {
339 | 'audio': userScreen ? false : true,
340 | 'video': userScreen
341 | ? true
342 | : {
343 | 'mandatory': {
344 | 'minWidth':
345 | '640', // Provide your own width, height and frame rate here
346 | 'minHeight': '480',
347 | 'minFrameRate': '30',
348 | },
349 | 'facingMode': 'user',
350 | 'optional': [],
351 | }
352 | };
353 | late MediaStream stream;
354 | if (userScreen) {
355 | if (WebRTC.platformIsDesktop) {
356 | final source = await showDialog(
357 | context: context!,
358 | builder: (context) => ScreenSelectDialog(),
359 | );
360 | stream = await navigator.mediaDevices.getDisplayMedia({
361 | 'video': source == null
362 | ? true
363 | : {
364 | 'deviceId': {'exact': source.id},
365 | 'mandatory': {'frameRate': 30.0}
366 | }
367 | });
368 | } else {
369 | stream = await navigator.mediaDevices.getDisplayMedia(mediaConstraints);
370 | }
371 | } else {
372 | stream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
373 | }
374 |
375 | onLocalStream?.call(stream);
376 | return stream;
377 | }
378 |
379 | Future _createSession(
380 | Session? session, {
381 | required String peerId,
382 | required String sessionId,
383 | required String media,
384 | required bool screenSharing,
385 | }) async {
386 | var newSession = session ?? Session(sid: sessionId, pid: peerId);
387 | if (media != 'data')
388 | _localStream =
389 | await createStream(media, screenSharing, context: _context);
390 | print(_iceServers);
391 | RTCPeerConnection pc = await createPeerConnection({
392 | ..._iceServers,
393 | ...{'sdpSemantics': sdpSemantics}
394 | }, _config);
395 | if (media != 'data') {
396 | switch (sdpSemantics) {
397 | case 'plan-b':
398 | pc.onAddStream = (MediaStream stream) {
399 | onAddRemoteStream?.call(newSession, stream);
400 | _remoteStreams.add(stream);
401 | };
402 | await pc.addStream(_localStream!);
403 | break;
404 | case 'unified-plan':
405 | // Unified-Plan
406 | pc.onTrack = (event) {
407 | if (event.track.kind == 'video') {
408 | onAddRemoteStream?.call(newSession, event.streams[0]);
409 | }
410 | };
411 | _localStream!.getTracks().forEach((track) async {
412 | _senders.add(await pc.addTrack(track, _localStream!));
413 | });
414 | break;
415 | }
416 |
417 | // Unified-Plan: Simuclast
418 | /*
419 | await pc.addTransceiver(
420 | track: _localStream.getAudioTracks()[0],
421 | init: RTCRtpTransceiverInit(
422 | direction: TransceiverDirection.SendOnly, streams: [_localStream]),
423 | );
424 |
425 | await pc.addTransceiver(
426 | track: _localStream.getVideoTracks()[0],
427 | init: RTCRtpTransceiverInit(
428 | direction: TransceiverDirection.SendOnly,
429 | streams: [
430 | _localStream
431 | ],
432 | sendEncodings: [
433 | RTCRtpEncoding(rid: 'f', active: true),
434 | RTCRtpEncoding(
435 | rid: 'h',
436 | active: true,
437 | scaleResolutionDownBy: 2.0,
438 | maxBitrate: 150000,
439 | ),
440 | RTCRtpEncoding(
441 | rid: 'q',
442 | active: true,
443 | scaleResolutionDownBy: 4.0,
444 | maxBitrate: 100000,
445 | ),
446 | ]),
447 | );*/
448 | /*
449 | var sender = pc.getSenders().find(s => s.track.kind == "video");
450 | var parameters = sender.getParameters();
451 | if(!parameters)
452 | parameters = {};
453 | parameters.encodings = [
454 | { rid: "h", active: true, maxBitrate: 900000 },
455 | { rid: "m", active: true, maxBitrate: 300000, scaleResolutionDownBy: 2 },
456 | { rid: "l", active: true, maxBitrate: 100000, scaleResolutionDownBy: 4 }
457 | ];
458 | sender.setParameters(parameters);
459 | */
460 | }
461 | pc.onIceCandidate = (candidate) async {
462 | if (candidate == null) {
463 | print('onIceCandidate: complete!');
464 | return;
465 | }
466 | // This delay is needed to allow enough time to try an ICE candidate
467 | // before skipping to the next one. 1 second is just an heuristic value
468 | // and should be thoroughly tested in your own environment.
469 | await Future.delayed(
470 | const Duration(seconds: 1),
471 | () => _send('candidate', {
472 | 'to': peerId,
473 | 'from': _selfId,
474 | 'candidate': {
475 | 'sdpMLineIndex': candidate.sdpMLineIndex,
476 | 'sdpMid': candidate.sdpMid,
477 | 'candidate': candidate.candidate,
478 | },
479 | 'session_id': sessionId,
480 | }));
481 | };
482 |
483 | pc.onIceConnectionState = (state) {};
484 |
485 | pc.onRemoveStream = (stream) {
486 | onRemoveRemoteStream?.call(newSession, stream);
487 | _remoteStreams.removeWhere((it) {
488 | return (it.id == stream.id);
489 | });
490 | };
491 |
492 | pc.onDataChannel = (channel) {
493 | _addDataChannel(newSession, channel);
494 | };
495 |
496 | newSession.pc = pc;
497 | return newSession;
498 | }
499 |
500 | void _addDataChannel(Session session, RTCDataChannel channel) {
501 | channel.onDataChannelState = (e) {};
502 | channel.onMessage = (RTCDataChannelMessage data) {
503 | onDataChannelMessage?.call(session, channel, data);
504 | };
505 | session.dc = channel;
506 | onDataChannel?.call(session, channel);
507 | }
508 |
509 | Future _createDataChannel(Session session,
510 | {label: 'fileTransfer'}) async {
511 | RTCDataChannelInit dataChannelDict = RTCDataChannelInit()
512 | ..maxRetransmits = 30;
513 | RTCDataChannel channel =
514 | await session.pc!.createDataChannel(label, dataChannelDict);
515 | _addDataChannel(session, channel);
516 | }
517 |
518 | Future _createOffer(Session session, String media) async {
519 | try {
520 | RTCSessionDescription s =
521 | await session.pc!.createOffer(media == 'data' ? _dcConstraints : {});
522 | await session.pc!.setLocalDescription(_fixSdp(s));
523 | _send('offer', {
524 | 'to': session.pid,
525 | 'from': _selfId,
526 | 'description': {'sdp': s.sdp, 'type': s.type},
527 | 'session_id': session.sid,
528 | 'media': media,
529 | });
530 | } catch (e) {
531 | print(e.toString());
532 | }
533 | }
534 |
535 | RTCSessionDescription _fixSdp(RTCSessionDescription s) {
536 | var sdp = s.sdp;
537 | s.sdp =
538 | sdp!.replaceAll('profile-level-id=640c1f', 'profile-level-id=42e032');
539 | return s;
540 | }
541 |
542 | Future _createAnswer(Session session, String media) async {
543 | try {
544 | RTCSessionDescription s =
545 | await session.pc!.createAnswer(media == 'data' ? _dcConstraints : {});
546 | await session.pc!.setLocalDescription(_fixSdp(s));
547 | _send('answer', {
548 | 'to': session.pid,
549 | 'from': _selfId,
550 | 'description': {'sdp': s.sdp, 'type': s.type},
551 | 'session_id': session.sid,
552 | });
553 | } catch (e) {
554 | print(e.toString());
555 | }
556 | }
557 |
558 | _send(event, data) {
559 | var request = Map();
560 | request["type"] = event;
561 | request["data"] = data;
562 | _socket?.send(_encoder.convert(request));
563 | }
564 |
565 | Future _cleanSessions() async {
566 | if (_localStream != null) {
567 | _localStream!.getTracks().forEach((element) async {
568 | await element.stop();
569 | });
570 | await _localStream!.dispose();
571 | _localStream = null;
572 | }
573 | _sessions.forEach((key, sess) async {
574 | await sess.pc?.close();
575 | await sess.dc?.close();
576 | });
577 | _sessions.clear();
578 | }
579 |
580 | void _closeSessionByPeerId(String peerId) {
581 | var session;
582 | _sessions.removeWhere((String key, Session sess) {
583 | var ids = key.split('-');
584 | session = sess;
585 | return peerId == ids[0] || peerId == ids[1];
586 | });
587 | if (session != null) {
588 | _closeSession(session);
589 | onCallStateChange?.call(session, CallState.CallStateBye);
590 | }
591 | }
592 |
593 | Future _closeSession(Session session) async {
594 | _localStream?.getTracks().forEach((element) async {
595 | await element.stop();
596 | });
597 | await _localStream?.dispose();
598 | _localStream = null;
599 |
600 | await session.pc?.close();
601 | await session.dc?.close();
602 | _senders.clear();
603 | _videoSource = VideoSource.Camera;
604 | }
605 | }
606 |
--------------------------------------------------------------------------------
/lib/src/route_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'dart:core';
3 |
4 | typedef void RouteCallback(BuildContext context);
5 |
6 | class RouteItem {
7 | RouteItem({
8 | required this.title,
9 | required this.subtitle,
10 | required this.push,
11 | });
12 |
13 | final String title;
14 | final String subtitle;
15 | final RouteCallback push;
16 | }
17 |
--------------------------------------------------------------------------------
/lib/src/utils/device_info.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | class DeviceInfo {
4 | static String get label {
5 | return 'Flutter ' +
6 | Platform.operatingSystem +
7 | '(' +
8 | Platform.localHostname +
9 | ")";
10 | }
11 |
12 | static String get userAgent {
13 | return 'flutter-webrtc/' + Platform.operatingSystem + '-plugin 0.0.1';
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lib/src/utils/device_info_web.dart:
--------------------------------------------------------------------------------
1 | // ignore: avoid_web_libraries_in_flutter
2 | import 'dart:html' as HTML;
3 |
4 | class DeviceInfo {
5 | static String get label {
6 | return 'Flutter Web';
7 | }
8 |
9 | static String get userAgent {
10 | return 'flutter-webrtc/web-plugin 0.0.1 ' +
11 | ' ( ' +
12 | HTML.window.navigator.userAgent +
13 | ' )';
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lib/src/utils/screen_select_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_webrtc/flutter_webrtc.dart';
5 |
6 | class ThumbnailWidget extends StatefulWidget {
7 | const ThumbnailWidget(
8 | {Key? key,
9 | required this.source,
10 | required this.selected,
11 | required this.onTap})
12 | : super(key: key);
13 | final DesktopCapturerSource source;
14 | final bool selected;
15 | final Function(DesktopCapturerSource) onTap;
16 |
17 | @override
18 | _ThumbnailWidgetState createState() => _ThumbnailWidgetState();
19 | }
20 |
21 | class _ThumbnailWidgetState extends State {
22 | final List _subscriptions = [];
23 |
24 | @override
25 | void initState() {
26 | super.initState();
27 | _subscriptions.add(widget.source.onThumbnailChanged.stream.listen((event) {
28 | setState(() {});
29 | }));
30 | _subscriptions.add(widget.source.onNameChanged.stream.listen((event) {
31 | setState(() {});
32 | }));
33 | }
34 |
35 | @override
36 | void deactivate() {
37 | _subscriptions.forEach((element) {
38 | element.cancel();
39 | });
40 | super.deactivate();
41 | }
42 |
43 | @override
44 | Widget build(BuildContext context) {
45 | return Column(
46 | children: [
47 | Expanded(
48 | child: Container(
49 | decoration: widget.selected
50 | ? BoxDecoration(
51 | border: Border.all(width: 2, color: Colors.blueAccent))
52 | : null,
53 | child: InkWell(
54 | onTap: () {
55 | print('Selected source id => ${widget.source.id}');
56 | widget.onTap(widget.source);
57 | },
58 | child: widget.source.thumbnail != null
59 | ? Image.memory(
60 | widget.source.thumbnail!,
61 | gaplessPlayback: true,
62 | alignment: Alignment.center,
63 | )
64 | : Container(),
65 | ),
66 | )),
67 | Text(
68 | widget.source.name,
69 | style: TextStyle(
70 | fontSize: 12,
71 | color: Colors.black87,
72 | fontWeight:
73 | widget.selected ? FontWeight.bold : FontWeight.normal),
74 | ),
75 | ],
76 | );
77 | }
78 | }
79 |
80 | // ignore: must_be_immutable
81 | class ScreenSelectDialog extends Dialog {
82 | ScreenSelectDialog() {
83 | Future.delayed(Duration(milliseconds: 100), () {
84 | _getSources();
85 | });
86 | _subscriptions.add(desktopCapturer.onAdded.stream.listen((source) {
87 | _sources[source.id] = source;
88 | _stateSetter?.call(() {});
89 | }));
90 |
91 | _subscriptions.add(desktopCapturer.onRemoved.stream.listen((source) {
92 | _sources.remove(source.id);
93 | _stateSetter?.call(() {});
94 | }));
95 |
96 | _subscriptions
97 | .add(desktopCapturer.onThumbnailChanged.stream.listen((source) {
98 | _stateSetter?.call(() {});
99 | }));
100 | }
101 | final Map _sources = {};
102 | SourceType _sourceType = SourceType.Screen;
103 | DesktopCapturerSource? _selected_source;
104 | final List> _subscriptions = [];
105 | StateSetter? _stateSetter;
106 | Timer? _timer;
107 |
108 | void _ok(context) async {
109 | _timer?.cancel();
110 | _subscriptions.forEach((element) {
111 | element.cancel();
112 | });
113 | Navigator.pop(context, _selected_source);
114 | }
115 |
116 | void _cancel(context) async {
117 | _timer?.cancel();
118 | _subscriptions.forEach((element) {
119 | element.cancel();
120 | });
121 | Navigator.pop(context, null);
122 | }
123 |
124 | Future _getSources() async {
125 | try {
126 | var sources = await desktopCapturer.getSources(types: [_sourceType]);
127 | sources.forEach((element) {
128 | print(
129 | 'name: ${element.name}, id: ${element.id}, type: ${element.type}');
130 | });
131 | _timer?.cancel();
132 | _timer = Timer.periodic(Duration(seconds: 3), (timer) {
133 | desktopCapturer.updateSources(types: [_sourceType]);
134 | });
135 | _sources.clear();
136 | sources.forEach((element) {
137 | _sources[element.id] = element;
138 | });
139 | _stateSetter?.call(() {});
140 | return;
141 | } catch (e) {
142 | print(e.toString());
143 | }
144 | }
145 |
146 | @override
147 | Widget build(BuildContext context) {
148 | return Material(
149 | type: MaterialType.transparency,
150 | child: Center(
151 | child: Container(
152 | width: 640,
153 | height: 560,
154 | color: Colors.white,
155 | child: Column(
156 | children: [
157 | Padding(
158 | padding: EdgeInsets.all(10),
159 | child: Stack(
160 | children: [
161 | Align(
162 | alignment: Alignment.topLeft,
163 | child: Text(
164 | 'Choose what to share',
165 | style: TextStyle(fontSize: 16, color: Colors.black87),
166 | ),
167 | ),
168 | Align(
169 | alignment: Alignment.topRight,
170 | child: InkWell(
171 | child: Icon(Icons.close),
172 | onTap: () => _cancel(context),
173 | ),
174 | ),
175 | ],
176 | ),
177 | ),
178 | Expanded(
179 | flex: 1,
180 | child: Container(
181 | width: double.infinity,
182 | padding: EdgeInsets.all(10),
183 | child: StatefulBuilder(
184 | builder: (context, setState) {
185 | _stateSetter = setState;
186 | return DefaultTabController(
187 | length: 2,
188 | child: Column(
189 | children: [
190 | Container(
191 | constraints: BoxConstraints.expand(height: 24),
192 | child: TabBar(
193 | onTap: (value) => Future.delayed(
194 | Duration(milliseconds: 300), () {
195 | _sourceType = value == 0
196 | ? SourceType.Screen
197 | : SourceType.Window;
198 | _getSources();
199 | }),
200 | tabs: [
201 | Tab(
202 | child: Text(
203 | 'Entire Screen',
204 | style: TextStyle(color: Colors.black54),
205 | )),
206 | Tab(
207 | child: Text(
208 | 'Window',
209 | style: TextStyle(color: Colors.black54),
210 | )),
211 | ]),
212 | ),
213 | SizedBox(
214 | height: 2,
215 | ),
216 | Expanded(
217 | child: Container(
218 | child: TabBarView(children: [
219 | Align(
220 | alignment: Alignment.center,
221 | child: Container(
222 | child: GridView.count(
223 | crossAxisSpacing: 8,
224 | crossAxisCount: 2,
225 | children: _sources.entries
226 | .where((element) =>
227 | element.value.type ==
228 | SourceType.Screen)
229 | .map((e) => ThumbnailWidget(
230 | onTap: (source) {
231 | setState(() {
232 | _selected_source = source;
233 | });
234 | },
235 | source: e.value,
236 | selected:
237 | _selected_source?.id ==
238 | e.value.id,
239 | ))
240 | .toList(),
241 | ),
242 | )),
243 | Align(
244 | alignment: Alignment.center,
245 | child: Container(
246 | child: GridView.count(
247 | crossAxisSpacing: 8,
248 | crossAxisCount: 3,
249 | children: _sources.entries
250 | .where((element) =>
251 | element.value.type ==
252 | SourceType.Window)
253 | .map((e) => ThumbnailWidget(
254 | onTap: (source) {
255 | setState(() {
256 | _selected_source = source;
257 | });
258 | },
259 | source: e.value,
260 | selected:
261 | _selected_source?.id ==
262 | e.value.id,
263 | ))
264 | .toList(),
265 | ),
266 | )),
267 | ]),
268 | ),
269 | )
270 | ],
271 | ),
272 | );
273 | },
274 | ),
275 | ),
276 | ),
277 | Container(
278 | width: double.infinity,
279 | child: ButtonBar(
280 | children: [
281 | MaterialButton(
282 | child: Text(
283 | 'Cancel',
284 | style: TextStyle(color: Colors.black54),
285 | ),
286 | onPressed: () {
287 | _cancel(context);
288 | },
289 | ),
290 | MaterialButton(
291 | color: Theme.of(context).primaryColor,
292 | child: Text(
293 | 'Share',
294 | ),
295 | onPressed: () {
296 | _ok(context);
297 | },
298 | ),
299 | ],
300 | ),
301 | ),
302 | ],
303 | ),
304 | )),
305 | );
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/lib/src/utils/turn.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:async';
3 | import 'dart:io';
4 |
5 | Future