├── .gitignore ├── JanusGateway ├── JanusGateway.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── JanusGateway.xcworkspace │ └── contents.xcworkspacedata ├── JanusGateway │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── ViewController.h │ ├── ViewController.m │ ├── WebRTCClient.h │ ├── WebRTCClient.m │ ├── WebRTCPeer.h │ ├── WebRTCPeer.m │ ├── WebRTCSignaling.h │ ├── WebRTCSignaling.m │ └── main.m └── Podfile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | *Pods/ 37 | *.lock 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 52 | 53 | fastlane/report.xml 54 | fastlane/screenshots 55 | 56 | #Code Injection 57 | # 58 | # After new code Injection tools there's a generated folder /iOSInjectionProject 59 | # https://github.com/johnno1962/injectionforxcode 60 | 61 | iOSInjectionProject/ 62 | 63 | .DS_Store 64 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 95964FF859650E2483CB54E0 /* libPods-JanusGateway.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 128A81F868A22098A7C62B06 /* libPods-JanusGateway.a */; }; 11 | F05CDBE71E496A82001979B4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F05CDBE61E496A82001979B4 /* main.m */; }; 12 | F05CDBEA1E496A82001979B4 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F05CDBE91E496A82001979B4 /* AppDelegate.m */; }; 13 | F05CDBED1E496A82001979B4 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F05CDBEC1E496A82001979B4 /* ViewController.m */; }; 14 | F05CDBF01E496A82001979B4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F05CDBEE1E496A82001979B4 /* Main.storyboard */; }; 15 | F05CDBF21E496A82001979B4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F05CDBF11E496A82001979B4 /* Assets.xcassets */; }; 16 | F05CDBF51E496A82001979B4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F05CDBF31E496A82001979B4 /* LaunchScreen.storyboard */; }; 17 | F05CDBFF1E49708C001979B4 /* WebRTCClient.m in Sources */ = {isa = PBXBuildFile; fileRef = F05CDBFE1E49708C001979B4 /* WebRTCClient.m */; }; 18 | F05CDC021E4993FD001979B4 /* WebRTCSignaling.m in Sources */ = {isa = PBXBuildFile; fileRef = F05CDC011E4993FD001979B4 /* WebRTCSignaling.m */; }; 19 | F05CDC051E49C4F5001979B4 /* WebRTCPeer.m in Sources */ = {isa = PBXBuildFile; fileRef = F05CDC041E49C4F5001979B4 /* WebRTCPeer.m */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 128A81F868A22098A7C62B06 /* libPods-JanusGateway.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-JanusGateway.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 6B49E75858D06F7FF68E0408 /* Pods-JanusGateway.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JanusGateway.debug.xcconfig"; path = "Pods/Target Support Files/Pods-JanusGateway/Pods-JanusGateway.debug.xcconfig"; sourceTree = ""; }; 25 | E72805F99D8AE04B4B0A9DA9 /* Pods-JanusGateway.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JanusGateway.release.xcconfig"; path = "Pods/Target Support Files/Pods-JanusGateway/Pods-JanusGateway.release.xcconfig"; sourceTree = ""; }; 26 | F05CDBE21E496A82001979B4 /* JanusGateway.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JanusGateway.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | F05CDBE61E496A82001979B4 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 28 | F05CDBE81E496A82001979B4 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 29 | F05CDBE91E496A82001979B4 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 30 | F05CDBEB1E496A82001979B4 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 31 | F05CDBEC1E496A82001979B4 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 32 | F05CDBEF1E496A82001979B4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 33 | F05CDBF11E496A82001979B4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 34 | F05CDBF41E496A82001979B4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 35 | F05CDBF61E496A82001979B4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | F05CDBFD1E49708C001979B4 /* WebRTCClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebRTCClient.h; sourceTree = ""; }; 37 | F05CDBFE1E49708C001979B4 /* WebRTCClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebRTCClient.m; sourceTree = ""; }; 38 | F05CDC001E4993FD001979B4 /* WebRTCSignaling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebRTCSignaling.h; sourceTree = ""; }; 39 | F05CDC011E4993FD001979B4 /* WebRTCSignaling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebRTCSignaling.m; sourceTree = ""; }; 40 | F05CDC031E49C4F5001979B4 /* WebRTCPeer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebRTCPeer.h; sourceTree = ""; }; 41 | F05CDC041E49C4F5001979B4 /* WebRTCPeer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebRTCPeer.m; sourceTree = ""; }; 42 | /* End PBXFileReference section */ 43 | 44 | /* Begin PBXFrameworksBuildPhase section */ 45 | F05CDBDF1E496A82001979B4 /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | 95964FF859650E2483CB54E0 /* libPods-JanusGateway.a in Frameworks */, 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXFrameworksBuildPhase section */ 54 | 55 | /* Begin PBXGroup section */ 56 | 18A2A03481D9718F75A7512D /* Pods */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 6B49E75858D06F7FF68E0408 /* Pods-JanusGateway.debug.xcconfig */, 60 | E72805F99D8AE04B4B0A9DA9 /* Pods-JanusGateway.release.xcconfig */, 61 | ); 62 | name = Pods; 63 | sourceTree = ""; 64 | }; 65 | 3BD15AE9916B6F2DF7AB5C45 /* Frameworks */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | 128A81F868A22098A7C62B06 /* libPods-JanusGateway.a */, 69 | ); 70 | name = Frameworks; 71 | sourceTree = ""; 72 | }; 73 | F05CDBD91E496A82001979B4 = { 74 | isa = PBXGroup; 75 | children = ( 76 | F05CDBE41E496A82001979B4 /* JanusGateway */, 77 | F05CDBE31E496A82001979B4 /* Products */, 78 | 18A2A03481D9718F75A7512D /* Pods */, 79 | 3BD15AE9916B6F2DF7AB5C45 /* Frameworks */, 80 | ); 81 | sourceTree = ""; 82 | }; 83 | F05CDBE31E496A82001979B4 /* Products */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | F05CDBE21E496A82001979B4 /* JanusGateway.app */, 87 | ); 88 | name = Products; 89 | sourceTree = ""; 90 | }; 91 | F05CDBE41E496A82001979B4 /* JanusGateway */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | F05CDC061E4B4A41001979B4 /* Category */, 95 | F05CDBE81E496A82001979B4 /* AppDelegate.h */, 96 | F05CDBE91E496A82001979B4 /* AppDelegate.m */, 97 | F05CDBEB1E496A82001979B4 /* ViewController.h */, 98 | F05CDBEC1E496A82001979B4 /* ViewController.m */, 99 | F05CDBEE1E496A82001979B4 /* Main.storyboard */, 100 | F05CDBF11E496A82001979B4 /* Assets.xcassets */, 101 | F05CDBF31E496A82001979B4 /* LaunchScreen.storyboard */, 102 | F05CDBF61E496A82001979B4 /* Info.plist */, 103 | F05CDBE51E496A82001979B4 /* Supporting Files */, 104 | F05CDBFD1E49708C001979B4 /* WebRTCClient.h */, 105 | F05CDBFE1E49708C001979B4 /* WebRTCClient.m */, 106 | F05CDC001E4993FD001979B4 /* WebRTCSignaling.h */, 107 | F05CDC011E4993FD001979B4 /* WebRTCSignaling.m */, 108 | F05CDC031E49C4F5001979B4 /* WebRTCPeer.h */, 109 | F05CDC041E49C4F5001979B4 /* WebRTCPeer.m */, 110 | ); 111 | path = JanusGateway; 112 | sourceTree = ""; 113 | }; 114 | F05CDBE51E496A82001979B4 /* Supporting Files */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | F05CDBE61E496A82001979B4 /* main.m */, 118 | ); 119 | name = "Supporting Files"; 120 | sourceTree = ""; 121 | }; 122 | F05CDC061E4B4A41001979B4 /* Category */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | ); 126 | name = Category; 127 | sourceTree = ""; 128 | }; 129 | /* End PBXGroup section */ 130 | 131 | /* Begin PBXNativeTarget section */ 132 | F05CDBE11E496A82001979B4 /* JanusGateway */ = { 133 | isa = PBXNativeTarget; 134 | buildConfigurationList = F05CDBF91E496A82001979B4 /* Build configuration list for PBXNativeTarget "JanusGateway" */; 135 | buildPhases = ( 136 | 8A772EE1F232B457162851EE /* [CP] Check Pods Manifest.lock */, 137 | F05CDBDE1E496A82001979B4 /* Sources */, 138 | F05CDBDF1E496A82001979B4 /* Frameworks */, 139 | F05CDBE01E496A82001979B4 /* Resources */, 140 | 858A8E30F873D2BEFEAF0AB8 /* [CP] Embed Pods Frameworks */, 141 | 8498C3DBF922D5D2835CF3A7 /* [CP] Copy Pods Resources */, 142 | ); 143 | buildRules = ( 144 | ); 145 | dependencies = ( 146 | ); 147 | name = JanusGateway; 148 | productName = JanusGateway; 149 | productReference = F05CDBE21E496A82001979B4 /* JanusGateway.app */; 150 | productType = "com.apple.product-type.application"; 151 | }; 152 | /* End PBXNativeTarget section */ 153 | 154 | /* Begin PBXProject section */ 155 | F05CDBDA1E496A82001979B4 /* Project object */ = { 156 | isa = PBXProject; 157 | attributes = { 158 | LastUpgradeCheck = 0800; 159 | ORGANIZATIONNAME = dotEngine; 160 | TargetAttributes = { 161 | F05CDBE11E496A82001979B4 = { 162 | CreatedOnToolsVersion = 8.0; 163 | DevelopmentTeam = CLKRLU8RPY; 164 | ProvisioningStyle = Automatic; 165 | }; 166 | }; 167 | }; 168 | buildConfigurationList = F05CDBDD1E496A82001979B4 /* Build configuration list for PBXProject "JanusGateway" */; 169 | compatibilityVersion = "Xcode 3.2"; 170 | developmentRegion = English; 171 | hasScannedForEncodings = 0; 172 | knownRegions = ( 173 | en, 174 | Base, 175 | ); 176 | mainGroup = F05CDBD91E496A82001979B4; 177 | productRefGroup = F05CDBE31E496A82001979B4 /* Products */; 178 | projectDirPath = ""; 179 | projectRoot = ""; 180 | targets = ( 181 | F05CDBE11E496A82001979B4 /* JanusGateway */, 182 | ); 183 | }; 184 | /* End PBXProject section */ 185 | 186 | /* Begin PBXResourcesBuildPhase section */ 187 | F05CDBE01E496A82001979B4 /* Resources */ = { 188 | isa = PBXResourcesBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | F05CDBF51E496A82001979B4 /* LaunchScreen.storyboard in Resources */, 192 | F05CDBF21E496A82001979B4 /* Assets.xcassets in Resources */, 193 | F05CDBF01E496A82001979B4 /* Main.storyboard in Resources */, 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXResourcesBuildPhase section */ 198 | 199 | /* Begin PBXShellScriptBuildPhase section */ 200 | 8498C3DBF922D5D2835CF3A7 /* [CP] Copy Pods Resources */ = { 201 | isa = PBXShellScriptBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | ); 205 | inputPaths = ( 206 | ); 207 | name = "[CP] Copy Pods Resources"; 208 | outputPaths = ( 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | shellPath = /bin/sh; 212 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-JanusGateway/Pods-JanusGateway-resources.sh\"\n"; 213 | showEnvVarsInLog = 0; 214 | }; 215 | 858A8E30F873D2BEFEAF0AB8 /* [CP] Embed Pods Frameworks */ = { 216 | isa = PBXShellScriptBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | ); 220 | inputPaths = ( 221 | ); 222 | name = "[CP] Embed Pods Frameworks"; 223 | outputPaths = ( 224 | ); 225 | runOnlyForDeploymentPostprocessing = 0; 226 | shellPath = /bin/sh; 227 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-JanusGateway/Pods-JanusGateway-frameworks.sh\"\n"; 228 | showEnvVarsInLog = 0; 229 | }; 230 | 8A772EE1F232B457162851EE /* [CP] Check Pods Manifest.lock */ = { 231 | isa = PBXShellScriptBuildPhase; 232 | buildActionMask = 2147483647; 233 | files = ( 234 | ); 235 | inputPaths = ( 236 | ); 237 | name = "[CP] Check Pods Manifest.lock"; 238 | outputPaths = ( 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | shellPath = /bin/sh; 242 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 243 | showEnvVarsInLog = 0; 244 | }; 245 | /* End PBXShellScriptBuildPhase section */ 246 | 247 | /* Begin PBXSourcesBuildPhase section */ 248 | F05CDBDE1E496A82001979B4 /* Sources */ = { 249 | isa = PBXSourcesBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | F05CDC051E49C4F5001979B4 /* WebRTCPeer.m in Sources */, 253 | F05CDBED1E496A82001979B4 /* ViewController.m in Sources */, 254 | F05CDC021E4993FD001979B4 /* WebRTCSignaling.m in Sources */, 255 | F05CDBEA1E496A82001979B4 /* AppDelegate.m in Sources */, 256 | F05CDBE71E496A82001979B4 /* main.m in Sources */, 257 | F05CDBFF1E49708C001979B4 /* WebRTCClient.m in Sources */, 258 | ); 259 | runOnlyForDeploymentPostprocessing = 0; 260 | }; 261 | /* End PBXSourcesBuildPhase section */ 262 | 263 | /* Begin PBXVariantGroup section */ 264 | F05CDBEE1E496A82001979B4 /* Main.storyboard */ = { 265 | isa = PBXVariantGroup; 266 | children = ( 267 | F05CDBEF1E496A82001979B4 /* Base */, 268 | ); 269 | name = Main.storyboard; 270 | sourceTree = ""; 271 | }; 272 | F05CDBF31E496A82001979B4 /* LaunchScreen.storyboard */ = { 273 | isa = PBXVariantGroup; 274 | children = ( 275 | F05CDBF41E496A82001979B4 /* Base */, 276 | ); 277 | name = LaunchScreen.storyboard; 278 | sourceTree = ""; 279 | }; 280 | /* End PBXVariantGroup section */ 281 | 282 | /* Begin XCBuildConfiguration section */ 283 | F05CDBF71E496A82001979B4 /* Debug */ = { 284 | isa = XCBuildConfiguration; 285 | buildSettings = { 286 | ALWAYS_SEARCH_USER_PATHS = NO; 287 | CLANG_ANALYZER_NONNULL = YES; 288 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 289 | CLANG_CXX_LIBRARY = "libc++"; 290 | CLANG_ENABLE_MODULES = YES; 291 | CLANG_ENABLE_OBJC_ARC = YES; 292 | CLANG_WARN_BOOL_CONVERSION = YES; 293 | CLANG_WARN_CONSTANT_CONVERSION = YES; 294 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 295 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 296 | CLANG_WARN_EMPTY_BODY = YES; 297 | CLANG_WARN_ENUM_CONVERSION = YES; 298 | CLANG_WARN_INFINITE_RECURSION = YES; 299 | CLANG_WARN_INT_CONVERSION = YES; 300 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 301 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 302 | CLANG_WARN_UNREACHABLE_CODE = YES; 303 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 304 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 305 | COPY_PHASE_STRIP = NO; 306 | DEBUG_INFORMATION_FORMAT = dwarf; 307 | ENABLE_STRICT_OBJC_MSGSEND = YES; 308 | ENABLE_TESTABILITY = YES; 309 | GCC_C_LANGUAGE_STANDARD = gnu99; 310 | GCC_DYNAMIC_NO_PIC = NO; 311 | GCC_NO_COMMON_BLOCKS = YES; 312 | GCC_OPTIMIZATION_LEVEL = 0; 313 | GCC_PREPROCESSOR_DEFINITIONS = ( 314 | "DEBUG=1", 315 | "$(inherited)", 316 | ); 317 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 318 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 319 | GCC_WARN_UNDECLARED_SELECTOR = YES; 320 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 321 | GCC_WARN_UNUSED_FUNCTION = YES; 322 | GCC_WARN_UNUSED_VARIABLE = YES; 323 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 324 | MTL_ENABLE_DEBUG_INFO = YES; 325 | ONLY_ACTIVE_ARCH = YES; 326 | SDKROOT = iphoneos; 327 | }; 328 | name = Debug; 329 | }; 330 | F05CDBF81E496A82001979B4 /* Release */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | ALWAYS_SEARCH_USER_PATHS = NO; 334 | CLANG_ANALYZER_NONNULL = YES; 335 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 336 | CLANG_CXX_LIBRARY = "libc++"; 337 | CLANG_ENABLE_MODULES = YES; 338 | CLANG_ENABLE_OBJC_ARC = YES; 339 | CLANG_WARN_BOOL_CONVERSION = YES; 340 | CLANG_WARN_CONSTANT_CONVERSION = YES; 341 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 342 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 343 | CLANG_WARN_EMPTY_BODY = YES; 344 | CLANG_WARN_ENUM_CONVERSION = YES; 345 | CLANG_WARN_INFINITE_RECURSION = YES; 346 | CLANG_WARN_INT_CONVERSION = YES; 347 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 348 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 349 | CLANG_WARN_UNREACHABLE_CODE = YES; 350 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 351 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 352 | COPY_PHASE_STRIP = NO; 353 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 354 | ENABLE_NS_ASSERTIONS = NO; 355 | ENABLE_STRICT_OBJC_MSGSEND = YES; 356 | GCC_C_LANGUAGE_STANDARD = gnu99; 357 | GCC_NO_COMMON_BLOCKS = YES; 358 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 359 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 360 | GCC_WARN_UNDECLARED_SELECTOR = YES; 361 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 362 | GCC_WARN_UNUSED_FUNCTION = YES; 363 | GCC_WARN_UNUSED_VARIABLE = YES; 364 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 365 | MTL_ENABLE_DEBUG_INFO = NO; 366 | SDKROOT = iphoneos; 367 | VALIDATE_PRODUCT = YES; 368 | }; 369 | name = Release; 370 | }; 371 | F05CDBFA1E496A82001979B4 /* Debug */ = { 372 | isa = XCBuildConfiguration; 373 | baseConfigurationReference = 6B49E75858D06F7FF68E0408 /* Pods-JanusGateway.debug.xcconfig */; 374 | buildSettings = { 375 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 376 | DEVELOPMENT_TEAM = CLKRLU8RPY; 377 | ENABLE_BITCODE = NO; 378 | INFOPLIST_FILE = JanusGateway/Info.plist; 379 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 380 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 381 | PRODUCT_BUNDLE_IDENTIFIER = cc.dot.engine.JanusGateway; 382 | PRODUCT_NAME = "$(TARGET_NAME)"; 383 | }; 384 | name = Debug; 385 | }; 386 | F05CDBFB1E496A82001979B4 /* Release */ = { 387 | isa = XCBuildConfiguration; 388 | baseConfigurationReference = E72805F99D8AE04B4B0A9DA9 /* Pods-JanusGateway.release.xcconfig */; 389 | buildSettings = { 390 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 391 | DEVELOPMENT_TEAM = CLKRLU8RPY; 392 | ENABLE_BITCODE = NO; 393 | INFOPLIST_FILE = JanusGateway/Info.plist; 394 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 395 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 396 | PRODUCT_BUNDLE_IDENTIFIER = cc.dot.engine.JanusGateway; 397 | PRODUCT_NAME = "$(TARGET_NAME)"; 398 | }; 399 | name = Release; 400 | }; 401 | /* End XCBuildConfiguration section */ 402 | 403 | /* Begin XCConfigurationList section */ 404 | F05CDBDD1E496A82001979B4 /* Build configuration list for PBXProject "JanusGateway" */ = { 405 | isa = XCConfigurationList; 406 | buildConfigurations = ( 407 | F05CDBF71E496A82001979B4 /* Debug */, 408 | F05CDBF81E496A82001979B4 /* Release */, 409 | ); 410 | defaultConfigurationIsVisible = 0; 411 | defaultConfigurationName = Release; 412 | }; 413 | F05CDBF91E496A82001979B4 /* Build configuration list for PBXNativeTarget "JanusGateway" */ = { 414 | isa = XCConfigurationList; 415 | buildConfigurations = ( 416 | F05CDBFA1E496A82001979B4 /* Debug */, 417 | F05CDBFB1E496A82001979B4 /* Release */, 418 | ); 419 | defaultConfigurationIsVisible = 0; 420 | defaultConfigurationName = Release; 421 | }; 422 | /* End XCConfigurationList section */ 423 | }; 424 | rootObject = F05CDBDA1E496A82001979B4 /* Project object */; 425 | } 426 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // JanusGateway 4 | // 5 | // Created by xiang on 07/02/2017. 6 | // Copyright © 2017 dotEngine. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // JanusGateway 4 | // 5 | // Created by xiang on 07/02/2017. 6 | // Copyright © 2017 dotEngine. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // 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. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // 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. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // 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. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway/Assets.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 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /JanusGateway/JanusGateway/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway/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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | NSCameraUsageDescription 38 | you will need to open camera to use this. 39 | NSMicrophoneUsageDescription 40 | you will need to open mic to use this. 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // JanusGateway 4 | // 5 | // Created by xiang on 07/02/2017. 6 | // Copyright © 2017 dotEngine. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @property (weak, nonatomic) IBOutlet UIButton *joinButton; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // JanusGateway 4 | // 5 | // Created by xiang on 07/02/2017. 6 | // Copyright © 2017 dotEngine. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | #import 12 | 13 | #import 14 | 15 | #import "WebRTCClient.h" 16 | 17 | #import 18 | 19 | 20 | 21 | 22 | 23 | 24 | static uint64_t ROOM = 1234; 25 | 26 | @interface ViewController () 27 | { 28 | WebRTCClient* webrtcClient; 29 | UIView* localVideoView; 30 | uint64_t userid; 31 | } 32 | 33 | @end 34 | 35 | 36 | @implementation ViewController 37 | 38 | - (void)viewDidLoad { 39 | [super viewDidLoad]; 40 | // Do any additional setup after loading the view, typically from a nib. 41 | 42 | [_joinButton addTarget:self action:@selector(joinClick:) forControlEvents:UIControlEventTouchUpInside]; 43 | 44 | _joinButton.center = CGPointMake(self.view.frame.size.width/2, self.view.frame.size.height/2); 45 | 46 | webrtcClient = [[WebRTCClient alloc] initWithDelegate:self]; 47 | 48 | RTCSetMinDebugLogLevel(RTCLoggingSeverityVerbose); 49 | } 50 | 51 | 52 | -(void) joinClick:(UIButton*)button 53 | { 54 | 55 | [self requestAudioAcess:^(BOOL granted) { 56 | BOOL audioGranted = granted; 57 | [self requestVideoAcess:^(BOOL granted) { 58 | BOOL videoGranted = granted; 59 | if (audioGranted && videoGranted) { 60 | _joinButton.hidden = YES; 61 | [webrtcClient startLocalMedia]; 62 | userid = (uint64_t)(arc4random_uniform(100000)); 63 | [webrtcClient joinRoomWith:ROOM userid:userid]; 64 | } else { 65 | [self toast:@"can not get media access"]; 66 | } 67 | }]; 68 | }]; 69 | 70 | 71 | 72 | } 73 | 74 | - (void)didReceiveMemoryWarning { 75 | [super didReceiveMemoryWarning]; 76 | // Dispose of any resources that can be recreated. 77 | } 78 | 79 | 80 | 81 | -(void)toast:(NSString*)text 82 | { 83 | 84 | NSDictionary *options = @{ 85 | kCRToastTextKey : text, 86 | kCRToastTextAlignmentKey : @(NSTextAlignmentCenter), 87 | kCRToastBackgroundColorKey : [UIColor redColor], 88 | kCRToastAnimationInTypeKey : @(CRToastAnimationTypeGravity), 89 | kCRToastAnimationOutTypeKey : @(CRToastAnimationTypeGravity), 90 | kCRToastAnimationInDirectionKey : @(CRToastAnimationDirectionLeft), 91 | kCRToastAnimationOutDirectionKey : @(CRToastAnimationDirectionRight) 92 | }; 93 | [CRToastManager showNotificationWithOptions:options 94 | completionBlock:^{ 95 | NSLog(@"Completed"); 96 | }]; 97 | } 98 | 99 | 100 | 101 | -(void)requestAudioAcess:(void (^)(BOOL granted))block 102 | { 103 | AVAudioSessionRecordPermission permissionStatus = [[AVAudioSession sharedInstance] recordPermission]; 104 | 105 | if (permissionStatus == AVAudioSessionRecordPermissionUndetermined) { 106 | [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL grante) { 107 | // CALL YOUR METHOD HERE - as this assumes being called only once from user interacting with permission alert! 108 | if (grante) { 109 | block(TRUE); 110 | // Microphone enabled code 111 | } 112 | else { 113 | block(FALSE); 114 | // Microphone disabled code 115 | } 116 | }]; 117 | } else if(permissionStatus == AVAudioSessionRecordPermissionDenied){ 118 | 119 | block(FALSE); 120 | 121 | } else if(permissionStatus == AVAudioSessionRecordPermissionGranted){ 122 | block(TRUE); 123 | 124 | } else { 125 | block(FALSE); 126 | } 127 | 128 | } 129 | 130 | 131 | -(void)requestVideoAcess:(void (^)(BOOL granted))block 132 | { 133 | NSString *mediaType = AVMediaTypeVideo; 134 | AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType]; 135 | if(authStatus == AVAuthorizationStatusAuthorized) { 136 | 137 | block(TRUE); 138 | 139 | } else if(authStatus == AVAuthorizationStatusDenied){ 140 | // denied 141 | block(FALSE); 142 | } else if(authStatus == AVAuthorizationStatusRestricted){ 143 | // restricted, normally won't happen 144 | block(FALSE); 145 | } else if(authStatus == AVAuthorizationStatusNotDetermined){ 146 | // not determined?! 147 | [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL grante) { 148 | if(grante){ 149 | block(TRUE); 150 | } else { 151 | block(FALSE); 152 | } 153 | }]; 154 | } else { 155 | // impossible, unknown authorization status 156 | } 157 | } 158 | 159 | 160 | -(CGRect)videoViewFrame:(int)index 161 | { 162 | 163 | float width = self.view.frame.size.width/2; 164 | float height = width; 165 | float x = (index%2) * width; 166 | float y = (index/2) * width; 167 | 168 | return CGRectMake(x, y, width, height); 169 | } 170 | 171 | #pragma delegate 172 | 173 | 174 | 175 | -(void)client:(WebRTCClient *)client didJoin:(uint64_t)userid 176 | { 177 | NSLog(@"didJoin %llu",userid); 178 | } 179 | 180 | -(void)client:(WebRTCClient *)client didLeave:(uint64_t)userid 181 | { 182 | NSLog(@"didLeave %llu",userid); 183 | } 184 | 185 | 186 | -(void)client:(WebRTCClient *)client didOccourError:(NSInteger)errorCode 187 | { 188 | NSLog(@"didOccourError"); 189 | } 190 | 191 | 192 | 193 | 194 | -(void)client:(WebRTCClient *)client didReceiveLocalVideo:(WebRTCPeer *)peer 195 | { 196 | NSLog(@"didReceiveLocalVideo "); 197 | NSMutableArray* users = [NSMutableArray arrayWithArray:[client.peers.allValues copy]]; 198 | [users insertObject:peer atIndex:0]; 199 | 200 | localVideoView = peer.view; 201 | CGRect frame = [self videoViewFrame:0]; 202 | [peer.view setSize:frame.size]; 203 | peer.view.contentMode = UIViewContentModeScaleAspectFill; 204 | [self.view addSubview:peer.view]; 205 | 206 | int i = 0; 207 | for (WebRTCPeer* peer in users) { 208 | CGRect frame = [self videoViewFrame:i]; 209 | peer.view.frame = frame; 210 | i++; 211 | } 212 | 213 | [peer setMaxBitrate:@100]; 214 | 215 | } 216 | 217 | -(void)client:(WebRTCClient *)client didReceiveRemoteVideo:(WebRTCPeer *)peer 218 | { 219 | 220 | NSLog(@"didReceiveRemoteVideo "); 221 | NSMutableArray* users = [NSMutableArray arrayWithArray:[client.peers.allValues copy]]; 222 | [users insertObject:client.localPeer atIndex:0]; 223 | 224 | CGRect frame = [self videoViewFrame:0]; 225 | [peer.view setSize:frame.size]; 226 | peer.view.contentMode = UIViewContentModeScaleAspectFill; 227 | [self.view addSubview:peer.view]; 228 | peer.view.backgroundColor = [UIColor blackColor]; 229 | 230 | int i = 0; 231 | for (WebRTCPeer* peer in users) { 232 | CGRect frame = [self videoViewFrame:i]; 233 | peer.view.frame = frame; 234 | i++; 235 | } 236 | 237 | [peer setMaxBitrate:@100]; 238 | 239 | } 240 | 241 | -(void)client:(WebRTCClient *)client didRemoveLocalVideo:(WebRTCPeer *)peer 242 | { 243 | NSLog(@"didRemoveLocalVideo "); 244 | [peer.view removeFromSuperview]; 245 | 246 | NSMutableArray* users = [NSMutableArray arrayWithArray:[client.peers.allValues copy]]; 247 | 248 | int i = 0; 249 | for(WebRTCPeer* peer in users){ 250 | CGRect frame = [self videoViewFrame:i]; 251 | peer.view.frame = frame; 252 | i++; 253 | } 254 | 255 | } 256 | 257 | -(void)client:(WebRTCClient *)client didRemoveRemoteVideo:(WebRTCPeer *)peer 258 | { 259 | NSLog(@"didRemoveRemoteVideo"); 260 | [peer.view removeFromSuperview]; 261 | 262 | NSMutableArray* users = [NSMutableArray arrayWithArray:[client.peers.allValues copy]]; 263 | 264 | [users insertObject:client.localPeer atIndex:0]; 265 | 266 | int i = 0; 267 | for(WebRTCPeer* peer in users){ 268 | CGRect frame = [self videoViewFrame:i]; 269 | peer.view.frame = frame; 270 | i++; 271 | } 272 | } 273 | 274 | 275 | 276 | @end 277 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway/WebRTCClient.h: -------------------------------------------------------------------------------- 1 | // 2 | // WebRTCClient.h 3 | // JanusGateway 4 | // 5 | // Created by xiang on 07/02/2017. 6 | // Copyright © 2017 dotEngine. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | #import 13 | 14 | #import "WebRTCPeer.h" 15 | 16 | typedef NS_ENUM(NSInteger, WebRTCClientState) { 17 | // Disconnected from servers. 18 | kClientStateDisconnected, 19 | // Connecting to servers. 20 | kClientStateConnecting, 21 | // Connected to servers. 22 | kClientStateConnected, 23 | }; 24 | 25 | 26 | @class WebRTCClient; 27 | 28 | @protocol WebRTCClientDelegate 29 | 30 | -(void)client:(WebRTCClient *)client didJoin:(uint64_t)userid; 31 | 32 | -(void)client:(WebRTCClient *)client didLeave:(uint64_t)userid; 33 | 34 | -(void)client:(WebRTCClient *)client didOccourError:(NSInteger)errorCode; 35 | 36 | -(void)client:(WebRTCClient *)client didReceiveLocalVideo:(WebRTCPeer *)peer; 37 | 38 | -(void)client:(WebRTCClient *)client didReceiveRemoteVideo:(WebRTCPeer *)peer; 39 | 40 | -(void)client:(WebRTCClient *)client didRemoveLocalVideo:(WebRTCPeer *)peer; 41 | 42 | -(void)client:(WebRTCClient *)client didRemoveRemoteVideo:(WebRTCPeer *)peer; 43 | 44 | 45 | @end 46 | 47 | 48 | @interface WebRTCClient : NSObject 49 | 50 | 51 | @property(nonatomic, readonly) WebRTCClientState state; 52 | @property(nonatomic, weak) id delegate; 53 | @property(nonatomic, strong) RTCMediaStream *localMediaStream; 54 | @property(nonatomic, strong) WebRTCPeer* localPeer; 55 | @property(nonatomic, strong) NSMutableDictionary* peers; 56 | 57 | 58 | -(instancetype)initWithDelegate:(id)delegate; 59 | 60 | - (void)startLocalMedia; 61 | 62 | - (void)stopLocalMedia; 63 | 64 | - (void)joinRoomWith:(uint64_t)room 65 | userid:(uint64_t)user; 66 | 67 | - (void)leaveRoom; 68 | 69 | - (void)sendMessage:(NSDictionary*)message; 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway/WebRTCClient.m: -------------------------------------------------------------------------------- 1 | // 2 | // WebRTCClient.m 3 | // JanusGateway 4 | // 5 | // Created by xiang on 07/02/2017. 6 | // Copyright © 2017 dotEngine. All rights reserved. 7 | // 8 | 9 | #import "WebRTCClient.h" 10 | 11 | 12 | #import "WebRTCSignaling.h" 13 | #import "WebRTCPeer.h" 14 | 15 | 16 | 17 | static NSString * const kMediaStreamId = @"ARDAMS"; 18 | 19 | 20 | NSString* kWebsocketServerURL = @"ws://101.201.141.179:9000/ws"; 21 | 22 | @interface WebRTCClient () 23 | { 24 | RTCMediaConstraints* _mediaConstraints; 25 | RTCPeerConnectionFactory *_peerConnectionFactory; 26 | RTCAudioTrack* _localAudioTrack; 27 | RTCVideoTrack* _localVideoTrack; 28 | WebRTCSignaling* _signalingChannel; 29 | 30 | uint64_t _room; 31 | 32 | // for local test 33 | uint64_t _session; 34 | 35 | NSNumber* _private_id; 36 | 37 | } 38 | 39 | @end 40 | 41 | 42 | @implementation WebRTCClient 43 | 44 | 45 | -(instancetype)initWithDelegate:(id)delegate 46 | { 47 | 48 | 49 | self = [super init]; 50 | _delegate = delegate; 51 | _state = kClientStateDisconnected; 52 | _peerConnectionFactory = [[RTCPeerConnectionFactory alloc] init]; 53 | _signalingChannel = [[WebRTCSignaling alloc] initWithURL:kWebsocketServerURL delegate:self]; 54 | _peers = [[NSMutableDictionary alloc] init]; 55 | _localPeer = [[WebRTCPeer alloc] initWithDelegate:self]; 56 | return self; 57 | } 58 | 59 | 60 | -(void)startLocalMedia 61 | { 62 | if (!self.localMediaStream) { 63 | _localMediaStream = [_peerConnectionFactory mediaStreamWithStreamId:kMediaStreamId]; 64 | } 65 | 66 | if (_localAudioTrack == nil) { 67 | _localAudioTrack = [_peerConnectionFactory audioTrackWithTrackId:@"Auido"]; 68 | [_localMediaStream addAudioTrack:_localAudioTrack]; 69 | } 70 | 71 | if (_localVideoTrack == nil) { 72 | RTCAVFoundationVideoSource *videosource = [_peerConnectionFactory avFoundationVideoSourceWithConstraints:[self videoConstraints]]; 73 | 74 | _localVideoTrack = [_peerConnectionFactory videoTrackWithSource:videosource trackId:@"Video"]; 75 | [_localMediaStream addVideoTrack:_localVideoTrack]; 76 | 77 | _localPeer.localStream = _localMediaStream; 78 | 79 | [_localPeer.localStream.videoTracks[0] addRenderer:_localPeer.view]; 80 | 81 | if ([_delegate respondsToSelector:@selector(client:didReceiveLocalVideo:)]) { 82 | [_delegate client:self didReceiveLocalVideo:_localPeer]; 83 | } 84 | } 85 | 86 | } 87 | 88 | 89 | -(void)stopLocalMedia 90 | { 91 | if (self.localMediaStream) { 92 | 93 | [_localVideoTrack removeRenderer:_localPeer.view]; 94 | [_delegate client:self didRemoveLocalVideo:_localPeer]; 95 | 96 | _localVideoTrack = nil; 97 | _localAudioTrack = nil; 98 | _localMediaStream = nil; 99 | } 100 | } 101 | 102 | 103 | -(void)sendMessage:(NSDictionary *)message 104 | { 105 | 106 | 107 | } 108 | 109 | 110 | -(void)joinRoomWith:(uint64_t)room userid:(uint64_t)user 111 | { 112 | 113 | if (_state == kClientStateConnected) { 114 | return; 115 | } 116 | 117 | if (!_localMediaStream) { 118 | [self startLocalMedia]; 119 | } 120 | 121 | _room = room; 122 | _localPeer.userID = user; 123 | 124 | [_signalingChannel connect]; 125 | 126 | _state = kClientStateConnecting; 127 | } 128 | 129 | 130 | -(void)leaveRoom 131 | { 132 | 133 | if (_state == kClientStateDisconnected) { 134 | return; 135 | } 136 | 137 | [self internalLeave]; 138 | 139 | } 140 | 141 | 142 | -(void)internalLeave 143 | { 144 | 145 | // lcoal send leave 146 | 147 | [self leave:_localPeer]; 148 | 149 | // remote send leave 150 | for (WebRTCPeer* peer in _peers) { 151 | [self leave:peer]; 152 | } 153 | 154 | 155 | [_signalingChannel disconnect]; 156 | 157 | } 158 | 159 | 160 | #pragma internal function 161 | 162 | -(RTCConfiguration *) rtcConfiguration 163 | { 164 | RTCConfiguration* config = [[RTCConfiguration alloc] init]; 165 | RTCIceServer* server = [[RTCIceServer alloc] initWithURLStrings:@[@"stun:101.201.141.179:3478"]]; 166 | config.iceServers = @[server]; 167 | 168 | config.continualGatheringPolicy = RTCContinualGatheringPolicyGatherContinually; 169 | 170 | // more config 171 | return config; 172 | } 173 | 174 | - (RTCMediaConstraints *)offerConstraints 175 | { 176 | //NSDictionary *optional = @{@"VoiceActivityDetection":@"true"}; 177 | NSDictionary *mandatoryConstraints = @{ 178 | @"OfferToReceiveAudio":@"false", 179 | @"OfferToReceiveVideo":@"false" 180 | }; 181 | 182 | 183 | RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] 184 | initWithMandatoryConstraints:mandatoryConstraints 185 | optionalConstraints:nil]; 186 | 187 | return constraints; 188 | } 189 | 190 | 191 | 192 | -(RTCMediaConstraints *)answerConstraints 193 | { 194 | NSDictionary *mandatoryConstraints = @{ 195 | @"OfferToReceiveAudio":@"true", 196 | @"OfferToReceiveVideo":@"true" 197 | }; 198 | RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] 199 | initWithMandatoryConstraints:mandatoryConstraints 200 | optionalConstraints:nil]; 201 | return constraints; 202 | } 203 | 204 | 205 | -(RTCMediaConstraints *)videoConstraints 206 | { 207 | 208 | NSDictionary *videoConstraints = @{ 209 | @"maxWidth":[NSString stringWithFormat:@"%d", 1280], 210 | @"maxHeight":[NSString stringWithFormat:@"%d", 1280], 211 | @"minWidth":[NSString stringWithFormat:@"%d", 120], 212 | @"minHeight":[NSString stringWithFormat:@"%d", 120], 213 | @"minFrameRate":[NSString stringWithFormat:@"%d", 15], 214 | @"maxFrameRate":[NSString stringWithFormat:@"%d", 15] 215 | }; 216 | 217 | RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] 218 | initWithMandatoryConstraints:videoConstraints 219 | optionalConstraints:nil]; 220 | return constraints; 221 | 222 | } 223 | 224 | 225 | - (RTCMediaConstraints *)connectionConstraints 226 | { 227 | 228 | 229 | NSDictionary *optionalConstraints = @{ 230 | @"DtlsSrtpKeyAgreement":@"true", 231 | @"googSuspendBelowMinBitrate":@"false", 232 | @"googCombinedAudioVideoBwe":@"true" 233 | }; 234 | 235 | RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] 236 | initWithMandatoryConstraints:nil 237 | optionalConstraints:optionalConstraints]; 238 | 239 | return constraints; 240 | } 241 | 242 | 243 | 244 | 245 | 246 | -(void)joinWithRoom:(uint64_t)room user:(uint64_t)user role:(NSString*)role session:(uint64_t)session 247 | { 248 | NSDictionary* message = @{ 249 | @"session":[NSNumber numberWithUnsignedLongLong:_session], 250 | @"type":@"join", 251 | @"room": [NSNumber numberWithUnsignedLongLong:_room], 252 | @"data":@{ 253 | @"room":[NSNumber numberWithUnsignedLongLong:room], 254 | @"userid":[NSNumber numberWithUnsignedLongLong:user], 255 | @"role":role, 256 | }, 257 | }; 258 | 259 | [_signalingChannel sendMessage:message]; 260 | } 261 | 262 | 263 | -(void)publish:(WebRTCPeer*)localPeer 264 | { 265 | 266 | _localPeer.peerconnection = [_peerConnectionFactory 267 | peerConnectionWithConfiguration:[self rtcConfiguration] 268 | constraints:[self connectionConstraints] delegate:_localPeer]; 269 | 270 | [_localPeer.peerconnection addStream:_localMediaStream]; 271 | 272 | [_localPeer setMaxBitrate:@200]; 273 | 274 | [_localPeer.peerconnection offerForConstraints:[self offerConstraints] completionHandler:^(RTCSessionDescription * _Nullable sdp, NSError * _Nullable error) { 275 | 276 | 277 | if (error!= nil) { 278 | NSLog(@"offerForConstraints error %@", error); 279 | return; 280 | } 281 | 282 | [_localPeer.peerconnection setLocalDescription:sdp completionHandler:^(NSError * _Nullable error) { 283 | 284 | if (error != nil){ 285 | NSLog(@"setLocalDescription error %@", [error localizedDescription]); 286 | return; 287 | } 288 | 289 | NSDictionary* message = @{ 290 | @"session":[NSNumber numberWithUnsignedLongLong:_session], 291 | @"handle":[NSNumber numberWithUnsignedLongLong:_localPeer.handleID], 292 | @"type":@"publish", 293 | @"room": [NSNumber numberWithUnsignedLongLong:_room], 294 | @"data":@{ 295 | @"media":@{@"audio":@TRUE,@"video":@TRUE}, 296 | @"sdp":@{ 297 | @"type":@"offer", 298 | @"sdp":sdp.sdp, 299 | }, 300 | }, 301 | }; 302 | 303 | [_signalingChannel sendMessage:message]; 304 | }]; 305 | 306 | }]; 307 | 308 | } 309 | 310 | 311 | 312 | -(void)newRemoteFeed:(uint64_t)userid 313 | { 314 | //todo need to check local 315 | [self.delegate client:self didJoin:userid]; 316 | 317 | // create remote feed 318 | WebRTCPeer* webrtcPeer = [[WebRTCPeer alloc] initWithDelegate:self]; 319 | webrtcPeer.userID = userid; 320 | [_peers setObject:webrtcPeer forKey:[NSNumber numberWithUnsignedLongLong:userid]]; 321 | 322 | webrtcPeer.peerconnection = [_peerConnectionFactory 323 | peerConnectionWithConfiguration:[self rtcConfiguration] 324 | constraints:[self connectionConstraints] delegate:webrtcPeer]; 325 | 326 | //[webrtcPeer.peerconnection addStream:_localMediaStream]; 327 | 328 | NSDictionary* message = @{ 329 | @"session":[NSNumber numberWithUnsignedLongLong:_session], 330 | @"type":@"attach", 331 | @"room": @1234, 332 | @"data":@{ 333 | @"room":[NSNumber numberWithUnsignedLongLong:_room], 334 | @"userid":[NSNumber numberWithUnsignedLongLong:userid], 335 | @"role":@"listener", 336 | @"private_id":_private_id, 337 | }, 338 | 339 | }; 340 | 341 | [_signalingChannel sendMessage:message]; 342 | 343 | } 344 | 345 | 346 | -(void)removeRemoteFeed:(uint64_t)userid 347 | { 348 | 349 | [self.delegate client:self didLeave:userid]; 350 | 351 | WebRTCPeer* webrtcPeer = [_peers objectForKey:[NSNumber numberWithUnsignedLongLong:userid]]; 352 | 353 | if (!webrtcPeer) { 354 | return; 355 | } 356 | [_peers removeObjectForKey:[NSNumber numberWithUnsignedLongLong:userid]]; 357 | [webrtcPeer leave]; 358 | // todo check if we need to remove the view; 359 | 360 | } 361 | 362 | 363 | -(void)subcribe:(WebRTCPeer*)peer withSDP:(RTCSessionDescription*)sdp 364 | { 365 | 366 | __weak WebRTCClient * weakSelf = self; 367 | [peer.peerconnection setRemoteDescription:sdp completionHandler:^(NSError * _Nullable error) { 368 | 369 | if (error != nil) { 370 | NSLog(@"error can not set remote offer sdp %@", error); 371 | return; 372 | } 373 | 374 | [peer answerWithConstraints:[weakSelf answerConstraints] Block:^(RTCSessionDescription *sdp, NSError *error) { 375 | 376 | if(error!= nil){ 377 | NSLog(@"can not generate answer sdp %@", error); 378 | return; 379 | } 380 | 381 | [peer.peerconnection setLocalDescription:sdp completionHandler:^(NSError * _Nullable error) { 382 | 383 | if (error != nil) { 384 | NSLog(@"error can not set local answer %@", error); 385 | return; 386 | } 387 | 388 | NSDictionary* message = @{ 389 | @"session":[NSNumber numberWithUnsignedLongLong:_session], 390 | @"handle":[NSNumber numberWithUnsignedLongLong:peer.handleID], 391 | @"type":@"subcribe", 392 | @"room":[NSNumber numberWithUnsignedLongLong:_room], 393 | @"data":@{ 394 | @"sdp":@{ 395 | @"type":@"answer", 396 | @"sdp":sdp.sdp, 397 | } 398 | }, 399 | }; 400 | 401 | [_signalingChannel sendMessage:message]; 402 | 403 | }]; 404 | 405 | }]; 406 | 407 | }]; 408 | 409 | 410 | } 411 | 412 | 413 | 414 | 415 | -(void)trickleCandidate:(WebRTCPeer*)peer candidate:(NSDictionary*)candidate 416 | { 417 | 418 | 419 | NSDictionary* message = @{ 420 | @"session":[NSNumber numberWithUnsignedLongLong:_session], 421 | @"handle":[NSNumber numberWithUnsignedLongLong:peer.handleID], 422 | @"type":@"ice", 423 | @"room": [NSNumber numberWithUnsignedLongLong:_room], 424 | @"data":@{ 425 | @"candidate":candidate, 426 | }, 427 | }; 428 | 429 | [_signalingChannel sendMessage:message]; 430 | 431 | 432 | } 433 | 434 | 435 | 436 | -(void)unpublish:(WebRTCPeer*)peer 437 | { 438 | // 暂时不实现 439 | // not for now 440 | 441 | } 442 | 443 | 444 | -(void) unsubcribe:(WebRTCPeer*)peer 445 | { 446 | 447 | // 暂时不实现 448 | // not for now 449 | } 450 | 451 | 452 | -(void)leave:(WebRTCPeer*)peer 453 | { 454 | 455 | [peer leave]; 456 | 457 | NSDictionary* message = @{ 458 | @"session":[NSNumber numberWithUnsignedLongLong:_session], 459 | @"handle":[NSNumber numberWithUnsignedLongLong:peer.handleID], 460 | @"type":@"leave", 461 | @"room": [NSNumber numberWithUnsignedLongLong:_room], 462 | @"data":@{ 463 | }, 464 | }; 465 | 466 | [_signalingChannel sendMessage:message]; 467 | 468 | } 469 | 470 | #pragma delegate 471 | 472 | 473 | -(void)channel:(WebRTCSignaling *)channel didReceiveMessage:(NSDictionary *)data 474 | { 475 | NSLog(@"didReceiveMessage %@", data); 476 | 477 | NSString* type = [data objectForKey:@"type"]; 478 | 479 | if ([type isEqualToString:@"created"]) { 480 | // session is created 481 | NSNumber* sessionID = [data objectForKey:@"session"]; 482 | _session = [sessionID unsignedLongLongValue]; 483 | // ok now we can join 484 | [self joinWithRoom:_room user:_localPeer.userID role:@"publisher" session:_session]; 485 | 486 | } 487 | 488 | if ([type isEqualToString: @"joined"]) { 489 | // now we have handle we can publish 490 | NSNumber* handleID = [data objectForKey:@"handle"]; 491 | _localPeer.handleID = [handleID unsignedLongLongValue]; 492 | 493 | if ([_delegate respondsToSelector:@selector(client:didJoin:)]) { 494 | [_delegate client:self didJoin:_localPeer.userID]; 495 | } 496 | 497 | [self publish:_localPeer]; 498 | 499 | _private_id = [data valueForKeyPath:@"data.private_id"]; 500 | 501 | NSArray* publishers = [data valueForKeyPath:@"data.publishers"]; 502 | 503 | if (!publishers) { 504 | return; 505 | } 506 | 507 | for (NSDictionary* publisher in publishers) { 508 | NSNumber* userid = [publisher objectForKey:@"id"]; 509 | if (userid) { 510 | [self newRemoteFeed:[userid unsignedLongLongValue]]; 511 | } 512 | } 513 | 514 | } else if([type isEqualToString:@"attached"]){ 515 | // here create a remote stream 516 | NSNumber* handleID = [data objectForKey:@"handle"]; 517 | NSNumber* userID = [data valueForKeyPath:@"data.userid"]; 518 | NSDictionary* sdp = [data valueForKeyPath:@"data.sdp"]; 519 | 520 | 521 | if (sdp && userID && [_peers objectForKey:userID]) { 522 | RTCSessionDescription* _sdp = [[RTCSessionDescription alloc] initWithType:RTCSdpTypeOffer sdp:[sdp objectForKey:@"sdp"]]; 523 | 524 | WebRTCPeer* peer = [_peers objectForKey:userID]; 525 | peer.handleID = [handleID unsignedLongLongValue]; 526 | [self subcribe:peer withSDP:_sdp]; 527 | } 528 | 529 | 530 | } else if ([type isEqualToString:@"publishers"]) { 531 | // new publisher 532 | NSNumber* handleID = [data objectForKey:@"handle"]; 533 | NSNumber* userID = [data valueForKeyPath:@"data.userid"]; 534 | 535 | NSArray* publishers = [data valueForKeyPath:@"data.publishers"]; 536 | 537 | if (!publishers) { 538 | return; 539 | } 540 | 541 | for (NSDictionary* publisher in publishers) { 542 | NSNumber* userid = [publisher objectForKey:@"id"]; 543 | if (userid) { 544 | 545 | [self newRemoteFeed:[userid unsignedLongLongValue]]; 546 | } 547 | } 548 | 549 | } else if ([type isEqualToString:@"leaving"]){ 550 | // some body leaving 551 | NSNumber* userid = [data objectForKey:@"data.leaving"]; 552 | if (userid){ 553 | [self removeRemoteFeed:[userid unsignedLongLongValue]]; 554 | } 555 | 556 | 557 | } else if([type isEqualToString:@"leaved"]){ 558 | 559 | NSLog(@"we just do not handle this"); 560 | 561 | } else if([type isEqualToString:@"published"]){ 562 | // here we got answer sdp 563 | //NSNumber* handleID = [data objectForKey:@"handle"]; 564 | NSDictionary* sdp = [data valueForKeyPath:@"data.sdp"]; 565 | if (sdp == nil) { 566 | NSLog(@"event published can not find sdp"); 567 | return; 568 | } 569 | NSString* sdpStr = [sdp objectForKey:@"sdp"]; 570 | RTCSessionDescription* _sdp = [[RTCSessionDescription alloc] 571 | initWithType:RTCSdpTypeAnswer sdp:sdpStr]; 572 | 573 | [_localPeer.peerconnection setRemoteDescription:_sdp completionHandler:^(NSError * _Nullable error) { 574 | if (error != nil) { 575 | NSLog(@"setRemoteDescription answer error %@", error); 576 | return; 577 | } 578 | 579 | }]; 580 | 581 | } else if([type isEqualToString:@"unpublished"]){ 582 | 583 | NSLog(@"does not have"); 584 | 585 | } else if([type isEqualToString:@"subcribed"]) { 586 | 587 | NSLog(@"does not have"); 588 | 589 | } 590 | } 591 | 592 | 593 | -(void)channel:(WebRTCSignaling *)channel didChangeState:(WebRTCSignalingState)signalingState 594 | { 595 | if (signalingState == kSignalingStateOpen) { 596 | 597 | 598 | } else if(signalingState == kSignalingStateClosed) { 599 | 600 | 601 | } else if(signalingState == kSignalingStateError) { 602 | 603 | } 604 | } 605 | 606 | 607 | 608 | #pragma 609 | 610 | 611 | -(void)peer:(WebRTCPeer*)peer didReceiveRemoteVideo:(RTCVideoTrack*)track 612 | { 613 | 614 | NSLog(@"peer userid %llu didReceiveRemoteVideo", peer.userID); 615 | 616 | dispatch_async(dispatch_get_main_queue(), ^{ 617 | 618 | [track addRenderer:peer.view]; 619 | [self.delegate client:self didReceiveRemoteVideo:peer]; 620 | 621 | }); 622 | 623 | } 624 | 625 | -(void)peer:(WebRTCPeer*)peer didRemoveRemoteVideo:(RTCVideoTrack*)track 626 | { 627 | 628 | 629 | dispatch_async(dispatch_get_main_queue(), ^{ 630 | 631 | [track removeRenderer:peer.view]; 632 | [self.delegate client:self didRemoveRemoteVideo:peer]; 633 | 634 | }); 635 | 636 | 637 | NSLog(@"peer userid %llu didRemoveRemoteVideo", peer.userID); 638 | 639 | } 640 | 641 | -(void)peer:(WebRTCPeer*)peer didOccurError:(NSInteger*)errorCode 642 | { 643 | 644 | 645 | } 646 | 647 | -(void)peer:(WebRTCPeer*)peer didGotCandidate:(RTCIceCandidate*)candidate 648 | { 649 | 650 | if (candidate) { 651 | 652 | NSDictionary* ice = @{ 653 | @"candidate":candidate.sdp, 654 | @"sdpMid":candidate.sdpMid, 655 | @"sdpMLineIndex":@(candidate.sdpMLineIndex) 656 | }; 657 | 658 | [self trickleCandidate:peer candidate:ice]; 659 | 660 | 661 | } else { 662 | NSDictionary* ice = @{ 663 | @"completed": @true 664 | }; 665 | [self trickleCandidate:peer candidate:ice]; 666 | 667 | } 668 | 669 | 670 | 671 | } 672 | 673 | 674 | @end 675 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway/WebRTCPeer.h: -------------------------------------------------------------------------------- 1 | // 2 | // WebRTCStream.h 3 | // JanusGateway 4 | // 5 | // Created by xiang on 07/02/2017. 6 | // Copyright © 2017 dotEngine. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import 12 | 13 | @class WebRTCPeer; 14 | 15 | @protocol WebRTCPeerDelegate 16 | 17 | -(void)peer:(WebRTCPeer*)peer didReceiveRemoteVideo:(RTCVideoTrack*)track; 18 | 19 | -(void)peer:(WebRTCPeer*)peer didRemoveRemoteVideo:(RTCVideoTrack*)track; 20 | 21 | -(void)peer:(WebRTCPeer*)peer didOccurError:(NSInteger*)errorCode; 22 | 23 | -(void)peer:(WebRTCPeer*)peer didGotCandidate:(RTCIceCandidate*)candidate; 24 | 25 | -(void)sendMessage:(NSDictionary*)message; 26 | 27 | @end 28 | 29 | 30 | @interface WebRTCPeer : NSObject 31 | 32 | @property(nonatomic) uint64_t userID; 33 | @property(nonatomic) uint64_t handleID; 34 | @property(nonatomic) uint64_t sessionID; 35 | @property(nonatomic) NSString* role; 36 | @property(nonatomic,strong) RTCPeerConnection *peerconnection; 37 | @property(nonatomic,strong) RTCEAGLVideoView *view; 38 | @property(nonatomic,weak)id delegate; 39 | @property(nonatomic,strong) RTCRtpSender* audioRender; 40 | @property(nonatomic,strong) RTCRtpSender* videoRender; 41 | @property(nonatomic,strong) RTCMediaStream* localStream; 42 | @property(nonatomic,readonly) RTCMediaStream* remoteStream; 43 | 44 | 45 | -(instancetype)initWithDelegate:(id)delegate; 46 | 47 | 48 | - (void)setMaxBitrate:(NSNumber *)maxBitrate; 49 | 50 | -(void)offerWithConstraints:(RTCMediaConstraints*)constraints Block:(void (^)(RTCSessionDescription* sdp, NSError* error))block; 51 | 52 | -(void)answerWithConstraints:(RTCMediaConstraints*)constraints Block:(void (^)(RTCSessionDescription* sdp, NSError* error))block; 53 | 54 | -(void)setRemoteSDP:(RTCSessionDescription*)sdp block:(void (^)(NSError* error))block; 55 | 56 | -(void)addCandidate:(RTCIceCandidate*)candidate; 57 | 58 | -(void)leave; 59 | 60 | @end 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway/WebRTCPeer.m: -------------------------------------------------------------------------------- 1 | // 2 | // WebRTCStream.m 3 | // JanusGateway 4 | // 5 | // Created by xiang on 07/02/2017. 6 | // Copyright © 2017 dotEngine. All rights reserved. 7 | // 8 | 9 | #import "WebRTCPeer.h" 10 | 11 | #import 12 | 13 | @interface WebRTCPeer () 14 | { 15 | 16 | NSNumber* _maxBitrate; 17 | 18 | BOOL _hasVideo; 19 | 20 | RTCVideoTrack* _removeVideoTrack; 21 | 22 | } 23 | 24 | @end 25 | 26 | @implementation WebRTCPeer 27 | 28 | -(instancetype)initWithDelegate:(id)delegate 29 | { 30 | self = [super init]; 31 | _delegate = delegate; 32 | _view = [[RTCEAGLVideoView alloc] init]; 33 | _view.delegate = self; 34 | return self; 35 | } 36 | 37 | -(void)setMaxBitrate:(NSNumber *)maxBitrate 38 | { 39 | _maxBitrate = maxBitrate; 40 | if (_peerconnection) { 41 | [self setMaxBitrateForPeerConnectionVideoSender]; 42 | } 43 | } 44 | 45 | -(void)offerWithConstraints:(RTCMediaConstraints *)constraints Block:(void (^)(RTCSessionDescription *, NSError *))block 46 | { 47 | 48 | [_peerconnection offerForConstraints:constraints completionHandler:^(RTCSessionDescription * _Nullable sdp, NSError * _Nullable error) { 49 | 50 | //we can handle more sdp info here 51 | 52 | block(sdp,error); 53 | }]; 54 | 55 | [self setMaxBitrateForPeerConnectionVideoSender]; 56 | } 57 | 58 | -(void)answerWithConstraints:(RTCMediaConstraints *)constraints Block:(void (^)(RTCSessionDescription *, NSError *))block 59 | { 60 | 61 | [_peerconnection answerForConstraints:constraints completionHandler:^(RTCSessionDescription * _Nullable sdp, NSError * _Nullable error) { 62 | 63 | // we can handle more sdp info here 64 | block(sdp,error); 65 | }]; 66 | 67 | [self setMaxBitrateForPeerConnectionVideoSender]; 68 | } 69 | 70 | -(void)setRemoteSDP:(RTCSessionDescription *)sdp block:(void (^)(NSError *))block 71 | { 72 | // we can handle sdp info here 73 | [_peerconnection setRemoteDescription:sdp completionHandler:^(NSError * _Nullable error) { 74 | 75 | block(error); 76 | }]; 77 | } 78 | 79 | -(void)addCandidate:(RTCIceCandidate *)candidate 80 | { 81 | // need add queue todo ? 82 | [_peerconnection addIceCandidate:candidate]; 83 | } 84 | 85 | -(void)leave 86 | { 87 | [_peerconnection close]; 88 | } 89 | 90 | 91 | - (void)setMaxBitrateForPeerConnectionVideoSender { 92 | RTCRtpSender* videoSender; 93 | for (RTCRtpSender *sender in _peerconnection.senders) { 94 | if (sender.track != nil) { 95 | if ([sender.track.kind isEqualToString:@"video"]) { 96 | videoSender = sender; 97 | } 98 | } 99 | } 100 | 101 | if(videoSender == nil){ 102 | return; 103 | } 104 | 105 | if (_maxBitrate.intValue <= 0) { 106 | return; 107 | } 108 | 109 | RTCRtpParameters *parametersToModify = videoSender.parameters; 110 | for (RTCRtpEncodingParameters *encoding in parametersToModify.encodings) { 111 | encoding.maxBitrateBps = @(_maxBitrate.intValue * 1000); 112 | } 113 | [videoSender setParameters:parametersToModify]; 114 | 115 | } 116 | 117 | #pragma 118 | 119 | 120 | -(void)videoView:(RTCEAGLVideoView *)videoView didChangeVideoSize:(CGSize)size 121 | { 122 | 123 | if (!_hasVideo) { 124 | _hasVideo = true; 125 | 126 | } 127 | NSLog(@"didChangeVideoSize height %f width %f", size.height,size.width); 128 | 129 | } 130 | 131 | 132 | /** Called when the SignalingState changed. */ 133 | - (void)peerConnection:(RTCPeerConnection *)peerConnection 134 | didChangeSignalingState:(RTCSignalingState)stateChanged 135 | { 136 | NSLog(@"didChangeSignalingState "); 137 | } 138 | 139 | /** Called when media is received on a new stream from remote peer. */ 140 | - (void)peerConnection:(RTCPeerConnection *)peerConnection 141 | didAddStream:(RTCMediaStream *)stream 142 | { 143 | NSLog(@"didAddStream "); 144 | 145 | dispatch_async(dispatch_get_main_queue(), ^{ 146 | RTCLog(@"Received %lu video tracks and %lu audio tracks", 147 | (unsigned long)stream.videoTracks.count, 148 | (unsigned long)stream.audioTracks.count); 149 | if (stream.videoTracks.count) { 150 | _removeVideoTrack = stream.videoTracks[0]; 151 | [_delegate peer:self didReceiveRemoteVideo:_removeVideoTrack]; 152 | 153 | } 154 | }); 155 | 156 | } 157 | 158 | /** Called when a remote peer closes a stream. */ 159 | - (void)peerConnection:(RTCPeerConnection *)peerConnection 160 | didRemoveStream:(RTCMediaStream *)stream 161 | { 162 | NSLog(@"didRemoveStream"); 163 | if ([_delegate respondsToSelector:@selector(peer:didRemoveRemoteVideo:)]) { 164 | 165 | if ([stream.videoTracks count] > 0) { 166 | [_delegate peer:self didRemoveRemoteVideo:stream.videoTracks[0]]; 167 | } 168 | 169 | } 170 | 171 | } 172 | 173 | /** Called when negotiation is needed, for example ICE has restarted. */ 174 | - (void)peerConnectionShouldNegotiate:(RTCPeerConnection *)peerConnection 175 | { 176 | NSLog(@"peerConnectionShouldNegotiate"); 177 | 178 | } 179 | 180 | /** Called any time the IceConnectionState changes. */ 181 | - (void)peerConnection:(RTCPeerConnection *)peerConnection 182 | didChangeIceConnectionState:(RTCIceConnectionState)newState 183 | { 184 | NSLog(@"didChangeIceConnectionState"); 185 | } 186 | 187 | /** Called any time the IceGatheringState changes. */ 188 | - (void)peerConnection:(RTCPeerConnection *)peerConnection 189 | didChangeIceGatheringState:(RTCIceGatheringState)newState 190 | { 191 | NSLog(@"didChangeIceGatheringState"); 192 | 193 | switch (newState) { 194 | case RTCIceGatheringStateNew: 195 | break; 196 | case RTCIceGatheringStateGathering: 197 | break; 198 | case RTCIceGatheringStateComplete: 199 | [_delegate peer:self didGotCandidate:nil]; 200 | default: 201 | break; 202 | } 203 | } 204 | 205 | /** New ice candidate has been found. */ 206 | - (void)peerConnection:(RTCPeerConnection *)peerConnection 207 | didGenerateIceCandidate:(RTCIceCandidate *)candidate 208 | { 209 | NSLog(@"didGenerateIceCandidate %@",candidate); 210 | 211 | [_delegate peer:self didGotCandidate:candidate]; 212 | 213 | } 214 | 215 | /** Called when a group of local Ice candidates have been removed. */ 216 | - (void)peerConnection:(RTCPeerConnection *)peerConnection 217 | didRemoveIceCandidates:(NSArray *)candidates 218 | { 219 | NSLog(@"didRemoveIceCandidates"); 220 | } 221 | 222 | /** New data channel has been opened. */ 223 | - (void)peerConnection:(RTCPeerConnection *)peerConnection 224 | didOpenDataChannel:(RTCDataChannel *)dataChannel 225 | { 226 | // will not happen 227 | NSLog(@"didOpenDataChannel"); 228 | 229 | } 230 | 231 | @end 232 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway/WebRTCSignaling.h: -------------------------------------------------------------------------------- 1 | // 2 | // WebRTCSignaling.h 3 | // JanusGateway 4 | // 5 | // Created by xiang on 07/02/2017. 6 | // Copyright © 2017 dotEngine. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import 12 | 13 | @class WebRTCSignaling; 14 | 15 | 16 | typedef NS_ENUM(NSInteger, WebRTCSignalingState) { 17 | // State when disconnected. 18 | kSignalingStateClosed, 19 | // State when connection is established. 20 | kSignalingStateOpen, 21 | // State when connection encounters a fatal error. 22 | kSignalingStateError 23 | }; 24 | 25 | 26 | 27 | @protocol WebRTCSignalingDelegate 28 | 29 | 30 | -(void)channel:(WebRTCSignaling *)channel didChangeState:(WebRTCSignalingState) state; 31 | 32 | -(void)channel:(WebRTCSignaling *)channel didReceiveMessage:(NSDictionary *)data; 33 | 34 | @end 35 | 36 | 37 | @interface WebRTCSignaling : NSObject 38 | 39 | @property(nonatomic,readonly) WebRTCSignalingState state; 40 | @property(nonatomic,weak) id delegate; 41 | 42 | 43 | -(instancetype) initWithURL:(NSString*)url delegate:(id)delegate; 44 | 45 | 46 | -(void)connect; 47 | 48 | -(void)disconnect; 49 | 50 | -(void)sendMessage:(NSDictionary*)message; 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway/WebRTCSignaling.m: -------------------------------------------------------------------------------- 1 | // 2 | // WebRTCSignaling.m 3 | // JanusGateway 4 | // 5 | // Created by xiang on 07/02/2017. 6 | // Copyright © 2017 dotEngine. All rights reserved. 7 | // 8 | 9 | #import "WebRTCSignaling.h" 10 | 11 | static NSTimeInterval kXSPeerClientKeepaliveInterval = 10.0; 12 | 13 | 14 | @interface WebRTCSignaling () 15 | 16 | @property (nonatomic, strong) NSTimer* presenceKeepAliveTimer; 17 | 18 | 19 | @end 20 | 21 | @implementation WebRTCSignaling { 22 | NSString* _url; 23 | SRWebSocket *_socekt; 24 | } 25 | 26 | -(instancetype)initWithURL:(NSString *)url delegate:(id)delegate 27 | { 28 | 29 | self = [super init]; 30 | _delegate = delegate; 31 | _url = url; 32 | _socekt = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:_url]]; 33 | _socekt.delegate = self; 34 | return self; 35 | } 36 | 37 | 38 | - (void)setState:(WebRTCSignalingState)state { 39 | if (_state == state) { 40 | return; 41 | } 42 | _state = state; 43 | [_delegate channel:self didChangeState:_state]; 44 | } 45 | 46 | 47 | -(void)connect 48 | { 49 | [_socekt open]; 50 | } 51 | 52 | 53 | -(void)disconnect 54 | { 55 | if (_state == kSignalingStateClosed || _state == kSignalingStateError) { 56 | return; 57 | } 58 | [_socekt close]; 59 | 60 | [self setState:kSignalingStateClosed]; 61 | 62 | } 63 | 64 | -(void)dealloc 65 | { 66 | [self disconnect]; 67 | } 68 | 69 | 70 | -(void)sendMessage:(NSDictionary *)message 71 | { 72 | 73 | if (_state != kSignalingStateOpen) { 74 | return; 75 | } 76 | NSError *error; 77 | NSData *jsonData = [NSJSONSerialization dataWithJSONObject:message 78 | options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string 79 | error:&error]; 80 | 81 | if (! jsonData) { 82 | NSLog(@"Got an error: %@", error); 83 | } else { 84 | NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 85 | [_socekt send:jsonString]; 86 | } 87 | } 88 | 89 | 90 | #pragma mark - SRWebSocketDelegate 91 | 92 | -(void)webSocketDidOpen:(SRWebSocket *)webSocket 93 | { 94 | self.state = kSignalingStateOpen; 95 | 96 | [self scheduleTimer]; 97 | } 98 | 99 | -(void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message 100 | { 101 | NSString *messageString = message; 102 | NSData *messageData = [messageString dataUsingEncoding:NSUTF8StringEncoding]; 103 | id jsonObject = [NSJSONSerialization JSONObjectWithData:messageData 104 | options:0 105 | error:nil]; 106 | if (![jsonObject isKindOfClass:[NSDictionary class]]) { 107 | return; 108 | } 109 | 110 | NSDictionary *wssMessage = jsonObject; 111 | [self.delegate channel:self didReceiveMessage:wssMessage]; 112 | 113 | } 114 | 115 | -(void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error 116 | { 117 | self.state = kSignalingStateError; 118 | NSLog(@"didFailWithError %@", error); 119 | [self invalidateTimer]; 120 | 121 | } 122 | 123 | -(void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean 124 | { 125 | self.state = kSignalingStateClosed; 126 | NSLog(@"didCloseWithCode %@", reason); 127 | [self invalidateTimer]; 128 | 129 | } 130 | 131 | 132 | 133 | 134 | - (void)scheduleTimer 135 | { 136 | [self invalidateTimer]; 137 | 138 | NSTimer *timer = [NSTimer timerWithTimeInterval:kXSPeerClientKeepaliveInterval target:self selector:@selector(handleTimer:) userInfo:nil repeats:NO]; 139 | 140 | [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 141 | 142 | self.presenceKeepAliveTimer = timer; 143 | } 144 | 145 | 146 | - (void)invalidateTimer 147 | { 148 | [self.presenceKeepAliveTimer invalidate]; 149 | self.presenceKeepAliveTimer = nil; 150 | } 151 | 152 | - (void)handleTimer:(NSTimer *)timer 153 | { 154 | [self sendPing]; 155 | 156 | [self scheduleTimer]; 157 | } 158 | 159 | - (void)sendPing 160 | { 161 | [_socekt sendPing:nil]; 162 | } 163 | 164 | 165 | @end 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /JanusGateway/JanusGateway/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // JanusGateway 4 | // 5 | // Created by xiang on 07/02/2017. 6 | // Copyright © 2017 dotEngine. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /JanusGateway/Podfile: -------------------------------------------------------------------------------- 1 | 2 | 3 | source 'https://github.com/CocoaPods/Specs.git' 4 | 5 | 6 | target "JanusGateway" do 7 | pod 'CRToast', '~> 0.0.7' 8 | pod 'AFNetworking' 9 | pod 'SocketRocket' 10 | pod 'WebRTC', '~> 56.10.15101' 11 | end 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # janus-gateway-ios 2 | ios webrtc sdk talks to janus gateway 3 | this project does not talks to janus gatewaydirectly, it talks to my own janus backend. 4 | 5 | # it just can work, lots work to do 6 | --------------------------------------------------------------------------------