diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index 80ccfeeef9..0000000000
--- a/.gitattributes
+++ /dev/null
@@ -1,4 +0,0 @@
-*.dll filter=lfs diff=lfs merge=lfs -text
-*.so filter=lfs diff=lfs merge=lfs -text
-*.zip filter=lfs diff=lfs merge=lfs -text
-*.aar filter=lfs diff=lfs merge=lfs -text
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 63fe195530..70eb0630e4 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -2,160 +2,30 @@ name: Build
on:
push:
- branches: [ main ]
+ branches: [ master ]
pull_request:
- branches: [ main ]
+ branches: [ master ]
jobs:
- dart-format-and-analyze-check:
- name: Dart Format Check
- runs-on: ubuntu-latest
+ test:
+ name: Test on ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: '12.x'
- - uses: actions/checkout@v2
- - uses: subosito/flutter-action@v2
- with:
- channel: 'stable'
- - name: Install project dependencies
- run: flutter pub get
- - name: Dart Format Check
- run: dart format lib/ test/ --set-exit-if-changed
- - name: Import Sorter Check
- run: flutter pub run import_sorter:main --no-comments --exit-if-changed
- - name: Dart Analyze Check
- run: flutter analyze
- - name: Dart Test Check
- run: flutter test
-
- build-for-android:
- name: Build for Flutter Android
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-java@v1
- with:
- java-version: '17.x'
- - uses: actions/checkout@v2
- - uses: subosito/flutter-action@v2
- with:
- channel: 'stable'
- - name: Install project dependencies
- run: flutter pub get
- - name: Build for Android
- working-directory: ./example
- run: flutter build apk
-
- build-for-ios:
- name: Build for Flutter iOS
- runs-on: macos-latest
-
- steps:
- - uses: actions/checkout@v2
- - uses: subosito/flutter-action@v2
- with:
- channel: 'stable'
- - name: Install project dependencies
- run: flutter pub get
- - name: Build for iOS
- working-directory: ./example
- run: flutter build ios --release --no-codesign
-
- build-for-windows:
- name: Build for flutter Windows
- runs-on: windows-latest
-
- steps:
- - uses: actions/checkout@v2
- uses: subosito/flutter-action@v1
with:
+ flutter-version: '3.0.2'
channel: 'stable'
- - name: Install project dependencies
- run: flutter pub get
- - name: Build for Windows
- working-directory: ./example
- run: flutter build windows --release
-
- build-for-macos:
- name: Build for flutter macOS
- runs-on: macos-latest
-
- steps:
- - uses: actions/checkout@v2
- - uses: subosito/flutter-action@v1
- with:
- channel: 'stable'
- - name: Install project dependencies
- run: flutter pub get
- - name: Build for macOS
- working-directory: ./example
- run: flutter build macos --release
-
- build-for-linux:
- name: Build for Flutter Linux
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-java@v1
- with:
- java-version: '12.x'
- - uses: actions/checkout@v2
- - uses: subosito/flutter-action@v2
- with:
- channel: 'stable'
- - name: Install project dependencies
- run: flutter pub get
- - name: Run apt update
- run: sudo apt-get update
- - name: Install ninja-build libgtk-3-dev
- run: sudo apt-get install -y ninja-build libgtk-3-dev
- - name: Build for Linux
- working-directory: ./example
- run: flutter build linux
-
- build-for-elinux:
- name: Build for Flutter Embedded Linux
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-java@v1
- with:
- java-version: '12.x'
- - uses: actions/checkout@v2
- - uses: subosito/flutter-action@v2
- with:
- channel: 'stable'
- - name: Run apt update
- run: sudo apt-get update
- - name: Install ninja-build libgtk-3-dev
- run: sudo apt-get install -y ninja-build libgtk-3-dev
- - name: Install elinux
- run: git clone https://github.com/sony/flutter-elinux.git -b 3.27.1 ~/flutter-elinux
- - name: Build for elinux
- working-directory: ./example
- run: /home/runner/flutter-elinux/bin/flutter-elinux pub get && /home/runner/flutter-elinux/bin/flutter-elinux build elinux
-
- build-for-web:
- name: Build for Flutter Web
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-java@v1
- with:
- java-version: '12.x'
- - uses: actions/checkout@v2
- - uses: subosito/flutter-action@v2
- with:
- channel: 'stable'
- - name: Install project dependencies
- run: flutter pub get
- - name: build for Web
- working-directory: ./example
- run: flutter build web
+ - run: flutter packages get
+ - run: flutter format lib/ test/ --set-exit-if-changed
+ - run: flutter pub run import_sorter:main --no-comments --exit-if-changed
+ - run: flutter analyze
+ - run: flutter test
+
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
index 417f0f5aa5..5702acbf1b 100644
--- a/.github/workflows/publish.yaml
+++ b/.github/workflows/publish.yaml
@@ -1,15 +1,21 @@
-# .github/workflows/publish.yml
-name: Publish to pub.dev
+name: Publish plugin
on:
- push:
- tags:
- - 'v[0-9]+.[0-9]+.[0-9]+*'
+ release:
+ types: [published]
jobs:
publish:
- permissions:
- id-token: write # Required for authentication using OIDC
- uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1
- # with:
- # working-directory: path/to/package/within/repository
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v1
+ - name: Publish
+ uses: sakebook/actions-flutter-pub-publisher@v1.4.1
+ with:
+ credential: ${{ secrets.CREDENTIAL_JSON }}
+ flutter_package: true
+ skip_test: true
+ dry_run: false
diff --git a/.gitignore b/.gitignore
index 0bab26c22a..d190d4abd0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@ pubspec.lock
example/ios/Podfile.lock
GeneratedPluginRegistrant.java
example/android/.gradle
+example/android/gradle*
WorkspaceSettings.xcsettings
example/.flutter-plugins
example/android/local.properties
@@ -52,7 +53,4 @@ android/.settings/org.eclipse.buildship.core.prefs
.vscode/
!webrtc_android.iml
-!webrtc.iml
-
-# vs
-*.pdb
\ No newline at end of file
+!webrtc.iml
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6120e9fb8c..7be02600da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,434 +1,6 @@
-
# Changelog
-[0.14.1] - 2025-05-22
-
-* [Android] fix: Recording bug (#1839)
-* [Android] fix: calls in terminated mode by disabling orientation manager (#1840)
-* [Android] fix: Wait for audio and video thread to fully stop to avoid corrupted recordings (#1836)
-
-[0.14.0] - 2025-05-06
-
-* [iOS/Android]feat: Media Recorder implementation Android and iOS (#1810)
-* [Wndows] fix: Pickup registrar for plugin by plugin registrar manager (#1752)
-* [Linux] fix: add task runner for linux. (#1821)
-* [iOS/macOS] fix: Fix deadlock when creating a frame cryptor on iOS/macOS.
-
-[0.13.1+hotfix.1] - 2025-04-07
-
-* [Android] fix: Fix `clearAndroidCommunicationDevice` call blocking.
-
-[0.13.1] - 2025-04-03
-
-* [Android] fix: remove setPreferredInputDevice when getUserAduio. (#1808)
-* [Web] fix: race condition in RTCVideoRenderer for Web (#1805)
-* [Android] fix: Migrate from onSurfaceDestroyed to onSurfaceCleanup for SurfaceProducer.Callback. (#1806)
-
-[0.13.0] - 2025-03-24
-
-* [All] feat: add getBufferedAmount for DataChannel. (#1796)
-* [Windows] fix: fixed non-platform thread call error. (#1795)
-
-[0.12.12+hotfix.1] - 2025-03-12
-
-* [Android] fix: fixed video not rendered after resume from background.
-
-[0.12.12] - 2025-03-09
-
-* [Android] feat: Migrate to the new Surface API. (#1726)
-* [Chore] chore: fix sponsors logo and links.
-
-[0.12.11] - 2025-02-23
-
-* [web] bump version for dart_webrtc.
-* [web] fix: compile error for web with --wasm.
-
-[0.12.10] - 2025-02-18
-
-* [web] bump version for dart_webrtc.
-* [web] fix: compile error for web with --wasm.
-
-[0.12.9] - 2025-02-13
-
-* [iOS] feat: Add option to start capture without broadcast picker (#1764)
-
-[0.12.8] - 2025-02-07
-
-* [Dart] feat: expose rtc video value (#1754)
-* [Dart] chore: bump webrtc-interface to 1.2.1.
-
-[0.12.7] - 2025-01-24
-
-* [iOS] More robustness for video renderer. (#1751)
-
-[0.12.6] - 2025-01-20
-
-* [iOS] fix In-app screen recording.
-* [Android] fix: avoid crashes when surfaceTextureHelper is null. (#1743)
-
-[0.12.5+hotfix.2] - 2024-12-25
-
-* [iOS] fix: Audio route issue for iOS.
-
-[0.12.5+hotfix.1] - 2024-12-25
-
-* [iOS/macOS] fix: Pass MediaConstraints for getUserAudio.
-
-[0.12.5] - 2024-12-23
-
-* [iOS/Android] Fixed buf for screen capture.
-* [Android] Fixed first frame flickering.
-
-[0.12.4] - 2024-12-16
-
-* [iOS/Android] add FocusMode/ExposureMode settings for mobile. (#1435)
-* [Dart] fix compiler errors.
-* [eLinux] add $ORIGIN to rpath in elinux (#1720).
-
-[0.12.3] - 2024-11-29
-
-* [iOS/Android/macOS] feat: Expose AV Processing and Sink native APIs.
-
-[0.12.2] - 2024-11-26
-
-* [Dart] fix: race condition during video renderer initialization. (#1692)
-* [Darwin] fix: Add mutex lock to avoid pixelbuffer access contention. (#1694)
-
-[0.12.1+hotfix.1] - 2024-10-25
-
-* [iOS] fix: fix switch camera broken on iOS.
-
-* [web] fix: add stub WebRTC.initialize for web.
-* [Docs] Fixing proguard rules link (#1686)
-* [iOS/Android] feat: support unprocessed audio (#825)
-* [eLinux] feat: add support for eLinux (#1338)
-
-[0.12.0+hotfix.1] - 2024-10-18
-
-* [macOS] fix compilation error for macOS.
-
-[0.12.0] - 2024-10-16
-
-* [iOS/macOS] Fix memory leak for iOS/macOS.
-* [iOS] Support MultiCam Session for iOS.
-
-[0.11.7] - 2024-09-04
-
-* [Web] Bump dart_webrtc to 1.4.9.
-* [Web] Bump web version to 1.0.0.
-
-[0.11.6+hotfix.1] - 2024-08-07
-
-* [iOS] Fixed PlatformView not rendering after resuming from background.
-
-[0.11.6] - 2024-08-02
-
-* [Web] change VideoElement to HTMLVideoElement.
-* [iOS] added shared singleton for FlutterWebRTCPlugin (#1634)
-* [iOS] Using av samplebuffer for PlatformView (#1635)
-
-[0.11.5] - 2024-07-23
-
-* [Android] Report actual sizes for camera media stream track (#1636).
-
-[0.11.4] - 2024-07-19
-
-* [Android] fix issue for camera switching.
-
-[0.11.3] - 2024-07-12
-
-* Bump version for libwebrtc.
-
-[0.11.2] - 2024-07-09
-
-* [Windows] fix crash for windows.
-* [Darwin] bump WebRTC version for darwin.
-
-[0.11.1] - 2024-06-17
-
-* [macOS] Downgrade macOS system dependencies to 10.14.
-
-[0.11.0] - 2024-06-17
-
-* [Native] upgrade libwebrtc to m125.6422.
-
-[0.10.8] - 2024-06-05
-
-* [iOS] fix(platform_view): fit cover works wrong (#1593)
-* [iOS/macOS] fix: Fix the issue that the video is not displayed when using 'video': true (#1592)
-* [Web] bump dart_webrtc to 1.4.6.
-
-[0.10.7] - 2024-05-30
-
-* [iOS] feat: add PlatformView Renderer for iOS. (#1569)
-* [iOS] fix: audio session control for iOS. (#1590)
-
-[0.10.6] - 2024-05-13
-
-* [Web] Some important fixes for web.
-
-[0.10.5] - 2024-05-13
-
-* [Android] fix: make MediaDeviceInfo (Audio deviceId, label, groupId) consistent. (#1583)
-
-[0.10.4] - 2024-05-06
-
-* [iOS/macOS] chore: update swift webrtc sdks to 114.5735.10 (#1576)
-* [Android] fix: actually call selectAudioOutput in enableSpeakerButPreferBluetooth
-* [iOS] fix: remember speakerphone mode for ensureAudioSession (#1568)
-* [Windows/Linux] Fix handling of unimplemented method (#1563)
-
-[0.10.3] - 2024-04-09
-
-* [iOS/macOS] Fix compilation warning for iOS/macOS.
-
-[0.10.2] - 2024-04-08
-
-* [Native/Web] feat: add keyRingSize/discardFrameWhenCryptorNotReady to KeyProviderOptions.
-
-[0.10.1] - 2024-04-08
-
-* [Web] fix renderer issue for web.
-
-[0.10.0] - 2024-04-08
-
-* [Web] move to package:web.
-
-[0.9.48+hotfix.1] - 2024-02-05
-
-* [Android] bump version for libwebrtc.
-
-[0.9.48] - 2024-02-05
-
-* [Android] bump version for libwebrtc.
-* [iOS] Supports ensureAudioSsession method for iOS only. (#1514)
-* [Android] fix android wrong display size. (#1508).
-
-[0.9.47] - 2023-11-29
-
-* [Windows/Linux] fix: Check the invalid value of candidate and session description. (#1484)
-* [Windows/Linux/macOS] fix: screen sharing issue for desktop.
-* [Web] fix: platformViewRegistry getter is deprecated (#1485)
-* [Dart] Throw exception for set src object (#1491).
-
-[0.9.46] - 2023-10-25
-
-* [iOS/macOS] fix: Crop video output size to target settings. (#1472)
-* [Android] fix: Fix bluetooth sco not stopping after room disconnect (#1475)
-
-[0.9.45] - 2023-09-27
-
-* [iOS/macOS] fix: send message on non-platform thread.
-* [Windows] fix: fix setSrcObj with trackId for Windows.
-* [Windows] fix: fix "unlock of unowned mutex" error when call "captureFrame()" func on windows.
-
-[0.9.44] - 2023-09-25
-
-* [Windows] fix: fix Renderer bug for Windows.
-* [Native] fix: Use independent threads to process frame encryption/decryption
-* [Native] fix: Correct handle SIF frame
-* [Native] fix: Fix a fault tolerance judgment failure
-
-[0.9.43] - 2023-09-20
-
-* [Native] fix: send frame cryptor events from signaling thread.
-* [Native] fix: h264 freeze when using E2EE.
-
-[0.9.42+hotfix.1] - 2023-09-15
-
-* [Windows/Linux] fix: fix cannot start vp8/h264 encoder correctly.
-
-[0.9.42] - 2023-09-15
-
-* [Dart/Native] feat: add more framcryptor api (#1444)
-* [Dart/Native] feat: support scalability mode (#1442)
-* [Android] fix: Turn off audio routing in non communication modes (#1438)
-
-* [Android] feat: Add more control over android audio options.
-
-[0.9.41] - 2023-08-30
-
-* [Android] feat: Add more control over android audio options.
-
-[0.9.40] - 2023-08-16
-
-* [Windows/Linux] fix: nullptr checking for sender/receiver for getStats.
-
-[0.9.39] - 2023-08-14
-
-* [Dart/Native] feat: add async methods for getting pc states.
-
-[0.9.38] - 2023-08-11
-
-* [Android] fix: Expose helper to clearCommunicationDevice on AudioManager.AUDIOFOCUS_LOSS
-* [Android] feat: support force SW codec list for android, and disable HW codec for VP9 by default.
-* [Android] fix: issue for audio device switch (#1417)
-* [Android/iOS] feat: Added setZoom method to support camera zooming while streaming. (#1412).
-
-[0.9.37] - 2023-08-07
-
-* [Native] fix: Skip set_sdp_fmtp_line if sdpFmtpLine is empty.
-* [Android] fix: fix android earpiece not being replaced after wired headset is disconnected.
-* [Dart] fix: partially rebuild RTCVideoView when renderVideo value changes.
-* [Android] feat: expose android audio modes.
-* [Android] feat: support forceSWCodec for Android.
-* [Linux] fix: add $ORIGIN to rpath.
-
-[0.9.36] - 2023-07-13
-
-* [Native] upgrade libwebrtc to m114.5735.02.
-* [Windows/Linux] Add implementation to MediaStreamTrack.captureFrame() for linux/windows.
-* [Darwin/Android] Support to ignore network adapters used for ICE on Android, iOS and macOS.
-
-[0.9.35] - 2023-06-30
-
-* [iOS] feat: expose audio mode for ios.
-* [Darwin] fix: compiler warning for Darwin.
-* [Dart] Fix setMicrophoneMute() not awaitable.
-* [Native] Update libwebrtc to m114.
-* [Dart/Web] Separate frame cryptor to dart-webrtc.
-
-[0.9.34] - 2023-06-14
-
-* [Web] fix facingMode for flutter web mobile.
-
-[0.9.33] - 2023-06-08
-
-* [Android] fix frame drops for android.
-
-[0.9.32] - 2023-05-30
-
-* [Android] fix issue for get user audio.
-* [Android] fix getStats throw LinkedHasMap exception.
-
-[0.9.31] - 2023-05-23
-
-* [Darwin] Improve iOS/macOS H264 encoder (Upgrade to WebRTC-SDK M104.5112.17).
-
-[0.9.30+hotfix.2] - 2023-05-18
-
-* [Windows/Linux] fix bug for eventchannel proxy.
-* [Windows/Linux] fix: crash for pc.close/dispose on win/linux. (#1360)
-
-[0.9.30+hotfix.1] - 2023-05-17
-
-* [Windows/Linux] Fix compiler error.
-
-[0.9.30] - 2023-05-16
-
-* [Darwin] Handle exceptions for frame rate settings for darinw. (#1351)
-* [Android] Fix bluetooth device enumerate. (#1349)
-* [Darwin/Android/Windows/Linux] Added maxIPv6Networks configuration (#1350)
-* [iOS] Fix: broadcast extension not found fallback logic (#1347)
-* [Android] Move the call of capturer.stopCapture() outside the main thread to avoid blocking of flutter method call.
-* [Windows/Linux] Fix the crash issue of video room (#1343)
-
-[0.9.29+hotfix.1] - 2023-05-08
-
-* [Android] fix: application context null when app is terminated.
-* [Android/iOS] feat: add way to enable speaker but prefer bluetooth.
-
-[0.9.28] - 2023-05-08
-
-* [Windows/Linux] fix: use the correct transceiver id.
-* [Windows/Linux] fix: Support restart camera for Windows/Linux.
-
-[0.9.27] - 2023-04-27
-
-* [Darwin/Android/Windows/Linux] feat: framecryptor.
-* [Windows/Linux] Fix the type/code mistake.
-* [Windows/Linux] Fix uneffective RTPTransceiver::GetCurrentDirection.
-* [Windows/Linux] RTPtransceiver::getCurrentDirection returns correct value.
-
-[0.9.26] - 2023-04-16
-
-* [iOS/macOS] motify h264 profile-level-id to support high resolution.
-* [Dawrin/Android/Windows] feat: add RTCDegradationPreference to RTCRtpParameters.
-
-[0.9.25] - 2023-04-10
-
-* [Dawrin/Android/Windows] Add `addStreams` to `RTCRtpSender`
-* [Android] fix: label for Wired Headset. (#1305)
-* [Dawrin/Android/Windows] Feat/media stream track get settings (#1294)
-* [Android/iOS] Fix track lookup in the platform specific code for Android and iOS (#1289)
-* [iOS] fix: ICE Connectivity doesn't establish with DualSIM iPhones.
-* [Android] Switch to webrtc hosted on maven central (#1288)
-
-[0.9.24] - 2023-03-07
-
-* [iOS] avaudiosession mode changed to AVAudioSessionModeVideoChat (#1285)
-* [macOS] fix memory leak for screen capture.
-
-[0.9.23] - 2023-02-17
-
-* [Windows/Linux] Updated libwebrtc binary for windows/linux to fix two crashes.
-
-[0.9.22] - 2023-02-14
-
-* [iOS] fix: Without any setActive for rtc session, libwebrtc manages the session counter by itself. (#1266)
-* [dart] fix: remove rtpsender.dispose.
-* [web] fix video renderer issue for safari.
-* [macOS] Fixed macOS desktop capture crash with simulcast enabled.
-* [macOS] Fix the crash when setting the fps of the virtual camera.
-
-[0.9.21] - 2023-02-10
-
-* [Web] Fix: RTCRtpParameters.fromJsObject for Firefox.
-* [Web] Add bufferedamountlow.
-* [Android] Fixed frame capturer returning images with wrong colors (#1258).
-* [Windows] bug fix.
-
-[0.9.20] - 2023-02-03
-
-* [Dawrin/Android/Windows] Add getCapabilities/setCodecPreferences methods
-* [Darwin] buffered amount
-* [Linux] Fixed audio device name buffer size
-* [Android] Start audioswitch and only activate it when needed
-* [Darwin] Fix typo which broke GcmCryptoSuites
-
-[0.9.19] - 2023-01-10
-
-* [Dart] Fix getStats: change 'track' to 'trackId' (#1199)
-* [Android] keep the audio switch after stopping (#1202)
-* [Dart] Enhance RTC video view with placeholder builder property (#1206)
-* [Android] Use forked version of audio switch to avoid BLUETOOTH_CONNECT permission (#1218)
-
-[0.9.18] - 2022-12-12
-
-* [Web] Bump dart_webrtc to 1.0.12, Convert iceconnectionstate to connectionstate for Firefox.
-* [Android] Start AudioSwitchManager only when audio track added (fix #1163) (#1196)
-* [iOS] Implement detachFromEngineForRegistrar (#1192)
-* [iOS] Handle Platform Exception on addCandidate (#1190)
-* [Native] Code format with clang-format.
-
-[0.9.17] - 2022-11-28
-
-* [Android] Update android webrtc version to 104.5112.05
-* [iOS] Update WebRTC.xframework version to 104.5112.07
-
-[0.9.16] - 2022-11-14
-
-* [Linux] Fixed compiler error for flutter 3.3.8.
-* [Linux] Remove 32-bit precompiled binaries.
-* [Linux] Supports linux-x64 and linux-arm64.
-
-[0.9.15] - 2022-11-13
-
-* [Linux] Add Linux Support.
-
-[0.9.14] - 2022-11-12
-
-* [iOS] Fix setSpeakerOn has no effect after change AVAudioSession mode to playback.
-
-[0.9.13] - 2022-11-12
-
-* [Dart] Change MediaStream.clone to async.
-* [iOS] Fixed the bug that the mic indicator light was still on when mic recording was stopped.
-* [iOS/macOS/Android/Windows] Allow sdpMLineIndex to be null when addCandidate.
-* [macOS] Frame capture support for MacOS.
-* [Android] Add enableCpuOveruseDetection configuration (#1165).
-* [Android] Update comments (#1164).
-
+--------------------------------------------
[0.9.12] - 2022-11-02
* [iOS] Fixed the problem that iOS earphones and speakers do not switch.
diff --git a/Documentation/E2EE.md b/Documentation/E2EE.md
deleted file mode 100644
index a91c8233f7..0000000000
--- a/Documentation/E2EE.md
+++ /dev/null
@@ -1,92 +0,0 @@
-# End to End Encryption
-
-E2EE is an AES-GCM encryption interface injected before sending the packaged RTP packet and after receiving the RTP packet, ensuring that the data is not eavesdropped when passing through SFU or any public transmission network. It coexists with DTLS-SRTP as two layers of encryption. You can control the key, ratchet and other operations of FrameCryptor yourself to ensure that no third party will monitor your tracks.
-
-## Process of enabling E2EE
-
-1, Prepare the key provider
-
-`ratchetSalt` is used to add to the mixture when ratcheting or deriving AES passwords
-`aesKey` aesKey is the plaintext password you entered, which will be used to derive the actual password
-
-```dart
- final aesKey = 'you-private-key-here'.codeUnits;
- final ratchetSalt = 'flutter-webrtc-ratchet-salt';
-
- var keyProviderOptions = KeyProviderOptions(
- sharedKey: true,
- ratchetSalt: Uint8List.fromList(ratchetSalt.codeUnits),
- ratchetWindowSize: 16,
- failureTolerance: -1,
- );
-
- var keyProvider = await frameCyrptorFactory.createDefaultKeyProvider(keyProviderOptions);
- /// set shared key for all track, default index is 0
- /// also you can set multiple keys by different indexes
- await keyProvider.setSharedKey(key: aesKey);
-```
-
-2, create PeerConnectioin
-
-when you use E2EE on the web, please add `encodedInsertableStreams`,
-
-``` dart
-var pc = await createPeerConnection( {
- 'encodedInsertableStreams': true,
- });
-```
-
-3, Enable FrameCryptor for RTPSender.
-
-```dart
-var stream = await navigator.mediaDevices
- .getUserMedia({'audio': true, 'video': false });
-var audioTrack = stream.getAudioTracks();
-var sender = await pc.addTrack(audioTrack, stream);
-
-var trackId = audioTrack?.id;
-var id = 'audio_' + trackId! + '_sender';
-
-var frameCyrptor =
- await frameCyrptorFactory.createFrameCryptorForRtpSender(
- participantId: id,
- sender: sender,
- algorithm: Algorithm.kAesGcm,
- keyProvider: keyProvider!);
-/// print framecyrptor state
-frameCyrptor.onFrameCryptorStateChanged = (participantId, state) =>
- print('EN onFrameCryptorStateChanged $participantId $state');
-
-/// set currently shared key index
-await frameCyrptor.setKeyIndex(0);
-
-/// enable encryption now.
-await frameCyrptor.setEnabled(true);
-```
-
-4, Enable FrameCryptor for RTPReceiver
-
-```dart
-
-pc.onTrack((RTCTrackEvent event) async {
- var receiver = event.receiver;
- var trackId = event.track?.id;
- var id = event.track.kind + '_' + trackId! + '_receiver';
-
- var frameCyrptor =
- await frameCyrptorFactory.createFrameCryptorForRtpReceiver(
- participantId: id,
- receiver: receiver,
- algorithm: Algorithm.kAesGcm,
- keyProvider: keyProvider);
-
- frameCyrptor.onFrameCryptorStateChanged = (participantId, state) =>
- print('DE onFrameCryptorStateChanged $participantId $state');
-
- /// set currently shared key index
- await frameCyrptor.setKeyIndex(0);
-
- /// enable encryption now.
- await frameCyrptor.setEnabled(true);
-});
-```
diff --git a/README.md b/README.md
index 2715e5a31c..0c4f2e514d 100644
--- a/README.md
+++ b/README.md
@@ -8,38 +8,33 @@ WebRTC plugin for Flutter Mobile/Desktop/Web
Sponsored with 💖   by
-
+
-Enterprise Grade APIs for Feeds, Chat, & Video. Try the Flutter Video tutorial 💬
+Enterprise Grade APIs for Feeds & Chat. Try the Flutter Chat tutorial 💬
-
+
- LiveKit - Open source WebRTC and realtime AI infrastructure
+ LiveKit - Open source WebRTC infrastructure
## Functionality
| Feature | Android | iOS | [Web](https://flutter.dev/web) | macOS | Windows | Linux | [Embedded](https://github.com/sony/flutter-elinux) | [Fuchsia](https://fuchsia.dev/) |
| :-------------: | :-------------:| :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: |
-| Audio/Video | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
-| Data Channel | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
-| Screen Capture | :heavy_check_mark: | [:heavy_check_mark:(*)](https://github.com/flutter-webrtc/flutter-webrtc/wiki/iOS-Screen-Sharing) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
-| Unified-Plan | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
-| Simulcast | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
+| Audio/Video | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | [WIP] | [WIP] | |
+| Data Channel | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | [WIP] | [WIP] ||
+| Screen Capture | :heavy_check_mark: | [:heavy_check_mark:(*)](https://github.com/flutter-webrtc/flutter-webrtc/wiki/iOS-Screen-Sharing) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | | |
+| Unified-Plan | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | [WIP] | [WIP] | |
+| Simulcast | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | [WIP] | | | |
| MediaRecorder | :warning: | :warning: | :heavy_check_mark: | | | | | |
-| End to End Encryption | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
| Insertable Streams | | | | | | | | |
-
-Additional platform/OS support from the other community
-
-- flutter-tizen:
-- flutter-elinux(WIP):
+## Usage
Add `flutter_webrtc` as a [dependency in your pubspec.yaml file](https://flutter.io/using-packages/).
@@ -56,8 +51,7 @@ Add the following entry to your _Info.plist_ file, located in `/io
This entry allows your app to access camera and microphone.
-### Note for iOS
-
+### Note for iOS.
The WebRTC.xframework compiled after the m104 release no longer supports iOS arm devices, so need to add the `config.build_settings['ONLY_ACTIVE_ARCH'] = 'YES'` to your ios/Podfile in your project
ios/Podfile
@@ -93,6 +87,7 @@ If you need to use a Bluetooth device, please add:
```xml
+
```
The Flutter project template adds it, so it may already be there.
@@ -112,9 +107,8 @@ android {
If necessary, in the same `build.gradle` you will need to increase `minSdkVersion` of `defaultConfig` up to `23` (currently default Flutter generator set it to `16`).
### Important reminder
-
When you compile the release apk, you need to add the following operations,
-[Setup Proguard Rules](https://github.com/flutter-webrtc/flutter-webrtc/blob/main/android/proguard-rules.pro)
+[Setup Proguard Rules](https://github.com/flutter-webrtc/flutter-webrtc/commit/d32dab13b5a0bed80dd9d0f98990f107b9b514f4)
## Contributing
diff --git a/analysis_options.yaml b/analysis_options.yaml
index e125cd7523..3ccff842b0 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -41,8 +41,10 @@ analyzer:
# allow self-reference to deprecated members (we do this because otherwise we have
# to annotate every member in every test, assert, etc, when we deprecate something)
deprecated_member_use_from_same_package: ignore
+ # Ignore analyzer hints for updating pubspecs when using Future or
+ # Stream and not importing dart:async
+ # Please see https://github.com/flutter/flutter/pull/24528 for details.
+ sdk_version_async_exported_from_core: ignore
# Conflict with import_sorter
directives_ordering: ignore
constant_identifier_names: ignore
- deprecated_member_use: ignore
- implementation_imports: ignore
diff --git a/android/build.gradle b/android/build.gradle
index f9b288f868..fad0d3c6d7 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -2,7 +2,7 @@ group 'com.cloudwebrtc.webrtc'
version '1.0-SNAPSHOT'
buildscript {
- ext.kotlin_version = '1.7.10'
+ ext.kotlin_version = '1.6.10'
repositories {
google()
mavenCentral()
@@ -26,9 +26,6 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
- if (project.android.hasProperty("namespace")) {
- namespace 'com.cloudwebrtc.webrtc'
- }
compileSdkVersion 31
defaultConfig {
@@ -52,8 +49,8 @@ android {
}
dependencies {
- implementation 'io.github.webrtc-sdk:android:125.6422.03'
- implementation 'com.github.davidliu:audioswitch:89582c47c9a04c62f90aa5e57251af4800a62c9a'
+ implementation 'com.github.webrtc-sdk:android:104.5112.03'
+ implementation "com.twilio:audioswitch:1.1.5"
implementation 'androidx.annotation:annotation:1.1.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
diff --git a/android/gradle.properties b/android/gradle.properties
index e60119f371..8bd86f6805 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,4 +1 @@
org.gradle.jvmargs=-Xmx1536M
-android.useAndroidX=true
-android.enableJetifier=true
-
diff --git a/android/src/main/java/com/cloudwebrtc/webrtc/CameraEventsHandler.java b/android/src/main/java/com/cloudwebrtc/webrtc/CameraEventsHandler.java
index d168ff4220..e355b0c953 100755
--- a/android/src/main/java/com/cloudwebrtc/webrtc/CameraEventsHandler.java
+++ b/android/src/main/java/com/cloudwebrtc/webrtc/CameraEventsHandler.java
@@ -5,80 +5,42 @@
import org.webrtc.CameraVideoCapturer;
class CameraEventsHandler implements CameraVideoCapturer.CameraEventsHandler {
- public enum CameraState {
- NEW,
- OPENING,
- OPENED,
- CLOSED,
- DISCONNECTED,
- ERROR,
- FREEZED
- }
private final static String TAG = FlutterWebRTCPlugin.TAG;
- private CameraState state = CameraState.NEW;
-
- public void waitForCameraOpen() {
- Log.d(TAG, "CameraEventsHandler.waitForCameraOpen");
- while (state != CameraState.OPENED && state != CameraState.ERROR) {
- try {
- Thread.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
-
- public void waitForCameraClosed() {
- Log.d(TAG, "CameraEventsHandler.waitForCameraClosed");
- while (state != CameraState.CLOSED && state != CameraState.ERROR) {
- try {
- Thread.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
// Camera error handler - invoked when camera can not be opened
// or any camera exception happens on camera thread.
@Override
public void onCameraError(String errorDescription) {
Log.d(TAG, String.format("CameraEventsHandler.onCameraError: errorDescription=%s", errorDescription));
- state = CameraState.ERROR;
}
// Called when camera is disconnected.
@Override
public void onCameraDisconnected() {
Log.d(TAG, "CameraEventsHandler.onCameraDisconnected");
- state = CameraState.DISCONNECTED;
}
// Invoked when camera stops receiving frames
@Override
public void onCameraFreezed(String errorDescription) {
Log.d(TAG, String.format("CameraEventsHandler.onCameraFreezed: errorDescription=%s", errorDescription));
- state = CameraState.FREEZED;
}
// Callback invoked when camera is opening.
@Override
public void onCameraOpening(String cameraName) {
Log.d(TAG, String.format("CameraEventsHandler.onCameraOpening: cameraName=%s", cameraName));
- state = CameraState.OPENING;
}
// Callback invoked when first camera frame is available after camera is opened.
@Override
public void onFirstFrameAvailable() {
Log.d(TAG, "CameraEventsHandler.onFirstFrameAvailable");
- state = CameraState.OPENED;
}
// Callback invoked when camera closed.
@Override
public void onCameraClosed() {
Log.d(TAG, "CameraEventsHandler.onFirstFrameAvailable");
- state = CameraState.CLOSED;
}
}
diff --git a/android/src/main/java/com/cloudwebrtc/webrtc/DataChannelObserver.java b/android/src/main/java/com/cloudwebrtc/webrtc/DataChannelObserver.java
index 83f316a036..2cf645f118 100755
--- a/android/src/main/java/com/cloudwebrtc/webrtc/DataChannelObserver.java
+++ b/android/src/main/java/com/cloudwebrtc/webrtc/DataChannelObserver.java
@@ -6,7 +6,6 @@
import org.webrtc.DataChannel;
import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import io.flutter.plugin.common.BinaryMessenger;
@@ -17,9 +16,9 @@ class DataChannelObserver implements DataChannel.Observer, EventChannel.StreamHa
private final String flutterId;
private final DataChannel dataChannel;
- private final EventChannel eventChannel;
+ private EventChannel eventChannel;
private EventChannel.EventSink eventSink;
- private final ArrayList eventQueue = new ArrayList();
+ private ArrayList eventQueue = new ArrayList();
DataChannelObserver(BinaryMessenger messenger, String peerConnectionId, String flutterId,
DataChannel dataChannel) {
@@ -96,7 +95,7 @@ public void onMessage(DataChannel.Buffer buffer) {
params.putByte("data", bytes);
} else {
params.putString("type", "text");
- params.putString("data", new String(bytes, StandardCharsets.UTF_8));
+ params.putString("data", new String(bytes, Charset.forName("UTF-8")));
}
sendEvent(params);
diff --git a/android/src/main/java/com/cloudwebrtc/webrtc/FlutterRTCFrameCryptor.java b/android/src/main/java/com/cloudwebrtc/webrtc/FlutterRTCFrameCryptor.java
deleted file mode 100644
index 199afdc4b9..0000000000
--- a/android/src/main/java/com/cloudwebrtc/webrtc/FlutterRTCFrameCryptor.java
+++ /dev/null
@@ -1,434 +0,0 @@
-package com.cloudwebrtc.webrtc;
-
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import org.webrtc.FrameCryptor;
-import org.webrtc.FrameCryptorAlgorithm;
-import org.webrtc.FrameCryptorFactory;
-import org.webrtc.FrameCryptorKeyProvider;
-import org.webrtc.RtpReceiver;
-import org.webrtc.RtpSender;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
-import io.flutter.plugin.common.BinaryMessenger;
-import io.flutter.plugin.common.EventChannel;
-import io.flutter.plugin.common.MethodCall;
-import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
-import io.flutter.plugin.common.MethodChannel.Result;
-
-import com.cloudwebrtc.webrtc.utils.AnyThreadSink;
-import com.cloudwebrtc.webrtc.utils.ConstraintsMap;
-import com.cloudwebrtc.webrtc.utils.ConstraintsArray;
-
-public class FlutterRTCFrameCryptor {
-
- class FrameCryptorStateObserver implements FrameCryptor.Observer, EventChannel.StreamHandler {
- public FrameCryptorStateObserver(BinaryMessenger messenger, String frameCryptorId){
- this.frameCryptorId = frameCryptorId;
- eventChannel = new EventChannel(messenger, "FlutterWebRTC/frameCryptorEvent" + frameCryptorId);
- eventChannel.setStreamHandler(new EventChannel.StreamHandler() {
- @Override
- public void onListen(Object o, EventChannel.EventSink sink) {
- eventSink = new AnyThreadSink(sink);
- for(Object event : eventQueue) {
- eventSink.success(event);
- }
- eventQueue.clear();
- }
- @Override
- public void onCancel(Object o) {
- eventSink = null;
- }
- });
- }
- private final EventChannel eventChannel;
- private EventChannel.EventSink eventSink;
- private final ArrayList eventQueue = new ArrayList();
- private final String frameCryptorId;
-
- @Override
- public void onListen(Object arguments, EventChannel.EventSink events) {
- eventSink = new AnyThreadSink(events);
- for(Object event : eventQueue) {
- eventSink.success(event);
- }
- eventQueue.clear();
- }
-
- @Override
- public void onCancel(Object arguments) {
- eventSink = null;
- }
-
- private String frameCryptorErrorStateToString( FrameCryptor.FrameCryptionState state) {
- switch (state) {
- case NEW:
- return "new";
- case OK:
- return "ok";
- case DECRYPTIONFAILED:
- return "decryptionFailed";
- case ENCRYPTIONFAILED:
- return "encryptionFailed";
- case INTERNALERROR:
- return "internalError";
- case KEYRATCHETED:
- return "keyRatcheted";
- case MISSINGKEY:
- return "missingKey";
- default:
- throw new IllegalArgumentException("Unknown FrameCryptorErrorState: " + state);
- }
- }
-
- @Override
- public void onFrameCryptionStateChanged(String participantId, FrameCryptor.FrameCryptionState state) {
- Map event = new HashMap<>();
- event.put("event", "frameCryptionStateChanged");
- event.put("participantId", participantId);
- event.put("state",frameCryptorErrorStateToString(state));
- if (eventSink != null) {
- eventSink.success(event);
- } else {
- eventQueue.add(event);
- }
- }
- }
-
- private static final String TAG = "FlutterRTCFrameCryptor";
- private final Map frameCryptos = new HashMap<>();
- private final Map frameCryptoObservers = new HashMap<>();
- private final Map keyProviders = new HashMap<>();
- private final StateProvider stateProvider;
- public FlutterRTCFrameCryptor(StateProvider stateProvider) {
- this.stateProvider = stateProvider;
- }
- public boolean handleMethodCall(MethodCall call, @NonNull Result result) {
- String method_name = call.method;
- Map params = (Map) call.arguments;
- if (method_name.equals("frameCryptorFactoryCreateFrameCryptor")) {
- frameCryptorFactoryCreateFrameCryptor(params, result);
- } else if (method_name.equals("frameCryptorSetKeyIndex")) {
- frameCryptorSetKeyIndex(params, result);
- } else if (method_name.equals("frameCryptorGetKeyIndex")) {
- frameCryptorGetKeyIndex(params, result);
- } else if (method_name.equals("frameCryptorSetEnabled")) {
- frameCryptorSetEnabled(params, result);
- } else if (method_name.equals("frameCryptorGetEnabled")) {
- frameCryptorGetEnabled(params, result);
- } else if (method_name.equals("frameCryptorDispose")) {
- frameCryptorDispose(params, result);
- } else if (method_name.equals("frameCryptorFactoryCreateKeyProvider")) {
- frameCryptorFactoryCreateKeyProvider(params, result);
- }else if (method_name.equals("keyProviderSetSharedKey")) {
- keyProviderSetSharedKey(params, result);
- } else if (method_name.equals("keyProviderRatchetSharedKey")) {
- keyProviderRatchetSharedKey(params, result);
- } else if (method_name.equals("keyProviderExportSharedKey")) {
- keyProviderExportKey(params, result);
- } else if (method_name.equals("keyProviderSetKey")) {
- keyProviderSetKey(params, result);
- } else if (method_name.equals("keyProviderRatchetKey")) {
- keyProviderRatchetKey(params, result);
- } else if (method_name.equals("keyProviderExportKey")) {
- keyProviderExportKey(params, result);
- } else if (method_name.equals("keyProviderSetSifTrailer")) {
- keyProviderSetSifTrailer(params, result);
- } else if (method_name.equals("keyProviderDispose")) {
- keyProviderDispose(params, result);
- } else {
- return false;
- }
- return true;
- }
-
- private FrameCryptorAlgorithm frameCryptorAlgorithmFromInt(int algorithm) {
- switch (algorithm) {
- case 0:
- return FrameCryptorAlgorithm.AES_GCM;
- default:
- return FrameCryptorAlgorithm.AES_GCM;
- }
- }
-
- private void frameCryptorFactoryCreateFrameCryptor(Map params, @NonNull Result result) {
- String keyProviderId = (String) params.get("keyProviderId");
- FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId);
- if (keyProvider == null) {
- result.error("frameCryptorFactoryCreateFrameCryptorFailed", "keyProvider not found", null);
- return;
- }
- String peerConnectionId = (String) params.get("peerConnectionId");
- PeerConnectionObserver pco = stateProvider.getPeerConnectionObserver(peerConnectionId);
- if (pco == null) {
- result.error("frameCryptorFactoryCreateFrameCryptorFailed", "peerConnection not found", null);
- return;
- }
- String participantId = (String) params.get("participantId");
- String type = (String) params.get("type");
- int algorithm = (int) params.get("algorithm");
- String rtpSenderId = (String) params.get("rtpSenderId");
- String rtpReceiverId = (String) params.get("rtpReceiverId");
-
- if(type.equals("sender")) {
- RtpSender rtpSender = pco.getRtpSenderById(rtpSenderId);
-
- FrameCryptor frameCryptor = FrameCryptorFactory.createFrameCryptorForRtpSender(stateProvider.getPeerConnectionFactory(),
- rtpSender,
- participantId,
- frameCryptorAlgorithmFromInt(algorithm),
- keyProvider);
- String frameCryptorId = UUID.randomUUID().toString();
- frameCryptos.put(frameCryptorId, frameCryptor);
- FrameCryptorStateObserver observer = new FrameCryptorStateObserver(stateProvider.getMessenger(), frameCryptorId);
- frameCryptor.setObserver(observer);
- frameCryptoObservers.put(frameCryptorId, observer);
- ConstraintsMap paramsResult = new ConstraintsMap();
- paramsResult.putString("frameCryptorId", frameCryptorId);
- result.success(paramsResult.toMap());
- } else if(type.equals("receiver")) {
- RtpReceiver rtpReceiver = pco.getRtpReceiverById(rtpReceiverId);
-
- FrameCryptor frameCryptor = FrameCryptorFactory.createFrameCryptorForRtpReceiver(stateProvider.getPeerConnectionFactory(),
- rtpReceiver,
- participantId,
- frameCryptorAlgorithmFromInt(algorithm),
- keyProvider);
- String frameCryptorId = UUID.randomUUID().toString();
- frameCryptos.put(frameCryptorId, frameCryptor);
- FrameCryptorStateObserver observer = new FrameCryptorStateObserver(stateProvider.getMessenger(), frameCryptorId);
- frameCryptor.setObserver(observer);
- frameCryptoObservers.put(frameCryptorId, observer);
- ConstraintsMap paramsResult = new ConstraintsMap();
- paramsResult.putString("frameCryptorId", frameCryptorId);
- result.success(paramsResult.toMap());
- } else {
- result.error("frameCryptorFactoryCreateFrameCryptorFailed", "type must be sender or receiver", null);
- return;
- }
- }
-
- private void frameCryptorSetKeyIndex(Map params, @NonNull Result result) {
- String frameCryptorId = (String) params.get("frameCryptorId");
- FrameCryptor frameCryptor = frameCryptos.get(frameCryptorId);
- if (frameCryptor == null) {
- result.error("frameCryptorSetKeyIndexFailed", "frameCryptor not found", null);
- return;
- }
- int keyIndex = (int) params.get("keyIndex");
- frameCryptor.setKeyIndex(keyIndex);
- ConstraintsMap paramsResult = new ConstraintsMap();
- paramsResult.putBoolean("result", true);
- result.success(paramsResult.toMap());
- }
-
- private void frameCryptorGetKeyIndex(Map params, @NonNull Result result) {
- String frameCryptorId = (String) params.get("frameCryptorId");
- FrameCryptor frameCryptor = frameCryptos.get(frameCryptorId);
- if (frameCryptor == null) {
- result.error("frameCryptorGetKeyIndexFailed", "frameCryptor not found", null);
- return;
- }
- int keyIndex = frameCryptor.getKeyIndex();
- ConstraintsMap paramsResult = new ConstraintsMap();
- paramsResult.putInt("keyIndex", keyIndex);
- result.success(paramsResult.toMap());
- }
-
- private void frameCryptorSetEnabled(Map params, @NonNull Result result) {
- String frameCryptorId = (String) params.get("frameCryptorId");
- FrameCryptor frameCryptor = frameCryptos.get(frameCryptorId);
- if (frameCryptor == null) {
- result.error("frameCryptorSetEnabledFailed", "frameCryptor not found", null);
- return;
- }
- boolean enabled = (boolean) params.get("enabled");
- frameCryptor.setEnabled(enabled);
-
- ConstraintsMap paramsResult = new ConstraintsMap();
- paramsResult.putBoolean("result", enabled);
- result.success(paramsResult.toMap());
- }
-
- private void frameCryptorGetEnabled(Map params, @NonNull Result result) {
- String frameCryptorId = (String) params.get("frameCryptorId");
- FrameCryptor frameCryptor = frameCryptos.get(frameCryptorId);
- if (frameCryptor == null) {
- result.error("frameCryptorGetEnabledFailed", "frameCryptor not found", null);
- return;
- }
- boolean enabled = frameCryptor.isEnabled();
- ConstraintsMap paramsResult = new ConstraintsMap();
- paramsResult.putBoolean("enabled", enabled);
- result.success(paramsResult.toMap());
- }
-
- private void frameCryptorDispose(Map params, @NonNull Result result) {
- String frameCryptorId = (String) params.get("frameCryptorId");
- FrameCryptor frameCryptor = frameCryptos.get(frameCryptorId);
- if (frameCryptor == null) {
- result.error("frameCryptorDisposeFailed", "frameCryptor not found", null);
- return;
- }
- frameCryptor.dispose();
- frameCryptos.remove(frameCryptorId);
- frameCryptoObservers.remove(frameCryptorId);
- ConstraintsMap paramsResult = new ConstraintsMap();
- paramsResult.putString("result", "success");
- result.success(paramsResult.toMap());
- }
-
- private void frameCryptorFactoryCreateKeyProvider(Map params, @NonNull Result result) {
- String keyProviderId = UUID.randomUUID().toString();
- Map keyProviderOptions = (Map) params.get("keyProviderOptions");
- boolean sharedKey = (boolean) keyProviderOptions.get("sharedKey");
- int ratchetWindowSize = (int) keyProviderOptions.get("ratchetWindowSize");
- int failureTolerance = (int) keyProviderOptions.get("failureTolerance");
- byte[] ratchetSalt = ( byte[]) keyProviderOptions.get("ratchetSalt");
- byte[] uncryptedMagicBytes = new byte[0];
- if(keyProviderOptions.containsKey("uncryptedMagicBytes")) {
- uncryptedMagicBytes = ( byte[]) keyProviderOptions.get("uncryptedMagicBytes");
- }
- int keyRingSize = (int) keyProviderOptions.get("keyRingSize");
- boolean discardFrameWhenCryptorNotReady = (boolean) keyProviderOptions.get("discardFrameWhenCryptorNotReady");
- FrameCryptorKeyProvider keyProvider = FrameCryptorFactory.createFrameCryptorKeyProvider(sharedKey, ratchetSalt, ratchetWindowSize, uncryptedMagicBytes, failureTolerance, keyRingSize, discardFrameWhenCryptorNotReady);
- ConstraintsMap paramsResult = new ConstraintsMap();
- keyProviders.put(keyProviderId, keyProvider);
- paramsResult.putString("keyProviderId", keyProviderId);
- result.success(paramsResult.toMap());
- }
-
- private void keyProviderSetSharedKey(Map params, @NonNull Result result) {
- String keyProviderId = (String) params.get("keyProviderId");
- FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId);
- if (keyProvider == null) {
- result.error("keyProviderSetKeySharedFailed", "keyProvider not found", null);
- return;
- }
- int keyIndex = (int) params.get("keyIndex");
- byte[] key = ( byte[]) params.get("key");
- keyProvider.setSharedKey(keyIndex, key);
-
- ConstraintsMap paramsResult = new ConstraintsMap();
- paramsResult.putBoolean("result", true);
- result.success(paramsResult.toMap());
- }
-
- private void keyProviderRatchetSharedKey(Map params, @NonNull Result result) {
- String keyProviderId = (String) params.get("keyProviderId");
- FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId);
- if (keyProvider == null) {
- result.error("keyProviderRatchetSharedKeyFailed", "keyProvider not found", null);
- return;
- }
- int keyIndex = (int) params.get("keyIndex");
-
- byte[] newKey = keyProvider.ratchetSharedKey(keyIndex);
-
- ConstraintsMap paramsResult = new ConstraintsMap();
- paramsResult.putByte("result", newKey);
- result.success(paramsResult.toMap());
- }
-
- private void keyProviderExportSharedKey(Map params, @NonNull Result result) {
- String keyProviderId = (String) params.get("keyProviderId");
- FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId);
- if (keyProvider == null) {
- result.error("keyProviderExportSharedKeyFailed", "keyProvider not found", null);
- return;
- }
- int keyIndex = (int) params.get("keyIndex");
-
- byte[] key = keyProvider.exportSharedKey(keyIndex);
-
- ConstraintsMap paramsResult = new ConstraintsMap();
- paramsResult.putByte("result", key);
- result.success(paramsResult.toMap());
- }
-
- private void keyProviderSetKey(Map params, @NonNull Result result) {
- String keyProviderId = (String) params.get("keyProviderId");
- FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId);
- if (keyProvider == null) {
- result.error("keyProviderSetKeyFailed", "keyProvider not found", null);
- return;
- }
- int keyIndex = (int) params.get("keyIndex");
- String participantId = (String) params.get("participantId");
- byte[] key = ( byte[]) params.get("key");
- keyProvider.setKey(participantId, keyIndex, key);
-
- ConstraintsMap paramsResult = new ConstraintsMap();
- paramsResult.putBoolean("result", true);
- result.success(paramsResult.toMap());
- }
-
- private void keyProviderRatchetKey(Map params, @NonNull Result result) {
- String keyProviderId = (String) params.get("keyProviderId");
- FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId);
- if (keyProvider == null) {
- result.error("keyProviderSetKeysFailed", "keyProvider not found", null);
- return;
- }
- String participantId = (String) params.get("participantId");
- int keyIndex = (int) params.get("keyIndex");
-
- byte[] newKey = keyProvider.ratchetKey(participantId, keyIndex);
-
- ConstraintsMap paramsResult = new ConstraintsMap();
- paramsResult.putByte("result", newKey);
- result.success(paramsResult.toMap());
- }
-
- private void keyProviderExportKey(Map params, @NonNull Result result) {
- String keyProviderId = (String) params.get("keyProviderId");
- FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId);
- if (keyProvider == null) {
- result.error("keyProviderExportKeyFailed", "keyProvider not found", null);
- return;
- }
- String participantId = (String) params.get("participantId");
- int keyIndex = (int) params.get("keyIndex");
-
- byte[] key = keyProvider.exportKey(participantId, keyIndex);
-
- ConstraintsMap paramsResult = new ConstraintsMap();
- paramsResult.putByte("result", key);
- result.success(paramsResult.toMap());
- }
-
- private void keyProviderSetSifTrailer(Map params, @NonNull Result result) {
- String keyProviderId = (String) params.get("keyProviderId");
- FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId);
- if (keyProvider == null) {
- result.error("keyProviderSetSifTrailerFailed", "keyProvider not found", null);
- return;
- }
- byte[] sifTrailer = ( byte[]) params.get("sifTrailer");
- keyProvider.setSifTrailer(sifTrailer);
-
- ConstraintsMap paramsResult = new ConstraintsMap();
- paramsResult.putBoolean("result", true);
- result.success(paramsResult.toMap());
- }
-
- private void keyProviderDispose(Map params, @NonNull Result result) {
- String keyProviderId = (String) params.get("keyProviderId");
- FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId);
- if (keyProvider == null) {
- result.error("keyProviderDisposeFailed", "keyProvider not found", null);
- return;
- }
- keyProvider.dispose();
- keyProviders.remove(keyProviderId);
- ConstraintsMap paramsResult = new ConstraintsMap();
- paramsResult.putString("result", "success");
- result.success(paramsResult.toMap());
- }
-}
diff --git a/android/src/main/java/com/cloudwebrtc/webrtc/FlutterRTCVideoRenderer.java b/android/src/main/java/com/cloudwebrtc/webrtc/FlutterRTCVideoRenderer.java
index 4c1598c98e..21ce27b656 100755
--- a/android/src/main/java/com/cloudwebrtc/webrtc/FlutterRTCVideoRenderer.java
+++ b/android/src/main/java/com/cloudwebrtc/webrtc/FlutterRTCVideoRenderer.java
@@ -2,7 +2,6 @@
import android.util.Log;
import android.graphics.SurfaceTexture;
-import android.view.Surface;
import com.cloudwebrtc.webrtc.utils.AnyThreadSink;
import com.cloudwebrtc.webrtc.utils.ConstraintsMap;
@@ -21,12 +20,11 @@
public class FlutterRTCVideoRenderer implements EventChannel.StreamHandler {
private static final String TAG = FlutterWebRTCPlugin.TAG;
- private final TextureRegistry.SurfaceProducer producer;
+ private final SurfaceTexture texture;
+ private TextureRegistry.SurfaceTextureEntry entry;
private int id = -1;
private MediaStream mediaStream;
- private String ownerTag;
-
public void Dispose() {
//destroy
if (surfaceTextureRenderer != null) {
@@ -36,7 +34,7 @@ public void Dispose() {
eventChannel.setStreamHandler(null);
eventSink = null;
- producer.release();
+ entry.release();
}
/**
@@ -55,9 +53,7 @@ public void onFirstFrameRendered() {
ConstraintsMap params = new ConstraintsMap();
params.putString("event", "didFirstFrameRendered");
params.putInt("id", id);
- if (eventSink != null) {
- eventSink.success(params.toMap());
- }
+ eventSink.success(params.toMap());
}
@Override
@@ -90,7 +86,7 @@ public void onFrameResolutionChanged(
};
}
- private final SurfaceTextureRenderer surfaceTextureRenderer;
+ private SurfaceTextureRenderer surfaceTextureRenderer;
/**
* The {@code VideoTrack}, if any, rendered by this {@code FlutterRTCVideoRenderer}.
@@ -100,15 +96,15 @@ public void onFrameResolutionChanged(
EventChannel eventChannel;
EventChannel.EventSink eventSink;
- public FlutterRTCVideoRenderer(TextureRegistry.SurfaceProducer producer) {
+ public FlutterRTCVideoRenderer(SurfaceTexture texture, TextureRegistry.SurfaceTextureEntry entry) {
this.surfaceTextureRenderer = new SurfaceTextureRenderer("");
listenRendererEvents();
surfaceTextureRenderer.init(EglUtils.getRootEglBaseContext(), rendererEvents);
- surfaceTextureRenderer.surfaceCreated(producer);
+ surfaceTextureRenderer.surfaceCreated(texture);
+ this.texture = texture;
this.eventSink = null;
- this.producer = producer;
- this.ownerTag = null;
+ this.entry = entry;
}
public void setEventChannel(EventChannel eventChannel) {
@@ -145,10 +141,9 @@ private void removeRendererFromVideoTrack() {
* @param mediaStream The {@code MediaStream} to be rendered by this
* {@code FlutterRTCVideoRenderer} or {@code null}.
*/
- public void setStream(MediaStream mediaStream, String ownerTag) {
+ public void setStream(MediaStream mediaStream) {
VideoTrack videoTrack;
this.mediaStream = mediaStream;
- this.ownerTag = ownerTag;
if (mediaStream == null) {
videoTrack = null;
} else {
@@ -169,10 +164,9 @@ public void setStream(MediaStream mediaStream, String ownerTag) {
* @param trackId The {@code trackId} to be rendered by this
* {@code FlutterRTCVideoRenderer} or {@code null}.
*/
- public void setStream(MediaStream mediaStream,String trackId, String ownerTag) {
+ public void setStream(MediaStream mediaStream,String trackId) {
VideoTrack videoTrack;
this.mediaStream = mediaStream;
- this.ownerTag = ownerTag;
if (mediaStream == null) {
videoTrack = null;
} else {
@@ -211,7 +205,7 @@ public void setVideoTrack(VideoTrack videoTrack) {
Log.w(TAG, "FlutterRTCVideoRenderer.setVideoTrack, set video track to " + videoTrack.id());
tryAddRendererToVideoTrack();
} catch (Exception e) {
- Log.e(TAG, "tryAddRendererToVideoTrack " + e);
+ Log.e(TAG, "tryAddRendererToVideoTrack " + e.toString());
}
} else {
Log.w(TAG, "FlutterRTCVideoRenderer.setVideoTrack, set video track to null");
@@ -237,21 +231,21 @@ private void tryAddRendererToVideoTrack() throws Exception {
surfaceTextureRenderer.release();
listenRendererEvents();
surfaceTextureRenderer.init(sharedContext, rendererEvents);
- surfaceTextureRenderer.surfaceCreated(producer);
+ surfaceTextureRenderer.surfaceCreated(texture);
videoTrack.addSink(surfaceTextureRenderer);
}
}
- public boolean checkMediaStream(String id, String ownerTag) {
- if (null == id || null == mediaStream || ownerTag == null || !ownerTag.equals(this.ownerTag)) {
+ public boolean checkMediaStream(String id) {
+ if (null == id || null == mediaStream) {
return false;
}
return id.equals(mediaStream.getId());
}
- public boolean checkVideoTrack(String id, String ownerTag) {
- if (null == id || null == videoTrack || ownerTag == null || !ownerTag.equals(this.ownerTag)) {
+ public boolean checkVideoTrack(String id) {
+ if (null == id || null == videoTrack) {
return false;
}
return id.equals(videoTrack.id());
diff --git a/android/src/main/java/com/cloudwebrtc/webrtc/FlutterWebRTCPlugin.java b/android/src/main/java/com/cloudwebrtc/webrtc/FlutterWebRTCPlugin.java
index 3a49f88c85..d6ce3889fe 100644
--- a/android/src/main/java/com/cloudwebrtc/webrtc/FlutterWebRTCPlugin.java
+++ b/android/src/main/java/com/cloudwebrtc/webrtc/FlutterWebRTCPlugin.java
@@ -11,14 +11,10 @@
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
-import com.cloudwebrtc.webrtc.audio.AudioProcessingController;
import com.cloudwebrtc.webrtc.audio.AudioSwitchManager;
import com.cloudwebrtc.webrtc.utils.AnyThreadSink;
import com.cloudwebrtc.webrtc.utils.ConstraintsMap;
-import org.webrtc.ExternalAudioProcessingFactory;
-import org.webrtc.MediaStreamTrack;
-
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
@@ -26,6 +22,7 @@
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodChannel;
+import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.view.TextureRegistry;
/**
@@ -36,6 +33,7 @@ public class FlutterWebRTCPlugin implements FlutterPlugin, ActivityAware, EventC
static public final String TAG = "FlutterWebRTCPlugin";
private static Application application;
+ private AudioSwitchManager audioSwitchManager;
private MethodChannel methodChannel;
private MethodCallHandlerImpl methodCallHandler;
private LifeCycleObserver observer;
@@ -44,25 +42,26 @@ public class FlutterWebRTCPlugin implements FlutterPlugin, ActivityAware, EventC
public EventChannel.EventSink eventSink;
public FlutterWebRTCPlugin() {
- sharedSingleton = this;
}
- public static FlutterWebRTCPlugin sharedSingleton;
-
- public AudioProcessingController getAudioProcessingController() {
- return methodCallHandler.audioProcessingController;
- }
+ /**
+ * Plugin registration.
+ */
+ public static void registerWith(Registrar registrar) {
+ final FlutterWebRTCPlugin plugin = new FlutterWebRTCPlugin();
- public MediaStreamTrack getTrackForId(String trackId, String peerConnectionId) {
- return methodCallHandler.getTrackForId(trackId, peerConnectionId);
- }
+ plugin.startListening(registrar.context(), registrar.messenger(), registrar.textures());
- public LocalTrack getLocalTrack(String trackId) {
- return methodCallHandler.getLocalTrack(trackId);
- }
+ if (registrar.activeContext() instanceof Activity) {
+ plugin.methodCallHandler.setActivity((Activity) registrar.activeContext());
+ }
+ application = ((Application) registrar.context().getApplicationContext());
+ application.registerActivityLifecycleCallbacks(plugin.observer);
- public MediaStreamTrack getRemoteTrack(String trackId) {
- return methodCallHandler.getRemoteTrack(trackId);
+ registrar.addViewDestroyListener(view -> {
+ plugin.stopListening();
+ return false;
+ });
}
@Override
@@ -99,8 +98,8 @@ public void onDetachedFromActivity() {
methodCallHandler.setActivity(null);
if (this.observer != null) {
this.lifecycle.removeObserver(this.observer);
- if (application!=null) {
- application.unregisterActivityLifecycleCallbacks(this.observer);
+ if (this.application!=null) {
+ this.application.unregisterActivityLifecycleCallbacks(this.observer);
}
}
this.lifecycle = null;
@@ -108,13 +107,14 @@ public void onDetachedFromActivity() {
private void startListening(final Context context, BinaryMessenger messenger,
TextureRegistry textureRegistry) {
- AudioSwitchManager.instance = new AudioSwitchManager(context);
- methodCallHandler = new MethodCallHandlerImpl(context, messenger, textureRegistry);
+ audioSwitchManager = new AudioSwitchManager(context);
+ methodCallHandler = new MethodCallHandlerImpl(context, messenger, textureRegistry,
+ audioSwitchManager);
methodChannel = new MethodChannel(messenger, "FlutterWebRTC.Method");
methodChannel.setMethodCallHandler(methodCallHandler);
eventChannel = new EventChannel( messenger,"FlutterWebRTC.Event");
eventChannel.setStreamHandler(this);
- AudioSwitchManager.instance.audioDeviceChangeListener = (devices, currentDevice) -> {
+ audioSwitchManager.audioDeviceChangeListener = (devices, currentDevice) -> {
Log.w(TAG, "audioFocusChangeListener " + devices+ " " + currentDevice);
ConstraintsMap params = new ConstraintsMap();
params.putString("event", "onDeviceChange");
@@ -128,9 +128,9 @@ private void stopListening() {
methodCallHandler = null;
methodChannel.setMethodCallHandler(null);
eventChannel.setStreamHandler(null);
- if (AudioSwitchManager.instance != null) {
+ if (audioSwitchManager != null) {
Log.d(TAG, "Stopping the audio manager...");
- AudioSwitchManager.instance.stop();
+ audioSwitchManager = null;
}
}
diff --git a/android/src/main/java/com/cloudwebrtc/webrtc/GetUserMediaImpl.java b/android/src/main/java/com/cloudwebrtc/webrtc/GetUserMediaImpl.java
index 0b0998f384..21e43322d6 100755
--- a/android/src/main/java/com/cloudwebrtc/webrtc/GetUserMediaImpl.java
+++ b/android/src/main/java/com/cloudwebrtc/webrtc/GetUserMediaImpl.java
@@ -4,38 +4,38 @@
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
-import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.graphics.Point;
+import android.hardware.Camera;
+import android.hardware.Camera.Parameters;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
import android.media.AudioDeviceInfo;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
-import android.net.Uri;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.os.ParcelFileDescriptor;
import android.os.ResultReceiver;
import android.provider.MediaStore;
import android.util.Log;
-import android.util.Pair;
+import android.util.Range;
import android.util.SparseArray;
-import android.view.Display;
+import android.view.Surface;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
-import com.cloudwebrtc.webrtc.audio.AudioSwitchManager;
-import com.cloudwebrtc.webrtc.audio.AudioUtils;
-import com.cloudwebrtc.webrtc.audio.LocalAudioTrack;
import com.cloudwebrtc.webrtc.record.AudioChannel;
import com.cloudwebrtc.webrtc.record.AudioSamplesInterceptor;
import com.cloudwebrtc.webrtc.record.MediaRecorderImpl;
@@ -47,24 +47,20 @@
import com.cloudwebrtc.webrtc.utils.MediaConstraintsUtils;
import com.cloudwebrtc.webrtc.utils.ObjectType;
import com.cloudwebrtc.webrtc.utils.PermissionUtils;
-import com.cloudwebrtc.webrtc.video.LocalVideoTrack;
-import com.cloudwebrtc.webrtc.video.VideoCapturerInfo;
import org.webrtc.AudioSource;
import org.webrtc.AudioTrack;
import org.webrtc.Camera1Capturer;
import org.webrtc.Camera1Enumerator;
-import org.webrtc.Camera1Helper;
import org.webrtc.Camera2Capturer;
import org.webrtc.Camera2Enumerator;
-import org.webrtc.Camera2Helper;
+import org.webrtc.CameraEnumerationAndroid.CaptureFormat;
import org.webrtc.CameraEnumerator;
import org.webrtc.CameraVideoCapturer;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.MediaStreamTrack;
import org.webrtc.PeerConnectionFactory;
-import org.webrtc.Size;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoSource;
@@ -72,9 +68,7 @@
import org.webrtc.audio.JavaAudioDeviceModule;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
+import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -86,7 +80,8 @@
* The implementation of {@code getUserMedia} extracted into a separate file in order to reduce
* complexity and to (somewhat) separate concerns.
*/
-public class GetUserMediaImpl {
+class GetUserMediaImpl {
+
private static final int DEFAULT_WIDTH = 1280;
private static final int DEFAULT_HEIGHT = 720;
private static final int DEFAULT_FPS = 30;
@@ -103,7 +98,7 @@ public class GetUserMediaImpl {
static final String TAG = FlutterWebRTCPlugin.TAG;
- private final Map mVideoCapturers = new HashMap<>();
+ private final Map mVideoCapturers = new HashMap<>();
private final Map mSurfaceTextureHelpers = new HashMap<>();
private final StateProvider stateProvider;
private final Context applicationContext;
@@ -114,13 +109,8 @@ public class GetUserMediaImpl {
private OutputAudioSamplesInterceptor outputSamplesInterceptor = null;
JavaAudioDeviceModule audioDeviceModule;
private final SparseArray mediaRecorders = new SparseArray<>();
- private AudioDeviceInfo preferredInput = null;
- private boolean isTorchOn;
- private Intent mediaProjectionData = null;
-
public void screenRequestPermissions(ResultReceiver resultReceiver) {
- mediaProjectionData = null;
final Activity activity = stateProvider.getActivity();
if (activity == null) {
// Activity went away, nothing we can do.
@@ -147,27 +137,11 @@ public void screenRequestPermissions(ResultReceiver resultReceiver) {
}
}
- public void requestCapturePermission(final Result result) {
- screenRequestPermissions(
- new ResultReceiver(new Handler(Looper.getMainLooper())) {
- @Override
- protected void onReceiveResult(int requestCode, Bundle resultData) {
- int resultCode = resultData.getInt(GRANT_RESULTS);
- if (resultCode == Activity.RESULT_OK) {
- mediaProjectionData = resultData.getParcelable(PROJECTION_DATA);
- result.success(true);
- } else {
- result.success(false);
- }
- }
- });
- }
-
public static class ScreenRequestPermissionsFragment extends Fragment {
private ResultReceiver resultReceiver = null;
private int requestCode = 0;
- private final int resultCode = 0;
+ private int resultCode = 0;
private void checkSelfPermissions(boolean requestPermissions) {
if (resultCode != Activity.RESULT_OK) {
@@ -267,21 +241,22 @@ private void addDefaultAudioConstraints(MediaConstraints audioConstraints) {
* @param isFacing 'user' mapped with 'front' is true (default) 'environment' mapped with 'back'
* is false
* @param sourceId (String) use this sourceId and ignore facing mode if specified.
- * @return Pair of deviceName to VideoCapturer. Can invoke with startCapture/stopCapture null
+ * @return VideoCapturer can invoke with startCapture/stopCapture null
* if not matched camera with specified facing mode.
*/
- private Pair createVideoCapturer(
- CameraEnumerator enumerator, boolean isFacing, String sourceId, CameraEventsHandler cameraEventsHandler) {
- VideoCapturer videoCapturer;
+ private VideoCapturer createVideoCapturer(
+ CameraEnumerator enumerator, boolean isFacing, String sourceId) {
+ VideoCapturer videoCapturer = null;
+
// if sourceId given, use specified sourceId first
final String[] deviceNames = enumerator.getDeviceNames();
- if (sourceId != null && !sourceId.equals("")) {
+ if (sourceId != null) {
for (String name : deviceNames) {
if (name.equals(sourceId)) {
- videoCapturer = enumerator.createCapturer(name, cameraEventsHandler);
+ videoCapturer = enumerator.createCapturer(name, new CameraEventsHandler());
if (videoCapturer != null) {
Log.d(TAG, "create user specified camera " + name + " succeeded");
- return new Pair<>(name, videoCapturer);
+ return videoCapturer;
} else {
Log.d(TAG, "create user specified camera " + name + " failed");
break; // fallback to facing mode
@@ -294,11 +269,10 @@ private Pair createVideoCapturer(
String facingStr = isFacing ? "front" : "back";
for (String name : deviceNames) {
if (enumerator.isFrontFacing(name) == isFacing) {
- videoCapturer = enumerator.createCapturer(name, cameraEventsHandler);
+ videoCapturer = enumerator.createCapturer(name, new CameraEventsHandler());
if (videoCapturer != null) {
Log.d(TAG, "Create " + facingStr + " camera " + name + " succeeded");
-
- return new Pair<>(name, videoCapturer);
+ return videoCapturer;
} else {
Log.e(TAG, "Create " + facingStr + " camera " + name + " failed");
}
@@ -306,13 +280,12 @@ private Pair createVideoCapturer(
}
// falling back to the first available camera
- if (deviceNames.length > 0) {
- videoCapturer = enumerator.createCapturer(deviceNames[0], cameraEventsHandler);
+ if (videoCapturer == null && deviceNames.length > 0){
+ videoCapturer = enumerator.createCapturer(deviceNames[0], new CameraEventsHandler());
Log.d(TAG, "Falling back to the first available camera");
- return new Pair<>(deviceNames[0], videoCapturer);
}
- return null;
+ return videoCapturer;
}
/**
@@ -332,10 +305,6 @@ private String getFacingMode(ConstraintsMap mediaConstraints) {
* @return String value of "sourceId" optional "GUM" constraint or null if not specified.
*/
private String getSourceIdConstraint(ConstraintsMap mediaConstraints) {
- if (mediaConstraints != null
- && mediaConstraints.hasKey("deviceId")) {
- return mediaConstraints.getString("deviceId");
- }
if (mediaConstraints != null
&& mediaConstraints.hasKey("optional")
&& mediaConstraints.getType("optional") == ObjectType.Array) {
@@ -355,15 +324,13 @@ private String getSourceIdConstraint(ConstraintsMap mediaConstraints) {
return null;
}
- private ConstraintsMap getUserAudio(ConstraintsMap constraints, MediaStream stream) {
- AudioSwitchManager.instance.start();
- MediaConstraints audioConstraints = new MediaConstraints();
- String deviceId = null;
+ private AudioTrack getUserAudio(ConstraintsMap constraints) {
+ MediaConstraints audioConstraints;
if (constraints.getType("audio") == ObjectType.Boolean) {
+ audioConstraints = new MediaConstraints();
addDefaultAudioConstraints(audioConstraints);
} else {
audioConstraints = MediaConstraintsUtils.parseMediaConstraints(constraints.getMap("audio"));
- deviceId = getSourceIdConstraint(constraints.getMap("audio"));
}
Log.i(TAG, "getUserMedia(audio): " + audioConstraints);
@@ -371,36 +338,8 @@ private ConstraintsMap getUserAudio(ConstraintsMap constraints, MediaStream stre
String trackId = stateProvider.getNextTrackUUID();
PeerConnectionFactory pcFactory = stateProvider.getPeerConnectionFactory();
AudioSource audioSource = pcFactory.createAudioSource(audioConstraints);
- AudioTrack track = pcFactory.createAudioTrack(trackId, audioSource);
- stream.addTrack(track);
-
- stateProvider.putLocalTrack(track.id(), new LocalAudioTrack(track));
-
- ConstraintsMap trackParams = new ConstraintsMap();
- trackParams.putBoolean("enabled", track.enabled());
- trackParams.putString("id", track.id());
- trackParams.putString("kind", "audio");
- trackParams.putString("label", track.id());
- trackParams.putString("readyState", track.state().toString());
- trackParams.putBoolean("remote", false);
-
- if (deviceId == null) {
- if (VERSION.SDK_INT >= VERSION_CODES.M) {
- deviceId = "" + getPreferredInputDevice(preferredInput);
- }
- }
- ConstraintsMap settings = new ConstraintsMap();
- settings.putString("deviceId", deviceId);
- settings.putString("kind", "audioinput");
- settings.putBoolean("autoGainControl", true);
- settings.putBoolean("echoCancellation", true);
- settings.putBoolean("noiseSuppression", true);
- settings.putInt("channelCount", 1);
- settings.putInt("latency", 0);
- trackParams.putMap("settings", settings.toMap());
-
- return trackParams;
+ return pcFactory.createAudioTrack(trackId, audioSource);
}
/**
@@ -484,112 +423,112 @@ public void invoke(Object... args) {
void getDisplayMedia(
final ConstraintsMap constraints, final Result result, final MediaStream mediaStream) {
- if (mediaProjectionData == null) {
- screenRequestPermissions(
- new ResultReceiver(new Handler(Looper.getMainLooper())) {
- @Override
- protected void onReceiveResult(int requestCode, Bundle resultData) {
- Intent mediaProjectionData = resultData.getParcelable(PROJECTION_DATA);
- int resultCode = resultData.getInt(GRANT_RESULTS);
-
- if (resultCode != Activity.RESULT_OK) {
- resultError("screenRequestPermissions", "User didn't give permission to capture the screen.", result);
- return;
- }
- getDisplayMedia(result, mediaStream, mediaProjectionData);
- }
- });
- } else {
- getDisplayMedia(result, mediaStream, mediaProjectionData);
- }
- }
- private void getDisplayMedia(final Result result, final MediaStream mediaStream, final Intent mediaProjectionData) {
- /* Create ScreenCapture */
- VideoTrack displayTrack = null;
- VideoCapturer videoCapturer = null;
- videoCapturer =
- new OrientationAwareScreenCapturer(
- mediaProjectionData,
- new MediaProjection.Callback() {
- @Override
- public void onStop() {
- super.onStop();
- // After Huawei P30 and Android 10 version test, the onstop method is called, which will not affect the next process,
- // and there is no need to call the resulterror method
- //resultError("MediaProjection.Callback()", "User revoked permission to capture the screen.", result);
- }
- });
- if (videoCapturer == null) {
- resultError("screenRequestPermissions", "GetDisplayMediaFailed, User revoked permission to capture the screen.", result);
- return;
- }
+ screenRequestPermissions(
+ new ResultReceiver(new Handler(Looper.getMainLooper())) {
+ @Override
+ protected void onReceiveResult(int requestCode, Bundle resultData) {
- PeerConnectionFactory pcFactory = stateProvider.getPeerConnectionFactory();
- VideoSource videoSource = pcFactory.createVideoSource(true);
+ /* Create ScreenCapture */
+ int resultCode = resultData.getInt(GRANT_RESULTS);
+ Intent mediaProjectionData = resultData.getParcelable(PROJECTION_DATA);
- String threadName = Thread.currentThread().getName() + "_texture_screen_thread";
- SurfaceTextureHelper surfaceTextureHelper =
- SurfaceTextureHelper.create(threadName, EglUtils.getRootEglBaseContext());
- videoCapturer.initialize(
- surfaceTextureHelper, applicationContext, videoSource.getCapturerObserver());
+ if (resultCode != Activity.RESULT_OK) {
+ resultError("screenRequestPermissions", "User didn't give permission to capture the screen.", result);
+ return;
+ }
- WindowManager wm =
- (WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE);
+ MediaStreamTrack[] tracks = new MediaStreamTrack[1];
+ VideoCapturer videoCapturer = null;
+ videoCapturer =
+ new OrientationAwareScreenCapturer(
+ mediaProjectionData,
+ new MediaProjection.Callback() {
+ @Override
+ public void onStop() {
+ super.onStop();
+ // After Huawei P30 and Android 10 version test, the onstop method is called, which will not affect the next process,
+ // and there is no need to call the resulterror method
+ //resultError("MediaProjection.Callback()", "User revoked permission to capture the screen.", result);
+ }
+ });
+ if (videoCapturer == null) {
+ resultError("screenRequestPermissions", "GetDisplayMediaFailed, User revoked permission to capture the screen.", result);
+ return;
+ }
- Display display = wm.getDefaultDisplay();
- Point size = new Point();
- display.getRealSize(size);
+ PeerConnectionFactory pcFactory = stateProvider.getPeerConnectionFactory();
+ VideoSource videoSource = pcFactory.createVideoSource(true);
- VideoCapturerInfoEx info = new VideoCapturerInfoEx();
- info.width = size.x;
- info.height = size.y;
- info.fps = DEFAULT_FPS;
- info.isScreenCapture = true;
- info.capturer = videoCapturer;
+ String threadName = Thread.currentThread().getName() + "_texture_screen_thread";
+ SurfaceTextureHelper surfaceTextureHelper =
+ SurfaceTextureHelper.create(threadName, EglUtils.getRootEglBaseContext());
+ videoCapturer.initialize(
+ surfaceTextureHelper, applicationContext, videoSource.getCapturerObserver());
- videoCapturer.startCapture(info.width, info.height, info.fps);
- Log.d(TAG, "OrientationAwareScreenCapturer.startCapture: " + info.width + "x" + info.height + "@" + info.fps);
+ WindowManager wm =
+ (WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE);
- String trackId = stateProvider.getNextTrackUUID();
- mVideoCapturers.put(trackId, info);
+ VideoCapturerInfo info = new VideoCapturerInfo();
+ info.width = wm.getDefaultDisplay().getWidth();
+ info.height = wm.getDefaultDisplay().getHeight();
+ info.fps = DEFAULT_FPS;
+ info.isScreenCapture = true;
+ info.capturer = videoCapturer;
- displayTrack = pcFactory.createVideoTrack(trackId, videoSource);
+ videoCapturer.startCapture(info.width, info.height, info.fps);
+ Log.d(TAG, "OrientationAwareScreenCapturer.startCapture: " + info.width + "x" + info.height + "@" + info.fps);
- ConstraintsArray audioTracks = new ConstraintsArray();
- ConstraintsArray videoTracks = new ConstraintsArray();
- ConstraintsMap successResult = new ConstraintsMap();
+ String trackId = stateProvider.getNextTrackUUID();
+ mVideoCapturers.put(trackId, info);
- if (displayTrack != null) {
- String id = displayTrack.id();
+ tracks[0] = pcFactory.createVideoTrack(trackId, videoSource);
- LocalVideoTrack displayLocalVideoTrack = new LocalVideoTrack(displayTrack);
- videoSource.setVideoProcessor(displayLocalVideoTrack);
+ ConstraintsArray audioTracks = new ConstraintsArray();
+ ConstraintsArray videoTracks = new ConstraintsArray();
+ ConstraintsMap successResult = new ConstraintsMap();
- stateProvider.putLocalTrack(id, displayLocalVideoTrack);
+ for (MediaStreamTrack track : tracks) {
+ if (track == null) {
+ continue;
+ }
- ConstraintsMap track_ = new ConstraintsMap();
- String kind = displayTrack.kind();
+ String id = track.id();
- track_.putBoolean("enabled", displayTrack.enabled());
- track_.putString("id", id);
- track_.putString("kind", kind);
- track_.putString("label", kind);
- track_.putString("readyState", displayTrack.state().toString());
- track_.putBoolean("remote", false);
-
- videoTracks.pushMap(track_);
- mediaStream.addTrack(displayTrack);
- }
+ if (track instanceof AudioTrack) {
+ mediaStream.addTrack((AudioTrack) track);
+ } else {
+ mediaStream.addTrack((VideoTrack) track);
+ }
+ stateProvider.putLocalTrack(id, track);
+
+ ConstraintsMap track_ = new ConstraintsMap();
+ String kind = track.kind();
+
+ track_.putBoolean("enabled", track.enabled());
+ track_.putString("id", id);
+ track_.putString("kind", kind);
+ track_.putString("label", kind);
+ track_.putString("readyState", track.state().toString());
+ track_.putBoolean("remote", false);
+
+ if (track instanceof AudioTrack) {
+ audioTracks.pushMap(track_);
+ } else {
+ videoTracks.pushMap(track_);
+ }
+ }
- String streamId = mediaStream.getId();
+ String streamId = mediaStream.getId();
- Log.d(TAG, "MediaStream id: " + streamId);
- stateProvider.putLocalStream(streamId, mediaStream);
- successResult.putString("streamId", streamId);
- successResult.putArray("audioTracks", audioTracks.toArrayList());
- successResult.putArray("videoTracks", videoTracks.toArrayList());
- result.success(successResult.toMap());
+ Log.d(TAG, "MediaStream id: " + streamId);
+ stateProvider.putLocalStream(streamId, mediaStream);
+ successResult.putString("streamId", streamId);
+ successResult.putArray("audioTracks", audioTracks.toArrayList());
+ successResult.putArray("videoTracks", videoTracks.toArrayList());
+ result.success(successResult.toMap());
+ }
+ });
}
/**
@@ -602,23 +541,19 @@ private void getUserMedia(
Result result,
MediaStream mediaStream,
List grantedPermissions) {
- ConstraintsMap[] trackParams = new ConstraintsMap[2];
+ MediaStreamTrack[] tracks = new MediaStreamTrack[2];
// If we fail to create either, destroy the other one and fail.
if ((grantedPermissions.contains(PERMISSION_AUDIO)
- && (trackParams[0] = getUserAudio(constraints, mediaStream)) == null)
+ && (tracks[0] = getUserAudio(constraints)) == null)
|| (grantedPermissions.contains(PERMISSION_VIDEO)
- && (trackParams[1] = getUserVideo(constraints, mediaStream)) == null)) {
- for (MediaStreamTrack track : mediaStream.audioTracks) {
- if (track != null) {
- track.dispose();
- }
- }
- for (MediaStreamTrack track : mediaStream.videoTracks) {
+ && (tracks[1] = getUserVideo(constraints)) == null)) {
+ for (MediaStreamTrack track : tracks) {
if (track != null) {
track.dispose();
}
}
+
// XXX The following does not follow the getUserMedia() algorithm
// specified by
// https://www.w3.org/TR/mediacapture-streams/#dom-mediadevices-getusermedia
@@ -631,18 +566,39 @@ private void getUserMedia(
ConstraintsArray videoTracks = new ConstraintsArray();
ConstraintsMap successResult = new ConstraintsMap();
- for (ConstraintsMap trackParam : trackParams) {
- if (trackParam == null) {
+ for (MediaStreamTrack track : tracks) {
+ if (track == null) {
continue;
}
- if (trackParam.getString("kind").equals("audio")) {
- audioTracks.pushMap(trackParam);
+
+ String id = track.id();
+
+ if (track instanceof AudioTrack) {
+ mediaStream.addTrack((AudioTrack) track);
} else {
- videoTracks.pushMap(trackParam);
+ mediaStream.addTrack((VideoTrack) track);
+ }
+ stateProvider.putLocalTrack(id, track);
+
+ ConstraintsMap track_ = new ConstraintsMap();
+ String kind = track.kind();
+
+ track_.putBoolean("enabled", track.enabled());
+ track_.putString("id", id);
+ track_.putString("kind", kind);
+ track_.putString("label", kind);
+ track_.putString("readyState", track.state().toString());
+ track_.putBoolean("remote", false);
+
+ if (track instanceof AudioTrack) {
+ audioTracks.pushMap(track_);
+ } else {
+ videoTracks.pushMap(track_);
}
}
String streamId = mediaStream.getId();
+
Log.d(TAG, "MediaStream id: " + streamId);
stateProvider.putLocalStream(streamId, mediaStream);
@@ -659,7 +615,7 @@ private void getUserMedia(
*/
@Nullable
private Integer getConstrainInt(@Nullable ConstraintsMap constraintsMap, String key) {
- if (constraintsMap == null) {
+ if(constraintsMap == null){
return null;
}
@@ -672,15 +628,6 @@ private Integer getConstrainInt(@Nullable ConstraintsMap constraintsMap, String
}
}
- if (constraintsMap.getType(key) == ObjectType.String) {
- try {
- return Integer.parseInt(constraintsMap.getString(key));
- } catch (Exception e) {
- // Could be a double instead
- return (int) Math.round(Double.parseDouble(constraintsMap.getString(key)));
- }
- }
-
if (constraintsMap.getType(key) == ObjectType.Map) {
ConstraintsMap innerMap = constraintsMap.getMap(key);
if (constraintsMap.getType("ideal") == ObjectType.Number) {
@@ -691,7 +638,7 @@ private Integer getConstrainInt(@Nullable ConstraintsMap constraintsMap, String
return null;
}
- private ConstraintsMap getUserVideo(ConstraintsMap constraints, MediaStream mediaStream) {
+ private VideoTrack getUserVideo(ConstraintsMap constraints) {
ConstraintsMap videoConstraintsMap = null;
ConstraintsMap videoConstraintsMandatory = null;
if (constraints.getType("video") == ObjectType.Map) {
@@ -702,6 +649,7 @@ private ConstraintsMap getUserVideo(ConstraintsMap constraints, MediaStream medi
}
}
+
Log.i(TAG, "getUserMedia(video): " + videoConstraintsMap);
// NOTE: to support Camera2, the device should:
@@ -722,138 +670,68 @@ private ConstraintsMap getUserVideo(ConstraintsMap constraints, MediaStream medi
String facingMode = getFacingMode(videoConstraintsMap);
isFacing = facingMode == null || !facingMode.equals("environment");
- String deviceId = getSourceIdConstraint(videoConstraintsMap);
- CameraEventsHandler cameraEventsHandler = new CameraEventsHandler();
- Pair result = createVideoCapturer(cameraEnumerator, isFacing, deviceId, cameraEventsHandler);
-
- if (result == null) {
- return null;
- }
+ String sourceId = getSourceIdConstraint(videoConstraintsMap);
- deviceId = result.first;
- VideoCapturer videoCapturer = result.second;
+ VideoCapturer videoCapturer = createVideoCapturer(cameraEnumerator, isFacing, sourceId);
- if (facingMode == null && cameraEnumerator.isFrontFacing(deviceId)) {
- facingMode = "user";
- } else if (facingMode == null && cameraEnumerator.isBackFacing(deviceId)) {
- facingMode = "environment";
+ if (videoCapturer == null) {
+ return null;
}
- // else, leave facingMode as it was
PeerConnectionFactory pcFactory = stateProvider.getPeerConnectionFactory();
VideoSource videoSource = pcFactory.createVideoSource(false);
String threadName = Thread.currentThread().getName() + "_texture_camera_thread";
SurfaceTextureHelper surfaceTextureHelper =
SurfaceTextureHelper.create(threadName, EglUtils.getRootEglBaseContext());
-
- if (surfaceTextureHelper == null) {
- Log.e(TAG, "surfaceTextureHelper is null");
- return null;
- }
-
videoCapturer.initialize(
surfaceTextureHelper, applicationContext, videoSource.getCapturerObserver());
- VideoCapturerInfoEx info = new VideoCapturerInfoEx();
+ VideoCapturerInfo info = new VideoCapturerInfo();
Integer videoWidth = getConstrainInt(videoConstraintsMap, "width");
- int targetWidth = videoWidth != null
+ info.width = videoWidth != null
? videoWidth
: videoConstraintsMandatory != null && videoConstraintsMandatory.hasKey("minWidth")
- ? videoConstraintsMandatory.getInt("minWidth")
- : DEFAULT_WIDTH;
+ ? videoConstraintsMandatory.getInt("minWidth")
+ : DEFAULT_WIDTH;
Integer videoHeight = getConstrainInt(videoConstraintsMap, "height");
- int targetHeight = videoHeight != null
+ info.height = videoHeight != null
? videoHeight
: videoConstraintsMandatory != null && videoConstraintsMandatory.hasKey("minHeight")
- ? videoConstraintsMandatory.getInt("minHeight")
- : DEFAULT_HEIGHT;
+ ? videoConstraintsMandatory.getInt("minHeight")
+ : DEFAULT_HEIGHT;
Integer videoFrameRate = getConstrainInt(videoConstraintsMap, "frameRate");
- int targetFps = videoFrameRate != null
+ info.fps = videoFrameRate != null
? videoFrameRate
: videoConstraintsMandatory != null && videoConstraintsMandatory.hasKey("minFrameRate")
- ? videoConstraintsMandatory.getInt("minFrameRate")
- : DEFAULT_FPS;
-
- info.width = targetWidth;
- info.height = targetHeight;
- info.fps = targetFps;
+ ? videoConstraintsMandatory.getInt("minFrameRate")
+ : DEFAULT_FPS;
info.capturer = videoCapturer;
- info.cameraName = deviceId;
-
- // Find actual capture format.
- Size actualSize = null;
- if (videoCapturer instanceof Camera1Capturer) {
- int cameraId = Camera1Helper.getCameraId(deviceId);
- actualSize = Camera1Helper.findClosestCaptureFormat(cameraId, targetWidth, targetHeight);
- } else if (videoCapturer instanceof Camera2Capturer) {
- CameraManager cameraManager = (CameraManager) applicationContext.getSystemService(Context.CAMERA_SERVICE);
- actualSize = Camera2Helper.findClosestCaptureFormat(cameraManager, deviceId, targetWidth, targetHeight);
- }
-
- if (actualSize != null) {
- info.width = actualSize.width;
- info.height = actualSize.height;
- }
-
- info.cameraEventsHandler = cameraEventsHandler;
- videoCapturer.startCapture(targetWidth, targetHeight, targetFps);
-
- cameraEventsHandler.waitForCameraOpen();
-
+ videoCapturer.startCapture(info.width, info.height, info.fps);
String trackId = stateProvider.getNextTrackUUID();
mVideoCapturers.put(trackId, info);
mSurfaceTextureHelpers.put(trackId, surfaceTextureHelper);
+ Log.d(TAG, "changeCaptureFormat: " + info.width + "x" + info.height + "@" + info.fps);
+ videoSource.adaptOutputFormat(info.width, info.height, info.fps);
- Log.d(TAG, "Target: " + targetWidth + "x" + targetHeight + "@" + targetFps + ", Actual: " + info.width + "x" + info.height + "@" + info.fps);
-
- VideoTrack track = pcFactory.createVideoTrack(trackId, videoSource);
- mediaStream.addTrack(track);
-
- LocalVideoTrack localVideoTrack = new LocalVideoTrack(track);
- videoSource.setVideoProcessor(localVideoTrack);
-
- stateProvider.putLocalTrack(track.id(),localVideoTrack);
-
- ConstraintsMap trackParams = new ConstraintsMap();
-
- trackParams.putBoolean("enabled", track.enabled());
- trackParams.putString("id", track.id());
- trackParams.putString("kind", "video");
- trackParams.putString("label", track.id());
- trackParams.putString("readyState", track.state().toString());
- trackParams.putBoolean("remote", false);
-
- ConstraintsMap settings = new ConstraintsMap();
- settings.putString("deviceId", deviceId);
- settings.putString("kind", "videoinput");
- settings.putInt("width", info.width);
- settings.putInt("height", info.height);
- settings.putInt("frameRate", info.fps);
- if (facingMode != null) settings.putString("facingMode", facingMode);
- trackParams.putMap("settings", settings.toMap());
-
- return trackParams;
+ return pcFactory.createVideoTrack(trackId, videoSource);
}
void removeVideoCapturer(String id) {
- VideoCapturerInfoEx info = mVideoCapturers.get(id);
+ VideoCapturerInfo info = mVideoCapturers.get(id);
if (info != null) {
try {
info.capturer.stopCapture();
- if (info.cameraEventsHandler != null) {
- info.cameraEventsHandler.waitForCameraClosed();
- }
} catch (InterruptedException e) {
Log.e(TAG, "removeVideoCapturer() Failed to stop video capturer");
} finally {
info.capturer.dispose();
mVideoCapturers.remove(id);
SurfaceTextureHelper helper = mSurfaceTextureHelpers.get(id);
- if (helper != null) {
+ if (helper != null) {
helper.stopListening();
helper.dispose();
mSurfaceTextureHelpers.remove(id);
@@ -969,70 +847,207 @@ void startRecordingToFile(
mediaRecorders.append(id, mediaRecorder);
}
- void stopRecording(Integer id, String albumName) {
- try {
- MediaRecorderImpl mediaRecorder = mediaRecorders.get(id);
- if (mediaRecorder != null) {
- mediaRecorder.stopRecording();
- mediaRecorders.remove(id);
- File file = mediaRecorder.getRecordFile();
- Uri collection;
-
- if (file != null) {
- ContentValues values = new ContentValues();
- values.put(MediaStore.Video.Media.TITLE, file.getName());
- values.put(MediaStore.Video.Media.DISPLAY_NAME, file.getName());
- values.put(MediaStore.Video.Media.ALBUM, albumName);
- values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
- values.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
- values.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
-
- //Android version above 9 MediaStore uses RELATIVE_PATH
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- values.put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/" + albumName);
- values.put(MediaStore.Video.Media.IS_PENDING, 1);
-
- collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
- } else {
- //Android version 9 and below MediaStore uses DATA
- values.put(MediaStore.Video.Media.DATA, "/storage/emulated/0/Movies/" + albumName + "/" + file.getName());
+ void stopRecording(Integer id) {
+ MediaRecorderImpl mediaRecorder = mediaRecorders.get(id);
+ if (mediaRecorder != null) {
+ mediaRecorder.stopRecording();
+ mediaRecorders.remove(id);
+ File file = mediaRecorder.getRecordFile();
+ if (file != null) {
+ ContentValues values = new ContentValues(3);
+ values.put(MediaStore.Video.Media.TITLE, file.getName());
+ values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
+ values.put(MediaStore.Video.Media.DATA, file.getAbsolutePath());
+ applicationContext
+ .getContentResolver()
+ .insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
+ }
+ }
+ }
- collection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
- }
+ void hasTorch(String trackId, Result result) {
+ VideoCapturerInfo info = mVideoCapturers.get(trackId);
+ if (info == null) {
+ resultError("hasTorch", "Video capturer not found for id: " + trackId, result);
+ return;
+ }
- ContentResolver resolver = applicationContext.getContentResolver();
- Uri uriSavedMedia = resolver.insert(collection, values);
+ if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && info.capturer instanceof Camera2Capturer) {
+ CameraManager manager;
+ CameraDevice cameraDevice;
- assert uriSavedMedia != null;
- ParcelFileDescriptor pfd = resolver.openFileDescriptor(uriSavedMedia, "w");
- assert pfd != null;
- FileOutputStream out = new FileOutputStream(pfd.getFileDescriptor());
+ try {
+ Object session =
+ getPrivateProperty(
+ Camera2Capturer.class.getSuperclass(), info.capturer, "currentSession");
+ manager =
+ (CameraManager)
+ getPrivateProperty(Camera2Capturer.class, info.capturer, "cameraManager");
+ cameraDevice =
+ (CameraDevice) getPrivateProperty(session.getClass(), session, "cameraDevice");
+ } catch (NoSuchFieldWithNameException e) {
+ // Most likely the upstream Camera2Capturer class have changed
+ resultError("hasTorch", "[TORCH] Failed to get `" + e.fieldName + "` from `" + e.className + "`", result);
+ return;
+ }
- InputStream in = new FileInputStream(file);
+ boolean flashIsAvailable;
+ try {
+ CameraCharacteristics characteristics =
+ manager.getCameraCharacteristics(cameraDevice.getId());
+ flashIsAvailable = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
+ } catch (CameraAccessException e) {
+ // Should never happen since we are already accessing the camera
+ throw new RuntimeException(e);
+ }
- byte[] buf = new byte[8192];
- int len;
+ result.success(flashIsAvailable);
+ return;
+ }
- while ((len = in.read(buf)) > 0) {
- out.write(buf, 0, len);
- }
+ if (info.capturer instanceof Camera1Capturer) {
+ Camera camera;
- out.close();
- in.close();
- pfd.close();
- values.clear();
- }
+ try {
+ Object session =
+ getPrivateProperty(
+ Camera1Capturer.class.getSuperclass(), info.capturer, "currentSession");
+ camera = (Camera) getPrivateProperty(session.getClass(), session, "camera");
+ } catch (NoSuchFieldWithNameException e) {
+ // Most likely the upstream Camera1Capturer class have changed
+ resultError("hasTorch", "[TORCH] Failed to get `" + e.fieldName + "` from `" + e.className + "`", result);
+ return;
+ }
+
+ Parameters params = camera.getParameters();
+ List supportedModes = params.getSupportedFlashModes();
+
+ result.success(
+ (supportedModes == null) ? false : supportedModes.contains(Parameters.FLASH_MODE_TORCH));
+ return;
+ }
+
+ resultError("hasTorch", "[TORCH] Video capturer not compatible", result);
+ }
+
+ @RequiresApi(api = VERSION_CODES.LOLLIPOP)
+ void setTorch(String trackId, boolean torch, Result result) {
+ VideoCapturerInfo info = mVideoCapturers.get(trackId);
+ if (info == null) {
+ resultError("setTorch", "Video capturer not found for id: " + trackId, result);
+ return;
+ }
+
+ if (info.capturer instanceof Camera2Capturer) {
+ CameraCaptureSession captureSession;
+ CameraDevice cameraDevice;
+ CaptureFormat captureFormat;
+ int fpsUnitFactor;
+ Surface surface;
+ Handler cameraThreadHandler;
+
+ try {
+ Object session =
+ getPrivateProperty(
+ Camera2Capturer.class.getSuperclass(), info.capturer, "currentSession");
+ CameraManager manager =
+ (CameraManager)
+ getPrivateProperty(Camera2Capturer.class, info.capturer, "cameraManager");
+ captureSession =
+ (CameraCaptureSession)
+ getPrivateProperty(session.getClass(), session, "captureSession");
+ cameraDevice =
+ (CameraDevice) getPrivateProperty(session.getClass(), session, "cameraDevice");
+ captureFormat =
+ (CaptureFormat) getPrivateProperty(session.getClass(), session, "captureFormat");
+ fpsUnitFactor = (int) getPrivateProperty(session.getClass(), session, "fpsUnitFactor");
+ surface = (Surface) getPrivateProperty(session.getClass(), session, "surface");
+ cameraThreadHandler =
+ (Handler) getPrivateProperty(session.getClass(), session, "cameraThreadHandler");
+ } catch (NoSuchFieldWithNameException e) {
+ // Most likely the upstream Camera2Capturer class have changed
+ resultError("setTorch", "[TORCH] Failed to get `" + e.fieldName + "` from `" + e.className + "`", result);
+ return;
+ }
+
+ try {
+ final CaptureRequest.Builder captureRequestBuilder =
+ cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
+ captureRequestBuilder.set(
+ CaptureRequest.FLASH_MODE,
+ torch ? CaptureRequest.FLASH_MODE_TORCH : CaptureRequest.FLASH_MODE_OFF);
+ captureRequestBuilder.set(
+ CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
+ new Range<>(
+ captureFormat.framerate.min / fpsUnitFactor,
+ captureFormat.framerate.max / fpsUnitFactor));
+ captureRequestBuilder.set(
+ CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
+ captureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false);
+ captureRequestBuilder.addTarget(surface);
+ captureSession.setRepeatingRequest(
+ captureRequestBuilder.build(), null, cameraThreadHandler);
+ } catch (CameraAccessException e) {
+ // Should never happen since we are already accessing the camera
+ throw new RuntimeException(e);
+ }
+
+ result.success(null);
+ return;
+ }
+
+ if (info.capturer instanceof Camera1Capturer) {
+ Camera camera;
+ try {
+ Object session =
+ getPrivateProperty(
+ Camera1Capturer.class.getSuperclass(), info.capturer, "currentSession");
+ camera = (Camera) getPrivateProperty(session.getClass(), session, "camera");
+ } catch (NoSuchFieldWithNameException e) {
+ // Most likely the upstream Camera1Capturer class have changed
+ resultError("setTorch", "[TORCH] Failed to get `" + e.fieldName + "` from `" + e.className + "`", result);
+ return;
}
- } catch(Exception e){
+ Camera.Parameters params = camera.getParameters();
+ params.setFlashMode(
+ torch ? Camera.Parameters.FLASH_MODE_TORCH : Camera.Parameters.FLASH_MODE_OFF);
+ camera.setParameters(params);
+
+ result.success(null);
+ return;
}
+ resultError("setTorch", "[TORCH] Video capturer not compatible", result);
+ }
+ private Object getPrivateProperty(Class klass, Object object, String fieldName)
+ throws NoSuchFieldWithNameException {
+ try {
+ Field field = klass.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ return field.get(object);
+ } catch (NoSuchFieldException e) {
+ throw new NoSuchFieldWithNameException(klass.getName(), fieldName, e);
+ } catch (IllegalAccessException e) {
+ // Should never happen since we are calling `setAccessible(true)`
+ throw new RuntimeException(e);
+ }
}
+ private class NoSuchFieldWithNameException extends NoSuchFieldException {
+
+ String className;
+ String fieldName;
+ NoSuchFieldWithNameException(String className, String fieldName, NoSuchFieldException e) {
+ super(e.getMessage());
+ this.className = className;
+ this.fieldName = fieldName;
+ }
+ }
public void reStartCamera(IsCameraEnabled getCameraId) {
- for (Map.Entry item : mVideoCapturers.entrySet()) {
+ for (Map.Entry item : mVideoCapturers.entrySet()) {
if (!item.getValue().isScreenCapture && getCameraId.isEnabled(item.getKey())) {
item.getValue().capturer.startCapture(
item.getValue().width,
@@ -1047,42 +1062,20 @@ public interface IsCameraEnabled {
boolean isEnabled(String id);
}
- public static class VideoCapturerInfoEx extends VideoCapturerInfo {
- public CameraEventsHandler cameraEventsHandler;
- }
-
- public VideoCapturerInfoEx getCapturerInfo(String trackId) {
- return mVideoCapturers.get(trackId);
+ public class VideoCapturerInfo {
+ VideoCapturer capturer;
+ int width;
+ int height;
+ int fps;
+ boolean isScreenCapture = false;
}
@RequiresApi(api = VERSION_CODES.M)
- void setPreferredInputDevice(String deviceId) {
+ void setPreferredInputDevice(int i) {
android.media.AudioManager audioManager = ((android.media.AudioManager) applicationContext.getSystemService(Context.AUDIO_SERVICE));
final AudioDeviceInfo[] devices = audioManager.getDevices(android.media.AudioManager.GET_DEVICES_INPUTS);
- if (devices.length > 0) {
- for (int i = 0; i < devices.length; i++) {
- AudioDeviceInfo device = devices[i];
- if(deviceId.equals(AudioUtils.getAudioDeviceId(device))) {
- preferredInput = device;
- audioDeviceModule.setPreferredInputDevice(preferredInput);
- return;
- }
- }
- }
- }
-
- @RequiresApi(api = VERSION_CODES.M)
- int getPreferredInputDevice(AudioDeviceInfo deviceInfo) {
- if (deviceInfo == null) {
- return -1;
- }
- android.media.AudioManager audioManager = ((android.media.AudioManager) applicationContext.getSystemService(Context.AUDIO_SERVICE));
- final AudioDeviceInfo[] devices = audioManager.getDevices(android.media.AudioManager.GET_DEVICES_INPUTS);
- for (int i = 0; i < devices.length; i++) {
- if (devices[i].getId() == deviceInfo.getId()) {
- return i;
- }
+ if (devices.length > i) {
+ audioDeviceModule.setPreferredInputDevice(devices[i]);
}
- return -1;
}
}
diff --git a/android/src/main/java/com/cloudwebrtc/webrtc/LocalTrack.java b/android/src/main/java/com/cloudwebrtc/webrtc/LocalTrack.java
deleted file mode 100644
index 6135fdf1b3..0000000000
--- a/android/src/main/java/com/cloudwebrtc/webrtc/LocalTrack.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.cloudwebrtc.webrtc;
-
-import org.webrtc.MediaStreamTrack;
-
-public class LocalTrack {
- public LocalTrack(MediaStreamTrack track) {
- this.track = track;
- }
-
- public MediaStreamTrack track;
-
- public void dispose() {
- track.dispose();
- }
-
- public boolean enabled() {
- return track.enabled();
- }
-
- public void setEnabled(boolean enabled) {
- track.setEnabled(enabled);
- }
-
- public String id() {
- return track.id();
- }
-
- public String kind() {
- return track.kind();
- }
-}
diff --git a/android/src/main/java/com/cloudwebrtc/webrtc/MethodCallHandlerImpl.java b/android/src/main/java/com/cloudwebrtc/webrtc/MethodCallHandlerImpl.java
index 8444c0e66b..7cb6bb9260 100644
--- a/android/src/main/java/com/cloudwebrtc/webrtc/MethodCallHandlerImpl.java
+++ b/android/src/main/java/com/cloudwebrtc/webrtc/MethodCallHandlerImpl.java
@@ -2,31 +2,24 @@
import static com.cloudwebrtc.webrtc.utils.MediaConstraintsUtils.parseMediaConstraints;
+import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
-import android.media.MediaRecorder;
-import android.media.AudioAttributes;
import android.media.AudioDeviceInfo;
import android.os.Build;
import android.util.Log;
import android.util.LongSparseArray;
-import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.cloudwebrtc.webrtc.audio.AudioDeviceKind;
-import com.cloudwebrtc.webrtc.audio.AudioProcessingController;
import com.cloudwebrtc.webrtc.audio.AudioSwitchManager;
-import com.cloudwebrtc.webrtc.audio.AudioUtils;
-import com.cloudwebrtc.webrtc.audio.LocalAudioTrack;
-import com.cloudwebrtc.webrtc.audio.PlaybackSamplesReadyCallbackAdapter;
-import com.cloudwebrtc.webrtc.audio.RecordSamplesReadyCallbackAdapter;
import com.cloudwebrtc.webrtc.record.AudioChannel;
import com.cloudwebrtc.webrtc.record.FrameCapturer;
import com.cloudwebrtc.webrtc.utils.AnyThreadResult;
@@ -36,15 +29,12 @@
import com.cloudwebrtc.webrtc.utils.EglUtils;
import com.cloudwebrtc.webrtc.utils.ObjectType;
import com.cloudwebrtc.webrtc.utils.PermissionUtils;
-import com.cloudwebrtc.webrtc.utils.Utils;
-import com.cloudwebrtc.webrtc.video.VideoCapturerInfo;
-import com.cloudwebrtc.webrtc.video.camera.CameraUtils;
-import com.cloudwebrtc.webrtc.video.camera.Point;
-import com.cloudwebrtc.webrtc.video.LocalVideoTrack;
+
import com.twilio.audioswitch.AudioDevice;
import org.webrtc.AudioTrack;
import org.webrtc.CryptoOptions;
+import org.webrtc.DefaultVideoDecoderFactory;
import org.webrtc.DtmfSender;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
@@ -68,7 +58,6 @@
import org.webrtc.PeerConnectionFactory;
import org.webrtc.PeerConnectionFactory.InitializationOptions;
import org.webrtc.PeerConnectionFactory.Options;
-import org.webrtc.RtpCapabilities;
import org.webrtc.RtpSender;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
@@ -76,12 +65,10 @@
import org.webrtc.VideoTrack;
import org.webrtc.audio.AudioDeviceModule;
import org.webrtc.audio.JavaAudioDeviceModule;
-import org.webrtc.video.CustomVideoDecoderFactory;
-import org.webrtc.video.CustomVideoEncoderFactory;
import java.io.File;
+import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -101,17 +88,16 @@ public class MethodCallHandlerImpl implements MethodCallHandler, StateProvider {
static public final String TAG = "FlutterWebRTCPlugin";
private final Map mPeerConnectionObservers = new HashMap<>();
- private final BinaryMessenger messenger;
- private final Context context;
+ private BinaryMessenger messenger;
+ private Context context;
private final TextureRegistry textures;
+
private PeerConnectionFactory mFactory;
- private final Map localStreams = new HashMap<>();
- private final Map localTracks = new HashMap<>();
- private final LongSparseArray renders = new LongSparseArray<>();
- public RecordSamplesReadyCallbackAdapter recordSamplesReadyCallbackAdapter;
+ private final Map localStreams = new HashMap<>();
+ private final Map localTracks = new HashMap<>();
- public PlaybackSamplesReadyCallbackAdapter playbackSamplesReadyCallbackAdapter;
+ private LongSparseArray renders = new LongSparseArray<>();
/**
* The implementation of {@code getUserMedia} extracted into a separate file in order to reduce
@@ -119,24 +105,18 @@ public class MethodCallHandlerImpl implements MethodCallHandler, StateProvider {
*/
private GetUserMediaImpl getUserMediaImpl;
- private CameraUtils cameraUtils;
+ private final AudioSwitchManager audioSwitchManager;
private AudioDeviceModule audioDeviceModule;
- private FlutterRTCFrameCryptor frameCryptor;
-
private Activity activity;
- private CustomVideoEncoderFactory videoEncoderFactory;
-
- private CustomVideoDecoderFactory videoDecoderFactory;
-
- public AudioProcessingController audioProcessingController;
-
- MethodCallHandlerImpl(Context context, BinaryMessenger messenger, TextureRegistry textureRegistry) {
+ MethodCallHandlerImpl(Context context, BinaryMessenger messenger, TextureRegistry textureRegistry,
+ @NonNull AudioSwitchManager audioManager) {
this.context = context;
this.textures = textureRegistry;
this.messenger = messenger;
+ this.audioSwitchManager = audioManager;
}
static private void resultError(String method, String error, Result result) {
@@ -151,7 +131,7 @@ void dispose() {
mediaStream.dispose();
}
localStreams.clear();
- for (final LocalTrack track : localTracks.values()) {
+ for (final MediaStreamTrack track : localTracks.values()) {
track.dispose();
}
localTracks.clear();
@@ -160,8 +140,8 @@ void dispose() {
}
mPeerConnectionObservers.clear();
}
- private void initialize(boolean bypassVoiceProcessing, int networkIgnoreMask, boolean forceSWCodec, List forceSWCodecList,
- @Nullable ConstraintsMap androidAudioConfiguration) {
+
+ private void ensureInitialized() {
if (mFactory != null) {
return;
}
@@ -171,181 +151,33 @@ private void initialize(boolean bypassVoiceProcessing, int networkIgnoreMask, bo
.setEnableInternalTracer(true)
.createInitializationOptions());
- getUserMediaImpl = new GetUserMediaImpl(this, context);
-
- cameraUtils = new CameraUtils(getUserMediaImpl, activity);
-
- frameCryptor = new FlutterRTCFrameCryptor(this);
-
- AudioAttributes audioAttributes = null;
- if (androidAudioConfiguration != null) {
- Integer usageType = AudioUtils.getAudioAttributesUsageTypeForString(
- androidAudioConfiguration.getString("androidAudioAttributesUsageType"));
- Integer contentType = AudioUtils.getAudioAttributesContentTypeFromString(
- androidAudioConfiguration.getString("androidAudioAttributesContentType"));
-
- // Warn if one is provided without the other.
- if (usageType == null ^ contentType == null) {
- Log.w(TAG, "usageType and contentType must both be provided!");
- }
-
- if (usageType != null && contentType != null) {
- audioAttributes = new AudioAttributes.Builder()
- .setUsage(usageType)
- .setContentType(contentType)
- .build();
- }
- }
- JavaAudioDeviceModule.Builder audioDeviceModuleBuilder = JavaAudioDeviceModule.builder(context);
-
- recordSamplesReadyCallbackAdapter = new RecordSamplesReadyCallbackAdapter();
- playbackSamplesReadyCallbackAdapter = new PlaybackSamplesReadyCallbackAdapter();
-
- if(bypassVoiceProcessing) {
- audioDeviceModuleBuilder.setUseHardwareAcousticEchoCanceler(false)
- .setUseHardwareNoiseSuppressor(false)
- .setUseStereoInput(true)
- .setUseStereoOutput(true)
- .setAudioSource(MediaRecorder.AudioSource.MIC);
- } else {
- boolean useHardwareAudioProcessing = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
- boolean useLowLatency = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
- audioDeviceModuleBuilder.setUseHardwareAcousticEchoCanceler(useHardwareAudioProcessing)
- .setUseLowLatency(useLowLatency)
- .setUseHardwareNoiseSuppressor(useHardwareAudioProcessing);
- }
-
- audioDeviceModuleBuilder.setSamplesReadyCallback(recordSamplesReadyCallbackAdapter);
- audioDeviceModuleBuilder.setPlaybackSamplesReadyCallback(playbackSamplesReadyCallbackAdapter);
-
- recordSamplesReadyCallbackAdapter.addCallback(getUserMediaImpl.inputSamplesInterceptor);
-
- recordSamplesReadyCallbackAdapter.addCallback(new JavaAudioDeviceModule.SamplesReadyCallback() {
- @Override
- public void onWebRtcAudioRecordSamplesReady(JavaAudioDeviceModule.AudioSamples audioSamples) {
- for(LocalTrack track : localTracks.values()) {
- if (track instanceof LocalAudioTrack) {
- ((LocalAudioTrack) track).onWebRtcAudioRecordSamplesReady(audioSamples);
- }
- }
- }
- });
-
- if (audioAttributes != null) {
- audioDeviceModuleBuilder.setAudioAttributes(audioAttributes);
- }
-
- audioDeviceModule = audioDeviceModuleBuilder.createAudioDeviceModule();
-
- if(!bypassVoiceProcessing) {
- if(JavaAudioDeviceModule.isBuiltInNoiseSuppressorSupported()) {
- audioDeviceModule.setNoiseSuppressorEnabled(true);
- }
- }
-
-
- getUserMediaImpl.audioDeviceModule = (JavaAudioDeviceModule) audioDeviceModule;
-
- final Options options = new Options();
- options.networkIgnoreMask = networkIgnoreMask;
-
- final PeerConnectionFactory.Builder factoryBuilder = PeerConnectionFactory.builder()
- .setOptions(options);
-
// Initialize EGL contexts required for HW acceleration.
EglBase.Context eglContext = EglUtils.getRootEglBaseContext();
- videoEncoderFactory = new CustomVideoEncoderFactory(eglContext, true, true);
- videoDecoderFactory = new CustomVideoDecoderFactory(eglContext);
-
- factoryBuilder
- .setVideoEncoderFactory(videoEncoderFactory)
- .setVideoDecoderFactory(videoDecoderFactory);
-
- videoDecoderFactory.setForceSWCodec(forceSWCodec);
- videoDecoderFactory.setForceSWCodecList(forceSWCodecList);
- videoEncoderFactory.setForceSWCodec(forceSWCodec);
- videoEncoderFactory.setForceSWCodecList(forceSWCodecList);
+ getUserMediaImpl = new GetUserMediaImpl(this, context);
- audioProcessingController = new AudioProcessingController();
+ audioDeviceModule = JavaAudioDeviceModule.builder(context)
+ .setUseHardwareAcousticEchoCanceler(true)
+ .setUseHardwareNoiseSuppressor(true)
+ .setSamplesReadyCallback(getUserMediaImpl.inputSamplesInterceptor)
+ .createAudioDeviceModule();
- factoryBuilder.setAudioProcessingFactory(audioProcessingController.externalAudioProcessingFactory);
+ getUserMediaImpl.audioDeviceModule = (JavaAudioDeviceModule) audioDeviceModule;
- mFactory = factoryBuilder
+ mFactory = PeerConnectionFactory.builder()
+ .setOptions(new Options())
+ .setVideoEncoderFactory(new SimulcastVideoEncoderFactoryWrapper(eglContext, true, true))
+ .setVideoDecoderFactory(new DefaultVideoDecoderFactory(eglContext))
.setAudioDeviceModule(audioDeviceModule)
.createPeerConnectionFactory();
-
}
@Override
public void onMethodCall(MethodCall call, @NonNull Result notSafeResult) {
+ ensureInitialized();
final AnyThreadResult result = new AnyThreadResult(notSafeResult);
switch (call.method) {
- case "initialize": {
- int networkIgnoreMask = Options.ADAPTER_TYPE_UNKNOWN;
- Map options = call.argument("options");
- ConstraintsMap constraintsMap = new ConstraintsMap(options);
- if (constraintsMap.hasKey("networkIgnoreMask")
- && constraintsMap.getType("networkIgnoreMask") == ObjectType.Array) {
- final ConstraintsArray ignoredAdapters = constraintsMap.getArray("networkIgnoreMask");
- if (ignoredAdapters != null) {
- for (Object adapter : ignoredAdapters.toArrayList()) {
- switch (adapter.toString()) {
- case "adapterTypeEthernet":
- networkIgnoreMask += Options.ADAPTER_TYPE_ETHERNET;
- break;
- case "adapterTypeWifi":
- networkIgnoreMask += Options.ADAPTER_TYPE_WIFI;
- break;
- case "adapterTypeCellular":
- networkIgnoreMask += Options.ADAPTER_TYPE_CELLULAR;
- break;
- case "adapterTypeVpn":
- networkIgnoreMask += Options.ADAPTER_TYPE_VPN;
- break;
- case "adapterTypeLoopback":
- networkIgnoreMask += Options.ADAPTER_TYPE_LOOPBACK;
- break;
- case "adapterTypeAny":
- networkIgnoreMask += Options.ADAPTER_TYPE_ANY;
- break;
- }
- }
-
- }
- }
- boolean forceSWCodec = false;
- if (constraintsMap.hasKey("forceSWCodec")
- && constraintsMap.getType("forceSWCodec") == ObjectType.Boolean) {
- final boolean v = constraintsMap.getBoolean("forceSWCodec");
- forceSWCodec = v;
- }
- List forceSWCodecList = new ArrayList<>();
- if(constraintsMap.hasKey("forceSWCodecList")
- && constraintsMap.getType("forceSWCodecList") == ObjectType.Array) {
- final List