├── WKWebViewRTC
├── Assets
│ └── .gitkeep
├── Classes
│ ├── .gitkeep
│ ├── iUtils.swift
│ ├── WkWebviewCommand.swift
│ ├── WkWebviewCmdResult.swift
│ ├── iRTCTypes.swift
│ ├── iRTCDTMFSender.swift
│ ├── iMediaStreamTrack.swift
│ ├── iRTCPeerConnectionConstraints.swift
│ ├── iEnumerateDevices.swift
│ ├── iMediaStream.swift
│ ├── iGetUserMedia.swift
│ ├── iRTCAudioController.swift
│ ├── iRTCPeerConnectionConfig.swift
│ ├── iRTCDataChannel.swift
│ └── iMediaStreamRenderer.swift
└── Js
│ ├── .gitignore
│ ├── src
│ ├── RTCRtpReceiver.js
│ ├── MediaTrackSettings.js
│ ├── MediaTrackCapabilities.js
│ ├── RTCSessionDescription.js
│ ├── RTCStatsReport.js
│ ├── RTCStatsResponse.js
│ ├── RTCRtpTransceiver.js
│ ├── MediaDeviceInfo.js
│ ├── EventTarget.js
│ ├── enumerateDevices.js
│ ├── Errors.js
│ ├── RTCRtpSender.js
│ ├── IOSExec.js
│ ├── MediaDevices.js
│ ├── RTCDTMFSender.js
│ ├── MediaStreamTrack.js
│ ├── RTCIceCandidate.js
│ ├── RTCDataChannel.js
│ ├── iosrtc.js
│ ├── MediaStreamRenderer.js
│ ├── videoElementsHandler.js
│ ├── MediaStream.js
│ └── getUserMedia.js
│ └── package.json
├── _Pods.xcodeproj
├── Example
├── Podfile
├── WKWebViewRTC.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── WKWebViewRTC-Example.xcscheme
├── WKWebViewRTC.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── WKWebViewRTC
│ ├── ViewController.swift
│ ├── Images.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Info.plist
│ ├── AppDelegate.swift
│ └── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.xib
└── WKWebViewRTC_ExampleTests
│ ├── Info.plist
│ └── WKWebViewRTC_ExampleTests.swift
├── .travis.yml
├── .gitignore
├── LICENSE
├── WKWebViewRTC.podspec
└── README.md
/WKWebViewRTC/Assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Classes/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/_Pods.xcodeproj:
--------------------------------------------------------------------------------
1 | Example/Pods/Pods.xcodeproj
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | use_frameworks!
2 |
3 | target 'WKWebViewRTC_Example' do
4 | pod 'WKWebViewRTC', :path => '../'
5 |
6 | end
7 |
--------------------------------------------------------------------------------
/Example/WKWebViewRTC.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/WKWebViewRTC.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/WKWebViewRTC.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Classes/iUtils.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | import Foundation
11 |
12 | class iUtils {
13 | class func randomInt(_ min: Int, max: Int) -> Int {
14 | return Int(arc4random_uniform(UInt32(max - min))) + min
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # references:
2 | # * https://www.objc.io/issues/6-build-tools/travis-ci/
3 | # * https://github.com/supermarin/xcpretty#usage
4 |
5 | osx_image: xcode11
6 | language: objective-c
7 | # cache: cocoapods
8 | # podfile: Example/Podfile
9 | # before_install:
10 | # - gem install cocoapods # Since Travis is not always on latest version
11 | # - pod install --project-directory=Example
12 | script:
13 | - set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/WKWebViewRTC.xcworkspace -scheme WKWebViewRTC-Example -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty
14 | - pod lib lint --allow-warnings
15 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/RTCRtpReceiver.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose the RTCRtpReceiver class.
12 | */
13 | module.exports = RTCRtpReceiver;
14 |
15 |
16 | function RTCRtpReceiver(data) {
17 | data = data || {};
18 |
19 | this._pc = data.pc;
20 | this.track = data.track;
21 | }
22 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/MediaTrackSettings.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose the MediaTrackSettings class.
12 | */
13 | module.exports = MediaTrackSettings;
14 |
15 | // Ref https://www.w3.org/TR/mediacapture-streams/#dom-mediatracksettings
16 | function MediaTrackSettings(data) {
17 | data = data || {};
18 | }
19 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/MediaTrackCapabilities.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose the MediaTrackSettings class.
12 | */
13 | module.exports = MediaTrackCapabilities;
14 |
15 | // Ref https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackcapabilities
16 | function MediaTrackCapabilities(data) {
17 | data = data || {};
18 | }
19 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/RTCSessionDescription.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose the RTCSessionDescription class.
12 | */
13 | module.exports = RTCSessionDescription;
14 |
15 |
16 | function RTCSessionDescription(data) {
17 | data = data || {};
18 |
19 | // Public atributes.
20 | this.type = data.type;
21 | this.sdp = data.sdp;
22 | }
23 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wkwebview-rtc",
3 | "version": "0.0.1",
4 | "description": "WKWebViewRTC",
5 | "author": "",
6 | "contributors": [],
7 | "license": "MIT",
8 | "keywords": [
9 | "webrtc",
10 | "ios",
11 | "wkwebview"
12 | ],
13 | "homepage": "",
14 | "repository": {
15 | "type": "git",
16 | "url": ""
17 | },
18 | "scripts": {
19 | "build": "browserify src/iosrtc.js -o jsWKWebViewRTC.js -s jsWKWebViewRTC"
20 | },
21 | "dependencies": {
22 | "debug": "^4.1.1",
23 | "domready": "^1.0.8",
24 | "random-number": "^0.0.7",
25 | "yaeti": "^1.0.2"
26 | },
27 | "devDependencies": {
28 | "browserify": "^14.4.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Example/WKWebViewRTC/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // WKWebViewRTC
4 | //
5 | // Created by JustDoIt9 on 07/28/2020.
6 | // Copyright (c) 2020 JustDoIt9. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import WebKit
11 | import WKWebViewRTC
12 |
13 | class ViewController: UIViewController {
14 |
15 | @IBOutlet weak var webView: WKWebView!
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 |
20 | // Do any additional setup after loading the view, typically from a nib.
21 | WKWebViewRTC(wkwebview: webView, contentController: webView.configuration.userContentController)
22 |
23 | webView.load(URLRequest(url: URL(string: "https://cordova-rtc.github.io/cordova-plugin-iosrtc-sample/index.html")!))
24 | }
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/Example/WKWebViewRTC_ExampleTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/RTCStatsReport.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose the RTCStatsReport class.
12 | */
13 | module.exports = RTCStatsReport;
14 |
15 | function RTCStatsReport(data) {
16 | data = data || {};
17 |
18 | this.id = data.reportId;
19 | this.timestamp = data.timestamp;
20 | this.type = data.type;
21 |
22 | this.names = function () {
23 | return Object.keys(data.values);
24 | };
25 |
26 | this.stat = function (key) {
27 | return data.values[key] || '';
28 | };
29 | }
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata/
15 | *.xccheckout
16 | profile
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 |
22 | # Bundler
23 | .bundle
24 |
25 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
26 | # Carthage/Checkouts
27 |
28 | Carthage/Build
29 |
30 | # We recommend against adding the Pods directory to your .gitignore. However
31 | # you should judge for yourself, the pros and cons are mentioned at:
32 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
33 | #
34 | # Note: if you ignore the Pods directory, make sure to uncomment
35 | # `pod install` in .travis.yml
36 | #
37 | Pods/
38 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/RTCStatsResponse.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose the RTCStatsResponse class.
12 | */
13 | module.exports = RTCStatsResponse;
14 |
15 | function RTCStatsResponse(data) {
16 | data = data || [];
17 |
18 | this.result = function () {
19 | return data;
20 | };
21 |
22 | this.forEach = function (callback, thisArg) {
23 | return data.forEach(callback, thisArg);
24 | };
25 |
26 | this.namedItem = function () {
27 | return null;
28 | };
29 |
30 | this.values = function () {
31 | return data;
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/RTCRtpTransceiver.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose the RTCRtpTransceiver class.
12 | */
13 | module.exports = RTCRtpTransceiver;
14 |
15 |
16 | function RTCRtpTransceiver(data) {
17 | data = data || {};
18 |
19 | this.receiver = data.receiver;
20 | this.sender = data.sender;
21 | }
22 |
23 | // TODO
24 | // https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/currentDirection
25 | // https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiverDirection
26 | // https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/mid
27 | // https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/stop
28 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/MediaDeviceInfo.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose the MediaDeviceInfo class.
12 | */
13 | module.exports = MediaDeviceInfo;
14 |
15 | function MediaDeviceInfo(data) {
16 | data = data || {};
17 |
18 | Object.defineProperties(this, {
19 | // MediaDeviceInfo spec.
20 | deviceId: {
21 | value: data.deviceId
22 | },
23 | kind: {
24 | value: data.kind
25 | },
26 | label: {
27 | value: data.label
28 | },
29 | groupId: {
30 | value: data.groupId || ''
31 | },
32 | // SourceInfo old spec.
33 | id: {
34 | value: data.deviceId
35 | },
36 | // Deprecated, but useful until there is an alternative
37 | facing: {
38 | value: ''
39 | }
40 | });
41 | }
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Open Telecom Foundation
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Example/WKWebViewRTC/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ios-marketing",
45 | "size" : "1024x1024",
46 | "scale" : "1x"
47 | }
48 | ],
49 | "info" : {
50 | "version" : 1,
51 | "author" : "xcode"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Example/WKWebViewRTC_ExampleTests/WKWebViewRTC_ExampleTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WKWebViewRTC_ExampleTests.swift
3 | // WKWebViewRTC_ExampleTests
4 | //
5 | // Created by Harold Thétiot on 21/05/2021.
6 | // Copyright © 2021 CocoaPods. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class WKWebViewRTC_ExampleTests: XCTestCase {
12 |
13 | override func setUpWithError() throws {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 | }
16 |
17 | override func tearDownWithError() throws {
18 | // Put teardown code here. This method is called after the invocation of each test method in the class.
19 | }
20 |
21 | func testExample() throws {
22 | // This is an example of a functional test case.
23 | // Use XCTAssert and related functions to verify your tests produce the correct results.
24 | }
25 |
26 | func testPerformanceExample() throws {
27 | // This is an example of a performance test case.
28 | measure {
29 | // Put the code you want to measure the time of here.
30 | }
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/EventTarget.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Dependencies.
12 | */
13 | var
14 | YaetiEventTarget = require('yaeti').EventTarget;
15 |
16 | var EventTarget = function () {
17 | YaetiEventTarget.call(this);
18 | };
19 |
20 | EventTarget.prototype = Object.create(YaetiEventTarget.prototype);
21 | EventTarget.prototype.constructor = EventTarget;
22 |
23 | Object.defineProperties(EventTarget.prototype, Object.getOwnPropertyDescriptors(YaetiEventTarget.prototype));
24 |
25 | EventTarget.prototype.dispatchEvent = function (event) {
26 |
27 | Object.defineProperty(event, 'target', {
28 | value: this,
29 | writable: false
30 | });
31 |
32 | YaetiEventTarget.prototype.dispatchEvent.call(this, event);
33 | };
34 |
35 | /**
36 | * Expose the EventTarget class.
37 | */
38 | module.exports = EventTarget;
39 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Classes/WkWebviewCommand.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WkWebviewCommand.swift
3 | // WKWebViewRTC
4 | //
5 | // Created by Open Telecom Foundation on 2020/6/30.
6 | // Copyright © 2020 Open Telecom Foundation. All rights reserved.
7 | // The MIT License (MIT)
8 | //
9 |
10 | import Foundation
11 |
12 | class WkWebviewCommand : NSObject {
13 | var callbackId : String = ""
14 | var className : String = ""
15 | var methodName : String = ""
16 | var arguments : [Any] = []
17 |
18 | init(commandFromJson:[Any])
19 | {
20 | super.init()
21 | let count = commandFromJson.count
22 | if count < 4 {
23 | print("argument is less")
24 | return
25 | }
26 |
27 | callbackId = commandFromJson[0] as! String
28 | className = commandFromJson[1] as! String
29 | methodName = commandFromJson[2] as! String
30 | arguments = commandFromJson[3] as! [Any]
31 |
32 | }
33 |
34 | func argument(at :UInt) -> Any?{
35 | let count = arguments.count
36 | if count > at {
37 | let arg = arguments[Int(at)]
38 | if arg is NSNull
39 | {
40 | return nil
41 | }
42 | else {
43 | return arguments[Int(at)]
44 | }
45 | }
46 | return nil
47 |
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/enumerateDevices.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose the enumerateDevices function.
12 | */
13 | module.exports = enumerateDevices;
14 |
15 |
16 | /**
17 | * Dependencies.
18 | */
19 | var
20 | debug = require('debug')('iosrtc:enumerateDevices'),
21 | exec = require('./IOSExec'),
22 | MediaDeviceInfo = require('./MediaDeviceInfo'),
23 | Errors = require('./Errors');
24 |
25 |
26 | function enumerateDevices() {
27 |
28 | return new Promise(function (resolve) {
29 | function onResultOK(data) {
30 | debug('enumerateDevices() | success');
31 | resolve(getMediaDeviceInfos(data.devices));
32 | }
33 |
34 | exec.execNative(onResultOK, null, 'WKWebViewRTC', 'enumerateDevices', []);
35 | });
36 | }
37 |
38 |
39 | /**
40 | * Private API.
41 | */
42 |
43 |
44 | function getMediaDeviceInfos(devices) {
45 | debug('getMediaDeviceInfos() | [devices:%o]', devices);
46 |
47 | var id,
48 | mediaDeviceInfos = [];
49 |
50 | for (id in devices) {
51 | if (devices.hasOwnProperty(id)) {
52 | mediaDeviceInfos.push(new MediaDeviceInfo(devices[id]));
53 | }
54 | }
55 |
56 | return mediaDeviceInfos;
57 | }
58 |
--------------------------------------------------------------------------------
/Example/WKWebViewRTC/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSMicrophoneUsageDescription
6 | WKWebViewRTC requires acess to mic.
7 | NSCameraUsageDescription
8 | WKWebViewRTC requires acess to cam.
9 | CFBundleDevelopmentRegion
10 | en
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | $(PRODUCT_NAME)
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | 1.0
23 | CFBundleSignature
24 | ????
25 | CFBundleVersion
26 | 1
27 | LSRequiresIPhoneOS
28 |
29 | UILaunchStoryboardName
30 | LaunchScreen
31 | UIMainStoryboardFile
32 | Main
33 | UIRequiredDeviceCapabilities
34 |
35 | armv7
36 |
37 | UISupportedInterfaceOrientations
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationLandscapeLeft
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/Errors.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose an object with WebRTC Errors.
12 | */
13 | var Errors = module.exports = {},
14 |
15 |
16 | /**
17 | * Local variables.
18 | */
19 | IntermediateInheritor = function () {};
20 |
21 |
22 | IntermediateInheritor.prototype = Error.prototype;
23 |
24 |
25 | /**
26 | * Create error classes.
27 | */
28 | addError('InvalidStateError');
29 | addError('InvalidSessionDescriptionError');
30 | addError('InternalError');
31 | addError('MediaStreamError');
32 |
33 |
34 | function addError(name) {
35 | Errors[name] = function () {
36 | var tmp = Error.apply(this, arguments);
37 |
38 | this.name = tmp.name = name;
39 | this.message = tmp.message;
40 |
41 | Object.defineProperty(this, 'stack', {
42 | get: function () {
43 | return tmp.stack;
44 | }
45 | });
46 |
47 | return this;
48 | };
49 |
50 | Errors[name].prototype = new IntermediateInheritor();
51 | }
52 |
53 | // Detect callback usage to assist 5.0.1 to 5.0.2 migration
54 | // TODO remove on 6.0.0
55 | Errors.detectDeprecatedCallbaksUsage = function detectDeprecatedCallbaksUsage(funcName, arg) {
56 | if (
57 | typeof arg[1] === 'function' ||
58 | typeof arg[2] === 'function'
59 | ) {
60 | throw new Error('Callbacks are not supported by "' + funcName + '" anymore, use Promise instead.');
61 | }
62 | };
63 |
--------------------------------------------------------------------------------
/WKWebViewRTC.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint WKWebViewRTC.podspec' to ensure this is a
3 | # valid spec before submitting.
4 | #
5 | # Any lines starting with a # are optional, but their use is encouraged
6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = 'WKWebViewRTC'
11 | s.version = '0.5.1'
12 | s.summary = 'WebRTC library for WKWebView for Swift on iOS'
13 |
14 | # This description is used to generate tags and improve search results.
15 | # * Think: What does it do? Why did you write it? What is the focus?
16 | # * Try to keep it short, snappy and to the point.
17 | # * Write the description between the DESC delimiters below.
18 | # * Finally, don't worry about the indent, CocoaPods strips it!
19 |
20 | s.description = <<-DESC
21 | WebRTC library for WKWebView for Swift on iOS (based on cordova-plugin-iosrtc: https://github.com/cordova-rtc/cordova-plugin-iosrtc)
22 | DESC
23 |
24 | s.homepage = 'https://github.com/OpenTelecom/WKWebViewRTC'
25 | s.license = { :type => 'MIT', :file => 'LICENSE' }
26 | s.author = { 'OpenTelecom' => 'contact@OpenTele.com' }
27 | s.source = { :git => 'https://github.com/OpenTelecom/WKWebViewRTC.git', :tag => s.version.to_s }
28 |
29 | s.swift_version = '4.2'
30 | s.ios.deployment_target = '11.0'
31 |
32 | s.source_files = 'WKWebViewRTC/Classes/**/*'
33 | s.resources = 'WKWebViewRTC/Js/jsWKWebViewRTC.js'
34 |
35 | s.dependency 'GoogleWebRTC', '1.1.29229'
36 |
37 | s.xcconfig = { 'ENABLE_BITCODE' => 'NO', 'ONLY_ACTIVE_ARCH' => 'Yes' }
38 | end
39 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Classes/WkWebviewCmdResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WkWebviewCmdResult.swift
3 | // WKWebViewRTC
4 | //
5 | // Created by Open Telecom Foundation on 2020/6/30.
6 | // Copyright © 2020 Open Telecom Foundation. All rights reserved.
7 | // The MIT License (MIT)
8 | //
9 |
10 | import Foundation
11 |
12 | enum WkWebviewCmdStatus : UInt {
13 | case WkWebviewCmdStatus_NO_RESULT = 0
14 | case WkWebviewCmdStatus_OK = 1
15 | case WkWebviewCmdStatus_ERROR = 2
16 | }
17 |
18 | class WkWebviewCmdResult {
19 | var status : WkWebviewCmdStatus
20 | var message : Any?
21 | init(status: WkWebviewCmdStatus) {
22 | self.status = status
23 | message = nil
24 | }
25 | init(status: WkWebviewCmdStatus, messageAs : Any?) {
26 | self.status = status
27 | message = messageAs
28 | }
29 | init(status: WkWebviewCmdStatus, messageAsArrayBuffer : Data) {
30 | self.status = status
31 | message = messageFromArrayBuffer(data: messageAsArrayBuffer)
32 |
33 | }
34 | func messageFromArrayBuffer(data: Data) -> Any
35 | {
36 | return ["Type" : "ArrayBuffer",
37 | "Data" : data.base64EncodedString()]
38 | }
39 | func argumentAsJSON() -> String
40 | {
41 | var argInArray : NSArray
42 | if let arg = self.message {
43 | argInArray = NSArray(objects: arg)
44 | }
45 | else{
46 | argInArray = NSArray(objects: NSNull())
47 | }
48 | if let data = try? JSONSerialization.data(withJSONObject: argInArray, options: []) {
49 | guard let argJSON = String(data: data, encoding: .utf8) else {return ""}
50 | return argJSON
51 | }
52 | return ""
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/RTCRtpSender.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose the RTCRtpSender class.
12 | */
13 | module.exports = RTCRtpSender;
14 |
15 | function RTCRtpSender(data) {
16 | data = data || {};
17 |
18 | this._pc = data.pc;
19 | this.track = data.track;
20 | this.params = data.params || {};
21 | }
22 |
23 | RTCRtpSender.prototype.getParameters = function () {
24 | return this.params;
25 | };
26 |
27 | RTCRtpSender.prototype.setParameters = function (params) {
28 | Object.assign(this.params, params);
29 | return Promise.resolve(this.params);
30 | };
31 |
32 | RTCRtpSender.prototype.replaceTrack = function (withTrack) {
33 | var self = this,
34 | pc = self._pc;
35 |
36 | return new Promise(function (resolve, reject) {
37 | pc.removeTrack(self);
38 | pc.addTrack(withTrack);
39 | self.track = withTrack;
40 |
41 | // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/negotiationneeded_event
42 | var event = new Event('negotiationneeded');
43 | pc.dispatchEvent('negotiationneeded', event);
44 |
45 | pc.addEventListener("signalingstatechange", function listener() {
46 | if (pc.signalingState === "closed") {
47 | pc.removeEventListener("signalingstatechange", listener);
48 | reject();
49 | } else if (pc.signalingState === "stable") {
50 | pc.removeEventListener("signalingstatechange", listener);
51 | resolve();
52 | }
53 | });
54 | });
55 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WKWebViewRTC
2 |
3 | [](https://travis-ci.org/JustDoIt9/WKWebViewRTC)
4 | [](https://cocoapods.org/pods/WKWebViewRTC)
5 | [](https://cocoapods.org/pods/WKWebViewRTC)
6 | [](https://cocoapods.org/pods/WKWebViewRTC)
7 |
8 | WebRTC library for WKWebView for Swift on iOS (based on cordova-plugin-iosrtc: https://github.com/cordova-rtc/cordova-plugin-iosrtc)
9 |
10 | ## Example
11 |
12 | To run the example project, clone the repo, and run `pod install` from the Example directory first.
13 |
14 | ## Requirements
15 |
16 | In order to make this framework run into a iOS application some requirements must be satisfied in both development computer and target devices:
17 |
18 | * Xcode >= 11.1 (11A1027)
19 | * iOS >= 10.2 (run on lower versions at your own risk, don't report issues)
20 | * `swift-version` => 4.2
21 |
22 | ## Installation
23 |
24 | WKWebViewRTC is available through [CocoaPods](https://cocoapods.org). To install
25 | it, simply add the following line to your Podfile:
26 |
27 | ```ruby
28 | pod 'WKWebViewRTC'
29 | ```
30 |
31 | ## Dependencies
32 | * [GoogleWebRTC](https://cocoapods.org/pods/GoogleWebRTC) => v1.1.29229
33 |
34 | ## Build JS (WKWebViewRTC/Js/jsWKWebViewRTC.js)
35 |
36 | * From the WKWebViewRTC/Js run `npm i` and then `npm run-script build`
37 |
38 | ### Third-Party Supported Library
39 |
40 | * Janus => 0.7.4
41 | * JSSip => 3.1.2
42 | * Sip.js => 0.15.6
43 |
44 | ## Author
45 |
46 | Open Telecom Foundation
47 |
48 | ## License
49 |
50 | WKWebViewRTC is available under the MIT license. See the LICENSE file for more info.
51 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/IOSExec.js:
--------------------------------------------------------------------------------
1 | //
2 | // IOSExec.js
3 | // WKWebViewRTC
4 | //
5 | // Created by Open Telecom Foundation on 2020/6/30.
6 | // Copyright © 2020 Open Telecom Foundation. All rights reserved.
7 | // The MIT License (MIT)
8 | //
9 |
10 | module.exports = execNative;
11 | module.exports.execNative = execNative;
12 | module.exports.nativeCallback = nativeCallback;
13 |
14 | var exec_queue = {
15 | callbackId : Math.floor(Math.random() * 2000000),
16 | callbacks: {}
17 | };
18 |
19 | function execNative(successCallback, failCallback, service, action, actionArgs)
20 | {
21 | if (window.webkit && window.webkit.messageHandlers)
22 | {
23 | if (window.webkit.messageHandlers[service])
24 | {
25 | var callbackId = service + exec_queue.callbackId++;
26 | if (successCallback || failCallback)
27 | {
28 | exec_queue.callbacks[callbackId] = {success: successCallback, fail:failCallback};
29 | }
30 | command = [callbackId, service, action, actionArgs]
31 | window.webkit.messageHandlers[service].postMessage(JSON.stringify(command))
32 | }
33 | }
34 | }
35 |
36 | function nativeCallback(callbackId, status, argumentsAsJson)
37 | {
38 | try {
39 | var callback = exec_queue.callbacks[callbackId];
40 | if (callback)
41 | {
42 | if (status == 1 && callback.success)
43 | {
44 | callback.success.apply(null, argumentsAsJson);
45 | }
46 | else if (status == 0 && callback.fail)
47 | {
48 | callback.fail.apply(null, argumentsAsJson);
49 | }
50 | //delete exec_queue.callbacks[callbackId];
51 | }
52 | }
53 | catch (err){
54 | var msg = "Error in callbackId " + callbackId + " : " + err;
55 | console.log(msg)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Classes/iRTCTypes.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | import WebRTC
11 | struct iRTCTypes {
12 | static let signalingStates = [
13 | RTCSignalingState.stable.rawValue: "stable",
14 | RTCSignalingState.haveLocalOffer.rawValue: "have-local-offer",
15 | RTCSignalingState.haveLocalPrAnswer.rawValue: "have-local-pranswer",
16 | RTCSignalingState.haveRemoteOffer.rawValue: "have-remote-offer",
17 | RTCSignalingState.haveRemotePrAnswer.rawValue: "have-remote-pranswer",
18 | RTCSignalingState.closed.rawValue: "closed"
19 | ]
20 |
21 | static let iceGatheringStates = [
22 | RTCIceGatheringState.new.rawValue: "new",
23 | RTCIceGatheringState.gathering.rawValue: "gathering",
24 | RTCIceGatheringState.complete.rawValue: "complete"
25 | ]
26 |
27 | static let iceConnectionStates = [
28 | RTCIceConnectionState.new.rawValue: "new",
29 | RTCIceConnectionState.checking.rawValue: "checking",
30 | RTCIceConnectionState.connected.rawValue: "connected",
31 | RTCIceConnectionState.completed.rawValue: "completed",
32 | RTCIceConnectionState.failed.rawValue: "failed",
33 | RTCIceConnectionState.disconnected.rawValue: "disconnected",
34 | RTCIceConnectionState.closed.rawValue: "closed"
35 | ]
36 |
37 | static let dataChannelStates = [
38 | RTCDataChannelState.connecting.rawValue: "connecting",
39 | RTCDataChannelState.open.rawValue: "open",
40 | RTCDataChannelState.closing.rawValue: "closing",
41 | RTCDataChannelState.closed.rawValue: "closed"
42 | ]
43 |
44 | static let mediaStreamTrackStates = [
45 | RTCMediaStreamTrackState.live.rawValue: "live",
46 | RTCMediaStreamTrackState.ended.rawValue: "ended"
47 | ]
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Classes/iRTCDTMFSender.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | import Foundation
11 | import WebRTC
12 |
13 | class iRTCDTMFSender : NSObject {
14 | var rtcRtpSender: RTCRtpSender?
15 | var eventListener: ((_ data: NSDictionary) -> Void)?
16 |
17 | /**
18 | * Constructor for pc.createDTMFSender().
19 | */
20 | init(
21 | rtcPeerConnection: RTCPeerConnection,
22 | track: RTCMediaStreamTrack,
23 | streamId: String,
24 | eventListener: @escaping (_ data: NSDictionary) -> Void
25 | ) {
26 | NSLog("iRTCDTMFSender#init()")
27 |
28 | self.eventListener = eventListener
29 |
30 | // TODO check if new rtcRtpSender can be used one Unified-Plan merged
31 | //let streamIds = [streamId]
32 | //self.rtcRtpSender = rtcPeerConnection.add(track, streamIds: streamIds);
33 | self.rtcRtpSender = rtcPeerConnection.senders[0]
34 |
35 | if self.rtcRtpSender == nil {
36 | NSLog("iRTCDTMFSender#init() | rtcPeerConnection.createDTMFSenderForTrack() failed")
37 | return
38 | }
39 | }
40 |
41 | deinit {
42 | NSLog("iRTCDTMFSender#deinit()")
43 | }
44 |
45 | func run() {
46 | NSLog("iRTCDTMFSender#run()")
47 | }
48 |
49 | func insertDTMF(_ tones: String, duration: TimeInterval, interToneGap: TimeInterval) {
50 | NSLog("iRTCDTMFSender#insertDTMF()")
51 |
52 | let dtmfSender = self.rtcRtpSender?.dtmfSender
53 | let durationMs = duration / 100
54 | let interToneGapMs = interToneGap / 100
55 | let result = dtmfSender!.insertDtmf(tones, duration: durationMs, interToneGap: interToneGapMs)
56 | if !result {
57 | NSLog("iRTCDTMFSender#indertDTMF() | RTCDTMFSender#indertDTMF() failed")
58 | }
59 | }
60 |
61 | /**
62 | * Methods inherited from RTCDTMFSenderDelegate.
63 | */
64 | func toneChange(_ tone: String) {
65 | NSLog("iRTCDTMFSender | tone change [tone:%@]", tone)
66 |
67 | if self.eventListener != nil {
68 | self.eventListener!([
69 | "type": "tonechange",
70 | "tone": tone
71 | ])
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/MediaDevices.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose the MediaDevices class.
12 | */
13 | module.exports = MediaDevices;
14 |
15 | /**
16 | * Spec: https://w3c.github.io/mediacapture-main/#dom-mediadevices
17 | */
18 |
19 | /**
20 | * Dependencies.
21 | */
22 | var
23 | EventTarget = require('./EventTarget'),
24 | getUserMedia = require('./getUserMedia'),
25 | enumerateDevices = require('./enumerateDevices');
26 |
27 | function MediaDevices(data) {
28 |
29 | //ondevicechange
30 | //enumerateDevices
31 | //getDisplayMedia
32 | //getSupportedConstraints
33 | //getUserMedia
34 |
35 | var self = this;
36 |
37 | // Make this an EventTarget.
38 | EventTarget.call(self);
39 |
40 | data = data || {};
41 | }
42 |
43 | MediaDevices.prototype = Object.create(EventTarget.prototype);
44 | MediaDevices.prototype.constructor = MediaDevices;
45 |
46 | MediaDevices.prototype.getUserMedia = function (constraints) {
47 | return getUserMedia(constraints);
48 | };
49 |
50 | MediaDevices.prototype.enumerateDevices = function () {
51 | return enumerateDevices();
52 | };
53 |
54 | MediaDevices.prototype.getSupportedConstraints = function () {
55 | return {
56 | // Supported
57 | height: true,
58 | width: true,
59 | deviceId: true,
60 | frameRate: true,
61 | sampleRate: true,
62 | aspectRatio: true,
63 | // Not Supported
64 | autoGainControl: false,
65 | brightness: false,
66 | channelCount: false,
67 | colorTemperature: false,
68 | contrast: false,
69 | echoCancellation: false,
70 | exposureCompensation: false,
71 | exposureMode: false,
72 | exposureTime: false,
73 | facingMode: true,
74 | focusDistance: false,
75 | focusMode: false,
76 | groupId: false,
77 | iso: false,
78 | latency: false,
79 | noiseSuppression: false,
80 | pointsOfInterest: false,
81 | resizeMode: false,
82 | sampleSize: false,
83 | saturation: false,
84 | sharpness: false,
85 | torch: false,
86 | whiteBalanceMode: false,
87 | zoom: false
88 | };
89 | };
90 |
91 |
--------------------------------------------------------------------------------
/Example/WKWebViewRTC/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // WKWebViewRTC
4 | //
5 | // Created by JustDoIt9 on 07/28/2020.
6 | // Copyright (c) 2020 JustDoIt9. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | private func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/Example/WKWebViewRTC/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/RTCDTMFSender.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose the RTCDTMFSender class.
12 | */
13 | module.exports = RTCDTMFSender;
14 |
15 |
16 | /**
17 | * Dependencies.
18 | */
19 | var
20 | debug = require('debug')('iosrtc:RTCDTMFSender'),
21 | debugerror = require('debug')('iosrtc:ERROR:RTCDTMFSender'),
22 | exec = require('./IOSExec'),
23 | randomNumber = require('random-number').generator({min: 10000, max: 99999, integer: true}),
24 | EventTarget = require('./EventTarget');
25 |
26 |
27 | debugerror.log = console.warn.bind(console);
28 |
29 |
30 | function RTCDTMFSender(peerConnection, track) {
31 | var self = this;
32 |
33 | // Make this an EventTarget.
34 | EventTarget.call(this);
35 |
36 | debug('new() | [track:%o]', track);
37 |
38 | // Public atributes (accessed as read-only properties)
39 | this._track = track;
40 | // TODO: read these from the properties exposed in Swift?
41 | this._duration = 100;
42 | this._interToneGap = 70;
43 | this._toneBuffer = '';
44 |
45 | // Private attributes.
46 | this.peerConnection = peerConnection;
47 | this.dsId = randomNumber();
48 |
49 | function onResultOK(data) {
50 | onEvent.call(self, data);
51 | }
52 |
53 | exec.execNative(onResultOK, null, 'WKWebViewRTC', 'RTCPeerConnection_createDTMFSender', [this.peerConnection.pcId, this.dsId, this._track.id]);
54 |
55 | }
56 |
57 | RTCDTMFSender.prototype = Object.create(EventTarget.prototype);
58 | RTCDTMFSender.prototype.constructor = RTCDTMFSender;
59 |
60 | Object.defineProperty(RTCDTMFSender.prototype, 'canInsertDTMF', {
61 | get: function () {
62 | // TODO: check if it's muted or stopped?
63 | return this._track && this._track.kind === 'audio' && this._track.enabled;
64 | }
65 | });
66 |
67 |
68 | Object.defineProperty(RTCDTMFSender.prototype, 'track', {
69 | get: function () {
70 | return this._track;
71 | }
72 | });
73 |
74 |
75 | Object.defineProperty(RTCDTMFSender.prototype, 'duration', {
76 | get: function () {
77 | return this._duration;
78 | }
79 | });
80 |
81 |
82 | Object.defineProperty(RTCDTMFSender.prototype, 'interToneGap', {
83 | get: function () {
84 | return this._interToneGap;
85 | }
86 | });
87 |
88 |
89 | Object.defineProperty(RTCDTMFSender.prototype, 'toneBuffer', {
90 | get: function () {
91 | return this._toneBuffer;
92 | }
93 | });
94 |
95 |
96 | RTCDTMFSender.prototype.insertDTMF = function (tones, duration, interToneGap) {
97 | if (isClosed.call(this)) {
98 | return;
99 | }
100 |
101 | debug('insertDTMF() | [tones:%o, duration:%o, interToneGap:%o]', tones, duration, interToneGap);
102 |
103 | if (!tones) {
104 | return;
105 | }
106 |
107 | this._duration = duration || 100;
108 | this._interToneGap = interToneGap || 70;
109 |
110 | var self = this;
111 |
112 | function onResultOK(data) {
113 | onEvent.call(self, data);
114 | }
115 |
116 | exec.execNative(onResultOK, null, 'WKWebViewRTC', 'RTCPeerConnection_RTCDTMFSender_insertDTMF', [this.peerConnection.pcId, this.dsId, tones, this._duration, this._interToneGap]);
117 | };
118 |
119 |
120 | /**
121 | * Private API.
122 | */
123 |
124 |
125 | function isClosed() {
126 | return this.peerConnection.signalingState === 'closed';
127 | }
128 |
129 |
130 | function onEvent(data) {
131 | var type = data.type,
132 | event;
133 |
134 | debug('onEvent() | [type:%s, data:%o]', type, data);
135 |
136 | if (type === 'tonechange') {
137 | event = new Event('tonechange');
138 | event.tone = data.tone;
139 | this.dispatchEvent(event);
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/Example/WKWebViewRTC/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/Example/WKWebViewRTC.xcodeproj/xcshareddata/xcschemes/WKWebViewRTC-Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
51 |
52 |
53 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
76 |
78 |
84 |
85 |
86 |
87 |
93 |
95 |
101 |
102 |
103 |
104 |
106 |
107 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Classes/iMediaStreamTrack.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | import Foundation
11 | import WebRTC
12 |
13 | class iMediaStreamTrack : NSObject {
14 | var rtcMediaStreamTrack: RTCMediaStreamTrack
15 | var id: String
16 | var kind: String
17 | var eventListener: ((_ data: NSDictionary) -> Void)?
18 | var eventListenerForEnded: (() -> Void)?
19 | var lostStates = Array()
20 | var renders: [String : iMediaStreamRenderer]
21 |
22 | init(rtcMediaStreamTrack: RTCMediaStreamTrack, trackId: String? = nil) {
23 | NSLog("iMediaStreamTrack#init()")
24 |
25 | self.rtcMediaStreamTrack = rtcMediaStreamTrack
26 |
27 | if (trackId == nil) {
28 | // Handle possible duplicate remote trackId with janus or short duplicate name
29 | // See: https://github.com/cordova-rtc/cordova-plugin-iosrtc/issues/432
30 | if (rtcMediaStreamTrack.trackId.count<36) {
31 | self.id = rtcMediaStreamTrack.trackId + "_" + UUID().uuidString;
32 | } else {
33 | self.id = rtcMediaStreamTrack.trackId;
34 | }
35 | } else {
36 | self.id = trackId!;
37 | }
38 |
39 | self.kind = rtcMediaStreamTrack.kind
40 | self.renders = [:]
41 | }
42 |
43 | deinit {
44 | NSLog("iMediaStreamTrack#deinit()")
45 | stop()
46 | }
47 |
48 | func run() {
49 | NSLog("iMediaStreamTrack#run() [kind:%@, id:%@]", String(self.kind), String(self.id))
50 | }
51 |
52 | func getReadyState() -> String {
53 | switch self.rtcMediaStreamTrack.readyState {
54 | case RTCMediaStreamTrackState.live:
55 | return "live"
56 | case RTCMediaStreamTrackState.ended:
57 | return "ended"
58 | default:
59 | return "ended"
60 | }
61 | }
62 |
63 | func getJSON() -> NSDictionary {
64 | return [
65 | "id": self.id,
66 | "kind": self.kind,
67 | "trackId": self.rtcMediaStreamTrack.trackId,
68 | "enabled": self.rtcMediaStreamTrack.isEnabled ? true : false,
69 | "capabilities": self.rtcMediaStreamTrack.capabilities,
70 | "readyState": self.getReadyState()
71 | ]
72 | }
73 |
74 | func setListener(
75 | _ eventListener: @escaping (_ data: NSDictionary) -> Void,
76 | eventListenerForEnded: @escaping () -> Void
77 | ) {
78 | if(self.eventListener != nil){
79 | NSLog("iMediaStreamTrack#setListener():Error Listener already Set [kind:%@, id:%@]", String(self.kind), String(self.id));
80 | return;
81 | }
82 |
83 | NSLog("iMediaStreamTrack#setListener() [kind:%@, id:%@]", String(self.kind), String(self.id))
84 |
85 | self.eventListener = eventListener
86 | self.eventListenerForEnded = eventListenerForEnded
87 |
88 | for readyState in self.lostStates {
89 | self.eventListener!([
90 | "type": "statechange",
91 | "readyState": readyState,
92 | "enabled": self.rtcMediaStreamTrack.isEnabled ? true : false
93 | ])
94 |
95 | if readyState == "ended" {
96 | if(self.eventListenerForEnded != nil) {
97 | self.eventListenerForEnded!()
98 | }
99 | }
100 | }
101 | self.lostStates.removeAll()
102 | }
103 |
104 | func setEnabled(_ value: Bool) {
105 | NSLog("iMediaStreamTrack#setEnabled() [kind:%@, id:%@, value:%@]",
106 | String(self.kind), String(self.id), String(value))
107 |
108 | if (self.rtcMediaStreamTrack.isEnabled != value) {
109 | self.rtcMediaStreamTrack.isEnabled = value
110 | if (value) {
111 | self.rtcMediaStreamTrack.videoCaptureController?.startCapture()
112 | }else {
113 | self.rtcMediaStreamTrack.videoCaptureController?.stopCapture()
114 | }
115 | }
116 | }
117 |
118 | func switchCamera() {
119 | self.rtcMediaStreamTrack.videoCaptureController?.switchCamera()
120 | }
121 |
122 | func registerRender(render: iMediaStreamRenderer) {
123 | if let exist = self.renders[render.id] {
124 | _ = exist
125 | } else {
126 | self.renders[render.id] = render
127 | }
128 | }
129 |
130 | func unregisterRender(render: iMediaStreamRenderer) {
131 | self.renders.removeValue(forKey: render.id);
132 | }
133 |
134 | func stop() {
135 | NSLog("iMediaStreamTrack#stop() [kind:%@, id:%@]", String(self.kind), String(self.id))
136 |
137 | self.rtcMediaStreamTrack.videoCaptureController?.stopCapture();
138 |
139 | // Let's try setEnabled(false), but it also fails.
140 | self.rtcMediaStreamTrack.isEnabled = false
141 | // eventListener could be null if the track is never used
142 | if(self.eventListener != nil){
143 | self.eventListener!([
144 | "type": "statechange",
145 | "readyState": "ended",
146 | "enabled": self.rtcMediaStreamTrack.isEnabled ? true : false
147 | ])
148 | }
149 |
150 | for (_, render) in self.renders {
151 | render.stop()
152 | }
153 | self.renders.removeAll();
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/MediaStreamTrack.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose the MediaStreamTrack class.
12 | */
13 | module.exports = MediaStreamTrack;
14 |
15 |
16 | /**
17 | * Spec: http://w3c.github.io/mediacapture-main/#mediastreamtrack
18 | */
19 |
20 |
21 | /**
22 | * Dependencies.
23 | */
24 | var
25 | debug = require('debug')('iosrtc:MediaStreamTrack'),
26 | exec = require('./IOSExec'),
27 | enumerateDevices = require('./enumerateDevices'),
28 | MediaTrackCapabilities = require('./MediaTrackCapabilities'),
29 | MediaTrackSettings = require('./MediaTrackSettings'),
30 | EventTarget = require('./EventTarget');
31 |
32 | // Save original MediaStreamTrack
33 | var originalMediaStreamTrack = window.MediaStreamTrack || function dummyMediaStreamTrack() {};
34 |
35 | function newMediaStreamTrackId() {
36 | return window.crypto.getRandomValues(new Uint32Array(4)).join('-');
37 | }
38 |
39 | function MediaStreamTrack(dataFromEvent) {
40 | if (!dataFromEvent) {
41 | throw new Error('Illegal constructor');
42 | }
43 |
44 | debug('new() | [dataFromEvent:%o]', dataFromEvent);
45 |
46 | var self = this;
47 |
48 | // Make this an EventTarget.
49 | EventTarget.call(this);
50 |
51 | // Public atributes.
52 | this.id = dataFromEvent.id; // NOTE: It's a string.
53 | this.kind = dataFromEvent.kind;
54 | this.label = dataFromEvent.label;
55 | this.muted = false; // TODO: No "muted" property in ObjC API.
56 | this.capabilities = dataFromEvent.capabilities;
57 | this.readyState = dataFromEvent.readyState;
58 |
59 | // Private attributes.
60 | this._enabled = dataFromEvent.enabled;
61 | this._ended = false;
62 |
63 | function onResultOK(data) {
64 | onEvent.call(self, data);
65 | }
66 |
67 | exec.execNative(onResultOK, null, 'WKWebViewRTC', 'MediaStreamTrack_setListener', [this.id]);
68 | }
69 |
70 | MediaStreamTrack.prototype = Object.create(EventTarget.prototype);
71 | MediaStreamTrack.prototype.constructor = MediaStreamTrack;
72 |
73 | // Static reference to original MediaStreamTrack
74 | MediaStreamTrack.originalMediaStreamTrack = originalMediaStreamTrack;
75 |
76 | // Setters.
77 | Object.defineProperty(MediaStreamTrack.prototype, 'enabled', {
78 | get: function () {
79 | return this._enabled;
80 | },
81 | set: function (value) {
82 | debug('enabled = %s', !!value);
83 |
84 | this._enabled = !!value;
85 | exec.execNative(null, null, 'WKWebViewRTC', 'MediaStreamTrack_setEnabled', [this.id, this._enabled]);
86 | }
87 | });
88 |
89 | MediaStreamTrack.prototype.getConstraints = function () {
90 | return {};
91 | };
92 |
93 | MediaStreamTrack.prototype.applyConstraints = function () {
94 | return Promise.reject(new Error('applyConstraints is not implemented.'));
95 | };
96 |
97 | MediaStreamTrack.prototype.clone = function () {
98 | var newTrackId = newMediaStreamTrackId();
99 |
100 | exec.execNative(null, null, 'WKWebViewRTC', 'MediaStreamTrack_clone', [this.id, newTrackId]);
101 |
102 | return new MediaStreamTrack({
103 | id: newTrackId,
104 | kind: this.kind,
105 | label: this.label,
106 | readyState: this.readyState,
107 | enabled: this.enabled
108 | });
109 | };
110 |
111 | MediaStreamTrack.prototype.getCapabilities = function () {
112 | //throw new Error('Not implemented.');
113 | // SHAM
114 | return new MediaTrackCapabilities(this.capabilities);
115 | };
116 |
117 | MediaStreamTrack.prototype.getSettings = function () {
118 | //throw new Error('Not implemented.');
119 | // SHAM
120 | return new MediaTrackSettings();
121 | };
122 |
123 | MediaStreamTrack.prototype.stop = function () {
124 | debug('stop()');
125 |
126 | if (this._ended) {
127 | return;
128 | }
129 |
130 | exec.execNative(null, null, 'WKWebViewRTC', 'MediaStreamTrack_stop', [this.id]);
131 | };
132 |
133 |
134 | // TODO: API methods and events.
135 |
136 |
137 | /**
138 | * Class methods.
139 | */
140 |
141 |
142 | MediaStreamTrack.getSources = function () {
143 | debug('getSources()');
144 |
145 | return enumerateDevices.apply(this, arguments);
146 | };
147 |
148 |
149 | /**
150 | * Private API.
151 | */
152 |
153 |
154 | function onEvent(data) {
155 | var type = data.type;
156 |
157 | debug('onEvent() | [type:%s, data:%o]', type, data);
158 |
159 | switch (type) {
160 | case 'statechange':
161 | this.readyState = data.readyState;
162 | this._enabled = data.enabled;
163 |
164 | switch (data.readyState) {
165 | case 'initializing':
166 | break;
167 | case 'live':
168 | break;
169 | case 'ended':
170 | this._ended = true;
171 | this.dispatchEvent(new Event('ended'));
172 | break;
173 | case 'failed':
174 | break;
175 | }
176 | break;
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Classes/iRTCPeerConnectionConstraints.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | import Foundation
11 | import WebRTC
12 |
13 | class iRTCPeerConnectionConstraints {
14 | fileprivate var constraints: RTCMediaConstraints
15 |
16 | init(pcConstraints: NSDictionary?) {
17 | NSLog("iRTCPeerConnectionConstraints#init()")
18 | var mandatoryConstraints: [String : String] = [:]
19 | var optionalConstraints: [String : String] = [:]
20 |
21 | // Handle constraints at the root of pcConstraints.mandatory
22 | let _mandatoryConstraints = pcConstraints?.object(forKey: "mandatory") as? NSDictionary
23 | if _mandatoryConstraints != nil {
24 | mandatoryConstraints = iRTCPeerConnectionConstraints.parseConstraints(constraints: _mandatoryConstraints!)
25 | }
26 |
27 | // Handle constraints at the root of pcConstraints.optional
28 | let _optionalConstraints = pcConstraints?.object(forKey: "optional") as? NSArray
29 | if _optionalConstraints != nil {
30 | optionalConstraints = iRTCPeerConnectionConstraints.parseOptionalConstraints(optionalConstraints: _optionalConstraints!)
31 | }
32 |
33 | // Handle constraints at the root of pcConstraints
34 | if pcConstraints != nil && _optionalConstraints == nil && _mandatoryConstraints == nil {
35 | mandatoryConstraints = iRTCPeerConnectionConstraints.parseConstraints(constraints: pcConstraints!)
36 | }
37 |
38 | NSLog("iRTCPeerConnectionConstraints#init() | [mandatoryConstraints:%@, optionalConstraints:%@]",
39 | mandatoryConstraints, optionalConstraints)
40 |
41 | self.constraints = RTCMediaConstraints.init(
42 | mandatoryConstraints: mandatoryConstraints,
43 | optionalConstraints: optionalConstraints
44 | )
45 | }
46 |
47 | // Custom proprietary constrains in libwebrtc
48 | fileprivate static let allowedOptionalConstraints : Array = [
49 | // Temporary pseudo-constraints used to enable DTLS-SRT
50 | "DtlsSrtpKeyAgreement",
51 | // Constraint to enable IPv6
52 | "googIPv6",
53 | // Constraint to enable combined audio+video bandwidth estimation.
54 | "googImprovedWifiBwe",
55 | // Temporary pseudo-constraint for enabling DSCP
56 | "googDscp",
57 | // If enabled, will lower outgoing video quality and video resolution
58 | "googCpuOveruseDetection",
59 | // Min CPU load (percents), used in pair with
60 | "googCpuUnderuseThreshold",
61 | // Max CPU (percents), used in pair with googCpuOveruseDetection
62 | "googCpuOveruseThreshold",
63 | // Cause video track to be automatically disabled if the detected bandwidth drops below "min bitrate"
64 | "googSuspendBelowMinBitrate",
65 | //"googScreencastMinBitrate"
66 | ]
67 |
68 | fileprivate static func parseOptionalConstraints(optionalConstraints: NSArray) -> [String : String] {
69 | var _constraints : [String : String] = [:]
70 |
71 | for optionalConstraint in optionalConstraints {
72 | if (optionalConstraint is NSDictionary) {
73 | for (key, value) in optionalConstraint as! NSDictionary {
74 | let finalKey: String = key as! String;
75 |
76 | // Parse only allowedOptionalConstraints
77 | if (allowedOptionalConstraints.firstIndex(of: finalKey) != nil) {
78 | if value is Int32 {
79 | _constraints[finalKey] = String(describing: value);
80 | } else if value is Bool {
81 | _constraints[finalKey] = value as! Bool ? "true" : "false"
82 | } else if value is String {
83 | _constraints[finalKey] = (value as! String)
84 | }
85 | }
86 | }
87 | }
88 | }
89 |
90 | return _constraints;
91 | }
92 |
93 | // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createOffer#RTCOfferOptions_dictionary
94 | // TODO TODO voiceActivityDetection This option defaults to true
95 | fileprivate static let allowedConstraints : Array = [
96 | "IceRestart",
97 | "OfferToReceiveVideo",
98 | "OfferToReceiveAudio",
99 | "voiceActivityDetection"
100 | ]
101 |
102 | fileprivate static func parseConstraints(constraints: NSDictionary) -> [String : String] {
103 | var _constraints : [String : String] = [:]
104 |
105 | for (key, value) in constraints {
106 | var finalValue: String,
107 | finalKey: String = key as! String;
108 |
109 | if value is Bool {
110 | finalValue = value as! Bool ? "true" : "false"
111 | } else if value is String {
112 | finalValue = value as! String
113 | } else {
114 | continue
115 | }
116 |
117 | // Handle Spec for offerToReceiveAudio|offerToReceiveVideo but
118 | // libwebrtc still use OfferToReceiveAudio|OfferToReceiveVideo
119 | if (finalKey == "offerToReceiveAudio") {
120 | finalKey = "OfferToReceiveAudio";
121 | } else if (finalKey == "offerToReceiveVideo") {
122 | finalKey = "OfferToReceiveVideo";
123 | } else if (finalKey == "iceRestart") {
124 | finalKey = "IceRestart";
125 | }
126 |
127 | // Filter to avoid injection
128 | if (allowedConstraints.firstIndex(of: finalKey) != nil) {
129 | _constraints[finalKey] = finalValue
130 | }
131 | }
132 |
133 | return _constraints;
134 | }
135 |
136 | deinit {
137 | NSLog("iRTCPeerConnectionConstraints#deinit()")
138 | }
139 |
140 | func getConstraints() -> RTCMediaConstraints {
141 | NSLog("iRTCPeerConnectionConstraints#getConstraints()")
142 |
143 | return self.constraints
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Classes/iEnumerateDevices.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | import Foundation
11 | import AVFoundation
12 | import WebRTC
13 |
14 | /**
15 | * Doc: https://developer.apple.com/library/mac/documentation/AVFoundation/Reference/AVCaptureDevice_Class/index.html
16 | */
17 |
18 | struct MediaDeviceInfo {
19 | let deviceId, groupId, kind, label: String
20 | init(deviceId: String, kind: String, label: String) {
21 | self.deviceId = deviceId
22 | self.groupId = ""
23 | self.kind = kind
24 | self.label = label
25 | }
26 | }
27 |
28 | // Helper function inserted by Swift 4.2 migrator.
29 | fileprivate func convertFromAVMediaType(_ input: AVMediaType) -> String {
30 | return input.rawValue
31 | }
32 |
33 | class iEnumerateDevices {
34 | class func call(_ callback: (_ data: NSDictionary) -> Void) {
35 | NSLog("iEnumerateDevices#call()")
36 |
37 | let audioDevices: [MediaDeviceInfo] = getAllAudioDevices()
38 | let videoDevices: [MediaDeviceInfo] = getAllVideoDevices()
39 | let allDevices = videoDevices + audioDevices;
40 |
41 | let json: NSMutableDictionary = [
42 | "devices": NSMutableArray()
43 | ]
44 |
45 | // Casting to NSMutableDictionary
46 | for device: MediaDeviceInfo in allDevices {
47 | (json["devices"] as! NSMutableArray).add([
48 | "deviceId": device.deviceId,
49 | "kind": device.kind,
50 | "label": device.label,
51 | "groupId": device.groupId
52 | ])
53 | }
54 |
55 | print("DEVICES => ", json)
56 | callback(json as NSDictionary)
57 | }
58 | }
59 |
60 | fileprivate func getAllVideoDevices() -> [MediaDeviceInfo] {
61 |
62 | var videoDevicesArr : [MediaDeviceInfo] = []
63 | var deviceTypes: [AVCaptureDevice.DeviceType] = [.builtInTelephotoCamera, .builtInWideAngleCamera]
64 | if #available(iOS 10.2, *) {
65 | deviceTypes.append(.builtInDualCamera)
66 |
67 | // Disabled tp prevent duplicate front camera
68 | //if #available(iOS 11.1, *) {
69 | // deviceTypes.append(.builtInTrueDepthCamera)
70 | //}
71 | }
72 |
73 | let videoDevices: [AVCaptureDevice] = AVCaptureDevice.DiscoverySession.init(
74 | deviceTypes: deviceTypes,
75 | mediaType: AVMediaType.video,
76 | position: AVCaptureDevice.Position.unspecified
77 | ).devices
78 |
79 | for device: AVCaptureDevice in videoDevices {
80 | var facing: String
81 | var facingLabel: String;
82 | let hasAudio = device.hasMediaType(AVMediaType(rawValue: convertFromAVMediaType(AVMediaType.audio)))
83 | let hasVideo = device.hasMediaType(AVMediaType(rawValue: convertFromAVMediaType(AVMediaType.video)))
84 |
85 | switch device.position {
86 | case AVCaptureDevice.Position.unspecified:
87 | facing = "unknown"
88 | facingLabel = "";
89 | case AVCaptureDevice.Position.back:
90 | facing = "back"
91 | facingLabel = "Back Camera"
92 | case AVCaptureDevice.Position.front:
93 | facing = "front"
94 | facingLabel = "Front Camera"
95 | }
96 |
97 | if device.isConnected == false || (hasAudio == false && hasVideo == false) {
98 | continue
99 | }
100 |
101 | NSLog("- device [uniqueID:'%@', localizedName:'%@', facing:%@, audio:%@, video:%@, connected:%@]",
102 | String(device.uniqueID), String(device.localizedName), String(facing),
103 | String(hasAudio), String(hasVideo), String(device.isConnected))
104 |
105 | if hasAudio == false {
106 | // Add English facingLabel suffix if localizedName does not match for facing detection using label
107 | let deviceLabel = device.localizedName.contains(facingLabel) ?
108 | device.localizedName : device.localizedName + " (" + facingLabel + ")";
109 |
110 | let device = MediaDeviceInfo(
111 | deviceId: device.uniqueID,
112 | kind: "videoinput",
113 | label: deviceLabel
114 | )
115 |
116 | // Put Front devices at beginning of the videoDevicesArr
117 | if (facing == "front") {
118 | // Simple Swift 4 array unshift
119 | let videoDevicesArrFirst : [MediaDeviceInfo] = [device]
120 | videoDevicesArr = videoDevicesArrFirst + videoDevicesArr;
121 | } else {
122 | videoDevicesArr.append(device)
123 | }
124 | }
125 | }
126 |
127 | return videoDevicesArr
128 | }
129 |
130 | // Getter function inserted by get all audio devices connected
131 | fileprivate func getAllAudioDevices() -> [MediaDeviceInfo] {
132 |
133 | let audioSession: AVAudioSession = AVAudioSession.sharedInstance()
134 | var audioDevicesArr : [MediaDeviceInfo] = []
135 | let audioInputDevices: [AVAudioSessionPortDescription] = audioSession.availableInputs!
136 | var bluetoothDevice: AVAudioSessionPortDescription? = nil
137 | var wiredDevice: AVAudioSessionPortDescription? = nil
138 | var builtMicDevice: AVAudioSessionPortDescription? = nil
139 |
140 | for audioInput in audioInputDevices {
141 |
142 | let device = MediaDeviceInfo(
143 | deviceId: audioInput.uid,
144 | kind: "audioinput",
145 | label: audioInput.portName
146 | );
147 |
148 | audioDevicesArr.append(device)
149 |
150 | // Initialize audioInputSelected. Default Built-In Microphone
151 | if audioInput.portType == AVAudioSession.Port.builtInMic {
152 | builtMicDevice = audioInput
153 | }
154 |
155 | if audioInput.portType == .bluetoothHFP || audioInput.portType == .bluetoothA2DP {
156 | bluetoothDevice = audioInput
157 | }
158 |
159 | if audioInput.portType == .usbAudio || audioInput.portType == .headsetMic {
160 | wiredDevice = audioInput
161 | }
162 | }
163 |
164 | // Initialize audioInputSelected. Priority: [Wired - Wireless - Built-In Microphone]
165 | if wiredDevice != nil {
166 | iRTCAudioController.saveInputAudioDevice(inputDeviceUID: wiredDevice!.uid)
167 | } else if bluetoothDevice != nil {
168 | iRTCAudioController.saveInputAudioDevice(inputDeviceUID: bluetoothDevice!.uid)
169 | } else if builtMicDevice != nil {
170 | iRTCAudioController.saveInputAudioDevice(inputDeviceUID: builtMicDevice!.uid)
171 | }
172 | return audioDevicesArr
173 | }
174 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Classes/iMediaStream.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | import Foundation
11 | import WebRTC
12 |
13 | class iMediaStream : NSObject {
14 |
15 | var rtcMediaStream: RTCMediaStream
16 | var id: String
17 | var audioTracks: [String : iMediaStreamTrack] = [:]
18 | var videoTracks: [String : iMediaStreamTrack] = [:]
19 | var eventListener: ((_ data: NSDictionary) -> Void)?
20 | var eventListenerForAddTrack: ((_ pluginMediaStreamTrack: iMediaStreamTrack) -> Void)?
21 | var eventListenerForRemoveTrack: ((_ pluginMediaStreamTrack: iMediaStreamTrack) -> Void)?
22 |
23 | /**
24 | * Constructor for pc.onaddstream event and getUserMedia().
25 | */
26 | init(rtcMediaStream: RTCMediaStream, streamId: String? = nil) {
27 | NSLog("iMediaStream#init()")
28 |
29 | self.rtcMediaStream = rtcMediaStream;
30 |
31 | if (streamId == nil) {
32 | // Handle possible duplicate remote trackId with janus or short duplicate name
33 | // See: https://github.com/cordova-rtc/cordova-plugin-iosrtc/issues/432
34 | if (rtcMediaStream.streamId.count < 36) {
35 | self.id = rtcMediaStream.streamId + "_" + UUID().uuidString;
36 | } else {
37 | self.id = rtcMediaStream.streamId;
38 | }
39 | } else {
40 | self.id = streamId!;
41 | }
42 |
43 | for track: RTCMediaStreamTrack in (self.rtcMediaStream.audioTracks as Array) {
44 | let pluginMediaStreamTrack = iMediaStreamTrack(rtcMediaStreamTrack: track)
45 |
46 | pluginMediaStreamTrack.run()
47 | self.audioTracks[pluginMediaStreamTrack.id] = pluginMediaStreamTrack
48 | }
49 |
50 | for track: RTCMediaStreamTrack in (self.rtcMediaStream.videoTracks as Array) {
51 | let pluginMediaStreamTrack = iMediaStreamTrack(rtcMediaStreamTrack: track)
52 |
53 | pluginMediaStreamTrack.run()
54 | self.videoTracks[pluginMediaStreamTrack.id] = pluginMediaStreamTrack
55 | }
56 | }
57 |
58 | deinit {
59 | NSLog("iMediaStream#deinit()")
60 | stop();
61 | }
62 |
63 | func run() {
64 | NSLog("iMediaStream#run()")
65 | }
66 |
67 | func stop() {
68 | NSLog("iMediaStream#stop()")
69 |
70 | for (_, track) in audioTracks {
71 | if(self.eventListenerForRemoveTrack != nil) {
72 | self.eventListenerForRemoveTrack!(track)
73 | }
74 | }
75 | for (_, track) in videoTracks {
76 | if(self.eventListenerForRemoveTrack != nil) {
77 | self.eventListenerForRemoveTrack!(track)
78 | }
79 | }
80 | }
81 |
82 | func getJSON() -> NSDictionary {
83 | let json: NSMutableDictionary = [
84 | "id": self.id,
85 | "audioTracks": NSMutableDictionary(),
86 | "videoTracks": NSMutableDictionary()
87 | ]
88 |
89 | for (id, pluginMediaStreamTrack) in self.audioTracks {
90 | (json["audioTracks"] as! NSMutableDictionary)[id] = pluginMediaStreamTrack.getJSON()
91 | }
92 |
93 | for (id, pluginMediaStreamTrack) in self.videoTracks {
94 | (json["videoTracks"] as! NSMutableDictionary)[id] = pluginMediaStreamTrack.getJSON()
95 | }
96 |
97 | return json as NSDictionary
98 | }
99 |
100 | func setListener(
101 | _ eventListener: @escaping (_ data: NSDictionary) -> Void,
102 | eventListenerForAddTrack: ((_ pluginMediaStreamTrack: iMediaStreamTrack) -> Void)?,
103 | eventListenerForRemoveTrack: ((_ pluginMediaStreamTrack: iMediaStreamTrack) -> Void)?
104 | ) {
105 | NSLog("iMediaStream#setListener()")
106 |
107 | self.eventListener = eventListener
108 | self.eventListenerForAddTrack = eventListenerForAddTrack
109 | self.eventListenerForRemoveTrack = eventListenerForRemoveTrack
110 | }
111 |
112 | func hasTrack(_ pluginMediaStreamTrack: iMediaStreamTrack) -> Bool {
113 | if pluginMediaStreamTrack.kind == "audio" {
114 | return self.audioTracks[pluginMediaStreamTrack.id] != nil;
115 | } else if pluginMediaStreamTrack.kind == "video" {
116 | return self.videoTracks[pluginMediaStreamTrack.id] != nil;
117 | } else {
118 | return false
119 | }
120 | }
121 |
122 | func addTrack(_ pluginMediaStreamTrack: iMediaStreamTrack) -> Bool {
123 | NSLog("iMediaStream#addTrack()")
124 |
125 | if pluginMediaStreamTrack.kind == "audio" {
126 | self.rtcMediaStream.addAudioTrack(pluginMediaStreamTrack.rtcMediaStreamTrack as! RTCAudioTrack)
127 | NSLog("iMediaStream#addTrack() | audio track added")
128 | self.audioTracks[pluginMediaStreamTrack.id] = pluginMediaStreamTrack
129 | } else if pluginMediaStreamTrack.kind == "video" {
130 | self.rtcMediaStream.addVideoTrack(pluginMediaStreamTrack.rtcMediaStreamTrack as! RTCVideoTrack)
131 | NSLog("iMediaStream#addTrack() | video track added")
132 | self.videoTracks[pluginMediaStreamTrack.id] = pluginMediaStreamTrack
133 | } else {
134 | return false
135 | }
136 |
137 | onAddTrack(pluginMediaStreamTrack)
138 | return true
139 | }
140 |
141 | func removeTrack(_ pluginMediaStreamTrack: iMediaStreamTrack) -> Bool {
142 | NSLog("iMediaStream#removeTrack()")
143 |
144 | if pluginMediaStreamTrack.kind == "audio" {
145 | self.audioTracks[pluginMediaStreamTrack.id] = nil
146 | self.rtcMediaStream.removeAudioTrack(pluginMediaStreamTrack.rtcMediaStreamTrack as! RTCAudioTrack)
147 | NSLog("iMediaStream#removeTrack() | audio track removed")
148 | } else if pluginMediaStreamTrack.kind == "video" {
149 | self.videoTracks[pluginMediaStreamTrack.id] = nil
150 | self.rtcMediaStream.removeVideoTrack(pluginMediaStreamTrack.rtcMediaStreamTrack as! RTCVideoTrack)
151 | NSLog("iMediaStream#removeTrack() | video track removed")
152 | } else {
153 | return false
154 | }
155 |
156 | onRemoveTrack(pluginMediaStreamTrack)
157 | return true
158 | }
159 |
160 | func onAddTrack(_ track: iMediaStreamTrack) {
161 | NSLog("iMediaStream | OnAddTrack [label:%@]", String(track.id))
162 |
163 | track.run()
164 |
165 | if self.eventListener != nil {
166 | self.eventListenerForAddTrack!(track)
167 |
168 | self.eventListener!([
169 | "type": "addtrack",
170 | "track": track.getJSON()
171 | ])
172 | }
173 | }
174 |
175 | func onRemoveTrack(_ track: iMediaStreamTrack) {
176 | NSLog("iMediaStream | OnRemoveTrack [label:%@]", String(track.id))
177 |
178 | // It may happen that track was removed due to user action (removeTrack()).
179 | if self.audioTracks[track.id] != nil {
180 | self.audioTracks[track.id] = nil
181 | } else if self.videoTracks[track.id] != nil {
182 | self.videoTracks[track.id] = nil
183 | } else {
184 | return
185 | }
186 |
187 | if self.eventListener != nil {
188 | self.eventListenerForRemoveTrack!(track)
189 |
190 | self.eventListener!([
191 | "type": "removetrack",
192 | "track": [
193 | "id": track.id,
194 | "kind": track.kind
195 | ]
196 | ])
197 | }
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Classes/iGetUserMedia.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | import Foundation
11 | import AVFoundation
12 | import WebRTC
13 |
14 | class iGetUserMedia {
15 |
16 | var rtcPeerConnectionFactory: RTCPeerConnectionFactory
17 |
18 | init(rtcPeerConnectionFactory: RTCPeerConnectionFactory) {
19 | NSLog("iGetUserMedia#init()")
20 | self.rtcPeerConnectionFactory = rtcPeerConnectionFactory
21 | }
22 |
23 | deinit {
24 | NSLog("iGetUserMedia#deinit()")
25 | }
26 |
27 | func call(
28 | _ constraints: NSDictionary,
29 | callback: (_ data: NSDictionary) -> Void,
30 | errback: (_ error: String) -> Void,
31 | eventListenerForNewStream: (_ pluginMediaStream: iMediaStream) -> Void
32 | ) {
33 |
34 | NSLog("iGetUserMedia#call()")
35 |
36 | var videoRequested: Bool = false
37 | var audioRequested: Bool = false
38 |
39 | if (constraints.object(forKey: "video") != nil) {
40 | videoRequested = true
41 | }
42 |
43 | if constraints.object(forKey: "audio") != nil {
44 | audioRequested = true
45 | }
46 |
47 | var rtcMediaStream: RTCMediaStream
48 | var pluginMediaStream: iMediaStream?
49 | var rtcAudioTrack: RTCAudioTrack?
50 | var rtcVideoTrack: RTCVideoTrack?
51 | var rtcVideoSource: RTCVideoSource?
52 |
53 | if videoRequested == true {
54 | switch AVCaptureDevice.authorizationStatus(for: AVMediaType.video) {
55 | case AVAuthorizationStatus.notDetermined:
56 | NSLog("iGetUserMedia#call() | video authorization: not determined")
57 | AVCaptureDevice.requestAccess(for: .video) { granted in
58 | if granted {
59 | NSLog("iGetUserMedia#call() | video authorization: authorized")
60 | }
61 | }
62 | case AVAuthorizationStatus.authorized:
63 | NSLog("iGetUserMedia#call() | video authorization: authorized")
64 | case AVAuthorizationStatus.denied:
65 | NSLog("iGetUserMedia#call() | video authorization: denied")
66 | errback("video denied")
67 | return
68 | case AVAuthorizationStatus.restricted:
69 | NSLog("iGetUserMedia#call() | video authorization: restricted")
70 | errback("video restricted")
71 | return
72 | }
73 | }
74 |
75 | if audioRequested == true {
76 | switch AVCaptureDevice.authorizationStatus(for: AVMediaType.audio) {
77 | case AVAuthorizationStatus.notDetermined:
78 | NSLog("iGetUserMedia#call() | audio authorization: not determined")
79 | AVCaptureDevice.requestAccess(for: .audio) { granted in
80 | if granted {
81 | NSLog("iGetUserMedia#call() | audio authorization: authorized")
82 | }
83 | }
84 | case AVAuthorizationStatus.authorized:
85 | NSLog("iGetUserMedia#call() | audio authorization: authorized")
86 | case AVAuthorizationStatus.denied:
87 | NSLog("iGetUserMedia#call() | audio authorization: denied")
88 | errback("audio denied")
89 | return
90 | case AVAuthorizationStatus.restricted:
91 | NSLog("iGetUserMedia#call() | audio authorization: restricted")
92 | errback("audio restricted")
93 | return
94 | }
95 | }
96 |
97 | rtcMediaStream = self.rtcPeerConnectionFactory.mediaStream(withStreamId: UUID().uuidString)
98 |
99 | if videoRequested {
100 |
101 | NSLog("iGetUserMedia#call() | video requested")
102 |
103 | rtcVideoSource = self.rtcPeerConnectionFactory.videoSource()
104 |
105 | rtcVideoTrack = self.rtcPeerConnectionFactory.videoTrack(with: rtcVideoSource!, trackId: UUID().uuidString)
106 |
107 | // Handle legacy plugin instance or video: true
108 | var videoConstraints : NSDictionary = [:];
109 | if (!(constraints.object(forKey: "video") is Bool)) {
110 | videoConstraints = constraints.object(forKey: "video") as! NSDictionary
111 | }
112 |
113 | NSLog("iGetUserMedia#call() | chosen video constraints: %@", videoConstraints)
114 |
115 | // Ignore Simulator cause does not support Camera
116 | #if !targetEnvironment(simulator)
117 | let videoCapturer: RTCCameraVideoCapturer = RTCCameraVideoCapturer(delegate: rtcVideoSource!)
118 | let videoCaptureController: iRTCVideoCaptureController = iRTCVideoCaptureController(capturer: videoCapturer)
119 | rtcVideoTrack!.videoCaptureController = videoCaptureController
120 |
121 | let constraintsSatisfied = videoCaptureController.setConstraints(constraints: videoConstraints)
122 | if (!constraintsSatisfied) {
123 | errback("constraints not satisfied")
124 | return
125 | }
126 |
127 | let captureStarted = videoCaptureController.startCapture()
128 | if (!captureStarted) {
129 | errback("constraints failed")
130 | return
131 | }
132 | #endif
133 |
134 | // If videoSource state is "ended" it means that constraints were not satisfied so
135 | // invoke the given errback.
136 | if (rtcVideoSource!.state == RTCSourceState.ended) {
137 | NSLog("iGetUserMedia() | rtcVideoSource.state is 'ended', constraints not satisfied")
138 |
139 | errback("constraints not satisfied")
140 | return
141 | }
142 |
143 | if let device = rtcVideoTrack!.videoCaptureController?.device {
144 | rtcVideoTrack!.capabilities["deviceId"] = device.uniqueID
145 | }
146 |
147 | rtcMediaStream.addVideoTrack(rtcVideoTrack!)
148 | }
149 |
150 | if audioRequested == true {
151 |
152 | NSLog("iGetUserMedia#call() | audio requested")
153 |
154 | // Handle legacy plugin instance or audio: true
155 | var audioConstraints : NSDictionary = [:];
156 | if (!(constraints.object(forKey: "audio") is Bool)) {
157 | audioConstraints = constraints.object(forKey: "audio") as! NSDictionary
158 | }
159 |
160 | NSLog("iGetUserMedia#call() | chosen audio constraints: %@", audioConstraints)
161 |
162 |
163 | var audioDeviceId = audioConstraints.object(forKey: "deviceId") as? String
164 | if(audioDeviceId == nil && audioConstraints.object(forKey: "deviceId") != nil){
165 | let audioId = audioConstraints.object(forKey: "deviceId") as! NSDictionary
166 | audioDeviceId = audioId.object(forKey: "exact") as? String
167 | if(audioDeviceId == nil){
168 | audioDeviceId = audioId.object(forKey: "ideal") as? String
169 | }
170 | }
171 |
172 | rtcAudioTrack = self.rtcPeerConnectionFactory.audioTrack(withTrackId: UUID().uuidString)
173 | rtcMediaStream.addAudioTrack(rtcAudioTrack!)
174 |
175 | if audioDeviceId == "default" {
176 | audioDeviceId = "Built-In Microphone"
177 | }
178 |
179 | if (audioDeviceId != nil) {
180 | iRTCAudioController.saveInputAudioDevice(inputDeviceUID: audioDeviceId!)
181 | }
182 | }
183 |
184 | pluginMediaStream = iMediaStream(rtcMediaStream: rtcMediaStream)
185 | pluginMediaStream!.run()
186 |
187 | // Let the plugin store it in its dictionary.
188 | eventListenerForNewStream(pluginMediaStream!)
189 |
190 | callback([
191 | "stream": pluginMediaStream!.getJSON()
192 | ])
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Classes/iRTCAudioController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | import Foundation
11 | import AVFoundation
12 | import WebRTC
13 |
14 | class iRTCAudioController {
15 |
16 | static private var audioCategory : AVAudioSession.Category = AVAudioSession.Category.playAndRecord
17 |
18 | static private var audioCategoryOptions : AVAudioSession.CategoryOptions = [
19 | AVAudioSession.CategoryOptions.mixWithOthers,
20 | AVAudioSession.CategoryOptions.allowBluetooth,
21 | AVAudioSession.CategoryOptions.allowAirPlay,
22 | AVAudioSession.CategoryOptions.allowBluetoothA2DP
23 | ]
24 |
25 | /*
26 | This mode is intended for Voice over IP (VoIP) apps and can only be used with the playAndRecord category. When this mode is used, the device’s tonal equalization is optimized for voice and the set of allowable audio routes is reduced to only those appropriate for voice chat.
27 |
28 | See: https://developer.apple.com/documentation/avfoundation/avaudiosession/mode/1616455-voicechat
29 | */
30 | static private var audioMode = AVAudioSession.Mode.voiceChat
31 | static private var audioModeDefault : AVAudioSession.Mode = AVAudioSession.Mode.default
32 |
33 | static private var audioInputSelected: AVAudioSessionPortDescription? = nil
34 |
35 | //
36 | // Audio Input
37 | //
38 |
39 | static func initAudioDevices() -> Void {
40 |
41 | iRTCAudioController.setCategory()
42 |
43 | do {
44 | let audioSession: AVAudioSession = AVAudioSession.sharedInstance()
45 | try audioSession.setActive(true)
46 | } catch {
47 | print("Error messing with audio session: \(error)")
48 | }
49 | }
50 |
51 | static func setCategory() -> Void {
52 | // Enable speaker
53 | NSLog("iRTCAudioController#setCategory()")
54 |
55 | do {
56 | let audioSession: AVAudioSession = AVAudioSession.sharedInstance()
57 | try audioSession.setCategory(
58 | iRTCAudioController.audioCategory,
59 | mode: iRTCAudioController.audioMode,
60 | options: iRTCAudioController.audioCategoryOptions
61 | )
62 | } catch {
63 | NSLog("iRTCAudioController#setCategory() | ERROR \(error)")
64 | };
65 | }
66 |
67 | // Setter function inserted by save specific audio device
68 | static func saveInputAudioDevice(inputDeviceUID: String) -> Void {
69 | let audioSession: AVAudioSession = AVAudioSession.sharedInstance()
70 | if let audioInput: AVAudioSessionPortDescription = audioSession.availableInputs!.first(where: { $0.uid == inputDeviceUID }) {
71 | iRTCAudioController.audioInputSelected = audioInput
72 | } else {
73 | NSLog("iRTCAudioController#saveInputAudioDevice() | ERROR invalid deviceId \(inputDeviceUID)")
74 | iRTCAudioController.audioInputSelected = audioSession.availableInputs!.first
75 | }
76 | }
77 |
78 | // Setter function inserted by set specific audio device
79 | static func restoreInputOutputAudioDevice() -> Void {
80 | let audioSession: AVAudioSession = AVAudioSession.sharedInstance()
81 |
82 | do {
83 | try audioSession.setPreferredInput(iRTCAudioController.audioInputSelected)
84 | } catch {
85 | NSLog("iRTCAudioController:restoreInputOutputAudioDevice: Error setting audioSession preferred input.")
86 | }
87 |
88 | iRTCAudioController.setOutputSpeakerIfNeed(enabled: speakerEnabled);
89 | }
90 |
91 | static func setOutputSpeakerIfNeed(enabled: Bool) {
92 |
93 | speakerEnabled = enabled
94 |
95 | let audioSession: AVAudioSession = AVAudioSession.sharedInstance()
96 | let currentRoute = audioSession.currentRoute
97 |
98 | if currentRoute.outputs.count != 0 {
99 | for description in currentRoute.outputs {
100 | if (
101 | description.portType == AVAudioSession.Port.headphones ||
102 | description.portType == AVAudioSession.Port.bluetoothA2DP ||
103 | description.portType == AVAudioSession.Port.carAudio ||
104 | description.portType == AVAudioSession.Port.airPlay ||
105 | description.portType == AVAudioSession.Port.lineOut
106 | ) {
107 | NSLog("iRTCAudioController#setOutputSpeakerIfNeed() | external audio output plugged in -> do nothing")
108 | } else {
109 | NSLog("iRTCAudioController#setOutputSpeakerIfNeed() | external audio pulled out")
110 |
111 | if (speakerEnabled) {
112 | do {
113 | try audioSession.overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
114 | } catch {
115 | NSLog("iRTCAudioController#setOutputSpeakerIfNeed() | ERROR \(error)")
116 | };
117 | }
118 | }
119 | }
120 | } else {
121 | NSLog("iRTCAudioController#setOutputSpeakerIfNeed() | requires connection to device")
122 | }
123 | }
124 |
125 | static func selectAudioOutputSpeaker() {
126 | // Enable speaker
127 | NSLog("iRTCAudioController#selectAudioOutputSpeaker()")
128 |
129 | speakerEnabled = true;
130 |
131 | setCategory()
132 |
133 | do {
134 | let audioSession: AVAudioSession = AVAudioSession.sharedInstance()
135 | try audioSession.overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
136 | } catch {
137 | NSLog("iRTCAudioController#selectAudioOutputSpeaker() | ERROR \(error)")
138 | };
139 | }
140 |
141 | static func selectAudioOutputEarpiece() {
142 | // Disable speaker, switched to default
143 | NSLog("iRTCAudioController#selectAudioOutputEarpiece()")
144 |
145 | speakerEnabled = false;
146 |
147 | setCategory()
148 |
149 | do {
150 | let audioSession: AVAudioSession = AVAudioSession.sharedInstance()
151 | try audioSession.overrideOutputAudioPort(AVAudioSession.PortOverride.none)
152 | } catch {
153 | NSLog("iRTCAudioController#selectAudioOutputEarpiece() | ERROR \(error)")
154 | };
155 | }
156 |
157 | //
158 | // Audio Output
159 | //
160 |
161 | static private var speakerEnabled: Bool = false
162 |
163 | init() {
164 | let shouldManualInit = Bundle.main.object(forInfoDictionaryKey: "ManualInitAudioDevice") as? String
165 |
166 | if(shouldManualInit == "FALSE") {
167 | iRTCAudioController.initAudioDevices()
168 | }
169 |
170 | NotificationCenter.default.addObserver(
171 | self,
172 | selector: #selector(self.audioRouteChangeListener(_:)),
173 | name: AVAudioSession.routeChangeNotification,
174 | object: nil)
175 | }
176 |
177 | @objc dynamic fileprivate func audioRouteChangeListener(_ notification:Notification) {
178 | let audioRouteChangeReason = notification.userInfo![AVAudioSessionRouteChangeReasonKey] as! UInt
179 |
180 | switch audioRouteChangeReason {
181 | case AVAudioSession.RouteChangeReason.newDeviceAvailable.rawValue:
182 | NSLog("iRTCAudioController#audioRouteChangeListener() | headphone plugged in")
183 | case AVAudioSession.RouteChangeReason.oldDeviceUnavailable.rawValue:
184 | NSLog("iRTCAudioController#audioRouteChangeListener() | headphone pulled out -> restore state speakerEnabled: %@", iRTCAudioController.speakerEnabled ? "true" : "false")
185 | iRTCAudioController.setOutputSpeakerIfNeed(enabled: iRTCAudioController.speakerEnabled)
186 | default:
187 | break
188 | }
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/RTCIceCandidate.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose the RTCIceCandidate class.
12 | */
13 | module.exports = RTCIceCandidate;
14 |
15 |
16 |
17 | /**
18 | * RFC-5245: http://tools.ietf.org/html/rfc5245#section-15.1
19 | *
20 | * candidate-attribute = "candidate" ":" foundation SP component-id SP
21 | transport SP
22 | priority SP
23 | connection-address SP ;from RFC 4566
24 | port ;port from RFC 4566
25 | SP cand-type
26 | [SP rel-addr]
27 | [SP rel-port]
28 | *(SP extension-att-name SP
29 | extension-att-value)
30 | *
31 | * foundation = 1*32ice-char
32 | * component-id = 1*5DIGIT
33 | * transport = "UDP" / transport-extension
34 | * transport-extension = token ; from RFC 3261
35 | * priority = 1*10DIGIT
36 | * cand-type = "typ" SP candidate-types
37 | * candidate-types = "host" / "srflx" / "prflx" / "relay" / token
38 | * rel-addr = "raddr" SP connection-address
39 | * rel-port = "rport" SP port
40 | * extension-att-name = byte-string ;from RFC 4566
41 | * extension-att-value = byte-string
42 | * ice-char = ALPHA / DIGIT / "+" / "/"
43 | */
44 |
45 | /**
46 | * RFC-3261: https://tools.ietf.org/html/rfc3261#section-25.1
47 | *
48 | * token = 1*(alphanum / "-" / "." / "!" / "%" / "*"
49 | / "_" / "+" / "`" / "'" / "~" )
50 | */
51 |
52 | /*
53 | * RFC-4566: https://tools.ietf.org/html/rfc4566#section-9
54 | *
55 | * port = 1*DIGIT
56 | * IP4-address = b1 3("." decimal-uchar)
57 | * b1 = decimal-uchar
58 | ; less than "224"
59 | * ; The following is consistent with RFC 2373 [30], Appendix B.
60 | * IP6-address = hexpart [ ":" IP4-address ]
61 | * hexpart = hexseq / hexseq "::" [ hexseq ] /
62 | "::" [ hexseq ]
63 | * hexseq = hex4 *( ":" hex4)
64 | * hex4 = 1*4HEXDIG
65 | * decimal-uchar = DIGIT
66 | / POS-DIGIT DIGIT
67 | / ("1" 2*(DIGIT))
68 | / ("2" ("0"/"1"/"2"/"3"/"4") DIGIT)
69 | / ("2" "5" ("0"/"1"/"2"/"3"/"4"/"5"))
70 | */
71 |
72 | var candidateToJson = (function () {
73 | var candidateFieldName = {
74 | FOUNDATION: 'foundation',
75 | COMPONENT_ID: 'componentId',
76 | TRANSPORT: 'transport',
77 | PRIORITY: 'priority',
78 | CONNECTION_ADDRESS: 'connectionAddress',
79 | PORT: 'port',
80 | CANDIDATE_TYPE: 'candidateType',
81 | REMOTE_CANDIDATE_ADDRESS: 'remoteConnectionAddress',
82 | REMOTE_CANDIDATE_PORT: 'remotePort'
83 | };
84 |
85 | var candidateType = {
86 | HOST: 'host',
87 | SRFLX: 'srflx',
88 | PRFLX: 'prflx',
89 | RELAY: 'relay'
90 | };
91 |
92 | var transport = {
93 | TCP: 'TCP',
94 | UDP: 'UDP'
95 | };
96 |
97 | var IPV4SEG = '(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])';
98 | var IPV4ADDR = `(?:${IPV4SEG}\\.){3}${IPV4SEG}`;
99 | var IPV6SEG = '[0-9a-fA-F]{1,4}';
100 | var IPV6ADDR =
101 | `(?:${IPV6SEG}:){7,7}${IPV6SEG}|` + // 1:2:3:4:5:6:7:8
102 | `(?:${IPV6SEG}:){1,7}:|` + // 1:: 1:2:3:4:5:6:7::
103 | `(?:${IPV6SEG}:){1,6}:${IPV6SEG}|` + // 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8
104 | `(?:${IPV6SEG}:){1,5}(?::${IPV6SEG}){1,2}|` + // 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8
105 | `(?:${IPV6SEG}:){1,4}(?::${IPV6SEG}){1,3}|` + // 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8
106 | `(?:${IPV6SEG}:){1,3}(?::${IPV6SEG}){1,4}|` + // 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8
107 | `(?:${IPV6SEG}:){1,2}(?::${IPV6SEG}){1,5}|` + // 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8
108 | `${IPV6SEG}:(?:(?::${IPV6SEG}){1,6})|` + // 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8
109 | `:(?:(?::${IPV6SEG}){1,7}|:)|` + // ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::
110 | `fe80:(?::${IPV6SEG}){0,4}%[0-9a-zA-Z]{1,}|` + // fe80::7:8%eth0 fe80::7:8%1 (link-local IPv6 addresses with zone index)
111 | `::(?:ffff(?::0{1,4}){0,1}:){0,1}${IPV4ADDR}|` + // ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses)
112 | `(?:${IPV6SEG}:){1,4}:${IPV4ADDR}`; // 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 (IPv4-Embedded IPv6 Address)
113 |
114 | var TOKEN = "[0-9a-zA-Z\\-\\.!\\%\\*_\\+\\`\\'\\~]+";
115 |
116 | var CANDIDATE_TYPE = '';
117 | Object.keys(candidateType).forEach(function (key) {
118 | CANDIDATE_TYPE += candidateType[key] + '|';
119 | });
120 | CANDIDATE_TYPE += TOKEN;
121 |
122 | var pattern = {
123 | COMPONENT_ID: '[0-9]{1,5}',
124 | FOUNDATION: '[a-zA-Z0-9\\+\\/\\-]+',
125 | PRIORITY: '[0-9]{1,10}',
126 | TRANSPORT: transport.UDP + '|' + TOKEN,
127 | CONNECTION_ADDRESS: IPV4ADDR + '|' + IPV6ADDR,
128 | PORT: '[0-9]{1,5}',
129 | CANDIDATE_TYPE: CANDIDATE_TYPE
130 | };
131 |
132 | return function candidateToJson(iceCandidate) {
133 | var iceCandidateJson = null;
134 |
135 | if (iceCandidate && typeof iceCandidate === 'string') {
136 | var ICE_CANDIDATE_PATTERN = new RegExp(
137 | `candidate:(${pattern.FOUNDATION})` + // 10
138 | `\\s(${pattern.COMPONENT_ID})` + // 1
139 | `\\s(${pattern.TRANSPORT})` + // UDP
140 | `\\s(${pattern.PRIORITY})` + // 1845494271
141 | `\\s(${pattern.CONNECTION_ADDRESS})` + // 13.93.107.159
142 | `\\s(${pattern.PORT})` + // 53705
143 | '\\s' +
144 | 'typ' +
145 | `\\s(${pattern.CANDIDATE_TYPE})` + // typ prflx
146 | '(?:\\s' +
147 | 'raddr' +
148 | `\\s(${pattern.CONNECTION_ADDRESS})` + // raddr 10.1.221.7
149 | '\\s' +
150 | 'rport' +
151 | `\\s(${pattern.PORT}))?` // rport 54805
152 | );
153 |
154 | var iceCandidateFields = iceCandidate.match(ICE_CANDIDATE_PATTERN);
155 | if (iceCandidateFields) {
156 | iceCandidateJson = {};
157 | Object.keys(candidateFieldName).forEach(function (key, i) {
158 | // i+1 because match returns the entire match result
159 | // and the parentheses-captured matched results.
160 | if (iceCandidateFields.length > i + 1 && iceCandidateFields[i + 1]) {
161 | iceCandidateJson[candidateFieldName[key]] = iceCandidateFields[i + 1];
162 | }
163 | });
164 | }
165 | }
166 |
167 | return iceCandidateJson;
168 | };
169 | })();
170 |
171 | // See https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidate/RTCIceCandidate
172 | function RTCIceCandidate(data) {
173 | data = data || {};
174 |
175 | // Public atributes.
176 | this.sdpMid = data.sdpMid;
177 | this.sdpMLineIndex = data.sdpMLineIndex;
178 | this.candidate = data.candidate;
179 |
180 | // Parse candidate SDP:
181 | // Example: candidate:1829696681 1 udp 2122262783 2a01:cb05:8d3e:a300:e1ad:79c1:7096:8ba0 49778 typ host generation 0 ufrag c9L6 network-id 2 network-cost 10
182 | var iceCandidateFields = candidateToJson(this.candidate);
183 | if (iceCandidateFields) {
184 | this.foundation = iceCandidateFields.foundation;
185 | this.component = iceCandidateFields.componentId;
186 | this.priority = iceCandidateFields.priority;
187 | this.type = iceCandidateFields.candidateType;
188 |
189 | this.address = iceCandidateFields.connectionAddress;
190 | this.ip = iceCandidateFields.connectionAddress;
191 | this.protocol = iceCandidateFields.transport;
192 | this.port = iceCandidateFields.port;
193 |
194 | this.relatedAddress = iceCandidateFields.remoteConnectionAddress || null;
195 | this.relatedPort = iceCandidateFields.remotePort || null;
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/RTCDataChannel.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose the RTCDataChannel class.
12 | */
13 | module.exports = RTCDataChannel;
14 |
15 |
16 | /**
17 | * Dependencies.
18 | */
19 | var
20 | debug = require('debug')('iosrtc:RTCDataChannel'),
21 | debugerror = require('debug')('iosrtc:ERROR:RTCDataChannel'),
22 | exec = require('./IOSExec'),
23 | randomNumber = require('random-number').generator({min: 10000, max: 99999, integer: true}),
24 | EventTarget = require('./EventTarget');
25 |
26 |
27 | debugerror.log = console.warn.bind(console);
28 |
29 |
30 | function RTCDataChannel(peerConnection, label, options, dataFromEvent) {
31 | var self = this;
32 |
33 | // Make this an EventTarget.
34 | EventTarget.call(this);
35 |
36 | // Created via pc.createDataChannel().
37 | if (!dataFromEvent) {
38 | debug('new() | [label:%o, options:%o]', label, options);
39 |
40 | if (typeof label !== 'string') {
41 | label = '';
42 | }
43 |
44 | options = options || {};
45 |
46 | if (options.hasOwnProperty('maxPacketLifeTime') && options.hasOwnProperty('maxRetransmits')) {
47 | throw new SyntaxError('both maxPacketLifeTime and maxRetransmits can not be present');
48 | }
49 |
50 | if (options.hasOwnProperty('id')) {
51 | if (typeof options.id !== 'number' || isNaN(options.id) || options.id < 0) {
52 | throw new SyntaxError('id must be a number');
53 | }
54 | // TODO:
55 | // https://code.google.com/p/webrtc/issues/detail?id=4618
56 | if (options.id > 1023) {
57 | throw new SyntaxError('id cannot be greater than 1023 (https://code.google.com/p/webrtc/issues/detail?id=4614)');
58 | }
59 | }
60 |
61 | // Public atributes.
62 | this.label = label;
63 | this.ordered = options.hasOwnProperty('ordered') ? !!options.ordered : true;
64 | this.maxPacketLifeTime = options.hasOwnProperty('maxPacketLifeTime') ? Number(options.maxPacketLifeTime) : null;
65 | this.maxRetransmits = options.hasOwnProperty('maxRetransmits') ? Number(options.maxRetransmits) : null;
66 | this.protocol = options.hasOwnProperty('protocol') ? String(options.protocol) : '';
67 | this.negotiated = options.hasOwnProperty('negotiated') ? !!options.negotiated : false;
68 | this.id = options.hasOwnProperty('id') ? Number(options.id) : undefined;
69 | this.readyState = 'connecting';
70 | this.bufferedAmount = 0;
71 | this.bufferedAmountLowThreshold = 0;
72 |
73 | // Private attributes.
74 | this.peerConnection = peerConnection;
75 | this.dcId = randomNumber();
76 |
77 | exec.execNative(onResultOK, null, 'WKWebViewRTC', 'RTCPeerConnection_createDataChannel', [this.peerConnection.pcId, this.dcId, label, options]);
78 | // Created via pc.ondatachannel.
79 | } else {
80 | debug('new() | [dataFromEvent:%o]', dataFromEvent);
81 |
82 | // Public atributes.
83 | this.label = dataFromEvent.label;
84 | this.ordered = dataFromEvent.ordered;
85 | this.maxPacketLifeTime = dataFromEvent.maxPacketLifeTime;
86 | this.maxRetransmits = dataFromEvent.maxRetransmits;
87 | this.protocol = dataFromEvent.protocol;
88 | this.negotiated = dataFromEvent.negotiated;
89 | this.id = dataFromEvent.id;
90 | this.readyState = dataFromEvent.readyState;
91 | this.bufferedAmount = dataFromEvent.bufferedAmount;
92 | this.bufferedAmountLowThreshold = dataFromEvent.bufferedAmountLowThreshold;
93 |
94 | // Private attributes.
95 | this.peerConnection = peerConnection;
96 | this.dcId = dataFromEvent.dcId;
97 |
98 | exec.execNative(onResultOK, null, 'WKWebViewRTC', 'RTCPeerConnection_RTCDataChannel_setListener', [this.peerConnection.pcId, this.dcId]);
99 | }
100 |
101 | function onResultOK(data) {
102 | if (data.type) {
103 | onEvent.call(self, data);
104 | // Special handler for received binary mesage.
105 | } else {
106 | onEvent.call(self, {
107 | type: 'message',
108 | message: data
109 | });
110 | }
111 | }
112 | }
113 |
114 | RTCDataChannel.prototype = Object.create(EventTarget.prototype);
115 | RTCDataChannel.prototype.constructor = RTCDataChannel;
116 |
117 | // Just 'arraybuffer' binaryType is implemented in Chromium.
118 | Object.defineProperty(RTCDataChannel.prototype, 'binaryType', {
119 | get: function () {
120 | return 'arraybuffer';
121 | },
122 | set: function (type) {
123 | if (type !== 'arraybuffer') {
124 | throw new Error('just "arraybuffer" is implemented for binaryType');
125 | }
126 | }
127 | });
128 |
129 |
130 | RTCDataChannel.prototype.send = function (data) {
131 | if (isClosed.call(this) || this.readyState !== 'open') {
132 | return;
133 | }
134 |
135 | debug('send() | [data:%o]', data);
136 |
137 | if (!data) {
138 | return;
139 | }
140 |
141 | if (typeof data === 'string' || data instanceof String) {
142 | exec.execNative(null, null, 'WKWebViewRTC', 'RTCPeerConnection_RTCDataChannel_sendString', [this.peerConnection.pcId, this.dcId, data]);
143 | } else if (window.ArrayBuffer && data instanceof window.ArrayBuffer) {
144 | exec.execNative(null, null, 'WKWebViewRTC', 'RTCPeerConnection_RTCDataChannel_sendBinary', [this.peerConnection.pcId, this.dcId, data]);
145 | } else if (
146 | (window.Int8Array && data instanceof window.Int8Array) ||
147 | (window.Uint8Array && data instanceof window.Uint8Array) ||
148 | (window.Uint8ClampedArray && data instanceof window.Uint8ClampedArray) ||
149 | (window.Int16Array && data instanceof window.Int16Array) ||
150 | (window.Uint16Array && data instanceof window.Uint16Array) ||
151 | (window.Int32Array && data instanceof window.Int32Array) ||
152 | (window.Uint32Array && data instanceof window.Uint32Array) ||
153 | (window.Float32Array && data instanceof window.Float32Array) ||
154 | (window.Float64Array && data instanceof window.Float64Array) ||
155 | (window.DataView && data instanceof window.DataView)
156 | ) {
157 | exec.execNative(null, null, 'WKWebViewRTC', 'RTCPeerConnection_RTCDataChannel_sendBinary', [this.peerConnection.pcId, this.dcId, data.buffer]);
158 | } else {
159 | throw new Error('invalid data type');
160 | }
161 | };
162 |
163 |
164 | RTCDataChannel.prototype.close = function () {
165 | if (isClosed.call(this)) {
166 | return;
167 | }
168 |
169 | debug('close()');
170 |
171 | this.readyState = 'closing';
172 |
173 | exec.execNative(null, null, 'WKWebViewRTC', 'RTCPeerConnection_RTCDataChannel_close', [this.peerConnection.pcId, this.dcId]);
174 | };
175 |
176 |
177 | /**
178 | * Private API.
179 | */
180 |
181 |
182 | function isClosed() {
183 | return this.readyState === 'closed' || this.readyState === 'closing' || this.peerConnection.signalingState === 'closed';
184 | }
185 |
186 |
187 | function onEvent(data) {
188 | var type = data.type,
189 | event;
190 |
191 | debug('onEvent() | [type:%s, data:%o]', type, data);
192 |
193 | switch (type) {
194 | case 'new':
195 | // Update properties and exit without firing the event.
196 | this.ordered = data.channel.ordered;
197 | this.maxPacketLifeTime = data.channel.maxPacketLifeTime;
198 | this.maxRetransmits = data.channel.maxRetransmits;
199 | this.protocol = data.channel.protocol;
200 | this.negotiated = data.channel.negotiated;
201 | this.id = data.channel.id;
202 | this.readyState = data.channel.readyState;
203 | this.bufferedAmount = data.channel.bufferedAmount;
204 | break;
205 |
206 | case 'statechange':
207 | this.readyState = data.readyState;
208 |
209 | switch (data.readyState) {
210 | case 'connecting':
211 | break;
212 | case 'open':
213 | this.dispatchEvent(new Event('open'));
214 | break;
215 | case 'closing':
216 | break;
217 | case 'closed':
218 | this.dispatchEvent(new Event('close'));
219 | break;
220 | }
221 | break;
222 |
223 | case 'message':
224 | event = new Event('message');
225 | event.data = data.message;
226 | this.dispatchEvent(event);
227 | break;
228 |
229 | case 'bufferedamount':
230 | this.bufferedAmount = data.bufferedAmount;
231 |
232 | if (this.bufferedAmountLowThreshold > 0 && this.bufferedAmountLowThreshold > this.bufferedAmount) {
233 | event = new Event('bufferedamountlow');
234 | event.bufferedAmount = this.bufferedAmount;
235 | this.dispatchEvent(event);
236 | }
237 |
238 | break;
239 | }
240 | }
241 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Classes/iRTCPeerConnectionConfig.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | import Foundation
11 | import WebRTC
12 |
13 | class iRTCPeerConnectionConfig {
14 |
15 | fileprivate let allowedSdpSemantics = [
16 | "plan-b": RTCSdpSemantics.planB,
17 | "unified-plan": RTCSdpSemantics.unifiedPlan
18 | ]
19 |
20 | fileprivate let allowedBundlePolicy = [
21 | "balanced": RTCBundlePolicy.balanced,
22 | "max-compat": RTCBundlePolicy.maxCompat,
23 | "max-bundle": RTCBundlePolicy.maxBundle
24 | ]
25 |
26 | fileprivate let allowedIceTransportPolicy = [
27 | "all": RTCIceTransportPolicy.all,
28 | "relay": RTCIceTransportPolicy.relay
29 | ]
30 |
31 | fileprivate let allowedRtcpMuxPolicy = [
32 | "require": RTCRtcpMuxPolicy.require,
33 | "negotiate": RTCRtcpMuxPolicy.negotiate,
34 | ]
35 |
36 | fileprivate var rtcConfiguration: RTCConfiguration
37 |
38 | init(pcConfig: NSDictionary?) {
39 | NSLog("iRTCPeerConnectionConfig#init()")
40 |
41 | self.rtcConfiguration = RTCConfiguration()
42 |
43 | /*
44 | Since Chrome 65, this has been an experimental feature that you can opt-in to by passing
45 | sdpSemantics:'unified-plan' to the RTCPeerConnection constructor, with transceivers added in M69.
46 | History: https://crbug.com/799030. Canary/Dev experiments making Unified Plan the default behavior started in 71.
47 | The target is to release Unified Plan by default in 72 Stable. For more information, see https://webrtc.org/web-apis/chrome/unified-plan/.
48 | See: https://www.chromestatus.com/feature/5723303167655936
49 | */
50 |
51 | let sdpSemanticsConfig = pcConfig?.object(forKey: "sdpSemantics") as? String;
52 | let sdpSemantics = (sdpSemanticsConfig != nil && allowedSdpSemantics[sdpSemanticsConfig!] != nil) ?
53 | allowedSdpSemantics[sdpSemanticsConfig!] : RTCSdpSemantics.planB
54 |
55 | rtcConfiguration.sdpSemantics = sdpSemantics!;
56 |
57 | /*
58 | Specifies how to handle negotiation of candidates when the remote peer is not compatible with the SDP BUNDLE standard.
59 | This must be one of the values from the enum RTCBundlePolicy. If this value isn't included in the dictionary, "balanced" is assumed.
60 |
61 | "balanced" On BUNDLE-aware connections, the ICE agent should gather candidates for all of the media types in use (audio, video, and data). Otherwise, the ICE agent should only negotiate one audio and video track on separate transports.
62 | "max-compat" The ICE agent should gather candidates for each track, using separate transports to negotiate all media tracks for connections which aren't BUNDLE-compatible.
63 | "max-bundle" The ICE agent should gather candidates for just one track. If the connection isn't BUNDLE-compatible, then the ICE agent should negotiate just one media track.
64 | */
65 |
66 | let bundlePolicyConfig = pcConfig?.object(forKey: "bundlePolicy") as? String;
67 | let bundlePolicy = (bundlePolicyConfig != nil && allowedBundlePolicy[bundlePolicyConfig!] != nil) ?
68 | allowedBundlePolicy[bundlePolicyConfig!] : RTCBundlePolicy.balanced
69 |
70 | rtcConfiguration.bundlePolicy = bundlePolicy!;
71 |
72 | /*
73 | An Array of objects of type RTCCertificate which are used by the connection for authentication.
74 | If this property isn't specified, a set of certificates is generated automatically for each RTCPeerConnection instance.
75 | Although only one certificate is used by a given connection, providing certificates for multiple algorithms may improve
76 | the odds of successfully connecting in some circumstances. See Using certificates below for additional information.
77 | This configuration option cannot be changed after it is first specified; once the certificates have been set,
78 | this property is ignored in future calls to RTCPeerConnection.setConfiguration().
79 | */
80 | // TODO certificates
81 |
82 | /*
83 | An unsigned 16-bit integer value which specifies the size of the prefetched ICE candidate pool.
84 | The default value is 0 (meaning no candidate prefetching will occur).
85 | You may find in some cases that connections can be established more quickly by allowing the ICE agent
86 | to start fetching ICE candidates before you start trying to connect, so that they're already available
87 | for inspection when RTCPeerConnection.setLocalDescription() is called.
88 | Changing the size of the ICE candidate pool may trigger the beginning of ICE gathering.
89 | */
90 |
91 | let iceCandidatePoolSizeConfig = pcConfig?.object(forKey: "iceCandidatePoolSize") as? Int32;
92 | rtcConfiguration.iceCandidatePoolSize = iceCandidatePoolSizeConfig != nil ? iceCandidatePoolSizeConfig! : 0;
93 |
94 | /*
95 | The current ICE transport policy; this must be one of the values from the RTCIceTransportPolicy enum.
96 | If this isn't specified, "all" is assumed.
97 |
98 | - "all" All ICE candidates will be considered.
99 | - "public" Only ICE candidates with public IP addresses will be considered. Removed from the specification's May 13, 2016 working draft
100 | - "relay" Only ICE candidates whose IP addresses are being relayed, such as those being passed through a TURN server, will be considered.
101 | */
102 |
103 | let iceTransportPolicyConfig = pcConfig?.object(forKey: "iceTransportPolicy") as? String;
104 | let iceTransportPolicy = (iceTransportPolicyConfig != nil && allowedIceTransportPolicy[iceTransportPolicyConfig!] != nil) ?
105 | allowedIceTransportPolicy[iceTransportPolicyConfig!] : RTCIceTransportPolicy.all
106 |
107 | rtcConfiguration.iceTransportPolicy = iceTransportPolicy!;
108 |
109 | /*
110 | The RTCP mux policy to use when gathering ICE candidates, in order to support non-multiplexed RTCP.
111 | The value must be one of those from the RTCRtcpMuxPolicy enum. The default is "require".
112 |
113 | - "negotiate" Instructs the ICE agent to gather both RTP and RTCP candidates. If the remote peer can multiplex RTCP,
114 | then RTCP candidates are multiplexed atop the corresponding RTP candidates. Otherwise, both the RTP and RTCP candidates are returned, separately.
115 | - "require" Tells the ICE agent to gather ICE candidates for only RTP, and to multiplex RTCP atop them.
116 | If the remote peer doesn't support RTCP multiplexing, then session negotiation fails.
117 | */
118 |
119 | let rtcpMuxPolicyConfig = pcConfig?.object(forKey: "rtcpMuxPolicy") as? String;
120 | let rtcpMuxPolicy = (rtcpMuxPolicyConfig != nil && allowedRtcpMuxPolicy[rtcpMuxPolicyConfig!] != nil) ?
121 | allowedRtcpMuxPolicy[rtcpMuxPolicyConfig!] : RTCRtcpMuxPolicy.require
122 |
123 | rtcConfiguration.rtcpMuxPolicy = rtcpMuxPolicy!;
124 |
125 | /*
126 | A DOMString which specifies the target peer identity for the RTCPeerConnection.
127 | If this value is set (it defaults to null), the RTCPeerConnection will not connect to a remote peer
128 | unless it can successfully authenticate with the given name.
129 | */
130 | // TODO peerIdentity
131 |
132 | /*
133 | An array of RTCIceServer objects, each describing one server which may be used by the ICE agent;
134 | these are typically STUN and/or TURN servers. If this isn't specified, the connection attempt
135 | will be made with no STUN or TURN server available, which limits the connection to local peers.
136 | */
137 | let iceServers = pcConfig?.object(forKey: "iceServers") as? [NSDictionary]
138 |
139 | if iceServers != nil {
140 | for iceServer: NSDictionary in iceServers! {
141 | let urlsConfig = (iceServer.object(forKey: "url") != nil ?
142 | iceServer.object(forKey: "url") : iceServer.object(forKey: "urls"))
143 |
144 | let urls = urlsConfig is String ? [urlsConfig as? String ?? ""] : urlsConfig as? [String] ?? nil;
145 | let username = iceServer.object(forKey: "username") as? String ?? ""
146 | let password = iceServer.object(forKey: "credential") as? String ?? ""
147 |
148 | if (urls != nil && urls!.count > 0) {
149 | NSLog("iRTCPeerConnectionConfig#init() | adding ICE server [url:'%@', username:'%@', password:'******']",
150 | String(urls![0]), String(username))
151 |
152 | self.rtcConfiguration.iceServers.append(RTCIceServer(
153 | urlStrings: urls!,
154 | username: username,
155 | credential: password
156 | ))
157 | }
158 | }
159 | }
160 | }
161 |
162 | deinit {
163 | NSLog("iRTCPeerConnectionConfig#deinit()")
164 | }
165 |
166 | func getConfiguration() -> RTCConfiguration {
167 | NSLog("iRTCPeerConnectionConfig#getConfiguration()")
168 |
169 | return self.rtcConfiguration
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/iosrtc.js:
--------------------------------------------------------------------------------
1 | //
2 | // iosrtc.js
3 | // WKWebViewRTC
4 | //
5 | // Created by Open Telecom Foundation on 2020/6/30.
6 | // Copyright © 2020 Open Telecom Foundation. All rights reserved.
7 | // The MIT License (MIT)
8 | //
9 |
10 | /**
11 | * Variables.
12 | */
13 |
14 | var
15 | // Dictionary of MediaStreamRenderers.
16 | // - key: MediaStreamRenderer id.
17 | // - value: MediaStreamRenderer.
18 | mediaStreamRenderers = {},
19 |
20 | // Dictionary of MediaStreams.
21 | // - key: MediaStream blobId.
22 | // - value: MediaStream.
23 | mediaStreams = {},
24 |
25 |
26 | /**
27 | * Dependencies.
28 | */
29 | debug = require('debug')('iosrtc'),
30 | exec = require('./IOSExec'),
31 | domready = require('domready'),
32 | getUserMedia = require('./getUserMedia'),
33 | enumerateDevices = require('./enumerateDevices'),
34 | RTCPeerConnection = require('./RTCPeerConnection'),
35 | RTCSessionDescription = require('./RTCSessionDescription'),
36 | RTCIceCandidate = require('./RTCIceCandidate'),
37 | MediaDevices = require('./MediaDevices'),
38 | MediaStream = require('./MediaStream'),
39 | MediaStreamTrack = require('./MediaStreamTrack'),
40 | videoElementsHandler = require('./videoElementsHandler');
41 |
42 |
43 | /**
44 | * Expose the iosrtc object.
45 | */
46 | module.exports = {
47 | // Expose WebRTC classes and functions.
48 | getUserMedia: getUserMedia,
49 | enumerateDevices: enumerateDevices,
50 | getMediaDevices: enumerateDevices, // TMP
51 | RTCPeerConnection: RTCPeerConnection,
52 | RTCSessionDescription: RTCSessionDescription,
53 | RTCIceCandidate: RTCIceCandidate,
54 | MediaDevices: MediaDevices,
55 | MediaStream: MediaStream,
56 | MediaStreamTrack: MediaStreamTrack,
57 |
58 | // Expose a function to refresh current videos rendering a MediaStream.
59 | refreshVideos: videoElementsHandler.refreshVideos,
60 |
61 | // Expose a function to handle a video not yet inserted in the DOM.
62 | observeVideo: videoElementsHandler.observeVideo,
63 |
64 | // Select audio output (earpiece or speaker).
65 | selectAudioOutput: selectAudioOutput,
66 |
67 | // turnOnSpeaker with options
68 | turnOnSpeaker: turnOnSpeaker,
69 |
70 | // Checking permision (audio and camera)
71 | requestPermission: requestPermission,
72 |
73 | // Expose a function to initAudioDevices if needed, sets the audio session active
74 | initAudioDevices: initAudioDevices,
75 |
76 | // Expose a function to pollute window and naigator namespaces.
77 | registerGlobals: registerGlobals,
78 |
79 | // Expose the debug module.
80 | debug: require('debug'),
81 |
82 | // Debug function to see what happens internally.
83 | dump: dump,
84 |
85 | // Debug Stores to see what happens internally.
86 | mediaStreamRenderers: mediaStreamRenderers,
87 | mediaStreams: mediaStreams,
88 | nativeCallback: exec.nativeCallback
89 | };
90 |
91 | // register global variables right after webview loading
92 | registerGlobals();
93 |
94 | MediaStreamTrack.prototype.clone = function () {
95 | return this;
96 | };
97 |
98 | domready(function () {
99 | // Let the MediaStream class and the videoElementsHandler share same MediaStreams container.
100 | MediaStream.setMediaStreams(mediaStreams);
101 | videoElementsHandler(mediaStreams, mediaStreamRenderers);
102 |
103 | // refreshVideos on device orientation change to resize peers video
104 | // while local video will resize du orientation change
105 | window.addEventListener('resize', function () {
106 | videoElementsHandler.refreshVideos();
107 | });
108 | });
109 |
110 | function selectAudioOutput(output) {
111 | debug('selectAudioOutput() | [output:"%s"]', output);
112 |
113 | switch (output) {
114 | case 'earpiece':
115 | exec.execNative(null, null, 'WKWebViewRTC', 'selectAudioOutputEarpiece', []);
116 | break;
117 | case 'speaker':
118 | exec.execNative(null, null, 'WKWebViewRTC', 'selectAudioOutputSpeaker', []);
119 | break;
120 | default:
121 | throw new Error('output must be "earpiece" or "speaker"');
122 | }
123 | }
124 |
125 | function turnOnSpeaker(isTurnOn) {
126 | debug('turnOnSpeaker() | [isTurnOn:"%s"]', isTurnOn);
127 |
128 | exec.execNative(null, null, 'WKWebViewRTC', "RTCTurnOnSpeaker", [isTurnOn]);
129 | }
130 |
131 | function requestPermission(needMic, needCamera, callback) {
132 | debug('requestPermission() | [needMic:"%s", needCamera:"%s"]', needMic, needCamera);
133 |
134 | function ok() {
135 | callback(true);
136 | }
137 |
138 | function error() {
139 | callback(false);
140 | }
141 | exec.execNative(ok, error, 'WKWebViewRTC', "RTCRequestPermission", [needMic, needCamera]);
142 | }
143 |
144 | function initAudioDevices() {
145 | debug('initAudioDevices()');
146 |
147 | exec.execNative(null, null, 'WKWebViewRTC', "initAudioDevices", []);
148 | }
149 |
150 | function callbackifyMethod(originalMethod) {
151 | return function (arg) { // jshint ignore:line
152 | var success, failure,
153 | originalArgs = Array.prototype.slice.call(arguments);
154 |
155 | var callbackArgs = [];
156 | originalArgs.forEach(function (arg) {
157 | if (typeof arg === 'function') {
158 | if (!success) {
159 | success = arg;
160 | } else {
161 | failure = arg;
162 | }
163 | } else {
164 | callbackArgs.push(arg);
165 | }
166 | });
167 |
168 | var promiseResult = originalMethod.apply(this, callbackArgs);
169 |
170 | // Only apply then if callback success available
171 | if (typeof success === 'function') {
172 | promiseResult = promiseResult.then(success);
173 | }
174 |
175 | // Only apply catch if callback failure available
176 | if (typeof failure === 'function') {
177 | promiseResult = promiseResult.catch(failure);
178 | }
179 |
180 | return promiseResult;
181 | };
182 | }
183 |
184 | function callbackifyPrototype(proto, method) {
185 | var originalMethod = proto[method];
186 | proto[method] = callbackifyMethod(originalMethod);
187 | }
188 |
189 | function restoreCallbacksSupport() {
190 | debug('restoreCallbacksSupport()');
191 | getUserMedia = callbackifyMethod(getUserMedia);
192 | enumerateDevices = callbackifyMethod(enumerateDevices);
193 | callbackifyPrototype(RTCPeerConnection.prototype, 'createAnswer');
194 | callbackifyPrototype(RTCPeerConnection.prototype, 'createOffer');
195 | callbackifyPrototype(RTCPeerConnection.prototype, 'setRemoteDescription');
196 | callbackifyPrototype(RTCPeerConnection.prototype, 'setLocalDescription');
197 | callbackifyPrototype(RTCPeerConnection.prototype, 'addIceCandidate');
198 | callbackifyPrototype(RTCPeerConnection.prototype, 'getStats');
199 | }
200 |
201 | function registerGlobals(doNotRestoreCallbacksSupport) {
202 | debug('registerGlobals()');
203 |
204 | if (!global.navigator) {
205 | global.navigator = {};
206 | }
207 |
208 | // Restore Callback support
209 | if (!doNotRestoreCallbacksSupport) {
210 | restoreCallbacksSupport();
211 | }
212 |
213 | navigator.getUserMedia = getUserMedia;
214 | navigator.webkitGetUserMedia = getUserMedia;
215 |
216 | // Prevent WebRTC-adapter to overide navigator.mediaDevices after shim is applied since ios 14.3
217 | if (!(navigator.mediaDevices instanceof MediaDevices)) {
218 | Object.defineProperty(
219 | navigator,
220 | 'mediaDevices',
221 | {
222 | value: new MediaDevices(),
223 | writable: false
224 | },
225 | {
226 | enumerable: false,
227 | configurable: false,
228 | writable: false,
229 | value: 'static'
230 | });
231 | }
232 |
233 | window.RTCPeerConnection = RTCPeerConnection;
234 | window.webkitRTCPeerConnection = RTCPeerConnection;
235 | window.RTCSessionDescription = RTCSessionDescription;
236 | window.RTCIceCandidate = RTCIceCandidate;
237 | window.MediaStream = MediaStream;
238 | window.webkitMediaStream = MediaStream;
239 | window.MediaStreamTrack = MediaStreamTrack;
240 |
241 | // Apply CanvasRenderingContext2D.drawImage monkey patch
242 | var drawImage = CanvasRenderingContext2D.prototype.drawImage;
243 | CanvasRenderingContext2D.prototype.drawImage = function (arg) {
244 | var args = Array.prototype.slice.call(arguments);
245 | var context = this;
246 | if (arg instanceof HTMLVideoElement && arg.render) {
247 | arg.render.save(function (data) {
248 | var img = new window.Image();
249 | img.addEventListener("load", function () {
250 | args.splice(0, 1, img);
251 | drawImage.apply(context, args);
252 | img.src = null;
253 | });
254 | img.setAttribute("src", "data:image/jpg;base64," + data);
255 | });
256 | } else {
257 | return drawImage.apply(context, args);
258 | }
259 | };
260 | }
261 |
262 | function dump() {
263 | exec.execNative(null, null, 'WKWebViewRTC', 'dump', []);
264 | }
265 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Classes/iRTCDataChannel.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | import Foundation
11 | import WebRTC
12 |
13 | // FIXME: comparison operators with optionals were removed from the Swift Standard Libary.
14 | // Consider refactoring the code to use the non-optional operators.
15 | fileprivate func < (lhs: T?, rhs: T?) -> Bool {
16 | switch (lhs, rhs) {
17 | case let (l?, r?):
18 | return l < r
19 | case (nil, _?):
20 | return true
21 | default:
22 | return false
23 | }
24 | }
25 |
26 | // FIXME: comparison operators with optionals were removed from the Swift Standard Libary.
27 | // Consider refactoring the code to use the non-optional operators.
28 | fileprivate func > (lhs: T?, rhs: T?) -> Bool {
29 | switch (lhs, rhs) {
30 | case let (l?, r?):
31 | return l > r
32 | default:
33 | return rhs < lhs
34 | }
35 | }
36 |
37 | class iRTCDataChannel : NSObject, RTCDataChannelDelegate {
38 | var rtcDataChannel: RTCDataChannel?
39 | var eventListener: ((_ data: NSDictionary) -> Void)?
40 | var eventListenerForBinaryMessage: ((_ data: Data) -> Void)?
41 | var lostStates = Array()
42 | var lostMessages = Array()
43 |
44 | /**
45 | * Constructor for pc.createDataChannel().
46 | */
47 | init(
48 | rtcPeerConnection: RTCPeerConnection,
49 | label: String,
50 | options: NSDictionary?,
51 | eventListener: @escaping (_ data: NSDictionary) -> Void,
52 | eventListenerForBinaryMessage: @escaping (_ data: Data) -> Void
53 | ) {
54 | NSLog("iRTCDataChannel#init()")
55 |
56 | self.eventListener = eventListener
57 | self.eventListenerForBinaryMessage = eventListenerForBinaryMessage
58 |
59 | let rtcDataChannelInit = RTCDataChannelConfiguration.init()
60 |
61 | if options?.object(forKey: "ordered") != nil {
62 | rtcDataChannelInit.isOrdered = options!.object(forKey: "ordered") as! Bool
63 | }
64 |
65 | if options?.object(forKey: "maxPacketLifeTime") != nil {
66 | // TODO: rtcDataChannel.maxRetransmitTime always reports 0.
67 | rtcDataChannelInit.maxRetransmitTimeMs = options!.object(forKey: "maxPacketLifeTime") as! Int
68 | }
69 |
70 | if options?.object(forKey: "maxRetransmits") != nil {
71 | rtcDataChannelInit.maxRetransmits = options!.object(forKey: "maxRetransmits") as! Int32
72 | }
73 |
74 | // TODO: error: expected member name following '.'
75 | // https://code.google.com/p/webrtc/issues/detail?id=4614
76 | // if options?.objectForKey("protocol") != nil {
77 | // rtcDataChannelInit.protocol = options!.objectForKey("protocol") as! String
78 | // }
79 | if options?.object(forKey: "protocol") != nil {
80 | rtcDataChannelInit.`protocol` = options!.object(forKey: "protocol") as! String
81 | }
82 |
83 | if options?.object(forKey: "negotiated") != nil {
84 | rtcDataChannelInit.isNegotiated = options!.object(forKey: "negotiated") as! Bool
85 | }
86 |
87 | if options?.object(forKey: "id") != nil {
88 | rtcDataChannelInit.channelId = options!.object(forKey: "id") as! Int32
89 | }
90 |
91 | self.rtcDataChannel = rtcPeerConnection.dataChannel(forLabel: label, configuration: rtcDataChannelInit)
92 |
93 | if self.rtcDataChannel == nil {
94 | NSLog("iRTCDataChannel#init() | rtcPeerConnection.createDataChannelWithLabel() failed")
95 | return
96 | }
97 |
98 | // Report definitive data to update the JS instance.
99 | self.eventListener!([
100 | "type": "new",
101 | "channel": [
102 | "ordered": self.rtcDataChannel!.isOrdered,
103 | "maxPacketLifeTime": self.rtcDataChannel!.maxPacketLifeTime,
104 | "maxRetransmits": self.rtcDataChannel!.maxRetransmits,
105 | "protocol": self.rtcDataChannel!.`protocol`,
106 | "negotiated": self.rtcDataChannel!.isNegotiated,
107 | "id": self.rtcDataChannel!.channelId,
108 | "readyState": iRTCDataChannel.stateToString(state: self.rtcDataChannel!.readyState),
109 | "bufferedAmount": self.rtcDataChannel!.bufferedAmount
110 | ]
111 | ])
112 | }
113 |
114 | deinit {
115 | NSLog("iRTCDataChannel#deinit()")
116 | }
117 |
118 | /**
119 | * Constructor for pc.ondatachannel event.
120 | */
121 | init(rtcDataChannel: RTCDataChannel) {
122 | NSLog("iRTCDataChannel#init()")
123 |
124 | self.rtcDataChannel = rtcDataChannel
125 | }
126 |
127 | func run() {
128 | NSLog("iRTCDataChannel#run()")
129 |
130 | self.rtcDataChannel!.delegate = self
131 |
132 | //if data channel is created after there is a connection,
133 | // we need to dispatch its current state.
134 | if (self.rtcDataChannel?.readyState != RTCDataChannelState.connecting) {
135 | dataChannelDidChangeState(self.rtcDataChannel!);
136 | }
137 | }
138 |
139 | func setListener(
140 | _ eventListener: @escaping (_ data: NSDictionary) -> Void,
141 | eventListenerForBinaryMessage: @escaping (_ data: Data) -> Void
142 | ) {
143 | NSLog("iRTCDataChannel#setListener()")
144 |
145 | self.eventListener = eventListener
146 | self.eventListenerForBinaryMessage = eventListenerForBinaryMessage
147 |
148 | for readyState in self.lostStates {
149 | self.eventListener!([
150 | "type": "statechange",
151 | "readyState": readyState
152 | ])
153 | }
154 | self.lostStates.removeAll()
155 |
156 | for buffer in self.lostMessages {
157 | self.emitReceivedMessage(buffer)
158 | }
159 | self.lostMessages.removeAll()
160 | }
161 |
162 | func sendString(
163 | _ data: String,
164 | callback: (_ data: NSDictionary) -> Void
165 | ) {
166 | NSLog("iRTCDataChannel#sendString()")
167 |
168 | let buffer = RTCDataBuffer(
169 | data: (data.data(using: String.Encoding.utf8))!,
170 | isBinary: false
171 | )
172 |
173 | let result = self.rtcDataChannel!.sendData(buffer)
174 | if !result {
175 | NSLog("iRTCDataChannel#sendString() | RTCDataChannel#sendData() failed")
176 | }
177 | }
178 |
179 | func sendBinary(
180 | _ data: Data,
181 | callback: (_ data: NSDictionary) -> Void
182 | ) {
183 | NSLog("iRTCDataChannel#sendBinary()")
184 |
185 | let buffer = RTCDataBuffer(
186 | data: data,
187 | isBinary: true
188 | )
189 |
190 | let result = self.rtcDataChannel!.sendData(buffer)
191 | if !result {
192 | NSLog("iRTCDataChannel#sendBinary() | RTCDataChannel#sendData() failed")
193 | }
194 | }
195 |
196 | func close() {
197 | NSLog("iRTCDataChannel#close()")
198 |
199 | self.rtcDataChannel!.close()
200 | }
201 |
202 | static func stateToString(state: RTCDataChannelState) -> String {
203 | switch state {
204 | case RTCDataChannelState.connecting:
205 | return "connecting"
206 | case RTCDataChannelState.open:
207 | return "open"
208 | case RTCDataChannelState.closing:
209 | return "closing"
210 | case RTCDataChannelState.closed:
211 | return "closed"
212 | }
213 | }
214 |
215 | func getState() -> String {
216 | return iRTCDataChannel.stateToString(state: self.rtcDataChannel!.readyState)
217 | }
218 |
219 | /**
220 | * Methods inherited from RTCDataChannelDelegate.
221 | */
222 |
223 | /** The data channel state changed. */
224 | func dataChannelDidChangeState(_ channel: RTCDataChannel) {
225 | let state_str = self.getState()
226 |
227 | NSLog("iRTCDataChannel | state changed [state:%@]", String(describing: state_str))
228 |
229 | if self.eventListener != nil {
230 | self.eventListener!([
231 | "type": "statechange",
232 | "readyState": state_str
233 | ])
234 | } else {
235 | // It may happen that the eventListener is not yet set, so store the lost states.
236 | self.lostStates.append(state_str)
237 | }
238 | }
239 |
240 | /** The data channel successfully received a data buffer. */
241 | func dataChannel(_ channel: RTCDataChannel, didReceiveMessageWith buffer: RTCDataBuffer) {
242 | if !buffer.isBinary {
243 | NSLog("iRTCDataChannel | utf8 message received")
244 |
245 | if self.eventListener != nil {
246 | self.emitReceivedMessage(buffer)
247 | } else {
248 | // It may happen that the eventListener is not yet set, so store the lost messages.
249 | self.lostMessages.append(buffer)
250 | }
251 | } else {
252 | NSLog("iRTCDataChannel | binary message received")
253 |
254 | if self.eventListenerForBinaryMessage != nil {
255 | self.emitReceivedMessage(buffer)
256 | } else {
257 | // It may happen that the eventListener is not yet set, so store the lost messages.
258 | self.lostMessages.append(buffer)
259 | }
260 | }
261 | }
262 |
263 | /** The data channel's |bufferedAmount| changed. */
264 | func dataChannel(_ channel: RTCDataChannel, didChangeBufferedAmount amount: UInt64) {
265 | NSLog("iRTCDataChannel | didChangeBufferedAmount %d", amount)
266 |
267 | self.eventListener!([
268 | "type": "bufferedamount",
269 | "bufferedAmount": amount
270 | ])
271 |
272 | }
273 |
274 | func emitReceivedMessage(_ buffer: RTCDataBuffer) {
275 | if !buffer.isBinary {
276 | let message = NSString(
277 | data: buffer.data,
278 | encoding: String.Encoding.utf8.rawValue
279 | )
280 |
281 | self.eventListener!([
282 | "type": "message",
283 | "message": message! as String
284 | ])
285 | } else {
286 | self.eventListenerForBinaryMessage!(buffer.data)
287 | }
288 | }
289 | }
290 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/MediaStreamRenderer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * cordova-plugin-iosrtc v6.0.17
3 | * Cordova iOS plugin exposing the ̶f̶u̶l̶l̶ WebRTC W3C JavaScript APIs.
4 | * Copyright 2015-2017 eFace2Face, Inc. (https://eface2face.com)
5 | * Copyright 2015-2019 BasqueVoIPMafia (https://github.com/BasqueVoIPMafia)
6 | * Copyright 2019 Cordova-RTC (https://github.com/cordova-rtc)
7 | * The MIT License (MIT)
8 | */
9 |
10 | /**
11 | * Expose the MediaStreamRenderer class.
12 | */
13 | module.exports = MediaStreamRenderer;
14 |
15 |
16 | /**
17 | * Dependencies.
18 | */
19 | var
20 | debug = require('debug')('iosrtc:MediaStreamRenderer'),
21 | exec = require('./IOSExec'),
22 | randomNumber = require('random-number').generator({min: 10000, max: 99999, integer: true}),
23 | EventTarget = require('./EventTarget'),
24 | MediaStream = require('./MediaStream');
25 |
26 |
27 | function MediaStreamRenderer(element) {
28 | debug('new() | [element:"%s"]', element);
29 |
30 | var self = this;
31 |
32 | // Make this an EventTarget.
33 | EventTarget.call(this);
34 |
35 | if (!(element instanceof HTMLElement)) {
36 | throw new Error('a valid HTMLElement is required');
37 | }
38 |
39 | // Public atributes.
40 | this.element = element;
41 | this.stream = undefined;
42 | this.videoWidth = undefined;
43 | this.videoHeight = undefined;
44 |
45 | // Private attributes.
46 | this.id = randomNumber();
47 |
48 | function onResultOK(data) {
49 | onEvent.call(self, data);
50 | }
51 |
52 | exec.execNative(onResultOK, null, 'WKWebViewRTC', 'new_MediaStreamRenderer', [this.id]);
53 |
54 | this.refresh();
55 |
56 | // TODO cause video resizing jiggling add semaphore
57 | //this.refreshInterval = setInterval(function () {
58 | // self.refresh(self);
59 | //}, 500);
60 |
61 | element.render = this;
62 | }
63 |
64 | MediaStreamRenderer.prototype = Object.create(EventTarget.prototype);
65 | MediaStreamRenderer.prototype.constructor = MediaStreamRenderer;
66 |
67 | MediaStreamRenderer.prototype.render = function (stream) {
68 | debug('render() [stream:%o]', stream);
69 |
70 | var self = this;
71 |
72 | if (!(stream instanceof MediaStream.originalMediaStream)) {
73 | throw new Error('render() requires a MediaStream instance as argument');
74 | }
75 |
76 | self.stream = stream;
77 |
78 | exec.execNative(null, null, 'WKWebViewRTC', 'MediaStreamRenderer_render', [self.id, stream.id]);
79 |
80 | // Subscribe to 'update' event so we call native mediaStreamChanged() on it.
81 | stream.addEventListener('update', function () {
82 | if (self.stream !== stream) {
83 | return;
84 | }
85 |
86 | debug('MediaStream emits "update", calling native mediaStreamChanged()');
87 |
88 | exec.execNative(null, null, 'WKWebViewRTC', 'MediaStreamRenderer_mediaStreamChanged', [self.id]);
89 | });
90 |
91 | // Subscribe to 'inactive' event and emit "close" so the video element can react.
92 | stream.addEventListener('inactive', function () {
93 | if (self.stream !== stream) {
94 | return;
95 | }
96 |
97 | debug('MediaStream emits "inactive", emiting "close" and closing this MediaStreamRenderer');
98 |
99 | self.dispatchEvent(new Event('close'));
100 | self.close();
101 | });
102 |
103 | if (stream.connected) {
104 | connected();
105 | // Otherwise subscribe to 'connected' event to emulate video elements events.
106 | } else {
107 | stream.addEventListener('connected', function () {
108 | if (self.stream !== stream) {
109 | return;
110 | }
111 |
112 | connected();
113 | });
114 | }
115 |
116 | function connected() {
117 | // Emit video events.
118 | self.element.dispatchEvent(new Event('loadedmetadata'));
119 | self.element.dispatchEvent(new Event('loadeddata'));
120 | self.element.dispatchEvent(new Event('canplay'));
121 | self.element.dispatchEvent(new Event('canplaythrough'));
122 | }
123 | };
124 |
125 | MediaStreamRenderer.prototype.save = function (callback) {
126 | debug('save()');
127 |
128 | if (!this.stream) {
129 | callback(null);
130 | return;
131 | }
132 |
133 | function onResultOK(data) {
134 | callback(data);
135 | }
136 |
137 | function onResultError() {
138 | callback(null);
139 | }
140 |
141 | exec.execNative(onResultOK, onResultError, 'WKWebViewRTC', 'MediaStreamRenderer_save', [this.id]);
142 | };
143 |
144 | MediaStreamRenderer.prototype.refresh = function () {
145 | debug('refresh()');
146 |
147 | var elementPositionAndSize = getElementPositionAndSize.call(this),
148 | computedStyle,
149 | videoRatio,
150 | elementRatio,
151 | elementLeft = elementPositionAndSize.left,
152 | elementTop = elementPositionAndSize.top,
153 | elementWidth = elementPositionAndSize.width,
154 | elementHeight = elementPositionAndSize.height,
155 | videoViewWidth,
156 | videoViewHeight,
157 | visible,
158 | opacity,
159 | zIndex,
160 | mirrored,
161 | objectFit,
162 | clip,
163 | borderRadius,
164 | paddingTop,
165 | paddingBottom,
166 | paddingLeft,
167 | paddingRight,
168 | backgroundColorRgba,
169 | self = this;
170 |
171 | computedStyle = window.getComputedStyle(this.element);
172 |
173 | // get background color
174 | backgroundColorRgba = computedStyle.backgroundColor
175 | .replace(/rgba?\((.*)\)/, '$1')
176 | .split(',')
177 | .map(function (x) {
178 | return x.trim();
179 | });
180 | backgroundColorRgba[3] = '0';
181 | this.element.style.backgroundColor = 'rgba(' + backgroundColorRgba.join(',') + ')';
182 | backgroundColorRgba.length = 3;
183 |
184 | // get padding values
185 | paddingTop = parseInt(computedStyle.paddingTop) | 0;
186 | paddingBottom = parseInt(computedStyle.paddingBottom) | 0;
187 | paddingLeft = parseInt(computedStyle.paddingLeft) | 0;
188 | paddingRight = parseInt(computedStyle.paddingRight) | 0;
189 |
190 | // fix position according to padding
191 | elementLeft += paddingLeft;
192 | elementTop += paddingTop;
193 |
194 | // fix width and height according to padding
195 | elementWidth -= paddingLeft + paddingRight;
196 | elementHeight -= paddingTop + paddingBottom;
197 |
198 | videoViewWidth = elementWidth;
199 | videoViewHeight = elementHeight;
200 |
201 | // visible
202 | if (computedStyle.visibility === 'hidden') {
203 | visible = false;
204 | } else {
205 | visible = !!this.element.offsetHeight; // Returns 0 if element or any parent is hidden.
206 | }
207 |
208 | // opacity
209 | opacity = parseFloat(computedStyle.opacity);
210 |
211 | // zIndex
212 | zIndex = parseFloat(computedStyle.zIndex) || parseFloat(this.element.style.zIndex) || 0;
213 |
214 | // mirrored (detect "-webkit-transform: scaleX(-1);" or equivalent)
215 | if (computedStyle.transform === 'matrix(-1, 0, 0, 1, 0, 0)' ||
216 | computedStyle['-webkit-transform'] === 'matrix(-1, 0, 0, 1, 0, 0)') {
217 | mirrored = true;
218 | } else {
219 | mirrored = false;
220 | }
221 |
222 | // objectFit ('contain' is set as default value)
223 | objectFit = computedStyle.objectFit || 'contain';
224 |
225 | // clip
226 | if (objectFit === 'none') {
227 | clip = false;
228 | } else {
229 | clip = true;
230 | }
231 |
232 | // borderRadius
233 | borderRadius = parseFloat(computedStyle.borderRadius);
234 | if (/%$/.test(borderRadius)) {
235 | borderRadius = Math.min(elementHeight, elementWidth) * borderRadius;
236 | }
237 |
238 | /**
239 | * No video yet, so just update the UIView with the element settings.
240 | */
241 |
242 | if (!this.videoWidth || !this.videoHeight) {
243 | debug('refresh() | no video track yet');
244 |
245 | nativeRefresh.call(this);
246 | return;
247 | }
248 |
249 | videoRatio = this.videoWidth / this.videoHeight;
250 |
251 | /**
252 | * Element has no width and/or no height.
253 | */
254 |
255 | if (!elementWidth || !elementHeight) {
256 | debug('refresh() | video element has 0 width and/or 0 height');
257 |
258 | nativeRefresh.call(this);
259 | return;
260 | }
261 |
262 | /**
263 | * Set video view position and size.
264 | */
265 |
266 | elementRatio = elementWidth / elementHeight;
267 |
268 | switch (objectFit) {
269 | case 'cover':
270 | // The element has higher or equal width/height ratio than the video.
271 | if (elementRatio >= videoRatio) {
272 | videoViewWidth = elementWidth;
273 | videoViewHeight = videoViewWidth / videoRatio;
274 | // The element has lower width/height ratio than the video.
275 | } else if (elementRatio < videoRatio) {
276 | videoViewHeight = elementHeight;
277 | videoViewWidth = videoViewHeight * videoRatio;
278 | }
279 | break;
280 |
281 | case 'fill':
282 | videoViewHeight = elementHeight;
283 | videoViewWidth = elementWidth;
284 | break;
285 |
286 | case 'none':
287 | videoViewHeight = this.videoHeight;
288 | videoViewWidth = this.videoWidth;
289 | break;
290 |
291 | case 'scale-down':
292 | // Same as 'none'.
293 | if (this.videoWidth <= elementWidth && this.videoHeight <= elementHeight) {
294 | videoViewHeight = this.videoHeight;
295 | videoViewWidth = this.videoWidth;
296 | // Same as 'contain'.
297 | } else {
298 | // The element has higher or equal width/height ratio than the video.
299 | if (elementRatio >= videoRatio) {
300 | videoViewHeight = elementHeight;
301 | videoViewWidth = videoViewHeight * videoRatio;
302 | // The element has lower width/height ratio than the video.
303 | } else if (elementRatio < videoRatio) {
304 | videoViewWidth = elementWidth;
305 | videoViewHeight = videoViewWidth / videoRatio;
306 | }
307 | }
308 | break;
309 |
310 | // 'contain'.
311 | default:
312 | objectFit = 'contain';
313 | // The element has higher or equal width/height ratio than the video.
314 | if (elementRatio >= videoRatio) {
315 | videoViewHeight = elementHeight;
316 | videoViewWidth = videoViewHeight * videoRatio;
317 | // The element has lower width/height ratio than the video.
318 | } else if (elementRatio < videoRatio) {
319 | videoViewWidth = elementWidth;
320 | videoViewHeight = videoViewWidth / videoRatio;
321 | }
322 | break;
323 | }
324 |
325 | nativeRefresh.call(this);
326 |
327 | function hash(str) {
328 | var hash = 5381,
329 | i = str.length;
330 |
331 | while (i) {
332 | hash = (hash * 33) ^ str.charCodeAt(--i);
333 | }
334 |
335 | return hash >>> 0;
336 | }
337 |
338 | function nativeRefresh() {
339 | var data = {
340 | elementLeft: Math.round(elementLeft),
341 | elementTop: Math.round(elementTop),
342 | elementWidth: Math.round(elementWidth),
343 | elementHeight: Math.round(elementHeight),
344 | videoViewWidth: Math.round(videoViewWidth),
345 | videoViewHeight: Math.round(videoViewHeight),
346 | visible: visible,
347 | backgroundColor: backgroundColorRgba.join(','),
348 | opacity: opacity,
349 | zIndex: zIndex,
350 | mirrored: mirrored,
351 | objectFit: objectFit,
352 | clip: clip,
353 | borderRadius: borderRadius
354 | },
355 | newRefreshCached = hash(JSON.stringify(data));
356 |
357 | if (newRefreshCached === self.refreshCached) {
358 | return;
359 | }
360 |
361 | self.refreshCached = newRefreshCached;
362 |
363 | debug('refresh() | [data:%o]', data);
364 |
365 | exec.execNative(null, null, 'WKWebViewRTC', 'MediaStreamRenderer_refresh', [this.id, data]);
366 | }
367 | };
368 |
369 |
370 | MediaStreamRenderer.prototype.close = function () {
371 | debug('close()');
372 |
373 | if (!this.stream) {
374 | return;
375 | }
376 | this.stream = undefined;
377 |
378 | exec.execNative(null, null, 'WKWebViewRTC', 'MediaStreamRenderer_close', [this.id]);
379 | if (this.refreshInterval) {
380 | clearInterval(this.refreshInterval);
381 | delete this.refreshInterval;
382 | }
383 | };
384 |
385 |
386 | /**
387 | * Private API.
388 | */
389 |
390 |
391 | function onEvent(data) {
392 | var type = data.type,
393 | event;
394 |
395 | debug('onEvent() | [type:%s, data:%o]', type, data);
396 |
397 | switch (type) {
398 | case 'videoresize':
399 | this.videoWidth = data.size.width;
400 | this.videoHeight = data.size.height;
401 | this.refresh();
402 |
403 | event = new Event(type);
404 | event.videoWidth = data.size.width;
405 | event.videoHeight = data.size.height;
406 | this.dispatchEvent(event);
407 |
408 | break;
409 | }
410 | }
411 |
412 |
413 | function getElementPositionAndSize() {
414 | var rect = this.element.getBoundingClientRect();
415 |
416 | return {
417 | left: rect.left + this.element.clientLeft,
418 | top: rect.top + this.element.clientTop,
419 | width: this.element.clientWidth,
420 | height: this.element.clientHeight
421 | };
422 | }
423 |
--------------------------------------------------------------------------------
/WKWebViewRTC/Js/src/videoElementsHandler.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Expose a function that must be called when the library is loaded.
3 | * And also a helper function.
4 | */
5 | module.exports = videoElementsHandler;
6 | module.exports.observeVideo = observeVideo;
7 | module.exports.refreshVideos = refreshVideos;
8 |
9 |
10 | /**
11 | * Dependencies.
12 | */
13 | var
14 | debug = require('debug')('iosrtc:videoElementsHandler'),
15 | MediaStreamRenderer = require('./MediaStreamRenderer'),
16 |
17 |
18 | /**
19 | * Local variables.
20 | */
21 |
22 | // RegExp for Blob URI.
23 | BLOB_INTERNAL_URI_REGEX = new RegExp(/^blob:/),
24 |
25 | // Dictionary of MediaStreamRenderers (provided via module argument).
26 | // - key: MediaStreamRenderer id.
27 | // - value: MediaStreamRenderer.
28 | mediaStreamRenderers,
29 |
30 | // Dictionary of MediaStreams (provided via module argument).
31 | // - key: MediaStream blobId.
32 | // - value: MediaStream.
33 | mediaStreams,
34 |
35 | // Video element mutation observer.
36 | videoObserver = new MutationObserver(function (mutations) {
37 | var i, numMutations, mutation, video;
38 |
39 | for (i = 0, numMutations = mutations.length; i < numMutations; i++) {
40 | mutation = mutations[i];
41 |
42 | // HTML video element.
43 | video = mutation.target;
44 |
45 | // .srcObject removed.
46 | if (!video.srcObject && !video.src) {
47 | // If this video element was previously handling a MediaStreamRenderer, release it.
48 | releaseMediaStreamRenderer(video);
49 | continue;
50 | }
51 |
52 | handleVideo(video);
53 | }
54 | }),
55 |
56 | // DOM mutation observer.
57 | domObserver = new MutationObserver(function (mutations) {
58 | var
59 | i, numMutations, mutation,
60 | j, numNodes, node;
61 |
62 | for (i = 0, numMutations = mutations.length; i < numMutations; i++) {
63 | mutation = mutations[i];
64 |
65 | // Check if there has been addition or deletion of nodes.
66 | if (mutation.type !== 'childList') {
67 | continue;
68 | }
69 |
70 | // Check added nodes.
71 | for (j = 0, numNodes = mutation.addedNodes.length; j < numNodes; j++) {
72 | node = mutation.addedNodes[j];
73 |
74 | checkNewNode(node);
75 | }
76 |
77 | // Check removed nodes.
78 | for (j = 0, numNodes = mutation.removedNodes.length; j < numNodes; j++) {
79 | node = mutation.removedNodes[j];
80 |
81 | checkRemovedNode(node);
82 | }
83 | }
84 |
85 | function checkNewNode(node) {
86 | var j, childNode;
87 |
88 | if (node.nodeName === 'VIDEO') {
89 | debug('new video element added');
90 |
91 | // Avoid same node firing more than once (really, may happen in some cases).
92 | if (node._iosrtcVideoHandled) {
93 | return;
94 | }
95 | node._iosrtcVideoHandled = true;
96 |
97 | // Observe changes in the video element.
98 | observeVideo(node);
99 | } else {
100 | for (j = 0; j < node.childNodes.length; j++) {
101 | childNode = node.childNodes.item(j);
102 |
103 | checkNewNode(childNode);
104 | }
105 | }
106 | }
107 |
108 | function checkRemovedNode(node) {
109 | var j, childNode;
110 |
111 | if (node.nodeName === 'VIDEO') {
112 | debug('video element removed');
113 |
114 | // If this video element was previously handling a MediaStreamRenderer, release it.
115 | releaseMediaStreamRenderer(node);
116 | delete node._iosrtcVideoHandled;
117 | } else {
118 | for (j = 0; j < node.childNodes.length; j++) {
119 | childNode = node.childNodes.item(j);
120 |
121 | checkRemovedNode(childNode);
122 | }
123 | }
124 | }
125 | });
126 |
127 | function refreshVideos() {
128 | debug('refreshVideos()');
129 |
130 | var id;
131 |
132 | for (id in mediaStreamRenderers) {
133 | if (mediaStreamRenderers.hasOwnProperty(id)) {
134 | mediaStreamRenderers[id].refresh();
135 | }
136 | }
137 | }
138 |
139 | function videoElementsHandler(_mediaStreams, _mediaStreamRenderers) {
140 | var
141 | existingVideos = document.querySelectorAll('video'),
142 | i, len, video;
143 |
144 | mediaStreams = _mediaStreams;
145 | mediaStreamRenderers = _mediaStreamRenderers;
146 |
147 | // Search the whole document for already existing HTML video elements and observe them.
148 | for (i = 0, len = existingVideos.length; i < len; i++) {
149 | video = existingVideos.item(i);
150 |
151 | debug('video element found');
152 |
153 | observeVideo(video);
154 | }
155 |
156 | // Observe the whole document for additions of new HTML video elements and observe them.
157 | domObserver.observe(document, {
158 | // Set to true if additions and removals of the target node's child elements (including text nodes) are to
159 | // be observed.
160 | childList: true,
161 | // Set to true if mutations to target's attributes are to be observed.
162 | attributes: false,
163 | // Set to true if mutations to target's data are to be observed.
164 | characterData: false,
165 | // Set to true if mutations to not just target, but also target's descendants are to be observed.
166 | subtree: true,
167 | // Set to true if attributes is set to true and target's attribute value before the mutation needs to be
168 | // recorded.
169 | attributeOldValue: false,
170 | // Set to true if characterData is set to true and target's data before the mutation needs to be recorded.
171 | characterDataOldValue: false
172 | // Set to an array of attribute local names (without namespace) if not all attribute mutations need to be
173 | // observed.
174 | // attributeFilter:
175 | });
176 | }
177 |
178 |
179 | function observeVideo(video) {
180 | debug('observeVideo()');
181 |
182 | // If the video already has a srcObject property but is not yet handled by the plugin
183 | // then handle it now.
184 | var hasStream = video.srcObject || video.src;
185 | if (hasStream && !video._iosrtcMediaStreamRendererId) {
186 | handleVideo(video);
187 | }
188 |
189 | // Add .srcObject observer to the video element.
190 | videoObserver.observe(video, {
191 | // Set to true if additions and removals of the target node's child elements (including text
192 | // nodes) are to be observed.
193 | childList: false,
194 | // Set to true if mutations to target's attributes are to be observed.
195 | attributes: true,
196 | // Set to true if mutations to target's data are to be observed.
197 | characterData: false,
198 | // Set to true if mutations to not just target, but also target's descendants are to be observed.
199 | subtree: false,
200 | // Set to true if attributes is set to true and target's attribute value before the mutation
201 | // needs to be recorded.
202 | attributeOldValue: false,
203 | // Set to true if characterData is set to true and target's data before the mutation needs to be
204 | // recorded.
205 | characterDataOldValue: false,
206 | // Set to an array of attribute local names (without namespace) if not all attribute mutations
207 | // need to be observed.
208 | // srcObject DO not trigger MutationObserver
209 | attributeFilter: ['srcObject', 'src']
210 | });
211 |
212 | // MutationObserver fail to trigger when using srcObject on ony tested browser.
213 | // But video.srcObject = new MediaStream() will trigger onloadstart and
214 | // video.srcObject = null will trigger onemptied events.
215 |
216 | video.addEventListener('loadstart', function () {
217 |
218 | var hasStream = video.srcObject || video.src;
219 |
220 | if (hasStream && !video._iosrtcMediaStreamRendererId) {
221 | // If this video element was NOT previously handling a MediaStreamRenderer, release it.
222 | handleVideo(video);
223 | } else if (hasStream && video._iosrtcMediaStreamRendererId) {
224 | // The video element has received a new srcObject.
225 | var stream = video.srcObject;
226 | if (stream && typeof stream.getBlobId === 'function') {
227 | // Release previous renderer
228 | releaseMediaStreamRenderer(video);
229 | // Install new renderer
230 | provideMediaStreamRenderer(video, stream.getBlobId());
231 | }
232 | }
233 | });
234 |
235 | video.addEventListener('emptied', function () {
236 | var hasStream = video.srcObject || video.src;
237 | if (!hasStream && video._iosrtcMediaStreamRendererId) {
238 | // If this video element was previously handling a MediaStreamRenderer, release it.
239 | releaseMediaStreamRenderer(video);
240 | }
241 | });
242 |
243 | // Intercept video 'error' events if it's due to the attached MediaStream.
244 | video.addEventListener('error', function (event) {
245 | if (video.error.code === window.MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED && BLOB_INTERNAL_URI_REGEX.test(video.src)) {
246 | debug('stopping "error" event propagation for video element');
247 |
248 | event.stopImmediatePropagation();
249 | }
250 | });
251 | }
252 |
253 |
254 | /**
255 | * Private API.
256 | */
257 |
258 | function handleVideo(video) {
259 | var
260 | stream;
261 |
262 | // The app has set video.srcObject.
263 | if (video.srcObject) {
264 | stream = video.srcObject;
265 | if (stream && typeof stream.getBlobId === 'function') {
266 |
267 | if (!stream.getBlobId()) {
268 | // If this video element was previously handling a MediaStreamRenderer, release it.
269 | releaseMediaStreamRenderer(video);
270 |
271 | return;
272 | }
273 |
274 | provideMediaStreamRenderer(video, stream.getBlobId());
275 | }
276 | } else if (video.src) {
277 |
278 | var xhr = new XMLHttpRequest();
279 | xhr.open('GET', video.src, true);
280 | xhr.responseType = 'blob';
281 | xhr.onload = function () {
282 | if (xhr.status !== 200) {
283 | // If this video element was previously handling a MediaStreamRenderer, release it.
284 | releaseMediaStreamRenderer(video);
285 |
286 | return;
287 | }
288 |
289 | var reader = new FileReader();
290 |
291 | // Some versions of Safari fail to set onloadend property, some others do not react
292 | // on 'loadend' event. Try everything here.
293 | try {
294 | reader.onloadend = onloadend;
295 | } catch (error) {
296 | reader.addEventListener('loadend', onloadend);
297 | }
298 | reader.readAsText(xhr.response);
299 |
300 | function onloadend() {
301 | var mediaStreamBlobId = reader.result;
302 |
303 | // The retrieved URL does not point to a MediaStream.
304 | if (!mediaStreamBlobId || typeof mediaStreamBlobId !== 'string') {
305 | // If this video element was previously handling a MediaStreamRenderer, release it.
306 | releaseMediaStreamRenderer(video);
307 |
308 | return;
309 | }
310 |
311 | provideMediaStreamRenderer(video, mediaStreamBlobId);
312 | }
313 | };
314 | xhr.send();
315 | }
316 | }
317 |
318 |
319 | function provideMediaStreamRenderer(video, mediaStreamBlobId) {
320 | var
321 | mediaStream = mediaStreams[mediaStreamBlobId],
322 | mediaStreamRenderer = mediaStreamRenderers[video._iosrtcMediaStreamRendererId];
323 |
324 | if (!mediaStream) {
325 | releaseMediaStreamRenderer(video);
326 |
327 | return;
328 | }
329 |
330 | if (mediaStreamRenderer) {
331 | mediaStreamRenderer.render(mediaStream);
332 | } else {
333 | mediaStreamRenderer = new MediaStreamRenderer(video);
334 | mediaStreamRenderer.render(mediaStream);
335 |
336 | mediaStreamRenderers[mediaStreamRenderer.id] = mediaStreamRenderer;
337 | video._iosrtcMediaStreamRendererId = mediaStreamRenderer.id;
338 | }
339 |
340 | // Close the MediaStreamRenderer of this video if it emits "close" event.
341 | mediaStreamRenderer.addEventListener('close', function () {
342 | if (mediaStreamRenderers[video._iosrtcMediaStreamRendererId] !== mediaStreamRenderer) {
343 | return;
344 | }
345 |
346 | releaseMediaStreamRenderer(video);
347 | });
348 |
349 | // Override some