diff --git a/.gitmodules b/.gitmodules index 479d55cc..0dab8586 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,10 +2,6 @@ path = modules/google-cast url = https://github.com/ConnectSDK/Connect-SDK-iOS-Google-Cast.git branch = dev -[submodule "core"] - path = core - url = https://github.com/ConnectSDK/Connect-SDK-iOS-Core.git - branch = dev [submodule "modules/firetv"] path = modules/firetv url = https://github.com/ConnectSDK/Connect-SDK-iOS-FireTV.git diff --git a/ConnectSDK.podspec b/ConnectSDK.podspec index 2911b28d..bbfa43d4 100644 --- a/ConnectSDK.podspec +++ b/ConnectSDK.podspec @@ -92,7 +92,8 @@ Pod::Spec.new do |s| sp.exclude_files = (non_arc_files.dup << "core/ConnectSDK*Tests/**/*" << "core/Frameworks/LGCast/**/*.h") sp.private_header_files = "core/**/*_Private.h" sp.requires_arc = true - + sp.dependency 'GCDWebServer', '~> 3.2' + sp.dependency 'ConnectSDK/no-arc' sp.ios.vendored_frameworks = 'core/Frameworks/LGCast/LGCast.xcframework', 'core/Frameworks/LGCast/GStreamerForLGCast.xcframework' sp.preserve_paths = 'core/Frameworks/LGCast/LGCast.xcframework', 'core/Frameworks/LGCast/GStreamerForLGCast.xcframework' diff --git a/ConnectSDK.xcodeproj/project.pbxproj b/ConnectSDK.xcodeproj/project.pbxproj index 7c77d802..240facc6 100644 --- a/ConnectSDK.xcodeproj/project.pbxproj +++ b/ConnectSDK.xcodeproj/project.pbxproj @@ -258,34 +258,6 @@ EA5FB896199AEC560057B4B4 /* CTASIProgressDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7CF199AEC550057B4B4 /* CTASIProgressDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; EA5FB897199AEC560057B4B4 /* CTReachability.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7D2199AEC550057B4B4 /* CTReachability.h */; settings = {ATTRIBUTES = (Public, ); }; }; EA5FB898199AEC560057B4B4 /* CTReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = EA5FB7D3199AEC550057B4B4 /* CTReachability.m */; settings = {COMPILER_FLAGS = "-w -fno-objc-arc"; }; }; - EA5FB899199AEC560057B4B4 /* GCDWebServer.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7D7199AEC550057B4B4 /* GCDWebServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA5FB89A199AEC560057B4B4 /* GCDWebServer.m in Sources */ = {isa = PBXBuildFile; fileRef = EA5FB7D8199AEC550057B4B4 /* GCDWebServer.m */; settings = {COMPILER_FLAGS = "-w"; }; }; - EA5FB89B199AEC560057B4B4 /* GCDWebServerConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7D9199AEC550057B4B4 /* GCDWebServerConnection.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA5FB89C199AEC560057B4B4 /* GCDWebServerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = EA5FB7DA199AEC550057B4B4 /* GCDWebServerConnection.m */; settings = {COMPILER_FLAGS = "-w"; }; }; - EA5FB89D199AEC560057B4B4 /* GCDWebServerFunctions.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7DB199AEC550057B4B4 /* GCDWebServerFunctions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA5FB89E199AEC560057B4B4 /* GCDWebServerFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = EA5FB7DC199AEC550057B4B4 /* GCDWebServerFunctions.m */; settings = {COMPILER_FLAGS = "-w"; }; }; - EA5FB89F199AEC560057B4B4 /* GCDWebServerHTTPStatusCodes.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7DD199AEC550057B4B4 /* GCDWebServerHTTPStatusCodes.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA5FB8A0199AEC560057B4B4 /* GCDWebServerPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7DE199AEC550057B4B4 /* GCDWebServerPrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA5FB8A1199AEC560057B4B4 /* GCDWebServerRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7DF199AEC550057B4B4 /* GCDWebServerRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA5FB8A2199AEC560057B4B4 /* GCDWebServerRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = EA5FB7E0199AEC550057B4B4 /* GCDWebServerRequest.m */; settings = {COMPILER_FLAGS = "-w"; }; }; - EA5FB8A3199AEC560057B4B4 /* GCDWebServerResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7E1199AEC550057B4B4 /* GCDWebServerResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA5FB8A4199AEC560057B4B4 /* GCDWebServerResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = EA5FB7E2199AEC550057B4B4 /* GCDWebServerResponse.m */; settings = {COMPILER_FLAGS = "-w"; }; }; - EA5FB8A5199AEC560057B4B4 /* GCDWebServerDataRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7E5199AEC550057B4B4 /* GCDWebServerDataRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA5FB8A6199AEC560057B4B4 /* GCDWebServerDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = EA5FB7E6199AEC550057B4B4 /* GCDWebServerDataRequest.m */; settings = {COMPILER_FLAGS = "-w"; }; }; - EA5FB8A7199AEC560057B4B4 /* GCDWebServerFileRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7E7199AEC550057B4B4 /* GCDWebServerFileRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA5FB8A8199AEC560057B4B4 /* GCDWebServerFileRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = EA5FB7E8199AEC550057B4B4 /* GCDWebServerFileRequest.m */; settings = {COMPILER_FLAGS = "-w"; }; }; - EA5FB8A9199AEC560057B4B4 /* GCDWebServerMultiPartFormRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7E9199AEC550057B4B4 /* GCDWebServerMultiPartFormRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA5FB8AA199AEC560057B4B4 /* GCDWebServerMultiPartFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = EA5FB7EA199AEC550057B4B4 /* GCDWebServerMultiPartFormRequest.m */; settings = {COMPILER_FLAGS = "-w"; }; }; - EA5FB8AB199AEC560057B4B4 /* GCDWebServerURLEncodedFormRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7EB199AEC550057B4B4 /* GCDWebServerURLEncodedFormRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA5FB8AC199AEC560057B4B4 /* GCDWebServerURLEncodedFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = EA5FB7EC199AEC550057B4B4 /* GCDWebServerURLEncodedFormRequest.m */; settings = {COMPILER_FLAGS = "-w"; }; }; - EA5FB8AD199AEC560057B4B4 /* GCDWebServerDataResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7EE199AEC550057B4B4 /* GCDWebServerDataResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA5FB8AE199AEC560057B4B4 /* GCDWebServerDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = EA5FB7EF199AEC550057B4B4 /* GCDWebServerDataResponse.m */; settings = {COMPILER_FLAGS = "-w"; }; }; - EA5FB8AF199AEC560057B4B4 /* GCDWebServerErrorResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7F0199AEC550057B4B4 /* GCDWebServerErrorResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA5FB8B0199AEC560057B4B4 /* GCDWebServerErrorResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = EA5FB7F1199AEC550057B4B4 /* GCDWebServerErrorResponse.m */; settings = {COMPILER_FLAGS = "-w"; }; }; - EA5FB8B1199AEC560057B4B4 /* GCDWebServerFileResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7F2199AEC550057B4B4 /* GCDWebServerFileResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA5FB8B2199AEC560057B4B4 /* GCDWebServerFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = EA5FB7F3199AEC550057B4B4 /* GCDWebServerFileResponse.m */; settings = {COMPILER_FLAGS = "-w"; }; }; - EA5FB8B3199AEC560057B4B4 /* GCDWebServerStreamedResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7F4199AEC550057B4B4 /* GCDWebServerStreamedResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA5FB8B4199AEC560057B4B4 /* GCDWebServerStreamedResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = EA5FB7F5199AEC550057B4B4 /* GCDWebServerStreamedResponse.m */; settings = {COMPILER_FLAGS = "-w"; }; }; EA5FB8B5199AEC560057B4B4 /* CTGuid.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7F7199AEC550057B4B4 /* CTGuid.h */; settings = {ATTRIBUTES = (Public, ); }; }; EA5FB8B6199AEC560057B4B4 /* CTGuid.m in Sources */ = {isa = PBXBuildFile; fileRef = EA5FB7F8199AEC550057B4B4 /* CTGuid.m */; settings = {COMPILER_FLAGS = "-w"; }; }; EA5FB8B7199AEC560057B4B4 /* LGSRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = EA5FB7FB199AEC550057B4B4 /* LGSRWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -667,34 +639,6 @@ EA5FB7CF199AEC550057B4B4 /* CTASIProgressDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTASIProgressDelegate.h; sourceTree = ""; }; EA5FB7D2199AEC550057B4B4 /* CTReachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTReachability.h; sourceTree = ""; }; EA5FB7D3199AEC550057B4B4 /* CTReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTReachability.m; sourceTree = ""; }; - EA5FB7D7199AEC550057B4B4 /* GCDWebServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServer.h; sourceTree = ""; }; - EA5FB7D8199AEC550057B4B4 /* GCDWebServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServer.m; sourceTree = ""; }; - EA5FB7D9199AEC550057B4B4 /* GCDWebServerConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerConnection.h; sourceTree = ""; }; - EA5FB7DA199AEC550057B4B4 /* GCDWebServerConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerConnection.m; sourceTree = ""; }; - EA5FB7DB199AEC550057B4B4 /* GCDWebServerFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFunctions.h; sourceTree = ""; }; - EA5FB7DC199AEC550057B4B4 /* GCDWebServerFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFunctions.m; sourceTree = ""; }; - EA5FB7DD199AEC550057B4B4 /* GCDWebServerHTTPStatusCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerHTTPStatusCodes.h; sourceTree = ""; }; - EA5FB7DE199AEC550057B4B4 /* GCDWebServerPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerPrivate.h; sourceTree = ""; }; - EA5FB7DF199AEC550057B4B4 /* GCDWebServerRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerRequest.h; sourceTree = ""; }; - EA5FB7E0199AEC550057B4B4 /* GCDWebServerRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerRequest.m; sourceTree = ""; }; - EA5FB7E1199AEC550057B4B4 /* GCDWebServerResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerResponse.h; sourceTree = ""; }; - EA5FB7E2199AEC550057B4B4 /* GCDWebServerResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerResponse.m; sourceTree = ""; }; - EA5FB7E5199AEC550057B4B4 /* GCDWebServerDataRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerDataRequest.h; sourceTree = ""; }; - EA5FB7E6199AEC550057B4B4 /* GCDWebServerDataRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerDataRequest.m; sourceTree = ""; }; - EA5FB7E7199AEC550057B4B4 /* GCDWebServerFileRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFileRequest.h; sourceTree = ""; }; - EA5FB7E8199AEC550057B4B4 /* GCDWebServerFileRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFileRequest.m; sourceTree = ""; }; - EA5FB7E9199AEC550057B4B4 /* GCDWebServerMultiPartFormRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerMultiPartFormRequest.h; sourceTree = ""; }; - EA5FB7EA199AEC550057B4B4 /* GCDWebServerMultiPartFormRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerMultiPartFormRequest.m; sourceTree = ""; }; - EA5FB7EB199AEC550057B4B4 /* GCDWebServerURLEncodedFormRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerURLEncodedFormRequest.h; sourceTree = ""; }; - EA5FB7EC199AEC550057B4B4 /* GCDWebServerURLEncodedFormRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerURLEncodedFormRequest.m; sourceTree = ""; }; - EA5FB7EE199AEC550057B4B4 /* GCDWebServerDataResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerDataResponse.h; sourceTree = ""; }; - EA5FB7EF199AEC550057B4B4 /* GCDWebServerDataResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerDataResponse.m; sourceTree = ""; }; - EA5FB7F0199AEC550057B4B4 /* GCDWebServerErrorResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerErrorResponse.h; sourceTree = ""; }; - EA5FB7F1199AEC550057B4B4 /* GCDWebServerErrorResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerErrorResponse.m; sourceTree = ""; }; - EA5FB7F2199AEC550057B4B4 /* GCDWebServerFileResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFileResponse.h; sourceTree = ""; }; - EA5FB7F3199AEC550057B4B4 /* GCDWebServerFileResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFileResponse.m; sourceTree = ""; }; - EA5FB7F4199AEC550057B4B4 /* GCDWebServerStreamedResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerStreamedResponse.h; sourceTree = ""; }; - EA5FB7F5199AEC550057B4B4 /* GCDWebServerStreamedResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerStreamedResponse.m; sourceTree = ""; }; EA5FB7F7199AEC550057B4B4 /* CTGuid.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTGuid.h; sourceTree = ""; }; EA5FB7F8199AEC550057B4B4 /* CTGuid.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTGuid.m; sourceTree = ""; }; EA5FB7FB199AEC550057B4B4 /* LGSRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LGSRWebSocket.h; sourceTree = ""; }; @@ -1385,7 +1329,6 @@ isa = PBXGroup; children = ( EA5FB7BA199AEC550057B4B4 /* asi-http-request */, - EA5FB7D5199AEC550057B4B4 /* GCDWebServer */, EA5FB7F6199AEC550057B4B4 /* objc-guid */, EA5FB7FA199AEC550057B4B4 /* SocketRocket */, EA5FB7FE199AEC550057B4B4 /* XMLReader */, @@ -1447,65 +1390,6 @@ path = Reachability; sourceTree = ""; }; - EA5FB7D5199AEC550057B4B4 /* GCDWebServer */ = { - isa = PBXGroup; - children = ( - EA5FB7D6199AEC550057B4B4 /* Core */, - EA5FB7E4199AEC550057B4B4 /* Requests */, - EA5FB7ED199AEC550057B4B4 /* Responses */, - ); - path = GCDWebServer; - sourceTree = ""; - }; - EA5FB7D6199AEC550057B4B4 /* Core */ = { - isa = PBXGroup; - children = ( - EA5FB7D7199AEC550057B4B4 /* GCDWebServer.h */, - EA5FB7D8199AEC550057B4B4 /* GCDWebServer.m */, - EA5FB7D9199AEC550057B4B4 /* GCDWebServerConnection.h */, - EA5FB7DA199AEC550057B4B4 /* GCDWebServerConnection.m */, - EA5FB7DB199AEC550057B4B4 /* GCDWebServerFunctions.h */, - EA5FB7DC199AEC550057B4B4 /* GCDWebServerFunctions.m */, - EA5FB7DD199AEC550057B4B4 /* GCDWebServerHTTPStatusCodes.h */, - EA5FB7DE199AEC550057B4B4 /* GCDWebServerPrivate.h */, - EA5FB7DF199AEC550057B4B4 /* GCDWebServerRequest.h */, - EA5FB7E0199AEC550057B4B4 /* GCDWebServerRequest.m */, - EA5FB7E1199AEC550057B4B4 /* GCDWebServerResponse.h */, - EA5FB7E2199AEC550057B4B4 /* GCDWebServerResponse.m */, - ); - path = Core; - sourceTree = ""; - }; - EA5FB7E4199AEC550057B4B4 /* Requests */ = { - isa = PBXGroup; - children = ( - EA5FB7E5199AEC550057B4B4 /* GCDWebServerDataRequest.h */, - EA5FB7E6199AEC550057B4B4 /* GCDWebServerDataRequest.m */, - EA5FB7E7199AEC550057B4B4 /* GCDWebServerFileRequest.h */, - EA5FB7E8199AEC550057B4B4 /* GCDWebServerFileRequest.m */, - EA5FB7E9199AEC550057B4B4 /* GCDWebServerMultiPartFormRequest.h */, - EA5FB7EA199AEC550057B4B4 /* GCDWebServerMultiPartFormRequest.m */, - EA5FB7EB199AEC550057B4B4 /* GCDWebServerURLEncodedFormRequest.h */, - EA5FB7EC199AEC550057B4B4 /* GCDWebServerURLEncodedFormRequest.m */, - ); - path = Requests; - sourceTree = ""; - }; - EA5FB7ED199AEC550057B4B4 /* Responses */ = { - isa = PBXGroup; - children = ( - EA5FB7EE199AEC550057B4B4 /* GCDWebServerDataResponse.h */, - EA5FB7EF199AEC550057B4B4 /* GCDWebServerDataResponse.m */, - EA5FB7F0199AEC550057B4B4 /* GCDWebServerErrorResponse.h */, - EA5FB7F1199AEC550057B4B4 /* GCDWebServerErrorResponse.m */, - EA5FB7F2199AEC550057B4B4 /* GCDWebServerFileResponse.h */, - EA5FB7F3199AEC550057B4B4 /* GCDWebServerFileResponse.m */, - EA5FB7F4199AEC550057B4B4 /* GCDWebServerStreamedResponse.h */, - EA5FB7F5199AEC550057B4B4 /* GCDWebServerStreamedResponse.m */, - ); - path = Responses; - sourceTree = ""; - }; EA5FB7F6199AEC550057B4B4 /* objc-guid */ = { isa = PBXGroup; children = ( @@ -1785,7 +1669,6 @@ EA5FB8D9199AEC560057B4B4 /* ToastControl.h in Headers */, 44C2CC941AB7948300B20E46 /* XMLWriter+ConvenienceMethods.h in Headers */, 44316EFE1AF2F04B000FE655 /* FireTVDiscoveryProvider.h in Headers */, - EA5FB8A9199AEC560057B4B4 /* GCDWebServerMultiPartFormRequest.h in Headers */, EA5FB891199AEC560057B4B4 /* CTASIHTTPRequestDelegate.h in Headers */, EA5FB8B9199AEC560057B4B4 /* CTXMLReader.h in Headers */, EA5FB8FC199AEC570057B4B4 /* NetcastTVService.h in Headers */, @@ -1799,17 +1682,14 @@ EA5FB892199AEC560057B4B4 /* CTASIInputStream.h in Headers */, EA5FB8DC199AEC560057B4B4 /* WebAppLauncher.h in Headers */, EA5FB8BB199AEC560057B4B4 /* AppInfo.h in Headers */, - EA5FB8B1199AEC560057B4B4 /* GCDWebServerFileResponse.h in Headers */, EA5FB896199AEC560057B4B4 /* CTASIProgressDelegate.h in Headers */, EA5FB88E199AEC560057B4B4 /* CTASIHTTPRequest.h in Headers */, EA5FB8E6199AEC560057B4B4 /* ServiceConfig.h in Headers */, 44C2CCA11AB7A33500B20E46 /* NSString+Common.h in Headers */, - EA5FB89B199AEC560057B4B4 /* GCDWebServerConnection.h in Headers */, EA5FB8D6199AEC560057B4B4 /* MouseControl.h in Headers */, 443063C11A4210F0007779DA /* AirPlayServiceHTTPKeepAlive.h in Headers */, EA5FB909199AEC570057B4B4 /* WebOSTVService.h in Headers */, EA5FB8CE199AEC560057B4B4 /* AirPlayService.h in Headers */, - EA5FB89F199AEC560057B4B4 /* GCDWebServerHTTPStatusCodes.h in Headers */, EA5FB8E4199AEC560057B4B4 /* NetcastTVServiceConfig.h in Headers */, EA5FB906199AEC570057B4B4 /* WebAppSessionDelegate.h in Headers */, 44D90AAC1B06B26C00A5CF2A /* AppStateChangeNotifier.h in Headers */, @@ -1820,7 +1700,6 @@ EA5FB8DF199AEC560057B4B4 /* ServiceCommand.h in Headers */, EA5FB8E1199AEC560057B4B4 /* ServiceCommandDelegate.h in Headers */, EA5FB8D1199AEC560057B4B4 /* ExternalInputControl.h in Headers */, - EA5FB8AB199AEC560057B4B4 /* GCDWebServerURLEncodedFormRequest.h in Headers */, EA5FB911199AEC570057B4B4 /* CastWebAppSession.h in Headers */, 441C9ED51B3DBCB200F912D5 /* SubscriptionDeduplicator.h in Headers */, EA5FB90F199AEC570057B4B4 /* CastServiceChannel.h in Headers */, @@ -1828,7 +1707,6 @@ EA5FB88C199AEC560057B4B4 /* CTASIFormDataRequest.h in Headers */, EA5FB8C1199AEC560057B4B4 /* ConnectUtil.h in Headers */, EA5FB8DA199AEC560057B4B4 /* TVControl.h in Headers */, - EA5FB8AD199AEC560057B4B4 /* GCDWebServerDataResponse.h in Headers */, EA5FB8E2199AEC560057B4B4 /* ServiceSubscription.h in Headers */, EA5FB8E8199AEC560057B4B4 /* ServiceConfigDelegate.h in Headers */, EA5FB8D7199AEC560057B4B4 /* PowerControl.h in Headers */, @@ -1839,18 +1717,15 @@ 44A5519F1AC36672001AF783 /* CommonMacros.h in Headers */, EA5FB8FA199AEC570057B4B4 /* WebOSTVServiceSocketClient.h in Headers */, EA5FB890199AEC560057B4B4 /* CTASIHTTPRequestConfig.h in Headers */, - EA5FB899199AEC560057B4B4 /* GCDWebServer.h in Headers */, 44BCC3F61AB77F7D001BCDAA /* XMLWriter.h in Headers */, EA5FB8C3199AEC560057B4B4 /* DeviceServiceReachability.h in Headers */, EA5FB90D199AEC570057B4B4 /* CastService.h in Headers */, EA5FB885199AEC560057B4B4 /* CTASICacheDelegate.h in Headers */, EA5FB870199AEC550057B4B4 /* ConnectableDeviceDelegate.h in Headers */, EA5FB8D2199AEC560057B4B4 /* KeyControl.h in Headers */, - EA5FB8A0199AEC560057B4B4 /* GCDWebServerPrivate.h in Headers */, EA5FB8F6199AEC570057B4B4 /* AirPlayServiceMirrored.h in Headers */, 448B28781B041A5600DFF18F /* FireTVMediaPlayer.h in Headers */, EA5FB883199AEC550057B4B4 /* CTASIAuthenticationDialog.h in Headers */, - EA5FB8A3199AEC560057B4B4 /* GCDWebServerResponse.h in Headers */, EA5FB86E199AEC550057B4B4 /* ConnectableDevice.h in Headers */, EA5FB87F199AEC550057B4B4 /* SSDPDiscoveryProvider.h in Headers */, EA5FB8E9199AEC570057B4B4 /* ServiceDescription.h in Headers */, @@ -1876,23 +1751,18 @@ EA5FB8C7199AEC560057B4B4 /* JSONObjectCoding.h in Headers */, 44D0ECEF1B55DA9000E02A8B /* SubtitleInfo.h in Headers */, EA5FB8F8199AEC570057B4B4 /* WebOSTVServiceMouse.h in Headers */, - EA5FB8A5199AEC560057B4B4 /* GCDWebServerDataRequest.h in Headers */, EA5FB886199AEC560057B4B4 /* CTASIDataCompressor.h in Headers */, EA5FB871199AEC550057B4B4 /* ConnectableDeviceStore.h in Headers */, EA5FB8F4199AEC570057B4B4 /* AirPlayServiceHTTP.h in Headers */, EA5FB874199AEC550057B4B4 /* DevicePicker.h in Headers */, - EA5FB8A1199AEC560057B4B4 /* GCDWebServerRequest.h in Headers */, EA5FB8BF199AEC560057B4B4 /* ConnectError.h in Headers */, EA5FB8BD199AEC560057B4B4 /* ChannelInfo.h in Headers */, EA5FB8ED199AEC570057B4B4 /* DeviceService.h in Headers */, - EA5FB89D199AEC560057B4B4 /* GCDWebServerFunctions.h in Headers */, EA5FB8D8199AEC560057B4B4 /* TextInputControl.h in Headers */, EA5FB8C8199AEC560057B4B4 /* ProgramInfo.h in Headers */, 449426301AF82F49006BAFF2 /* BlockRunner.h in Headers */, 44AD91771AFA7F1000C23A71 /* FireTVService.h in Headers */, EA5FB87B199AEC550057B4B4 /* DiscoveryManagerDelegate.h in Headers */, - EA5FB8A7199AEC560057B4B4 /* GCDWebServerFileRequest.h in Headers */, - EA5FB8AF199AEC560057B4B4 /* GCDWebServerErrorResponse.h in Headers */, EA5FB8B5199AEC560057B4B4 /* CTGuid.h in Headers */, EA5FB902199AEC570057B4B4 /* LaunchSession.h in Headers */, 22D1EDBD282D0FBD00D74B56 /* CameraSinkCapability.h in Headers */, @@ -1906,7 +1776,6 @@ 22D1EDC8282D0FBD00D74B56 /* MobileCapability.h in Headers */, 22D1EDCA282D0FBD00D74B56 /* MirroringSinkCapability.h in Headers */, EA5FB8B7199AEC560057B4B4 /* LGSRWebSocket.h in Headers */, - EA5FB8B3199AEC560057B4B4 /* GCDWebServerStreamedResponse.h in Headers */, 0F446CC21A6D8353000BB1C0 /* PlayListControl.h in Headers */, EA5F82F4199BDCD300B7302B /* ConnectSDK.h in Headers */, EA5F82F0199BDA2100B7302B /* ConnectSDKDefaultPlatforms.h in Headers */, @@ -2199,14 +2068,11 @@ files = ( EA5FB884199AEC560057B4B4 /* CTASIAuthenticationDialog.m in Sources */, EA5FB895199AEC560057B4B4 /* CTASINetworkQueue.m in Sources */, - EA5FB8B0199AEC560057B4B4 /* GCDWebServerErrorResponse.m in Sources */, EA5FB8F1199AEC570057B4B4 /* DIALService.m in Sources */, EA5FB887199AEC560057B4B4 /* CTASIDataCompressor.m in Sources */, - EA5FB8AC199AEC560057B4B4 /* GCDWebServerURLEncodedFormRequest.m in Sources */, 448B28771B041A5600DFF18F /* FireTVMediaControl.m in Sources */, EA5FB8CB199AEC560057B4B4 /* SSDPSocketListener.m in Sources */, EA5FB8DE199AEC560057B4B4 /* ServiceAsyncCommand.m in Sources */, - EA5FB8B2199AEC560057B4B4 /* GCDWebServerFileResponse.m in Sources */, 443063C21A4210F0007779DA /* AirPlayServiceHTTPKeepAlive.m in Sources */, EA5FB8C4199AEC560057B4B4 /* DeviceServiceReachability.m in Sources */, 22D1EDCC282D0FBD00D74B56 /* MirroringSinkCapability.m in Sources */, @@ -2230,12 +2096,10 @@ EA5FB8FB199AEC570057B4B4 /* WebOSTVServiceSocketClient.m in Sources */, EA5FB8C9199AEC560057B4B4 /* ProgramInfo.m in Sources */, EA5FB8C6199AEC560057B4B4 /* ExternalInputInfo.m in Sources */, - EA5FB89E199AEC560057B4B4 /* GCDWebServerFunctions.m in Sources */, EA5FB905199AEC570057B4B4 /* WebAppSession.m in Sources */, EA5FB912199AEC570057B4B4 /* CastWebAppSession.m in Sources */, EA5FB87A199AEC550057B4B4 /* DiscoveryManager.m in Sources */, EA5FB903199AEC570057B4B4 /* LaunchSession.m in Sources */, - EA5FB8A8199AEC560057B4B4 /* GCDWebServerFileRequest.m in Sources */, EA5FB880199AEC550057B4B4 /* SSDPDiscoveryProvider.m in Sources */, EA5FB8CD199AEC560057B4B4 /* TextInputStatusInfo.m in Sources */, 22D1EDC5282D0FBD00D74B56 /* WebOSTVService+LGCast.m in Sources */, @@ -2246,7 +2110,6 @@ EA5FB898199AEC560057B4B4 /* CTReachability.m in Sources */, EA5FB8C0199AEC560057B4B4 /* ConnectError.m in Sources */, 44C2CCA21AB7A33500B20E46 /* NSString+Common.m in Sources */, - EA5FB8A6199AEC560057B4B4 /* GCDWebServerDataRequest.m in Sources */, EA5FB908199AEC570057B4B4 /* WebOSWebAppSession.m in Sources */, EA5FB8EA199AEC570057B4B4 /* ServiceDescription.m in Sources */, EA5FB8BE199AEC560057B4B4 /* ChannelInfo.m in Sources */, @@ -2257,7 +2120,6 @@ EA5FB88D199AEC560057B4B4 /* CTASIFormDataRequest.m in Sources */, EA5FB893199AEC560057B4B4 /* CTASIInputStream.m in Sources */, 22D1EDC6282D0FBD00D74B56 /* ConnectionManager.m in Sources */, - EA5FB8B4199AEC560057B4B4 /* GCDWebServerStreamedResponse.m in Sources */, EA5FB90E199AEC570057B4B4 /* CastService.m in Sources */, 44316EFF1AF2F04B000FE655 /* FireTVDiscoveryProvider.m in Sources */, EA5FB8F7199AEC570057B4B4 /* AirPlayServiceMirrored.m in Sources */, @@ -2266,13 +2128,10 @@ EA5FB8C2199AEC560057B4B4 /* ConnectUtil.m in Sources */, EA5FB87D199AEC550057B4B4 /* DiscoveryProvider.m in Sources */, EA5FB8FF199AEC570057B4B4 /* RokuService.m in Sources */, - EA5FB8AE199AEC560057B4B4 /* GCDWebServerDataResponse.m in Sources */, 22D1EDC4282D0FBD00D74B56 /* RemoteCameraService.m in Sources */, EA5FB8BC199AEC560057B4B4 /* AppInfo.m in Sources */, - EA5FB8A4199AEC560057B4B4 /* GCDWebServerResponse.m in Sources */, 44BCC3F71AB77F7D001BCDAA /* XMLWriter.m in Sources */, 44A499061B0AD0C1002C94E7 /* FireTVCapabilityMixin.m in Sources */, - EA5FB89A199AEC560057B4B4 /* GCDWebServer.m in Sources */, EA5FB8FD199AEC570057B4B4 /* NetcastTVService.m in Sources */, EA5FB901199AEC570057B4B4 /* AirPlayWebAppSession.m in Sources */, 4498D98D1A659F43008C0B72 /* NSDictionary+KeyPredicateSearch.m in Sources */, @@ -2285,11 +2144,8 @@ EA5FB88F199AEC560057B4B4 /* CTASIHTTPRequest.m in Sources */, 441C9ED61B3DBCB200F912D5 /* SubscriptionDeduplicator.m in Sources */, 22D1EDC9282D0FBD00D74B56 /* ScreenMirroringService.m in Sources */, - EA5FB89C199AEC560057B4B4 /* GCDWebServerConnection.m in Sources */, - EA5FB8A2199AEC560057B4B4 /* GCDWebServerRequest.m in Sources */, EA5FB889199AEC560057B4B4 /* CTASIDataDecompressor.m in Sources */, EA5FB910199AEC570057B4B4 /* CastServiceChannel.m in Sources */, - EA5FB8AA199AEC560057B4B4 /* GCDWebServerMultiPartFormRequest.m in Sources */, EA5FB8E0199AEC560057B4B4 /* ServiceCommand.m in Sources */, BB9F7B7905ABBE72E0843AA0 /* MediaInfo.m in Sources */, BB9F776F4924E08D116738B1 /* ImageInfo.m in Sources */, @@ -2343,7 +2199,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 6P799F3DP3; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", @@ -2385,7 +2241,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 6P799F3DP3; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", @@ -2421,7 +2277,7 @@ buildSettings = { CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 6P799F3DP3; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", @@ -2460,7 +2316,7 @@ buildSettings = { CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 6P799F3DP3; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", @@ -2496,7 +2352,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 6P799F3DP3; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -2539,7 +2395,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 6P799F3DP3; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( diff --git a/core b/core deleted file mode 160000 index fca71f74..00000000 --- a/core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fca71f7454d9708f2f1c57c99262ee8149f849ec diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 00000000..a7fbef6d --- /dev/null +++ b/core/.gitignore @@ -0,0 +1,24 @@ +# Xcode +.DS_Store +build +*/build/* +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +profile +*.moved-aside +DerivedData +.idea/ +*.hmap + +#CocoaPods +Pods + +#GoogleCast +ConnectSDK/Frameworks/GoogleCast.framework diff --git a/core/CONTRIBUTING.md b/core/CONTRIBUTING.md new file mode 100644 index 00000000..61400d0b --- /dev/null +++ b/core/CONTRIBUTING.md @@ -0,0 +1,3 @@ +#Contributing to Connect SDK + +Please see our [contributing guidelines](https://github.com/ConnectSDK/Connect-SDK-iOS/blob/master/CONTRIBUTING.md) in the main [Connect-SDK-iOS](https://github.com/ConnectSDK/Connect-SDK-iOS) repo. diff --git a/core/ConnectSDK-Prefix.pch b/core/ConnectSDK-Prefix.pch new file mode 100644 index 00000000..44c056c5 --- /dev/null +++ b/core/ConnectSDK-Prefix.pch @@ -0,0 +1,43 @@ +// +// Prefix header +// +// The contents of this file are implicitly included at the beginning of every source file. +// +// Copyright (c) 2015 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#define CONNECT_SDK_VERSION @"1.6.0" + +#ifdef __OBJC__ + #import +#endif + +// Uncomment this line to enable SDK logging +//#define CONNECT_SDK_ENABLE_LOG + +#ifndef kConnectSDKWirelessSSIDChanged +#define kConnectSDKWirelessSSIDChanged @"Connect_SDK_Wireless_SSID_Changed" +#endif + +#ifdef CONNECT_SDK_ENABLE_LOG + // credit: http://stackoverflow.com/a/969291/2715 + #ifdef DEBUG + # define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); + #else + # define DLog(...) + #endif +#else + # define DLog(...) +#endif diff --git a/core/ConnectSDK.h b/core/ConnectSDK.h new file mode 100644 index 00000000..bde450e4 --- /dev/null +++ b/core/ConnectSDK.h @@ -0,0 +1,66 @@ +// +// ConnectSDK.h +// Connect SDK +// +// Created by Andrew Longstaff on 9/6/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#import "DiscoveryManager.h" +#import "DiscoveryManagerDelegate.h" +#import "DiscoveryProviderDelegate.h" + +#import "ConnectableDevice.h" +#import "ConnectableDeviceDelegate.h" + +#import "DevicePicker.h" +#import "DevicePickerDelegate.h" + +#import "ServiceAsyncCommand.h" +#import "ServiceCommand.h" +#import "ServiceCommandDelegate.h" +#import "ServiceSubscription.h" + +#import "CapabilityFilter.h" +#import "ExternalInputControl.h" +#import "KeyControl.h" +#import "TextInputControl.h" +#import "Launcher.h" +#import "MediaControl.h" +#import "PlayListControl.h" +#import "MediaPlayer.h" +#import "MouseControl.h" +#import "PowerControl.h" +#import "SubtitleInfo.h" +#import "ToastControl.h" +#import "TVControl.h" +#import "VolumeControl.h" +#import "WebAppLauncher.h" + +#import "AppInfo.h" +#import "ChannelInfo.h" +#import "ExternalInputInfo.h" +#import "ImageInfo.h" +#import "MediaInfo.h" +#import "TextInputStatusInfo.h" +#import "ProgramInfo.h" +#import "LaunchSession.h" +#import "WebAppSession.h" + +@interface ConnectSDK : NSObject + +@end diff --git a/core/ConnectSDKAcceptanceTests/AirPlayServiceAcceptanceTests.m b/core/ConnectSDKAcceptanceTests/AirPlayServiceAcceptanceTests.m new file mode 100644 index 00000000..accd0c91 --- /dev/null +++ b/core/ConnectSDKAcceptanceTests/AirPlayServiceAcceptanceTests.m @@ -0,0 +1,102 @@ +// +// AirPlayServiceAcceptanceTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-02-06. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "AirPlayService.h" +#import "DiscoveryManager.h" +#import "ZeroConfDiscoveryProvider.h" + +#import "OCMArg+ArgumentCaptor.h" +#import "OCMStubRecorder+SpectaAsync.h" + +SpecBegin(AirPlayService) + +describe(@"ConnectSDK", ^{ + __block DiscoveryManager *manager; + __block id delegateStub; + + beforeEach(^{ + manager = [DiscoveryManager new]; + manager.deviceStore = nil; + + delegateStub = OCMProtocolMock(@protocol(DiscoveryManagerDelegate)); + manager.delegate = delegateStub; + + [AirPlayService setAirPlayServiceMode:AirPlayServiceModeMedia]; + [manager registerDeviceService:[AirPlayService class] + withDiscovery:[ZeroConfDiscoveryProvider class]]; + }); + + context(@"after AppleTV device is connected", ^{ + __block ConnectableDevice *appleTV; + + beforeEach(^{ + waitUntil(^(DoneCallback done) { + [OCMStub([delegateStub discoveryManager:manager + didFindDevice:[OCMArg captureTo:&appleTV]]) + andDoneWaiting:done]; + + [manager startDiscovery]; + }); + + expect([appleTV serviceWithName:kConnectSDKAirPlayServiceId]).notTo.beNil(); + }); + + it(@"should display photo", ^{ + NSURL *url = [[NSBundle bundleForClass:self.class] + URLForResource:@"the-san-francisco-peaks-of-flagstaff-718x544" + withExtension:@"jpg"]; + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:url + mimeType:@"image/jpg"]; + + waitUntil(^(DoneCallback done) { + MediaPlayerSuccessBlock successBlock = ^(MediaLaunchObject *_) { + // the delay is not required here, but allows to visually + // check if the image is indeed displayed + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, .5 * NSEC_PER_SEC), + dispatch_get_main_queue(), + ^{ + done(); + }); + }; + FailureBlock failureBlock = ^(NSError *error) { + failure([NSString stringWithFormat:@"should not happen: %@", + error]); + done(); + }; + [[appleTV mediaPlayer] displayImageWithMediaInfo:mediaInfo + success:successBlock + failure:failureBlock]; + }); + }); + + afterEach(^{ + [appleTV disconnect]; + appleTV = nil; + }); + }); + + afterEach(^{ + [manager stopDiscovery]; + delegateStub = nil; + manager = nil; + }); +}); + +SpecEnd diff --git a/core/ConnectSDKAcceptanceTests/DLNAServiceAcceptanceTests.m b/core/ConnectSDKAcceptanceTests/DLNAServiceAcceptanceTests.m new file mode 100644 index 00000000..0deea2b3 --- /dev/null +++ b/core/ConnectSDKAcceptanceTests/DLNAServiceAcceptanceTests.m @@ -0,0 +1,114 @@ +// +// DLNAServiceAcceptanceTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/26/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DiscoveryManager.h" +#import "DLNAService.h" +#import "SSDPDiscoveryProvider.h" + +#import "EXPMatchers+matchRegex.h" +#import "OCMStubRecorder+SpectaAsync.h" + +#pragma mark - Environment-specific constants + +static NSString *const kExpectedDeviceName = @"Living Room - Sonos PLAY:1 Media Renderer"; +static NSString *const kExpectedIPAddressRegex = @"192\\.168\\.1\\.\\d{1,3}"; + +#pragma mark - + +SpecBegin(DLNAService) + +describe(@"ConnectSDK", ^{ + __block ConnectableDevice *device; + + beforeEach(^{ + // the official way to access DiscoveryManager is through the singleton, + // but that's no good for tests + DiscoveryManager *manager = [DiscoveryManager new]; + // don't need to save any state information + manager.deviceStore = nil; + + // use a custom delegate to avoid showing any UI and get the discovery + // callbacks + id delegateStub = OCMProtocolMock(@protocol(DiscoveryManagerDelegate)); + manager.delegate = delegateStub; + + // use DLNA service only + [manager registerDeviceService:[DLNAService class] + withDiscovery:[SSDPDiscoveryProvider class]]; + + // wait for a matching device + waitUntil(^(DoneCallback done) { + OCMStub([delegateStub discoveryManager:manager + didFindDevice:[OCMArg checkWithBlock:^BOOL(ConnectableDevice *dev) { + if ([kExpectedDeviceName isEqualToString:dev.friendlyName]) { + device = dev; + done(); + } + + return YES; + }]]); + + [manager startDiscovery]; + }); + + [manager stopDiscovery]; + }); + + it(@"should discover Sonos device in the network", ^{ + expect(device.address).matchRegex(kExpectedIPAddressRegex); + expect(device.id).notTo.beNil(); + expect([device serviceWithName:kConnectSDKDLNAServiceId]).notTo.beNil(); + }); + + it(@"should not send wrong volume error when mute changes", ^{ + id deviceDelegateStub = OCMProtocolMock(@protocol(ConnectableDeviceDelegate)); + device.delegate = deviceDelegateStub; + + waitUntil(^(DoneCallback done) { + [OCMStub([deviceDelegateStub connectableDeviceReady:device]) + andDoneWaiting:done]; + + [device connect]; + }); + + waitUntil(^(DoneCallback done) { + [device.volumeControl subscribeVolumeWithSuccess:nil + failure:^(NSError *error) { + failure(@"should not send volume error %@", error); + done(); + }]; + + [device.volumeControl getMuteWithSuccess:^(BOOL mute) { + [device.volumeControl setMute:!mute + success:^(id _) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), + dispatch_get_main_queue(), + ^{ + done(); + }); + } + failure:nil]; + } + failure:nil]; + }); + }); +}); + +SpecEnd diff --git a/core/ConnectSDKAcceptanceTests/Info.plist b/core/ConnectSDKAcceptanceTests/Info.plist new file mode 100644 index 00000000..cb399fd9 --- /dev/null +++ b/core/ConnectSDKAcceptanceTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.connectsdk.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/core/ConnectSDKIntegrationTests/ConnectSDKIntegrationTests-Prefix.pch b/core/ConnectSDKIntegrationTests/ConnectSDKIntegrationTests-Prefix.pch new file mode 100644 index 00000000..f1f28842 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/ConnectSDKIntegrationTests-Prefix.pch @@ -0,0 +1,32 @@ +// +// ConnectSDKIntegrationTests-Prefix.pch +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/26/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef ConnectSDK_ConnectSDKIntegrationTests_Prefix_pch +#define ConnectSDK_ConnectSDKIntegrationTests_Prefix_pch + +// Include any system framework and library headers here that should be included in all compilation units. +// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file. + +#import "ConnectSDKTests-Prefix.pch" + +#import +#import + +#endif diff --git a/core/ConnectSDKIntegrationTests/EXPMatchers+matchRegex.h b/core/ConnectSDKIntegrationTests/EXPMatchers+matchRegex.h new file mode 100644 index 00000000..e6c63407 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/EXPMatchers+matchRegex.h @@ -0,0 +1,24 @@ +// +// EXPMatchers+matchRegex.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/26/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +/// Expects a string to fully match the given @c regex. +EXPMatcherInterface(matchRegex, (NSString *regex)); diff --git a/core/ConnectSDKIntegrationTests/EXPMatchers+matchRegex.m b/core/ConnectSDKIntegrationTests/EXPMatchers+matchRegex.m new file mode 100644 index 00000000..096ad444 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/EXPMatchers+matchRegex.m @@ -0,0 +1,42 @@ +// +// EXPMatchers+matchRegex.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/26/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "EXPMatchers+matchRegex.h" + +EXPMatcherImplementationBegin(matchRegex, (NSString *expected)) { + match(^BOOL { + NSString *string = actual; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:expected + options:0 + error:nil]; + NSRange wholeRange = NSMakeRange(0, string.length); + NSRange matchedRange = [regex rangeOfFirstMatchInString:string + options:NSMatchingAnchored + range:wholeRange]; + return NSEqualRanges(matchedRange, wholeRange); + }); + + failureMessageForTo(^NSString * { + return [NSString stringWithFormat: + @"expected: a string matching regex %@, got: %@", + expected, actual]; + }); +} +EXPMatcherImplementationEnd diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Expecta b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Expecta new file mode 120000 index 00000000..caa14ed5 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Expecta @@ -0,0 +1 @@ +Versions/Current/Expecta \ No newline at end of file diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Headers b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Headers new file mode 120000 index 00000000..a177d2a6 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Headers @@ -0,0 +1 @@ +Versions/Current/Headers \ No newline at end of file diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/LICENSE b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/LICENSE new file mode 120000 index 00000000..7da29fc7 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/LICENSE @@ -0,0 +1 @@ +Versions/Current/LICENSE \ No newline at end of file diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Resources b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Resources new file mode 120000 index 00000000..953ee36f --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Resources @@ -0,0 +1 @@ +Versions/Current/Resources \ No newline at end of file diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Expecta b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Expecta new file mode 100644 index 00000000..c47f5e41 Binary files /dev/null and b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Expecta differ diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPBlockDefinedMatcher.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPBlockDefinedMatcher.h new file mode 100644 index 00000000..58b12828 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPBlockDefinedMatcher.h @@ -0,0 +1,25 @@ +// +// EXPRuntimeMatcher.h +// Expecta +// +// Created by Luke Redpath on 26/03/2012. +// Copyright (c) 2012 Peter Jihoon Kim. All rights reserved. +// + +#import +#import "EXPMatcher.h" +#import "EXPDefines.h" + +@interface EXPBlockDefinedMatcher : NSObject { + EXPBoolBlock prerequisiteBlock; + EXPBoolBlock matchBlock; + EXPStringBlock failureMessageForToBlock; + EXPStringBlock failureMessageForNotToBlock; +} + +@property(nonatomic, copy) EXPBoolBlock prerequisiteBlock; +@property(nonatomic, copy) EXPBoolBlock matchBlock; +@property(nonatomic, copy) EXPStringBlock failureMessageForToBlock; +@property(nonatomic, copy) EXPStringBlock failureMessageForNotToBlock; + +@end diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPDefines.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPDefines.h new file mode 100644 index 00000000..52af7219 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPDefines.h @@ -0,0 +1,17 @@ +// +// EXPDefines.h +// Expecta +// +// Created by Luke Redpath on 26/03/2012. +// Copyright (c) 2012 Peter Jihoon Kim. All rights reserved. +// + +#ifndef Expecta_EXPDefines_h +#define Expecta_EXPDefines_h + +typedef void (^EXPBasicBlock)(); +typedef id (^EXPIdBlock)(); +typedef BOOL (^EXPBoolBlock)(); +typedef NSString *(^EXPStringBlock)(); + +#endif diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPExpect.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPExpect.h new file mode 100644 index 00000000..985c1200 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPExpect.h @@ -0,0 +1,45 @@ +#import +#import "EXPMatcher.h" +#import "EXPDefines.h" + +@interface EXPExpect : NSObject { + EXPIdBlock _actualBlock; + id _testCase; + int _lineNumber; + char *_fileName; + BOOL _negative; + BOOL _asynchronous; + NSTimeInterval _timeout; +} + +@property(nonatomic, copy) EXPIdBlock actualBlock; +@property(nonatomic, readonly) id actual; +@property(nonatomic, assign) id testCase; +@property(nonatomic) int lineNumber; +@property(nonatomic) const char *fileName; +@property(nonatomic) BOOL negative; +@property(nonatomic) BOOL asynchronous; +@property(nonatomic) NSTimeInterval timeout; + +@property(nonatomic, readonly) EXPExpect *to; +@property(nonatomic, readonly) EXPExpect *toNot; +@property(nonatomic, readonly) EXPExpect *notTo; +@property(nonatomic, readonly) EXPExpect *will; +@property(nonatomic, readonly) EXPExpect *willNot; +@property(nonatomic, readonly) EXPExpect *(^after)(NSTimeInterval timeInterval); + +- (instancetype)initWithActualBlock:(id)actualBlock testCase:(id)testCase lineNumber:(int)lineNumber fileName:(const char *)fileName NS_DESIGNATED_INITIALIZER; ++ (EXPExpect *)expectWithActualBlock:(id)actualBlock testCase:(id)testCase lineNumber:(int)lineNumber fileName:(const char *)fileName; + +- (void)applyMatcher:(id)matcher; +- (void)applyMatcher:(id)matcher to:(NSObject **)actual; + +@end + +@interface EXPDynamicPredicateMatcher : NSObject { + EXPExpect *_expectation; + SEL _selector; +} +- (instancetype)initWithExpectation:(EXPExpect *)expectation selector:(SEL)selector NS_DESIGNATED_INITIALIZER; +@property (nonatomic, readonly, copy) void (^dispatch)(void); +@end diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatcher.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatcher.h new file mode 100644 index 00000000..b207a842 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatcher.h @@ -0,0 +1,20 @@ +// +// EXPMatcher.h +// Expecta +// +// Created by Luke Redpath on 26/03/2012. +// Copyright (c) 2012 Peter Jihoon Kim. All rights reserved. +// + +#import + +@protocol EXPMatcher + +- (BOOL)matches:(id)actual; + +@optional +- (BOOL)meetsPrerequesiteFor:(id)actual; +- (NSString *)failureMessageForTo:(id)actual; +- (NSString *)failureMessageForNotTo:(id)actual; + +@end diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beCloseTo.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beCloseTo.h new file mode 100644 index 00000000..f683d6b0 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beCloseTo.h @@ -0,0 +1,7 @@ +#import "Expecta.h" + +EXPMatcherInterface(_beCloseToWithin, (id expected, id within)); +EXPMatcherInterface(beCloseToWithin, (id expected, id within)); + +#define beCloseTo(expected) _beCloseToWithin(EXPObjectify((expected)), nil) +#define beCloseToWithin(expected, range) _beCloseToWithin(EXPObjectify((expected)), EXPObjectify((range))) diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beFalsy.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beFalsy.h new file mode 100644 index 00000000..89c8e003 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beFalsy.h @@ -0,0 +1,3 @@ +#import "Expecta.h" + +EXPMatcherInterface(beFalsy, (void)); diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beGreaterThan.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beGreaterThan.h new file mode 100644 index 00000000..a2f9fbac --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beGreaterThan.h @@ -0,0 +1,6 @@ +#import "Expecta.h" + +EXPMatcherInterface(_beGreaterThan, (id expected)); +EXPMatcherInterface(beGreaterThan, (id expected)); + +#define beGreaterThan(expected) _beGreaterThan(EXPObjectify((expected))) diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beGreaterThanOrEqualTo.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beGreaterThanOrEqualTo.h new file mode 100644 index 00000000..3e91c64c --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beGreaterThanOrEqualTo.h @@ -0,0 +1,6 @@ +#import "Expecta.h" + +EXPMatcherInterface(_beGreaterThanOrEqualTo, (id expected)); +EXPMatcherInterface(beGreaterThanOrEqualTo, (id expected)); + +#define beGreaterThanOrEqualTo(expected) _beGreaterThanOrEqualTo(EXPObjectify((expected))) diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beIdenticalTo.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beIdenticalTo.h new file mode 100644 index 00000000..d13619f8 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beIdenticalTo.h @@ -0,0 +1,10 @@ +#import "Expecta.h" + +EXPMatcherInterface(_beIdenticalTo, (void *expected)); +EXPMatcherInterface(beIdenticalTo, (void *expected)); // to aid code completion + +#if __has_feature(objc_arc) +#define beIdenticalTo(expected) _beIdenticalTo((__bridge void*)expected) +#else +#define beIdenticalTo(expected) _beIdenticalTo(expected) +#endif diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beInTheRangeOf.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beInTheRangeOf.h new file mode 100644 index 00000000..8ea990e2 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beInTheRangeOf.h @@ -0,0 +1,6 @@ +#import "Expecta.h" + +EXPMatcherInterface(_beInTheRangeOf, (id expectedLowerBound, id expectedUpperBound)); +EXPMatcherInterface(beInTheRangeOf, (id expectedLowerBound, id expectedUpperBound)); + +#define beInTheRangeOf(expectedLowerBound, expectedUpperBound) _beInTheRangeOf(EXPObjectify((expectedLowerBound)), EXPObjectify((expectedUpperBound))) diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beInstanceOf.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beInstanceOf.h new file mode 100644 index 00000000..a8e8175d --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beInstanceOf.h @@ -0,0 +1,6 @@ +#import "Expecta.h" + +EXPMatcherInterface(beInstanceOf, (Class expected)); +EXPMatcherInterface(beAnInstanceOf, (Class expected)); +EXPMatcherInterface(beMemberOf, (Class expected)); +EXPMatcherInterface(beAMemberOf, (Class expected)); diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beKindOf.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beKindOf.h new file mode 100644 index 00000000..b8623e0b --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beKindOf.h @@ -0,0 +1,4 @@ +#import "Expecta.h" + +EXPMatcherInterface(beKindOf, (Class expected)); +EXPMatcherInterface(beAKindOf, (Class expected)); diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beLessThan.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beLessThan.h new file mode 100644 index 00000000..5ed0a24c --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beLessThan.h @@ -0,0 +1,6 @@ +#import "Expecta.h" + +EXPMatcherInterface(_beLessThan, (id expected)); +EXPMatcherInterface(beLessThan, (id expected)); + +#define beLessThan(expected) _beLessThan(EXPObjectify((expected))) diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beLessThanOrEqualTo.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beLessThanOrEqualTo.h new file mode 100644 index 00000000..2c313415 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beLessThanOrEqualTo.h @@ -0,0 +1,6 @@ +#import "Expecta.h" + +EXPMatcherInterface(_beLessThanOrEqualTo, (id expected)); +EXPMatcherInterface(beLessThanOrEqualTo, (id expected)); + +#define beLessThanOrEqualTo(expected) _beLessThanOrEqualTo(EXPObjectify((expected))) diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beNil.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beNil.h new file mode 100644 index 00000000..6d781620 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beNil.h @@ -0,0 +1,4 @@ +#import "Expecta.h" + +EXPMatcherInterface(beNil, (void)); +EXPMatcherInterface(beNull, (void)); diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beSubclassOf.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beSubclassOf.h new file mode 100644 index 00000000..65401c59 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beSubclassOf.h @@ -0,0 +1,4 @@ +#import "Expecta.h" + +EXPMatcherInterface(beSubclassOf, (Class expected)); +EXPMatcherInterface(beASubclassOf, (Class expected)); diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beSupersetOf.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beSupersetOf.h new file mode 100644 index 00000000..f9a47ba6 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beSupersetOf.h @@ -0,0 +1,4 @@ +#import "Expecta.h" + +EXPMatcherInterface(beSupersetOf, (id subset)); + diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beTruthy.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beTruthy.h new file mode 100644 index 00000000..1e4e78f9 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beTruthy.h @@ -0,0 +1,3 @@ +#import "Expecta.h" + +EXPMatcherInterface(beTruthy, (void)); diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beginWith.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beginWith.h new file mode 100644 index 00000000..07ddd6c2 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+beginWith.h @@ -0,0 +1,4 @@ +#import "Expecta.h" + +EXPMatcherInterface(beginWith, (id expected)); +EXPMatcherInterface(startWith, (id expected)); diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+conformTo.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+conformTo.h new file mode 100644 index 00000000..efc7b982 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+conformTo.h @@ -0,0 +1,3 @@ +#import "Expecta.h" + +EXPMatcherInterface(conformTo, (Protocol *expected)); diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+contain.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+contain.h new file mode 100644 index 00000000..58031461 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+contain.h @@ -0,0 +1,5 @@ +#import "Expecta.h" + +EXPMatcherInterface(_contain, (id expected)); +EXPMatcherInterface(contain, (id expected)); // to aid code completion +#define contain(expected) _contain(EXPObjectify((expected))) diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+endWith.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+endWith.h new file mode 100644 index 00000000..228cea95 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+endWith.h @@ -0,0 +1,3 @@ +#import "Expecta.h" + +EXPMatcherInterface(endWith, (id expected)); diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+equal.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+equal.h new file mode 100644 index 00000000..b4047c0e --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+equal.h @@ -0,0 +1,5 @@ +#import "Expecta.h" + +EXPMatcherInterface(_equal, (id expected)); +EXPMatcherInterface(equal, (id expected)); // to aid code completion +#define equal(...) _equal(EXPObjectify((__VA_ARGS__))) diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+haveCountOf.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+haveCountOf.h new file mode 100644 index 00000000..2e9aef57 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+haveCountOf.h @@ -0,0 +1,10 @@ +#import "Expecta.h" + +EXPMatcherInterface(haveCountOf, (NSUInteger expected)); +EXPMatcherInterface(haveCount, (NSUInteger expected)); +EXPMatcherInterface(haveACountOf, (NSUInteger expected)); +EXPMatcherInterface(haveLength, (NSUInteger expected)); +EXPMatcherInterface(haveLengthOf, (NSUInteger expected)); +EXPMatcherInterface(haveALengthOf, (NSUInteger expected)); + +#define beEmpty() haveCountOf(0) diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+match.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+match.h new file mode 100644 index 00000000..4f0e8e47 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+match.h @@ -0,0 +1,3 @@ +#import "Expecta.h" + +EXPMatcherInterface(match, (NSString *expected)); diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+postNotification.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+postNotification.h new file mode 100644 index 00000000..cdba4a34 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+postNotification.h @@ -0,0 +1,4 @@ +#import "Expecta.h" + +EXPMatcherInterface(postNotification, (id expectedNotification)); +EXPMatcherInterface(notify, (id expectedNotification)); diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+raise.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+raise.h new file mode 100644 index 00000000..1f7fae02 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+raise.h @@ -0,0 +1,4 @@ +#import "Expecta.h" + +EXPMatcherInterface(raise, (NSString *expectedExceptionName)); +#define raiseAny() raise(nil) diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+raiseWithReason.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+raiseWithReason.h new file mode 100644 index 00000000..2cf5a5d2 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+raiseWithReason.h @@ -0,0 +1,3 @@ +#import "Expecta.h" + +EXPMatcherInterface(raiseWithReason, (NSString *expectedExceptionName, NSString *expectedReason)); diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+respondTo.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+respondTo.h new file mode 100644 index 00000000..279131dd --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers+respondTo.h @@ -0,0 +1,3 @@ +#import "Expecta.h" + +EXPMatcherInterface(respondTo, (SEL expected)); diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers.h new file mode 100644 index 00000000..ed6ef85f --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/EXPMatchers.h @@ -0,0 +1,25 @@ +#import "EXPMatchers+beNil.h" +#import "EXPMatchers+equal.h" +#import "EXPMatchers+beInstanceOf.h" +#import "EXPMatchers+beKindOf.h" +#import "EXPMatchers+beSubclassOf.h" +#import "EXPMatchers+conformTo.h" +#import "EXPMatchers+beTruthy.h" +#import "EXPMatchers+beFalsy.h" +#import "EXPMatchers+contain.h" +#import "EXPMatchers+beSupersetOf.h" +#import "EXPMatchers+haveCountOf.h" +#import "EXPMatchers+beIdenticalTo.h" +#import "EXPMatchers+beGreaterThan.h" +#import "EXPMatchers+beGreaterThanOrEqualTo.h" +#import "EXPMatchers+beLessThan.h" +#import "EXPMatchers+beLessThanOrEqualTo.h" +#import "EXPMatchers+beInTheRangeOf.h" +#import "EXPMatchers+beCloseTo.h" +#import "EXPMatchers+raise.h" +#import "EXPMatchers+raiseWithReason.h" +#import "EXPMatchers+respondTo.h" +#import "EXPMatchers+postNotification.h" +#import "EXPMatchers+beginWith.h" +#import "EXPMatchers+endWith.h" +#import "EXPMatchers+match.h" diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/Expecta.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/Expecta.h new file mode 100644 index 00000000..066e9883 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/Expecta.h @@ -0,0 +1,15 @@ +#import + +//! Project version number for Expecta. +FOUNDATION_EXPORT double ExpectaVersionNumber; + +//! Project version string for Expecta. +FOUNDATION_EXPORT const unsigned char ExpectaVersionString[]; + +#import +#import +#import + +// Enable shorthand by default +#define expect(...) EXP_expect((__VA_ARGS__)) +#define failure(...) EXP_failure((__VA_ARGS__)) diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/ExpectaObject.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/ExpectaObject.h new file mode 100644 index 00000000..ef2a38aa --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/ExpectaObject.h @@ -0,0 +1,18 @@ +#import + +#define EXPObjectify(value) _EXPObjectify(@encode(__typeof__((value))), (value)) +#define EXP_expect(actual) _EXP_expect(self, __LINE__, __FILE__, ^id{ return EXPObjectify((actual)); }) +#define EXPMatcherInterface(matcherName, matcherArguments) _EXPMatcherInterface(matcherName, matcherArguments) +#define EXPMatcherImplementationBegin(matcherName, matcherArguments) _EXPMatcherImplementationBegin(matcherName, matcherArguments) +#define EXPMatcherImplementationEnd _EXPMatcherImplementationEnd +#define EXPMatcherAliasImplementation(newMatcherName, oldMatcherName, matcherArguments) _EXPMatcherAliasImplementation(newMatcherName, oldMatcherName, matcherArguments) + +#define EXP_failure(message) EXPFail(self, __LINE__, __FILE__, message) + + +@interface Expecta : NSObject + ++ (NSTimeInterval)asynchronousTestTimeout; ++ (void)setAsynchronousTestTimeout:(NSTimeInterval)timeout; + +@end diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/ExpectaSupport.h b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/ExpectaSupport.h new file mode 100644 index 00000000..fb0ef4f4 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Headers/ExpectaSupport.h @@ -0,0 +1,73 @@ +#import "EXPExpect.h" +#import "EXPBlockDefinedMatcher.h" + +#ifdef __cplusplus +extern "C" { +#endif + +id _EXPObjectify(const char *type, ...); +EXPExpect *_EXP_expect(id testCase, int lineNumber, const char *fileName, EXPIdBlock actualBlock); + +void EXPFail(id testCase, int lineNumber, const char *fileName, NSString *message); +NSString *EXPDescribeObject(id obj); + +void EXP_prerequisite(EXPBoolBlock block); +void EXP_match(EXPBoolBlock block); +void EXP_failureMessageForTo(EXPStringBlock block); +void EXP_failureMessageForNotTo(EXPStringBlock block); + +#if __has_feature(objc_arc) +#define _EXP_release(x) +#define _EXP_autorelease(x) (x) + +#else +#define _EXP_release(x) [x release] +#define _EXP_autorelease(x) [x autorelease] +#endif + +// workaround for the categories bug: http://developer.apple.com/library/mac/#qa/qa1490/_index.html +#define EXPFixCategoriesBug(name) \ +__attribute__((constructor)) static void EXPFixCategoriesBug##name() {} + +#define _EXPMatcherInterface(matcherName, matcherArguments) \ +@interface EXPExpect (matcherName##Matcher) \ +@property (nonatomic, readonly) void(^ matcherName) matcherArguments; \ +@end + +#define _EXPMatcherImplementationBegin(matcherName, matcherArguments) \ +EXPFixCategoriesBug(EXPMatcher##matcherName##Matcher); \ +@implementation EXPExpect (matcherName##Matcher) \ +@dynamic matcherName;\ +- (void(^) matcherArguments) matcherName { \ + EXPBlockDefinedMatcher *matcher = [[EXPBlockDefinedMatcher alloc] init]; \ + [[[NSThread currentThread] threadDictionary] setObject:matcher forKey:@"EXP_currentMatcher"]; \ + __block id actual = self.actual; \ + __block void (^prerequisite)(EXPBoolBlock block) = ^(EXPBoolBlock block) { EXP_prerequisite(block); }; \ + __block void (^match)(EXPBoolBlock block) = ^(EXPBoolBlock block) { EXP_match(block); }; \ + __block void (^failureMessageForTo)(EXPStringBlock block) = ^(EXPStringBlock block) { EXP_failureMessageForTo(block); }; \ + __block void (^failureMessageForNotTo)(EXPStringBlock block) = ^(EXPStringBlock block) { EXP_failureMessageForNotTo(block); }; \ + prerequisite(nil); match(nil); failureMessageForTo(nil); failureMessageForNotTo(nil); \ + void (^matcherBlock) matcherArguments = [^ matcherArguments { \ + { + +#define _EXPMatcherImplementationEnd \ + } \ + [self applyMatcher:matcher to:&actual]; \ + } copy]; \ + _EXP_release(matcher); \ + return _EXP_autorelease(matcherBlock); \ +} \ +@end + +#define _EXPMatcherAliasImplementation(newMatcherName, oldMatcherName, matcherArguments) \ +EXPFixCategoriesBug(EXPMatcher##newMatcherName##Matcher); \ +@implementation EXPExpect (newMatcherName##Matcher) \ +@dynamic newMatcherName;\ +- (void(^) matcherArguments) newMatcherName { \ + return [self oldMatcherName]; \ +}\ +@end + +#ifdef __cplusplus +} +#endif diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/LICENSE b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/LICENSE new file mode 100644 index 00000000..a036c85f --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2015 Specta Team - https://github.com/specta + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Resources/Info.plist b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Resources/Info.plist new file mode 100644 index 00000000..ecc0430d Binary files /dev/null and b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/A/Resources/Info.plist differ diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/Current b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/Current new file mode 120000 index 00000000..8c7e5a66 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Expecta.framework/Versions/Current @@ -0,0 +1 @@ +A \ No newline at end of file diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Headers b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Headers new file mode 120000 index 00000000..a177d2a6 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Headers @@ -0,0 +1 @@ +Versions/Current/Headers \ No newline at end of file diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/LICENSE b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/LICENSE new file mode 120000 index 00000000..7da29fc7 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/LICENSE @@ -0,0 +1 @@ +Versions/Current/LICENSE \ No newline at end of file diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Resources b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Resources new file mode 120000 index 00000000..953ee36f --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Resources @@ -0,0 +1 @@ +Versions/Current/Resources \ No newline at end of file diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Specta b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Specta new file mode 120000 index 00000000..c6cd0553 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Specta @@ -0,0 +1 @@ +Versions/Current/Specta \ No newline at end of file diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/SPTSharedExampleGroups.h b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/SPTSharedExampleGroups.h new file mode 100644 index 00000000..090acba7 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/SPTSharedExampleGroups.h @@ -0,0 +1,17 @@ +#import +#import +#import + +@class _XCTestCaseImplementation; + +@class SPTExampleGroup; + +@interface SPTSharedExampleGroups : XCTestCase + ++ (void)addSharedExampleGroupWithName:(NSString *)name block:(SPTDictionaryBlock)block exampleGroup:(SPTExampleGroup *)exampleGroup; ++ (SPTDictionaryBlock)sharedExampleGroupWithName:(NSString *)name exampleGroup:(SPTExampleGroup *)exampleGroup; + +- (void)sharedExampleGroups; + +@end + diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/SPTSpec.h b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/SPTSpec.h new file mode 100644 index 00000000..ada4ea2d --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/SPTSpec.h @@ -0,0 +1,28 @@ +#import +#import + +@class + SPTTestSuite +, SPTCompiledExample +; + +@interface SPTSpec : XCTestCase + +@property (strong) XCTestCaseRun *spt_run; +@property (nonatomic) BOOL spt_pending; +@property (nonatomic) BOOL spt_skipped; + ++ (BOOL)spt_isDisabled; ++ (void)spt_setDisabled:(BOOL)disabled; ++ (BOOL)spt_focusedExamplesExist; ++ (SEL)spt_convertToTestMethod:(SPTCompiledExample *)example; ++ (SPTTestSuite *)spt_testSuite; ++ (void)spt_setCurrentTestSuite; ++ (void)spt_unsetCurrentTestSuite; ++ (void)spt_setCurrentTestSuiteFileName:(NSString *)fileName lineNumber:(NSUInteger)lineNumber; + +- (void)spec; +- (BOOL)spt_shouldRunExample:(SPTCompiledExample *)example; +- (void)spt_runExample:(SPTCompiledExample *)example; + +@end diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/Specta.h b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/Specta.h new file mode 100644 index 00000000..dda17f91 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/Specta.h @@ -0,0 +1,14 @@ +#import +#import + +//! Project version number for Specta. +FOUNDATION_EXPORT double SpectaVersionNumber; + +//! Project version string for Specta. +FOUNDATION_EXPORT const unsigned char SpectaVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + +#import +#import +#import diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/SpectaDSL.h b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/SpectaDSL.h new file mode 100644 index 00000000..284d4f54 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/SpectaDSL.h @@ -0,0 +1,90 @@ +#import + +#define SpecBegin(name) _SPTSpecBegin(name, __FILE__, __LINE__) +#define SpecEnd _SPTSpecEnd + +#define SharedExamplesBegin(name) _SPTSharedExampleGroupsBegin(name) +#define SharedExamplesEnd _SPTSharedExampleGroupsEnd +#define SharedExampleGroupsBegin(name) _SPTSharedExampleGroupsBegin(name) +#define SharedExampleGroupsEnd _SPTSharedExampleGroupsEnd + +typedef void (^DoneCallback)(void); + +OBJC_EXTERN void describe(NSString *name, void (^block)()); +OBJC_EXTERN void fdescribe(NSString *name, void (^block)()); + +OBJC_EXTERN void context(NSString *name, void (^block)()); +OBJC_EXTERN void fcontext(NSString *name, void (^block)()); + +OBJC_EXTERN void it(NSString *name, void (^block)()); +OBJC_EXTERN void fit(NSString *name, void (^block)()); + +OBJC_EXTERN void example(NSString *name, void (^block)()); +OBJC_EXTERN void fexample(NSString *name, void (^block)()); + +OBJC_EXTERN void specify(NSString *name, void (^block)()); +OBJC_EXTERN void fspecify(NSString *name, void (^block)()); + +#define pending(...) spt_pending_(__VA_ARGS__, nil) +#define xdescribe(...) spt_pending_(__VA_ARGS__, nil) +#define xcontext(...) spt_pending_(__VA_ARGS__, nil) +#define xexample(...) spt_pending_(__VA_ARGS__, nil) +#define xit(...) spt_pending_(__VA_ARGS__, nil) +#define xspecify(...) spt_pending_(__VA_ARGS__, nil) + +OBJC_EXTERN void beforeAll(void (^block)()); +OBJC_EXTERN void afterAll(void (^block)()); + +OBJC_EXTERN void beforeEach(void (^block)()); +OBJC_EXTERN void afterEach(void (^block)()); + +OBJC_EXTERN void before(void (^block)()); +OBJC_EXTERN void after(void (^block)()); + +OBJC_EXTERN void sharedExamplesFor(NSString *name, void (^block)(NSDictionary *data)); +OBJC_EXTERN void sharedExamples(NSString *name, void (^block)(NSDictionary *data)); + +#define itShouldBehaveLike(...) spt_itShouldBehaveLike_(@(__FILE__), __LINE__, __VA_ARGS__) +#define itBehavesLike(...) spt_itShouldBehaveLike_(@(__FILE__), __LINE__, __VA_ARGS__) + +OBJC_EXTERN void waitUntil(void (^block)(DoneCallback done)); +/** + * Runs the @c block and waits until the @c done block is called or the + * @c timeout has passed. + * + * @param timeout timeout for this @c block only; does not affect the global + * timeout, as @c setAsyncSpecTimeout() does. + * @param ^block runs test code + */ +OBJC_EXTERN void waitUntilTimeout(NSTimeInterval timeout, void (^block)(DoneCallback done)); + +OBJC_EXTERN void setAsyncSpecTimeout(NSTimeInterval timeout); + +// ---------------------------------------------------------------------------- + +#define _SPTSpecBegin(name, file, line) \ +@interface name##Spec : SPTSpec \ +@end \ +@implementation name##Spec \ +- (void)spec { \ + [[self class] spt_setCurrentTestSuiteFileName:(@(file)) lineNumber:(line)]; + +#define _SPTSpecEnd \ +} \ +@end + +#define _SPTSharedExampleGroupsBegin(name) \ +@interface name##SharedExampleGroups : SPTSharedExampleGroups \ +@end \ +@implementation name##SharedExampleGroups \ +- (void)sharedExampleGroups { + +#define _SPTSharedExampleGroupsEnd \ +} \ +@end + +OBJC_EXTERN void spt_it_(NSString *name, NSString *fileName, NSUInteger lineNumber, void (^block)()); +OBJC_EXTERN void spt_fit_(NSString *name, NSString *fileName, NSUInteger lineNumber, void (^block)()); +OBJC_EXTERN void spt_pending_(NSString *name, ...); +OBJC_EXTERN void spt_itShouldBehaveLike_(NSString *fileName, NSUInteger lineNumber, NSString *name, id dictionaryOrBlock); +OBJC_EXTERN void spt_itShouldBehaveLike_block(NSString *fileName, NSUInteger lineNumber, NSString *name, NSDictionary *(^block)()); diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/SpectaTypes.h b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/SpectaTypes.h new file mode 100644 index 00000000..f1f0ae3e --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/SpectaTypes.h @@ -0,0 +1,5 @@ +@class SPTSpec; + +typedef void (^SPTVoidBlock)(); +typedef void (^SPTSpecBlock)(SPTSpec *spec); +typedef void (^SPTDictionaryBlock)(NSDictionary *dictionary); diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/XCTestCase+Specta.h b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/XCTestCase+Specta.h new file mode 100644 index 00000000..9ca8f8ab --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Headers/XCTestCase+Specta.h @@ -0,0 +1,7 @@ +#import + +@interface XCTestCase (Specta) + +- (void)spt_handleException:(NSException *)exception; + +@end diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/LICENSE b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/LICENSE new file mode 100644 index 00000000..ca257c03 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2012-2014 Specta Team. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Resources/Info.plist b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Resources/Info.plist new file mode 100644 index 00000000..9243d7f5 Binary files /dev/null and b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Resources/Info.plist differ diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Specta b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Specta new file mode 100644 index 00000000..daccdba2 Binary files /dev/null and b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/A/Specta differ diff --git a/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/Current b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/Current new file mode 120000 index 00000000..8c7e5a66 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Frameworks/Specta.framework/Versions/Current @@ -0,0 +1 @@ +A \ No newline at end of file diff --git a/core/ConnectSDKIntegrationTests/Info.plist b/core/ConnectSDKIntegrationTests/Info.plist new file mode 100644 index 00000000..cb399fd9 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.connectsdk.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/core/ConnectSDKIntegrationTests/OCMStubRecorder+SpectaAsync.h b/core/ConnectSDKIntegrationTests/OCMStubRecorder+SpectaAsync.h new file mode 100644 index 00000000..ef4b5f65 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/OCMStubRecorder+SpectaAsync.h @@ -0,0 +1,32 @@ +// +// OCMStubRecorder+SpectaAsync.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/27/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN +@interface OCMStubRecorder (SpectaAsync) + +/// Convenience method to call the Specta's @c DoneCallback received from the +/// @c waitUntil() method. +- (id)andDoneWaiting:(DoneCallback)done; + +@end +NS_ASSUME_NONNULL_END diff --git a/core/ConnectSDKIntegrationTests/OCMStubRecorder+SpectaAsync.m b/core/ConnectSDKIntegrationTests/OCMStubRecorder+SpectaAsync.m new file mode 100644 index 00000000..2a0d1c31 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/OCMStubRecorder+SpectaAsync.m @@ -0,0 +1,31 @@ +// +// OCMStubRecorder+SpectaAsync.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/27/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "OCMStubRecorder+SpectaAsync.h" + +@implementation OCMStubRecorder (SpectaAsync) + +- (id __nonnull)andDoneWaiting:(DoneCallback __nonnull)done { + return [self andDo:^(NSInvocation *_) { + done(); + }]; +} + +@end diff --git a/core/ConnectSDKIntegrationTests/SupportingFiles/Info.plist b/core/ConnectSDKIntegrationTests/SupportingFiles/Info.plist new file mode 100644 index 00000000..cb399fd9 --- /dev/null +++ b/core/ConnectSDKIntegrationTests/SupportingFiles/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.connectsdk.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/core/ConnectSDKTests/ConnectSDKDefaultPlatformsTests.m b/core/ConnectSDKTests/ConnectSDKDefaultPlatformsTests.m new file mode 100644 index 00000000..8c8436d1 --- /dev/null +++ b/core/ConnectSDKTests/ConnectSDKDefaultPlatformsTests.m @@ -0,0 +1,62 @@ +// +// ConnectSDKDefaultPlatformsTests.m +// ConnectSDKDefaultPlatformsTests +// +// Created by Eugene Nikolskyi on 11/11/14. +// Copyright (c) 2014 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import "ConnectSDKDefaultPlatforms.h" + +/// Tests for the ConnectSDK's default platforms list. +@interface ConnectSDKDefaultPlatformsTests : XCTestCase + +@end + +@implementation ConnectSDKDefaultPlatformsTests + +/// Tests that the default platforms list is defined and not empty. +- (void)testThereShouldBeDefaultPlatforms { + NSDictionary *platforms = kConnectSDKDefaultPlatforms; + XCTAssertGreaterThan(platforms.count, 0, @"The default platforms list must not be empty"); +} + +/// Tests that the default service classes are available in runtime. +/// NB: the success of this test relies on the test target's setup. +- (void)testServiceClassesAreAvailable { + NSDictionary *platforms = kConnectSDKDefaultPlatforms; + [platforms enumerateKeysAndObjectsWithOptions:NSEnumerationConcurrent + usingBlock:^(NSString *key, id obj, BOOL *stop) { + XCTAssertNotNil(NSClassFromString(key), + @"Service class %@ must be available at runtime", key); + }]; +} + +/// Tests that the default discovery provider classes are available at runtime. +/// NB: the success of this test relies on the test target's setup. +- (void)testDiscoveryProviderClassesAreAvailable { + NSDictionary *platforms = kConnectSDKDefaultPlatforms; + // we get the unique providers only because one can be used for different + // services + NSSet *uniqueDiscoveryProviders = [NSSet setWithArray:platforms.allValues]; + [uniqueDiscoveryProviders enumerateObjectsWithOptions:NSEnumerationConcurrent + usingBlock:^(NSString *obj, BOOL *stop) { + XCTAssertNotNil(NSClassFromString(obj), + @"Discovery provider class %@ must be available at runtime", obj); + }]; +} + +@end diff --git a/core/ConnectSDKTests/ConnectSDKTests-Prefix.pch b/core/ConnectSDKTests/ConnectSDKTests-Prefix.pch new file mode 100644 index 00000000..ae2c7d7d --- /dev/null +++ b/core/ConnectSDKTests/ConnectSDKTests-Prefix.pch @@ -0,0 +1,40 @@ +// +// ConnectSDKTests-Prefix.pch +// ConnectSDK +// +// Created by Eugene Nikolskyi on 11/11/14. +// Copyright (c) 2014 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef ConnectSDK_ConnectSDKTests_Prefix_pch +#define ConnectSDK_ConnectSDKTests_Prefix_pch + +// Include any system framework and library headers here that should be included in all compilation units. +// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file. + +@import UIKit; +@import XCTest; + +#import +#define OCMOCK_NOTNIL [OCMArg isNotNil] + +#define CONNECT_SDK_VERSION @"0.test" + +#define DLog(...) + +/// Default timeout for asynchronous tests, in seconds +#define kDefaultAsyncTestTimeout 0.5 + +#endif diff --git a/core/ConnectSDKTests/Discovery/DiscoveryManagerTests.m b/core/ConnectSDKTests/Discovery/DiscoveryManagerTests.m new file mode 100644 index 00000000..83f89205 --- /dev/null +++ b/core/ConnectSDKTests/Discovery/DiscoveryManagerTests.m @@ -0,0 +1,129 @@ +// +// DiscoveryManagerTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-06-19. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DiscoveryManager_Private.h" + +#import "AppStateChangeNotifier.h" +#import "DiscoveryProvider.h" + +#import "OCMArg+ArgumentCaptor.h" + +@interface MockDeviceService : DeviceService @end +@implementation MockDeviceService + ++ (NSDictionary *)discoveryParameters { + return @{@"serviceId": @"mockService"}; +} + +@end + + + +@interface DiscoveryManagerTests : XCTestCase + +@property (nonatomic, strong) DiscoveryManager *discoveryManager; +@property (nonatomic, strong) id /*AppStateChangeNotifier **/ stateNotifierMock; +@property (nonatomic, strong) id /*DiscoveryProvider **/ discoveryProviderMock; + +@end + +@implementation DiscoveryManagerTests + +#pragma mark - Setup + +- (void)setUp { + [super setUp]; + + self.stateNotifierMock = OCMClassMock([AppStateChangeNotifier class]); + self.discoveryProviderMock = OCMClassMock([DiscoveryProvider class]); + self.discoveryManager = [self createDiscoveryManager]; +} + +- (DiscoveryManager *)createDiscoveryManager { + DiscoveryManager *discoveryManager = [[DiscoveryManager alloc] + initWithAppStateChangeNotifier:self.stateNotifierMock]; + [discoveryManager registerDeviceService:[MockDeviceService class] + withDiscoveryProviderFactory:^{ + return self.discoveryProviderMock; + }]; + + return discoveryManager; +} + +- (void)tearDown { + [self.discoveryManager stopDiscovery]; + self.discoveryManager = nil; + self.stateNotifierMock = nil; + + [super tearDown]; +} + +#pragma mark - UIApplication State Change Tests + +- (void)testDefaultStateNotifierShouldBeCreated { + DiscoveryManager *discoveryManager = [DiscoveryManager new]; + XCTAssertNotNil(discoveryManager.appStateChangeNotifier, + @"a real AppStateChangeNotifier should be created"); +} + +- (void)testStartDiscoveryShouldStartListeningStateNotifier { + OCMExpect([self.stateNotifierMock startListening]); + [self.discoveryManager startDiscovery]; + OCMVerifyAll(self.stateNotifierMock); +} + +- (void)testStopDiscoveryShouldStopListeningStateNotifier { + OCMExpect([self.stateNotifierMock stopListening]); + [self.discoveryManager startDiscovery]; + [self.discoveryManager stopDiscovery]; + OCMVerifyAll(self.stateNotifierMock); +} + +- (void)testBackgroundingShouldPauseDiscoveryProviders { + AppStateChangeBlock backgroundStateBlock; + OCMExpect([self.stateNotifierMock setDidBackgroundBlock: + [OCMArg captureBlockTo:&backgroundStateBlock]]); + + DiscoveryManager *discoveryManager = [self createDiscoveryManager]; + [discoveryManager startDiscovery]; + + OCMExpect([self.discoveryProviderMock pauseDiscovery]); + backgroundStateBlock(); + OCMVerifyAll(self.discoveryProviderMock); +} + +- (void)testForegroundingShouldResumeDiscoveryProviders { + AppStateChangeBlock backgroundStateBlock; + AppStateChangeBlock foregroundStateBlock; + OCMExpect([self.stateNotifierMock setDidBackgroundBlock: + [OCMArg captureBlockTo:&backgroundStateBlock]]); + OCMExpect([self.stateNotifierMock setDidForegroundBlock: + [OCMArg captureBlockTo:&foregroundStateBlock]]); + + DiscoveryManager *discoveryManager = [self createDiscoveryManager]; + [discoveryManager startDiscovery]; + + backgroundStateBlock(); + OCMExpect([self.discoveryProviderMock resumeDiscovery]); + foregroundStateBlock(); + OCMVerifyAll(self.discoveryProviderMock); +} + +@end diff --git a/core/ConnectSDKTests/Discovery/Providers/SSDPDiscoveryProviderTests.m b/core/ConnectSDKTests/Discovery/Providers/SSDPDiscoveryProviderTests.m new file mode 100644 index 00000000..4495e881 --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/SSDPDiscoveryProviderTests.m @@ -0,0 +1,359 @@ +// +// SSDPDiscoveryProviderTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 11/11/14. +// Copyright (c) 2014 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import + +#import "SSDPDiscoveryProvider_Private.h" +#import "SSDPSocketListener.h" +#import "ServiceDescription.h" + +static NSString *const kSSDPMulticastIPAddress = @"239.255.255.250"; +static const NSUInteger kSSDPMulticastTCPPort = 1900; + +static NSString *const kKeySSDP = @"ssdp"; +static NSString *const kKeyFilter = @"filter"; +static NSString *const kKeyServiceID = @"serviceId"; + +static inline NSString *httpHeaderValue(CFHTTPMessageRef msg, NSString *header) { + return CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(msg, (__bridge CFStringRef)header)); +} + + +/// Tests for the SSDPDiscoveryProvider class. +/// In some tests, the SSDPSocketListener class is mocked to verify the output +/// data or provide some input data. Also, HTTP requests/responses are mocked +/// to fake device description data. +@interface SSDPDiscoveryProviderTests : XCTestCase + +@property (nonatomic, strong) SSDPDiscoveryProvider *provider; + +@end + +@implementation SSDPDiscoveryProviderTests + +#pragma mark - Setup + +- (void)setUp { + [super setUp]; + + self.provider = [SSDPDiscoveryProvider new]; +} + +- (void)tearDown { + self.provider = nil; + + [OHHTTPStubs removeAllStubs]; + + [super tearDown]; +} + +#pragma mark - General tests + +/// Tests that a new provider is not running by default. +- (void)testShouldNotBeRunningAfterCreation { + XCTAssertFalse(self.provider.isRunning, @"The provider must not be running after creation"); +} + +#pragma mark - Device Filters tests + +/// Tests that an attempt to remove a nil device filter is handled gracefully. +- (void)testRemovingNilDeviceFilterShouldNotCrash { + [self.provider removeDeviceFilter:nil]; + + XCTAssert(YES, @"Removing nil device filter must not crash"); +} + +/// Tests that an attempt to remove a device filter that wasn't added before is +/// handled gracefully. +- (void)testRemovingUnknownDeviceFilterShouldNotCrash { + NSDictionary *filter = @{kKeySSDP: @{kKeyFilter: @"some:thing"}}; + [self.provider removeDeviceFilter:filter]; + + XCTAssert(YES, @"Removing not previously add device filter must not crash"); +} + +#pragma mark - Discovery & Delegate tests + +/// Tests that -startDiscovery sets up the isRunning flag. +- (void)testShouldBeRunningAfterDiscoveryStart { + [self.provider startDiscovery]; + + XCTAssertTrue(self.provider.isRunning, @"The provider should be running after discovery start"); +} + +/// Tests that starting a discovery should send the correct multicast SSDP +/// M-SEARCH request, with the headers according to the UPnP specification. +- (void)testStartDiscoveryShouldSendCorrectSearchRequest { + // Arrange + id searchSocketMock = OCMClassMock([SSDPSocketListener class]); + self.provider.searchSocket = searchSocketMock; + + NSDictionary *filter = @{kKeySSDP: @{kKeyFilter: @"some:thing"}}; + [self.provider addDeviceFilter:filter]; + + // Act + [self.provider startDiscovery]; + + // Assert + BOOL (^httpDataVerificationBlock)(id obj) = ^BOOL(NSData *data) { + CFHTTPMessageRef msg = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, YES); + XCTAssertTrue(CFHTTPMessageAppendBytes(msg, data.bytes, data.length), + @"Couldn't parse the HTTP request"); + + // assert the SSDP search request is according to the UPnP specification + NSString *statusLine = [@[CFBridgingRelease(CFHTTPMessageCopyRequestMethod(msg)), + CFBridgingRelease(CFURLCopyPath(CFHTTPMessageCopyRequestURL(msg))), + CFBridgingRelease(CFHTTPMessageCopyVersion(msg))] componentsJoinedByString:@" "]; + XCTAssertEqualObjects(statusLine, @"M-SEARCH * HTTP/1.1", @"The status line is incorrect"); + + NSString *host = httpHeaderValue(msg, @"HOST"); + NSString *correctHost = [NSString stringWithFormat:@"%@:%lu", + kSSDPMulticastIPAddress, + (unsigned long) kSSDPMulticastTCPPort]; + XCTAssertEqualObjects(host, correctHost, @"The HOST header value is incorrect"); + + NSString *man = httpHeaderValue(msg, @"MAN"); + XCTAssertEqualObjects(man, @"\"ssdp:discover\"", @"The MAN header value is incorrect"); + + NSInteger mx = [httpHeaderValue(msg, @"MX") integerValue]; + XCTAssertGreaterThan(mx, 1, @"The MX header value must be > 1"); + XCTAssertLessThanOrEqual(mx, 5, @"The MX header value must be <= 5"); + + NSString *searchTarget = httpHeaderValue(msg, @"ST"); + XCTAssertEqualObjects(searchTarget, filter[kKeySSDP][kKeyFilter], @"The Search Target header value is incorrect"); + + NSString *userAgent = httpHeaderValue(msg, @"USER-AGENT"); + if (userAgent) { + XCTAssertNotEqual([userAgent rangeOfString:@"UPnP/1.1"].location, NSNotFound, + @"The User Agent header value must include UPnP version"); + } + + NSData *body = CFBridgingRelease(CFHTTPMessageCopyBody(msg)); + XCTAssertEqual(body.length, 0, @"There must be no body"); + + return YES; + }; + + OCMVerify([searchSocketMock sendData:[OCMArg checkWithBlock:httpDataVerificationBlock] + toAddress:kSSDPMulticastIPAddress + andPort:kSSDPMulticastTCPPort]); +} + +/// Tests that the delegate's -discoveryProvider:didFindService: method is +/// called with the correct service description after receiving a search +/// response. The test uses the `ssdp_device_description.xml` file for mocked +/// device description response. +- (void)testDelegateDidFindServiceShouldBeCalledAfterReceivingSearchResponse { + // Arrange + id delegateMock = OCMProtocolMock(@protocol(DiscoveryProviderDelegate)); + self.provider.delegate = delegateMock; + + id searchSocketMock = OCMClassMock([SSDPSocketListener class]); + self.provider.searchSocket = searchSocketMock; + + NSString *serviceType = @"urn:schemas-upnp-org:device:thing:1"; + NSDictionary *filter = @{kKeySSDP: @{kKeyFilter: serviceType}, + kKeyServiceID: @"SomethingNew"}; + [self.provider addDeviceFilter:filter]; + + NSString *kServiceAddress = @"127.0.1.2"; + NSString *kDeviceDescriptionURL = [NSString stringWithFormat:@"http://%@:7676/root", kServiceAddress]; + NSString *kUUID = @"f21e800a-1000-ab08-8e5a-76f4fcb5e772"; + + OCMStub([searchSocketMock sendData:[OCMArg isNotNil] + toAddress:kSSDPMulticastIPAddress + andPort:kSSDPMulticastTCPPort]).andDo((^(NSInvocation *invocation) { + NSString *searchResponse = [NSString stringWithFormat: + @"HTTP/1.1 200 OK\r\n" + @"CACHE-CONTROL: max-age=1800\r\n" + @"Date: Thu, 01 Jan 1970 04:04:04 GMT\r\n" + @"EXT:\r\n" + @"LOCATION: %@\r\n" + @"SERVER: Linux/4.2 UPnP/1.1 MagicDevice/1.0\r\n" + @"ST: %@\r\n" + @"USN: uuid:%@::urn:schemas-upnp-org:device:thing:1\r\n" + @"Content-Length: 0\r\n" + @"\r\n", + kDeviceDescriptionURL, serviceType, kUUID]; + NSData *searchResponseData = [searchResponse dataUsingEncoding:NSUTF8StringEncoding]; + + [self.provider socket:searchSocketMock + didReceiveData:searchResponseData + fromAddress:kServiceAddress]; + })); + + XCTestExpectation *deviceDescriptionResponseExpectation = [self expectationWithDescription:@"Device description response"]; + + NSString *deviceDescriptionResponseFilePath = OHPathForFileInBundle(@"ssdp_device_description.xml", nil); + [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { + return [kDeviceDescriptionURL isEqualToString:request.URL.absoluteString]; + } withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) { + return [OHHTTPStubsResponse responseWithFileAtPath:deviceDescriptionResponseFilePath + statusCode:200 + headers:nil]; + }]; + + OCMExpect(([delegateMock discoveryProvider:[OCMArg isEqual:self.provider] + didFindService:[OCMArg checkWithBlock:^BOOL(ServiceDescription *service) { + XCTAssertEqualObjects(service.address, kServiceAddress, @"The service's address is incorrect"); + XCTAssertEqualObjects(service.serviceId, filter[kKeyServiceID], @"The service ID is incorrect"); + XCTAssertEqualObjects(service.UUID, kUUID, @"The UUID is incorrect"); + XCTAssertEqualObjects(service.type, serviceType, @"The service type is incorrect"); + XCTAssertEqualObjects(service.friendlyName, @"short user-friendly title", @"The friendly name is incorrect"); + XCTAssertEqualObjects(service.manufacturer, @"manufacturer name", @"The manufacturer is incorrect"); + XCTAssertEqualObjects(service.modelName, @"model name", @"The model name is incorrect"); + XCTAssertEqualObjects(service.modelDescription, @"long user-friendly title", @"The model description is incorrect"); + XCTAssertEqualObjects(service.modelNumber, @"model number", @"The model number is incorrect"); + XCTAssertEqualObjects(service.commandURL.absoluteString, kDeviceDescriptionURL, @"The command URL is incorrect"); + XCTAssertEqualObjects(service.locationXML, [NSString stringWithContentsOfFile:deviceDescriptionResponseFilePath + encoding:NSUTF8StringEncoding + error:nil], @"The XML content is incorrect"); +// XCTAssertEqual(service.port, 9999, @"The port is incorrect"); +// XCTAssertEqualObjects(service.version, @"1", @"The version is incorrect"); + + XCTAssertEqual(service.serviceList.count, 1, @"The service count is incorrect"); + NSDictionary *serviceInfo = @{@"serviceType": @{@"text": @"urn:schemas-upnp-org:service:serviceType:v"}, + @"serviceId": @{@"text": @"urn:upnp-org:serviceId:serviceID"}, + @"SCPDURL": @{@"text": @"http://127.0.1.2:9999/scpd"}, + @"controlURL": @{@"text": @"http://127.0.1.2:9999/cnc"}, + @"eventSubURL": @{@"text": @"http://127.0.1.2:9999/event"}, + @"text": @""}; + XCTAssertEqualObjects(service.serviceList[0], serviceInfo, @"The service info is incorrect"); + + [deviceDescriptionResponseExpectation fulfill]; + return YES; + }]])); + + // Act + [self.provider startDiscovery]; + + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error, @"Test timeout"); + OCMVerifyAll(delegateMock); + }]; +} + +/// Tests that the delegate's -discoveryProvider:didLoseService: method is +/// called with the correct service description (matching the found one) after +/// receiving a UPnP bye-bye notification. The test uses the +/// `ssdp_device_description.xml` file for mocked device description response. +- (void)testDelegateDidLoseServiceShouldBeCalledAfterReceivingByeByeNotification { + // Arrange + id delegateMock = OCMProtocolMock(@protocol(DiscoveryProviderDelegate)); + self.provider.delegate = delegateMock; + + id searchSocketMock = OCMClassMock([SSDPSocketListener class]); + self.provider.searchSocket = searchSocketMock; + + NSString *serviceType = @"urn:schemas-upnp-org:device:thing:1"; + NSDictionary *filter = @{kKeySSDP: @{kKeyFilter: serviceType}, + kKeyServiceID: @"SomethingNew"}; + [self.provider addDeviceFilter:filter]; + + NSString *kServiceAddress = @"127.0.1.2"; + NSString *kDeviceDescriptionURL = [NSString stringWithFormat:@"http://%@:7676/root", kServiceAddress]; + NSString *kUUID = @"f21e800a-1000-ab08-8e5a-76f4fcb5e772"; + + OCMStub([searchSocketMock sendData:[OCMArg isNotNil] + toAddress:kSSDPMulticastIPAddress + andPort:kSSDPMulticastTCPPort]).andDo((^(NSInvocation *invocation) { + NSString *searchResponse = [NSString stringWithFormat: + @"HTTP/1.1 200 OK\r\n" + @"CACHE-CONTROL: max-age=1800\r\n" + @"Date: Thu, 01 Jan 1970 04:04:04 GMT\r\n" + @"EXT:\r\n" + @"LOCATION: %@\r\n" + @"SERVER: Linux/4.2 UPnP/1.1 MagicDevice/1.0\r\n" + @"ST: %@\r\n" + @"USN: uuid:%@::urn:schemas-upnp-org:device:thing:1\r\n" + @"Content-Length: 0\r\n" + @"\r\n", + kDeviceDescriptionURL, serviceType, kUUID]; + NSData *searchResponseData = [searchResponse dataUsingEncoding:NSUTF8StringEncoding]; + + [self.provider socket:searchSocketMock + didReceiveData:searchResponseData + fromAddress:kServiceAddress]; + })); + + XCTestExpectation *deviceDescriptionResponseExpectation = [self expectationWithDescription:@"Device description response"]; + + [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { + return [kDeviceDescriptionURL isEqualToString:request.URL.absoluteString]; + } withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) { + return [OHHTTPStubsResponse responseWithFileAtPath:OHPathForFileInBundle(@"ssdp_device_description.xml", nil) + statusCode:200 + headers:nil]; + }]; + + __block ServiceDescription *foundService; + OCMStub([delegateMock discoveryProvider:self.provider + didFindService:[OCMArg isNotNil]]).andDo(^(NSInvocation *inv) { + // credit: http://stackoverflow.com/questions/17907987/nsinvocation-getargumentatindex-confusion-while-testing-blocks-with-ocmock#comment26357777_17907987 + __unsafe_unretained ServiceDescription *tmp; + [inv getArgument:&tmp atIndex:3]; + foundService = tmp; + + [deviceDescriptionResponseExpectation fulfill]; + }); + + NSString *byebyeNotification = [NSString stringWithFormat: + @"NOTIFY * HTTP/1.1\r\n" + @"HOST: 239.255.255.250:1900\r\n" + @"NT: %@\r\n" + @"NTS: ssdp:byebye\r\n" + @"USN: uuid:%@::urn:schemas-upnp-org:device:thing:1\r\n" + @"\r\n", + serviceType, kUUID]; + NSData *byebyeNotificationData = [byebyeNotification dataUsingEncoding:NSUTF8StringEncoding]; + + [self.provider startDiscovery]; + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error, @"Find service timeout"); + OCMVerifyAll(delegateMock); + }]; + + XCTestExpectation *didLoseServiceExpectation = [self expectationWithDescription:@"didLoseService: expectation"]; + OCMExpect([delegateMock discoveryProvider:self.provider + didLoseService:[OCMArg checkWithBlock:^BOOL(ServiceDescription *service) { + XCTAssertEqualObjects(service, foundService, @"The lost service is not the found one"); + + [didLoseServiceExpectation fulfill]; + return YES; + }]]); + + // Act + [self.provider socket:searchSocketMock + didReceiveData:byebyeNotificationData + fromAddress:kServiceAddress]; + + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error, @"Lose service timeout"); + OCMVerifyAll(delegateMock); + }]; +} + +@end diff --git a/core/ConnectSDKTests/Discovery/Providers/SSDPDiscoveryProvider_FilteringTests.m b/core/ConnectSDKTests/Discovery/Providers/SSDPDiscoveryProvider_FilteringTests.m new file mode 100644 index 00000000..f545bda4 --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/SSDPDiscoveryProvider_FilteringTests.m @@ -0,0 +1,299 @@ +// +// SSDPDiscoveryProvider_FilteringTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 1/15/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#import "SSDPDiscoveryProvider_Private.h" +#import "SSDPSocketListener.h" + +#import "DIALService.h" +#import "DLNAService.h" +#import "NetcastTVService.h" +#import "RokuService.h" +#import "WebOSTVService.h" + +static const NSUInteger kSSDPMulticastTCPPort = 1900; + + +/// Tests for the @c SSDPDiscoveryProvider 's discovery and filtering features. +@interface SSDPDiscoveryProvider_FilteringTests : XCTestCase + +@end + +@implementation SSDPDiscoveryProvider_FilteringTests + +#pragma mark - DLNA/Netcast Services Filtering Tests + +/// Tests that the @c SSDPDiscoveryProvider properly parses Sonos' XML device +/// description with DLNA filter only and accepts the service. +- (void)testShouldFindDLNAService_Sonos { + [self checkShouldFindDevice:@"sonos" + withExpectedFriendlyName:@"Office - Sonos PLAY:1 Media Renderer" + usingDiscoveryProviders:@[[DLNAService class]]]; +} + +/// Tests that the @c SSDPDiscoveryProvider properly parses Sonos' XML device +/// description (without the root serviceList) with DLNA filter only and accepts +/// the service. +- (void)testShouldFindDLNAService_SonosBased_NoRootServices { + [self checkShouldFindDevice:@"sonos_no_root_services" + withExpectedFriendlyName:@"Office - Sonos PLAY:1 Media Renderer" + usingDiscoveryProviders:@[[DLNAService class]]]; +} + +/// Tests that the @c SSDPDiscoveryProvider properly parses Xbox's XML device +/// description with DLNA filter only and accepts the service. +- (void)testShouldFindDLNAService_Xbox { + [self checkShouldFindDevice:@"xbox" + withExpectedFriendlyName:@"XboxOne" + usingDiscoveryProviders:@[[DLNAService class]]]; +} + +/// Tests that the @c SSDPDiscoveryProvider properly parses Sonos' XML device +/// description with Netcast and DLNA filters (in this order!) and accepts the +/// service. +- (void)testShouldFindDLNAServiceConsideringNetcast_Sonos { + // the Netcast, then DLNA order is crucial here, since the Netcast service + // doesn't have any required services, thus short-circuiting the check for + // all DLNA devices (both services have the same filter) + [self checkShouldFindDevice:@"sonos" + withExpectedFriendlyName:@"Office - Sonos PLAY:1 Media Renderer" + usingDiscoveryProviders:@[[NetcastTVService class], [DLNAService class]]]; +} + +/// Tests that the @c SSDPDiscoveryProvider properly parses Sonos' XML device +/// description (without the root serviceList) with Netcast and DLNA filters (in +/// this order!) and accepts the service. +- (void)testShouldFindDLNAServiceConsideringNetcast_SonosBased_NoRootServices { + [self checkShouldFindDevice:@"sonos_no_root_services" + withExpectedFriendlyName:@"Office - Sonos PLAY:1 Media Renderer" + usingDiscoveryProviders:@[[NetcastTVService class], [DLNAService class]]]; +} + +/// Tests that the @c SSDPDiscoveryProvider properly parses Xbox's XML device +/// description with Netcast and DLNA filters (in this order!) and accepts the +/// service. +- (void)testShouldFindDLNAServiceConsideringNetcast_Xbox { + [self checkShouldFindDevice:@"xbox" + withExpectedFriendlyName:@"XboxOne" + usingDiscoveryProviders:@[[NetcastTVService class], [DLNAService class]]]; +} + +- (void)testShouldNotFindDLNAServiceWithoutRequiredServices { + [self checkShouldNotFindDevice:@"dlna_no_required_services" + usingDiscoveryProviders:@[[DLNAService class]]]; +} + +- (void)testShouldNotFindDLNAServiceConsideringNetcastWithoutRequiredServices { + [self checkShouldNotFindDevice:@"dlna_no_required_services" + usingDiscoveryProviders:@[[NetcastTVService class], [DLNAService class]]]; +} + +- (void)testShouldNotFindDLNAServiceWithoutRequiredServicesInRoot { + [self checkShouldNotFindDevice:@"dlna_root_no_required_services" + usingDiscoveryProviders:@[[DLNAService class]]]; +} + +- (void)testShouldNotFindDLNAServiceConsideringNetcastWithoutRequiredServicesInRoot { + [self checkShouldNotFindDevice:@"dlna_root_no_required_services" + usingDiscoveryProviders:@[[NetcastTVService class], [DLNAService class]]]; +} + +- (void)testShouldFindSamsungTVWithDLNAService { + [self checkShouldFindDevice:@"samsung_tv_dlna" + withExpectedFriendlyName:@"Samsung LED" + usingDiscoveryProviders:@[[DLNAService class]]]; +} + +- (void)testShouldFindWebosWithDLNAService { + [self checkShouldFindDevice:@"webos_minor_dlna" + withExpectedFriendlyName:@"MR" + usingDiscoveryProviders:@[[DLNAService class]]]; +} + +- (void)testShouldFindLGSpeakerWithDLNAService { + [self checkShouldFindDevice:@"lg_speaker" + withExpectedFriendlyName:@"Music Flow H3" + usingDiscoveryProviders:@[[DLNAService class]]]; +} + +#pragma mark - DIAL Service Filtering Tests + +- (void)testShouldFindFireTVWithDIALService { + [self checkShouldFindDevice:@"firetv" + withExpectedFriendlyName:@"Fire TV" + usingDiscoveryProviders:@[[DIALService class]]]; +} + +- (void)testShouldFindChromecastWithDIALService { + [self checkShouldFindDevice:@"chromecast" + withExpectedFriendlyName:@"Chromecast" + usingDiscoveryProviders:@[[DIALService class]]]; +} + +- (void)testShouldFindRokuWithDIALService { + [self checkShouldFindDevice:@"roku2" + withExpectedFriendlyName:@"Roku2" + usingDiscoveryProviders:@[[DIALService class]]]; +} + +- (void)testShouldFindSamsungTVWithDIALService { + [self checkShouldFindDevice:@"samsung_tv" + withExpectedFriendlyName:@"Samsung LED" + usingDiscoveryProviders:@[[DIALService class]]]; +} + +- (void)testShouldFindXboxWithDIALService { + [self checkShouldFindDevice:@"xbox_dial" + withExpectedFriendlyName:@"XboxOne" + usingDiscoveryProviders:@[[DIALService class]]]; +} + +- (void)testShouldFindWebosWithDIALService { + [self checkShouldFindDevice:@"webos_minor" + withExpectedFriendlyName:@"MR" + usingDiscoveryProviders:@[[DIALService class]]]; +} + +#pragma mark - Roku Service Filtering Tests + +- (void)testShouldFindRokuWithRokuService { + [self checkShouldFindDevice:@"roku2" + withExpectedFriendlyName:@"Roku2" + usingDiscoveryProviders:@[[RokuService class]]]; +} + +#pragma mark - WebOS Service Filtering Tests + +- (void)testShouldFindWebOSWithWebOSService { + [self checkShouldFindDevice:@"webos_minor_webos" + withExpectedFriendlyName:@"MR" + usingDiscoveryProviders:@[[WebOSTVService class]]]; +} + +#pragma mark - Helpers + +- (void)checkShouldFindDevice:(NSString *)device + withExpectedFriendlyName:(NSString *)friendlyName + usingDiscoveryProviders:(NSArray *)discoveryProviders { + // Arrange + SSDPDiscoveryProvider *provider = [SSDPDiscoveryProvider new]; + [discoveryProviders enumerateObjectsUsingBlock:^(Class class, NSUInteger idx, BOOL *stop) { + [provider addDeviceFilter:[class discoveryParameters]]; + }]; + + id searchSocketMock = OCMClassMock([SSDPSocketListener class]); + provider.searchSocket = searchSocketMock; + + NSString *serviceType = [discoveryProviders.firstObject discoveryParameters][@"ssdp"][@"filter"]; + OCMStub([searchSocketMock sendData:OCMOCK_NOTNIL + toAddress:OCMOCK_NOTNIL + andPort:kSSDPMulticastTCPPort]).andDo((^(NSInvocation *invocation) { + NSString *searchResponse = [NSString stringWithFormat: + @"HTTP/1.1 200 OK\r\n" + @"CACHE-CONTROL: max-age=1800\r\n" + @"Date: Thu, 01 Jan 1970 04:04:04 GMT\r\n" + @"EXT:\r\n" + @"LOCATION: http://127.1/\r\n" + @"SERVER: Linux/4.2 UPnP/1.1 MagicDevice/1.0\r\n" + @"ST: %@\r\n" + @"USN: uuid:f21e800a-1000-ab08-8e5a-76f4fcb5e772::urn:schemas-upnp-org:device:thing:1\r\n" + @"Content-Length: 0\r\n" + @"\r\n", + // NOTE: be careful with setting the service type from + // the discovery parameters. properly, it should be the + // value from the real device + serviceType]; + NSData *searchResponseData = [searchResponse dataUsingEncoding:NSUTF8StringEncoding]; + + [provider socket:searchSocketMock + didReceiveData:searchResponseData + fromAddress:@"127.2"]; + })); + + [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { + return YES; + } withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) { + NSString *filename = OHPathForFileInBundle(([NSString stringWithFormat:@"ssdp_device_description_%@.xml", device]), nil); + return [OHHTTPStubsResponse responseWithFileAtPath:filename + statusCode:200 + headers:nil]; + }]; + + id discoveryProviderDelegateMock = OCMProtocolMock(@protocol(DiscoveryProviderDelegate)); + provider.delegate = discoveryProviderDelegateMock; + + if (friendlyName) { + XCTestExpectation *didFindServiceExpectation = [self expectationWithDescription:@"Did find device with given service"]; + + OCMExpect([discoveryProviderDelegateMock discoveryProvider:[OCMArg isEqual:provider] + didFindService:[OCMArg checkWithBlock:^BOOL(ServiceDescription *service) { + XCTAssertEqualObjects(service.friendlyName, friendlyName, + @"The device's friendlyName doesn't match"); + [didFindServiceExpectation fulfill]; + return YES; + }]]); + + // Act + [provider startDiscovery]; + + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + OCMVerifyAll(discoveryProviderDelegateMock); + }]; + } else { + // I tried to use [mock reject] and strict mocks at first, but they + // throw an `NSInternalInconsistencyException` that terminates the test + // process. It's impossible to catch, `XCTAssertNoThrow()` and + // `@try/@catch` don't work. So this is a workaround to make sure the + // method isn't called without throwing an exception. + // /* yeah, Xcode and iOS SDK don't seem to be test-friendly :( */ + OCMStub([discoveryProviderDelegateMock discoveryProvider:OCMOCK_ANY + didFindService:OCMOCK_ANY]).andDo(^(NSInvocation *_) { + XCTFail(@"discoveryProvider:didFindService: must not be called"); + }); + + // Act + [provider startDiscovery]; + [self runRunLoopForInterval:kDefaultAsyncTestTimeout]; + + // Assert + OCMVerifyAll(discoveryProviderDelegateMock); + } +} + +- (void)checkShouldNotFindDevice:(NSString *)device + usingDiscoveryProviders:(NSArray *)discoveryProviders { + [self checkShouldFindDevice:device + withExpectedFriendlyName:nil + usingDiscoveryProviders:discoveryProviders]; +} + +- (void)runRunLoopForInterval:(CGFloat)interval { + NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:interval]; + while ([timeoutDate timeIntervalSinceNow] > 0) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:timeoutDate]; + } +} + +@end diff --git a/core/ConnectSDKTests/Discovery/Providers/ZeroConfDiscoveryProviderTests.m b/core/ConnectSDKTests/Discovery/Providers/ZeroConfDiscoveryProviderTests.m new file mode 100644 index 00000000..f8d8088c --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/ZeroConfDiscoveryProviderTests.m @@ -0,0 +1,471 @@ +// +// ZeroConfDiscoveryProviderTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 11/18/14. +// Copyright (c) 2014 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import + + +#import "ZeroConfDiscoveryProvider_Private.h" +#import "ServiceDescription.h" + +static NSString *const kKeyZeroconf = @"zeroconf"; +static NSString *const kKeyFilter = @"filter"; +static NSString *const kKeyServiceID = @"serviceId"; + + +/// Tests for the ZeroConfDiscoveryProvider class. +/// NSNetServiceBrowser and NSNetService classes are mocked to avoid using the +/// real networking and verify the interactions are correct. +@interface ZeroConfDiscoveryProviderTests : XCTestCase + +@property (nonatomic, strong) ZeroConfDiscoveryProvider *provider; + +@end + +@implementation ZeroConfDiscoveryProviderTests + +#pragma mark - Setup + +- (void)setUp { + [super setUp]; + + self.provider = [ZeroConfDiscoveryProvider new]; +} + +- (void)tearDown { + self.provider = nil; + + [super tearDown]; +} + +#pragma mark - Discovery & Delegate tests + +/// Tests that -startDiscovery starts to search for services of the specified +/// type. +- (void)testStartDiscoveryShouldSearchForServices { + // Arrange + id serviceBrowserMock = OCMClassMock([NSNetServiceBrowser class]); + self.provider.netServiceBrowser = serviceBrowserMock; + + NSString *serviceType = @"zerotest"; + NSDictionary *filter = @{kKeyZeroconf: @{kKeyFilter: serviceType}}; + [self.provider addDeviceFilter:filter]; + + // Act + [self.provider startDiscovery]; + + // Assert + OCMVerify([serviceBrowserMock searchForServicesOfType:serviceType + inDomain:@"local."]); +} + +/// Tests that -stopDiscovery stops searching for services. +- (void)testStopDiscoveryShouldStopServiceBrowser { + // Arrange + id serviceBrowserMock = OCMClassMock([NSNetServiceBrowser class]); + self.provider.netServiceBrowser = serviceBrowserMock; + + // Act + [self.provider startDiscovery]; + [self.provider stopDiscovery]; + + // Assert + OCMVerify([serviceBrowserMock stop]); +} + +/// Tests that a found service is asked to resolve the addresses. +- (void)testShouldResolveServiceAfterDiscovering { + // Arrange + id serviceBrowserMock = OCMClassMock([NSNetServiceBrowser class]); + self.provider.netServiceBrowser = serviceBrowserMock; + + NSString *serviceType = @"zerotest"; + NSDictionary *filter = @{kKeyZeroconf: @{kKeyFilter: serviceType}}; + [self.provider addDeviceFilter:filter]; + + id netServiceMock = OCMClassMock([NSNetService class]); + OCMStub([netServiceMock name]).andReturn(@"zeroservice"); + + OCMStub([serviceBrowserMock searchForServicesOfType:serviceType + inDomain:[OCMArg isNotNil]]).andDo(^(NSInvocation *_) { + [self.provider netServiceBrowser:serviceBrowserMock + didFindService:netServiceMock + moreComing:NO]; + }); + + // Act + [self.provider startDiscovery]; + + // Assert + [[[netServiceMock verify] ignoringNonObjectArgs] resolveWithTimeout:0]; +} + +/// Tests that the delegate's -discoveryProvider:didFindService: method is +/// called with the correct service description after resolving a service +/// successfully. +- (void)testShouldCallDelegateDidFindServiceAfterResolvingService { + // Arrange + id serviceBrowserMock = OCMClassMock([NSNetServiceBrowser class]); + self.provider.netServiceBrowser = serviceBrowserMock; + + id delegateMock = OCMProtocolMock(@protocol(DiscoveryProviderDelegate)); + self.provider.delegate = delegateMock; + + NSString *serviceType = @"zerotest"; + NSDictionary *filter = @{kKeyZeroconf: @{kKeyFilter: serviceType}, + kKeyServiceID: @"ZeroService"}; + [self.provider addDeviceFilter:filter]; + + id netServiceMock = OCMClassMock([NSNetService class]); + OCMStub([netServiceMock name]).andReturn(@"zeroservice"); + OCMStub([(NSNetService *)netServiceMock type]).andReturn(serviceType); + + OCMStub([serviceBrowserMock searchForServicesOfType:serviceType + inDomain:[OCMArg isNotNil]]).andDo(^(NSInvocation *_) { + [self.provider netServiceBrowser:serviceBrowserMock + didFindService:netServiceMock + moreComing:NO]; + }); + + NSString *kServiceAddress = @"10.8.8.8"; + static const NSUInteger kServicePort = 8889; + + struct sockaddr_in socket; + bzero(&socket, sizeof(socket)); + socket.sin_family = AF_INET; + socket.sin_port = htons(kServicePort); + XCTAssertEqual(inet_pton(socket.sin_family, [kServiceAddress UTF8String], &socket.sin_addr), 1, @"Failed to prepare mocked IP address"); + NSData *socketData = [NSData dataWithBytes:&socket length:sizeof(socket)]; + NSArray *addresses = @[socketData]; + OCMStub([netServiceMock addresses]).andReturn(addresses); + + [[[[netServiceMock stub] ignoringNonObjectArgs] andDo:^(NSInvocation *_) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.provider netServiceDidResolveAddress:netServiceMock]; + }); + }] resolveWithTimeout:0]; + + XCTestExpectation *didFindServiceExpectation = [self expectationWithDescription:@"didFindService: is called"]; + OCMExpect([delegateMock discoveryProvider:self.provider + didFindService:[OCMArg checkWithBlock:^BOOL(ServiceDescription *service) { + XCTAssertEqualObjects(service.address, kServiceAddress, @"The service's address is incorrect"); + XCTAssertEqual(service.port, kServicePort, @"The port is incorrect"); + XCTAssertEqualObjects(service.serviceId, filter[kKeyServiceID], @"The service ID is incorrect"); + XCTAssertEqualObjects(service.UUID, [netServiceMock name], @"The UUID is incorrect"); + XCTAssertEqualObjects(service.friendlyName, [netServiceMock name], @"The friendly name is incorrect"); + XCTAssertNil(service.manufacturer, @"The manufacturer should be nil"); + XCTAssertNil(service.modelName, @"The model name should be nil"); + XCTAssertNil(service.modelDescription, @"The model description should be nil"); + XCTAssertNil(service.modelNumber, @"The model number should be nil"); + XCTAssertEqualObjects(service.commandURL.absoluteString, ([NSString stringWithFormat:@"http://%@:%lu/", kServiceAddress, (unsigned long)kServicePort]), @"The command URL is incorrect"); + XCTAssertNil(service.locationXML, @"The XML content should be nil"); +// XCTAssertEqualObjects(service.type, [(NSNetService *)netServiceMock type], @"The service type is incorrect"); +// XCTAssertEqualObjects(service.version, @"1", @"The version is incorrect"); + XCTAssertNil(service.serviceList, @"The service list should be nil"); + + [didFindServiceExpectation fulfill]; + return YES; + }]]); + + // Act + [self.provider startDiscovery]; + + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error, @"didFindService: isn't called"); + OCMVerifyAll(delegateMock); + }]; +} + +/// Tests that the delegate's -discoveryProvider:didLoseService: method is +/// called with the correct service description (mathcing the found one) after +/// removing a previously found service. +- (void)testShouldCallDelegateDidLoseServiceAfterRemovingService { + // Arrange + id serviceBrowserMock = OCMClassMock([NSNetServiceBrowser class]); + self.provider.netServiceBrowser = serviceBrowserMock; + + id delegateMock = OCMProtocolMock(@protocol(DiscoveryProviderDelegate)); + self.provider.delegate = delegateMock; + + NSString *serviceType = @"zerotest"; + NSDictionary *filter = @{kKeyZeroconf: @{kKeyFilter: serviceType}, + kKeyServiceID: @"ZeroService"}; + [self.provider addDeviceFilter:filter]; + + id netServiceMock = OCMClassMock([NSNetService class]); + OCMStub([netServiceMock name]).andReturn(@"zeroservice"); + OCMStub([(NSNetService *)netServiceMock type]).andReturn(serviceType); + + OCMStub([serviceBrowserMock searchForServicesOfType:serviceType + inDomain:[OCMArg isNotNil]]).andDo(^(NSInvocation *_) { + [self.provider netServiceBrowser:serviceBrowserMock + didFindService:netServiceMock + moreComing:NO]; + }); + + NSString *kServiceAddress = @"10.8.8.8"; + static const NSUInteger kServicePort = 8889; + + struct sockaddr_in socket; + bzero(&socket, sizeof(socket)); + socket.sin_family = AF_INET; + socket.sin_port = htons(kServicePort); + XCTAssertEqual(inet_pton(socket.sin_family, [kServiceAddress UTF8String], &socket.sin_addr), 1, @"Failed to prepare mocked IP address"); + NSData *socketData = [NSData dataWithBytes:&socket length:sizeof(socket)]; + NSArray *addresses = @[socketData]; + OCMStub([netServiceMock addresses]).andReturn(addresses); + + [[[[netServiceMock stub] ignoringNonObjectArgs] andDo:^(NSInvocation *_) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.provider netServiceDidResolveAddress:netServiceMock]; + }); + }] resolveWithTimeout:0]; + + XCTestExpectation *didFindServiceExpectation = [self expectationWithDescription:@"didFindService: is called"]; + + __block ServiceDescription *foundService; + OCMStub([delegateMock discoveryProvider:self.provider + didFindService:[OCMArg isNotNil]]).andDo(^(NSInvocation *inv) { + __unsafe_unretained ServiceDescription *tmp; + [inv getArgument:&tmp atIndex:3]; + foundService = tmp; + + [didFindServiceExpectation fulfill]; + }); + + [self.provider startDiscovery]; + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error, @"didFindService: isn't called"); + OCMVerifyAll(delegateMock); + }]; + + XCTestExpectation *didLoseServiceExpectation = [self expectationWithDescription:@"didLoseService: is called"]; + OCMExpect([delegateMock discoveryProvider:self.provider + didLoseService:[OCMArg checkWithBlock:^BOOL(ServiceDescription *service) { + XCTAssertEqualObjects(service, foundService, @"The lost service is not the found one"); + + [didLoseServiceExpectation fulfill]; + return YES; + }]]); + + // Act + [self.provider netServiceBrowser:serviceBrowserMock + didRemoveService:netServiceMock + moreComing:NO]; + + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error, @"Lose service timeout"); + OCMVerifyAll(delegateMock); + }]; +} + +/// Tests that the delegate's -discoveryProvider:didFindService: method is not +/// called if a service is resolved with no addresses. +- (void)testShouldNotCallDelegateDidFindServiceAfterFindingServiceWithNoAddresses { + // Arrange + id serviceBrowserMock = OCMClassMock([NSNetServiceBrowser class]); + self.provider.netServiceBrowser = serviceBrowserMock; + + id delegateMock = OCMProtocolMock(@protocol(DiscoveryProviderDelegate)); + self.provider.delegate = delegateMock; + + NSString *serviceType = @"zerotest"; + NSDictionary *filter = @{kKeyZeroconf: @{kKeyFilter: serviceType}, + kKeyServiceID: @"ZeroService"}; + [self.provider addDeviceFilter:filter]; + + id netServiceMock = OCMClassMock([NSNetService class]); + OCMStub([netServiceMock name]).andReturn(@"zeroservice"); + OCMStub([(NSNetService *)netServiceMock type]).andReturn(serviceType); + + OCMStub([serviceBrowserMock searchForServicesOfType:serviceType + inDomain:[OCMArg isNotNil]]).andDo(^(NSInvocation *_) { + [self.provider netServiceBrowser:serviceBrowserMock + didFindService:netServiceMock + moreComing:NO]; + }); + + NSArray *addresses = @[]; + OCMStub([netServiceMock addresses]).andReturn(addresses); + + [[[[netServiceMock stub] ignoringNonObjectArgs] andDo:^(NSInvocation *_) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.provider netServiceDidResolveAddress:netServiceMock]; + }); + }] resolveWithTimeout:0]; + + OCMStub([delegateMock discoveryProvider:self.provider + didFindService:[OCMArg isNotNil]]).andDo(^(NSInvocation *inv) { + XCTFail(@"didFindService: should not be called"); + }); + + // Act + [self.provider startDiscovery]; + + // Assert + // XCTestExpectation doesn't work in this case, because it fails the test on + // timeout, whereas we need to catch that event + NSDate *const timeoutDate = [NSDate dateWithTimeIntervalSinceNow:kDefaultAsyncTestTimeout]; + while ([timeoutDate timeIntervalSinceNow] > 0) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:timeoutDate]; + } +} + +/// Tests that the delegate's -discoveryProvider:didLoseService: method is not +/// called when removing a found service with no resolved addresses. +- (void)testShouldNotCallDelegateDidLoseServiceAfterRemovingServiceWithNoAddresses { + // Arrange + id serviceBrowserMock = OCMClassMock([NSNetServiceBrowser class]); + self.provider.netServiceBrowser = serviceBrowserMock; + + id delegateMock = OCMProtocolMock(@protocol(DiscoveryProviderDelegate)); + self.provider.delegate = delegateMock; + + NSString *serviceType = @"zerotest"; + NSDictionary *filter = @{kKeyZeroconf: @{kKeyFilter: serviceType}, + kKeyServiceID: @"ZeroService"}; + [self.provider addDeviceFilter:filter]; + + id netServiceMock = OCMClassMock([NSNetService class]); + OCMStub([netServiceMock name]).andReturn(@"zeroservice"); + OCMStub([(NSNetService *)netServiceMock type]).andReturn(serviceType); + + OCMStub([serviceBrowserMock searchForServicesOfType:serviceType + inDomain:[OCMArg isNotNil]]).andDo(^(NSInvocation *_) { + [self.provider netServiceBrowser:serviceBrowserMock + didFindService:netServiceMock + moreComing:NO]; + }); + + NSArray *addresses = @[]; + OCMStub([netServiceMock addresses]).andReturn(addresses); + + [[[[netServiceMock stub] ignoringNonObjectArgs] andDo:^(NSInvocation *_) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.provider netServiceDidResolveAddress:netServiceMock]; + }); + }] resolveWithTimeout:0]; + + OCMStub([delegateMock discoveryProvider:self.provider + didFindService:OCMOCK_ANY]).andDo(^(NSInvocation *_) { + XCTFail(@"didFindService: should not be called"); + }); + + [self.provider startDiscovery]; + + // XCTestExpectation doesn't work in this case, because it fails the test on + // timeout, whereas we need to catch that event + NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:kDefaultAsyncTestTimeout]; + while ([timeoutDate timeIntervalSinceNow] > 0) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:timeoutDate]; + } + + OCMStub([delegateMock discoveryProvider:self.provider + didLoseService:OCMOCK_ANY]).andDo(^(NSInvocation *_) { + XCTFail(@"didLoseService: should not be called"); + }); + + // Act + [self.provider netServiceBrowser:serviceBrowserMock + didRemoveService:netServiceMock + moreComing:NO]; + + // Assert + timeoutDate = [NSDate dateWithTimeIntervalSinceNow:kDefaultAsyncTestTimeout]; + while ([timeoutDate timeIntervalSinceNow] > 0) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:timeoutDate]; + } +} + +#pragma mark - IPv6 (lack of) support tests + +/// Tests that resolved services with IPv6 addresses only should be ignored. +- (void)testShouldIgnoreResolvedIPv6Address { + // Arrange + uint8_t ip6Bytes[] = {0x1c, 0x1e, 0x1b, 0x58, 0x00, 0x00, 0x00, 0x00, 0xfe, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x48, 0xe3, 0xce, 0xd1, + 0x70, 0x68, 0x61, 0x04, 0x00, 0x00, 0x00}; + NSData *ip6Data = [NSData dataWithBytes:ip6Bytes length:sizeof(ip6Bytes)]; + + NSArray *addresses = @[ip6Data]; + + id netServiceMock = OCMClassMock([NSNetService class]); + [OCMStub([netServiceMock name]) andReturn:@"zeroservice"]; + [OCMStub([netServiceMock addresses]) andReturn:addresses]; + + id delegateMock = OCMProtocolMock(@protocol(DiscoveryProviderDelegate)); + [OCMStub([delegateMock discoveryProvider:self.provider + didFindService:OCMOCK_ANY]) andDo:^(NSInvocation *_) { + XCTFail(@"Should not be called for IPv6 address only"); + }]; + self.provider.delegate = delegateMock; + + // Act + [self.provider netServiceDidResolveAddress:netServiceMock]; + + // Assert + OCMVerifyAll(delegateMock); +} + +/// Tests that the IPv4 address is picked between resolved IPv6 and IPv4 +/// addresses, and provided in the @c ServiceDescription. +- (void)testShouldCallDelegateWithIPv4AndNotIPv6Address { + // Arrange + uint8_t ip4Bytes[] = {0x10, 0x02, 0x1b, 0x58, 0xc0, 0xa8, 0x01, 0x84, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + NSData *ip4Data = [NSData dataWithBytes:ip4Bytes length:sizeof(ip4Bytes)]; + + uint8_t ip6Bytes[] = {0x1c, 0x1e, 0x1b, 0x58, 0x00, 0x00, 0x00, 0x00, 0xfe, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x48, 0xe3, 0xce, 0xd1, + 0x70, 0x68, 0x61, 0x04, 0x00, 0x00, 0x00}; + NSData *ip6Data = [NSData dataWithBytes:ip6Bytes length:sizeof(ip6Bytes)]; + + NSArray *addresses = @[ip4Data, ip6Data]; + + id netServiceMock = OCMClassMock([NSNetService class]); + [OCMStub([netServiceMock name]) andReturn:@"zeroservice"]; + [OCMStub([netServiceMock addresses]) andReturn:addresses]; + + id delegateMock = OCMProtocolMock(@protocol(DiscoveryProviderDelegate)); + OCMExpect([delegateMock discoveryProvider:self.provider + didFindService:[OCMArg checkWithBlock:^BOOL(ServiceDescription *description) { + XCTAssertEqualObjects(description.commandURL.absoluteString, + @"http://192.168.1.132:7000/", + @"CommandURL should use IPv4 address"); + return YES; + }]]); + self.provider.delegate = delegateMock; + + // Act + [self.provider netServiceDidResolveAddress:netServiceMock]; + + // Assert + OCMVerifyAll(delegateMock); +} + +@end diff --git a/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_chromecast.xml b/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_chromecast.xml new file mode 100644 index 00000000..7859bdd9 --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_chromecast.xml @@ -0,0 +1,48 @@ + + + + + 1 + 0 + + http://192.168.1.1:8008 + + urn:dial-multiscreen-org:device:dial:1 + Chromecast + Google Inc. + Eureka Dongle + uuid:d37b3685-632d-5c18-b7a6-cb01d2d8ecf0 + + + image/png + 98 + 55 + 32 + /setup/icon.png + + + + + urn:dial-multiscreen-org:service:dial:1 + urn:dial-multiscreen-org:serviceId:dial + /ssdp/notfound + /ssdp/notfound + /ssdp/notfound + + + + diff --git a/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_firetv.xml b/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_firetv.xml new file mode 100644 index 00000000..aa791589 --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_firetv.xml @@ -0,0 +1,16 @@ +10urn:dial-multiscreen-org:device:dial:1Fire TVAmazon.com, Inc.FireTVuuid:198f731a-a6a2-da41-0000-00001df7c1e2urn:dial-multiscreen-org:service:dial:1urn:dial-multiscreen-org:serviceId:dial/upnp/dev/198f731a-a6a2-da41-0000-00001df7c1e2/svc/dial-multiscreen-org/dial/desc/upnp/dev/198f731a-a6a2-da41-0000-00001df7c1e2/svc/dial-multiscreen-org/dial/action/upnp/dev/198f731a-a6a2-da41-0000-00001df7c1e2/svc/dial-multiscreen-org/dial/event + \ No newline at end of file diff --git a/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_roku2.xml b/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_roku2.xml new file mode 100644 index 00000000..7e4a1eaa --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_roku2.xml @@ -0,0 +1,50 @@ + + + + +1 +0 + + +urn:roku-com:device:player:1-0 +Roku2 +Roku +http://www.roku.com/ +Roku Streaming Player Network Media +Roku 2 +2720X +http://www.roku.com/ +4RB3CL080248 +uuid:018950d3-7401-1011-8077-dc3a5e453531 + + +urn:roku-com:service:ecp:1 +urn:roku-com:serviceId:ecp1-0 + + +ecp_SCPD.xml + + +urn:dial-multiscreen-org:service:dial:1 +urn:dial-multiscreen-org:serviceId:dial1-0 + + +dial_SCPD.xml + + + + diff --git a/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_samsung_tv.xml b/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_samsung_tv.xml new file mode 100644 index 00000000..6522ef03 --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_samsung_tv.xml @@ -0,0 +1,45 @@ + + + + + 1 + 0 + + + urn:dial-multiscreen-org:device:dialreceiver:1 + Samsung LED + Samsung Electronics + http://www.samsung.com/sec + Samsung TV NS + UN46F8000 + 1.0 + http://www.samsung.com/sec + 20098138ACB + uuid:0b532b80-00be-1000-9e19-f47b5e7672cf + CNCACBZIDAHUU + Resolution:1920X1080,Y2013 + + + urn:dial-multiscreen-org:service:dial:1 + urn:dial-multiscreen-org:serviceId:dial + /smp_31_ + /smp_32_ + /smp_30_ + + + + diff --git a/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_webos_minor.xml b/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_webos_minor.xml new file mode 100644 index 00000000..def5a449 --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_webos_minor.xml @@ -0,0 +1,33 @@ + + + + + + urn:dial-multiscreen-org:device:dial:1 + MR + ee157bca-a2c7-4f33-bff8-5a177bd17181 + + + urn:dial-multiscreen-org:service:dial:1 + urn:dial-multiscreen-org:serviceId:dial + /ssdp/notfound + /ssdp/notfound + /ssdp/notfound + + + + diff --git a/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_xbox_dial.xml b/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_xbox_dial.xml new file mode 100644 index 00000000..572859ad --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/sample_data/dial/ssdp_device_description_xbox_dial.xml @@ -0,0 +1,17 @@ + + +10urn:dial-multiscreen-org:device:dial:1XboxOneMicrosoft CorporationXbox Oneuuid:aa3da5dd-935e-4148-969b-8d5997a67207{0F4810A6-4FB4-4FDF-8ACC-81E751E7EC8A}urn:dial-multiscreen-org:service:dial:1urn:dial-multiscreen-org:serviceId:dial/upnphost/udhisapi.dll?control=uuid:aa3da5dd-935e-4148-969b-8d5997a67207+urn:dial-multiscreen-org:serviceId:dial/upnphost/udhisapi.dll?content=uuid:72133162-b195-4fac-869b-ca5a12b29839 diff --git a/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_dlna_no_required_services.xml b/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_dlna_no_required_services.xml new file mode 100644 index 00000000..974a3e21 --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_dlna_no_required_services.xml @@ -0,0 +1,162 @@ + + + + + 1 + 0 + + + urn:schemas-upnp-org:device:ZonePlayer:1 + 192.168.1.99 - Sonos PLAY:1 + Sonos, Inc. + http://www.sonos.com + S1 + Sonos PLAY:1 + Sonos PLAY:1 + http://www.sonos.com/products/zoneplayers/S1 + 27.2-80271 + 1.8.3.7-1 + 09-9E-44-90-F9-1C:E + uuid:RINCON_B8E93443F9A609059 + + + 0 + image/png + 48 + 48 + 24 + /img/icon-S1.png + + + 26.0-00000 + 24.0-0000 + 5.1 + OTP: + Office + PLAY:1 + 9 + 0x00000000 + 0x00403332 + 0x0001000e + 5 + 75.000 + 6.000 + 128 + 64 + 10 + + + urn:schemas-upnp-org:service:AlarmClock:1 + urn:upnp-org:serviceId:AlarmClock + /AlarmClock/Control + /AlarmClock/Event + /xml/AlarmClock1.xml + + + urn:schemas-upnp-org:service:MusicServices:1 + urn:upnp-org:serviceId:MusicServices + /MusicServices/Control + /MusicServices/Event + /xml/MusicServices1.xml + + + urn:schemas-upnp-org:service:DeviceProperties:1 + urn:upnp-org:serviceId:DeviceProperties + /DeviceProperties/Control + /DeviceProperties/Event + /xml/DeviceProperties1.xml + + + urn:schemas-upnp-org:service:SystemProperties:1 + urn:upnp-org:serviceId:SystemProperties + /SystemProperties/Control + /SystemProperties/Event + /xml/SystemProperties1.xml + + + urn:schemas-upnp-org:service:ZoneGroupTopology:1 + urn:upnp-org:serviceId:ZoneGroupTopology + /ZoneGroupTopology/Control + /ZoneGroupTopology/Event + /xml/ZoneGroupTopology1.xml + + + urn:schemas-upnp-org:service:GroupManagement:1 + urn:upnp-org:serviceId:GroupManagement + /GroupManagement/Control + /GroupManagement/Event + /xml/GroupManagement1.xml + + + urn:schemas-tencent-com:service:QPlay:1 + urn:tencent-com:serviceId:QPlay + /QPlay/Control + /QPlay/Event + /xml/QPlay1.xml + + + + + urn:schemas-upnp-org:device:MediaServer:1 + 192.168.1.99 - Sonos PLAY:1 Media Server + Sonos, Inc. + http://www.sonos.com + S1 + Sonos PLAY:1 Media Server + Sonos PLAY:1 + http://www.sonos.com/products/zoneplayers/S1 + uuid:RINCON_B8E93443F9A609059_MS + + + urn:schemas-upnp-org:service:ContentDirectory:1 + urn:upnp-org:serviceId:ContentDirectory + /MediaServer/ContentDirectory/Control + /MediaServer/ContentDirectory/Event + /xml/ContentDirectory1.xml + + + urn:schemas-upnp-org:service:ConnectionManager:1 + urn:upnp-org:serviceId:ConnectionManager + /MediaServer/ConnectionManager/Control + /MediaServer/ConnectionManager/Event + /xml/ConnectionManager1.xml + + + + + urn:schemas-upnp-org:device:MediaRenderer:1 + Office - Sonos PLAY:1 Media Renderer + Sonos, Inc. + http://www.sonos.com + S1 + Sonos PLAY:1 Media Renderer + Sonos PLAY:1 + http://www.sonos.com/products/zoneplayers/S1 + uuid:RINCON_B8E93443F9A609059_MR + + + image/png + 48 + 48 + 24 + /img/icon-S1.png + + + + + + diff --git a/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_dlna_root_no_required_services.xml b/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_dlna_root_no_required_services.xml new file mode 100644 index 00000000..cda0adcb --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_dlna_root_no_required_services.xml @@ -0,0 +1,27 @@ + + + + + 1 + 0 + + + urn:schemas-upnp-org:device:MediaRenderer:1XboxOneXbox OneDigital Media RendererMicrosoft Corporationhttp://www.microsoft.comhttp://xbox.com + uuid:f887c686-27c7-4fa7-b8d4-43d7d74da84f + + + diff --git a/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_lg_speaker.xml b/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_lg_speaker.xml new file mode 100644 index 00000000..92b704ef --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_lg_speaker.xml @@ -0,0 +1,70 @@ + + + + + 1 + 0 + + + DMR-1.50 + urn:schemas-upnp-org:device:MediaRenderer:1 + Music Flow H3 + LG Electronics + http://www.lg.com + LG HTS + LG HTS + 1.0 + http://www.lg.com + uuid:140479c0-58f3-1cef-84bf-000ce7060000 + + + urn:schemas-upnp-org:service:RenderingControl:1 + urn:upnp-org:serviceId:RenderingControl + /dmr_rcs.xml + control/RenderingControl + event/RenderingControl + + + urn:schemas-upnp-org:service:ConnectionManager:1 + urn:upnp-org:serviceId:ConnectionManager + /dmr_cms.xml + control/ConnectionManager + event/ConnectionManager + + + urn:schemas-upnp-org:service:AVTransport:1 + urn:upnp-org:serviceId:AVTransport + /dmr_avts.xml + control/AVTransport + event/AVTransport + + + + + urn:lge-com:device:MultiroomSpk:1 + LG HTS + LG Electronics + http://www.lg.com + LG HTS + LG HTS + 1.0 + http://www.lg.com + uuid:00000000-0000-0000-0000-000000000000 + + + + diff --git a/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_samsung_tv_dlna.xml b/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_samsung_tv_dlna.xml new file mode 100644 index 00000000..7549cce1 --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_samsung_tv_dlna.xml @@ -0,0 +1,93 @@ + + + + + 1 + 0 + + + urn:schemas-upnp-org:device:MediaRenderer:1 + MS_DigitalMediaDeviceClass_DMR_V001 + Display.TV.LCD Multimedia.DMR + DMR-1.50 + Samsung LED + Samsung Electronics + http://www.samsung.com/sec + Samsung RVU TV 2013 + UN46F8000 + AllShare1.0 + http://www.samsung.com/sec + 20138413DML + uuid:0a21fe80-00aa-1000-8e5a-f47b5e7672cf + KPEHCBZXDAQYU + + + image/jpeg + 48 + 48 + 24 + /dmr/icon_SML.jpg + + + image/jpeg + 120 + 120 + 24 + /dmr/icon_LRG.jpg + + + image/png + 48 + 48 + 24 + /dmr/icon_SML.png + + + image/png + 120 + 120 + 24 + /dmr/icon_LRG.png + + + + + urn:schemas-upnp-org:service:RenderingControl:1 + urn:upnp-org:serviceId:RenderingControl + /smp_21_ + /smp_22_ + /smp_20_ + + + urn:schemas-upnp-org:service:ConnectionManager:1 + urn:upnp-org:serviceId:ConnectionManager + /smp_24_ + /smp_25_ + /smp_23_ + + + urn:schemas-upnp-org:service:AVTransport:1 + urn:upnp-org:serviceId:AVTransport + /smp_27_ + /smp_28_ + /smp_26_ + + + Y2013,WebURIPlayable,SeekTRACK_NR,NavigateInPause + VEN_0105&DEV_VD0001 + + diff --git a/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_sonos.xml b/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_sonos.xml new file mode 100644 index 00000000..3a4c7151 --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_sonos.xml @@ -0,0 +1,199 @@ + + + + + 1 + 0 + + + urn:schemas-upnp-org:device:ZonePlayer:1 + 192.168.1.99 - Sonos PLAY:1 + Sonos, Inc. + http://www.sonos.com + S1 + Sonos PLAY:1 + Sonos PLAY:1 + http://www.sonos.com/products/zoneplayers/S1 + 27.2-80271 + 1.8.3.7-1 + 09-9E-44-90-F9-1C:E + uuid:RINCON_B8E93443F9A609059 + + + 0 + image/png + 48 + 48 + 24 + /img/icon-S1.png + + + 26.0-00000 + 24.0-0000 + 5.1 + OTP: + Office + PLAY:1 + 9 + 0x00000000 + 0x00403332 + 0x0001000e + 5 + 75.000 + 6.000 + 128 + 64 + 10 + + + urn:schemas-upnp-org:service:AlarmClock:1 + urn:upnp-org:serviceId:AlarmClock + /AlarmClock/Control + /AlarmClock/Event + /xml/AlarmClock1.xml + + + urn:schemas-upnp-org:service:MusicServices:1 + urn:upnp-org:serviceId:MusicServices + /MusicServices/Control + /MusicServices/Event + /xml/MusicServices1.xml + + + urn:schemas-upnp-org:service:DeviceProperties:1 + urn:upnp-org:serviceId:DeviceProperties + /DeviceProperties/Control + /DeviceProperties/Event + /xml/DeviceProperties1.xml + + + urn:schemas-upnp-org:service:SystemProperties:1 + urn:upnp-org:serviceId:SystemProperties + /SystemProperties/Control + /SystemProperties/Event + /xml/SystemProperties1.xml + + + urn:schemas-upnp-org:service:ZoneGroupTopology:1 + urn:upnp-org:serviceId:ZoneGroupTopology + /ZoneGroupTopology/Control + /ZoneGroupTopology/Event + /xml/ZoneGroupTopology1.xml + + + urn:schemas-upnp-org:service:GroupManagement:1 + urn:upnp-org:serviceId:GroupManagement + /GroupManagement/Control + /GroupManagement/Event + /xml/GroupManagement1.xml + + + urn:schemas-tencent-com:service:QPlay:1 + urn:tencent-com:serviceId:QPlay + /QPlay/Control + /QPlay/Event + /xml/QPlay1.xml + + + + + urn:schemas-upnp-org:device:MediaServer:1 + 192.168.1.99 - Sonos PLAY:1 Media Server + Sonos, Inc. + http://www.sonos.com + S1 + Sonos PLAY:1 Media Server + Sonos PLAY:1 + http://www.sonos.com/products/zoneplayers/S1 + uuid:RINCON_B8E93443F9A609059_MS + + + urn:schemas-upnp-org:service:ContentDirectory:1 + urn:upnp-org:serviceId:ContentDirectory + /MediaServer/ContentDirectory/Control + /MediaServer/ContentDirectory/Event + /xml/ContentDirectory1.xml + + + urn:schemas-upnp-org:service:ConnectionManager:1 + urn:upnp-org:serviceId:ConnectionManager + /MediaServer/ConnectionManager/Control + /MediaServer/ConnectionManager/Event + /xml/ConnectionManager1.xml + + + + + urn:schemas-upnp-org:device:MediaRenderer:1 + Office - Sonos PLAY:1 Media Renderer + Sonos, Inc. + http://www.sonos.com + S1 + Sonos PLAY:1 Media Renderer + Sonos PLAY:1 + http://www.sonos.com/products/zoneplayers/S1 + uuid:RINCON_B8E93443F9A609059_MR + + + urn:schemas-upnp-org:service:RenderingControl:1 + urn:upnp-org:serviceId:RenderingControl + /MediaRenderer/RenderingControl/Control + /MediaRenderer/RenderingControl/Event + /xml/RenderingControl1.xml + + + urn:schemas-upnp-org:service:ConnectionManager:1 + urn:upnp-org:serviceId:ConnectionManager + /MediaRenderer/ConnectionManager/Control + /MediaRenderer/ConnectionManager/Event + /xml/ConnectionManager1.xml + + + urn:schemas-upnp-org:service:AVTransport:1 + urn:upnp-org:serviceId:AVTransport + /MediaRenderer/AVTransport/Control + /MediaRenderer/AVTransport/Event + /xml/AVTransport1.xml + + + urn:schemas-sonos-com:service:Queue:1 + urn:sonos-com:serviceId:Queue + /MediaRenderer/Queue/Control + /MediaRenderer/Queue/Event + /xml/Queue1.xml + + + urn:schemas-upnp-org:service:GroupRenderingControl:1 + urn:upnp-org:serviceId:GroupRenderingControl + /MediaRenderer/GroupRenderingControl/Control + /MediaRenderer/GroupRenderingControl/Event + /xml/GroupRenderingControl1.xml + + + + + image/png + 48 + 48 + 24 + /img/icon-S1.png + + + + + + diff --git a/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_sonos_no_root_services.xml b/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_sonos_no_root_services.xml new file mode 100644 index 00000000..a99f1b79 --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_sonos_no_root_services.xml @@ -0,0 +1,149 @@ + + + + + 1 + 0 + + + urn:schemas-upnp-org:device:ZonePlayer:1 + 192.168.1.99 - Sonos PLAY:1 + Sonos, Inc. + http://www.sonos.com + S1 + Sonos PLAY:1 + Sonos PLAY:1 + http://www.sonos.com/products/zoneplayers/S1 + 27.2-80271 + 1.8.3.7-1 + 09-9E-44-90-F9-1C:E + uuid:RINCON_B8E93443F9A609059 + + + 0 + image/png + 48 + 48 + 24 + /img/icon-S1.png + + + 26.0-00000 + 24.0-0000 + 5.1 + OTP: + Office + PLAY:1 + 9 + 0x00000000 + 0x00403332 + 0x0001000e + 5 + 75.000 + 6.000 + 128 + 64 + 10 + + + + urn:schemas-upnp-org:device:MediaServer:1 + 192.168.1.99 - Sonos PLAY:1 Media Server + Sonos, Inc. + http://www.sonos.com + S1 + Sonos PLAY:1 Media Server + Sonos PLAY:1 + http://www.sonos.com/products/zoneplayers/S1 + uuid:RINCON_B8E93443F9A609059_MS + + + urn:schemas-upnp-org:service:ContentDirectory:1 + urn:upnp-org:serviceId:ContentDirectory + /MediaServer/ContentDirectory/Control + /MediaServer/ContentDirectory/Event + /xml/ContentDirectory1.xml + + + urn:schemas-upnp-org:service:ConnectionManager:1 + urn:upnp-org:serviceId:ConnectionManager + /MediaServer/ConnectionManager/Control + /MediaServer/ConnectionManager/Event + /xml/ConnectionManager1.xml + + + + + urn:schemas-upnp-org:device:MediaRenderer:1 + Office - Sonos PLAY:1 Media Renderer + Sonos, Inc. + http://www.sonos.com + S1 + Sonos PLAY:1 Media Renderer + Sonos PLAY:1 + http://www.sonos.com/products/zoneplayers/S1 + uuid:RINCON_B8E93443F9A609059_MR + + + urn:schemas-upnp-org:service:RenderingControl:1 + urn:upnp-org:serviceId:RenderingControl + /MediaRenderer/RenderingControl/Control + /MediaRenderer/RenderingControl/Event + /xml/RenderingControl1.xml + + + urn:schemas-upnp-org:service:ConnectionManager:1 + urn:upnp-org:serviceId:ConnectionManager + /MediaRenderer/ConnectionManager/Control + /MediaRenderer/ConnectionManager/Event + /xml/ConnectionManager1.xml + + + urn:schemas-upnp-org:service:AVTransport:1 + urn:upnp-org:serviceId:AVTransport + /MediaRenderer/AVTransport/Control + /MediaRenderer/AVTransport/Event + /xml/AVTransport1.xml + + + urn:schemas-sonos-com:service:Queue:1 + urn:sonos-com:serviceId:Queue + /MediaRenderer/Queue/Control + /MediaRenderer/Queue/Event + /xml/Queue1.xml + + + urn:schemas-upnp-org:service:GroupRenderingControl:1 + urn:upnp-org:serviceId:GroupRenderingControl + /MediaRenderer/GroupRenderingControl/Control + /MediaRenderer/GroupRenderingControl/Event + /xml/GroupRenderingControl1.xml + + + + + image/png + 48 + 48 + 24 + /img/icon-S1.png + + + + + + diff --git a/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_webos_minor_dlna.xml b/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_webos_minor_dlna.xml new file mode 100644 index 00000000..06619e0e --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_webos_minor_dlna.xml @@ -0,0 +1,72 @@ + + + + + 1 + 0 + + + urn:schemas-upnp-org:device:MediaRenderer:1 + MR + LG Electronics. + http://www.lge.com + LG WebOSTV DMRplus + LG TV + + 1.0 + + uuid:5bb03745-4532-2c55-f48f-99742ab93698 + MS_DigitalMediaDeviceClass_DMR_V001 + MediaDevices + Multimedia.DMR + LG Digital Media Renderer TV + 1.0 + DMR-1.50 + + + image/png + 191 + 191 + 32 + /dmrIcon.png + + + + + urn:schemas-upnp-org:service:AVTransport:1 + urn:upnp-org:serviceId:AVTransport + /AVTransport/5bb03745-4532-2c55-f48f-99742ab93698/scpd.xml + /AVTransport/5bb03745-4532-2c55-f48f-99742ab93698/control.xml + /AVTransport/5bb03745-4532-2c55-f48f-99742ab93698/event.xml + + + urn:schemas-upnp-org:service:ConnectionManager:1 + urn:upnp-org:serviceId:ConnectionManager + /ConnectionManager/5bb03745-4532-2c55-f48f-99742ab93698/scpd.xml + /ConnectionManager/5bb03745-4532-2c55-f48f-99742ab93698/control.xml + /ConnectionManager/5bb03745-4532-2c55-f48f-99742ab93698/event.xml + + + urn:schemas-upnp-org:service:RenderingControl:1 + urn:upnp-org:serviceId:RenderingControl + /RenderingControl/5bb03745-4532-2c55-f48f-99742ab93698/scpd.xml + /RenderingControl/5bb03745-4532-2c55-f48f-99742ab93698/control.xml + /RenderingControl/5bb03745-4532-2c55-f48f-99742ab93698/event.xml + + + + diff --git a/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_xbox.xml b/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_xbox.xml new file mode 100644 index 00000000..44d8cf87 --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/sample_data/dlna/ssdp_device_description_xbox.xml @@ -0,0 +1,51 @@ + + + + + 1 + 0 + + + urn:schemas-upnp-org:device:MediaRenderer:1XboxOneXbox OneDigital Media RendererMicrosoft Corporationhttp://www.microsoft.comhttp://xbox.com + uuid:f887c686-27c7-4fa7-b8d4-43d7d74da84f + {F887C686-27C7-4FA7-B8D4-43D7D74DA84F}DMR-1.50MediaDevicesVEN_0125&DEV_0002&REV_0001 VEN_0125&DEV_0002image/jpeg12012024/upnphost/udhisapi.dll?content=uuid:8df04193-da4e-4764-a20d-155c9f7f1795image/jpeg484824/upnphost/udhisapi.dll?content=uuid:fec54d6c-8653-49ae-8e84-7863b4a6a436image/png12012024/upnphost/udhisapi.dll?content=uuid:f32bca00-fe56-4c9b-97c0-7107e498a763image/png484824/upnphost/udhisapi.dll?content=uuid:cdad2c8e-023f-4a3b-82ea-573048f043e8 + + + urn:schemas-upnp-org:service:RenderingControl:1 + urn:upnp-org:serviceId:RenderingControl + /upnphost/udhisapi.dll?control=uuid:f887c686-27c7-4fa7-b8d4-43d7d74da84f+urn:upnp-org:serviceId:RenderingControl + /upnphost/udhisapi.dll?event=uuid:f887c686-27c7-4fa7-b8d4-43d7d74da84f+urn:upnp-org:serviceId:RenderingControl + /upnphost/udhisapi.dll?content=uuid:c027b4c1-527e-4f85-985a-b9948b931c2c + + + urn:schemas-upnp-org:service:AVTransport:1 + urn:upnp-org:serviceId:AVTransport + /upnphost/udhisapi.dll?control=uuid:f887c686-27c7-4fa7-b8d4-43d7d74da84f+urn:upnp-org:serviceId:AVTransport + /upnphost/udhisapi.dll?event=uuid:f887c686-27c7-4fa7-b8d4-43d7d74da84f+urn:upnp-org:serviceId:AVTransport + /upnphost/udhisapi.dll?content=uuid:48e34611-1834-484a-8333-779ee1bc26e3 + + + urn:schemas-upnp-org:service:ConnectionManager:1 + urn:upnp-org:serviceId:ConnectionManager + /upnphost/udhisapi.dll?control=uuid:f887c686-27c7-4fa7-b8d4-43d7d74da84f+urn:upnp-org:serviceId:ConnectionManager + /upnphost/udhisapi.dll?event=uuid:f887c686-27c7-4fa7-b8d4-43d7d74da84f+urn:upnp-org:serviceId:ConnectionManager + /upnphost/udhisapi.dll?content=uuid:9c3570a8-913a-4807-9537-62b282e178f1 + + + + + diff --git a/core/ConnectSDKTests/Discovery/Providers/sample_data/ssdp_device_description.xml b/core/ConnectSDKTests/Discovery/Providers/sample_data/ssdp_device_description.xml new file mode 100644 index 00000000..5beb8f7b --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/sample_data/ssdp_device_description.xml @@ -0,0 +1,43 @@ + + + + + 1 + 1 + + + urn:schemas-upnp-org:device:thing:1 + short user-friendly title + manufacturer name + URL to manufacturer site + long user-friendly title + model name + model number + URL to model site + manufacturer's serial number + uuid:f21e800a-1000-ab08-8e5a-76f4fcb5e772 + + + urn:schemas-upnp-org:service:serviceType:v + urn:upnp-org:serviceId:serviceID + http://127.0.1.2:9999/scpd + http://127.0.1.2:9999/cnc + http://127.0.1.2:9999/event + + + + diff --git a/core/ConnectSDKTests/Discovery/Providers/sample_data/webos/ssdp_device_description_webos_minor_webos.xml b/core/ConnectSDKTests/Discovery/Providers/sample_data/webos/ssdp_device_description_webos_minor_webos.xml new file mode 100644 index 00000000..73577269 --- /dev/null +++ b/core/ConnectSDKTests/Discovery/Providers/sample_data/webos/ssdp_device_description_webos_minor_webos.xml @@ -0,0 +1,43 @@ + + + + + 1 + 0 + + + urn:schemas-upnp-org:device:Basic:1 + MR + LG Electronics + http://www.lge.com + + LG Smart TV + http://www.lge.com + 42LB6300-UQ + + uuid:44186fd2-e0cd-eaf0-efef-efc9ff8c8a4d + + + urn:lge-com:service:webos-second-screen:1 + urn:lge-com:serviceId:webos-second-screen-3000-3001 + /WebOS_SecondScreen/44186fd2-e0cd-eaf0-efef-efc9ff8c8a4d/scpd.xml + /WebOS_SecondScreen/44186fd2-e0cd-eaf0-efef-efc9ff8c8a4d/control.xml + /WebOS_SecondScreen/44186fd2-e0cd-eaf0-efef-efc9ff8c8a4d/event.xml + + + + diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/Headers b/core/ConnectSDKTests/Frameworks/OCMock.framework/Headers new file mode 120000 index 00000000..a177d2a6 --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OCMock.framework/Headers @@ -0,0 +1 @@ +Versions/Current/Headers \ No newline at end of file diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/License.txt b/core/ConnectSDKTests/Frameworks/OCMock.framework/License.txt new file mode 100644 index 00000000..f433b1a5 --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OCMock.framework/License.txt @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/OCMock b/core/ConnectSDKTests/Frameworks/OCMock.framework/OCMock new file mode 120000 index 00000000..c388ea8d --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OCMock.framework/OCMock @@ -0,0 +1 @@ +Versions/Current/OCMock \ No newline at end of file diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/Resources b/core/ConnectSDKTests/Frameworks/OCMock.framework/Resources new file mode 120000 index 00000000..953ee36f --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OCMock.framework/Resources @@ -0,0 +1 @@ +Versions/Current/Resources \ No newline at end of file diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/NSNotificationCenter+OCMAdditions.h b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/NSNotificationCenter+OCMAdditions.h new file mode 100644 index 00000000..c20a9c2b --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/NSNotificationCenter+OCMAdditions.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2009-2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@class OCObserverMockObject; + + +@interface NSNotificationCenter(OCMAdditions) + +- (void)addMockObserver:(OCObserverMockObject *)notificationObserver name:(NSString *)notificationName object:(id)notificationSender; + +@end diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMArg.h b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMArg.h new file mode 100644 index 00000000..d53437cb --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMArg.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface OCMArg : NSObject + +// constraining arguments + ++ (id)any; ++ (SEL)anySelector; ++ (void *)anyPointer; ++ (id __autoreleasing *)anyObjectRef; ++ (id)isNil; ++ (id)isNotNil; ++ (id)isEqual:(id)value; ++ (id)isNotEqual:(id)value; ++ (id)isKindOfClass:(Class)cls; ++ (id)checkWithSelector:(SEL)selector onObject:(id)anObject; ++ (id)checkWithBlock:(BOOL (^)(id obj))block; + +// manipulating arguments + ++ (id *)setTo:(id)value; ++ (void *)setToValue:(NSValue *)value; + +// internal use only + ++ (id)resolveSpecialValues:(NSValue *)value; + +@end + +#define OCMOCK_ANY [OCMArg any] + +#if defined(__GNUC__) && !defined(__STRICT_ANSI__) + #define OCMOCK_VALUE(variable) \ + ({ __typeof__(variable) __v = (variable); [NSValue value:&__v withObjCType:@encode(__typeof__(__v))]; }) +#else + #define OCMOCK_VALUE(variable) [NSValue value:&variable withObjCType:@encode(__typeof__(variable))] +#endif diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMConstraint.h b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMConstraint.h new file mode 100644 index 00000000..777966ab --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMConstraint.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2007-2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + + +@interface OCMConstraint : NSObject + ++ (instancetype)constraint; +- (BOOL)evaluate:(id)value; + +// if you are looking for any, isNil, etc, they have moved to OCMArg + +// try to use [OCMArg checkWith...] instead of the constraintWith... methods below + ++ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject; ++ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject withValue:(id)aValue; + + +@end + +@interface OCMAnyConstraint : OCMConstraint +@end + +@interface OCMIsNilConstraint : OCMConstraint +@end + +@interface OCMIsNotNilConstraint : OCMConstraint +@end + +@interface OCMIsNotEqualConstraint : OCMConstraint +{ + @public + id testValue; +} + +@end + +@interface OCMInvocationConstraint : OCMConstraint +{ + @public + NSInvocation *invocation; +} + +@end + +@interface OCMBlockConstraint : OCMConstraint +{ + BOOL (^block)(id); +} + +- (instancetype)initWithConstraintBlock:(BOOL (^)(id))block; + +@end + + +#define CONSTRAINT(aSelector) [OCMConstraint constraintWithSelector:aSelector onObject:self] +#define CONSTRAINTV(aSelector, aValue) [OCMConstraint constraintWithSelector:aSelector onObject:self withValue:(aValue)] diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMLocation.h b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMLocation.h new file mode 100644 index 00000000..e510db7a --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMLocation.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@interface OCMLocation : NSObject +{ + id testCase; + NSString *file; + NSUInteger line; +} + ++ (instancetype)locationWithTestCase:(id)aTestCase file:(NSString *)aFile line:(NSUInteger)aLine; + +- (instancetype)initWithTestCase:(id)aTestCase file:(NSString *)aFile line:(NSUInteger)aLine; + +- (id)testCase; +- (NSString *)file; +- (NSUInteger)line; + +@end + +extern OCMLocation *OCMMakeLocation(id testCase, const char *file, int line); diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMMacroState.h b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMMacroState.h new file mode 100644 index 00000000..4b2d6350 --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMMacroState.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@class OCMLocation; +@class OCMRecorder; +@class OCMStubRecorder; +@class OCMockObject; + + +@interface OCMMacroState : NSObject +{ + OCMRecorder *recorder; +} + ++ (void)beginStubMacro; ++ (OCMStubRecorder *)endStubMacro; + ++ (void)beginExpectMacro; ++ (OCMStubRecorder *)endExpectMacro; + ++ (void)beginVerifyMacroAtLocation:(OCMLocation *)aLocation; ++ (void)endVerifyMacro; + ++ (OCMMacroState *)globalState; + +- (OCMRecorder *)recorder; + +- (void)switchToClassMethod; + +@end diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMRecorder.h b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMRecorder.h new file mode 100644 index 00000000..f56d2ca4 --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMRecorder.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@class OCMockObject; +@class OCMInvocationMatcher; + + +@interface OCMRecorder : NSProxy +{ + OCMockObject *mockObject; + OCMInvocationMatcher *invocationMatcher; +} + +- (instancetype)init; +- (instancetype)initWithMockObject:(OCMockObject *)aMockObject; + +- (void)setMockObject:(OCMockObject *)aMockObject; + +- (OCMInvocationMatcher *)invocationMatcher; + +- (id)classMethod; +- (id)ignoringNonObjectArgs; + +@end diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMStubRecorder.h b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMStubRecorder.h new file mode 100644 index 00000000..73b401f8 --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMStubRecorder.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2004-2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMRecorder.h" + + +@interface OCMStubRecorder : OCMRecorder + +- (id)andReturn:(id)anObject; +- (id)andReturnValue:(NSValue *)aValue; +- (id)andThrow:(NSException *)anException; +- (id)andPost:(NSNotification *)aNotification; +- (id)andCall:(SEL)selector onObject:(id)anObject; +- (id)andDo:(void (^)(NSInvocation *invocation))block; +- (id)andForwardToRealObject; + +@end + + +@interface OCMStubRecorder (Properties) + +#define andReturn(aValue) _andReturn(({ typeof(aValue) _v = (aValue); [NSValue value:&_v withObjCType:@encode(typeof(_v))]; })) +@property (nonatomic, readonly) OCMStubRecorder *(^ _andReturn)(NSValue *); + +#define andThrow(anException) _andThrow(anException) +@property (nonatomic, readonly) OCMStubRecorder *(^ _andThrow)(NSException *); + +#define andPost(aNotification) _andPost(aNotification) +@property (nonatomic, readonly) OCMStubRecorder *(^ _andPost)(NSNotification *); + +#define andCall(anObject, aSelector) _andCall(anObject, aSelector) +@property (nonatomic, readonly) OCMStubRecorder *(^ _andCall)(id, SEL); + +#define andDo(aBlock) _andDo(aBlock) +@property (nonatomic, readonly) OCMStubRecorder *(^ _andDo)(void (^)(NSInvocation *)); + +#define andForwardToRealObject() _andForwardToRealObject() +@property (nonatomic, readonly) OCMStubRecorder *(^ _andForwardToRealObject)(void); + +@end + + + diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMock.h b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMock.h new file mode 100644 index 00000000..f0083b35 --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMock.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2004-2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import +#import +#import +#import +#import +#import +#import + + +#define OCMClassMock(cls) [OCMockObject niceMockForClass:cls] + +#define OCMStrictClassMock(cls) [OCMockObject mockForClass:cls] + +#define OCMProtocolMock(protocol) [OCMockObject niceMockForProtocol:protocol] + +#define OCMStrictProtocolMock(protocol) [OCMockObject mockForProtocol:protocol] + +#define OCMPartialMock(obj) [OCMockObject partialMockForObject:obj] + +#define OCMObserverMock() [OCMockObject observerMock] + + +#define OCMStub(invocation) \ +({ \ + _OCMSilenceWarnings( \ + [OCMMacroState beginStubMacro]; \ + invocation; \ + [OCMMacroState endStubMacro]; \ + ); \ +}) + +#define OCMExpect(invocation) \ +({ \ + _OCMSilenceWarnings( \ + [OCMMacroState beginExpectMacro]; \ + invocation; \ + [OCMMacroState endExpectMacro]; \ + ); \ +}) + +#define ClassMethod(invocation) \ + _OCMSilenceWarnings( \ + [[OCMMacroState globalState] switchToClassMethod]; \ + invocation; \ + ); + + +#define OCMVerifyAll(mock) [mock verifyAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)] + +#define OCMVerifyAllWithDelay(mock, delay) [mock verifyWithDelay:delay atLocation:OCMMakeLocation(self, __FILE__, __LINE__)] + +#define OCMVerify(invocation) \ +({ \ + _OCMSilenceWarnings( \ + [OCMMacroState beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)]; \ + invocation; \ + [OCMMacroState endVerifyMacro]; \ + ); \ +}) + +#define _OCMSilenceWarnings(macro) \ +({ \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wunused-value\"") \ + macro \ + _Pragma("clang diagnostic pop") \ +}) diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMockObject.h b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMockObject.h new file mode 100644 index 00000000..63f2bae2 --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Headers/OCMockObject.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2004-2014 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import + +@class OCMLocation; +@class OCMInvocationStub; +@class OCMStubRecorder; +@class OCMInvocationMatcher; +@class OCMInvocationExpectation; + + +@interface OCMockObject : NSProxy +{ + BOOL isNice; + BOOL expectationOrderMatters; + NSMutableArray *stubs; + NSMutableArray *expectations; + NSMutableArray *exceptions; + NSMutableArray *invocations; +} + ++ (id)mockForClass:(Class)aClass; ++ (id)mockForProtocol:(Protocol *)aProtocol; ++ (id)partialMockForObject:(NSObject *)anObject; + ++ (id)niceMockForClass:(Class)aClass; ++ (id)niceMockForProtocol:(Protocol *)aProtocol; + ++ (id)observerMock; + +- (instancetype)init; + +- (void)setExpectationOrderMatters:(BOOL)flag; + +- (id)stub; +- (id)expect; +- (id)reject; + +- (id)verify; +- (id)verifyAtLocation:(OCMLocation *)location; + +- (void)verifyWithDelay:(NSTimeInterval)delay; +- (void)verifyWithDelay:(NSTimeInterval)delay atLocation:(OCMLocation *)location; + +- (void)stopMocking; + +// internal use only + +- (void)addStub:(OCMInvocationStub *)aStub; +- (void)addExpectation:(OCMInvocationExpectation *)anExpectation; + +- (BOOL)handleInvocation:(NSInvocation *)anInvocation; +- (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation; +- (BOOL)handleSelector:(SEL)sel; + +- (void)verifyInvocation:(OCMInvocationMatcher *)matcher; +- (void)verifyInvocation:(OCMInvocationMatcher *)matcher atLocation:(OCMLocation *)location; + +@end + diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/OCMock b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/OCMock new file mode 100644 index 00000000..03157f66 Binary files /dev/null and b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/OCMock differ diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Resources/Info.plist b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Resources/Info.plist new file mode 100644 index 00000000..0a42c899 --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Resources/Info.plist @@ -0,0 +1,42 @@ + + + + + BuildMachineOSBuild + 13E28 + CFBundleDevelopmentRegion + English + CFBundleExecutable + OCMock + CFBundleIdentifier + com.mulle-kybernetik.OCMock + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + OCMock + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 5B1008 + DTPlatformVersion + GM + DTSDKBuild + 13C64 + DTSDKName + macosx10.9 + DTXcode + 0511 + DTXcodeBuild + 5B1008 + NSHumanReadableCopyright + Copyright © 2004-2013 Mulle Kybernetik. + + diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Resources/en.lproj/InfoPlist.strings b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Resources/en.lproj/InfoPlist.strings new file mode 100644 index 00000000..5e45963c Binary files /dev/null and b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/A/Resources/en.lproj/InfoPlist.strings differ diff --git a/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/Current b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/Current new file mode 120000 index 00000000..8c7e5a66 --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OCMock.framework/Versions/Current @@ -0,0 +1 @@ +A \ No newline at end of file diff --git a/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Headers b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Headers new file mode 120000 index 00000000..a177d2a6 --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Headers @@ -0,0 +1 @@ +Versions/Current/Headers \ No newline at end of file diff --git a/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/LICENSE b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/LICENSE new file mode 100644 index 00000000..a83928dd --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/LICENSE @@ -0,0 +1,9 @@ +- MIT LICENSE - + +Copyright (c) 2012 Olivier Halligon + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/OHHTTPStubs b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/OHHTTPStubs new file mode 120000 index 00000000..abdfdf94 --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/OHHTTPStubs @@ -0,0 +1 @@ +Versions/Current/OHHTTPStubs \ No newline at end of file diff --git a/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Resources b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Resources new file mode 120000 index 00000000..953ee36f --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Resources @@ -0,0 +1 @@ +Versions/Current/Resources \ No newline at end of file diff --git a/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/Headers/OHHTTPStubs.h b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/Headers/OHHTTPStubs.h new file mode 100644 index 00000000..acdcf30a --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/Headers/OHHTTPStubs.h @@ -0,0 +1,120 @@ +/*********************************************************************************** + * + * Copyright (c) 2012 Olivier Halligon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + ***********************************************************************************/ + + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Imports + +#import +#import "OHHTTPStubsResponse.h" + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Types + +typedef BOOL(^OHHTTPStubsTestBlock)(NSURLRequest* request); +typedef OHHTTPStubsResponse*(^OHHTTPStubsResponseBlock)(NSURLRequest* request); + +@protocol OHHTTPStubsDescriptor +/*! Arbitrary name that you can set and get to describe your stub. Use it as your own convenience. */ +@property(nonatomic, strong) NSString* name; +@end + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Interface + +/*! Stubs Manager. Use this class to add and remove stubs and stub your network requests. */ +@interface OHHTTPStubs : NSObject + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Adding & Removing stubs + +/*! Dedicated method to add a stub + @param testBlock Block that should return `YES` if the request passed as parameter should be stubbed with the response block, + and `NO` if it should hit the real world (or be managed by another stub). + @param responseBlock Block that will return the `OHHTTPStubsResponse` (response to use for stubbing) corresponding to the given request + @return a stub descriptor that uniquely identifies the stub and can be later used to remove it with `removeStub:`. + @note The returned stub descriptor is retained (`__strong` reference) by `OHHTTPStubs` until it is removed + (with one of the `removeStub:`/`removeLastStub`/`removeAllStubs` methods); it is thus recommended to + keep it in a `__weak` storage (and not `__strong`) in your app code, to let the stub descriptor be destroyed + and let the variable go back to `nil` automatically when the stub is removed. + */ ++(id)stubRequestsPassingTest:(OHHTTPStubsTestBlock)testBlock + withStubResponse:(OHHTTPStubsResponseBlock)responseBlock; + +/*! Remove a stub from the list of stubs + @param stubDesc the stub descriptor that has been returned when adding the stub using `stubRequestsPassingTest:withStubResponse:` + @return `YES` if the stub has been successfully removed, `NO` if the parameter was not a valid stub identifier + */ ++(BOOL)removeStub:(id)stubDesc; + +/*! Remove the last added stub from the stubs list */ ++(void)removeLastStub; + +/*! Remove all the stubs from the stubs list. */ ++(void)removeAllStubs; + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Disabling & Re-Enabling stubs + +/*! Enable or disable the stubs + @param enabled if `YES`, enables the stubs. If `NO`, disable all the stubs and let all the requests hit the real world. + @note OHHTTPStubs are enabled by default, so there is no need to call this method with `YES` for stubs to work, + except if you explicitely disabled the stubs before. + @note This only affects requests that are further made using `NSURLConnection` or using `[NSURLSession sharedSession]`. + This does not affect requests sent on an `NSURLSession` created using an `NSURLSessionConfiguration`. + */ ++(void)setEnabled:(BOOL)enabled; + +#if defined(__IPHONE_7_0) || defined(__MAC_10_9) +/*! + @param enabled If `YES`, enables the stubs for this `NSURLSessionConfiguration`. + If `NO`, disable the stubs and let all the requests hit the real world + @param sessionConfig The NSURLSessionConfiguration on which to enabled/disable the stubs + @note OHHTTPStubs are enabled by default on newly created `defaultSessionConfiguration` and `ephemeralSessionConfiguration`, + so there is no need to call this method with `YES` for stubs to work. You generally only use this if you want to + disable `OHTTPStubs` per `NSURLSession` by calling it before building the `NSURLSession` with the `NSURLSessionConfiguration`. + @note Important: As usual according to the way `NSURLSessionConfiguration` works, you must set this property + *before* creating the `NSURLSession`. Once the `NSURLSession` object is created, they use a deep copy of + the `NSURLSessionConfiguration` object used to create them, so changing the configuration later does not + affect already created sessions. + */ ++ (void)setEnabled:(BOOL)enabled forSessionConfiguration:(NSURLSessionConfiguration *)sessionConfig; +#endif + +#pragma mark - Debug Methods + +/*! List all the installed stubs + @return An array of `id` objects currently installed. Useful for debug. + */ ++(NSArray*)allStubs; + +/*! Setup a block to be called each time a stub is triggered. + + Useful if you want to log all your requests being stubbed for example and see which stub was used to respond to each request. + @param block The block to call each time a request is being stubbed by OHHTTPStubs. Set it to `nil` to do nothing. Defaults is `nil`. + */ ++(void)onStubActivation:( void(^)(NSURLRequest* request, id stub) )block; + +@end + diff --git a/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/Headers/OHHTTPStubsResponse+HTTPMessage.h b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/Headers/OHHTTPStubsResponse+HTTPMessage.h new file mode 100644 index 00000000..40c01a45 --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/Headers/OHHTTPStubsResponse+HTTPMessage.h @@ -0,0 +1,35 @@ +// +// OHHTTPStubsResponse+HTTPMessage.h +// OHHTTPStubs +// +// Created by Olivier Halligon on 01/09/13. +// Copyright (c) 2013 AliSoftware. All rights reserved. +// + +#import "OHHTTPStubsResponse.h" + +@interface OHHTTPStubsResponse (HTTPMessage) + +/*! @name Building a response from HTTP Message data */ + +// TODO: Try to implement it using NSInputStream someday? + +/*! Builds a response given a message data as returned by `curl -is [url]`, that is containing both the headers and the body. + This method will split the headers and the body and build a OHHTTPStubsReponse accordingly + @param responseData The NSData containing the whole HTTP response, including the headers and the body + @return An `OHHTTPStubsResponse` describing the corresponding response to return by the stub + */ ++(instancetype)responseWithHTTPMessageData:(NSData*)responseData; + +/*! Builds a response given the name of a `"*.response"` file containing both the headers and the body. + The response file is expected to be in the specified bundle (or the application bundle if nil). + This method will split the headers and the body and build a OHHTTPStubsReponse accordingly + @param responseName The name of the `"*.response"` file (without extension) containing the whole HTTP response (including the headers and the body) + @param bundleOrNil The bundle in which the `"*.response"` file is located. If `nil`, the `[NSBundle bundleForClass:self.class]` will be used. + @return An `OHHTTPStubsResponse` describing the corresponding response to return by the stub + */ ++(instancetype)responseNamed:(NSString*)responseName + inBundle:(NSBundle*)bundleOrNil; + + +@end diff --git a/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/Headers/OHHTTPStubsResponse+JSON.h b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/Headers/OHHTTPStubsResponse+JSON.h new file mode 100644 index 00000000..1934c102 --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/Headers/OHHTTPStubsResponse+JSON.h @@ -0,0 +1,27 @@ +// +// OHHTTPStubsResponse+JSON.h +// OHHTTPStubs +// +// Created by Olivier Halligon on 01/09/13. +// Copyright (c) 2013 AliSoftware. All rights reserved. +// + +#import "OHHTTPStubsResponse.h" + +@interface OHHTTPStubsResponse (JSON) + +/*! Builds a response given a JSON object for the response body, status code, and headers. + @param jsonObject Object representing the response body. + Typically a `NSDictionary`; may be any object accepted by `+[NSJSONSerialization dataWithJSONObject:options:error:]` + @param statusCode The HTTP Status Code to use in the response + @param httpHeaders The HTTP Headers to return in the response + If a "Content-Type" header is not included, "Content-Type: application/json" will be added. + @return An `OHHTTPStubsResponse` describing the corresponding response to return by the stub + + @note This method typically calls responseWithData:statusCode:headers: passing the serialized JSON object as the data parameter and adding the Content-Type header if necessary. + */ ++ (instancetype)responseWithJSONObject:(id)jsonObject + statusCode:(int)statusCode + headers:(NSDictionary *)httpHeaders; + +@end diff --git a/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/Headers/OHHTTPStubsResponse.h b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/Headers/OHHTTPStubsResponse.h new file mode 100644 index 00000000..95c85f88 --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/Headers/OHHTTPStubsResponse.h @@ -0,0 +1,219 @@ +/*********************************************************************************** + * + * Copyright (c) 2012 Olivier Halligon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + ***********************************************************************************/ + + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Imports + +#import + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Defines & Constants + +// Standard download speeds. +extern const double +OHHTTPStubsDownloadSpeedGPRS, +OHHTTPStubsDownloadSpeedEDGE, +OHHTTPStubsDownloadSpeed3G, +OHHTTPStubsDownloadSpeed3GPlus, +OHHTTPStubsDownloadSpeedWifi; + + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Interface + +/*! Stubs Response. This describes a stubbed response to be returned by the URL Loading System, including its + HTTP headers, body, statusCode and response time. */ +@interface OHHTTPStubsResponse : NSObject + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Properties + +@property(nonatomic, strong) NSDictionary* httpHeaders; +@property(nonatomic, assign) int statusCode; +@property(nonatomic, strong) NSInputStream* inputStream; +@property(nonatomic, assign) unsigned long long dataSize; +@property(nonatomic, assign) NSTimeInterval requestTime; //!< Defaults to 0.0 +//! @note if responseTime<0, it is interpreted as a download speed in KBps ( -200 => 200KB/s ) +@property(nonatomic, assign) NSTimeInterval responseTime; +@property(nonatomic, strong) NSError* error; + + + + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Commodity Constructors +/*! @name Commodity */ + +/* -------------------------------------------------------------------------- */ +#pragma mark > Building response from NSData + +/*! Builds a response given raw data. + @note Internally calls `-initWithInputStream:dataSize:statusCode:headers:` with and inputStream build from the NSData. + + @param data The raw data to return in the response + @param statusCode The HTTP Status Code to use in the response + @param httpHeaders The HTTP Headers to return in the response + @return An `OHHTTPStubsResponse` describing the corresponding response to return by the stub + */ ++(instancetype)responseWithData:(NSData*)data + statusCode:(int)statusCode + headers:(NSDictionary*)httpHeaders; + + +/* -------------------------------------------------------------------------- */ +#pragma mark > Building response from a file + +/*! Useful macro to build a path given a file name and a bundle. + @param fileName The name of the file to get the path to, including file extension + @param bundleOrNil The bundle in which the file is located. If nil, the application bundle (`[NSBundle bundleForClass:self.class]`) is used + @return The path of the given file in the given bundle + */ +#define OHPathForFileInBundle(fileName,bundleOrNil) ({ \ + [(bundleOrNil?:[NSBundle bundleForClass:self.class]) pathForResource:[fileName stringByDeletingPathExtension] ofType:[fileName pathExtension]]; \ +}) + +/*! Useful macro to build a path to a file in the Documents's directory in the app sandbox, used by iTunes File Sharing for example. + @param fileName The name of the file to get the path to, including file extension + @return The path of the file in the Documents directory in your App Sandbox + */ +#define OHPathForFileInDocumentsDir(fileName) ({ \ + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); \ + NSString *basePath = (paths.count > 0) ? [paths objectAtIndex:0] : nil; \ + [basePath stringByAppendingPathComponent:fileName]; \ +}) + +/*! Useful macro to build an NSBundle located in the application's resources simply from its name + @param bundleBasename The base name, without extension (extension is assumed to be ".bundle"). + @return The NSBundle object representing the bundle with the given basename located in your application's resources. + */ +#define OHResourceBundle(bundleBasename) ({ \ + [NSBundle bundleWithPath:[[NSBundle bundleForClass:self.class] pathForResource:bundleBasename ofType:@"bundle"]]; \ +}) + + +/*! Builds a response given a file path, the status code and headers. + @param filePath The file path that contains the response body to return. + @param statusCode The HTTP Status Code to use in the response + @param httpHeaders The HTTP Headers to return in the response + @return An `OHHTTPStubsResponse` describing the corresponding response to return by the stub + @note It is encouraged to use the `OHPathForFileInBundle(fileName, bundleOrNil)` and `OHResourceBundle(bundleBasename)` macros + to easily build a path to a file located in the app bundle or any arbitrary bundle. + Likewise, you may use the `OHPathForFileInDocumentsDir(fileName)` macro to build a path to a file located in + the Documents directory of your application' sandbox. + */ ++(instancetype)responseWithFileAtPath:(NSString *)filePath + statusCode:(int)statusCode + headers:(NSDictionary*)httpHeaders; + +/* -------------------------------------------------------------------------- */ +#pragma mark > Building an error response + +/*! Builds a response that corresponds to the given error + @param error The error to use in the stubbed response. + @return An `OHHTTPStubsResponse` describing the corresponding response to return by the stub + @note For example you could use an error like `[NSError errorWithDomain:NSURLErrorDomain code:kCFURLErrorNotConnectedToInternet userInfo:nil]` + */ ++(instancetype)responseWithError:(NSError*)error; + + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Commotidy Setters + +/*! Set the `responseTime` of the `OHHTTPStubsResponse` and return `self`. Useful for chaining method calls. + + _Usage example:_ +
return [[OHHTTPStubsReponse responseWithData:data statusCode:200 headers:nil] responseTime:5.0];
+ + @param responseTime If positive, the amount of time used to send the entire response. + If negative, the rate in KB/s at which to send the response data. + Useful to simulate slow networks for example. You may use the OHHTTPStubsDownloadSpeed* constants here. + @return `self` (= the same `OHHTTPStubsResponse` that was the target of this method). Useful for chaining method calls. + */ +-(instancetype)responseTime:(NSTimeInterval)responseTime; + +/*! Set both the `requestTime` and the `responseTime` of the `OHHTTPStubsResponse` at once. Useful for chaining method calls. + + _Usage example:_ +
return [[OHHTTPStubsReponse responseWithData:data statusCode:200 headers:nil]
+               requestTime:1.0 responseTime:5.0];
+ + @param requestTime The time to wait before the response begins to send. This value must be greater than or equal to zero. + @param responseTime If positive, the amount of time used to send the entire response. + If negative, the rate in KB/s at which to send the response data. + Useful to simulate slow networks for example. You may use the OHHTTPStubsDownloadSpeed* constants here. + @return `self` (= the same `OHHTTPStubsResponse` that was the target of this method). Useful for chaining method calls. + */ +-(instancetype)requestTime:(NSTimeInterval)requestTime responseTime:(NSTimeInterval)responseTime; + + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Initializers +/*! @name Initializers */ + +/*! Designed initializer. Initialize a response with the given input stream, dataSize, statusCode and headers. + @param inputStream The input stream that will provide the data to return in the response + @param dataSize The size of the data in the stream. + @param statusCode The HTTP Status Code to use in the response + @param httpHeaders The HTTP Headers to return in the response + @return An `OHHTTPStubsResponse` describing the corresponding response to return by the stub + @note You will probably never need to call this method yourself. Prefer the other initializers (that will call this method eventually) + */ +-(instancetype)initWithInputStream:(NSInputStream*)inputStream + dataSize:(unsigned long long)dataSize + statusCode:(int)statusCode + headers:(NSDictionary*)httpHeaders; + + +/*! Initialize a response with a given file path, statusCode and headers. + @param filePath The file path of the data to return in the response + @param statusCode The HTTP Status Code to use in the response + @param httpHeaders The HTTP Headers to return in the response + @return An `OHHTTPStubsResponse` describing the corresponding response to return by the stub + @note This method simply builds the NSInputStream, compute the file size, and then call `-initWithInputStream:dataSize:statusCode:headers:` + */ +-(instancetype)initWithFileAtPath:(NSString*)filePath + statusCode:(int)statusCode + headers:(NSDictionary*)httpHeaders; + + +/*! Initialize a response with the given data, statusCode and headers. + @param data The raw data to return in the response + @param statusCode The HTTP Status Code to use in the response + @param httpHeaders The HTTP Headers to return in the response + @return An `OHHTTPStubsResponse` describing the corresponding response to return by the stub + */ +-(instancetype)initWithData:(NSData*)data + statusCode:(int)statusCode + headers:(NSDictionary*)httpHeaders; + + +/*! Designed initializer. Initialize a response with the given error. + @param error The error to use in the stubbed response. + @return An `OHHTTPStubsResponse` describing the corresponding response to return by the stub + @note For example you could use an error like `[NSError errorWithDomain:NSURLErrorDomain code:kCFURLErrorNotConnectedToInternet userInfo:nil]` + */ +-(instancetype)initWithError:(NSError*)error; + +@end diff --git a/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/OHHTTPStubs b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/OHHTTPStubs new file mode 100644 index 00000000..bb44ea25 Binary files /dev/null and b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/OHHTTPStubs differ diff --git a/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/Resources/Info.plist b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/Resources/Info.plist new file mode 100644 index 00000000..048125a6 --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/A/Resources/Info.plist @@ -0,0 +1,42 @@ + + + + + BuildMachineOSBuild + 14A389 + CFBundleDevelopmentRegion + English + CFBundleExecutable + OHHTTPStubs + CFBundleIdentifier + com.alisoftware.OHHTTPStubs + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + OHHTTPStubs + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 6A1052d + DTPlatformVersion + GM + DTSDKBuild + 14A382 + DTSDKName + macosx10.10 + DTXcode + 0610 + DTXcodeBuild + 6A1052d + NSHumanReadableCopyright + Copyright © 2014 AliSoftware. All rights reserved. + + diff --git a/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/Current b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/Current new file mode 120000 index 00000000..8c7e5a66 --- /dev/null +++ b/core/ConnectSDKTests/Frameworks/OHHTTPStubs.framework/Versions/Current @@ -0,0 +1 @@ +A \ No newline at end of file diff --git a/core/ConnectSDKTests/Helpers/AppStateChangeNotifierTests.m b/core/ConnectSDKTests/Helpers/AppStateChangeNotifierTests.m new file mode 100644 index 00000000..3291fbb9 --- /dev/null +++ b/core/ConnectSDKTests/Helpers/AppStateChangeNotifierTests.m @@ -0,0 +1,227 @@ +// +// AppStateChangeNotifierTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/15/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "AppStateChangeNotifier.h" +#import "DispatchQueueBlockRunner.h" +#import "SynchronousBlockRunner.h" + +@interface AppStateChangeNotifierTests : XCTestCase + +@property (strong) AppStateChangeNotifier *notifier; + +@end + +@implementation AppStateChangeNotifierTests + +#pragma mark - Setup + +- (void)setUp { + [super setUp]; + + self.notifier = [AppStateChangeNotifier new]; + self.notifier.blockRunner = [SynchronousBlockRunner new]; +} + +- (void)tearDown { + [self.notifier stopListening]; + self.notifier = nil; + + [super tearDown]; +} + +#pragma mark - General Tests + +- (void)testDefaultBlockRunnerShouldBeMainQueueRunner { + AppStateChangeNotifier *notifier = [AppStateChangeNotifier new]; + XCTAssertEqualObjects(notifier.blockRunner, + [DispatchQueueBlockRunner mainQueueRunner], + @"blocks should run on main queue by default"); +} + +#pragma mark - DidEnterBackground Notification Tests + +- (void)testStartListeningShouldSubscribeToDidBackgroundEvent { + [self.notifier startListening]; + + __block BOOL verified = NO; + self.notifier.didBackgroundBlock = ^() { + verified = YES; + }; + [self postNotificationName:UIApplicationDidEnterBackgroundNotification]; + + XCTAssertTrue(verified, @"didBackgroundBlock should be called"); +} + +// idempotence test +- (void)testStartListeningTwiceShouldSubscribeToDidBackgroundEventOnce { + [self.notifier startListening]; + [self.notifier startListening]; + + __block NSUInteger callCount = 0; + self.notifier.didBackgroundBlock = ^() { + ++callCount; + }; + [self postNotificationName:UIApplicationDidEnterBackgroundNotification]; + + XCTAssertEqual(callCount, 1, @"didBackgroundBlock should be called once"); +} + +- (void)testStopListeningShouldUnsubscribeFromDidBackgroundEvent { + [self.notifier startListening]; + [self.notifier stopListening]; + + __block BOOL verified = NO; + self.notifier.didBackgroundBlock = ^() { + verified = YES; + }; + [self postNotificationName:UIApplicationDidEnterBackgroundNotification]; + + XCTAssertFalse(verified, @"didBackgroundBlock should not be called after " + @"stopping listening"); +} + +// idempotence test +- (void)testStopListeningTwiceShouldUnsubscribeFromDidBackgroundEventOnce { + [self.notifier startListening]; + [self.notifier stopListening]; + [self.notifier stopListening]; + + __block BOOL verified = NO; + self.notifier.didBackgroundBlock = ^() { + verified = YES; + }; + [self postNotificationName:UIApplicationDidEnterBackgroundNotification]; + + XCTAssertFalse(verified, @"didBackgroundBlock should not be called after " + @"stopping listening"); +} + +- (void)testDidBackgroundBlockShouldBeCalledOnGivenQueue { + dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL); + self.notifier.blockRunner = [[DispatchQueueBlockRunner alloc] initWithDispatchQueue:queue]; + [self.notifier startListening]; + + XCTestExpectation *blockIsCalled = [self expectationWithDescription: + @"didBackgroundBlock is called"]; + self.notifier.didBackgroundBlock = ^() { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#pragma clang diagnostic ignored "-Warc-retain-cycles" + XCTAssertEqual(dispatch_get_current_queue(), queue); +#pragma clang diagnostic pop + [blockIsCalled fulfill]; + }; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + [self postNotificationName:UIApplicationDidEnterBackgroundNotification]; + }); + + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout handler:nil]; +} + +#pragma mark - DidBecomeActive Notification Tests + +- (void)testStartListeningShouldSubscribeToDidBecomeActiveEvent { + [self.notifier startListening]; + + __block BOOL verified = NO; + self.notifier.didForegroundBlock = ^() { + verified = YES; + }; + [self postNotificationName:UIApplicationDidBecomeActiveNotification]; + + XCTAssertTrue(verified, @"didForegroundBlock should be called"); +} + +// idempotence test +- (void)testStartListeningTwiceShouldSubscribeToDidBecomeActiveEventOnce { + [self.notifier startListening]; + [self.notifier startListening]; + + __block NSUInteger callCount = 0; + self.notifier.didForegroundBlock = ^() { + ++callCount; + }; + [self postNotificationName:UIApplicationDidBecomeActiveNotification]; + + XCTAssertEqual(callCount, 1, @"didForegroundBlock should be called once"); +} + +- (void)testStopListeningShouldUnsubscribeFromDidBecomeActiveEvent { + [self.notifier startListening]; + [self.notifier stopListening]; + + __block BOOL verified = NO; + self.notifier.didForegroundBlock = ^() { + verified = YES; + }; + [self postNotificationName:UIApplicationDidBecomeActiveNotification]; + + XCTAssertFalse(verified, @"didForegroundBlock should not be called after " + @"stopping listening"); +} + +// idempotence test +- (void)testStopListeningTwiceShouldUnsubscribeFromDidBecomeActiveEventOnce { + [self.notifier startListening]; + [self.notifier stopListening]; + [self.notifier stopListening]; + + __block BOOL verified = NO; + self.notifier.didForegroundBlock = ^() { + verified = YES; + }; + [self postNotificationName:UIApplicationDidBecomeActiveNotification]; + + XCTAssertFalse(verified, @"didBackgroundBlock should not be called after " + @"stopping listening"); +} + +- (void)testDidForegroundBlockShouldBeCalledOnGivenQueue { + dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL); + self.notifier.blockRunner = [[DispatchQueueBlockRunner alloc] initWithDispatchQueue:queue]; + [self.notifier startListening]; + + XCTestExpectation *blockIsCalled = [self expectationWithDescription: + @"didForegroundBlock is called"]; + self.notifier.didForegroundBlock = ^() { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#pragma clang diagnostic ignored "-Warc-retain-cycles" + XCTAssertEqual(dispatch_get_current_queue(), queue); +#pragma clang diagnostic pop + [blockIsCalled fulfill]; + }; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + [self postNotificationName:UIApplicationDidBecomeActiveNotification]; + }); + + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout handler:nil]; +} + +#pragma mark - Helpers + +- (void)postNotificationName:(NSString *)name { + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center postNotificationName:name + object:[UIApplication sharedApplication]]; + +} + +@end diff --git a/core/ConnectSDKTests/Helpers/BlockRunner/BlockRunnerTests.m b/core/ConnectSDKTests/Helpers/BlockRunner/BlockRunnerTests.m new file mode 100644 index 00000000..884b7da0 --- /dev/null +++ b/core/ConnectSDKTests/Helpers/BlockRunner/BlockRunnerTests.m @@ -0,0 +1,46 @@ +// +// BlockRunnerTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/4/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "BlockRunner.h" + +#import + +@interface BlockRunnerTests : XCTestCase + +@end + +@implementation BlockRunnerTests + +- (void)testProtocolShouldInheritFromNSObject { + XCTAssertTrue(protocol_conformsToProtocol(@protocol(BlockRunner), + @protocol(NSObject)), + @"BlockRunner should inherit from NSObject"); +} + +- (void)testProtocolShouldHaveRunBlockMethod { + struct objc_method_description desc = protocol_getMethodDescription(@protocol(BlockRunner), + @selector(runBlock:), + YES, + YES); + XCTAssertNotEqual(desc.name, NULL, @"runBlock: should be defined"); + XCTAssertNotEqual(desc.types, NULL, @"runBlock: should be defined"); +} + +@end diff --git a/core/ConnectSDKTests/Helpers/BlockRunner/DispatchQueueBlockRunnerTests.m b/core/ConnectSDKTests/Helpers/BlockRunner/DispatchQueueBlockRunnerTests.m new file mode 100644 index 00000000..2b597be2 --- /dev/null +++ b/core/ConnectSDKTests/Helpers/BlockRunner/DispatchQueueBlockRunnerTests.m @@ -0,0 +1,151 @@ +// +// DispatchQueueBlockRunnerTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/4/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DispatchQueueBlockRunner.h" + +@interface DispatchQueueBlockRunnerTests : XCTestCase + +@property (strong) DispatchQueueBlockRunner *runner; +@property (strong) dispatch_queue_t queue; + +@end + +@implementation DispatchQueueBlockRunnerTests + +- (void)setUp { + [super setUp]; + + self.queue = dispatch_queue_create("DispatchQueueBlockRunnerTests.queue", + DISPATCH_QUEUE_SERIAL); + self.runner = [[DispatchQueueBlockRunner alloc] initWithDispatchQueue:self.queue]; +} + +- (void)tearDown { + self.queue = nil; + self.runner = nil; + + [super tearDown]; +} + +- (void)testClassShouldImplementBlockRunner { + XCTAssertTrue([self.runner.class conformsToProtocol:@protocol(BlockRunner)]); +} + +- (void)testNilQueueInInitShouldNotBeAccepted { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + XCTAssertThrows([[DispatchQueueBlockRunner alloc] initWithDispatchQueue:nil], + @"nil queue is not accepted"); +#pragma clang diagnostic pop +} + +- (void)testDefaultInitShouldNotBeAllowed { + XCTAssertThrows([DispatchQueueBlockRunner new], @"queue must be specified"); +} + +- (void)testNilBlockShouldNotBeAccepted { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + XCTAssertThrows([self.runner runBlock:nil], @"nil block is not accepted"); +#pragma clang diagnostic pop +} + +- (void)ignored_testBlockShouldNotBeRunSynchronously { + /* NB: this test is ignored for now, because it sometimes fails on CI + servers, occasionally running the block synchronously in all iterations. */ + + /* to verify the block is not called synchronously, it shouldn't increment a + value, that is checked after scheduling the block. however, GCD doesn't + promise that the block won't run immediately, which actually happens and + failed the test very occasionally. now, we run the test thousands of times + to verify the block is called asynchronously in most cases (>= 80%). */ + + NSUInteger failCount = 0; + static const NSUInteger iterationCount = 10000; + for (NSUInteger i = 0; i < iterationCount; ++i) { + __block NSUInteger testValue = 0; + void(^incrementValueBlock)(void) = ^{ + ++testValue; + + XCTAssertFalse([NSThread isMainThread]); + }; + [self.runner runBlock:incrementValueBlock]; + + NSUInteger isFail = (0 != testValue); + failCount += isFail; + } + + XCTAssertLessThan(failCount, (NSUInteger)trunc(iterationCount * 0.2), + @"The block should not run synchronously " + @"(in more than 20%% of cases)"); +} + +- (void)testBlockShouldBeRunAsynchronously { + for (NSUInteger i = 0; i < 10000; ++i) { + __block NSUInteger testValue = 0; + void(^incrementValueBlock)(void) = ^{ + ++testValue; + }; + + XCTestExpectation *allBlockAreRun = [self expectationWithDescription: + @"All blocks on the queue are run"]; + // NB: since dispatch_get_current_queue() is deprecated, we have to make + // assumptions with workarounds + dispatch_async(self.queue, ^{ + XCTAssertEqual(testValue, 0, @"The block should not have been run yet"); + }); + [self.runner runBlock:incrementValueBlock]; + dispatch_async(self.queue, ^{ + [allBlockAreRun fulfill]; + }); + + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + }]; + XCTAssertEqual(testValue, 1, @"The block should have been run already"); + } +} + +- (void)testInstancesShouldBeEqualIfQueuesAreEqual { + DispatchQueueBlockRunner *runner = [[DispatchQueueBlockRunner alloc] + initWithDispatchQueue:self.queue]; + XCTAssertEqualObjects(self.runner, runner, + @"The two instances should be equal because they use the same queue"); +} + +- (void)testHashesShouldBeEqualIfQueuesAreEqual { + DispatchQueueBlockRunner *runner = [[DispatchQueueBlockRunner alloc] + initWithDispatchQueue:self.queue]; + XCTAssertEqual(self.runner.hash, runner.hash, + @"The two instances should have equal hash because they are equal"); +} + +- (void)testMainDispatchQueueBlockRunnerShouldCreateMainQueueInstance { + dispatch_queue_t mainQueue = dispatch_get_main_queue(); + DispatchQueueBlockRunner *manualMainQueueRunner = [[DispatchQueueBlockRunner alloc] + initWithDispatchQueue:mainQueue]; + + DispatchQueueBlockRunner *convenienceMainQueueRunner = [DispatchQueueBlockRunner mainQueueRunner]; + XCTAssertEqualObjects(manualMainQueueRunner, convenienceMainQueueRunner, + @"The mainQueueRunner should return runner with main queue"); +} + +@end diff --git a/core/ConnectSDKTests/Helpers/BlockRunner/SynchronousBlockRunnerTests.m b/core/ConnectSDKTests/Helpers/BlockRunner/SynchronousBlockRunnerTests.m new file mode 100644 index 00000000..7675e111 --- /dev/null +++ b/core/ConnectSDKTests/Helpers/BlockRunner/SynchronousBlockRunnerTests.m @@ -0,0 +1,64 @@ +// +// SynchronousBlockRunnerTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/4/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "SynchronousBlockRunner.h" + +@interface SynchronousBlockRunnerTests : XCTestCase + +@property (strong) SynchronousBlockRunner *runner; + +@end + +@implementation SynchronousBlockRunnerTests + +- (void)setUp { + [super setUp]; + + self.runner = [SynchronousBlockRunner new]; +} + +- (void)tearDown { + self.runner = nil; + + [super tearDown]; +} + +- (void)testClassShouldImplementBlockRunner { + XCTAssertTrue([self.runner.class conformsToProtocol:@protocol(BlockRunner)]); +} + +- (void)testNilBlockShouldNotBeAccepted { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + XCTAssertThrows([self.runner runBlock:nil], @"nil block is not accepted"); +#pragma clang diagnostic pop +} + +- (void)testBlockShouldBeRunSynchronously { + __block NSUInteger testValue = 0; + void(^incrementValueBlock)(void) = ^{ + ++testValue; + }; + [self.runner runBlock:incrementValueBlock]; + XCTAssertEqual(testValue, 1, + @"The block should have synchronously incremented the value"); +} + +@end diff --git a/core/ConnectSDKTests/Helpers/MediaInfoTests.m b/core/ConnectSDKTests/Helpers/MediaInfoTests.m new file mode 100644 index 00000000..ae408136 --- /dev/null +++ b/core/ConnectSDKTests/Helpers/MediaInfoTests.m @@ -0,0 +1,48 @@ +// +// MediaInfoTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-07-20. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "MediaInfo.h" +#import "SubtitleInfo.h" + +@interface MediaInfoTests : XCTestCase + +@end + +@implementation MediaInfoTests + +- (void)testClassShouldHaveSubtitleInfoProperty { + NSURL *url = [NSURL URLWithString:@"http://example.com/"]; + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:url + mimeType:@"image/png"]; + + SubtitleInfo *subtitleInfo = [SubtitleInfo infoWithURL:url]; + mediaInfo.subtitleInfo = subtitleInfo; + + XCTAssertEqualObjects(mediaInfo.subtitleInfo, subtitleInfo); +} + +- (void)testDefaultSubtitleInfoShouldBeNil { + NSURL *url = [NSURL URLWithString:@"http://example.com/"]; + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:url + mimeType:@"image/png"]; + XCTAssertNil(mediaInfo.subtitleInfo); +} + +@end diff --git a/core/ConnectSDKTests/Helpers/NSDictionary+KeyPredicateSearchTests.m b/core/ConnectSDKTests/Helpers/NSDictionary+KeyPredicateSearchTests.m new file mode 100644 index 00000000..5d2a51a8 --- /dev/null +++ b/core/ConnectSDKTests/Helpers/NSDictionary+KeyPredicateSearchTests.m @@ -0,0 +1,109 @@ +// +// NSDictionary+KeyPredicateSearchTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 1/13/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NSDictionary+KeyPredicateSearch.h" + +/// Tests for the @c NSDictionary+KeyPredicateSearch category. +@interface NSDictionary_KeyPredicateSearchTests : XCTestCase + +@property (nonatomic, strong) NSDictionary *sampleDict; + +@end + +@implementation NSDictionary_KeyPredicateSearchTests + +- (void)setUp { + [super setUp]; + self.sampleDict = @{@"ab:cd": @42, + @"ef:hg": @100, + @"something": @"else"}; +} + +#pragma mark - Key Predicate Search Tests + +/// Tests that @c -objectForKeyWithPredicate: returns the object for a key that +/// matches the predicate. +- (void)testMatchingKeyShouldReturnValue { + id object = [self.sampleDict objectForKeyWithPredicate: + [NSPredicate predicateWithFormat:@"self contains %@", @"b:c"]]; + XCTAssertEqualObjects(object, @42, @"The found object is wrong"); +} + +/// Tests that @c -objectForKeyWithPredicate: throws an exception when two +/// matching keys are found in the dictionary. +- (void)testTwoMatchingKeysShouldThrowException { + XCTAssertThrows(([self.sampleDict objectForKeyWithPredicate: + [NSPredicate predicateWithFormat:@"self contains %@", @"e"]]), + @"More than one matching key should throw an exception"); +} + +/// @c -objectForKeyWithPredicate: should return @c nil when the predicate is +/// @c nil. +- (void)testNilPredicateShouldReturnNil { + XCTAssertNil([self.sampleDict objectForKeyWithPredicate:nil], + @"Nil predicate should return nil object"); +} + +/// @c -objectForKeyWithPredicate: should return @c nil when there is no key +/// matching the predicate. +- (void)testNotMatchingPredicateShouldReturnNil { + XCTAssertNil(([self.sampleDict objectForKeyWithPredicate: + [NSPredicate predicateWithFormat:@"self beginswith %@", @"aaa"]]), + @"Dictionary not containing a matching key should return nil"); +} + +#pragma mark - Key Ending With String Search Tests + +/// @c -objectForKeyEndingWithString: should return the object for a key ending +/// with the given string. +- (void)testKeyEndingWithStringShouldReturnValue { + id object = [self.sampleDict objectForKeyEndingWithString:@":cd"]; + XCTAssertEqualObjects(object, @42, @"The found object is wrong"); +} + +/// @c -objectForKeyEndingWithString: should throw an exception when two keys +/// end with the given string. +- (void)testTwoMatchingKeysEndingWithStringShouldThrowException { + XCTAssertThrows([self.sampleDict objectForKeyEndingWithString:@"g"], + @"More than one matching key should throw an exception"); +} + +/// @c -objectForKeyEndingWithString: should return @c nil when the string is +/// empty. +- (void)testKeyEndingWithEmptyStringShouldReturnNil { + XCTAssertNil([self.sampleDict objectForKeyEndingWithString:@""], + @"Keys ending with empty string should return nil"); +} + +/// @c -objectForKeyEndingWithString: should return nil when the string is +/// @c nil. +- (void)testKeyEndingWithNilStringShouldReturnNil { + XCTAssertNil([self.sampleDict objectForKeyEndingWithString:nil], + @"Keys ending with nil string should return nil"); +} + +/// @c -objectForKeyEndingWithString: should return @c nil if no keys ending +/// with the given string found. +- (void)testNotMatchingKeyEndingWithStringShouldReturnNil { + XCTAssertNil([self.sampleDict objectForKeyEndingWithString:@"bbb"], + @"Not matching keys ending with string should return nil"); +} + +@end diff --git a/core/ConnectSDKTests/Helpers/NSMutableDictionary+NilSafeTests.m b/core/ConnectSDKTests/Helpers/NSMutableDictionary+NilSafeTests.m new file mode 100644 index 00000000..98575b9f --- /dev/null +++ b/core/ConnectSDKTests/Helpers/NSMutableDictionary+NilSafeTests.m @@ -0,0 +1,62 @@ +// +// NSMutableDictionary+NilSafeTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-07-23. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NSMutableDictionary+NilSafe.h" + +@interface NSMutableDictionary_NilSafeTests : XCTestCase + +@property(nonatomic, strong) NSMutableDictionary *dict; + +@end + +@implementation NSMutableDictionary_NilSafeTests + +#pragma mark - Setup + +- (void)setUp { + [super setUp]; + self.dict = [NSMutableDictionary dictionary]; +} + +#pragma mark - Tests + +- (void)testShouldSetNotNullObject { + [self.dict setNullableObject:@42 forKey:@"key"]; + XCTAssertEqualObjects(self.dict[@"key"], @42); +} + +- (void)testShouldNotSetNil { + [self.dict setNullableObject:nil forKey:@"key"]; + XCTAssertNil(self.dict[@"key"]); +} + +- (void)testNonNullObjectShouldOverwriteExistingObject { + self.dict[@"key"] = @0; + [self.dict setNullableObject:@42 forKey:@"key"]; + XCTAssertEqualObjects(self.dict[@"key"], @42); +} + +- (void)testNilObjectShouldNotOverwriteExistingObject { + self.dict[@"key"] = @0; + [self.dict setNullableObject:nil forKey:@"key"]; + XCTAssertEqualObjects(self.dict[@"key"], @0); +} + +@end diff --git a/core/ConnectSDKTests/Helpers/SubscriptionDeduplicatorTests.m b/core/ConnectSDKTests/Helpers/SubscriptionDeduplicatorTests.m new file mode 100644 index 00000000..cd62d6b6 --- /dev/null +++ b/core/ConnectSDKTests/Helpers/SubscriptionDeduplicatorTests.m @@ -0,0 +1,91 @@ +// +// SubscriptionDeduplicatorTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-06-26. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "SubscriptionDeduplicator.h" + +@interface SubscriptionDeduplicatorTests : XCTestCase + +@property (nonatomic, strong) SubscriptionDeduplicator *deduplicator; + +@end + +@implementation SubscriptionDeduplicatorTests + +#pragma mark - Setup + +- (void)setUp { + [super setUp]; + self.deduplicator = [SubscriptionDeduplicator new]; +} + +- (void)tearDown { + self.deduplicator = nil; + [super tearDown]; +} + +#pragma mark - State Tests + +- (void)testInstanceShouldBeCreated { + XCTAssertNotNil(self.deduplicator, @"Empty state instance should be created"); +} + +- (void)testFirstStateChangeShouldRunBlock { + __block BOOL blockRun = NO; + [self.deduplicator runBlock:^{ + blockRun = YES; + } + ifStateDidChangeTo:@42]; + XCTAssertTrue(blockRun, @"Block should be run on first state change"); +} + +- (void)testSameStateChangeShouldNotRunBlock { + self.deduplicator = [self.deduplicator runBlock:^{} ifStateDidChangeTo:@42]; + __block BOOL blockRun = NO; + [self.deduplicator runBlock:^{ + blockRun = YES; + } + ifStateDidChangeTo:@42]; + XCTAssertFalse(blockRun, + @"Block should not be run after the same state change"); +} + +- (void)testTwoStateChangesShouldRunBlocks { + __block BOOL firstBlockRun = NO; + self.deduplicator = [self.deduplicator runBlock:^{ + firstBlockRun = YES; + } + ifStateDidChangeTo:@42]; + __block BOOL secondBlockRun = NO; + [self.deduplicator runBlock:^{ + secondBlockRun = YES; + } + ifStateDidChangeTo:@"hello"]; + XCTAssertTrue(firstBlockRun && secondBlockRun, + @"Both blocks should be run"); +} + +- (void)testSameStateChangeShouldNotCreateNewInstance { + self.deduplicator = [self.deduplicator runBlock:^{} ifStateDidChangeTo:@42]; + XCTAssertEqual([self.deduplicator runBlock:^{} ifStateDidChangeTo:@42], + self.deduplicator, + @"Should return self if state hasn't changed"); +} + +@end diff --git a/core/ConnectSDKTests/Helpers/SubtitleInfoTests.m b/core/ConnectSDKTests/Helpers/SubtitleInfoTests.m new file mode 100644 index 00000000..552cfb31 --- /dev/null +++ b/core/ConnectSDKTests/Helpers/SubtitleInfoTests.m @@ -0,0 +1,88 @@ +// +// SubtitleInfoTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-07-14. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "SubtitleInfo.h" + +@interface SubtitleInfoTests : XCTestCase + +@property (nonatomic, strong) NSURL *url; + +@end + +@implementation SubtitleInfoTests + +- (void)setUp { + [super setUp]; + + self.url = [NSURL URLWithString:@"http://example.com/"]; +} + +#pragma mark - Init Tests + +- (void)testDefaultInitShouldThrowException { + XCTAssertThrowsSpecificNamed([SubtitleInfo new], + NSException, + NSInternalInconsistencyException, + @"Default initializer is not permitted"); +} + +- (void)testInfoConstructorShouldNotAcceptNilURL { + NSURL *nilUrl = [NSURL URLWithString:nil]; + XCTAssertThrowsSpecificNamed([SubtitleInfo infoWithURL:nilUrl], + NSException, + NSInternalInconsistencyException); +} + +- (void)testInfoConstructorWithBuilderShouldNotAcceptNilURL { + NSURL *nilUrl = [NSURL URLWithString:nil]; + XCTAssertThrowsSpecificNamed([SubtitleInfo infoWithURL:nilUrl + andBlock:^(SubtitleInfoBuilder *_) { + }], + NSException, + NSInternalInconsistencyException); +} + +- (void)testInfoConstructorShouldSetURL { + SubtitleInfo *info = [SubtitleInfo infoWithURL:self.url]; + XCTAssertEqualObjects(info.url, self.url); +} + +- (void)testInfoConstructorShouldLeaveOptionalPropertiesNil { + SubtitleInfo *info = [SubtitleInfo infoWithURL:self.url]; + XCTAssertNil(info.mimeType); + XCTAssertNil(info.language); + XCTAssertNil(info.label); +} + +- (void)testBuilderShouldSetProperties { + SubtitleInfo *info = [SubtitleInfo infoWithURL:self.url + andBlock:^(SubtitleInfoBuilder *builder) { + builder.mimeType = @"text/vtt"; + builder.language = @"en"; + builder.label = @"Test"; + }]; + + XCTAssertEqualObjects(info.url, self.url); + XCTAssertEqualObjects(info.mimeType, @"text/vtt"); + XCTAssertEqualObjects(info.language, @"en"); + XCTAssertEqualObjects(info.label, @"Test"); +} + +@end diff --git a/core/ConnectSDKTests/NSInvocation+ObjectGetter.h b/core/ConnectSDKTests/NSInvocation+ObjectGetter.h new file mode 100644 index 00000000..dfbbdf89 --- /dev/null +++ b/core/ConnectSDKTests/NSInvocation+ObjectGetter.h @@ -0,0 +1,29 @@ +// +// NSInvocation+ObjectGetter.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2/23/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@interface NSInvocation (ObjectGetter) + +/// Returns an object argument in the invocation at the given index. +/// @warning The index is zero-based, as in a method definition! +- (id)objectArgumentAtIndex:(NSInteger)idx; + +@end diff --git a/core/ConnectSDKTests/NSInvocation+ObjectGetter.m b/core/ConnectSDKTests/NSInvocation+ObjectGetter.m new file mode 100644 index 00000000..f3c3a1ef --- /dev/null +++ b/core/ConnectSDKTests/NSInvocation+ObjectGetter.m @@ -0,0 +1,33 @@ +// +// NSInvocation+ObjectGetter.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2/23/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NSInvocation+ObjectGetter.h" + +@implementation NSInvocation (ObjectGetter) + +- (id)objectArgumentAtIndex:(NSInteger)idx { + __unsafe_unretained id tmp; + // the first two arguments are `self` and `_cmd` + [self getArgument:&tmp atIndex:(idx + 2)]; + id object = tmp; + return object; +} + +@end diff --git a/core/ConnectSDKTests/OCMArg+ArgumentCaptor.h b/core/ConnectSDKTests/OCMArg+ArgumentCaptor.h new file mode 100644 index 00000000..f02584c0 --- /dev/null +++ b/core/ConnectSDKTests/OCMArg+ArgumentCaptor.h @@ -0,0 +1,36 @@ +// +// OCMArg+ArgumentCaptor.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/13/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN +@interface OCMArg (ArgumentCaptor) + +/// A block type that has no arguments and no return value. +typedef dispatch_block_t VoidBlock; + +/// Convenience method to capture an argument for later use/verification. ++ (id)captureTo:(out __strong __nullable id *__nonnull)objectPointer; + +/// Convenience method to capture a block argument for later use/verification. ++ (id)captureBlockTo:(out __strong __nullable VoidBlock *__nonnull)blockPointer; + +@end +NS_ASSUME_NONNULL_END diff --git a/core/ConnectSDKTests/OCMArg+ArgumentCaptor.m b/core/ConnectSDKTests/OCMArg+ArgumentCaptor.m new file mode 100644 index 00000000..1c7c91c0 --- /dev/null +++ b/core/ConnectSDKTests/OCMArg+ArgumentCaptor.m @@ -0,0 +1,39 @@ +// +// OCMArg+ArgumentCaptor.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/13/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "OCMArg+ArgumentCaptor.h" + +@implementation OCMArg (ArgumentCaptor) + ++ (id __nonnull)captureTo:(out id __nullable __strong *__nonnull)objectPointer { + return [self checkWithBlock:^BOOL(id obj) { + *objectPointer = obj; + return YES; + }]; +} + ++ (id __nonnull)captureBlockTo:(out VoidBlock __nullable __strong * __nonnull)blockPointer { + return [self checkWithBlock:^BOOL(id obj) { + *blockPointer = [obj copy]; + return YES; + }]; +} + +@end diff --git a/core/ConnectSDKTests/OCMStubRecorder+XCTestExpectation.h b/core/ConnectSDKTests/OCMStubRecorder+XCTestExpectation.h new file mode 100644 index 00000000..e5aea80b --- /dev/null +++ b/core/ConnectSDKTests/OCMStubRecorder+XCTestExpectation.h @@ -0,0 +1,30 @@ +// +// OCMStubRecorder+XCTestExpectation.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 4/24/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN +@interface OCMStubRecorder (XCTestExpectation) + +/// Convenience method to fulfill an @c XCTestExpectation with a stub. +- (id)andFulfillExpectation:(XCTestExpectation *)expectation; + +@end +NS_ASSUME_NONNULL_END diff --git a/core/ConnectSDKTests/OCMStubRecorder+XCTestExpectation.m b/core/ConnectSDKTests/OCMStubRecorder+XCTestExpectation.m new file mode 100644 index 00000000..f33a3a79 --- /dev/null +++ b/core/ConnectSDKTests/OCMStubRecorder+XCTestExpectation.m @@ -0,0 +1,31 @@ +// +// OCMStubRecorder+XCTestExpectation.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 4/24/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "OCMStubRecorder+XCTestExpectation.h" + +@implementation OCMStubRecorder (XCTestExpectation) + +- (id)andFulfillExpectation:(XCTestExpectation *)expectation { + return [self andDo:^(NSInvocation *invocation) { + [expectation fulfill]; + }]; +} + +@end diff --git a/core/ConnectSDKTests/Services/AirPlayServiceTests.m b/core/ConnectSDKTests/Services/AirPlayServiceTests.m new file mode 100644 index 00000000..af3f37b9 --- /dev/null +++ b/core/ConnectSDKTests/Services/AirPlayServiceTests.m @@ -0,0 +1,53 @@ +// +// AirPlayServiceTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-07-29. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "AirPlayService_Private.h" + +#import "XCTestCase+Common.h" + +@interface AirPlayServiceTests : XCTestCase + +@end + +@implementation AirPlayServiceTests + +#pragma mark - Unsupported Methods Tests + +- (void)testGetMediaMetadataShouldBeForwardedToHTTPServiceMediaControl { + AirPlayService *service = OCMPartialMock([AirPlayService new]); + + id /*AirPlayServiceHTTP **/ httpServiceStub = OCMClassMock([AirPlayServiceHTTP class]); + OCMStub([service createHTTPService]).andReturn(httpServiceStub); + + id /**/ mediaControlMock = OCMProtocolMock(@protocol(MediaControl)); + OCMStub([httpServiceStub mediaControl]).andReturn(mediaControlMock); + + SuccessBlock successBlock = ^(id _) {}; + FailureBlock failureBlock = ^(NSError *_) {}; + OCMExpect([mediaControlMock getMediaMetaDataWithSuccess:successBlock + failure:failureBlock]); + + [service getMediaMetaDataWithSuccess:successBlock + failure:failureBlock]; + + OCMVerifyAll(mediaControlMock); +} + +@end diff --git a/core/ConnectSDKTests/Services/Config/ServiceDescriptionTests.m b/core/ConnectSDKTests/Services/Config/ServiceDescriptionTests.m new file mode 100644 index 00000000..25e1695a --- /dev/null +++ b/core/ConnectSDKTests/Services/Config/ServiceDescriptionTests.m @@ -0,0 +1,90 @@ +// +// ServiceDescriptionTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/5/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ServiceDescription.h" + +@interface ServiceDescriptionTests : XCTestCase + +@end + +@implementation ServiceDescriptionTests + +#pragma mark - Equality Tests + +- (void)testInstancesWithNilDevicesShouldBeEqual { + ServiceDescription *sd1 = [ServiceDescription descriptionWithAddress:@"1" + UUID:@"2"]; + sd1.device = nil; + ServiceDescription *sd2 = [ServiceDescription descriptionWithAddress:@"1" + UUID:@"2"]; + sd2.device = nil; + XCTAssertEqualObjects(sd1, sd2, + @"Two instances with equal address and UUID should be equal without devices"); + XCTAssertEqual(sd1.hash, sd2.hash, @"And hashes should be equal"); +} + +- (void)testInstancesWithSameDevicesShouldBeEqual { + id abstractDevice = @"device"; + ServiceDescription *sd1 = [ServiceDescription descriptionWithAddress:@"1" + UUID:@"2"]; + sd1.device = abstractDevice; + ServiceDescription *sd2 = [ServiceDescription descriptionWithAddress:@"1" + UUID:@"2"]; + sd2.device = abstractDevice; + XCTAssertEqualObjects(sd1, sd2, + @"Two instances with equal address and UUID should be equal with the same devices"); + XCTAssertEqual(sd1.hash, sd2.hash, @"And hashes should be equal"); +} + +- (void)testInstancesWithEqualDevicesShouldBeEqual { + ServiceDescription *sd1 = [ServiceDescription descriptionWithAddress:@"1" + UUID:@"2"]; + sd1.device = @{@"key": @"device"}; + ServiceDescription *sd2 = [ServiceDescription descriptionWithAddress:@"1" + UUID:@"2"]; + sd2.device = @{@"key": @"device"}; + + // safety check + XCTAssertEqualObjects(sd1.device, sd2.device, @"Devices should be equal objects"); + XCTAssertNotEqual(sd1.device, sd2.device, @"but should not be equal pointers"); + + XCTAssertEqualObjects(sd1, sd2, + @"Two instances with equal address and UUID should be equal with the equal devices"); + XCTAssertEqual(sd1.hash, sd2.hash, @"And hashes should be equal"); +} + +- (void)testInstancesWithNotEqualDevicesShouldNotBeEqual { + ServiceDescription *sd1 = [ServiceDescription descriptionWithAddress:@"1" + UUID:@"2"]; + sd1.device = @"device1"; + ServiceDescription *sd2 = [ServiceDescription descriptionWithAddress:@"1" + UUID:@"2"]; + sd2.device = @"device2"; + + // safety check + XCTAssertNotEqualObjects(sd1.device, sd2.device, @"Devices should not be equal objects"); + XCTAssertNotEqual(sd1.hash, sd2.hash, @"Devices should not have equal hashes"); + + XCTAssertNotEqualObjects(sd1, sd2, + @"Two instances with equal address and UUID should not be equal with not equal devices"); + XCTAssertNotEqual(sd1.hash, sd2.hash, @"And hashes should not be equal"); +} + +@end diff --git a/core/ConnectSDKTests/Services/DLNAServiceTests.m b/core/ConnectSDKTests/Services/DLNAServiceTests.m new file mode 100644 index 00000000..8fe2b923 --- /dev/null +++ b/core/ConnectSDKTests/Services/DLNAServiceTests.m @@ -0,0 +1,1413 @@ +// +// DLNAServiceTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 1/13/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "NSInvocation+ObjectGetter.h" +#import "OCMStubRecorder+XCTestExpectation.h" + +#import "CTXMLReader.h" +#import "DLNAService_Private.h" +#import "ConnectError.h" +#import "NSDictionary+KeyPredicateSearch.h" +#import "SSDPDiscoveryProvider_Private.h" +#import "DLNAHTTPServer.h" +#import "DeviceServiceReachability.h" +#import "SubtitleInfo.h" + +static NSString *const kPlatformXbox = @"xbox"; +static NSString *const kPlatformSonos = @"sonos"; + +static NSString *const kAVTransportNamespace = @"urn:schemas-upnp-org:service:AVTransport:1"; +static NSString *const kRenderingControlNamespace = @"urn:schemas-upnp-org:service:RenderingControl:1"; + +/// Executes the given block wrapping it in pragmas ignoring the capturing +/// @c self warning, for a rare case when a compiler thinks a method is a setter, +/// but it is not (e.g., -[DLNAService setVolume:success:failure:]). +/// @see http://stackoverflow.com/questions/15535899/blocks-retain-cycle-from-naming-convention +// +// http://stackoverflow.com/questions/13826722/how-do-i-define-a-macro-with-multiple-pragmas-for-clang +#define silence_retain_cycle_warning(block) ({\ + _Pragma("clang diagnostic push"); \ + _Pragma("clang diagnostic ignored \"-Warc-retain-cycles\""); \ + block(); \ + _Pragma("clang diagnostic pop"); \ +}) + +static NSString *const kAVTransportControlURLKey = @"avTrCtrlURL"; +static NSString *const kAVTransportEventURLKey = @"avTrEventURL"; +static NSString *const kRenderingControlControlURLKey = @"rndCtrCtrlURL"; +static NSString *const kRenderingControlEventURLKey = @"rndCtrEventURL"; + +static NSString *const kIconURLMetadataKey = @"iconURL"; + +static NSString *const kSecCaptionInfoTag = @"sec:CaptionInfo"; +static NSString *const kSecCaptionInfoExTag = @"sec:CaptionInfoEx"; + + +/// Tests for the @c DLNAService class. +@interface DLNAServiceTests : XCTestCase + +@property (nonatomic, strong) id serviceCommandDelegateMock; +@property (nonatomic, strong) DLNAService *service; +@property (nonatomic, strong) FailureBlock failFailureBlock; +@property (nonatomic, strong) void (^failSuccessBlock)(); + +@end + +@implementation DLNAServiceTests + +- (void)setUp { + [super setUp]; + self.serviceCommandDelegateMock = OCMProtocolMock(@protocol(ServiceCommandDelegate)); + self.service = [DLNAService new]; + self.service.serviceCommandDelegate = self.serviceCommandDelegateMock; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-retain-cycles" + self.failFailureBlock = ^(NSError *error) { + XCTFail(@"fail? %@", error); + }; + self.failSuccessBlock = ^() { + XCTFail(@"success?"); + }; +#pragma clang diagnostic pop +} + +- (void)tearDown { + self.service = nil; + self.serviceCommandDelegateMock = nil; + [OHHTTPStubs removeAllStubs]; + [super tearDown]; +} + +#pragma mark - General Tests + +- (void)testInstanceShouldHaveSubtitleSRTCapability { + XCTAssertNotEqual([self.service.capabilities indexOfObject:kMediaPlayerSubtitleSRT], + NSNotFound); +} + +#pragma mark - Request Generation Tests + +static NSString *const kDefaultTitle = @"Hello, &]]> \"others'\\ ура ξ中]]>…"; +static NSString *const kDefaultDescription = @" &\"'"; +static NSString *const kDefaultURL = @"http://example.com/media.ogg"; +static NSString *const kDefaultAlbumArtURL = @"http://example.com/media.png"; + +/// Tests that @c -playMediaWithMediaInfo:shouldLoop:success:failure: creates a +/// proper and valid SetAVTransportURI XML request. +- (void)testPlayMediaShouldCreateProperSetAVTransportURIXML { + [self checkPlayMediaShouldCreateProperSetAVTransportURIXMLWithTitle:kDefaultTitle + description:kDefaultDescription + url:kDefaultURL + andAlbumArtURL:kDefaultAlbumArtURL]; +} + +/// Tests that @c -playMediaWithMediaInfo:shouldLoop:success:failure: creates a +/// proper and valid SetAVTransportURI XML request without title. +- (void)testPlayMediaShouldCreateProperSetAVTransportURIXMLWithoutTitle { + [self checkPlayMediaShouldCreateProperSetAVTransportURIXMLWithTitle:nil + description:kDefaultDescription + url:kDefaultURL + andAlbumArtURL:kDefaultAlbumArtURL]; +} + +/// Tests that @c -playMediaWithMediaInfo:shouldLoop:success:failure: creates a +/// proper and valid SetAVTransportURI XML request without description. +- (void)testPlayMediaShouldCreateProperSetAVTransportURIXMLWithoutDescription { + [self checkPlayMediaShouldCreateProperSetAVTransportURIXMLWithTitle:kDefaultTitle + description:nil + url:kDefaultURL + andAlbumArtURL:kDefaultAlbumArtURL]; +} + +/// Tests that @c -playMediaWithMediaInfo:shouldLoop:success:failure: creates a +/// proper and valid SetAVTransportURI XML request without URL. +- (void)testPlayMediaShouldCreateProperSetAVTransportURIXMLWithoutURL { + [self checkPlayMediaShouldCreateProperSetAVTransportURIXMLWithTitle:kDefaultTitle + description:kDefaultDescription + url:nil + andAlbumArtURL:kDefaultAlbumArtURL]; +} + +/// Tests that @c -playMediaWithMediaInfo:shouldLoop:success:failure: creates a +/// proper and valid SetAVTransportURI XML request without album art URL. +- (void)testPlayMediaShouldCreateProperSetAVTransportURIXMLWithoutAlbumArtURL { + [self checkPlayMediaShouldCreateProperSetAVTransportURIXMLWithTitle:kDefaultTitle + description:kDefaultDescription + url:kDefaultURL + andAlbumArtURL:nil]; +} + +/// Tests that @c -displayImageWithMediaInfo:success:failure: creates a +/// proper and valid SetAVTransportURI XML request. +- (void)testDisplayImageShouldCreateProperSetAVTransportURIXML { + [self checkDisplayImageShouldCreateProperSetAVTransportURIXMLWithTitle:kDefaultTitle + url:kDefaultAlbumArtURL]; +} + +/// Tests that @c -displayImageWithMediaInfo:success:failure: creates a +/// proper and valid SetAVTransportURI XML request without title. +- (void)testDisplayImageShouldCreateProperSetAVTransportURIXMLWithoutTitle { + [self checkDisplayImageShouldCreateProperSetAVTransportURIXMLWithTitle:nil + url:kDefaultAlbumArtURL]; +} + +/// Tests that @c -displayImageWithMediaInfo:success:failure: creates a +/// proper and valid SetAVTransportURI XML request without URL. +- (void)testDisplayImageShouldCreateProperSetAVTransportURIXMLWithoutURL { + [self checkDisplayImageShouldCreateProperSetAVTransportURIXMLWithTitle:kDefaultTitle + url:nil]; +} + +/// Tests that @c -playWithSuccess:failure: creates a proper and valid Play XML +/// request. +- (void)testPlayShouldCreateProperPlayXML { + [self setupSendCommandTestWithName:@"Play" + namespace:kAVTransportNamespace + actionBlock:^{ + [self.service playWithSuccess:^(id responseObject) { + XCTFail(@"success?"); + } failure:^(NSError *error) { + XCTFail(@"fail? %@", error); + }]; + } andVerificationBlock:^(NSDictionary *request) { + XCTAssertEqualObjects([request valueForKeyPath:@"Speed.text"], @"1", @"Speed must equal 1"); + }]; +} + +/// Tests that @c -pauseWithSuccess:failure: creates a proper and valid Pause +/// XML request. +- (void)testPauseShouldCreateProperPauseXML { + [self setupSendCommandTestWithName:@"Pause" + namespace:kAVTransportNamespace + actionBlock:^{ + [self.service pauseWithSuccess:^(id responseObject) { + XCTFail(@"success?"); + } failure:^(NSError *error) { + XCTFail(@"fail? %@", error); + }]; + } andVerificationBlock:nil]; +} + +/// Tests that @c -stopWithSuccess:failure: creates a proper and valid Stop XML +/// request. +- (void)testStopShouldCreateProperStopXML { + [self setupSendCommandTestWithName:@"Stop" + namespace:kAVTransportNamespace + actionBlock:^{ + [self.service stopWithSuccess:^(id responseObject) { + XCTFail(@"success?"); + } failure:^(NSError *error) { + XCTFail(@"fail? %@", error); + }]; + } andVerificationBlock:nil]; +} + +/// Tests that @c -seek:success:failure: creates a proper and valid Seek XML +/// request. +- (void)testSeekShouldCreateProperSeekXML { + [self setupSendCommandTestWithName:@"Seek" + namespace:kAVTransportNamespace + actionBlock:^{ + [self.service seek:(1 * 60 * 60) + (45 * 60) + 33 + success:^(id responseObject) { + XCTFail(@"success?"); + } failure:^(NSError *error) { + XCTFail(@"fail? %@", error); + }]; + } andVerificationBlock:^(NSDictionary *request) { + XCTAssertEqualObjects([request valueForKeyPath:@"Target.text"], + @"01:45:33", @"Seek position is incorrect"); + XCTAssertEqualObjects([request valueForKeyPath:@"Unit.text"], + @"REL_TIME", @"Unit is incorrect"); + }]; +} + +/// Tests that @c -getPlayStateWithSuccess:failure: creates a proper and valid +/// GetTransportInfo XML request. +- (void)testGetPlayStateShouldCreateProperGetTransportInfoXML { + [self setupSendCommandTestWithName:@"GetTransportInfo" + namespace:kAVTransportNamespace + actionBlock:^{ + [self.service getPlayStateWithSuccess:^(MediaControlPlayState playState) { + XCTFail(@"success?"); + } failure:^(NSError *error) { + XCTFail(@"fail? %@", error); + }]; + } andVerificationBlock:nil]; +} + +/// Tests that @c -getPositionWithSuccess:failure: creates a proper and valid +/// GetPositionInfo XML request. +- (void)testGetPositionInfoShouldCreateProperGetPositionInfoXML { + [self setupSendCommandTestWithName:@"GetPositionInfo" + namespace:kAVTransportNamespace + actionBlock:^{ + [self.service getPositionWithSuccess:^(NSTimeInterval position) { + XCTFail(@"success?"); + } failure:^(NSError *error) { + XCTFail(@"fail? %@", error); + }]; + } andVerificationBlock:nil]; +} + +/// Tests that @c -getVolumeWithSuccess:failure: creates a proper and valid +/// GetVolume XML request. +- (void)testGetVolumeShouldCreateProperGetVolumeXML { + [self setupSendCommandTestWithName:@"GetVolume" + namespace:kRenderingControlNamespace + actionBlock:^{ + [self.service getVolumeWithSuccess:^(float volume) { + XCTFail(@"success?"); + } failure:^(NSError *error) { + XCTFail(@"fail? %@", error); + }]; + } andVerificationBlock:^(NSDictionary *request) { + XCTAssertEqualObjects([request valueForKeyPath:@"Channel.text"], + @"Master", @"Channel must be Master"); + }]; +} + +/// Tests that @c -setVolume:success:failure: creates a proper and valid +/// SetVolume XML request. +- (void)testSetVolumeShouldCreateProperSetVolumeXML { + [self setupSendCommandTestWithName:@"SetVolume" + namespace:kRenderingControlNamespace + actionBlock:^{ + [self.service setVolume:0.99 + success:^(id responseObject) { + silence_retain_cycle_warning(^() { + XCTFail(@"success?"); + }); + } failure:^(NSError *error) { + XCTFail(@"fail? %@", error); + }]; + } andVerificationBlock:^(NSDictionary *request) { + XCTAssertEqualObjects([request valueForKeyPath:@"Channel.text"], + @"Master", @"Channel must be Master"); + XCTAssertEqualObjects([request valueForKeyPath:@"DesiredVolume.text"], + @"99", @"Volume is incorrect"); + }]; +} + +/// Tests that @c -getMuteWithSuccess:failure: creates a proper and valid +/// GetMute XML request. +- (void)testGetMuteShouldCreateProperGetMuteXML { + [self setupSendCommandTestWithName:@"GetMute" + namespace:kRenderingControlNamespace + actionBlock:^{ + [self.service getMuteWithSuccess:^(BOOL mute) { + XCTFail(@"success?"); + } failure:^(NSError *error) { + XCTFail(@"fail? %@", error); + }]; + } andVerificationBlock:^(NSDictionary *request) { + XCTAssertEqualObjects([request valueForKeyPath:@"Channel.text"], + @"Master", @"Channel must be Master"); + }]; +} + +/// Tests that @c -setMute:success:failure: creates a proper and valid +/// SetMute XML request. +- (void)testSetMuteShouldCreateProperSetMuteXML { + [self setupSendCommandTestWithName:@"SetMute" + namespace:kRenderingControlNamespace + actionBlock:^{ + [self.service setMute:YES + success:^(id responseObject) { + silence_retain_cycle_warning(^() { + XCTFail(@"success?"); + }); + } failure:^(NSError *error) { + XCTFail(@"fail? %@", error); + }]; + } andVerificationBlock:^(NSDictionary *request) { + XCTAssertEqualObjects([request valueForKeyPath:@"Channel.text"], + @"Master", @"Channel must be Master"); + XCTAssertEqualObjects([request valueForKeyPath:@"DesiredMute.text"], + @"1", @"DesiredMute is incorrect"); + }]; +} + +/// Tests that @c -playNextWithSuccess:failure: creates a proper and valid Next +/// XML request. +- (void)testPlayNextShouldCreateProperNextXML { + [self setupSendCommandTestWithName:@"Next" + namespace:kAVTransportNamespace + actionBlock:^{ + [self.service playNextWithSuccess:^(id responseObject) { + XCTFail(@"success?"); + } failure:^(NSError *error) { + XCTFail(@"fail? %@", error); + }]; + } andVerificationBlock:nil]; +} + +/// Tests that @c -playPreviousWithSuccess:failure: creates a proper and valid +/// Previous XML request. +- (void)testPlayPreviousShouldCreateProperPreviousXML { + [self setupSendCommandTestWithName:@"Previous" + namespace:kAVTransportNamespace + actionBlock:^{ + [self.service playPreviousWithSuccess:^(id responseObject) { + XCTFail(@"success?"); + } failure:^(NSError *error) { + XCTFail(@"fail? %@", error); + }]; + } andVerificationBlock:nil]; +} + +/// Tests that @c -jumpToTrackWithIndex:success:failure: creates a proper and +/// valid Seek XML request. +- (void)testJumpToTrackShouldCreateProperSeekXML { + [self setupSendCommandTestWithName:@"Seek" + namespace:kAVTransportNamespace + actionBlock:^{ + [self.service jumpToTrackWithIndex:0 + success:^(id responseObject) { + XCTFail(@"success?"); + } failure:^(NSError *error) { + XCTFail(@"fail? %@", error); + }]; + } andVerificationBlock:^(NSDictionary *request) { + XCTAssertEqualObjects([request valueForKeyPath:@"Target.text"], + @"1", @"Track number is incorrect"); + XCTAssertEqualObjects([request valueForKeyPath:@"Unit.text"], + @"TRACK_NR", @"Unit is incorrect"); + }]; +} + +#pragma mark - Subtitles Support Tests + +- (void)testPlayVideoWithSubtitlesRequestShouldContainSMICaptionProtocolInfo { + [self checkPlayVideoWithSubtitlesRequestShouldContainProtocolInfoWithAttributeValue:@"http-get:*:smi/caption"]; +} + +- (void)testPlayVideoWithSubtitlesRequestShouldContainMimeTypeProtocolInfo { + [self checkPlayVideoWithSubtitlesRequestShouldContainProtocolInfoWithAttributeValue:@"http-get:*:text/srt:*"]; +} + +- (void)testPlayVideoWithSubtitlesRequestShouldContainSecCaptionInfo { + [self checkPlayVideoWithSubtitlesRequestShouldContainSecTagWithName:kSecCaptionInfoTag]; +} + +- (void)testPlayVideoWithSubtitlesRequestShouldContainSecCaptionInfoEx { + [self checkPlayVideoWithSubtitlesRequestShouldContainSecTagWithName:kSecCaptionInfoExTag]; +} + +- (void)testPlayVideoWithSubtitlesRequestShouldContainPVSubtitleAttributes { + [self checkPlayVideoRequestWithMediaInfo:[self mediaInfoWithSubtitle] + shouldContainPVSubtitleAttributes:YES]; +} + +- (void)testPlayVideoWithoutSubtitlesRequestShouldNotContainSMICaptionProtocolInfo { + [self checkPlayVideoWithoutSubtitlesRequestShouldNotContainProtocolInfoWithAttributeValue:@"http-get:*:smi/caption"]; +} + +- (void)testPlayVideoWithoutSubtitlesRequestShouldNotContainMimeTypeProtocolInfo { + [self checkPlayVideoWithoutSubtitlesRequestShouldNotContainProtocolInfoWithAttributeValue:@"http-get:*:text/srt:*"]; +} + +- (void)testPlayVideoWithoutSubtitlesRequestShouldNotContainNullMimeTypeProtocolInfo { + [self checkPlayVideoWithoutSubtitlesRequestShouldNotContainProtocolInfoWithAttributeValue:@"http-get:*:(null):*"]; +} + +- (void)testPlayVideoWithoutSubtitlesRequestShouldNotContainSecCaptionInfo { + [self checkPlayVideoWithoutSubtitlesRequestShouldNotContainSecTagWithName:kSecCaptionInfoTag]; +} + +- (void)testPlayVideoWithoutSubtitlesRequestShouldNotContainSecCaptionInfoEx { + [self checkPlayVideoWithoutSubtitlesRequestShouldNotContainSecTagWithName:kSecCaptionInfoExTag]; +} + +- (void)testPlayVideoWithoutSubtitlesRequestShouldNotContainPVSubtitleAttributes { + [self checkPlayVideoRequestWithMediaInfo:[self mediaInfoWithoutSubtitle] + shouldContainPVSubtitleAttributes:NO]; +} + +- (void)testPlayVideoWithSubtitlesWithoutMimeTypeShouldSendDefaultMimeTypeProtocolInfo { + [self checkPlayVideoRequestWithMediaInfo:[self mediaInfoWithSubtitleWithoutMimeType] + shouldContain:YES + protocolInfoWithAttributeValue:@"http-get:*:text/srt:*"]; +} + +- (void)testPlayVideoWithSubtitlesWithoutMimeTypeShouldSendDefaultTypeInSecCaptionInfo { + [self checkPlayVideoRequestWithMediaInfo:[self mediaInfoWithSubtitleWithoutMimeType] +shouldContainSecTagWithDefaultFileTypeAndName:kSecCaptionInfoTag]; +} + +- (void)testPlayVideoWithSubtitlesWithoutMimeTypeShouldSendDefaultTypeInSecCaptionInfoEx { + [self checkPlayVideoRequestWithMediaInfo:[self mediaInfoWithSubtitleWithoutMimeType] +shouldContainSecTagWithDefaultFileTypeAndName:kSecCaptionInfoExTag]; +} + +- (void)testPlayVideoWithSubtitlesWithoutMimeTypeShouldSendDefaultTypeInPVSubtitleAttribute { + [self checkPlayVideoRequestWithMediaInfo:[self mediaInfoWithSubtitleWithoutMimeType] + shouldContainPVSubtitleAttributes:YES + withFileType:@"srt"]; +} + +// TODO wrong mime type tests would not be here if we used a specialized +// MIMEType class +- (void)testPlayVideoWithSubtitlesWithWrongMimeTypeShouldSendDefaultMimeTypeProtocolInfo { + [self checkPlayVideoRequestWithMediaInfo:[self mediaInfoWithSubtitleWithWrongMimeType] + shouldContain:YES + protocolInfoWithAttributeValue:@"http-get:*:text/srt:*"]; +} + +- (void)testPlayVideoWithSubtitlesWithWrongMimeTypeShouldSendDefaultTypeInSecCaptionInfo { + [self checkPlayVideoRequestWithMediaInfo:[self mediaInfoWithSubtitleWithWrongMimeType] +shouldContainSecTagWithDefaultFileTypeAndName:kSecCaptionInfoTag]; +} + +- (void)testPlayVideoWithSubtitlesWithWrongMimeTypeShouldSendDefaultTypeInSecCaptionInfoEx { + [self checkPlayVideoRequestWithMediaInfo:[self mediaInfoWithSubtitleWithWrongMimeType] +shouldContainSecTagWithDefaultFileTypeAndName:kSecCaptionInfoExTag]; +} + +- (void)testPlayVideoWithSubtitlesWithWrongMimeTypeShouldSendDefaultTypeInPVSubtitleAttribute { + [self checkPlayVideoRequestWithMediaInfo:[self mediaInfoWithSubtitleWithWrongMimeType] + shouldContainPVSubtitleAttributes:YES + withFileType:@"srt"]; +} + +#pragma mark - Response Parsing Tests + +/// Tests that @c -getPositionWithSuccess:failure: parses the position time from +/// a sample Xbox response properly. +- (void)testGetPositionShouldParseTimeProperly_Xbox { + [self checkGetPositionShouldParseTimeProperlyWithSamplePlatform:kPlatformXbox]; +} + +/// Tests that @c -getPositionWithSuccess:failure: parses the position time from +/// a sample Sonos response properly. +- (void)testGetPositionShouldParseTimeProperly_Sonos { + [self checkGetPositionShouldParseTimeProperlyWithSamplePlatform:kPlatformSonos]; +} + +/// Tests that @c -getDurationWithSuccess:failure: parses the duration time from +/// a sample Xbox response properly. +- (void)testGetDurationShouldParseTimeProperly_Xbox { + [self checkGetDurationShouldParseTimeProperlyWithSamplePlatform:kPlatformXbox]; +} + +/// Tests that @c -getDurationWithSuccess:failure: parses the duration time from +/// a sample Sonos response properly. +- (void)testGetDurationShouldParseTimeProperly_Sonos { + [self checkGetDurationShouldParseTimeProperlyWithSamplePlatform:kPlatformSonos]; +} + +/// Tests that @c -getMediaMetaDataWithSuccess:failure: parses the metadata from +/// a sample Xbox response properly. +- (void)testGetMediaMetadataShouldParseTimeProperly_Xbox { + NSDictionary *expectedMetadata = @{@"title": @"Sintel Character Design", + @"subtitle": @"Blender Open Movie Project", + @"iconURL": @"http://ec2-54-201-108-205.us-west-2.compute.amazonaws.com/samples/media/videoIcon.jpg"}; + [self checkGetMediaMetadataShouldParseTimeProperlyWithSamplePlatform:kPlatformXbox + andExpectedMetadata:expectedMetadata]; +} + +/// Tests that @c -getMediaMetaDataWithSuccess:failure: parses the metadata from +/// a sample Sonos response properly. +- (void)testGetMediaMetadataShouldParseTimeProperly_Sonos { + NSDictionary *expectedMetadata = @{@"title": @"Sintel Trailer", + @"subtitle": @"Durian Open Movie Team"}; + [self checkGetMediaMetadataShouldParseTimeProperlyWithSamplePlatform:kPlatformSonos + andExpectedMetadata:expectedMetadata]; +} + +/// Tests that @c -getPlayStateWithSuccess:failure: parses the play state from +/// a sample Xbox response properly. +- (void)testGetPlayStateShouldParsePlayStateProperly_Xbox { + [self checkGetPlayStateShouldParsePlayStateProperlyWithSamplePlatform:kPlatformXbox]; +} + +/// Tests that @c -getPlayStateWithSuccess:failure: parses the play state from +/// a sample Sonos response properly. +- (void)testGetPlayStateShouldParsePlayStateProperly_Sonos { + [self checkGetPlayStateShouldParsePlayStateProperlyWithSamplePlatform:kPlatformSonos]; +} + +/// Tests that @c -getVolumeWithSuccess:failure: parses the volume from a sample +/// Xbox response properly. +- (void)testGetVolumeShouldParseVolumeProperly_Xbox { + [self checkGetVolumeShouldParseVolumeProperlyWithSamplePlatform:kPlatformXbox]; +} + +/// Tests that @c -getVolumeWithSuccess:failure: parses the volume from a sample +/// Sonos response properly. +- (void)testGetVolumeShouldParseVolumeProperly_Sonos { + [self checkGetVolumeShouldParseVolumeProperlyWithSamplePlatform:kPlatformSonos]; +} + +/// Tests that @c -getMuteWithSuccess:failure: parses the mute from a sample +/// Xbox response properly. +- (void)testGetMuteShouldParseMuteProperly_Xbox { + [self checkGetMuteShouldParseMuteProperlyWithSamplePlatform:kPlatformXbox]; +} + +/// Tests that @c -getMuteWithSuccess:failure: parses the mute from a sample +/// Sonos response properly. +- (void)testGetMuteShouldParseMuteProperly_Sonos { + [self checkGetMuteShouldParseMuteProperlyWithSamplePlatform:kPlatformSonos]; +} + +/// Tests that @c DLNAService parses a UPnP error from a sample Xbox response +/// properly. +- (void)testUPnPErrorShouldBeParsedProperly_Xbox { + [self checkUPnPErrorShouldBeParsedProperlyWithSamplePlatform:kPlatformXbox + andErrorDescription:@"Invalid Action"]; +} + +/// Tests that @c DLNAService parses a UPnP error from a sample Sonos response +/// properly. +- (void)testUPnPErrorShouldBeParsedProperly_Sonos { + [self checkUPnPErrorShouldBeParsedProperlyWithSamplePlatform:kPlatformSonos + andErrorDescription:nil]; +} + +#pragma mark - Service URL Construction Tests + +- (void)testUpdateControlURLsWithoutSlash { + NSDictionary *urls = @{ + kAVTransportControlURLKey: @"http://127.0.0.0:0/control/AVTransport", + kAVTransportEventURLKey: @"http://127.0.0.0:0/event/AVTransport", + kRenderingControlControlURLKey: @"http://127.0.0.0:0/control/RenderingControl", + kRenderingControlEventURLKey: @"http://127.0.0.0:0/event/RenderingControl" + }; + [self checkUpdateControlURLForDevice:@"lg_speaker" withURLs:urls]; +} + +- (void)testUpdateControlURLsWithSlash { + NSDictionary *urls = @{ + kAVTransportControlURLKey: @"http://127.0.0.0:0/MediaRenderer/AVTransport/Control", + kAVTransportEventURLKey: @"http://127.0.0.0:0/MediaRenderer/AVTransport/Event", + kRenderingControlControlURLKey: @"http://127.0.0.0:0/MediaRenderer/RenderingControl/Control", + kRenderingControlEventURLKey: @"http://127.0.0.0:0/MediaRenderer/RenderingControl/Event" + }; + [self checkUpdateControlURLForDevice:@"sonos" withURLs:urls]; +} + +-(void)testServiceSubscriptionURLsWithoutSlash { + [self checkServiceSubscriptionURLForDevice:@"lg_speaker"]; +} + +-(void)testServiceSubscriptionURLsWithSlash { + [self checkServiceSubscriptionURLForDevice:@"sonos"]; +} + +/// Tests that @c -parseMetadataDictionaryFromXMLString: returns the passed +/// album art URL if it's absolute and includes a host. +- (void)testAbsoluteAlbumArtURLShouldBeParsedAsIs { + NSString *xml = @"http://example.com/image.jpg"; + NSDictionary *metadata = [self.service parseMetadataDictionaryFromXMLString:xml]; + XCTAssertEqualObjects(metadata[@"iconURL"], @"http://example.com/image.jpg", + @"The album art URL is incorrect"); +} + +/// Tests that @c -parseMetadataDictionaryFromXMLString: prepends the service's +/// @c commandURL to an absolute album art URL if it doesn't include a host. +- (void)testAbsoluteAlbumArtURLWithoutHostShouldBeRelativeToServicesCommandURL { + id serviceDescriptionMock = OCMClassMock([ServiceDescription class]); + [OCMStub([serviceDescriptionMock commandURL]) andReturn:[NSURL URLWithString:@"http://10.0.0.1:9099/yes"]]; + self.service.serviceDescription = serviceDescriptionMock; + + NSString *xml = @"/aa?u=http%3A%2F%2Fexample.com%2Fimage.jpg&v=0"; + NSDictionary *metadata = [self.service parseMetadataDictionaryFromXMLString:xml]; + XCTAssertEqualObjects(metadata[@"iconURL"], + @"http://10.0.0.1:9099/aa?u=http%3A%2F%2Fexample.com%2Fimage.jpg&v=0", + @"The album art URL is incorrect"); +} + +/// Tests that @c -parseMetadataDictionaryFromXMLString: prepends the service's +/// @c commandURL to a relative album art URL. +- (void)testRelativeAlbumArtURLWithoutHostShouldBeRelativeToServicesCommandURL { + id serviceDescriptionMock = OCMClassMock([ServiceDescription class]); + [OCMStub([serviceDescriptionMock commandURL]) andReturn:[NSURL URLWithString:@"http://10.0.0.1:9099/yes"]]; + self.service.serviceDescription = serviceDescriptionMock; + + NSString *xml = @"aa?u=http%3A%2F%2Fexample.com%2Fimage.jpg&v=0"; + NSDictionary *metadata = [self.service parseMetadataDictionaryFromXMLString:xml]; + XCTAssertEqualObjects(metadata[@"iconURL"], + @"http://10.0.0.1:9099/aa?u=http%3A%2F%2Fexample.com%2Fimage.jpg&v=0", + @"The album art URL is incorrect"); +} + +#pragma mark - Subscription Tests + +- (void)testSubscribeVolumeShouldIgnoreMuteEvent { + // getVolume + OCMStub([self.serviceCommandDelegateMock sendCommand:OCMOCK_ANY + withPayload:OCMOCK_ANY + toURL:OCMOCK_ANY]); + + [[OCMExpect([self.serviceCommandDelegateMock sendSubscription:OCMOCK_ANY + type:ServiceSubscriptionTypeSubscribe + payload:OCMOCK_ANY + toURL:OCMOCK_ANY + withId:0]) ignoringNonObjectArgs] andDo:^(NSInvocation *invocation) { + ServiceSubscription *subscription = [invocation objectArgumentAtIndex:0]; + SuccessBlock block = subscription.successCalls[0]; + block(@{@"Event": @{@"InstanceID": @{@"Mute": @{@"channel": @"Master", @"val": @0}}}}); + }]; + + [self.service subscribeVolumeWithSuccess:nil + failure:^(NSError *error) { + XCTFail(@"%@", error); + }]; + OCMVerifyAll(self.serviceCommandDelegateMock); +} + +- (void)testSubscribeMuteShouldIgnoreVolumeEvent { + // getMute + OCMStub([self.serviceCommandDelegateMock sendCommand:OCMOCK_ANY + withPayload:OCMOCK_ANY + toURL:OCMOCK_ANY]); + + [[OCMExpect([self.serviceCommandDelegateMock sendSubscription:OCMOCK_ANY + type:ServiceSubscriptionTypeSubscribe + payload:OCMOCK_ANY + toURL:OCMOCK_ANY + withId:0]) ignoringNonObjectArgs] andDo:^(NSInvocation *invocation) { + ServiceSubscription *subscription = [invocation objectArgumentAtIndex:0]; + SuccessBlock block = subscription.successCalls[0]; + block(@{@"Event": @{@"InstanceID": @{@"Volume": @{@"channel": @"Master", @"val": @0}}}}); + }]; + + [self.service subscribeMuteWithSuccess:nil + failure:^(NSError *error) { + XCTFail(@"%@", error); + }]; + OCMVerifyAll(self.serviceCommandDelegateMock); +} + +#pragma mark - Disconnect Tests + +/// Tests that @c -disconnect shuts down the http server. +- (void)testDisconnectShouldStopHTTPServer { + // Arrange + id httpServerMock = OCMClassMock([DLNAHTTPServer class]); + XCTestExpectation *serverIsStoppedExpectation = [self expectationWithDescription:@"httpServer should be stopped"]; + [OCMExpect([httpServerMock stop]) andFulfillExpectation:serverIsStoppedExpectation]; + + // have to install a partial mock to be able to inject the httpServerMock + DLNAService *service = OCMPartialMock([DLNAService new]); + [OCMStub([service createDLNAHTTPServer]) andReturn:httpServerMock]; + + id serviceDescriptionMock = OCMClassMock([ServiceDescription class]); + [OCMStub([serviceDescriptionMock locationXML]) andReturn:@"http://127.1/"]; + + // Act + service.serviceDescription = serviceDescriptionMock; + [service disconnect]; + + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + OCMVerifyAll(httpServerMock); + }]; +} + +/// Tests that @c -disconnect unsubscribes from all subscriptions. +- (void)testDisconnectShouldUnsubscribeAllSubscriptions { + // Arrange + id httpServerMock = OCMClassMock([DLNAHTTPServer class]); + id reachabilityMock = OCMClassMock([DeviceServiceReachability class]); + + // have to install a partial mock to be able to inject the httpServerMock + // and reachabilityMock + DLNAService *service = OCMPartialMock([DLNAService new]); + [OCMStub([service createDLNAHTTPServer]) andReturn:httpServerMock]; + [OCMStub([service createDeviceServiceReachabilityWithTargetURL:OCMOCK_ANY]) andReturn:reachabilityMock]; + + id serviceDescriptionMock = OCMClassMock([ServiceDescription class]); + [OCMStub([serviceDescriptionMock locationXML]) andReturn:@"http://127.0.0.1/"]; + NSDictionary *serviceDict = @{@"serviceId": @{@"text": @"id"}, + @"eventSubURL": @{@"text": @"/sub"}}; + NSArray *serviceList = @[serviceDict]; + [OCMStub([serviceDescriptionMock serviceList]) andReturn:serviceList]; + NSURL *commandURL = [NSURL URLWithString:@"http://127.0.0.1:8080/"]; + [OCMStub([serviceDescriptionMock commandURL]) andReturn:commandURL]; + + static NSString *const kHOST = @"127.0.0.1"; + XCTestExpectation *subscribeRequestSent = [self expectationWithDescription:@"service is subscribed"]; + [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { + XCTAssertEqualObjects(request.URL.host, kHOST); + return ([request.URL.host isEqualToString:kHOST] && + [request.HTTPMethod isEqualToString:@"SUBSCRIBE"]); + } + withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) { + // NB: the expectation should be fulfilled after the + // response is handled in DLNAService, for it to + // save the SID! + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDefaultAsyncTestTimeout / 2 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + [subscribeRequestSent fulfill]; + }); + return [OHHTTPStubsResponse responseWithData:nil + statusCode:200 + headers:@{@"SID": @"42"}]; + }]; + + service.serviceDescription = serviceDescriptionMock; + [service connect]; + + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + }]; + + XCTestExpectation *unsubscribeRequestSent = [self expectationWithDescription:@"service is unsubscribed"]; + [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { + XCTAssertEqualObjects(request.URL.host, kHOST); + return ([request.URL.host isEqualToString:kHOST] && + [request.HTTPMethod isEqualToString:@"UNSUBSCRIBE"] && + [request.allHTTPHeaderFields[@"SID"] isEqualToString:@"42"]); + } + withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) { + [unsubscribeRequestSent fulfill]; + return [OHHTTPStubsResponse responseWithData:nil + statusCode:200 + headers:nil]; + }]; + + // Act + [service disconnect]; + + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + }]; +} + +#pragma mark - Helpers + +- (void)checkGetPositionShouldParseTimeProperlyWithSamplePlatform:(NSString *)platform { + // Arrange + OCMExpect([self.serviceCommandDelegateMock sendCommand:OCMOCK_NOTNIL + withPayload:OCMOCK_NOTNIL + toURL:OCMOCK_ANY]).andDo((^(NSInvocation *inv) { + [self callCommandCallbackFromInvocation:inv + andResponseFilename:[NSString stringWithFormat:@"getpositioninfo_response_%@", platform]]; + })); + + XCTestExpectation *getPositionSuccessExpectation = [self expectationWithDescription:@"The position time is parsed properly"]; + + // Act + [self.service getPositionWithSuccess:^(NSTimeInterval position) { + XCTAssertEqualWithAccuracy(position, 66.0, 0.001, + @"The position time is incorrect"); + [getPositionSuccessExpectation fulfill]; + } + failure:^(NSError *error) { + XCTFail(@"Should not be a failure: %@", error); + }]; + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + OCMVerifyAll(self.serviceCommandDelegateMock); + }]; +} + +- (void)checkGetDurationShouldParseTimeProperlyWithSamplePlatform:(NSString *)platform { + // Arrange + OCMExpect([self.serviceCommandDelegateMock sendCommand:OCMOCK_NOTNIL + withPayload:OCMOCK_NOTNIL + toURL:OCMOCK_ANY]).andDo((^(NSInvocation *inv) { + [self callCommandCallbackFromInvocation:inv + andResponseFilename:[NSString stringWithFormat:@"getpositioninfo_response_%@", platform]]; + })); + + XCTestExpectation *getDurationSuccessExpectation = [self expectationWithDescription:@"The duration is parsed properly"]; + + // Act + [self.service getDurationWithSuccess:^(NSTimeInterval position) { + XCTAssertEqualWithAccuracy(position, (8.0*60 + 52), 0.001, + @"The duration is incorrect"); + [getDurationSuccessExpectation fulfill]; + } + failure:^(NSError *error) { + XCTFail(@"Should not be a failure: %@", error); + }]; + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + OCMVerifyAll(self.serviceCommandDelegateMock); + }]; +} + +- (void)checkGetMediaMetadataShouldParseTimeProperlyWithSamplePlatform:(NSString *)platform + andExpectedMetadata:(NSDictionary *)expectedMetadata { + // Arrange + OCMExpect([self.serviceCommandDelegateMock sendCommand:OCMOCK_NOTNIL + withPayload:OCMOCK_NOTNIL + toURL:OCMOCK_ANY]).andDo((^(NSInvocation *inv) { + [self callCommandCallbackFromInvocation:inv + andResponseFilename:[NSString stringWithFormat:@"getpositioninfo_response_%@", platform]]; + })); + + XCTestExpectation *getMetadataSuccessExpectation = [self expectationWithDescription:@"The metadata is parsed properly"]; + + // Act + [self.service getMediaMetaDataWithSuccess:^(NSDictionary *metadata) { + XCTAssertEqualObjects(metadata, expectedMetadata, @"The metadata is incorrect"); + [getMetadataSuccessExpectation fulfill]; + } + failure:^(NSError *error) { + XCTFail(@"Should not be a failure: %@", error); + }]; + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + OCMVerifyAll(self.serviceCommandDelegateMock); + }]; +} + +- (void)checkGetPlayStateShouldParsePlayStateProperlyWithSamplePlatform:(NSString *)platform { + // Arrange + OCMExpect([self.serviceCommandDelegateMock sendCommand:OCMOCK_NOTNIL + withPayload:OCMOCK_NOTNIL + toURL:OCMOCK_ANY]).andDo((^(NSInvocation *inv) { + [self callCommandCallbackFromInvocation:inv + andResponseFilename:[NSString stringWithFormat:@"gettransportinfo_response_%@", platform]]; + })); + + XCTestExpectation *getPlayStateSuccessExpectation = [self expectationWithDescription:@"The play state is parsed properly"]; + + // Act + [self.service getPlayStateWithSuccess:^(MediaControlPlayState playState) { + XCTAssertEqual(playState, MediaControlPlayStatePlaying, + @"The play state is incorrect"); + [getPlayStateSuccessExpectation fulfill]; + } + failure:^(NSError *error) { + XCTFail(@"Should not be a failure: %@", error); + }]; + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + OCMVerifyAll(self.serviceCommandDelegateMock); + }]; +} + +- (void)checkGetVolumeShouldParseVolumeProperlyWithSamplePlatform:(NSString *)platform { + // Arrange + OCMExpect([self.serviceCommandDelegateMock sendCommand:OCMOCK_NOTNIL + withPayload:OCMOCK_NOTNIL + toURL:OCMOCK_ANY]).andDo((^(NSInvocation *inv) { + [self callCommandCallbackFromInvocation:inv + andResponseFilename:[NSString stringWithFormat:@"getvolume_response_%@", platform]]; + })); + + XCTestExpectation *getVolumeSuccessExpectation = [self expectationWithDescription:@"The volume is parsed properly"]; + + // Act + [self.service getVolumeWithSuccess:^(float volume) { + XCTAssertEqualWithAccuracy(volume, 0.14f, 0.0001, @"The volume is incorrect"); + [getVolumeSuccessExpectation fulfill]; + } + failure:^(NSError *error) { + XCTFail(@"Should not be a failure: %@", error); + }]; + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + OCMVerifyAll(self.serviceCommandDelegateMock); + }]; +} + +- (void)checkGetMuteShouldParseMuteProperlyWithSamplePlatform:(NSString *)platform { + // Arrange + OCMExpect([self.serviceCommandDelegateMock sendCommand:OCMOCK_NOTNIL + withPayload:OCMOCK_NOTNIL + toURL:OCMOCK_ANY]).andDo((^(NSInvocation *inv) { + [self callCommandCallbackFromInvocation:inv + andResponseFilename:[NSString stringWithFormat:@"getmute_response_%@", platform]]; + })); + + XCTestExpectation *getMuteSuccessExpectation = [self expectationWithDescription:@"The mute is parsed properly"]; + + // Act + [self.service getMuteWithSuccess:^(BOOL mute) { + XCTAssertTrue(mute, @"The mute value is incorrect"); + [getMuteSuccessExpectation fulfill]; + } + failure:^(NSError *error) { + XCTFail(@"Should not be a failure: %@", error); + }]; + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + OCMVerifyAll(self.serviceCommandDelegateMock); + }]; +} + +- (void)checkUPnPErrorShouldBeParsedProperlyWithSamplePlatform:(NSString *)platform + andErrorDescription:(NSString *)errorDescription { + // Arrange + self.service.serviceCommandDelegate = nil; + [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { + return YES; + } + withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) { + NSString *filename = [NSString stringWithFormat:@"upnperror_response_%@.xml", platform]; + return [OHHTTPStubsResponse responseWithFileAtPath:OHPathForFileInBundle(filename, nil) + statusCode:500 + headers:nil]; + }]; + + XCTestExpectation *failExpectation = [self expectationWithDescription:@"The failure: block should be called"]; + + // Act + [self.service getMuteWithSuccess:^(BOOL mute) { + XCTFail(@"Should not succeed here"); + } + failure:^(NSError *error) { + XCTAssertEqualObjects(error.domain, ConnectErrorDomain, @"The error domain is incorrect"); + XCTAssertEqual(error.code, ConnectStatusCodeTvError, @"The error code is incorrect"); + if (errorDescription) { + XCTAssertNotEqual(NSNotFound, + [error.localizedDescription rangeOfString:errorDescription].location, + @"The error description is incorrect"); + } else { + XCTAssertGreaterThan(error.localizedDescription.length, + 0, @"The error description must not be empty"); + } + [failExpectation fulfill]; + }]; + + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + }]; +} + +- (void)callCommandCallbackFromInvocation:(NSInvocation *)invocation + andResponseFilename:(NSString *)filename { + __unsafe_unretained ServiceCommand *tmp; + [invocation getArgument:&tmp atIndex:2]; + ServiceCommand *command = tmp; + XCTAssertNotNil(command, @"Couldn't get the command argument"); + + NSData *xmlData = [NSData dataWithContentsOfFile: + OHPathForFileInBundle([filename stringByAppendingPathExtension:@"xml"], nil)]; + XCTAssertNotNil(xmlData, @"Response data is unavailable"); + + NSError *error; + NSDictionary *dict = [CTXMLReader dictionaryForXMLData:xmlData + error:&error]; + XCTAssertNil(error, @"XML parsing error: %@", error); + + dispatch_async(dispatch_get_main_queue(), ^{ + command.callbackComplete(dict); + }); +} + +- (void)setupSendCommandTestWithName:(NSString *)commandName + namespace:(NSString *)namespace + actionBlock:(void (^)())actionBlock + andVerificationBlock:(void (^)(NSDictionary *request))checkBlock { + // Arrange + XCTestExpectation *commandIsSent = [self expectationWithDescription: + [NSString stringWithFormat:@"%@ command is sent", commandName]]; + + [OCMExpect([self.serviceCommandDelegateMock sendCommand:OCMOCK_NOTNIL + withPayload:OCMOCK_NOTNIL + toURL:OCMOCK_ANY]) andDo:^(NSInvocation *inv) { + NSDictionary *payload = [inv objectArgumentAtIndex:1]; + NSString *xmlString = payload[kDataFieldName]; + XCTAssertNotNil(xmlString, @"XML request not found"); + + NSError *error = nil; + NSDictionary *dict = [CTXMLReader dictionaryForXMLString:xmlString + error:&error]; + XCTAssertNil(error, @"XML parsing error"); + XCTAssertNotNil(dict, @"Couldn't parse XML"); + + NSDictionary *envelope = [dict objectForKeyEndingWithString:@":Envelope"]; + XCTAssertNotNil(envelope, @"Envelope tag must be present"); + XCTAssertEqualObjects(envelope[@"xmlns:u"], namespace, @"Namespace is incorrect"); + XCTAssertEqualObjects(envelope[@"s:encodingStyle"], @"http://schemas.xmlsoap.org/soap/encoding/"); + NSDictionary *body = [envelope objectForKeyEndingWithString:@":Body"]; + XCTAssertNotNil(body, @"Body tag must be present"); + NSDictionary *request = [body objectForKeyEndingWithString:[@":" stringByAppendingString:commandName]]; + XCTAssertNotNil(request, @"%@ tag must be present", commandName); + + XCTAssertNotNil(request[@"InstanceID"], @"InstanceID must be present"); + + // any extra verification required? + if (checkBlock) { + checkBlock(request); + } + + [commandIsSent fulfill]; + }]; + + // Act + actionBlock(); + + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + OCMVerifyAll(self.serviceCommandDelegateMock); + }]; +} + +- (void)checkPlayMediaShouldCreateProperSetAVTransportURIXMLWithTitle:(NSString *)sampleTitle + description:(NSString *)sampleDescription + url:(NSString *)sampleURL + andAlbumArtURL:(NSString *)sampleAlbumArtURL { + NSString *sampleMimeType = @"audio/ogg"; + + [self setupSendCommandTestWithName:@"SetAVTransportURI" + namespace:kAVTransportNamespace + actionBlock:^{ + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:[NSURL URLWithString:sampleURL] + mimeType:sampleMimeType]; + mediaInfo.title = sampleTitle; + mediaInfo.description = sampleDescription; + mediaInfo.images = @[[[ImageInfo alloc] initWithURL:[NSURL URLWithString:sampleAlbumArtURL] + type:ImageTypeAlbumArt]]; + + [self.service playMediaWithMediaInfo:mediaInfo + shouldLoop:NO + success:^(MediaLaunchObject *mediaLanchObject) { + XCTFail(@"success?"); + } failure:^(NSError *error) { + XCTFail(@"fail? %@", error); + }]; + } andVerificationBlock:^(NSDictionary *request) { + XCTAssertEqualObjects([request valueForKeyPath:@"CurrentURI.text"], sampleURL, @"CurrentURI must match"); + + NSString *metadataString = [request valueForKeyPath:@"CurrentURIMetaData.text"]; + XCTAssertNotNil(metadataString, @"CurrentURIMetaData must be present"); + + NSError *error = nil; + NSDictionary *metadata = [CTXMLReader dictionaryForXMLString:metadataString + error:&error]; + XCTAssertNil(error, @"Metadata XML parsing error"); + XCTAssertNotNil(metadata, @"Couldn't parse metadata XML"); + + NSDictionary *didl = metadata[@"DIDL-Lite"]; + XCTAssertNotNil(didl, @"DIDL-Lite tag must be present"); + NSDictionary *item = didl[@"item"]; + XCTAssertNotNil(item, @"item tag must be present"); + + NSString *title = [item objectForKeyEndingWithString:@":title"][@"text"]; + XCTAssertEqualObjects(title, sampleTitle, @"Title must match"); + + NSString *description = [item objectForKeyEndingWithString:@":description"][@"text"]; + XCTAssertEqualObjects(description, sampleDescription, @"Description must match"); + + NSDictionary *res = item[@"res"]; + XCTAssertEqualObjects(res[@"text"], sampleURL, @"res URL must match"); + XCTAssertNotEqual([res[@"protocolInfo"] rangeOfString:sampleMimeType].location, NSNotFound, @"mimeType must be in protocolInfo"); + + NSString *albumArtURI = [item objectForKeyEndingWithString:@":albumArtURI"][@"text"]; + XCTAssertEqualObjects(albumArtURI, sampleAlbumArtURL, @"albumArtURI must match"); + + NSString *itemClass = [item objectForKeyEndingWithString:@":class"][@"text"]; + XCTAssertEqualObjects(itemClass, @"object.item.audioItem", @"class must be audioItem"); + }]; +} + +- (void)checkDisplayImageShouldCreateProperSetAVTransportURIXMLWithTitle:(NSString *)sampleTitle + url:(NSString *)sampleURL { + NSString *sampleMimeType = @"image/png"; + + [self setupSendCommandTestWithName:@"SetAVTransportURI" + namespace:kAVTransportNamespace + actionBlock:^{ + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:[NSURL URLWithString:sampleURL] + mimeType:sampleMimeType]; + mediaInfo.title = sampleTitle; + + [self.service displayImageWithMediaInfo:mediaInfo + success:^(MediaLaunchObject *mediaLanchObject) { + XCTFail(@"success?"); + } failure:^(NSError *error) { + XCTFail(@"fail? %@", error); + }]; + } andVerificationBlock:^(NSDictionary *request) { + XCTAssertEqualObjects([request valueForKeyPath:@"CurrentURI.text"], sampleURL, @"CurrentURI must match"); + + NSString *metadataString = [request valueForKeyPath:@"CurrentURIMetaData.text"]; + XCTAssertNotNil(metadataString, @"CurrentURIMetaData must be present"); + + NSError *error = nil; + NSDictionary *metadata = [CTXMLReader dictionaryForXMLString:metadataString + error:&error]; + XCTAssertNil(error, @"Metadata XML parsing error"); + XCTAssertNotNil(metadata, @"Couldn't parse metadata XML"); + + NSDictionary *didl = metadata[@"DIDL-Lite"]; + XCTAssertNotNil(didl, @"DIDL-Lite tag must be present"); + NSDictionary *item = didl[@"item"]; + XCTAssertNotNil(item, @"item tag must be present"); + + NSString *title = [item objectForKeyEndingWithString:@":title"][@"text"]; + XCTAssertEqualObjects(title, sampleTitle, @"Title must match"); + + NSDictionary *res = item[@"res"]; + XCTAssertEqualObjects(res[@"text"], sampleURL, @"res URL must match"); + XCTAssertNotEqual([res[@"protocolInfo"] rangeOfString:sampleMimeType].location, NSNotFound, @"mimeType must be in protocolInfo"); + + NSString *itemClass = [item objectForKeyEndingWithString:@":class"][@"text"]; + XCTAssertEqualObjects(itemClass, @"object.item.imageItem", @"class must be imageItem"); + }]; +} + +- (void)checkUpdateControlURLForDevice:(NSString *)device + withURLs:(NSDictionary *)urls{ + ServiceDescription *serviceDescription = [self serviceDescriptionForDevice:device]; + [self.service setServiceDescription:serviceDescription]; + + XCTAssertEqualObjects(urls[kAVTransportControlURLKey], self.service.avTransportControlURL.absoluteString); + XCTAssertEqualObjects(urls[kAVTransportEventURLKey], self.service.avTransportEventURL.absoluteString); + XCTAssertEqualObjects(urls[kRenderingControlControlURLKey], self.service.renderingControlControlURL + .absoluteString); + XCTAssertEqualObjects(urls[kRenderingControlEventURLKey], self.service.renderingControlEventURL.absoluteString); +} + +- (void)checkServiceSubscriptionURLForDevice:(NSString *)device{ + ServiceDescription *serviceDescription = [self serviceDescriptionForDevice:device]; + [self.service setServiceDescription:serviceDescription]; + + [serviceDescription.serviceList enumerateObjectsUsingBlock:^(id service, NSUInteger idx, BOOL *stop) { + NSString *eventPath = service[@"eventSubURL"][@"text"]; + NSURL *eventSubURL = [self.service serviceURLForPath:eventPath]; + [self assertURLIsValid:eventSubURL]; + }]; +} + +- (ServiceDescription *)serviceDescriptionForDevice:(NSString *)device { + NSString *filename = [NSString stringWithFormat:@"ssdp_device_description_%@", device]; + NSData *xmlData = [NSData dataWithContentsOfFile: + OHPathForFileInBundle([filename stringByAppendingPathExtension:@"xml"], nil)]; + ServiceDescription *serviceDescription = [ServiceDescription new]; + serviceDescription.locationXML = @""; + serviceDescription.commandURL = [NSURL URLWithString:@"http://127.0.0.0:0"]; + NSError *error; + NSDictionary *dict = [CTXMLReader dictionaryForXMLData:xmlData error:&error]; + SSDPDiscoveryProvider *ssdp = [SSDPDiscoveryProvider new]; + serviceDescription.serviceList = [ssdp serviceListForDevice:[dict valueForKeyPath:@"root.device"]]; + + return serviceDescription; +} + +- (void)assertURLIsValid:(NSURL *)url { + XCTAssertNotNil(url); + XCTAssertNotNil(url.scheme); + XCTAssertNotNil(url.host); + XCTAssertNotNil(url.port); + XCTAssertNotNil(url.path); +} + +#pragma mark - Subtitle Helpers + +- (void)checkPlayVideoWithSubtitles:(MediaInfo *)mediaInfo + DIDLRequestShouldPassTest:(void (^)(NSDictionary *didl))testBlock { + [self setupSendCommandTestWithName:@"SetAVTransportURI" + namespace:kAVTransportNamespace + actionBlock:^{ + [self.service playMediaWithMediaInfo:mediaInfo + shouldLoop:NO + success:self.failSuccessBlock + failure:self.failFailureBlock]; + } + andVerificationBlock:^(NSDictionary *request) { + NSString *metadataString = [request valueForKeyPath:@"CurrentURIMetaData.text"]; + NSDictionary *metadata = [CTXMLReader dictionaryForXMLString:metadataString + error:nil]; + testBlock(metadata[@"DIDL-Lite"]); + }]; +} + +- (void)checkPlayVideoWithSubtitlesRequestShouldContainProtocolInfoWithAttributeValue:(NSString *)attributeValue { + [self checkPlayVideoRequestWithMediaInfo:[self mediaInfoWithSubtitle] + shouldContain:YES + protocolInfoWithAttributeValue:attributeValue]; +} + +- (void)checkPlayVideoWithoutSubtitlesRequestShouldNotContainProtocolInfoWithAttributeValue:(NSString *)attributeValue { + [self checkPlayVideoRequestWithMediaInfo:[self mediaInfoWithoutSubtitle] + shouldContain:NO + protocolInfoWithAttributeValue:attributeValue]; +} + +- (void)checkPlayVideoRequestWithMediaInfo:(MediaInfo *)mediaInfo + shouldContain:(BOOL)shouldContain + protocolInfoWithAttributeValue:(NSString *)attributeValue { + [self checkPlayVideoWithSubtitles:mediaInfo + DIDLRequestShouldPassTest:^(NSDictionary *didl) { + // TODO switch from the current XMLReader? + // we have to parse tags as either a dictionary or an array, + // depending on the number of tags with the same name in XML + + if (shouldContain) { + // in this case we're expecting to have 2+ res tags + NSArray *resources = [didl valueForKeyPath:@"item.res"]; + NSPredicate *attributePredicate = [NSPredicate predicateWithFormat:@"protocolInfo == %@", + attributeValue]; + NSArray *filteredResources = [resources filteredArrayUsingPredicate:attributePredicate]; + + XCTAssertEqual(filteredResources.count, 1); + XCTAssertEqualObjects(filteredResources[0][@"text"], + mediaInfo.subtitleInfo.url.absoluteString); + } else { + // in this case we're expecting 1 res tag for media file only + NSDictionary *resource = [didl valueForKeyPath:@"item.res"]; + XCTAssertNotEqualObjects(resource[@"protocolInfo"], + attributeValue); + } + }]; +} + +- (void)checkPlayVideoWithSubtitlesRequestShouldContainSecTagWithName:(NSString *)tagName { + [self checkPlayVideoRequestWithMediaInfo:[self mediaInfoWithSubtitle] + shouldContain:YES + secTagWithName:tagName]; +} + +- (void)checkPlayVideoWithoutSubtitlesRequestShouldNotContainSecTagWithName:(NSString *)tagName { + [self checkPlayVideoRequestWithMediaInfo:[self mediaInfoWithoutSubtitle] + shouldContain:NO + secTagWithName:tagName]; +} + +- (void)checkPlayVideoRequestWithMediaInfo:(MediaInfo *)mediaInfo + shouldContain:(BOOL)shouldContain + secTagWithName:(NSString *)tagName { + [self checkPlayVideoRequestWithMediaInfo:mediaInfo + shouldContain:shouldContain + secTagWithName:tagName + andFileType:@"srt"]; +} + +- (void)checkPlayVideoRequestWithMediaInfo:(MediaInfo *)mediaInfo + shouldContain:(BOOL)shouldContain + secTagWithName:(NSString *)tagName + andFileType:(NSString *)fileType { + [self checkPlayVideoWithSubtitles:mediaInfo + DIDLRequestShouldPassTest:^(NSDictionary *didl) { + NSDictionary *captionInfo = didl[@"item"][tagName]; + + if (shouldContain) { + XCTAssertEqualObjects(didl[@"xmlns:sec"], + @"http://www.sec.co.kr/"); + + XCTAssertEqualObjects(captionInfo[@"text"], + mediaInfo.subtitleInfo.url.absoluteString); + XCTAssertEqualObjects(captionInfo[@"sec:type"], fileType); + } else { + // NOTE: we don't check the "xmlns:sec" presence/absence, + // because it's not very important here + XCTAssertNil(captionInfo); + } + }]; +} + +- (void)checkPlayVideoRequestWithMediaInfo:(MediaInfo *)mediaInfo + shouldContainPVSubtitleAttributes:(BOOL)shouldContain { + [self checkPlayVideoRequestWithMediaInfo:mediaInfo + shouldContainPVSubtitleAttributes:shouldContain + withFileType:@"srt"]; +} + +- (void)checkPlayVideoRequestWithMediaInfo:(MediaInfo *)mediaInfo + shouldContainPVSubtitleAttributes:(BOOL)shouldContain + withFileType:(NSString *)fileType { + [self checkPlayVideoWithSubtitles:mediaInfo + DIDLRequestShouldPassTest:^(NSDictionary *didl) { + id resources = [didl valueForKeyPath:@"item.res"]; + if (shouldContain) { + NSPredicate *mimeTypePredicate = [NSPredicate predicateWithFormat:@"protocolInfo CONTAINS %@", + mediaInfo.mimeType]; + NSArray *mediaResource = [resources filteredArrayUsingPredicate:mimeTypePredicate]; + XCTAssertEqual(mediaResource.count, 1); + + NSDictionary *res = [mediaResource firstObject]; + XCTAssertEqualObjects(res[@"xmlns:pv"], @"http://www.pv.com/pvns/"); + XCTAssertEqualObjects(res[@"pv:subtitleFileUri"], + mediaInfo.subtitleInfo.url.absoluteString); + XCTAssertEqualObjects(res[@"pv:subtitleFileType"], fileType); + } else { + NSDictionary *res = resources; + XCTAssertNil(res[@"pv:subtitleFileUri"]); + XCTAssertNil(res[@"pv:subtitleFileType"]); + } + }]; +} + +- (void)checkPlayVideoRequestWithMediaInfo:(MediaInfo *)mediaInfo +shouldContainSecTagWithDefaultFileTypeAndName:(NSString *)name { + [self checkPlayVideoRequestWithMediaInfo:mediaInfo + shouldContain:YES + secTagWithName:name + andFileType:@"srt"]; +} + +- (MediaInfo *)mediaInfoWithSubtitle { + NSURL *subtitleURL = [NSURL URLWithString:@"http://example.com/"]; + MediaInfo *mediaInfo = [self mediaInfoWithoutSubtitle]; + SubtitleInfo *info = [SubtitleInfo infoWithURL:subtitleURL + andBlock:^(SubtitleInfoBuilder *builder) { + builder.mimeType = @"text/srt"; + }]; + mediaInfo.subtitleInfo = info; + + return mediaInfo; +} + +- (MediaInfo *)mediaInfoWithoutSubtitle { + NSString *sampleURL = kDefaultURL; + NSString *sampleMimeType = @"audio/ogg"; + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:[NSURL URLWithString:sampleURL] + mimeType:sampleMimeType]; + + return mediaInfo; +} + +- (MediaInfo *)mediaInfoWithSubtitleWithWrongMimeType { + MediaInfo *mediaInfo = [self mediaInfoWithoutSubtitle]; + mediaInfo.subtitleInfo = [SubtitleInfo infoWithURL:[NSURL URLWithString:@"http://example.com/"] + andBlock:^(SubtitleInfoBuilder *builder) { + builder.mimeType = @"wrong!"; + }]; + return mediaInfo; +} + +- (MediaInfo *)mediaInfoWithSubtitleWithoutMimeType { + MediaInfo *mediaInfo = [self mediaInfoWithoutSubtitle]; + mediaInfo.subtitleInfo = [SubtitleInfo infoWithURL:[NSURL URLWithString:@"http://example.com/"]]; + return mediaInfo; +} + +@end diff --git a/core/ConnectSDKTests/Services/Helpers/AirPlayServiceHTTPKeepAliveTests.m b/core/ConnectSDKTests/Services/Helpers/AirPlayServiceHTTPKeepAliveTests.m new file mode 100644 index 00000000..a919401c --- /dev/null +++ b/core/ConnectSDKTests/Services/Helpers/AirPlayServiceHTTPKeepAliveTests.m @@ -0,0 +1,156 @@ +// +// AirPlayServiceHTTPKeepAliveTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 12/17/14. +// Copyright (c) 2014 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + + +#import "AirPlayServiceHTTPKeepAlive.h" +#import "ServiceCommandDelegate.h" + +static const CGFloat kDefaultKeepAliveInterval = 0.1f; + + +/// Tests for the @c AirPlayServiceHTTPKeepAlive class. +@interface AirPlayServiceHTTPKeepAliveTests : XCTestCase + +@property (nonatomic, strong) AirPlayServiceHTTPKeepAlive *keepAlive; +@property (nonatomic, strong) id commandDelegateMock; + +@end + +@implementation AirPlayServiceHTTPKeepAliveTests + +#pragma mark - Setup + +- (void)setUp { + [super setUp]; + + self.commandDelegateMock = OCMProtocolMock(@protocol(ServiceCommandDelegate)); + self.keepAlive = [[AirPlayServiceHTTPKeepAlive alloc] initWithInterval:kDefaultKeepAliveInterval + andCommandDelegate:self.commandDelegateMock]; + self.keepAlive.commandURL = [NSURL URLWithString:@"http://example.com/"]; +} + +- (void)tearDown { + self.keepAlive = nil; + self.commandDelegateMock = nil; + + [super tearDown]; +} + +#pragma mark - Keep-Alive timer tests + +/// Tests that a newly created keep-alive object should not start automatically. +- (void)testCreatedKeepAliveShouldNotBeStarted { + // Arrange + [[self.commandDelegateMock reject] sendCommand:OCMOCK_ANY + withPayload:OCMOCK_ANY + toURL:OCMOCK_ANY]; + AirPlayServiceHTTPKeepAlive *keepAlive = [[AirPlayServiceHTTPKeepAlive alloc] initWithInterval:kDefaultKeepAliveInterval + andCommandDelegate:self.commandDelegateMock]; + keepAlive.commandURL = [NSURL URLWithString:@"http://example.com/"]; + + // Act + [self runRunLoopForInterval:kDefaultAsyncTestTimeout]; + + // Assert + OCMVerifyAll(self.commandDelegateMock); +} + +/// Tests that -startTimer makes the object send keep-alive commands. +- (void)testStartTimerShouldSendRequest { + // Arrange + XCTestExpectation *sendKeepAliveExpectation = [self expectationWithDescription:@"Keep-alive is sent"]; + OCMStub([self.commandDelegateMock sendCommand:[OCMArg isNotNil] + withPayload:OCMOCK_ANY + toURL:[OCMArg isNotNil]]).andDo(^(NSInvocation *_) { + [self.keepAlive stopTimer]; + [sendKeepAliveExpectation fulfill]; + }); + + // Act + [self.keepAlive startTimer]; + + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + OCMVerifyAll(self.commandDelegateMock); + }]; +} + +/// Tests that -startTimer makes the object send keep-alive requests perodically. +- (void)testStartTimerShouldSendRequests { + // Arrange + __block NSUInteger invocationCount = 0; + OCMStub([self.commandDelegateMock sendCommand:[OCMArg isNotNil] + withPayload:OCMOCK_ANY + toURL:[OCMArg isNotNil]]).andDo(^(NSInvocation *_) { + ++invocationCount; + }); + + // Act + [self.keepAlive startTimer]; + + // Assert + [self runRunLoopForInterval:kDefaultAsyncTestTimeout]; + [self.keepAlive stopTimer]; + + const NSUInteger expectedInvocationCount = (NSUInteger)ceil(kDefaultAsyncTestTimeout / kDefaultKeepAliveInterval); + XCTAssertGreaterThanOrEqual(invocationCount, + expectedInvocationCount - 1, + @"The request wasn't sent the expected number of times"); +} + +/// Tests that -stopTimer after starting it stops the keep-alive requests. +- (void)testStopTimerShouldStopSendingRequests { + // Arrange + id commandDelegateMock2 = OCMProtocolMock(@protocol(ServiceCommandDelegate)); + [[commandDelegateMock2 reject] sendCommand:OCMOCK_ANY + withPayload:OCMOCK_ANY + toURL:OCMOCK_ANY]; + + OCMExpect([self.commandDelegateMock sendCommand:[OCMArg isNotNil] + withPayload:OCMOCK_ANY + toURL:[OCMArg isNotNil]]).andDo(^(NSInvocation *_) { + [self.keepAlive stopTimer]; + // replace the delegate with a fresh mock + self.keepAlive.commandDelegate = commandDelegateMock2; + }); + + // Act + [self.keepAlive startTimer]; + + // Assert + [self runRunLoopForInterval:kDefaultAsyncTestTimeout]; + OCMVerifyAll(self.commandDelegateMock); + OCMVerifyAll(commandDelegateMock2); +} + +#pragma mark - Helpers + +- (void)runRunLoopForInterval:(CGFloat)interval { + NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:interval]; + while ([timeoutDate timeIntervalSinceNow] > 0) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:timeoutDate]; + } +} + +@end diff --git a/core/ConnectSDKTests/Services/Helpers/AirPlayServiceHTTPTests.m b/core/ConnectSDKTests/Services/Helpers/AirPlayServiceHTTPTests.m new file mode 100644 index 00000000..72be9779 --- /dev/null +++ b/core/ConnectSDKTests/Services/Helpers/AirPlayServiceHTTPTests.m @@ -0,0 +1,200 @@ +// +// AirPlayServiceHTTPTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 4/21/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "AirPlayService.h" +#import "AirPlayServiceHTTP_Private.h" + +#import "NSInvocation+ObjectGetter.h" +#import "OCMStubRecorder+XCTestExpectation.h" +#import "XCTestCase+Common.h" + +/// Tests for the @c AirPlayServiceHTTP class. +@interface AirPlayServiceHTTPTests : XCTestCase + +@property (strong) id /*AirPlayService **/ serviceMock; +@property (strong) AirPlayServiceHTTP *serviceHTTP; +@property (strong) id /**/ serviceCommandDelegateMock; + +@end + +@implementation AirPlayServiceHTTPTests + +#pragma mark - Setup + +- (void)setUp { + [super setUp]; + + self.serviceMock = OCMClassMock([AirPlayService class]); + self.serviceHTTP = [[AirPlayServiceHTTP alloc] + initWithAirPlayService:self.serviceMock]; + + self.serviceCommandDelegateMock = OCMStrictProtocolMock(@protocol(ServiceCommandDelegate)); + self.serviceHTTP.serviceCommandDelegate = self.serviceCommandDelegateMock; +} + +- (void)tearDown { + self.serviceCommandDelegateMock = nil; + self.serviceHTTP = nil; + self.serviceMock = nil; + + [super tearDown]; +} + +#pragma mark - Request Tests + +- (void)testDisplayImageShouldSendPUTPhotoRequest { + id serviceDescriptionMock = OCMClassMock([ServiceDescription class]); + [OCMStub([serviceDescriptionMock commandURL]) andReturn: + [NSURL URLWithString:@"http://10.0.0.1:9099/"]]; + [OCMStub([self.serviceMock serviceDescription]) andReturn:serviceDescriptionMock]; + + XCTestExpectation *commandIsSent = [self expectationWithDescription: + @"Proper request should be sent"]; + [OCMExpect([self.serviceCommandDelegateMock sendCommand: + [OCMArg checkWithBlock:^BOOL(ServiceCommand *command) { + return [@"PUT" isEqualToString:command.HTTPMethod]; + }] + withPayload:OCMOCK_NOTNIL + toURL: + [OCMArg checkWithBlock:^BOOL(NSURL *url) { + return [@"/photo" isEqualToString:url.path]; + }]]) andFulfillExpectation:commandIsSent]; + + NSURL *url = [[NSBundle bundleForClass:self.class] URLForResource:@"the-san-francisco-peaks-of-flagstaff-718x544" + withExtension:@"jpg"]; + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:url + mimeType:@"image/jpg"]; + [self.serviceHTTP displayImageWithMediaInfo:mediaInfo + success:nil + failure:nil]; + + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout handler:nil]; + OCMVerifyAll(self.serviceCommandDelegateMock); +} + +#pragma mark - getPlayState Tests + +/// Tests that @c -getPlayStateWithSuccess:failure: properly infers the Paused +/// play state from a sample playback-info response. +- (void)testGetPlayStateShouldReturnPausedWhenRateIsZero { + [self checkGetPlayStateShouldReturnPlayState:MediaControlPlayStatePaused + forMockResponseInFile:@"airplay_playbackinfo_paused"]; +} + +/// Tests that @c -getPlayStateWithSuccess:failure: properly infers the Playing +/// play state from a sample playback-info response. +- (void)testGetPlayStateShouldReturnPlayingWhenRateIsOne { + [self checkGetPlayStateShouldReturnPlayState:MediaControlPlayStatePlaying + forMockResponseInFile:@"airplay_playbackinfo_playing"]; +} + +/// Tests that @c -getPlayStateWithSuccess:failure: properly infers the Playing +/// play state from a sample playback-info response. +- (void)testGetPlayStateShouldReturnPlayingWhenRateIsTwo { + [self checkGetPlayStateShouldReturnPlayState:MediaControlPlayStatePlaying + forMockResponseInFile:@"airplay_playbackinfo_ff"]; +} + +/// Tests that @c -getPlayStateWithSuccess:failure: properly infers the Playing +/// play state from a sample playback-info response. +- (void)testGetPlayStateShouldReturnPlayingWhenRateIsMinusTwo { + [self checkGetPlayStateShouldReturnPlayState:MediaControlPlayStatePlaying + forMockResponseInFile:@"airplay_playbackinfo_rewind"]; +} + +/// Tests that @c -getPlayStateWithSuccess:failure: properly infers the Finished +/// play state from a sample playback-info response. +- (void)testGetPlayStateShouldReturnFinishedWhenRateIsMissing { + [self checkGetPlayStateShouldReturnPlayState:MediaControlPlayStateFinished + forMockResponseInFile:@"airplay_playbackinfo_finished"]; +} + +/// Tests that @c -getPlayStateWithSuccess:failure: infers the Unknown +/// play state from an empty playback-info response. +- (void)testGetPlayStateShouldReturnUnknownWhenResponseIsEmpty { + [self checkGetPlayStateShouldReturnPlayState:MediaControlPlayStateUnknown + forMockResponse:[NSDictionary dictionary]]; +} + +#pragma mark - Unsupported Methods Tests + +- (void)testGetMediaMetadataShouldReturnNotSupportedError { + [self checkOperationShouldReturnNotSupportedErrorUsingBlock: + ^(SuccessBlock successVerifier, FailureBlock failureVerifier) { + [self.serviceHTTP getMediaMetaDataWithSuccess:successVerifier + failure:failureVerifier]; + }]; +} + +#pragma mark - Helpers + +- (void)checkGetPlayStateShouldReturnPlayState:(MediaControlPlayState)expectedPlayState + forMockResponseInFile:(NSString *)responseFilename { + NSString *responseFile = [[NSBundle bundleForClass:self.class] pathForResource:responseFilename + ofType:@"json"]; + NSData *responseData = [NSData dataWithContentsOfFile:responseFile]; + NSError *error; + NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseData + options:0 + error:&error]; + XCTAssertNil(error, @"Couldn't read response"); + + [self checkGetPlayStateShouldReturnPlayState:expectedPlayState + forMockResponse:response]; +} + +- (void)checkGetPlayStateShouldReturnPlayState:(MediaControlPlayState)expectedPlayState + forMockResponse:(NSDictionary *)response { + // Arrange + [OCMExpect([self.serviceCommandDelegateMock sendCommand:OCMOCK_NOTNIL + withPayload:OCMOCK_ANY + toURL:OCMOCK_ANY]) + andDo:^(NSInvocation *invocation) { + ServiceCommand *command = [invocation objectArgumentAtIndex:0]; + XCTAssertNotNil(command, @"Couldn't get the command argument"); + + dispatch_async(dispatch_get_main_queue(), ^{ + command.callbackComplete(response); + }); + }]; + + XCTestExpectation *didReceivePlayState = [self expectationWithDescription: + @"received playState"]; + + // Act + [self.serviceHTTP getPlayStateWithSuccess:^(MediaControlPlayState playState) { + XCTAssertEqual(playState, expectedPlayState, + @"playState is incorrect"); + + [didReceivePlayState fulfill]; + } + failure:^(NSError *error) { + XCTFail(@"Failure %@", error); + }]; + + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + }]; + OCMVerifyAll(self.serviceCommandDelegateMock); +} + +@end diff --git a/core/ConnectSDKTests/Services/Helpers/DLNAHTTPServerTests.m b/core/ConnectSDKTests/Services/Helpers/DLNAHTTPServerTests.m new file mode 100644 index 00000000..746d9bc6 --- /dev/null +++ b/core/ConnectSDKTests/Services/Helpers/DLNAHTTPServerTests.m @@ -0,0 +1,134 @@ +// +// DLNAHTTPServerTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 1/14/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DLNAHTTPServer_Private.h" +#import "GCDWebServerDataRequest.h" + +@interface EventInfo : NSObject + +@property (nonatomic, strong) NSString *url; +@property (nonatomic, strong) NSString *path; +@property (nonatomic, strong) NSDictionary *query; + +- (instancetype)initWithURL:(NSString *)url path:(NSString *)path andQuery:(NSDictionary *)query; + +@end + +@implementation EventInfo + +- (instancetype)initWithURL:(NSString *)url path:(NSString *)path andQuery:(NSDictionary *)query { + if (self = [super init]) { + _url = url; + _path = path; + _query = query; + } + return self; +} + +@end + + +/// Tests for the @c DLNAHTTPServer class. +@interface DLNAHTTPServerTests : XCTestCase + +@end + +@implementation DLNAHTTPServerTests + +#pragma mark - Subscription Tests + +/// Tests that the RenderingControl notification will only trigger the +/// RenderingControl success callback for sample Sonos event URLs. +- (void)testRenderingControlNotificationShouldTriggerRenderingControlSubscriptionOnly_Sonos { + EventInfo *const kRenderingControlEventInfo = [[EventInfo alloc] initWithURL:@"/MediaRenderer/RenderingControl/Event" + path:@"/MediaRenderer/RenderingControl/Event" + andQuery:@{}]; + EventInfo *const kAVTransportEventInfo = [[EventInfo alloc] initWithURL:@"/MediaRenderer/AVTransport/Event" + path:@"/MediaRenderer/AVTransport/Event" + andQuery:@{}]; + [self checkRenderingControlNotificationShouldTriggerRenderingControlSubscriptionOnlyWithRenderingControlEventInfo:kRenderingControlEventInfo + andAVTransportEventInfo:kAVTransportEventInfo]; +} + +/// Tests that the RenderingControl notification will only trigger the +/// RenderingControl success callback for sample Xbox event URLs. +- (void)testRenderingControlNotificationShouldTriggerRenderingControlSubscriptionOnly_Xbox { + EventInfo *const kRenderingControlEventInfo = [[EventInfo alloc] initWithURL:@"/upnphost/udhisapi.dll?event=uuid:0f4810a6-4fb4-4fdf-8acc-81e751e7ec8a+urn:upnp-org:serviceId:RenderingControl" + path:@"/upnphost/udhisapi.dll" + andQuery:@{@"event": @"uuid:0f4810a6-4fb4-4fdf-8acc-81e751e7ec8a+urn:upnp-org:serviceId:RenderingControl"}]; + EventInfo *const kAVTransportEventInfo = [[EventInfo alloc] initWithURL:@"/upnphost/udhisapi.dll?event=uuid:0f4810a6-4fb4-4fdf-8acc-81e751e7ec8a+urn:upnp-org:serviceId:AVTransport" + path:@"/upnphost/udhisapi.dll" + andQuery:@{@"event": @"uuid:0f4810a6-4fb4-4fdf-8acc-81e751e7ec8a+urn:upnp-org:serviceId:AVTransport"}]; + [self checkRenderingControlNotificationShouldTriggerRenderingControlSubscriptionOnlyWithRenderingControlEventInfo:kRenderingControlEventInfo + andAVTransportEventInfo:kAVTransportEventInfo]; +} + +- (void)checkRenderingControlNotificationShouldTriggerRenderingControlSubscriptionOnlyWithRenderingControlEventInfo:(EventInfo *)renderingControlEventInfo + andAVTransportEventInfo:(EventInfo *)avTransportEventInfo { + // Arrange + DLNAHTTPServer *server = [DLNAHTTPServer new]; + + NSString *const kSubscriptionBaseURL = @"http://127.2:222"; + + XCTestExpectation *callbackIsCalledExpectation = [self expectationWithDescription:@"RenderingControl success callback is called"]; + ServiceSubscription *renderingControlSubscription = [[ServiceSubscription alloc] initWithDelegate:nil + target:[NSURL URLWithString:[kSubscriptionBaseURL stringByAppendingString:renderingControlEventInfo.url]] + payload:nil]; + [renderingControlSubscription addSuccess:^(id responseObject) { + XCTAssertNotNil(responseObject); + [callbackIsCalledExpectation fulfill]; + }]; + [renderingControlSubscription addFailure:^(NSError *error) { + XCTFail(@"Should not be called"); + }]; + [server addSubscription:renderingControlSubscription]; + + ServiceSubscription *avTransportSubscription = [[ServiceSubscription alloc] initWithDelegate:nil + target:[NSURL URLWithString:[kSubscriptionBaseURL stringByAppendingString:avTransportEventInfo.url]] + payload:nil]; + [avTransportSubscription addSuccess:^(id responseObject) { + XCTFail(@"Must not be called"); + }]; + [avTransportSubscription addFailure:^(NSError *error) { + XCTFail(@"Must not be called"); + }]; + [server addSubscription:avTransportSubscription]; + + // Act + NSURL *const kNotificationBaseURL = [NSURL URLWithString:@"http://127.1:111"]; + NSData *const kNotificationData = [@"<Event xmlns='urn:schemas-upnp-org:metadata-1-0/RCS/'><InstanceID val='0'><Mute channel='Master' val='0'/><Volume channel='Master' val='3'/><PresetNameList val='FactoryDefaults'/></InstanceID></Event>" dataUsingEncoding:NSUTF8StringEncoding]; + NSURL *kRenderingControlNotificationURL = [NSURL URLWithString:renderingControlEventInfo.url + relativeToURL:kNotificationBaseURL]; + + id renderingControlNotificationMock = OCMClassMock(GCDWebServerDataRequest.class); + OCMStub([renderingControlNotificationMock data]).andReturn(kNotificationData); + OCMStub([renderingControlNotificationMock path]).andReturn(renderingControlEventInfo.path); + OCMStub([renderingControlNotificationMock query]).andReturn(renderingControlEventInfo.query); + OCMStub([renderingControlNotificationMock URL]).andReturn(kRenderingControlNotificationURL); + [server processRequest:renderingControlNotificationMock]; + + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + }]; +} + +@end diff --git a/core/ConnectSDKTests/Services/Helpers/WebOSTVServiceSocketClientTests.m b/core/ConnectSDKTests/Services/Helpers/WebOSTVServiceSocketClientTests.m new file mode 100644 index 00000000..c88c8471 --- /dev/null +++ b/core/ConnectSDKTests/Services/Helpers/WebOSTVServiceSocketClientTests.m @@ -0,0 +1,152 @@ +// +// WebOSTVServiceSocketClientTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2/6/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import "WebOSTVService.h" +#import "WebOSTVServiceSocketClient_Private.h" + +/// Tests for the @c WebOSTVServiceSocketClient class. +@interface WebOSTVServiceSocketClientTests : XCTestCase + +@end + +@implementation WebOSTVServiceSocketClientTests + +#pragma mark - Registration Tests + +/// Tests that -[WebOSTVServiceSocketClientDelegate socket:registrationFailed:] +/// method is called when the user has reject pairing on the TV. In this case, +/// the TV first sends a response with pairing info, then an error with the same +/// message id. +/// https://github.com/ConnectSDK/Connect-SDK-iOS/issues/130 +- (void)testDeniedPairingShouldCallRegistrationFailed { + // Arrange + id serviceMock = OCMClassMock([WebOSTVService class]); + id webSocketMock = OCMClassMock([LGSRWebSocket class]); + + id socketClientDelegateMock = OCMProtocolMock(@protocol(WebOSTVServiceSocketClientDelegate)); + OCMStub([socketClientDelegateMock socket:OCMOCK_ANY didReceiveMessage:OCMOCK_ANY]).andReturn(YES); + XCTestExpectation *registrationFailedCalled = [self expectationWithDescription:@"socket:registrationFailed: is called"]; + OCMExpect([socketClientDelegateMock socket:OCMOCK_NOTNIL + registrationFailed:OCMOCK_NOTNIL]).andDo(^(NSInvocation *_) { + [registrationFailedCalled fulfill]; + }); + + // have to install a partial mock on the SUT (class under test) to stub + // the web socket object (LGSRWebSocket) and some manifest. + WebOSTVServiceSocketClient *socketClient = OCMPartialMock([[WebOSTVServiceSocketClient alloc] initWithService:serviceMock]); + socketClient.delegate = socketClientDelegateMock; + OCMStub([socketClient createSocketWithURLRequest:OCMOCK_ANY]).andReturn(webSocketMock); + OCMStub([socketClient manifest]).andReturn(@{}); + + // Act + [socketClient connect]; + + OCMStub([webSocketMock send:OCMOCK_ANY]).andDo(^(NSInvocation *inv) { + __unsafe_unretained NSString *tmp; + [inv getArgument:&tmp atIndex:2]; + NSString *msg = tmp; + + if (NSNotFound != [msg rangeOfString:@"\"hello\""].location) { + NSString *response = @"{\"type\":\"hello\",\"payload\":{\"protocolVersion\":1,\"deviceType\":\"tv\",\"deviceOS\":\"webOS\",\"deviceOSVersion\":\"4.0.3\",\"deviceOSReleaseVersion\":\"1.3.2\",\"deviceUUID\":\"3C763B8E-8AED-4330-8838-3B1CFABBC16A\",\"pairingTypes\":[\"PIN\",\"PROMPT\"]}}"; + dispatch_async(dispatch_get_main_queue(), ^{ + [socketClient webSocket:webSocketMock didReceiveMessage:response]; + }); + } else if (NSNotFound != [msg rangeOfString:@"\"register\""].location) { + // here a pairing alert is displayed on TV + NSString *response = @"{\"type\":\"response\",\"id\":\"2\",\"payload\":{\"pairingType\":\"PROMPT\",\"returnValue\":true}}"; + dispatch_async(dispatch_get_main_queue(), ^{ + [socketClient webSocket:webSocketMock didReceiveMessage:response]; + }); + + // here the user has rejected access + NSString *error = @"{\"type\":\"error\",\"id\":\"2\",\"error\":\"403 User denied access\",\"payload\":\"\"}"; + dispatch_async(dispatch_get_main_queue(), ^{ + [socketClient webSocket:webSocketMock didReceiveMessage:error]; + }); + } else { + XCTFail(@"Unexpected request %@", msg); + } + }); + [socketClient webSocketDidOpen:webSocketMock]; + + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + OCMVerifyAll(socketClientDelegateMock); + }]; +} + +-(void)testPairingShouldCallSocketWillRegister{ + id serviceMock = OCMClassMock([WebOSTVService class]); + id webSocketMock = OCMClassMock([LGSRWebSocket class]); + //Send Pairing type which is not supported by the TV. Supported pairing type is PROMPT + OCMStub([serviceMock pairingType]).andReturn(DeviceServicePairingTypeMixed); + id socketClientDelegateMock = OCMProtocolMock(@protocol(WebOSTVServiceSocketClientDelegate)); + OCMStub([socketClientDelegateMock socket:OCMOCK_ANY didReceiveMessage:OCMOCK_ANY]).andReturn(YES); + XCTestExpectation *socketWillRegisterCalled = [self expectationWithDescription:@"socketWillRegister: is called"]; + OCMExpect([socketClientDelegateMock socketWillRegister:OCMOCK_NOTNIL]).andDo(^(NSInvocation *_) { + [socketWillRegisterCalled fulfill]; + }); + + // have to install a partial mock on the SUT (class under test) to stub + // the web socket object (LGSRWebSocket) and some manifest. + WebOSTVServiceSocketClient *socketClient = OCMPartialMock([[WebOSTVServiceSocketClient alloc] initWithService:serviceMock]); + socketClient.delegate = socketClientDelegateMock; + OCMStub([socketClient createSocketWithURLRequest:OCMOCK_ANY]).andReturn(webSocketMock); + OCMStub([socketClient manifest]).andReturn(@{}); + + // Act + [socketClient connect]; + + OCMStub([webSocketMock send:OCMOCK_ANY]).andDo(^(NSInvocation *inv) { + __unsafe_unretained NSString *tmp; + [inv getArgument:&tmp atIndex:2]; + NSString *msg = tmp; + NSLog(@"Message %@",msg); + if (NSNotFound != [msg rangeOfString:@"\"hello\""].location) { + NSString *response = @"{\"type\":\"hello\",\"payload\":{\"protocolVersion\":1,\"deviceType\":\"tv\",\"deviceOS\":\"webOS\",\"deviceOSVersion\":\"4.0.3\",\"deviceOSReleaseVersion\":\"1.3.2\",\"deviceUUID\":\"3C763B8E-8AED-4330-8838-3B1CFABBC16A\",\"pairingTypes\":[\"PIN\",\"PROMPT\"]}}"; + dispatch_async(dispatch_get_main_queue(), ^{ + [socketClient webSocket:webSocketMock didReceiveMessage:response]; + }); + } else if (NSNotFound != [msg rangeOfString:@"\"register\""].location) { + // here a pairing alert is displayed on TV + NSLog(@"Register called"); + NSString *response = @"{\"type\":\"response\",\"id\":\"2\",\"payload\":{\"pairingType\":\"PROMPT\",\"returnValue\":true}}"; + dispatch_async(dispatch_get_main_queue(), ^{ + [socketClient webSocket:webSocketMock didReceiveMessage:response]; + }); + + } else { + XCTFail(@"Unexpected request %@", msg); + } + }); + [socketClient webSocketDidOpen:webSocketMock]; + + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + OCMVerifyAll(socketClientDelegateMock); + }]; +} + +@end diff --git a/core/ConnectSDKTests/Services/Helpers/sample_data/airplay_playbackinfo_ff.json b/core/ConnectSDKTests/Services/Helpers/sample_data/airplay_playbackinfo_ff.json new file mode 100644 index 00000000..494f7723 --- /dev/null +++ b/core/ConnectSDKTests/Services/Helpers/sample_data/airplay_playbackinfo_ff.json @@ -0,0 +1,23 @@ +{ + "duration": 52.209, + "loadedTimeRanges": [ + { + "duration": 52.209, + "start": 0 + } + ], + "playbackBufferEmpty": 1, + "playbackBufferFull": 0, + "playbackLikelyToKeepUp": 1, + "position": 18.001816139, + "rate": 2, + "readyToPlay": 1, + "seekableTimeRanges": [ + { + "duration": 52.209, + "start": 0 + } + ], + "stallCount": 0, + "uuid": "32CA44EB-AACE-4FBE-AC98-69E3CCDA92EF-1410-000003ED1BA9B63D" +} diff --git a/core/ConnectSDKTests/Services/Helpers/sample_data/airplay_playbackinfo_finished.json b/core/ConnectSDKTests/Services/Helpers/sample_data/airplay_playbackinfo_finished.json new file mode 100644 index 00000000..04aa29de --- /dev/null +++ b/core/ConnectSDKTests/Services/Helpers/sample_data/airplay_playbackinfo_finished.json @@ -0,0 +1,4 @@ +{ + "readyToPlay": 0, + "uuid": "C9E28C31-59FB-4F78-97C7-C8CF8960F243-1410-000003E741054B97" +} diff --git a/core/ConnectSDKTests/Services/Helpers/sample_data/airplay_playbackinfo_paused.json b/core/ConnectSDKTests/Services/Helpers/sample_data/airplay_playbackinfo_paused.json new file mode 100644 index 00000000..13e18e3e --- /dev/null +++ b/core/ConnectSDKTests/Services/Helpers/sample_data/airplay_playbackinfo_paused.json @@ -0,0 +1,23 @@ +{ + "duration": 5.568, + "loadedTimeRanges": [ + { + "duration": 5.568, + "start": 0 + } + ], + "playbackBufferEmpty": 1, + "playbackBufferFull": 0, + "playbackLikelyToKeepUp": 1, + "position": 2.480788888888889, + "rate": 0, + "readyToPlay": 1, + "seekableTimeRanges": [ + { + "duration": 5.568, + "start": 0 + } + ], + "stallCount": 0, + "uuid": "C9E28C31-59FB-4F78-97C7-C8CF8960F243-1410-000003E741054B97" +} diff --git a/core/ConnectSDKTests/Services/Helpers/sample_data/airplay_playbackinfo_playing.json b/core/ConnectSDKTests/Services/Helpers/sample_data/airplay_playbackinfo_playing.json new file mode 100644 index 00000000..4ff94a61 --- /dev/null +++ b/core/ConnectSDKTests/Services/Helpers/sample_data/airplay_playbackinfo_playing.json @@ -0,0 +1,24 @@ +{ + "duration": 5.568, + "loadedTimeRanges": [ + { + "duration": 5.568, + "start": 0 + } + ], + "playbackBufferEmpty": 1, + "playbackBufferFull": 0, + "playbackLikelyToKeepUp": 1, + "position": 1.207569559, + "rate": 1, + "readyToPlay": 1, + "readyToPlayMs": 432, + "seekableTimeRanges": [ + { + "duration": 5.568, + "start": 0 + } + ], + "stallCount": 0, + "uuid": "C9E28C31-59FB-4F78-97C7-C8CF8960F243-1410-000003E741054B97" +} diff --git a/core/ConnectSDKTests/Services/Helpers/sample_data/airplay_playbackinfo_rewind.json b/core/ConnectSDKTests/Services/Helpers/sample_data/airplay_playbackinfo_rewind.json new file mode 100644 index 00000000..a532690e --- /dev/null +++ b/core/ConnectSDKTests/Services/Helpers/sample_data/airplay_playbackinfo_rewind.json @@ -0,0 +1,23 @@ +{ + "duration": 52.209, + "loadedTimeRanges": [ + { + "duration": 52.209, + "start": 0 + } + ], + "playbackBufferEmpty": 1, + "playbackBufferFull": 0, + "playbackLikelyToKeepUp": 1, + "position": 33.206252437, + "rate": -2, + "readyToPlay": 1, + "seekableTimeRanges": [ + { + "duration": 52.209, + "start": 0 + } + ], + "stallCount": 0, + "uuid": "32CA44EB-AACE-4FBE-AC98-69E3CCDA92EF-1410-000003ED1BA9B63D" +} diff --git a/core/ConnectSDKTests/Services/NetcastTVServiceTests.m b/core/ConnectSDKTests/Services/NetcastTVServiceTests.m new file mode 100644 index 00000000..994757d7 --- /dev/null +++ b/core/ConnectSDKTests/Services/NetcastTVServiceTests.m @@ -0,0 +1,288 @@ +// +// NetcastTVServiceTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 3/24/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NetcastTVService_Private.h" +#import "CTXMLReader.h" +#import "DiscoveryManager.h" +#import "NSInvocation+ObjectGetter.h" +#import "XCTestCase+Common.h" + +static NSString *const kClientCode = @"nop"; + +/// Tests for the @c NetcastTVService class. +@interface NetcastTVServiceTests : XCTestCase + +@property (nonatomic, strong) id serviceCommandDelegateMock; +@property (nonatomic, strong) NetcastTVService *service; + +@end + +@implementation NetcastTVServiceTests + +- (void)setUp { + [super setUp]; + self.serviceCommandDelegateMock = OCMProtocolMock(@protocol(ServiceCommandDelegate)); + self.service = [NetcastTVService new]; + self.service.serviceCommandDelegate = self.serviceCommandDelegateMock; +} + +- (void)tearDown { + self.service = nil; + self.serviceCommandDelegateMock = nil; + [super tearDown]; +} + +#pragma mark - General Tests + +- (void)testInstanceShouldHaveSubtitleSRTCapabilityWithPairingLevelOn { + [self checkInstanceShouldHaveSubtitleSRTCapabilityWithPairingLevel:DeviceServicePairingLevelOn]; +} + +- (void)testInstanceShouldHaveSubtitleSRTCapabilityWithPairingLevelOff { + [self checkInstanceShouldHaveSubtitleSRTCapabilityWithPairingLevel:DeviceServicePairingLevelOff]; +} + +#pragma mark - Request Generation Tests + +/// Tests that @c -sendText:success:failure: creates a proper and valid +/// TextEdited XML request. +- (void)testSendTextShouldCreateProperRequest { + // Arrange + NSString *commandName = @"TextEdited"; + static NSString *const defaultText = @"Hello, &]]> \"others'\\ ура ξ中]]>…"; + + XCTestExpectation *commandIsSent = [self expectationWithDescription: + [NSString stringWithFormat:@"%@ command is sent", commandName]]; + + [OCMExpect([self.serviceCommandDelegateMock sendCommand:OCMOCK_NOTNIL + withPayload:OCMOCK_NOTNIL + toURL:OCMOCK_ANY]) andDo:^(NSInvocation *inv) { + NSString *xmlString = [inv objectArgumentAtIndex:1]; + XCTAssertNotNil(xmlString, @"XML request not found"); + + NSError *error = nil; + NSDictionary *dict = [CTXMLReader dictionaryForXMLString:xmlString + error:&error]; + XCTAssertNil(error, @"XML parsing error"); + XCTAssertNotNil(dict, @"Couldn't parse XML"); + + NSDictionary *envelope = dict[@"envelope"]; + XCTAssertNotNil(envelope, @"envelope tag must be present"); + + NSDictionary *api = envelope[@"api"]; + XCTAssertNotNil(api, @"api tag must be present"); + XCTAssertEqualObjects(api[@"type"], @"event", @"api.type is incorrect"); + + XCTAssertEqualObjects([api valueForKeyPath:@"name.text"], commandName, @"api name is incorrect"); + XCTAssertEqualObjects([api valueForKeyPath:@"state.text"], @"Editing", @"api state is incorrect"); + XCTAssertEqualObjects([api valueForKeyPath:@"value.text"], defaultText, @"api value is incorrect"); + + [commandIsSent fulfill]; + }]; + + // Act + [self.service sendText:defaultText + success:^(id responseObject) { + XCTFail(@"success?"); + } failure:^(NSError *error) { + XCTFail(@"fail? %@", error); + }]; + + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout + handler:^(NSError *error) { + XCTAssertNil(error); + OCMVerifyAll(self.serviceCommandDelegateMock); + }]; +} + +/* The setter tests below test different cases of setting various service + * config objects and whether those throw an exception when some important data + * from @c NetcastTVServiceConfig would be lost. + */ + +#pragma mark - ServiceConfig Setter Tests (Base <=> Netcast) + +- (void)testSwitching_Base_To_NetcastWithoutCode_ServiceConfigShouldNotThrowException { + ServiceConfig *config = [ServiceConfig new]; + NetcastTVService *service = [[NetcastTVService alloc] initWithServiceConfig:config]; + + NetcastTVServiceConfig *netcastConfig = [NetcastTVServiceConfig new]; + XCTAssertNoThrowSpecificNamed(service.serviceConfig = netcastConfig, + NSException, + NSInternalInconsistencyException, + @"Should not throw exception"); +} + +- (void)testSwitching_Base_To_NetcastWithCode_ServiceConfigShouldNotThrowException { + ServiceConfig *config = [ServiceConfig new]; + NetcastTVService *service = [[NetcastTVService alloc] initWithServiceConfig:config]; + + NetcastTVServiceConfig *netcastConfig = [NetcastTVServiceConfig new]; + netcastConfig.pairingCode = kClientCode; + XCTAssertNoThrowSpecificNamed(service.serviceConfig = netcastConfig, + NSException, + NSInternalInconsistencyException, + @"Should not throw exception"); +} + +- (void)testSwitching_NetcastWithoutCode_To_Base_ServiceConfigShouldNotThrowException { + NetcastTVServiceConfig *netcastConfig = [NetcastTVServiceConfig new]; + NetcastTVService *service = [[NetcastTVService alloc] initWithServiceConfig:netcastConfig]; + + ServiceConfig *config = [ServiceConfig new]; + XCTAssertNoThrowSpecificNamed(service.serviceConfig = config, + NSException, + NSInternalInconsistencyException, + @"Should not throw exception"); +} + +- (void)testSwitching_NetcastWithCode_To_Base_ServiceConfigShouldThrowException { + NetcastTVServiceConfig *netcastConfig = [NetcastTVServiceConfig new]; + netcastConfig.pairingCode = kClientCode; + NetcastTVService *service = [[NetcastTVService alloc] initWithServiceConfig:netcastConfig]; + + ServiceConfig *config = [ServiceConfig new]; + XCTAssertThrowsSpecificNamed(service.serviceConfig = config, + NSException, + NSInternalInconsistencyException, + @"Should throw exception because the code will disappear"); +} + +#pragma mark - ServiceConfig Setter Tests (Netcast <=> Netcast) + +- (void)testSwitching_NetcastWithoutCode_To_NetcastWithoutCode_ServiceConfigShouldNotThrowException { + NetcastTVServiceConfig *netcastConfig = [NetcastTVServiceConfig new]; + NetcastTVService *service = [[NetcastTVService alloc] initWithServiceConfig:netcastConfig]; + + NetcastTVServiceConfig *netcastConfig2 = [NetcastTVServiceConfig new]; + XCTAssertNoThrowSpecificNamed(service.serviceConfig = netcastConfig2, + NSException, + NSInternalInconsistencyException, + @"Should not throw exception"); +} + +- (void)testSwitching_NetcastWithoutCode_To_NetcastWithCode_ServiceConfigShouldNotThrowException { + NetcastTVServiceConfig *netcastConfig = [NetcastTVServiceConfig new]; + NetcastTVService *service = [[NetcastTVService alloc] initWithServiceConfig:netcastConfig]; + + NetcastTVServiceConfig *netcastConfig2 = [NetcastTVServiceConfig new]; + netcastConfig2.pairingCode = kClientCode; + XCTAssertNoThrowSpecificNamed(service.serviceConfig = netcastConfig2, + NSException, + NSInternalInconsistencyException, + @"Should not throw exception"); +} + +- (void)testSwitching_NetcastWithCode_To_NetcastWithCode_ServiceConfigShouldNotThrowException { + NetcastTVServiceConfig *netcastConfig = [NetcastTVServiceConfig new]; + netcastConfig.pairingCode = kClientCode; + NetcastTVService *service = [[NetcastTVService alloc] initWithServiceConfig:netcastConfig]; + + NetcastTVServiceConfig *netcastConfig2 = [NetcastTVServiceConfig new]; + netcastConfig2.pairingCode = @"anotherCode"; + XCTAssertNoThrowSpecificNamed(service.serviceConfig = netcastConfig2, + NSException, + NSInternalInconsistencyException, + @"Should not throw exception"); +} + +- (void)testSwitching_NetcastWithCode_To_NetcastWithoutCode_ServiceConfigShouldThrowException { + NetcastTVServiceConfig *netcastConfig = [NetcastTVServiceConfig new]; + netcastConfig.pairingCode = kClientCode; + NetcastTVService *service = [[NetcastTVService alloc] initWithServiceConfig:netcastConfig]; + + NetcastTVServiceConfig *netcastConfig2 = [NetcastTVServiceConfig new]; + XCTAssertThrowsSpecificNamed(service.serviceConfig = netcastConfig2, + NSException, + NSInternalInconsistencyException, + @"Should throw exception because the code will disappear"); +} + +- (void)testMediaPlayerPriorityShouldReturnHigh { + XCTAssertEqual([self.service mediaPlayerPriority], CapabilityPriorityLevelHigh,@"media player priority should be CapabilityPriorityLevelHigh"); +} + +- (void)testMediaControlPriorityShouldReturnHigh { + XCTAssertEqual([self.service mediaControlPriority], CapabilityPriorityLevelHigh,@"Media control priority should be CapabilityPriorityLevelHigh"); +} + +- (void)testRewindShouldReturnFailureBlock { + [self checkOperationShouldReturnNotSupportedErrorUsingBlock: + ^(SuccessBlock successVerifier, FailureBlock failureVerifier) { + [self.service rewindWithSuccess:successVerifier + failure:failureVerifier]; + }]; +} + +- (void)testFastForwardShouldReturnFailureBlock { + [self checkOperationShouldReturnNotSupportedErrorUsingBlock: + ^(SuccessBlock successVerifier, FailureBlock failureVerifier) { + [self.service fastForwardWithSuccess:successVerifier + failure:failureVerifier]; + }]; +} + +- (void)testShouldHaveFollowingCapabilities { + NSSet *expectedCapabilities = [NSSet setWithObjects: + kMediaPlayerDisplayImage, + kMediaPlayerPlayVideo, + kMediaPlayerPlayAudio, + kMediaPlayerClose, + kMediaPlayerMetaDataTitle, + kMediaPlayerMetaDataDescription, + kMediaPlayerMetaDataThumbnail, + kMediaPlayerMetaDataMimeType, + kMediaPlayerSubtitleSRT, + kMediaControlPlay, + kMediaControlPause, + kMediaControlStop, + kLauncherYouTube, + kLauncherYouTubeParams, + nil]; + NSSet *actualCapabilities = [NSSet setWithArray:self.service.capabilities]; + XCTAssertEqualObjects(expectedCapabilities, actualCapabilities, + @"Netcast capabilities are incorrect"); +} + +- (void)testNetcastShouldNotHaveSpecificCapabipilities{ + NSSet *currentCapabilities = [NSSet setWithArray:self.service.capabilities]; + XCTAssertFalse([currentCapabilities containsObject:kMediaControlRewind]); + XCTAssertFalse([currentCapabilities containsObject:kMediaControlFastForward]); +} + +#pragma mark - Helpers + +- (void)checkInstanceShouldHaveSubtitleSRTCapabilityWithPairingLevel:(DeviceServicePairingLevel)pairingLevel { + // the test looks ugly because of the implicit dependency on the + // singleton DiscoveryManager + // TODO remove the dependency + + DiscoveryManager *discoveryManager = [DiscoveryManager sharedManager]; + DeviceServicePairingLevel oldPairingLevel = discoveryManager.pairingLevel; + + discoveryManager.pairingLevel = pairingLevel; + XCTAssertNotEqual([self.service.capabilities indexOfObject:kMediaPlayerSubtitleSRT], + NSNotFound); + + discoveryManager.pairingLevel = oldPairingLevel; +} + +@end diff --git a/core/ConnectSDKTests/Services/RokuServiceTests.m b/core/ConnectSDKTests/Services/RokuServiceTests.m new file mode 100644 index 00000000..e6f4e736 --- /dev/null +++ b/core/ConnectSDKTests/Services/RokuServiceTests.m @@ -0,0 +1,199 @@ +// +// RokuServiceTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-07-16. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "RokuService_Private.h" + +#import "ConnectError.h" + +#import "NSInvocation+ObjectGetter.h" +#import "XCTestCase+Common.h" + +@interface RokuServiceTests : XCTestCase + +@property (nonatomic, strong) RokuService *service; +@property (nonatomic, strong) id /**/ serviceCommandDelegateMock; + +@end + +@implementation RokuServiceTests + +- (void)setUp { + [super setUp]; + + self.service = [RokuService new]; + self.serviceCommandDelegateMock = OCMProtocolMock(@protocol(ServiceCommandDelegate)); + self.service.serviceCommandDelegate = self.serviceCommandDelegateMock; +} + +#pragma mark - Request Tests + +- (void)testPlayVideoShouldNotSendEventURL { + NSURL *url = [NSURL URLWithString:@"http://example.com/"]; + MediaInfo *videoInfo = [[MediaInfo alloc] initWithURL:url mimeType:@"video/mp4"]; + [self checkPlayMediaBlockShouldNotSendEventURL:^{ + [self.service playMediaWithMediaInfo:videoInfo + shouldLoop:NO + success:nil + failure:nil]; + }]; +} + +- (void)testPlayAudioShouldNotSendEventURL { + NSURL *url = [NSURL URLWithString:@"http://example.com/"]; + MediaInfo *audioInfo = [[MediaInfo alloc] initWithURL:url mimeType:@"audio/ogg"]; + [self checkPlayMediaBlockShouldNotSendEventURL:^{ + [self.service playMediaWithMediaInfo:audioInfo + shouldLoop:NO + success:nil + failure:nil]; + }]; +} + +- (void)testDisplayImageShouldNotSendEventURL { + NSURL *url = [NSURL URLWithString:@"http://example.com/"]; + MediaInfo *imageInfo = [[MediaInfo alloc] initWithURL:url mimeType:@"image/png"]; + [self checkPlayMediaBlockShouldNotSendEventURL:^{ + [self.service displayImageWithMediaInfo:imageInfo + success:nil + failure:nil]; + }]; +} + +#pragma mark - GetAppList Response Tests + +- (void)testGetAppListShouldAcceptMultipleApps { + NSString *appsXML = @"The AppYApp"; + AppInfo *app0 = [AppInfo appInfoForId:@"X"]; app0.name = @"The App"; + AppInfo *app1 = [AppInfo appInfoForId:@"Y"]; app1.name = @"YApp"; + [self checkGetAppListShouldReturnApps:@[app0, app1] forXMLString:appsXML]; +} + +- (void)testGetAppListShouldAcceptOneApp { + NSString *appsXML = @"The App"; + AppInfo *app = [AppInfo appInfoForId:@"X"]; app.name = @"The App"; + [self checkGetAppListShouldReturnApps:@[app] forXMLString:appsXML]; +} + +- (void)testGetAppListShouldAcceptZeroApps { + NSString *appsXML = @""; + [self checkGetAppListShouldReturnApps:@[] forXMLString:appsXML]; +} + +- (void)testGetAppListWrongXMLShouldReturnExmptyList { + NSString *appsXML = @""; + [self checkGetAppListShouldReturnApps:@[] forXMLString:appsXML]; +} + +- (void)testGetAppListInvalidXMLShouldCallFailure { + NSString *appsXML = @"&oops;"; + [self checkGetAppListShouldReturnApps:nil + orErrorWithCode:ConnectStatusCodeTvError + forXMLString:appsXML]; +} + +- (void)testGetAppListNilXMLShouldCallFailure { + [self checkGetAppListShouldReturnApps:nil + orErrorWithCode:ConnectStatusCodeTvError + forXMLString:nil]; +} + +#pragma mark - Unsupported Methods Tests + +- (void)testGetDurationShouldReturnNotSupportedError { + [self checkOperationShouldReturnNotSupportedErrorUsingBlock: + ^(SuccessBlock successVerifier, FailureBlock failureVerifier) { + [self.service getDurationWithSuccess:^(NSTimeInterval _) { + successVerifier(nil); + } + failure:failureVerifier]; + }]; +} + +- (void)testGetMediaMetadataShouldReturnNotSupportedError { + [self checkOperationShouldReturnNotSupportedErrorUsingBlock: + ^(SuccessBlock successVerifier, FailureBlock failureVerifier) { + [self.service getMediaMetaDataWithSuccess:successVerifier + failure:failureVerifier]; + }]; +} + +#pragma mark - Helpers + +- (void)checkPlayMediaBlockShouldNotSendEventURL:(void (^)())testBlock { + id serviceDescriptionMock = OCMClassMock([ServiceDescription class]); + [OCMStub([serviceDescriptionMock commandURL]) andReturn:[NSURL URLWithString:@"http://42"]]; + self.service.serviceDescription = serviceDescriptionMock; + + XCTestExpectation *commandSentExpectation = [self expectationWithDescription:@"command is sent"]; + + [OCMExpect([self.serviceCommandDelegateMock sendCommand:OCMOCK_NOTNIL + withPayload:OCMOCK_ANY + toURL:OCMOCK_NOTNIL]) andDo:^(NSInvocation *inv) { + NSURL *url = [inv objectArgumentAtIndex:2]; + NSURLComponents *components = [NSURLComponents componentsWithURL:url + resolvingAgainstBaseURL:NO]; + NSArray *eventURLQueryItems = [components.queryItems filteredArrayUsingPredicate: + [NSPredicate predicateWithFormat:@"%K == %@", @"name", @"h"]]; + XCTAssertEqual(eventURLQueryItems.count, 0, + @"The event URL should not be sent in the request"); + + [commandSentExpectation fulfill]; + }]; + + testBlock(); + + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout handler:nil]; +} + +- (void)checkGetAppListShouldReturnApps:(NSArray *)apps + forXMLString:(NSString *)xmlString { + [self checkGetAppListShouldReturnApps:apps + orErrorWithCode:0 + forXMLString:xmlString]; +} + +- (void)checkGetAppListShouldReturnApps:(NSArray *)apps + orErrorWithCode:(ConnectStatusCode)errorCode + forXMLString:(NSString *)xmlString { + [OCMExpect([self.serviceCommandDelegateMock sendCommand:OCMOCK_ANY + withPayload:OCMOCK_ANY + toURL:OCMOCK_ANY]) andDo:^(NSInvocation *invocation) { + ServiceCommand *command = [invocation objectArgumentAtIndex:0]; + command.callbackComplete(xmlString); + }]; + + XCTestExpectation *expectation = [self expectationWithDescription:@""]; + [self.service getAppListWithSuccess:^(NSArray *appList) { + if (apps) { + XCTAssertEqualObjects(appList, apps); + [expectation fulfill]; + } + } + failure:^(NSError *error) { + if (!apps) { + XCTAssertEqual(error.code, errorCode); + [expectation fulfill]; + } + }]; + + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout handler:nil]; +} + +@end diff --git a/core/ConnectSDKTests/Services/Sessions/WebAppSessionTests.m b/core/ConnectSDKTests/Services/Sessions/WebAppSessionTests.m new file mode 100644 index 00000000..33dd6041 --- /dev/null +++ b/core/ConnectSDKTests/Services/Sessions/WebAppSessionTests.m @@ -0,0 +1,42 @@ +// +// WebAppSessionTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-07-29. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "WebAppSession.h" + +#import "XCTestCase+Common.h" + +@interface WebAppSessionTests : XCTestCase + +@end + +@implementation WebAppSessionTests + +#pragma mark - Unsupported Methods Tests + +- (void)testGetMediaMetadataShouldReturnNotSupportedError { + [self checkOperationShouldReturnNotSupportedErrorUsingBlock: + ^(SuccessBlock successVerifier, FailureBlock failureVerifier) { + WebAppSession *session = [WebAppSession new]; + [session getMediaMetaDataWithSuccess:successVerifier + failure:failureVerifier]; + }]; +} + +@end diff --git a/core/ConnectSDKTests/Services/Sessions/WebOSWebAppSessionTests.m b/core/ConnectSDKTests/Services/Sessions/WebOSWebAppSessionTests.m new file mode 100644 index 00000000..50aa2c36 --- /dev/null +++ b/core/ConnectSDKTests/Services/Sessions/WebOSWebAppSessionTests.m @@ -0,0 +1,205 @@ +// +// WebOSWebAppSessionTests.m +// ConnectSDK +// +// Created by Ibrahim Adnan on 6/18/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "WebOSWebAppSession_Private.h" + +#import "SubtitleInfo.h" + +@interface WebOSWebAppSessionTests : XCTestCase + +@property(nonatomic, strong) WebOSWebAppSession *session; +@property(nonatomic, strong) id socketMock; + +@end + +@implementation WebOSWebAppSessionTests + +- (void)setUp { + [super setUp]; + + self.session = OCMPartialMock([WebOSWebAppSession new]); + self.session.fullAppId = @"com.lgsmartplatform.redirect.MediaPlayer"; + + self.socketMock = OCMClassMock([WebOSTVServiceSocketClient class]); + OCMStub([self.session createSocketWithService:OCMOCK_ANY]).andReturn(self.socketMock); + + [self.session connectWithSuccess:nil failure:nil]; + OCMStub([self.session connected]).andReturn(YES); +} + +#pragma mark - MediaPlayer Tests + +- (void)testMediaPlayerErrorShouldCallFailureBlockInPlayStateSubscription{ + // Arrange + XCTestExpectation *failureBlockCalledExpectation = [self expectationWithDescription:@"Failure block is called"]; + [self.session subscribePlayStateWithSuccess:^(MediaControlPlayState playState) { + XCTFail(@"Success should not be called when Media player throws error"); + } failure:^(NSError *error) { + [failureBlockCalledExpectation fulfill]; + }]; + + NSDictionary *errorPayload = @{ + @"from" : @"com.lgsmartplatform.redirect.MediaPlayer", + @"payload" : @{ + @"contentType" : @"connectsdk.media-error", + @"error" : @"The file cannot be recognized", + }, + @"type" : @"p2p" + }; + + // Act + [self.session socket:self.socketMock didReceiveMessage:errorPayload]; + + // Assert + [self waitForExpectationsWithTimeout:kDefaultAsyncTestTimeout handler:nil]; +} + +#pragma mark - Subtitles Support Tests + +- (void)testPlayVideoWithSubtitlesShouldContainSubtitlesKey { + [self checkPlayVideoWithSubtitles:[self mediaInfoWithSubtitle] + shouldContainSubtitlesPassingTest:^(NSDictionary *subtitles) { + XCTAssertNotNil(subtitles); + }]; +} + +- (void)testPlayVideoWithSubtitlesShouldContainOneSubtitleTrack { + [self checkPlayVideoWithSubtitles:[self mediaInfoWithSubtitle] + shouldContainSubtitlesPassingTest:^(NSDictionary *subtitles) { + NSArray *tracks = subtitles[@"tracks"]; + XCTAssertEqual(tracks.count, 1); + }]; +} + +- (void)testPlayVideoWithSubtitlesShouldContainSubtitleSourceAsString { + MediaInfo *const mediaInfo = [self mediaInfoWithSubtitle]; + [self checkPlayVideoWithSubtitles:mediaInfo + shouldContainSubtitlesPassingTest:^(NSDictionary *subtitles) { + NSDictionary *track = subtitles[@"tracks"][0]; + XCTAssertEqualObjects(track[@"source"], mediaInfo.subtitleInfo.url.absoluteString); + }]; +} + +- (void)testPlayVideoWithSubtitlesShouldContainSubtitleLanguage { + MediaInfo *const mediaInfo = [self mediaInfoWithSubtitle]; + [self checkPlayVideoWithSubtitles:mediaInfo + shouldContainSubtitlesPassingTest:^(NSDictionary *subtitles) { + NSDictionary *track = subtitles[@"tracks"][0]; + XCTAssertEqualObjects(track[@"language"], mediaInfo.subtitleInfo.language); + }]; +} + +- (void)testPlayVideoWithSubtitlesShouldContainSubtitleLabel { + MediaInfo *const mediaInfo = [self mediaInfoWithSubtitle]; + [self checkPlayVideoWithSubtitles:mediaInfo + shouldContainSubtitlesPassingTest:^(NSDictionary *subtitles) { + NSDictionary *track = subtitles[@"tracks"][0]; + XCTAssertEqualObjects(track[@"label"], mediaInfo.subtitleInfo.label); + }]; +} + +- (void)testPlayVideoWithSubtitlesShouldContainStringSubtitleId { + MediaInfo *const mediaInfo = [self mediaInfoWithSubtitle]; + [self checkPlayVideoWithSubtitles:mediaInfo + shouldContainSubtitlesPassingTest:^(NSDictionary *subtitles) { + NSDictionary *track = subtitles[@"tracks"][0]; + XCTAssertTrue([track[@"id"] isKindOfClass:[NSString class]]); + }]; +} + +- (void)testPlayVideoWithSubtitlesShouldEnableTheSubtitles { + MediaInfo *const mediaInfo = [self mediaInfoWithSubtitle]; + [self checkPlayVideoWithSubtitles:mediaInfo + shouldContainSubtitlesPassingTest:^(NSDictionary *subtitles) { + NSString *enabledId = subtitles[@"enabled"]; + NSString *trackId = subtitles[@"tracks"][0][@"id"]; + XCTAssertEqualObjects(enabledId, trackId); + }]; +} + +- (void)testPlayVideoWithSubtitlesWithoutLanguageShouldNotContainSubtitleLanguage { + MediaInfo *const mediaInfo = [self mediaInfoWithSubtitleLanguage:nil + label:@"label"]; + [self checkPlayVideoWithSubtitles:mediaInfo + shouldContainSubtitlesPassingTest:^(NSDictionary *subtitles) { + NSDictionary *track = subtitles[@"tracks"][0]; + XCTAssertNil(track[@"language"]); + }]; +} + +- (void)testPlayVideoWithSubtitlesWithoutLabelShouldNotContainSubtitleLabel { + MediaInfo *const mediaInfo = [self mediaInfoWithSubtitleLanguage:@"en" + label:nil]; + [self checkPlayVideoWithSubtitles:mediaInfo + shouldContainSubtitlesPassingTest:^(NSDictionary *subtitles) { + NSDictionary *track = subtitles[@"tracks"][0]; + XCTAssertNil(track[@"label"]); + }]; +} + +- (void)testPlayVideoWithoutSubtitlesShouldNotContainSubtitlesKey { + [self checkPlayVideoWithSubtitles:[self videoInfo] + shouldContainSubtitlesPassingTest:^(NSDictionary *subtitles) { + XCTAssertNil(subtitles); + }]; +} + +#pragma mark - Helpers + +- (void)checkPlayVideoWithSubtitles:(MediaInfo *)mediaInfo + shouldContainSubtitlesPassingTest:(void (^)(NSDictionary *subtitles))checkBlock { + OCMExpect([self.socketMock sendDictionaryOverSocket: + [OCMArg checkWithBlock:^BOOL(NSDictionary *dict) { + checkBlock([dict valueForKeyPath:@"payload.mediaCommand.subtitles"]); + return YES; + }]]); + + [self.session playMediaWithMediaInfo:mediaInfo + shouldLoop:NO + success:nil + failure:nil]; + + OCMVerifyAll(self.socketMock); +} + +- (MediaInfo *)mediaInfoWithSubtitle { + return [self mediaInfoWithSubtitleLanguage:@"en" label:@"Test"]; +} + +- (MediaInfo *)mediaInfoWithSubtitleLanguage:(NSString *)language + label:(NSString *)label { + NSURL *subtitleURL = [NSURL URLWithString:@"http://example.com/"]; + SubtitleInfo *subtitleInfo = [SubtitleInfo infoWithURL:subtitleURL + andBlock:^(SubtitleInfoBuilder *builder) { + builder.language = language; + builder.label = label; + }]; + MediaInfo *mediaInfo = [self videoInfo]; + mediaInfo.subtitleInfo = subtitleInfo; + + return mediaInfo; +} + +- (MediaInfo *)videoInfo { + return [[MediaInfo alloc] initWithURL:[NSURL URLWithString:@"http://url"] + mimeType:@"video/mp4"]; +} + +@end diff --git a/core/ConnectSDKTests/Services/WebOSTVServiceTests.m b/core/ConnectSDKTests/Services/WebOSTVServiceTests.m new file mode 100644 index 00000000..79d2c805 --- /dev/null +++ b/core/ConnectSDKTests/Services/WebOSTVServiceTests.m @@ -0,0 +1,437 @@ +// +// WebOSTVServiceTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 3/25/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "WebOSTVService_Private.h" + +#import "DiscoveryManager.h" +#import "DLNAService.h" +#import "SubtitleInfo.h" +#import "WebOSWebAppSession.h" + +#import "NSInvocation+ObjectGetter.h" +#import "XCTestCase+Common.h" + +static NSString *const kClientKey = @"clientKey"; + +static NSString *const kRecentWebOSVersion = @"5.0.0"; +static NSString *const kLegacyWebOSVersion = @"4.0.0"; + +/// Tests for the @c WebOSTVService class. +@interface WebOSTVServiceTests : XCTestCase + +@end + +@implementation WebOSTVServiceTests + +#define CAPTEST(capability, version, pairingLevel, usingDLNA, shouldHave) ({\ + [self checkShouldHave:shouldHave \ + subtitleCapability:capability \ + forVersion:version \ + withPairingLevel:pairingLevel \ + andUsingDLNA:usingDLNA]; \ +}) + +/* Truth table for the capabilities: + * Version | Pairing | DLNA || VTT | SRT + * ------------------------------------- + * nil | off | NO || YES | NO + * nil | off | YES || YES | NO + * nil | on | NO || YES | NO + * nil | on | YES || YES | NO + * 4.0.0 | off | NO || NO | NO + * 4.0.0 | off | YES || NO | YES + * 4.0.0 | on | NO || NO | NO + * 4.0.0 | on | YES || NO | YES + * 5.0.0 | off | NO || YES | NO + * 5.0.0 | off | YES || YES | NO + * 5.0.0 | on | NO || YES | NO + * 5.0.0 | on | YES || YES | NO + * + * — If only XCTest supported parameterized tests… + */ + +#pragma mark - VTT Subtitles Capabilities Tests + +- (void)testShouldHaveVTTCapabilityForMissingVersionWithPairingLevelOffWithoutDLNA { + CAPTEST(kMediaPlayerSubtitleWebVTT, nil, DeviceServicePairingLevelOff, NO, YES); +} + +- (void)testShouldHaveVTTCapabilityForMissingVersionWithPairingLevelOffWithDLNA { + CAPTEST(kMediaPlayerSubtitleWebVTT, nil, DeviceServicePairingLevelOff, YES, YES); +} + +- (void)testShouldHaveVTTCapabilityForMissingVersionWithPairingLevelOnWithoutDLNA { + CAPTEST(kMediaPlayerSubtitleWebVTT, nil, DeviceServicePairingLevelOn, NO, YES); +} + +- (void)testShouldHaveVTTCapabilityForMissingVersionWithPairingLevelOnWithDLNA { + CAPTEST(kMediaPlayerSubtitleWebVTT, nil, DeviceServicePairingLevelOn, YES, YES); +} + +- (void)testShouldNotHaveVTTCapabilityForLegacyVersionWithPairingLevelOffWithoutDLNA { + CAPTEST(kMediaPlayerSubtitleWebVTT, kLegacyWebOSVersion, DeviceServicePairingLevelOff, NO, NO); +} + +- (void)testShouldNotHaveVTTCapabilityForLegacyVersionWithPairingLevelOffWithDLNA { + CAPTEST(kMediaPlayerSubtitleWebVTT, kLegacyWebOSVersion, DeviceServicePairingLevelOff, YES, NO); +} + +- (void)testShouldNotHaveVTTCapabilityForLegacyVersionWithPairingLevelOnWithoutDLNA { + CAPTEST(kMediaPlayerSubtitleWebVTT, kLegacyWebOSVersion, DeviceServicePairingLevelOn, NO, NO); +} + +- (void)testShouldNotHaveVTTCapabilityForLegacyVersionWithPairingLevelOnWithDLNA { + CAPTEST(kMediaPlayerSubtitleWebVTT, kLegacyWebOSVersion, DeviceServicePairingLevelOn, YES, NO); +} + +- (void)testShouldHaveVTTCapabilityForRecentVersionWithPairingLevelOffWithoutDLNA { + CAPTEST(kMediaPlayerSubtitleWebVTT, kRecentWebOSVersion, DeviceServicePairingLevelOff, NO, YES); +} + +- (void)testShouldHaveVTTCapabilityForRecentVersionWithPairingLevelOffWithDLNA { + CAPTEST(kMediaPlayerSubtitleWebVTT, kRecentWebOSVersion, DeviceServicePairingLevelOff, YES, YES); +} + +- (void)testShouldHaveVTTCapabilityForRecentVersionWithPairingLevelOnWithoutDLNA { + CAPTEST(kMediaPlayerSubtitleWebVTT, kRecentWebOSVersion, DeviceServicePairingLevelOn, NO, YES); +} + +- (void)testShouldHaveVTTCapabilityForRecentVersionWithPairingLevelOnWithDLNA { + CAPTEST(kMediaPlayerSubtitleWebVTT, kRecentWebOSVersion, DeviceServicePairingLevelOn, YES, YES); +} + +#pragma mark - SRT Subtitles Capabilities Tests + +- (void)testShouldNotHaveSRTCapabilityForMissingVersionWithPairingLevelOffWithoutDLNA { + CAPTEST(kMediaPlayerSubtitleSRT, nil, DeviceServicePairingLevelOff, NO, NO); +} + +- (void)testShouldNotHaveSRTCapabilityForMissingVersionWithPairingLevelOffWithDLNA { + CAPTEST(kMediaPlayerSubtitleSRT, nil, DeviceServicePairingLevelOff, YES, NO); +} + +- (void)testShouldNotHaveSRTCapabilityForMissingVersionWithPairingLevelOnWithoutDLNA { + CAPTEST(kMediaPlayerSubtitleSRT, nil, DeviceServicePairingLevelOn, NO, NO); +} + +- (void)testShouldNotHaveSRTCapabilityForMissingVersionWithPairingLevelOnWithDLNA { + CAPTEST(kMediaPlayerSubtitleSRT, nil, DeviceServicePairingLevelOn, YES, NO); +} + +- (void)testShouldNotHaveSRTCapabilityForLegacyVersionWithPairingLevelOffWithoutDLNA { + CAPTEST(kMediaPlayerSubtitleSRT, kLegacyWebOSVersion, DeviceServicePairingLevelOff, NO, NO); +} + +- (void)testShouldHaveSRTCapabilityForLegacyVersionWithPairingLevelOffWithDLNA { + CAPTEST(kMediaPlayerSubtitleSRT, kLegacyWebOSVersion, DeviceServicePairingLevelOff, YES, YES); +} + +- (void)testShouldNotHaveSRTCapabilityForLegacyVersionWithPairingLevelOnWithoutDLNA { + CAPTEST(kMediaPlayerSubtitleSRT, kLegacyWebOSVersion, DeviceServicePairingLevelOn, NO, NO); +} + +- (void)testShouldHaveSRTCapabilityForLegacyVersionWithPairingLevelOnWithDLNA { + CAPTEST(kMediaPlayerSubtitleSRT, kLegacyWebOSVersion, DeviceServicePairingLevelOn, YES, YES); +} + +- (void)testShouldNotHaveSRTCapabilityForRecentVersionWithPairingLevelOffWithoutDLNA { + CAPTEST(kMediaPlayerSubtitleSRT, kRecentWebOSVersion, DeviceServicePairingLevelOff, NO, NO); +} + +- (void)testShouldNotHaveSRTCapabilityForRecentVersionWithPairingLevelOffWithDLNA { + CAPTEST(kMediaPlayerSubtitleSRT, kRecentWebOSVersion, DeviceServicePairingLevelOff, YES, NO); +} + +- (void)testShouldNotHaveSRTCapabilityForRecentVersionWithPairingLevelOnWithoutDLNA { + CAPTEST(kMediaPlayerSubtitleSRT, kRecentWebOSVersion, DeviceServicePairingLevelOn, NO, NO); +} + +- (void)testShouldNotHaveSRTCapabilityForRecentVersionWithPairingLevelOnWithDLNA { + CAPTEST(kMediaPlayerSubtitleSRT, kRecentWebOSVersion, DeviceServicePairingLevelOn, YES, NO); +} + +#pragma mark - Subtitles Support Tests + +- (void)testPlayMediaWithSubtitlesShouldCallWebOSWebAppSessionForRecentVersion { + WebOSTVService *service = OCMPartialMock([WebOSTVService new]); + [self mockWebOSVersion:kRecentWebOSVersion onService:service]; + + id webAppSessionMock = OCMClassMock([WebOSWebAppSession class]); + OCMStub([service createWebAppSessionWithLaunchSession:OCMOCK_ANY + andService:OCMOCK_ANY]).andReturn(webAppSessionMock); + [OCMStub([webAppSessionMock joinWithSuccess:OCMOCK_ANY failure:OCMOCK_ANY]) andDo:^(NSInvocation *invocation) { + SuccessBlock joinSuccess = [invocation objectArgumentAtIndex:0]; + joinSuccess(webAppSessionMock); + }]; + OCMStub([webAppSessionMock mediaPlayer]).andReturn(webAppSessionMock); + + MediaInfo *mediaInfo = [self mediaInfoWithSubtitle]; + MediaPlayerSuccessBlock success = ^(MediaLaunchObject *mediaLaunchObject) {}; + FailureBlock failure = ^(NSError *error) {}; + + OCMExpect([webAppSessionMock playMediaWithMediaInfo:mediaInfo + shouldLoop:NO + success:success + failure:failure]); + [service playMediaWithMediaInfo:mediaInfo + shouldLoop:NO + success:success + failure:failure]; + + OCMVerifyAll(webAppSessionMock); +} + +- (void)testPlayMediaWithSubtitlesShouldCallDLNAServiceForLegacyVersion { + WebOSTVService *service = OCMPartialMock([WebOSTVService new]); + [self mockWebOSVersion:kLegacyWebOSVersion onService:service]; + + id dlnaServiceMock = OCMClassMock([DLNAService class]); + OCMStub([service dlnaService]).andReturn(dlnaServiceMock); + OCMStub([dlnaServiceMock mediaPlayer]).andReturn(dlnaServiceMock); + + MediaInfo *mediaInfo = [self mediaInfoWithSubtitle]; + MediaPlayerSuccessBlock success = ^(MediaLaunchObject *mediaLaunchObject) {}; + FailureBlock failure = ^(NSError *error) {}; + + OCMExpect([dlnaServiceMock playMediaWithMediaInfo:mediaInfo + shouldLoop:NO + success:success + failure:failure]); + [service playMediaWithMediaInfo:mediaInfo + shouldLoop:NO + success:success + failure:failure]; + + OCMVerifyAll(dlnaServiceMock); +} + +- (void)testPlayMediaWithSubtitlesShouldSendMediaViewerOpenCommandForLegacyVersionWithoutDLNA { + WebOSTVService *service = OCMPartialMock([WebOSTVService new]); + [self mockWebOSVersion:kLegacyWebOSVersion onService:service]; + + OCMStub([service dlnaService]).andReturn(nil); + + MediaInfo *mediaInfo = [self mediaInfoWithSubtitle]; + + id serviceCommandDelegateMock = OCMProtocolMock(@protocol(ServiceCommandDelegate)); + service.serviceCommandDelegate = serviceCommandDelegateMock; + OCMExpect([serviceCommandDelegateMock sendCommand:OCMOCK_NOTNIL + withPayload:[OCMArg checkWithBlock:(^BOOL(NSDictionary *payload) { + NSDictionary *expectedPayload = @{ + @"target": mediaInfo.url.absoluteString, + @"mimeType": mediaInfo.mimeType, + @"loop": @"false", + @"title": @"", + @"description": @"", + @"iconSrc": @"", + }; + XCTAssertEqualObjects(expectedPayload, payload); + return YES; + })] + toURL:[OCMArg checkWithBlock:^BOOL(NSURL *url) { + XCTAssertEqualObjects(@"ssap://media.viewer/open", + url.absoluteString); + return YES; + }]]); + + [service playMediaWithMediaInfo:mediaInfo + shouldLoop:NO + success:nil + failure:nil]; + + OCMVerifyAll(serviceCommandDelegateMock); +} + +#pragma mark - Unsupported Methods Tests + +- (void)testGetDurationShouldReturnNotSupportedError { + [self checkOperationShouldReturnNotSupportedErrorUsingBlock: + ^(SuccessBlock successVerifier, FailureBlock failureVerifier) { + WebOSTVService *service = [WebOSTVService new]; + [service getDurationWithSuccess:^(NSTimeInterval _) { + successVerifier(nil); + } + failure:failureVerifier]; + }]; +} + +- (void)testGetMediaMetadataShouldReturnNotSupportedError { + [self checkOperationShouldReturnNotSupportedErrorUsingBlock: + ^(SuccessBlock successVerifier, FailureBlock failureVerifier) { + WebOSTVService *service = [WebOSTVService new]; + [service getMediaMetaDataWithSuccess:successVerifier + failure:failureVerifier]; + }]; +} + +#pragma mark - ServiceConfig Setter Tests (Base <=> WebOS) + +/* The setter tests below test different cases of setting various service + * config objects and whether those throw an exception when a client key from + * @c WebOSTVServiceConfig would be lost. + */ + +- (void)testSwitching_Base_To_WebOSWithoutKey_ServiceConfigShouldNotThrowException { + ServiceConfig *config = [ServiceConfig new]; + WebOSTVService *service = [[WebOSTVService alloc] initWithServiceConfig:config]; + + WebOSTVServiceConfig *webosConfig = [WebOSTVServiceConfig new]; + XCTAssertNoThrowSpecificNamed(service.serviceConfig = webosConfig, + NSException, + NSInternalInconsistencyException, + @"Should not throw exception"); +} + +- (void)testSwitching_Base_To_WebOSWithKey_ServiceConfigShouldNotThrowException { + ServiceConfig *config = [ServiceConfig new]; + WebOSTVService *service = [[WebOSTVService alloc] initWithServiceConfig:config]; + + WebOSTVServiceConfig *webosConfig = [WebOSTVServiceConfig new]; + webosConfig.clientKey = kClientKey; + XCTAssertNoThrowSpecificNamed(service.serviceConfig = webosConfig, + NSException, + NSInternalInconsistencyException, + @"Should not throw exception"); +} + +- (void)testSwitching_WebOSWithoutKey_To_Base_ServiceConfigShouldNotThrowException { + WebOSTVServiceConfig *webosConfig = [WebOSTVServiceConfig new]; + WebOSTVService *service = [[WebOSTVService alloc] initWithServiceConfig:webosConfig]; + + ServiceConfig *config = [ServiceConfig new]; + XCTAssertNoThrowSpecificNamed(service.serviceConfig = config, + NSException, + NSInternalInconsistencyException, + @"Should not throw exception"); +} + +- (void)testSwitching_WebOSWithKey_To_Base_ServiceConfigShouldThrowException { + WebOSTVServiceConfig *webosConfig = [WebOSTVServiceConfig new]; + webosConfig.clientKey = kClientKey; + WebOSTVService *service = [[WebOSTVService alloc] initWithServiceConfig:webosConfig]; + + ServiceConfig *config = [ServiceConfig new]; + XCTAssertThrowsSpecificNamed(service.serviceConfig = config, + NSException, + NSInternalInconsistencyException, + @"Should throw exception because the key will disappear"); +} + +#pragma mark - ServiceConfig Setter Tests (WebOS <=> WebOS) + +- (void)testSwitching_WebOSWithoutKey_To_WebOSWithoutKey_ServiceConfigShouldNotThrowException { + WebOSTVServiceConfig *webosConfig = [WebOSTVServiceConfig new]; + WebOSTVService *service = [[WebOSTVService alloc] initWithServiceConfig:webosConfig]; + + WebOSTVServiceConfig *webosConfig2 = [WebOSTVServiceConfig new]; + XCTAssertNoThrowSpecificNamed(service.serviceConfig = webosConfig2, + NSException, + NSInternalInconsistencyException, + @"Should not throw exception"); +} + +- (void)testSwitching_WebOSWithoutKey_To_WebOSWithKey_ServiceConfigShouldNotThrowException { + WebOSTVServiceConfig *webosConfig = [WebOSTVServiceConfig new]; + WebOSTVService *service = [[WebOSTVService alloc] initWithServiceConfig:webosConfig]; + + WebOSTVServiceConfig *webosConfig2 = [WebOSTVServiceConfig new]; + webosConfig2.clientKey = kClientKey; + XCTAssertNoThrowSpecificNamed(service.serviceConfig = webosConfig2, + NSException, + NSInternalInconsistencyException, + @"Should not throw exception"); +} + +- (void)testSwitching_WebOSWithKey_To_WebOSWithKey_ServiceConfigShouldNotThrowException { + WebOSTVServiceConfig *webosConfig = [WebOSTVServiceConfig new]; + webosConfig.clientKey = kClientKey; + WebOSTVService *service = [[WebOSTVService alloc] initWithServiceConfig:webosConfig]; + + WebOSTVServiceConfig *webosConfig2 = [WebOSTVServiceConfig new]; + webosConfig2.clientKey = @"anotherKey"; + XCTAssertNoThrowSpecificNamed(service.serviceConfig = webosConfig2, + NSException, + NSInternalInconsistencyException, + @"Should not throw exception"); +} + +- (void)testSwitching_WebOSWithKey_To_WebOSWithoutKey_ServiceConfigShouldThrowException { + WebOSTVServiceConfig *webosConfig = [WebOSTVServiceConfig new]; + webosConfig.clientKey = kClientKey; + WebOSTVService *service = [[WebOSTVService alloc] initWithServiceConfig:webosConfig]; + + WebOSTVServiceConfig *webosConfig2 = [WebOSTVServiceConfig new]; + XCTAssertThrowsSpecificNamed(service.serviceConfig = webosConfig2, + NSException, + NSInternalInconsistencyException, + @"Should throw exception because the key will disappear"); +} + +#pragma mark - Helpers + +- (void)checkShouldHave:(BOOL)shouldHave + subtitleCapability:(NSString *)subtitleCapability + forVersion:(NSString *)version + withPairingLevel:(DeviceServicePairingLevel)pairingLevel + andUsingDLNA:(BOOL)usingDLNA { + DiscoveryManager *discoveryManager = [DiscoveryManager sharedManager]; + DeviceServicePairingLevel oldPairingLevel = discoveryManager.pairingLevel; + discoveryManager.pairingLevel = pairingLevel; + + WebOSTVService *service = OCMPartialMock([WebOSTVService new]); + id dlnaServiceStub = usingDLNA ? OCMClassMock([DLNAService class]) : nil; + OCMStub([service dlnaService]).andReturn(dlnaServiceStub); + [self mockWebOSVersion:version onService:service]; + + XCTAssertEqual([service.capabilities containsObject:subtitleCapability], + shouldHave); + + discoveryManager.pairingLevel = oldPairingLevel; +} + +- (void)mockWebOSVersion:(NSString *)version + onService:(WebOSTVService *)service { + if (version) { + id serviceDescriptionStub = OCMClassMock([ServiceDescription class]); + NSDictionary *headers = @{ + @"Server": [NSString stringWithFormat:@"A/%@ x", version]}; + OCMStub([serviceDescriptionStub locationResponseHeaders]).andReturn( + headers); + OCMStub([(ServiceDescription *) serviceDescriptionStub version]).andReturn( + version); + service.serviceDescription = serviceDescriptionStub; + } +} + +- (MediaInfo *)mediaInfoWithSubtitle { + NSURL *subtitleURL = [NSURL URLWithString:@"http://example.com/"]; + SubtitleInfo *subtitleInfo = [SubtitleInfo infoWithURL:subtitleURL + andBlock:^(SubtitleInfoBuilder *builder) { + builder.language = @"en"; + builder.label = @"test"; + }]; + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:[NSURL URLWithString:@"http://url"] + mimeType:@"video/mp4"]; + mediaInfo.subtitleInfo = subtitleInfo; + + return mediaInfo; +} + +@end diff --git a/core/ConnectSDKTests/Services/getmute_response_sonos.xml b/core/ConnectSDKTests/Services/getmute_response_sonos.xml new file mode 100644 index 00000000..e68cb97f --- /dev/null +++ b/core/ConnectSDKTests/Services/getmute_response_sonos.xml @@ -0,0 +1,16 @@ + +1 \ No newline at end of file diff --git a/core/ConnectSDKTests/Services/getmute_response_xbox.xml b/core/ConnectSDKTests/Services/getmute_response_xbox.xml new file mode 100644 index 00000000..2f010c9a --- /dev/null +++ b/core/ConnectSDKTests/Services/getmute_response_xbox.xml @@ -0,0 +1,17 @@ + + +1 \ No newline at end of file diff --git a/core/ConnectSDKTests/Services/getpositioninfo_response_sonos.xml b/core/ConnectSDKTests/Services/getpositioninfo_response_sonos.xml new file mode 100644 index 00000000..3254b65d --- /dev/null +++ b/core/ConnectSDKTests/Services/getpositioninfo_response_sonos.xml @@ -0,0 +1,16 @@ + +10:08:52<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="-1" parentID="-1" restricted="true"><res protocolInfo="http-get:*:audio/mp4:*" duration="0:00:52">http://ec2-54-201-108-205.us-west-2.compute.amazonaws.com/samples/media/video.mp4</res><r:streamContent></r:streamContent><dc:title>Sintel Trailer</dc:title><upnp:class>object.item.audioItem.musicTrack</upnp:class><dc:creator>Durian Open Movie Team</dc:creator><r:albumArtist>Durian Open Movie Team</r:albumArtist></item></DIDL-Lite>http://ec2-54-201-108-205.us-west-2.compute.amazonaws.com/samples/media/video.mp40:01:06NOT_IMPLEMENTED21474836472147483647 \ No newline at end of file diff --git a/core/ConnectSDKTests/Services/getpositioninfo_response_xbox.xml b/core/ConnectSDKTests/Services/getpositioninfo_response_xbox.xml new file mode 100644 index 00000000..5f2c6b52 --- /dev/null +++ b/core/ConnectSDKTests/Services/getpositioninfo_response_xbox.xml @@ -0,0 +1,17 @@ + + +10:08:52<DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:dc="http://purl.org/dc/elements/1.1/"><item id="0" parentID="0" restricted="0"><dc:title>Sintel Character Design</dc:title><dc:description>Blender Open Movie Project</dc:description><res protocolInfo="http-get:*:video/mp4:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000">http://ec2-54-201-108-205.us-west-2.compute.amazonaws.com/samples/media/video.mp4</res><upnp:albumArtURI>http://ec2-54-201-108-205.us-west-2.compute.amazonaws.com/samples/media/videoIcon.jpg</upnp:albumArtURI><upnp:class>object.item.videoItem</upnp:class></item></DIDL-Lite>http://ec2-54-201-108-205.us-west-2.compute.amazonaws.com/samples/media/video.mp40:01:06NOT_IMPLEMENTED21474836472147483647 \ No newline at end of file diff --git a/core/ConnectSDKTests/Services/gettransportinfo_response_sonos.xml b/core/ConnectSDKTests/Services/gettransportinfo_response_sonos.xml new file mode 100644 index 00000000..f3216615 --- /dev/null +++ b/core/ConnectSDKTests/Services/gettransportinfo_response_sonos.xml @@ -0,0 +1,16 @@ + +PLAYINGOK1 \ No newline at end of file diff --git a/core/ConnectSDKTests/Services/gettransportinfo_response_xbox.xml b/core/ConnectSDKTests/Services/gettransportinfo_response_xbox.xml new file mode 100644 index 00000000..3c1a07c8 --- /dev/null +++ b/core/ConnectSDKTests/Services/gettransportinfo_response_xbox.xml @@ -0,0 +1,17 @@ + + +PLAYINGOK1 \ No newline at end of file diff --git a/core/ConnectSDKTests/Services/getvolume_response_sonos.xml b/core/ConnectSDKTests/Services/getvolume_response_sonos.xml new file mode 100644 index 00000000..e5a4c4bb --- /dev/null +++ b/core/ConnectSDKTests/Services/getvolume_response_sonos.xml @@ -0,0 +1,16 @@ + +14 \ No newline at end of file diff --git a/core/ConnectSDKTests/Services/getvolume_response_xbox.xml b/core/ConnectSDKTests/Services/getvolume_response_xbox.xml new file mode 100644 index 00000000..5306e35b --- /dev/null +++ b/core/ConnectSDKTests/Services/getvolume_response_xbox.xml @@ -0,0 +1,17 @@ + + +14 \ No newline at end of file diff --git a/core/ConnectSDKTests/Services/upnperror_response_sonos.xml b/core/ConnectSDKTests/Services/upnperror_response_sonos.xml new file mode 100644 index 00000000..c68b50a0 --- /dev/null +++ b/core/ConnectSDKTests/Services/upnperror_response_sonos.xml @@ -0,0 +1,16 @@ + +s:ClientUPnPError401 \ No newline at end of file diff --git a/core/ConnectSDKTests/Services/upnperror_response_xbox.xml b/core/ConnectSDKTests/Services/upnperror_response_xbox.xml new file mode 100644 index 00000000..312271e7 --- /dev/null +++ b/core/ConnectSDKTests/Services/upnperror_response_xbox.xml @@ -0,0 +1,17 @@ + + +SOAP-ENV:ClientUPnPError401Invalid Action \ No newline at end of file diff --git a/core/ConnectSDKTests/SupportingFiles/Info.plist b/core/ConnectSDKTests/SupportingFiles/Info.plist new file mode 100644 index 00000000..cb399fd9 --- /dev/null +++ b/core/ConnectSDKTests/SupportingFiles/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.connectsdk.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/core/ConnectSDKTests/SupportingFiles/the-san-francisco-peaks-of-flagstaff-718x544.jpg b/core/ConnectSDKTests/SupportingFiles/the-san-francisco-peaks-of-flagstaff-718x544.jpg new file mode 100644 index 00000000..25948967 Binary files /dev/null and b/core/ConnectSDKTests/SupportingFiles/the-san-francisco-peaks-of-flagstaff-718x544.jpg differ diff --git a/core/ConnectSDKTests/XCTestCase+Common.h b/core/ConnectSDKTests/XCTestCase+Common.h new file mode 100644 index 00000000..cb01244a --- /dev/null +++ b/core/ConnectSDKTests/XCTestCase+Common.h @@ -0,0 +1,39 @@ +// +// XCTestCase+Common.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-07-31. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "Capability.h" + +@interface XCTestCase (Common) + +/** + * A block type that should do an action on an SUT (system under test) and use + * the provided block verifiers as the callbacks to verify they are + * called/not called according to the check method. + */ +typedef void(^ActionBlock)(SuccessBlock successVerifier, + FailureBlock failureVerifier); + +/** + * Checks that the @c FailureBlock of the operation done in the @c block is + * called with the @c ConnectStatusCodeNotSupported error code. + */ +- (void)checkOperationShouldReturnNotSupportedErrorUsingBlock:(ActionBlock)block; + +@end diff --git a/core/ConnectSDKTests/XCTestCase+Common.m b/core/ConnectSDKTests/XCTestCase+Common.m new file mode 100644 index 00000000..6c11e2bb --- /dev/null +++ b/core/ConnectSDKTests/XCTestCase+Common.m @@ -0,0 +1,55 @@ +// +// XCTestCase+Common.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-07-31. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "XCTestCase+Common.h" + +#import "ConnectError.h" + +@implementation XCTestCase (Common) + +- (void)checkOperationShouldReturnNotSupportedErrorUsingBlock:(ActionBlock)block { + NSDictionary *errorInfo = @{NSLocalizedDescriptionKey:@"This feature is not supported."}; + NSError *unsupportedError = [NSError errorWithDomain:ConnectErrorDomain + code:ConnectStatusCodeNotSupported + userInfo:errorInfo]; + + ActionBlock actionBlock = ^(SuccessBlock successVerifier, FailureBlock failureVerifier) { + SuccessBlock success = ^(id response) { + XCTFail(@"should be no success"); + successVerifier(nil); + }; + FailureBlock failure = ^(NSError *error) { + XCTAssertEqualObjects(error, unsupportedError, @"Error is wrong"); + failureVerifier(nil); + }; + block(success, failure); + }; + + __block BOOL verified = NO; + void(^blockCallVerifier)(id) = ^(id object) { + verified = YES; + }; + // either block should be called + actionBlock(blockCallVerifier, blockCallVerifier); + + XCTAssertTrue(verified, @"failure block should be called"); +} + +@end diff --git a/core/Devices/ConnectableDevice.h b/core/Devices/ConnectableDevice.h new file mode 100644 index 00000000..35af8d52 --- /dev/null +++ b/core/Devices/ConnectableDevice.h @@ -0,0 +1,211 @@ +// +// ConnectableDevice.h +// Connect SDK +// +// Created by Jeremy White on 12/9/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "ServiceDescription.h" +#import "DeviceService.h" +#import "ConnectableDeviceDelegate.h" +#import "DeviceServiceDelegate.h" +#import "JSONObjectCoding.h" + +#import "Launcher.h" +#import "VolumeControl.h" +#import "TVControl.h" +#import "MediaControl.h" +#import "ExternalInputControl.h" +#import "ToastControl.h" +#import "TextInputControl.h" +#import "MediaPlayer.h" +#import "WebAppLauncher.h" +#import "KeyControl.h" +#import "MouseControl.h" +#import "PowerControl.h" +#import "ScreenMirroringControl.h" +#import "RemoteCameraControl.h" + +/*! + * ###Overview + * ConnectableDevice serves as a normalization layer between your app and each of the device's services. It consolidates a lot of key data about the physical device and provides access to underlying functionality. + * + * ###In Depth + * ConnectableDevice consolidates some key information about the physical device, including model name, friendly name, ip address, connected DeviceService names, etc. In some cases, it is not possible to accurately select which DeviceService has the best friendly name, model name, etc. In these cases, the values of these properties are dependent upon the order of DeviceService discovery. + * + * To be informed of any ready/pairing/disconnect messages from each of the DeviceService, you must set a delegate. + * + * ConnectableDevice exposes capabilities that exist in the underlying DeviceServices such as TV Control, Media Player, Media Control, Volume Control, etc. These capabilities, when accessed through the ConnectableDevice, will be automatically chosen from the most suitable DeviceService by using that DeviceService's CapabilityPriorityLevel. + */ +@interface ConnectableDevice : NSObject + +// @cond INTERNAL ++ (instancetype) connectableDeviceWithDescription:(ServiceDescription *)description; +@property (nonatomic, strong) ServiceDescription *serviceDescription; +// @endcond + +/*! + * Delegate which should receive messages on certain events. + */ +@property (nonatomic, weak) id delegate; + +#pragma mark - General info + +/*! Universally unique ID of this particular ConnectableDevice object, persists between sessions in ConnectableDeviceStore for connected devices */ +@property (nonatomic, readonly) NSString *id; + +/*! Current IP address of the ConnectableDevice. */ +@property (nonatomic, readonly) NSString *address; + +/*! An estimate of the ConnectableDevice's current friendly name. */ +@property (nonatomic, readonly) NSString *friendlyName; + +/*! An estimate of the ConnectableDevice's current model name. */ +@property (nonatomic, readonly) NSString *modelName; + +/*! An estimate of the ConnectableDevice's current model number. */ +@property (nonatomic, readonly) NSString *modelNumber; + +/*! Last IP address this ConnectableDevice was discovered at. */ +@property (nonatomic, copy) NSString *lastKnownIPAddress; + +/*! Name of the last wireless network this ConnectableDevice was discovered on. */ +@property (nonatomic, copy) NSString *lastSeenOnWifi; + +/*! Last time (in seconds from 1970) that this ConnectableDevice was connected to. */ +@property (nonatomic) double lastConnected; + +/*! Last time (in seconds from 1970) that this ConnectableDevice was detected. */ +@property (nonatomic) double lastDetection; + +// @cond INTERNAL +- (NSString *) connectedServiceNames; +// @endcond + +#pragma mark - Connection + +/*! + * Enumerates through all DeviceServices and attempts to connect to each of them. When all of a ConnectableDevice's DeviceServices are ready to receive commands, the ConnectableDevice will send a connectableDeviceReady: message to its delegate. + * + * It is always necessary to call connect on a ConnectableDevice, even if it contains no connectable DeviceServices. + */ +- (void) connect; + +/*! Enumerates through all DeviceServices and attempts to disconnect from each of them. */ +- (void) disconnect; + +/*! Whether the device has any DeviceServices that require an active connection (websocket, HTTP registration, etc) */ +@property (nonatomic, readonly) BOOL isConnectable; + +/*! Whether all the DeviceServices are connected. */ +@property (nonatomic, readonly) BOOL connected; + +#pragma mark - Service management + +/*! Array of all currently discovered DeviceServices this ConnectableDevice has associated with it. */ +@property (nonatomic, readonly) NSArray *services; + +/*! Whether the ConnectableDevice has any running DeviceServices associated with it. */ +@property (nonatomic, readonly) BOOL hasServices; + +/*! + * Adds a DeviceService to the ConnectableDevice instance. Only one instance of each DeviceService type (webOS, Netcast, etc) may be attached to a single ConnectableDevice instance. If a device contains your service type already, your service will not be added. + * + * @param service DeviceService to be added to the ConnectableDevice + */ +- (void) addService:(DeviceService *)service; + +/*! + * Removes a DeviceService from the ConnectableDevice instance. serviceId is used as the identifier because only one instance of each DeviceService type may be attached to a single ConnectableDevice instance. + * + * @param serviceId Id of the DeviceService to be removed from the ConnectableDevice + */ +- (void) removeServiceWithId:(NSString *)serviceId; + +/*! + * Obtains a service from the device with the provided serviceId + * + * @param serviceId Service ID of the targeted DeviceService (webOS, Netcast, DLNA, etc) + * @return DeviceService with the specified serviceId or nil, if none exists + */ +- (DeviceService *)serviceWithName:(NSString *)serviceId; + +#pragma mark - Capabilities + +#pragma mark Info + +/*! A combined list of all capabilities that are supported among the detected DeviceServices. */ +@property (nonatomic, readonly) NSArray *capabilities; + +/*! + * Test to see if the capabilities array contains a given capability. See the individual Capability classes for acceptable capability values. + * + * It is possible to append a wildcard search term `.Any` to the end of the search term. This method will return true for capabilities that match the term up to the wildcard. + * + * Example: `Launcher.App.Any` + * + * @param capability Capability to test against + */ +- (BOOL) hasCapability:(NSString *)capability; + +/*! + * Test to see if the capabilities array contains a given set of capabilities. See the individual Capability classes for acceptable capability values. + * + * See hasCapability: for a description of the wildcard feature provided by this method. + * + * @param capabilities Array of capabilities to test against + */ +- (BOOL) hasCapabilities:(NSArray *)capabilities; + +/*! + * Test to see if the capabilities array contains at least one capability in a given set of capabilities. See the individual Capability classes for acceptable capability values. + * + * See hasCapability: for a description of the wildcard feature provided by this method. + * + * @param capabilities Array of capabilities to test against + */ +- (BOOL) hasAnyCapability:(NSArray *)capabilities; + +/*! + * Set the type of pairing for the ConnectableDevice services. By default the value will be DeviceServicePairingTypeNone + * + * For WebOSTV's If pairingType is set to DeviceServicePairingTypeFirstScreen(default), the device will prompt the user to pair when connecting to the ConnectableDevice. + * + * If pairingType is set to DeviceServicePairingTypePinCode, the device will prompt the user to enter a pin to pair when connecting to the ConnectableDevice. + * + * @param pairingType value to be set for the device service from DeviceServicePairingType + */ +- (void)setPairingType:(DeviceServicePairingType)pairingType; + +#pragma mark Accessors + +- (id) launcher; /*! Accessor for highest priority Launcher object */ +- (id) externalInputControl; /*! Accessor for highest priority ExternalInputControl object */ +- (id) mediaPlayer; /*! Accessor for highest priority MediaPlayer object */ +- (id) mediaControl; /*! Accessor for highest priority MediaControl object */ +- (id)volumeControl; /*! Accessor for highest priority VolumeControl object */ +- (id)tvControl; /*! Accessor for highest priority TVControl object */ +- (id) keyControl; /*! Accessor for highest priority KeyControl object */ +- (id) textInputControl; /*! Accessor for highest priority TextInputControl object */ +- (id)mouseControl; /*! Accessor for highest priority MouseControl object */ +- (id)powerControl; /*! Accessor for highest priority PowerControl object */ +- (id) toastControl; /*! Accessor for highest priority ToastControl object */ +- (id) webAppLauncher; /*! Accessor for highest priority WebAppLauncher object */ +- (id)screenMirroringControl; /*! Accessor for highest priority ScreenMirroring object */ +- (id)remoteCameraControl; /*! Accessor for highest priority RemoteCamera object */ + +@end diff --git a/core/Devices/ConnectableDevice.m b/core/Devices/ConnectableDevice.m new file mode 100644 index 00000000..060111c6 --- /dev/null +++ b/core/Devices/ConnectableDevice.m @@ -0,0 +1,960 @@ +// +// ConnectableDevice.m +// Connect SDK +// +// Created by Jeremy White on 12/9/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ConnectableDevice.h" +#import "DLNAService.h" +#import "MediaControl.h" +#import "ExternalInputControl.h" +#import "ToastControl.h" +#import "TextInputControl.h" +#import "CTGuid.h" +#import "DiscoveryManager.h" + +@implementation ConnectableDevice +{ + NSMutableDictionary *_services; +} + +@synthesize serviceDescription = _consolidatedServiceDescription; +@synthesize delegate = _delegate; +@synthesize id = _id; + +- (instancetype) init +{ + self = [super init]; + + if (self) + { + _consolidatedServiceDescription = [ServiceDescription new]; + _services = [[NSMutableDictionary alloc] init]; + } + + return self; +} + +- (instancetype) initWithDescription:(ServiceDescription *)description +{ + self = [self init]; + + if (self) + { + _consolidatedServiceDescription = description; + } + + return self; +} + +- (instancetype) initWithJSONObject:(NSDictionary *)dict +{ + self = [self init]; + + if (self) + { + _id = dict[@"id"]; + _lastKnownIPAddress = dict[@"lastKnownIPAddress"]; + _lastSeenOnWifi = dict[@"lastSeenOnWifi"]; + + id lastConnected = dict[@"lastConnected"]; + if (lastConnected && ![lastConnected isKindOfClass:[NSNull class]]) + _lastConnected = [lastConnected doubleValue]; + + id lastDetection = dict[@"lastDetection"]; + if (lastDetection && ![lastDetection isKindOfClass:[NSNull class]]) + _lastDetection = [lastDetection doubleValue]; + + if (!self.address) + _consolidatedServiceDescription.address = _lastKnownIPAddress; + } + + return self; +} + +- (NSDictionary *) toJSONObject +{ + NSMutableDictionary *jsonObject = [NSMutableDictionary new]; + + if (self.id) jsonObject[@"id"] = self.id; + if (self.friendlyName) jsonObject[@"friendlyName"] = self.friendlyName; + if (self.lastKnownIPAddress) jsonObject[@"lastKnownIPAddress"] = self.lastKnownIPAddress; + if (self.lastSeenOnWifi) jsonObject[@"lastSeenOnWifi"] = self.lastSeenOnWifi; + if (self.lastConnected) jsonObject[@"lastConnected"] = @(self.lastConnected); + if (self.lastDetection) jsonObject[@"lastDetection"] = @(self.lastDetection); + + NSMutableDictionary *services = [NSMutableDictionary new]; + + [self.services enumerateObjectsUsingBlock:^(DeviceService *service, NSUInteger idx, BOOL *stop) + { + NSDictionary *serviceJSON = [service toJSONObject]; + [services setObject:serviceJSON forKey:service.serviceDescription.UUID]; + }]; + + if (services.count > 0) + jsonObject[@"services"] = [NSDictionary dictionaryWithDictionary:services]; + + return jsonObject; +} + ++ (instancetype) connectableDeviceWithDescription:(ServiceDescription *)description +{ + return [[ConnectableDevice alloc] initWithDescription:description]; +} + +- (void) setDelegate:(id)delegate +{ + _delegate = delegate; + + if (_delegate && [self.delegate respondsToSelector:@selector(connectableDeviceConnectionRequired:forService:)]) + { + [_services enumerateKeysAndObjectsUsingBlock:^(id key, DeviceService *service, BOOL *stop) { + if (service.isConnectable && !service.connected) + [_delegate connectableDeviceConnectionRequired:self forService:service]; + }]; + } +} + +#pragma mark - General info + +- (NSString *) id +{ + if (!_id) + _id = [[CTGuid randomGuid] stringValueWithFormat:CTGuidFormatDashed]; + + return _id; +} + +- (NSString *) address +{ + return _consolidatedServiceDescription.address; +} + +- (NSString *) friendlyName +{ + return _consolidatedServiceDescription.friendlyName; +} + +- (NSString *) modelName +{ + return _consolidatedServiceDescription.modelName; +} + +- (NSString *) modelNumber +{ + return _consolidatedServiceDescription.modelNumber; +} + +- (NSString *) connectedServiceNames +{ + __block NSString *serviceNames = @""; + + if (_services.count == 0) + return serviceNames; + + [_services enumerateKeysAndObjectsUsingBlock:^(NSString * key, DeviceService *service, BOOL *stop) { + serviceNames = [serviceNames stringByAppendingString:[NSString stringWithFormat:@"%@, ", key]]; + }]; + + serviceNames = [serviceNames substringToIndex:serviceNames.length - 2]; + + return serviceNames; +} + +- (BOOL) connected +{ + __block int connectedCount = 0; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, DeviceService *service, BOOL *stop) + { + if (!service.isConnectable) + connectedCount++; + else + { + if (service.connected) + connectedCount++; + } + }]; + + return connectedCount >= _services.count; +} + +- (void) connect +{ + if (self.connected) + { + dispatch_on_main(^{ [self.delegate connectableDeviceReady:self]; }); + } else + { + [_services enumerateKeysAndObjectsUsingBlock:^(id key, DeviceService *service, BOOL *stop) + { + if (!service.connected) + [service connect]; + }]; + } + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(disconnect) name:kConnectSDKWirelessSSIDChanged object:nil]; +} + +- (void) disconnect +{ + [_services enumerateKeysAndObjectsUsingBlock:^(id key, DeviceService *service, BOOL *stop) + { + if (service.connected) + [service disconnect]; + }]; + + [[NSNotificationCenter defaultCenter] removeObserver:self name:kConnectSDKWirelessSSIDChanged object:nil]; + + dispatch_on_main(^{ [self.delegate connectableDeviceDisconnected:self withError:nil]; }); +} + +- (BOOL) isConnectable +{ + __block BOOL connectable = NO; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, DeviceService *service, BOOL *stop) + { + if (service.isConnectable) + { + connectable = YES; + *stop = YES; + } + }]; + + return connectable; +} + +- (int) connectedServiceCount +{ + __block int count = 0; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, DeviceService *service, BOOL *stop) + { + if ([service isConnectable]) + { + if (service.connected) + count++; + } else + { + count++; + } + + }]; + + return count; +} + +- (int) connectableServiceCount +{ + __block int count = 0; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, DeviceService *service, BOOL *stop) + { + if ([service isConnectable]) + count++; + }]; + + return count; +} + +#pragma mark - Service management + +- (NSArray *) services +{ + return [_services allValues]; +} + +- (BOOL) hasServices +{ + return _services.count > 0; +} + +- (void) addService:(DeviceService *)service +{ + DeviceService *existingService = [_services objectForKey:service.serviceName]; + + NSArray *oldCapabilities = self.capabilities; + + if (existingService) + { + if (service.serviceDescription.lastDetection > existingService.serviceDescription.lastDetection) + { + if (existingService.connected) + [existingService disconnect]; + + DLog(@"Removing %@ (%@)", existingService.serviceDescription.friendlyName, existingService.serviceName); + [self removeServiceWithId:existingService.serviceName]; + } else + { + DLog(@"Ignoring %@ (%@)", service.serviceDescription.friendlyName, service.serviceName); + return; + } + } + + [_services setObject:service forKey:service.serviceName]; + + if (service.delegate == nil) + service.delegate = self; + + if (service.isConnectable && !service.connected) + { + if (self.delegate && [self.delegate respondsToSelector:@selector(connectableDeviceConnectionRequired:forService:)]) + dispatch_on_main(^{ [_delegate connectableDeviceConnectionRequired:self forService:service]; }); + } + + [self updateCapabilitiesList:oldCapabilities]; + + [self updateConsolidatedServiceDescription:service.serviceDescription]; +} + +- (void) removeServiceWithId:(NSString *)serviceId +{ + DeviceService *service = [_services objectForKey:serviceId]; + + if (service == nil) + return; + + NSArray *oldCapabilities = self.capabilities; + + [service disconnect]; + + [_services removeObjectForKey:serviceId]; + + [self updateCapabilitiesList:oldCapabilities]; +} + +- (void) updateCapabilitiesList:(NSArray *)oldCapabilities +{ + NSArray *newCapabilities = self.capabilities; + + NSMutableArray *removedCapabilities = [NSMutableArray new]; + + [oldCapabilities enumerateObjectsUsingBlock:^(NSString *capability, NSUInteger idx, BOOL *stop) { + if (![newCapabilities containsObject:capability]) + [removedCapabilities addObject:capability]; + }]; + + NSMutableArray *addedCapabilities = [NSMutableArray new]; + + [newCapabilities enumerateObjectsUsingBlock:^(NSString *capability, NSUInteger idx, BOOL *stop) { + if (![oldCapabilities containsObject:capability]) + [addedCapabilities addObject:capability]; + }]; + + NSArray *added = [NSArray arrayWithArray:addedCapabilities]; + NSArray *removed = [NSArray arrayWithArray:removedCapabilities]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(connectableDevice:capabilitiesAdded:removed:)]) + dispatch_on_main(^{ [self.delegate connectableDevice:self capabilitiesAdded:added removed:removed]; }); +} + +- (void) updateConsolidatedServiceDescription:(ServiceDescription *)serviceDescription +{ + if (serviceDescription.address) + _consolidatedServiceDescription.address = serviceDescription.address; + + if (serviceDescription.friendlyName) + _consolidatedServiceDescription.friendlyName = serviceDescription.friendlyName; + + if (serviceDescription.modelName) + _consolidatedServiceDescription.modelName = serviceDescription.modelName; + + if (serviceDescription.modelNumber) + _consolidatedServiceDescription.modelNumber = serviceDescription.modelNumber; +} + +- (DeviceService *)serviceWithName:(NSString *)serviceId +{ + __block DeviceService *foundService; + + [_services enumerateKeysAndObjectsUsingBlock:^(NSString *id, DeviceService *service, BOOL *stop) + { + if ([id isEqualToString:serviceId]) + { + foundService = service; + *stop = YES; + } + }]; + + return foundService; +} + +#pragma mark - DeviceServiceDelegate +#pragma mark Connection + +- (void)deviceServiceConnectionRequired:(DeviceService *)service +{ + if (self.delegate && [self.delegate respondsToSelector:@selector(connectableDeviceConnectionRequired:forService:)]) + dispatch_on_main(^{ [self.delegate connectableDeviceConnectionRequired:self forService:service]; }); +} + +- (void)deviceServiceConnectionSuccess:(DeviceService *)service +{ + if (self.delegate && [self.delegate respondsToSelector:@selector(connectableDeviceConnectionSuccess:forService:)]) + dispatch_on_main(^{ [self.delegate connectableDeviceConnectionSuccess:self forService:service]; }); + + if (self.connected) + { + [[[DiscoveryManager sharedManager] deviceStore] addDevice:self]; + + dispatch_on_main(^{ [self.delegate connectableDeviceReady:self]; }); + + self.lastConnected = [[NSDate date] timeIntervalSince1970]; + } +} + +- (void)deviceService:(DeviceService *)service didFailConnectWithError:(NSError *)error +{ + if (self.delegate && [self.delegate respondsToSelector:@selector(connectableDevice:connectionFailedWithError:)]) + dispatch_on_main(^{ [self.delegate connectableDevice:self connectionFailedWithError:error]; }); + + if (self.delegate && [self.delegate respondsToSelector:@selector(connectableDevice:service:didFailConnectWithError:)]) + dispatch_on_main(^{ [self.delegate connectableDevice:self service:service didFailConnectWithError:error]; }); +} + +- (void)deviceService:(DeviceService *)service disconnectedWithError:(NSError *)error +{ + // TODO: need to aggregate errors between disconnects + if ([self connectedServiceCount] == 0 || _services.count == 0) + dispatch_on_main(^{ [self.delegate connectableDeviceDisconnected:self withError:error]; }); + + if (self.delegate && [self.delegate respondsToSelector:@selector(connectableDevice:service:disconnectedWithError:)]) + dispatch_on_main(^{ [self.delegate connectableDevice:self service:service disconnectedWithError:error]; }); +} + +#pragma mark Pairing + +- (void)setPairingType:(DeviceServicePairingType)pairingType { + [self.services enumerateObjectsUsingBlock:^(DeviceService *service, NSUInteger serviceIdx, BOOL *serviceStop) + { + service.pairingType = pairingType; + }]; +} + +- (void)deviceService:(DeviceService *)service pairingRequiredOfType:(DeviceServicePairingType)pairingType withData:(id)pairingData +{ + if (self.delegate) + { + if ([self.delegate respondsToSelector:@selector(connectableDevice:service:pairingRequiredOfType:withData:)]) + dispatch_on_main(^{ [self.delegate connectableDevice:self service:service pairingRequiredOfType:pairingType withData:pairingData]; }); + else + { + if (pairingType == DeviceServicePairingTypeAirPlayMirroring) { + UIWindow *window = [UIApplication sharedApplication].keyWindow; + UIViewController *rootViewController = window.rootViewController; + [rootViewController presentViewController: (UIAlertController *)pairingData animated:YES completion:nil]; + } + } + } +} + +- (void)deviceServicePairingSuccess:(DeviceService *)service +{ + if (self.delegate && [self.delegate respondsToSelector:@selector(connectableDevicePairingSuccess:service:)]) + dispatch_on_main(^{ [self.delegate connectableDevicePairingSuccess:self service:service]; }); +} + +- (void)deviceService:(DeviceService *)service pairingFailedWithError:(NSError *)error +{ + if (self.delegate && [self.delegate respondsToSelector:@selector(connectableDevice:service:pairingFailedWithError:)]) + dispatch_on_main(^{ [self.delegate connectableDevice:self service:service pairingFailedWithError:error]; }); +} + +#pragma mark Capability updates + +- (void) deviceService:(DeviceService *)service capabilitiesAdded:(NSArray *)added removed:(NSArray *)removed +{ + [[DiscoveryManager sharedManager] connectableDevice:self capabilitiesAdded:added removed:removed]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(connectableDevice:capabilitiesAdded:removed:)]) + dispatch_on_main(^{ [self.delegate connectableDevice:self capabilitiesAdded:added removed:removed]; }); +} + +#pragma mark - Capabilities + +- (NSArray *) capabilities +{ + NSMutableArray *caps = [NSMutableArray new]; + + [self.services enumerateObjectsUsingBlock:^(DeviceService *service, NSUInteger serviceIdx, BOOL *serviceStop) + { + [service.capabilities enumerateObjectsUsingBlock:^(NSString *capability, NSUInteger capabilityIdx, BOOL *capabilityStop) + { + if (![caps containsObject:capability]) + [caps addObject:capability]; + }]; + }]; + + return [NSArray arrayWithArray:caps]; +} + +- (BOOL) hasCapability:(NSString *)capability +{ + __block BOOL hasCap = NO; + + [self.services enumerateObjectsUsingBlock:^(DeviceService *service, NSUInteger idx, BOOL *stop) + { + if ([service hasCapability:capability]) + { + hasCap = YES; + *stop = YES; + } + }]; + + return hasCap; +} + +- (BOOL) hasCapabilities:(NSArray *)capabilities +{ + __block BOOL hasCaps = YES; + + [capabilities enumerateObjectsUsingBlock:^(NSString *capability, NSUInteger idx, BOOL *stop) + { + if (![self hasCapability:capability]) + { + hasCaps = NO; + *stop = YES; + } + }]; + + return hasCaps; +} + +- (BOOL) hasAnyCapability:(NSArray *)capabilities +{ + __block BOOL hasCap = NO; + + [self.services enumerateObjectsUsingBlock:^(DeviceService *service, NSUInteger idx, BOOL *stop) + { + if ([service hasAnyCapability:capabilities]) + { + hasCap = YES; + *stop = YES; + } + }]; + + return hasCap; +} + +- (id) launcher +{ + __block id foundLauncher; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, id service, BOOL *stop) + { + if (![service respondsToSelector:@selector(launcher)]) + return; + + id launcher = [service launcher]; + + if (launcher) + { + if (foundLauncher) + { + if (launcher.launcherPriority > foundLauncher.launcherPriority) + { + foundLauncher = launcher; + } + } else + { + foundLauncher = launcher; + } + } + }]; + + return foundLauncher; +} + +- (id)externalInputControl +{ + __block id foundExternalInputControl; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, id service, BOOL *stop) + { + if (![service respondsToSelector:@selector(externalInputControl)]) + return; + + id externalInputControl = [service externalInputControl]; + + if (externalInputControl) + { + if (foundExternalInputControl) + { + if (externalInputControl.externalInputControlPriority > foundExternalInputControl.externalInputControlPriority) + { + foundExternalInputControl = externalInputControl; + } + } else + { + foundExternalInputControl = externalInputControl; + } + } + }]; + + return foundExternalInputControl; +} + +- (id) mediaPlayer +{ + __block id foundPlayer; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, id service, BOOL *stop) + { + if (![service respondsToSelector:@selector(mediaPlayer)]) + return; + + id player = [service mediaPlayer]; + + if (player) + { + if (foundPlayer) + { + if (player.mediaPlayerPriority > foundPlayer.mediaPlayerPriority) + { + foundPlayer = player; + } + } else + { + foundPlayer = player; + } + } + }]; + + return foundPlayer; +} + +- (id) mediaControl +{ + __block id foundMediaControl; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, id service, BOOL *stop) + { + if (![service respondsToSelector:@selector(mediaControl)]) + return; + + id mediaControl = [service mediaControl]; + + if (mediaControl) + { + if (foundMediaControl) + { + if (mediaControl.mediaControlPriority > foundMediaControl.mediaControlPriority) + { + foundMediaControl = mediaControl; + } + } else + { + foundMediaControl = mediaControl; + } + } + }]; + + return foundMediaControl; +} + +- (id)volumeControl +{ + __block id foundVolume; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, id service, BOOL *stop) + { + if (![service respondsToSelector:@selector(volumeControl)]) + return; + + id volume = [service volumeControl]; + + if (volume) + { + if (foundVolume) + { + if (volume.volumeControlPriority > foundVolume.volumeControlPriority) + { + foundVolume = volume; + } + } else + { + foundVolume = volume; + } + } + }]; + + return foundVolume; +} + +- (id)tvControl +{ + __block id foundTV; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, id service, BOOL *stop) + { + if (![service respondsToSelector:@selector(tvControl)]) + return; + + id tv = [service tvControl]; + + if (tv) + { + if (foundTV) + { + if (tv.tvControlPriority > foundTV.tvControlPriority) + { + foundTV = tv; + } + } else + { + foundTV = tv; + } + } + }]; + + return foundTV; +} + +- (id) keyControl +{ + __block id foundKeyControl; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, id service, BOOL *stop) + { + if (![service respondsToSelector:@selector(keyControl)]) + return; + + id keyControl = [service keyControl]; + + if (keyControl) + { + if (foundKeyControl) + { + if (keyControl.keyControlPriority > foundKeyControl.keyControlPriority) + { + foundKeyControl = keyControl; + } + } else + { + foundKeyControl = keyControl; + } + } + }]; + + return foundKeyControl; +} + +- (id) textInputControl +{ + __block id foundTextInput; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, id service, BOOL *stop) + { + if (![service respondsToSelector:@selector(textInputControl)]) + return; + + id textInput = [service textInputControl]; + + if (textInput) + { + if (foundTextInput) + { + if (textInput.textInputControlPriority > foundTextInput.textInputControlPriority) + { + foundTextInput = textInput; + } + } else + { + foundTextInput = textInput; + } + } + }]; + + return foundTextInput; +} + +- (id)mouseControl +{ + __block id foundMouse; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, id service, BOOL *stop) + { + if (![service respondsToSelector:@selector(mouseControl)]) + return; + + id mouse = [service mouseControl]; + + if (mouse) + { + if (foundMouse) + { + if (mouse.mouseControlPriority > foundMouse.mouseControlPriority) + { + foundMouse = mouse; + } + } else + { + foundMouse = mouse; + } + } + }]; + + return foundMouse; +} + +- (id)powerControl +{ + __block id foundPower; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, id service, BOOL *stop) + { + if (![service respondsToSelector:@selector(powerControl)]) + return; + + id power = [service powerControl]; + + if (power) + { + if (foundPower) + { + if (power.powerControlPriority > foundPower.powerControlPriority) + { + foundPower = power; + } + } else + { + foundPower = power; + } + } + }]; + + return foundPower; +} + +- (id) toastControl +{ + __block id foundToastControl; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, id service, BOOL *stop) + { + if (![service respondsToSelector:@selector(toastControl)]) + return; + + id toastControl = [service toastControl]; + + if (toastControl) + { + if (foundToastControl) + { + if (toastControl.toastControlPriority > foundToastControl.toastControlPriority) + { + foundToastControl = toastControl; + } + } else + { + foundToastControl = toastControl; + } + } + }]; + + return foundToastControl; +} + +- (id) webAppLauncher +{ + __block id foundWebAppLauncher; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, id service, BOOL *stop) + { + if (![service respondsToSelector:@selector(webAppLauncher)]) + return; + + id webAppLauncher = [service webAppLauncher]; + + if (webAppLauncher) + { + if (foundWebAppLauncher) + { + if (webAppLauncher.webAppLauncherPriority > foundWebAppLauncher.webAppLauncherPriority) + { + foundWebAppLauncher = webAppLauncher; + } + } else + { + foundWebAppLauncher = webAppLauncher; + } + } + }]; + + return foundWebAppLauncher; +} + +- (id)screenMirroringControl +{ + __block id foundScreenMirroring; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, id service, BOOL *stop) + { + if (![service respondsToSelector:@selector(screenMirroringControl)]) + return; + + id screenMirroring = [service screenMirroringControl]; + + if (screenMirroring) + { + if (foundScreenMirroring) + { + if (screenMirroring.screenMirroringControlPriority > foundScreenMirroring.screenMirroringControlPriority) + { + foundScreenMirroring = screenMirroring; + } + } else + { + foundScreenMirroring = screenMirroring; + } + } + }]; + + return foundScreenMirroring; +} + +- (id)remoteCameraControl +{ + __block id foundRemoteCamera; + + [_services enumerateKeysAndObjectsUsingBlock:^(id key, id service, BOOL *stop) + { + if (![service respondsToSelector:@selector(remoteCameraControl)]) + return; + + id remoteCamera = [service remoteCameraControl]; + + if (remoteCamera) + { + if (foundRemoteCamera) + { + if (remoteCamera.remoteCameraControlPriority > foundRemoteCamera.remoteCameraControlPriority) + { + foundRemoteCamera = remoteCamera; + } + } else + { + foundRemoteCamera = remoteCamera; + } + } + }]; + + return foundRemoteCamera; +} + +@end diff --git a/core/Devices/ConnectableDeviceDelegate.h b/core/Devices/ConnectableDeviceDelegate.h new file mode 100644 index 00000000..9c6c5e03 --- /dev/null +++ b/core/Devices/ConnectableDeviceDelegate.h @@ -0,0 +1,147 @@ +// +// ConnectableDeviceDelegate.h +// Connect SDK +// +// Created by Andrew Longstaff on 9/6/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@class ConnectableDevice; +@class DeviceService; + +/*! + * ConnectableDeviceDelegate allows for a class to receive messages about ConnectableDevice connection, disconnect, and update events. + * + * It also serves as a delegate proxy for message handling when connecting and pairing with each of a ConnectableDevice's DeviceServices. Each of the DeviceService proxy methods are optional and would only be useful in a few use cases. + * - providing your own UI for the pairing process. + * - interacting directly and exclusively with a single type of DeviceService + */ +@protocol ConnectableDeviceDelegate + +/*! + * A ConnectableDevice sends out a ready message when all of its connectable DeviceServices have been connected and are ready to receive commands. + * + * @param device ConnectableDevice that is ready for commands. + */ +- (void) connectableDeviceReady:(ConnectableDevice *)device; + +/*! + * When all of a ConnectableDevice's DeviceServices have become disconnected, the disconnected message is sent. + * + * @param device ConnectableDevice that has been disconnected. + */ +- (void) connectableDeviceDisconnected:(ConnectableDevice *)device withError:(NSError *)error; + +@optional + +/*! + * When a ConnectableDevice finds & loses DeviceServices, that ConnectableDevice will experience a change in its collective capabilities list. When such a change occurs, this message will be sent with arrays of capabilities that were added & removed. + * + * This message will allow you to decide when to stop/start interacting with a ConnectableDevice, based off of its supported capabilities. + * + * @param device ConnectableDevice that has experienced a change in capabilities + * @param added NSArray of capabilities that are new to the ConnectableDevice + * @param removed NSArray of capabilities that the ConnectableDevice has lost + */ +- (void) connectableDevice:(ConnectableDevice *)device capabilitiesAdded:(NSArray *)added removed:(NSArray *)removed; + +/*! + * This method is called when the connection to the ConnectableDevice has failed. + * + * @param device ConnectableDevice that has failed to connect + * @param error NSError with a description of the failure + */ +- (void) connectableDevice:(ConnectableDevice *)device connectionFailedWithError:(NSError *)error; + +#pragma mark - DeviceService delegate proxy methods + +/*! + * DeviceService delegate proxy method. + * + * This method is called when a DeviceService requires an active connection. This will be the case for DeviceServices that send messages over websockets (webOS, etc) and DeviceServices that require pairing to send messages (Netcast, etc). + * + * @param device ConnectableDevice containing the DeviceService + * @param service DeviceService which requires a connection + */ +- (void) connectableDeviceConnectionRequired:(ConnectableDevice *)device forService:(DeviceService *)service; + +/*! + * DeviceService delegate proxy method. + * + * This method is called when a DeviceService has successfully connected. + * + * @param device ConnectableDevice containing the DeviceService + * @param service DeviceService which has connected + */ +- (void) connectableDeviceConnectionSuccess:(ConnectableDevice*)device forService:(DeviceService *)service; + +/*! + * DeviceService delegate proxy method. + * + * This method is called when a DeviceService becomes disconnected. + * + * @param device ConnectableDevice containing the DeviceService + * @param service DeviceService which has disconnected + * @param error NSError with a description of any errors causing the disconnect. If this value is nil, then the disconnect was clean/expected. + */ +- (void) connectableDevice:(ConnectableDevice*)device service:(DeviceService *)service disconnectedWithError:(NSError*)error; + +/*! + * DeviceService delegate proxy method. + * + * This method is called when a DeviceService fails to connect. + * + * @param device ConnectableDevice containing the DeviceService + * @param service DeviceService which has failed to connect + * @param error NSError with a description of the failure + */ +- (void) connectableDevice:(ConnectableDevice *)device service:(DeviceService *)service didFailConnectWithError:(NSError*)error; + +/*! + * DeviceService delegate proxy method. + * + * This method is called when a DeviceService tries to connect and finds out that it requires pairing information from the user. + * + * @param device ConnectableDevice containing the DeviceService + * @param service DeviceService that requires pairing + * @param pairingType DeviceServicePairingType that the DeviceService requires + * @param pairingData Any data that might be required for the pairing process, will usually be nil + */ +- (void) connectableDevice:(ConnectableDevice *)device service:(DeviceService *)service pairingRequiredOfType:(int)pairingType withData:(id)pairingData; + +/*! + * DeviceService delegate proxy method. + * + * This method is called when a DeviceService completes the pairing process. + * + * @param device ConnectableDevice containing the DeviceService + * @param service DeviceService that has successfully completed pairing + */ +- (void) connectableDevicePairingSuccess:(ConnectableDevice*)device service:(DeviceService *)service; + +/*! + * DeviceService delegate proxy method. + * + * This method is called when a DeviceService fails to complete the pairing process. + * + * @param device ConnectableDevice containing the DeviceService + * @param service DeviceService that has failed to complete pairing + * @param error NSError with a description of the failure + */ +- (void) connectableDevice:(ConnectableDevice *)device service:(DeviceService *)service pairingFailedWithError:(NSError*)error; + +@end diff --git a/core/Devices/ConnectableDeviceStore.h b/core/Devices/ConnectableDeviceStore.h new file mode 100644 index 00000000..5612ceed --- /dev/null +++ b/core/Devices/ConnectableDeviceStore.h @@ -0,0 +1,89 @@ +// +// ConnectableDeviceStore.h +// Connect SDK +// +// Created by Jeremy White on 3/21/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "ConnectableDevice.h" + +/*! + * ConnectableDeviceStore is a protocol which can be implemented to save key information about ConnectableDevices that have been connected to. Any class which implements this protocol can be used as DiscoveryManager's deviceStore. + * + * A default implementation, DefaultConnectableDeviceStore, will be used by DiscoveryManager if no other ConnectableDeviceStore is provided to DiscoveryManager when startDiscovery is called. + * + * ###Privacy Considerations + * If you chose to implement ConnectableDeviceStore, it is important to keep your users' privacy in mind. + * - There should be UI elements in your app to + * + completely disable ConnectableDeviceStore + * + purge all data from ConnectableDeviceStore (removeAll) + * - Your ConnectableDeviceStore implementation should + * + avoid tracking too much data (indefinitely storing all discovered devices) + * + periodically remove ConnectableDevices from the ConnectableDeviceStore if they haven't been used/connected in X amount of time + */ +@protocol ConnectableDeviceStore + +/*! + * Add a ConnectableDevice to the ConnectableDeviceStore. If the ConnectableDevice is already stored, it's record will be updated. + * + * @param device ConnectableDevice to add to the ConnectableDeviceStore + */ +- (void) addDevice:(ConnectableDevice *)device; + +/*! + * Updates a ConnectableDevice's record in the ConnectableDeviceStore. If the ConnectableDevice is not in the store, this call will be ignored. + * + * @param device ConnectableDevice to update in the ConnectableDeviceStore + */ +- (void) updateDevice:(ConnectableDevice *)device; + +/*! + * Removes a ConnectableDevice's record from the ConnectableDeviceStore. + * + * @param device ConnectableDevice to remove from the ConnectableDeviceStore + */ +- (void) removeDevice:(ConnectableDevice *)device; + +/*! + * Gets a ConnectableDevice object for a provided id. The id may be for the ConnectableDevice object or any of the device's DeviceServices. + * + * @param id Unique ID for a ConnectableDevice or any of its DeviceService objects + * + * @return ConnectableDevice object if a matching id was found, otherwise will return nil + */ +- (ConnectableDevice *) deviceForId:(NSString *)id; + +/*! + * Gets a ServiceConfig object for a provided UUID. This is used by DiscoveryManager to retain crucial service information between sessions (pairing code, etc). + * + * @param UUID Unique ID for the service + * + * @return ServiceConfig object if a matching UUID was found, otherwise will return nil + */ +- (ServiceConfig *) serviceConfigForUUID:(NSString *)UUID; + +/*! + * Clears out the ConnectableDeviceStore, removing all records. + */ +- (void) removeAll; + +/*! + * A dictionary containing information about all ConnectableDevices in the ConnectableDeviceStore. To get a strongly-typed ConnectableDevice object, use the `getDeviceForUUID:` method. + */ +@property (nonatomic, readonly) NSDictionary *storedDevices; + +@end diff --git a/core/Devices/DefaultConnectableDeviceStore.h b/core/Devices/DefaultConnectableDeviceStore.h new file mode 100644 index 00000000..2f90c947 --- /dev/null +++ b/core/Devices/DefaultConnectableDeviceStore.h @@ -0,0 +1,103 @@ +// +// DefaultConnectableDeviceStore.h +// Connect SDK +// +// Created by Jeremy White on 3/21/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "ConnectableDeviceStore.h" + + +/*! + * DefaultConnectableDeviceStore is an implementation of ConnectableDeviceStore provided by Connect SDK for your convenience. This class will be used by DiscoveryManager as the default ConnectableDeviceStore if no other ConnectableDeviceStore implementation is provided before calling startDiscovery. + * + * ###Privacy Considerations + * As outlined in ConnectableDeviceStore, this class takes the following steps to ensure users' privacy. + * - Only ConnectableDevices that have been connected to will be permanently stored + * - On load & store, ConnectableDevices that have not been discovered within the maxStoreDuration will be removed from the ConnectableDeviceStore + * + * ###File Format + * DefaultConnectableDeviceStore stores data in a JSON file named `Connect_SDK_Device_Store.json` in the documents directory. + * +@code +{ + // necessary for migrations, if needed + version: 1, + created: 1395892958.220422, + updated: 1395892958.220422, + devices: { + "12345678-5137-1358-1544-123456789012" : { // each ConnectableDevice is keyed against its id + "id": "12345678-5137-1358-1544-123456789012", + "friendlyName": "My TV", + "lastKnownIPAddress": "192.168.1.107", + "lastSeenOnWifi": "My WiFi Network", + "lastConnected": 1395892958.220422, + "lastDetection": 1395892958.220422, + "services": { + "66be8e5d-51be-b18f-f733-6c4dc8c97aca": { // each DeviceService discovered is keyed against its UUID + { + "class": "WebOSTVService", // DeviceService subclass name + "config": { + "class": "WebOSTVServiceConfig", // ServiceConfig subclass name + "UUID": "66be8e5d-51be-b18f-f733-6c4dc8c97aca", + "connected": false, + "wasConnected": false, + "lastDetection": 1395892958.220422, + + // should be appropriate to the platform (in iOS this is an array) + "SSLCertificates": ..., + "clientKey": "..." + }, + "description": { + "serviceId": "webOS TV", + "port": 3001, + "UUID": "66be8e5d-51be-b18f-f733-6c4dc8c97aca", + "type": "urn:lge-com:service:webos-second-screen:1", + "version": "4.1.0", + "friendlyName": "My TV", + "manufacturer": "LG Electronics", + "modelName": "LG Smart TV", + "modelDescription": "", + "modelNumber": "", + "commandURL": "http://192.168.1.107:1914/" + } + } + } + } + }, + ... + } +} +@endcode + */ +@interface DefaultConnectableDeviceStore : NSObject + +/*! + * Max length of time for a ConnectableDevice to remain in the ConnectableDeviceStore without being discovered. Default is 3 days, and modifications to this value will trigger a scan for old devices. ConnectableDevices that have been connected to will never be removed from the device store unless `remove:` or `removeAll` are called. + */ +@property (nonatomic) double maxStoreDuration; + +/*! Date (in seconds from 1970) that the ConnectableDeviceStore was created. */ +@property (nonatomic, readonly) double created; + +/*! Date (in seconds from 1970) that the ConnectableDeviceStore was last updated. */ +@property (nonatomic, readonly) double updated; + +/*! Current version of the ConnectableDeviceStore, may be necessary for migrations */ +@property (nonatomic, readonly) int version; + +@end diff --git a/core/Devices/DefaultConnectableDeviceStore.m b/core/Devices/DefaultConnectableDeviceStore.m new file mode 100644 index 00000000..4d8811d8 --- /dev/null +++ b/core/Devices/DefaultConnectableDeviceStore.m @@ -0,0 +1,458 @@ +// +// DefaultConnectableDeviceStore.m +// Connect SDK +// +// Created by Jeremy White on 3/21/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DefaultConnectableDeviceStore.h" + + +@implementation DefaultConnectableDeviceStore +{ + NSMutableDictionary *_activeDevices; // active ConnectableDevice objects + NSMutableDictionary *_storedDevices; // inactive NSDictionary objects containing ConnectableDevice information + NSString *_deviceStoreFilename; + NSFileManager *_fileManager; + + NSDictionary *_deviceStore; + + BOOL _waitToWrite; + + dispatch_queue_t _deviceStoreQueue; +} + +- (instancetype) init +{ + self = [super init]; + + if (self) + { + _maxStoreDuration = 3 * 24 * 60 * 60; // 3 days + _deviceStoreQueue = dispatch_queue_create("Connect_SDK_Device_Store", DISPATCH_QUEUE_SERIAL); + + _activeDevices = [NSMutableDictionary new]; + _storedDevices = [NSMutableDictionary new]; + + NSArray *base = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documents = [base lastObject]; + + _deviceStoreFilename = [documents stringByAppendingPathComponent:@"Connect_SDK_Device_Store.json"]; + + _fileManager = [NSFileManager defaultManager]; + + [self load]; + } + + return self; +} + +- (void) load +{ + if (![_fileManager fileExistsAtPath:_deviceStoreFilename]) + { + _version = 1; + _created = [[NSDate date] timeIntervalSince1970]; + _updated = [[NSDate date] timeIntervalSince1970]; + + [_fileManager createFileAtPath:_deviceStoreFilename contents:nil attributes:nil]; + } else + { + NSError *error; + NSString *deviceStoreJSON = [NSString stringWithContentsOfFile:_deviceStoreFilename encoding:NSUTF8StringEncoding error:&error]; + + if (error) + { + DLog(@"Experienced error loading file: %@", error.localizedDescription); + return; + } + + NSData *deviceStoreData = [deviceStoreJSON dataUsingEncoding:NSUTF8StringEncoding]; + + @synchronized (self) + { + _deviceStore = [NSJSONSerialization JSONObjectWithData:deviceStoreData options:0 error:&error]; + } + + if (error) + { + DLog(@"Experienced error parsing file: %@", error.localizedDescription); + return; + } + + NSDictionary *devicesFromStore = [_deviceStore objectForKey:@"devices"]; + + if (!devicesFromStore || [devicesFromStore isKindOfClass:[NSNull class]]) + _storedDevices = [NSMutableDictionary new]; + else + _storedDevices = [NSMutableDictionary dictionaryWithDictionary:devicesFromStore]; + + id version = _deviceStore[@"version"]; + if (version && ![version isKindOfClass:[NSNull class]]) + _version = [version intValue]; + + id created = _deviceStore[@"created"]; + if (created && ![created isKindOfClass:[NSNull class]]) + _created = [created intValue]; + + id updated = _deviceStore[@"updated"]; + if (updated && ![updated isKindOfClass:[NSNull class]]) + _updated = [updated intValue]; + } +} + +- (void) setMaxStoreDuration:(double)maxStoreDuration +{ + _maxStoreDuration = maxStoreDuration; + + [self deleteOldUnusedDevices]; +} + +- (void) store +{ + if (_storedDevices && _deviceStoreFilename) + { + self.maxStoreDuration = _maxStoreDuration; // clean out old devices + + _updated = [[NSDate date] timeIntervalSince1970]; + + NSMutableDictionary *newDeviceStore = [NSMutableDictionary new]; + newDeviceStore[@"version"] = @(self.version); + newDeviceStore[@"created"] = @(self.created); + newDeviceStore[@"updated"] = @(self.updated); + newDeviceStore[@"devices"] = [NSDictionary dictionaryWithDictionary:_storedDevices]; + + _updated = [[NSDate date] timeIntervalSince1970]; + + @synchronized(self) + { + _deviceStore = [NSDictionary dictionaryWithDictionary:newDeviceStore]; + } + + if (!_waitToWrite) + [self writeStoreToDisk]; + } +} + +- (void) addDevice:(ConnectableDevice *)device +{ + if (!device || device.services.count == 0) + return; + + if (![_activeDevices objectForKey:device.id]) + [_activeDevices setObject:device forKey:device.id]; + + NSDictionary *storedDevice = [_storedDevices objectForKey:device.id]; + + if (storedDevice) + { + [self updateDevice:device]; + } else + { + storedDevice = [self jsonRepresentationForDevice:device]; + + if (storedDevice) + { + [_storedDevices setObject:storedDevice forKey:device.id]; + + [self store]; + } + } +} + +- (void) removeDevice:(ConnectableDevice *)device +{ + if (!device || device.services.count == 0) + return; + + [_storedDevices removeObjectForKey:device.id]; + + [self store]; +} + +- (void) removeAll +{ + _storedDevices = [NSMutableDictionary new]; + + [self store]; +} + +- (void) updateDevice:(ConnectableDevice *)device +{ + if (!device || device.services.count == 0) + return; + + NSDictionary *storedDeviceInfo = [self storedDeviceForUUID:device.id]; + + if (!storedDeviceInfo || [storedDeviceInfo isKindOfClass:[NSNull class]]) + return; + + NSMutableDictionary *storedDevice = [NSMutableDictionary dictionaryWithDictionary:storedDeviceInfo]; + + // since this is an update, we will serialize ConnectableDevice into JSON manually. This way we avoid + // removing reference to any services that weren't discovered for whatever reason during this session + + storedDevice[@"lastKnownIPAddress"] = device.lastKnownIPAddress; + storedDevice[@"lastSeenOnWifi"] = device.lastSeenOnWifi; + storedDevice[@"lastConnected"] = @(device.lastConnected); + storedDevice[@"lastDetection"] = @(device.lastDetection); + + NSDictionary *servicesInfo = storedDevice[@"services"]; + + if (!servicesInfo || [servicesInfo isKindOfClass:[NSNull class]]) + servicesInfo = [NSDictionary new]; + + NSMutableDictionary *services = [NSMutableDictionary dictionaryWithDictionary:servicesInfo]; + + [device.services enumerateObjectsUsingBlock:^(DeviceService *service, NSUInteger idx, BOOL *stop) + { + NSDictionary *serviceInfo = [service toJSONObject]; + + if (serviceInfo) + [services setObject:serviceInfo forKey:service.serviceDescription.UUID]; + }]; + + storedDevice[@"services"] = [NSDictionary dictionaryWithDictionary:services]; + + NSDictionary *deviceToStore = [NSDictionary dictionaryWithDictionary:storedDevice]; + [_storedDevices setObject:deviceToStore forKey:device.id]; + [_activeDevices setObject:device forKey:device.id]; + + [self store]; +} + +- (NSDictionary *) storedDevices +{ + if (_storedDevices) + return [NSDictionary dictionaryWithDictionary:_storedDevices]; + else + return [NSDictionary new]; +} + +- (ConnectableDevice *) deviceForId:(NSString *)id +{ + if (!id || id.length == 0 || [id isKindOfClass:[NSNull class]]) + return nil; + + ConnectableDevice *foundDevice = [self activeDeviceForUUID:id]; + + if (!foundDevice) + { + NSDictionary *foundDeviceInfo = [self storedDeviceForUUID:id]; + + if (foundDeviceInfo) + foundDevice = [[ConnectableDevice alloc] initWithJSONObject:foundDeviceInfo]; + } + + if (foundDevice && ![_activeDevices objectForKey:foundDevice.id]) + [_activeDevices setObject:foundDevice forKey:foundDevice.id]; + + return foundDevice; +} + +- (ServiceConfig *) serviceConfigForUUID:(NSString *)UUID +{ + if (!UUID || UUID.length == 0 || [UUID isKindOfClass:[NSNull class]]) + return nil; + + ServiceConfig *foundConfig = nil; + + NSDictionary *device = [self storedDeviceForUUID:UUID]; + + if (device && ![device isKindOfClass:[NSNull class]]) + { + NSDictionary *services = [device objectForKey:@"services"]; + + if (services && ![services isKindOfClass:[NSNull class]]) + { + NSDictionary *service = [services objectForKey:UUID]; + + if (service && ![service isKindOfClass:[NSNull class]]) + { + NSDictionary *serviceConfigInfo = [service objectForKey:@"config"]; + + if (serviceConfigInfo && ![serviceConfigInfo isKindOfClass:[NSNull class]]) + { + foundConfig = [ServiceConfig serviceConfigWithJSONObject:serviceConfigInfo]; + } + } + } + } + + return foundConfig; +} + +#pragma mark - Helper methods + +- (ConnectableDevice *) activeDeviceForUUID:(NSString *)UUID +{ + __block ConnectableDevice *foundDevice = nil; + + // Check active devices + foundDevice = [_activeDevices objectForKey:UUID]; + + // Check active device services + if (!foundDevice) + { + [_activeDevices enumerateKeysAndObjectsUsingBlock:^(id key, ConnectableDevice *device, BOOL *deviceStop) + { + [device.services enumerateObjectsUsingBlock:^(DeviceService *service, NSUInteger idx, BOOL *serviceStop) + { + if ([UUID isEqualToString:service.serviceDescription.UUID]) + { + foundDevice = device; + + *deviceStop = YES; + *serviceStop = YES; + } + }]; + }]; + } + + return foundDevice; +} + +- (NSDictionary *) storedDeviceForUUID:(NSString *)UUID +{ + __block NSDictionary *foundDevice = nil; + + // Check stored devices + foundDevice = [_storedDevices objectForKey:UUID]; + + // Check stored device services + if (!foundDevice) + { + [_storedDevices enumerateKeysAndObjectsUsingBlock:^(NSString *deviceUUID, NSDictionary *device, BOOL *stop) + { + NSDictionary *services = device[@"services"]; + + if (services && [services objectForKey:UUID]) + { + foundDevice = device; + *stop = YES; + } + }]; + } + + return foundDevice; +} + +- (void) writeStoreToDisk +{ + double lastUpdated = self.updated; + _waitToWrite = YES; + + dispatch_async(_deviceStoreQueue, ^ + { + NSError *jsonError; + NSData *deviceStoreJSONData; + + @synchronized (self) + { + deviceStoreJSONData = [NSJSONSerialization dataWithJSONObject:_deviceStore options:NSJSONWritingPrettyPrinted error:&jsonError]; + } + + NSString *deviceStoreJSON = [[NSString alloc] initWithData:deviceStoreJSONData encoding:NSUTF8StringEncoding]; + + if (jsonError) + { + DLog(@"Failed to parse with error: %@", jsonError.localizedDescription); + _waitToWrite = NO; + return; + } + + NSError *writeError; + [deviceStoreJSON writeToFile:_deviceStoreFilename atomically:YES encoding:NSUTF8StringEncoding error:&writeError]; + + if (writeError) + { + DLog(@"Failed to write with error: %@", writeError.localizedDescription); + _waitToWrite = NO; + return; + } + + _waitToWrite = NO; + + if (lastUpdated != _updated) + [self writeStoreToDisk]; + }); +} + +- (NSDictionary *) jsonRepresentationForDevice:(ConnectableDevice *)device +{ + if (!device || device.services.count == 0) + return nil; + + NSMutableDictionary *deviceDictionary = [NSMutableDictionary new]; + deviceDictionary[@"id"] = device.id; + deviceDictionary[@"friendlyName"] = device.friendlyName; + deviceDictionary[@"lastKnownIPAddress"] = device.lastKnownIPAddress; + deviceDictionary[@"lastSeenOnWifi"] = device.lastSeenOnWifi; + deviceDictionary[@"lastConnected"] = @(device.lastConnected); + deviceDictionary[@"lastDetection"] = @(device.lastDetection); + + NSMutableDictionary *servicesDictionary = [NSMutableDictionary new]; + + [device.services enumerateObjectsUsingBlock:^(DeviceService *service, NSUInteger serviceIdx, BOOL *serviceStop) + { + NSDictionary *serviceDictionary = [service toJSONObject]; + + if (serviceDictionary) + [servicesDictionary setObject:serviceDictionary forKey:service.serviceDescription.UUID]; + }]; + + deviceDictionary[@"services"] = [NSDictionary dictionaryWithDictionary:servicesDictionary]; + + return [NSDictionary dictionaryWithDictionary:deviceDictionary]; +} + +- (void) deleteOldUnusedDevices +{ + __block NSMutableArray *devicesToRemove = [NSMutableArray new]; + + [_storedDevices enumerateKeysAndObjectsUsingBlock:^(NSString *UUID, NSDictionary *deviceInfo, BOOL *stop) + { + double lastConnected = 0; + double lastDetection = 0; + + id lastConnectedObject = deviceInfo[@"lastConnected"]; + if (lastConnectedObject && ![lastConnectedObject isKindOfClass:[NSNull class]]) + lastConnected = [lastConnectedObject doubleValue]; + + id lastDetectionObject = deviceInfo[@"lastDetection"]; + if (lastDetectionObject && ![lastDetectionObject isKindOfClass:[NSNull class]]) + lastDetection = [lastDetectionObject doubleValue]; + + if (lastConnected > 0) + return; + + double currentTime = [[NSDate date] timeIntervalSince1970]; + double storeDuration = currentTime - lastDetection; + + if (storeDuration > _maxStoreDuration) + [devicesToRemove addObject:UUID]; + }]; + + if (devicesToRemove) + { + [devicesToRemove enumerateObjectsUsingBlock:^(NSString *UUID, NSUInteger idx, BOOL *stop) + { + [_storedDevices removeObjectForKey:UUID]; + }]; + } +} + +@end diff --git a/core/Devices/DevicePicker.h b/core/Devices/DevicePicker.h new file mode 100644 index 00000000..48b2774b --- /dev/null +++ b/core/Devices/DevicePicker.h @@ -0,0 +1,69 @@ +// +// DevicePicker.h +// Connect SDK +// +// Created by Andrew Longstaff on 9/6/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "DevicePickerDelegate.h" +#import "DiscoveryManagerDelegate.h" +#import "ConnectableDevice.h" + +/*! + * ###Overview + * The DevicePicker is provided by the DiscoveryManager as a simple way for you to present a list of available devices to your users. + * + * ###In Depth + * The DevicePicker takes a sender parameter on the showPicker method. The sender parameter is used to display a popover from a particular UIView on iPads. + * + * You should not attempt to instantiate the DevicePicker on your own. Instead, get the reference from the DeviceManager with [[DeviceManager sharedManager] devicePicker]; + */ +@interface DevicePicker : NSObject + +/*! Delegate that receives selected/cancelled messages. */ +@property (nonatomic, weak) id delegate; + +/*! + * When the showPicker method is called, it can animate onto the screen if this value is set to YES. This value will also be used to determine if the picker should animate when it is dismissed. + */ +@property (nonatomic) BOOL shouldAnimatePicker; + +/*! + * When the device is rotated, the DevicePicker can automatically adjust the view to compenstate. Default is NO. + */ +@property (nonatomic) BOOL shouldAutoRotate; + +/*! + * If you wish to show a checkmark next to a device in the picker, you can set that device object to currentDevice. The setter for currentDevice will also reload the tableView in the picker. + */ +@property (nonatomic, weak) ConnectableDevice *currentDevice; + +/*! + * This method will animate the picker onto the screen. On iPad, the picker will appear as a popover view and will animate from the sender object, if you provide one. On iPhone, the picker will appear as a full-screen table view that will animate up from the bottom of the screen. This picker will animate in real time with additions, losses, and updates of ConnectableDevices. + * + * @param sender On iPad, this should be a UIView for the popover view to animate from. On iPhone, this property is ignored. + */ +- (void) showPicker:(id)sender; + +/*! + * This method will animate an action sheet onto the screen containing a button for each discovered ConnectableDevice. Due to the nature of action sheets, it is not possible to update the action sheet after it has appeared. It is recommended to use the showPicker: method if you want a picker that will update in real time. + * + * @param sender The UIView that the action sheet should appear in + */ +- (void) showActionSheet:(id)sender; + +@end diff --git a/core/Devices/DevicePicker.m b/core/Devices/DevicePicker.m new file mode 100644 index 00000000..eeed262c --- /dev/null +++ b/core/Devices/DevicePicker.m @@ -0,0 +1,490 @@ +// +// DevicePicker.m +// Connect SDK +// +// Created by Andrew Longstaff on 9/6/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DevicePicker.h" +#import "DiscoveryProvider.h" +#import "DiscoveryManager.h" + +@implementation DevicePicker +{ + NSArray *_generatedDeviceList; + NSArray *_actionSheetDeviceList; + NSMutableDictionary *_devices; + + UINavigationController *_navigationController; + UITableViewController *_tableViewController; + + UIActionSheet *_actionSheet; + UIView *_actionSheetTargetView; + + UIPopoverController *_popover; + NSDictionary *_popoverParams; + + dispatch_queue_t _sortQueue; + + BOOL _showServiceLabel; +} + +- (instancetype) init +{ + self = [super init]; + + if (self) + { + _sortQueue = dispatch_queue_create("Connect SDK Device Picker Sort", DISPATCH_QUEUE_SERIAL); + _devices = [[NSMutableDictionary alloc] init]; + + self.shouldAnimatePicker = YES; + } + + return self; +} + +- (void)setCurrentDevice:(ConnectableDevice *)currentDevice +{ + _currentDevice = currentDevice; + + [_tableViewController.tableView reloadData]; +} + +#pragma mark - Picker display methods + +- (void) showPicker:(id)sender +{ + [self sortDevices]; + + _showServiceLabel = [DiscoveryManager sharedManager].capabilityFilters.count == 0; + + NSString *pickerTitle = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Search_Title" value:@"Pick a device" table:@"ConnectSDK"]; + + _tableViewController = [[UITableViewController alloc] initWithStyle:UITableViewStylePlain]; + _tableViewController.title = pickerTitle; + _tableViewController.tableView.delegate = self; + _tableViewController.tableView.dataSource = self; + + _navigationController = [[UINavigationController alloc] initWithRootViewController:_tableViewController]; + + UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + [indicator startAnimating]; + + _tableViewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:indicator]; + + if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) + [self showPopover:sender]; + else + [self showNavigation]; +} + +- (void) showPopover:(id)source +{ + _popover = [[UIPopoverController alloc] initWithContentViewController:_navigationController]; + _popover.delegate = self; + + if ([source isKindOfClass:[UIBarButtonItem class]]) + { + [_popover presentPopoverFromBarButtonItem:source permittedArrowDirections:UIPopoverArrowDirectionAny animated:self.shouldAnimatePicker]; + } else if ([source isKindOfClass:[UIView class]]) + { + UIView *sourceView = (UIView *)source; + CGRect sourceRect; + UIView *targetView; + UIPopoverArrowDirection permittedArrowDirections; + + if (sourceView.superview && ![sourceView.superview isKindOfClass:[UIWindow class]]) + { + sourceRect = sourceView.frame; + targetView = sourceView.superview; + permittedArrowDirections = UIPopoverArrowDirectionAny; + } else + { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRotation) name:UIDeviceOrientationDidChangeNotification object:nil]; + + sourceRect = sourceView.frame; + targetView = sourceView; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wint-conversion" + permittedArrowDirections = NULL; +#pragma clang diagnostic pop + + _popoverParams = @{ + @"sourceView" : sourceView, + @"targetView" : targetView + }; + } + + [_popover presentPopoverFromRect:sourceRect inView:targetView permittedArrowDirections:permittedArrowDirections animated:self.shouldAnimatePicker]; + } else + { + DLog(@"Sender should be a subclass of either UIBarButtonItem or UIView"); + + [self cleanupViews]; + } +} + +- (void) showActionSheet:(id)sender +{ + NSString *pickerTitle = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Search_Title" value:@"Pick a device" table:@"ConnectSDK"]; + NSString *pickerCancel = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Search_Cancel" value:@"Cancel" table:@"ConnectSDK"]; + + _actionSheet = [[UIActionSheet alloc] initWithTitle:pickerTitle + delegate:self + cancelButtonTitle:nil + destructiveButtonTitle:nil + otherButtonTitles:nil]; + + @synchronized (_generatedDeviceList) + { + _actionSheetDeviceList = [_generatedDeviceList copy]; + } + + [_actionSheetDeviceList enumerateObjectsUsingBlock:^(ConnectableDevice *device, NSUInteger idx, BOOL *stop) + { + [_actionSheet addButtonWithTitle:device.friendlyName]; + }]; + + _actionSheet.cancelButtonIndex = [_actionSheet addButtonWithTitle:pickerCancel]; + + if ([sender isKindOfClass:[UIBarButtonItem class]]) + [_actionSheet showFromBarButtonItem:sender animated:_shouldAnimatePicker]; + else if ([sender isKindOfClass:[UITabBar class]]) + [_actionSheet showFromTabBar:sender]; + else if ([sender isKindOfClass:[UIToolbar class]]) + [_actionSheet showFromToolbar:sender]; + else if ([sender isKindOfClass:[UIControl class]]) + { + UIControl *senderView = (UIControl *)sender; + [_actionSheet showFromRect:senderView.frame inView:senderView.superview animated:_shouldAnimatePicker]; + } else + { + [_actionSheet showInView:sender]; + + _actionSheetTargetView = sender; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRotation) name:UIDeviceOrientationDidChangeNotification object:nil]; + } +} + +- (void) showNavigation +{ + NSString *pickerCancel = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Search_Cancel" value:@"Cancel" table:@"ConnectSDK"]; + + _tableViewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:pickerCancel style:UIBarButtonItemStylePlain target:self action:@selector(dismissPicker:)]; + + UIWindow *mainWindow = [[UIApplication sharedApplication].windows firstObject]; + [mainWindow.rootViewController presentViewController:_navigationController animated:self.shouldAnimatePicker completion:nil]; +} + +- (void) dismissPicker:(id)sender +{ + if (_actionSheet) + { + [_actionSheet dismissWithClickedButtonIndex:_actionSheet.cancelButtonIndex animated:YES]; + } else + { + if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) + [_popover dismissPopoverAnimated:_shouldAnimatePicker]; + else + [_navigationController dismissViewControllerAnimated:_shouldAnimatePicker completion:nil]; + } + + [self cleanupViews]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(devicePicker:didCancelWithError:)]) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate devicePicker:self didCancelWithError:nil]; + }); + } +} + +#pragma mark - Helper methods + +- (void) cleanupViews +{ + if (_tableViewController) + { + _tableViewController.tableView.delegate = nil; + _tableViewController.tableView.dataSource = nil; + } + + if (_popover) + _popover.delegate = nil; + + if (_actionSheet) + _actionSheet.delegate = nil; + + _actionSheetTargetView = nil; + _actionSheet = nil; + _actionSheetDeviceList = nil; + _navigationController = nil; + _tableViewController = nil; + _popoverParams = nil; + _popover = nil; + + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; +} + +- (void) sortDevices +{ + dispatch_async(_sortQueue, ^{ + NSArray *devices; + + @synchronized (_devices) { devices = [_devices allValues]; } + + @synchronized (_generatedDeviceList) + { + _generatedDeviceList = [devices sortedArrayUsingComparator:^NSComparisonResult(ConnectableDevice *device1, ConnectableDevice *device2) { + NSString *device1Name = [[self nameForDevice:device1] lowercaseString]; + NSString *device2Name = [[self nameForDevice:device2] lowercaseString]; + + return [device1Name compare:device2Name]; + }]; + } + }); +} + +- (NSString *) nameForDevice:(ConnectableDevice *)device +{ + NSString *name; + + if (device.serviceDescription.friendlyName && device.serviceDescription.friendlyName.length > 0) + name = device.serviceDescription.friendlyName; + else if (device.serviceDescription.address && device.serviceDescription.address.length > 0) + name = device.serviceDescription.address; + else + name = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Unnamed_Device" value:@"Unnamed device" table:@"ConnectSDK"]; + + return name; +} + +- (void) handleRotation +{ + if (!self.shouldAutoRotate) + return; + + if (_popover && _popoverParams) + { + UIView *sourceView = [_popoverParams objectForKey:@"sourceView"]; + UIView *targetView = [_popoverParams objectForKey:@"targetView"]; + + if (!sourceView || !targetView) + return; + + CGRect sourceRect = sourceView.bounds; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wint-conversion" + UIPopoverArrowDirection permittedArrowDirections = NULL; +#pragma clang diagnostic pop + + [_popover presentPopoverFromRect:sourceRect inView:targetView permittedArrowDirections:permittedArrowDirections animated:self.shouldAnimatePicker]; + } else if (_actionSheet && _actionSheetTargetView) + { + [_actionSheet showInView:_actionSheetTargetView]; + } +} + +#pragma mark UIActionSheet methods + +- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex +{ + if (buttonIndex == actionSheet.cancelButtonIndex) + return; + + ConnectableDevice *device = [_actionSheetDeviceList objectAtIndex:buttonIndex]; + BOOL deviceExists = YES; + + @synchronized (_generatedDeviceList) + { + deviceExists = [_generatedDeviceList containsObject:device]; + } + + if (!deviceExists) + { + DLog(@"User selected a device that no longer exists"); + return; + } + + if (self.delegate && [self.delegate respondsToSelector:@selector(devicePicker:didSelectDevice:)]) + { + dispatch_async(dispatch_get_main_queue(), ^ + { + [self.delegate devicePicker:self didSelectDevice:device]; + }); + } +} + +- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex +{ + [self cleanupViews]; +} + +- (void)actionSheetCancel:(UIActionSheet *)actionSheet +{ + [self dismissPicker:nil]; +} + +#pragma mark UITableViewDelegate methods + +- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + ConnectableDevice *device; + + @synchronized (_generatedDeviceList) + { + device = (ConnectableDevice *) [_generatedDeviceList objectAtIndex:indexPath.row]; + } + + if (self.currentDevice) + { + if ([self.currentDevice.serviceDescription.address isEqualToString:device.serviceDescription.address]) + return; + } + + if (_delegate && [_delegate respondsToSelector:@selector(devicePicker:didSelectDevice:)]) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [_delegate devicePicker:self didSelectDevice:device]; + }); + } + + [self dismissPicker:self]; +} + +#pragma mark UITableViewDataSource methods + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + NSInteger numberOfRows = 0; + + @synchronized (_generatedDeviceList) + { + if (_generatedDeviceList) + numberOfRows = _generatedDeviceList.count; + } + + return numberOfRows; +} + +static NSString *cellIdentifier = @"connectPickerCell"; + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; + + if (cell == nil) + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier]; + + ConnectableDevice *device; + + @synchronized (_generatedDeviceList) + { + if (_generatedDeviceList.count > 0 && indexPath.row < _generatedDeviceList.count) + device = (ConnectableDevice *) [_generatedDeviceList objectAtIndex:indexPath.row]; + } + + if (!device) + return cell; + + NSString *deviceName = [self nameForDevice:device]; + [cell.textLabel setText:deviceName]; + + #ifdef DEBUG + [cell.detailTextLabel setText:[device connectedServiceNames]]; + #endif + + if (_showServiceLabel) + [cell.detailTextLabel setText:[device connectedServiceNames]]; + + if (self.currentDevice) + { + if ([self.currentDevice.serviceDescription.address isEqualToString:device.serviceDescription.address]) + [cell setAccessoryType:UITableViewCellAccessoryCheckmark]; + else + [cell setAccessoryType:UITableViewCellAccessoryNone]; + } + + return cell; +} + +#pragma mark - UIPopoverControllerDelegate methods + +- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController +{ + if (self.delegate && [self.delegate respondsToSelector:@selector(devicePicker:didCancelWithError:)]) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate devicePicker:self didCancelWithError:nil]; + }); + } + + [self cleanupViews]; +} + +# pragma mark - DiscoveryManagerDelegate methods + +- (void)discoveryManager:(DiscoveryManager *)manager didFindDevice:(ConnectableDevice *)device +{ + if (_devices) + { + @synchronized (_devices) { [_devices setObject:device forKey:device.address]; } + + [self sortDevices]; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (_tableViewController) + [_tableViewController.tableView reloadData]; + }); + } +} + +- (void)discoveryManager:(DiscoveryManager *)manager didLoseDevice:(ConnectableDevice *)device +{ + if (_devices) + { + @synchronized (_devices) { [_devices removeObjectForKey:device.address]; } + + [self sortDevices]; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (_tableViewController) + [_tableViewController.tableView reloadData]; + }); + } +} + +- (void)discoveryManager:(DiscoveryManager *)manager didUpdateDevice:(ConnectableDevice *)device +{ + if (_devices) + { + dispatch_async(dispatch_get_main_queue(), ^{ + if (_tableViewController) + [_tableViewController.tableView reloadData]; + }); + } +} + +@end diff --git a/core/Devices/DevicePickerDelegate.h b/core/Devices/DevicePickerDelegate.h new file mode 100644 index 00000000..cc27443c --- /dev/null +++ b/core/Devices/DevicePickerDelegate.h @@ -0,0 +1,49 @@ +// +// DevicePickerDelegate.h +// Connect SDK +// +// Created by Andrew Longstaff on 9/6/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@class DevicePicker; +@class ConnectableDevice; + +/*! + * The DevicePickerDelegate will receive a message when the user cancels or selects a ConnectableDevice from the DevicePicker list. This is the preferred method of selecting a device from DiscoveryManager. + */ +@protocol DevicePickerDelegate + +/*! + * When the user selects a ConnectableDevice from the DevicePicker's list, this method will be called with the selected ConnectableDevice. + * + * @param picker DevicePicker that device was selected from + * @param device ConnectableDevice that was selected by the user + */ +- (void) devicePicker:(DevicePicker *)picker didSelectDevice:(ConnectableDevice *)device; + +@optional + +/*! + * This method is called if the user presses the cancel button in the picker or if Connect SDK forces a cancellation. If Connect SDK forces a cancellation, there will be an NSError object passed with the reason. + * + * @param picker DevicePicker that was cancelled + * @param error NSError with a description of the failure + */ +- (void) devicePicker:(DevicePicker *)picker didCancelWithError:(NSError*)error; + +@end diff --git a/core/Discovery/CapabilityFilter.h b/core/Discovery/CapabilityFilter.h new file mode 100644 index 00000000..a57bb94f --- /dev/null +++ b/core/Discovery/CapabilityFilter.h @@ -0,0 +1,107 @@ +// +// CapabilityFilter.h +// Connect SDK +// +// Created by Jeremy White on 1/29/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + + +/*! + * CapabilityFilter is an object that wraps an NSArray of required capabilities. This CapabilityFilter is used for determining which devices will appear in DiscoveryManager's compatibleDevices array. The contents of a CapabilityFilter's array must be any of the string constants defined in the Capability header files. + * + * ###CapabilityFilter values + * Here are some examples of values for the Capability constants. + * + * - kMediaPlayerPlayVideo = "MediaPlayer.Display.Video" + * - kMediaPlayerDisplayImage = "MediaPlayer.Display.Image" + * - kVolumeControlSubscribe = "VolumeControl.Subscribe" + * - kMediaControlAny = "Media.Control.Any" + * + * All Capability header files also define a constant array of all capabilities defined in that header (ex. kVolumeControlCapabilities). + * + * ###AND/OR Filtering + * CapabilityFilter is an AND filter. A ConnectableDevice would need to satisfy all conditions of a CapabilityFilter to pass. + * + * [DiscoveryManager capabilityFilters] is an OR filter. a ConnectableDevice only needs to satisfy one condition (CapabilityFilter) to pass. + * + * ###Examples + * Filter for all devices that support video playback AND any media controls AND volume up/down. + * +@code + NSArray *capabilities = @[ + kMediaPlayerPlayVideo, + kMediaControlAny, + kVolumeControlVolumeUpDown + ]; + + CapabilityFilter *filter = + [CapabilityFilter filterWithCapabilities:capabilities]; + + [[DiscoveryManager sharedManager] setCapabilityFilters:@[filter]]; +@endcode + * + * Filter for all devices that support (video playback AND any media controls AND volume up/down) OR (image display). + * +@code + NSArray *videoCapabilities = @[ + kMediaPlayerPlayVideo, + kMediaControlAny, + kVolumeControlVolumeUpDown + ]; + + NSArray *imageCapabilities = @[ + kMediaPlayerDisplayImage + ]; + + CapabilityFilter *videoFilter = + [CapabilityFilter filterWithCapabilities:videoCapabilities]; + CapabilityFilter *imageFilter = + [CapabilityFilter filterWithCapabilities:imageCapabilities]; + + [[DiscoveryManager sharedManager] setCapabilityFilters:@[videoFilter, imageFilter]]; +@endcode + */ +@interface CapabilityFilter : NSObject + +/*! + * Array of capabilities required by this filter. This property is readonly -- use the addCapability or addCapabilities to build this object. + */ +@property (nonatomic, strong, readonly) NSArray *capabilities; + +/*! + * Create a CapabilityFilter with the given array required capabilities. + * + * @param capabilities Capabilities to be added to the new filter + */ ++ (CapabilityFilter *)filterWithCapabilities:(NSArray *)capabilities; + +/*! + * Add a required capability to the filter. + * + * @param capability Capability name to add (see capability header files for NSString constants) + */ +- (void)addCapability:(NSString *)capability; + +/*! + * Add array of required capabilities to the filter. + * + * @param capabilities List of capability names (see capability header files for NSString constants) + */ +- (void)addCapabilities:(NSArray *)capabilities; + +@end diff --git a/core/Discovery/CapabilityFilter.m b/core/Discovery/CapabilityFilter.m new file mode 100644 index 00000000..86abb220 --- /dev/null +++ b/core/Discovery/CapabilityFilter.m @@ -0,0 +1,56 @@ +// +// CapabilityFilter.m +// Connect SDK +// +// Created by Jeremy White on 1/29/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "CapabilityFilter.h" + + +@implementation CapabilityFilter + +- (instancetype) init +{ + self = [super init]; + + if (self) + { + _capabilities = [[NSArray alloc] init]; + } + + return self; +} + ++ (CapabilityFilter *)filterWithCapabilities:(NSArray *)capabilities +{ + CapabilityFilter *filter = [[CapabilityFilter alloc] init]; + [filter addCapabilities:capabilities]; + + return filter; +} + +- (void)addCapability:(NSString *)capability +{ + _capabilities = [_capabilities arrayByAddingObject:capability]; +} + +- (void)addCapabilities:(NSArray *)capabilities +{ + _capabilities = [_capabilities arrayByAddingObjectsFromArray:capabilities]; +} + +@end diff --git a/core/Discovery/DiscoveryManager.h b/core/Discovery/DiscoveryManager.h new file mode 100644 index 00000000..2dacb2ff --- /dev/null +++ b/core/Discovery/DiscoveryManager.h @@ -0,0 +1,182 @@ +// +// DiscoveryManager.h +// Connect SDK +// +// Created by Jeremy White on 12/2/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "DiscoveryManagerDelegate.h" +#import "DevicePicker.h" +#import "ConnectableDeviceStore.h" + +/*! + * ###Overview + * + * At the heart of Connect SDK is DiscoveryManager, a multi-protocol service discovery engine with a pluggable architecture. Much of your initial experience with Connect SDK will be with the DiscoveryManager class, as it consolidates discovered service information into ConnectableDevice objects. + * + * ###In depth + * DiscoveryManager supports discovering services of differing protocols by using DiscoveryProviders. Many services are discoverable over [SSDP][0] and are registered to be discovered with the SSDPDiscoveryProvider class. + * + * As services are discovered on the network, the DiscoveryProviders will notify DiscoveryManager. DiscoveryManager is capable of attributing multiple services, if applicable, to a single ConnectableDevice instance. Thus, it is possible to have a mixed-mode ConnectableDevice object that is theoretically capable of more functionality than a single service can provide. + * + * DiscoveryManager keeps a running list of all discovered devices and maintains a filtered list of devices that have satisfied any of your CapabilityFilters. This filtered list is used by the DevicePicker when presenting the user with a list of devices. + * + * Only one instance of the DiscoveryManager should be in memory at a time. To assist with this, DiscoveryManager has singleton accessors at sharedManager and sharedManagerWithDeviceStore:. + * + * Example: + * + * @capability kMediaControlPlay + * + @code + DiscoveryManager *discoveryManager = [DiscoveryManager sharedManager]; + discoveryManager.delegate = self; // set delegate to listen for discovery events + [discoveryManager startDiscovery]; + @endcode + * + * [0]: http://tools.ietf.org/html/draft-cai-ssdp-v1-03 + */ +@interface DiscoveryManager : NSObject + +/*! + * Delegate which should receive discovery updates. It is not necessary to set this delegate property unless you are implementing your own device picker. Connect SDK provides a default DevicePicker which acts as a DiscoveryManagerDelegate, and should work for most cases. + * + * If you have provided a capabilityFilters array, the delegate will only receive update messages for ConnectableDevices which satisfy at least one of the CapabilityFilters. If no capabilityFilters array is provided, the delegate will receive update messages for all ConnectableDevice objects that are discovered. + */ +@property (nonatomic, weak) id delegate; + +/*! + * Singleton accessor for DiscoveryManager. This method calls sharedManagerWithDeviceStore: and passes an instance of DefaultConnectableDeviceStore. + */ ++ (instancetype) sharedManager; + +/*! + * Singleton accessor for DiscoveryManager, will initialize singleton with reference to a custom ConnectableDeviceStore object. + * + * @param deviceStore (optional) An object which implements the ConnectableDeviceStore protocol to be used for save/load of device information. You may provide nil to completely disable the device store capabilities of the SDK. + */ ++ (instancetype) sharedManagerWithDeviceStore:(id)deviceStore; + +/*! + * Filtered list of discovered ConnectableDevices, limited to devices that match at least one of the CapabilityFilters in the capabilityFilters array. Each ConnectableDevice object is keyed against its current IP address. + */ +- (NSDictionary *) compatibleDevices; + +/*! + * List of all devices discovered by DiscoveryManager. Each ConnectableDevice object is keyed against its current IP address. + */ +- (NSDictionary *) allDevices; + +#pragma mark - Configuration & Device Registration + +// @cond INTERNAL + +/* + * Registers a commonly-used set of DeviceServices with DiscoveryManager. This method will be called on first call of startDiscovery if no DeviceServices have been registered. + * + * - CastDiscoveryProvider + * + CastService + * - SSDPDiscoveryProvider + * + DIALService + * + DLNAService (limited to LG TVs, currently) + * + NetcastTVService + * + RokuService + * + WebOSTVService + */ +- (void) registerDefaultServices; + +/* + * Registers a DeviceService with DiscoveryManager and tells it which DiscoveryProvider to use to find it. Each DeviceService has an NSDictionary of discovery parameters that its DiscoveryProvider will use to find it. + * + * @param deviceClass Class for object that should be instantiated when DeviceService is found + * @param discoveryClass Class for object that should discover this DeviceService. If a DiscoveryProvider of this class already exists, then the existing DiscoveryProvider will be used. + */ +- (void) registerDeviceService:(Class)deviceClass withDiscovery:(Class)discoveryClass; + +/* + * Unregisters a DeviceService with DiscoveryManager. If no other DeviceServices are set to being discovered with the associated DiscoveryProvider, then that DiscoveryProvider instance will be stopped and shut down. + * + * @param deviceClass Class for DeviceService that should no longer be discovered + * @param discoveryClass Class for DiscoveryProvider that is discovering DeviceServices of deviceClass type + */ +- (void) unregisterDeviceService:(Class)deviceClass withDiscovery:(Class)discoveryClass; + +// @endcond + +/*! + * A ConnectableDevice will be displayed in the DevicePicker and compatibleDevices array if it matches any of the CapabilityFilter objects in this array. + */ +@property (nonatomic, strong) NSArray *capabilityFilters; + +/*! + * The pairingLevel property determines whether capabilities that require pairing (such as entering a PIN) will be available. + * + * If pairingLevel is set to DeviceServicePairingLevelOn, ConnectableDevices that require pairing will prompt the user to pair when connecting to the ConnectableDevice. + * + * If pairingLevel is set to DeviceServicePairingLevelOff (the default), connecting to the device will avoid requiring pairing if possible but some capabilities may not be available. + */ +@property (nonatomic) DeviceServicePairingLevel pairingLevel; + +#pragma mark - Control + +/*! + * Start scanning for devices on the local network. + */ +- (void) startDiscovery; + +/*! + * Stop scanning for devices. + * + * This method will be called when your app enters a background state. When your app resumes, startDiscovery will be called. + */ +- (void) stopDiscovery; + +#pragma mark - Device Picker + +/*! + * Get a DevicePicker to show compatible ConnectableDevices that have been found by DiscoveryManager. + * + * @return DevicePicker DevicePicker singleton for use in picking devices + */ +- (DevicePicker*) devicePicker; + +#pragma mark - Device Store + +/*! + * ConnectableDeviceStore object which loads & stores references to all discovered devices. Pairing codes/keys, SSL certificates, recent access times, etc are kept in the device store. + * + * ConnectableDeviceStore is a protocol which may be implemented as needed. A default implementation, DefaultConnectableDeviceStore, exists for convenience and will be used if no other device store is provided. + * + * In order to satisfy user privacy concerns, you should provide a UI element in your app which exposes the ConnectableDeviceStore removeAll method. + * + * To disable the ConnectableDeviceStore capabilities of Connect SDK, set this value to nil. This may be done at the time of instantiation with `[DiscoveryManager sharedManagerWithDeviceStore:nil]`. + */ +@property (nonatomic, strong) id deviceStore; + +/*! + * Whether pairing state will be automatically loaded/saved in the deviceStore. This property is not available for direct modification. To disable the device store, + */ +@property (nonatomic, readonly) BOOL useDeviceStore; + +// @cond INTERNAL + +@property (nonatomic, readonly) NSDictionary *deviceClasses; + +@property (nonatomic, readonly) NSArray *discoveryProviders; + +// @endcond + +@end diff --git a/core/Discovery/DiscoveryManager.m b/core/Discovery/DiscoveryManager.m new file mode 100644 index 00000000..672ce82a --- /dev/null +++ b/core/Discovery/DiscoveryManager.m @@ -0,0 +1,733 @@ +// +// DiscoveryManager.m +// Connect SDK +// +// Created by Jeremy White on 12/2/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DiscoveryManager_Private.h" + +#import "DiscoveryProviderDelegate.h" +#import "DiscoveryProvider.h" + +#import "ConnectSDKDefaultPlatforms.h" + +#import "DLNAService.h" +#import "NetcastTVService.h" + +#import "ConnectableDevice.h" +#import "DefaultConnectableDeviceStore.h" +#import "ServiceDescription.h" +#import "ServiceConfig.h" +#import "ServiceConfigDelegate.h" +#import "CapabilityFilter.h" + +#import "AppStateChangeNotifier.h" + +#import + +@interface DiscoveryManager() + +@end + +@implementation DiscoveryManager +{ + NSMutableDictionary *_allDevices; + NSMutableDictionary *_compatibleDevices; + + BOOL _shouldResumeSearch; + BOOL _searching; + + DevicePicker *_currentPicker; + + NSTimer *_ssidTimer; + NSString *_currentSSID; +} + +@synthesize pairingLevel = _pairingLevel; +@synthesize useDeviceStore = _useDeviceStore; + ++ (DiscoveryManager *) _sharedManager +{ + static dispatch_once_t predicate = 0; + __strong static id _sharedManager = nil; + + dispatch_once(&predicate, ^{ + _sharedManager = [[self alloc] init]; + }); + + return _sharedManager; +} + ++ (DiscoveryManager *) sharedManager +{ + DiscoveryManager *manager = [self _sharedManager]; + + if (!manager.deviceStore && manager.useDeviceStore) + [manager setDeviceStore:[DefaultConnectableDeviceStore new]]; + + return manager; +} + ++ (DiscoveryManager *) sharedManagerWithDeviceStore:(id )deviceStore +{ + DiscoveryManager *manager = [self _sharedManager]; + + if (deviceStore == nil || manager.deviceStore != deviceStore) + [manager setDeviceStore:deviceStore]; + + return manager; +} + +- (void) setDeviceStore:(id )deviceStore +{ + _deviceStore = deviceStore; + _useDeviceStore = (_deviceStore != nil); +} + +- (instancetype)init { + return [self initWithAppStateChangeNotifier:nil]; +} + +#pragma mark - Private Init + +- (instancetype) initWithAppStateChangeNotifier:(nullable AppStateChangeNotifier *)stateNotifier +{ + self = [super init]; + + if (self) + { + _shouldResumeSearch = NO; + _searching = NO; + _useDeviceStore = YES; + + _discoveryProviders = [[NSMutableArray alloc] init]; + _deviceClasses = [[NSMutableDictionary alloc] init]; + + _allDevices = [[NSMutableDictionary alloc] init]; + _compatibleDevices = [[NSMutableDictionary alloc] init]; + + _appStateChangeNotifier = stateNotifier ?: [AppStateChangeNotifier new]; + __weak typeof(self) wself = self; + _appStateChangeNotifier.didBackgroundBlock = ^{ + typeof(self) sself = wself; + [sself pauseDiscovery]; + }; + _appStateChangeNotifier.didForegroundBlock = ^{ + typeof(self) sself = wself; + [sself resumeDiscovery]; + }; + + [self startSSIDTimer]; + } + + return self; +} + +#pragma mark - Setup & Registration + +- (void) registerDefaultServices +{ + NSDictionary *defaultPlatforms = kConnectSDKDefaultPlatforms; + + [defaultPlatforms enumerateKeysAndObjectsUsingBlock:^(NSString *platformClassName, NSString *discoveryProviderClassName, BOOL *stop) { + Class platformClass = NSClassFromString(platformClassName); + Class discoveryProviderClass = NSClassFromString(discoveryProviderClassName); + + [self registerDeviceService:platformClass withDiscovery:discoveryProviderClass]; + }]; +} + +- (void) registerDeviceService:(Class)deviceClass withDiscovery:(Class)discoveryClass +{ + if (![discoveryClass isSubclassOfClass:[DiscoveryProvider class]]) + return; + + [self registerDeviceService:deviceClass + withDiscoveryProviderFactory:^DiscoveryProvider *{ + return [discoveryClass new]; + }]; +} + +- (void)registerDeviceService:(Class)deviceClass + withDiscoveryProviderFactory:(DiscoveryProvider *(^)(void))providerFactory +{ + if (![deviceClass isSubclassOfClass:[DeviceService class]]) + return; + + __block DiscoveryProvider *discoveryProvider; + // FIXME don't create new provider unless necessary + DiscoveryProvider *newDiscoveryProvider = providerFactory(); + + [_discoveryProviders enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + if ([[obj class] isSubclassOfClass:[newDiscoveryProvider class]]) + { + discoveryProvider = obj; + *stop = YES; + } + }]; + + if (discoveryProvider == nil) + { + discoveryProvider = newDiscoveryProvider; + discoveryProvider.delegate = self; + _discoveryProviders = [_discoveryProviders arrayByAddingObject:discoveryProvider]; + } + + NSDictionary *discoveryParameters = [deviceClass discoveryParameters]; + + NSString *serviceId = [discoveryParameters objectForKey:@"serviceId"]; + + NSMutableDictionary *mutableClasses = [NSMutableDictionary dictionaryWithDictionary:_deviceClasses]; + [mutableClasses setObject:deviceClass forKey:serviceId]; + _deviceClasses = [NSDictionary dictionaryWithDictionary:mutableClasses]; + + [discoveryProvider addDeviceFilter:discoveryParameters]; +} + +- (void) unregisterDeviceService:(Class)deviceClass withDiscovery:(Class)discoveryClass +{ + if (![discoveryClass isSubclassOfClass:[DiscoveryProvider class]]) + return; + + if (![deviceClass isSubclassOfClass:[DeviceService class]]) + return; + + __block DiscoveryProvider *discoveryProvider; + + [_discoveryProviders enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + if ([[obj class] isSubclassOfClass:discoveryClass]) + { + discoveryProvider = obj; + *stop = YES; + } + }]; + + if (discoveryProvider == nil) + return; + + NSDictionary *discoveryParameters = [discoveryClass discoveryParameters]; + + NSString *serviceId = [discoveryParameters objectForKey:@"serviceId"]; + + NSMutableDictionary *mutableClasses = [NSMutableDictionary dictionaryWithDictionary:_deviceClasses]; + [mutableClasses removeObjectForKey:serviceId]; + _deviceClasses = [NSDictionary dictionaryWithDictionary:mutableClasses]; + + [discoveryProvider removeDeviceFilter:discoveryParameters]; + + if ([discoveryProvider isEmpty]) + { + [discoveryProvider stopDiscovery]; + discoveryProvider.delegate = nil; + + NSMutableArray *mutableProviders = [NSMutableArray arrayWithArray:_discoveryProviders]; + [mutableProviders removeObject:discoveryProvider]; + _discoveryProviders = [NSArray arrayWithArray:mutableProviders]; + } +} + +#pragma mark - Wireless SSID Change Detection + +- (void) startSSIDTimer +{ + _ssidTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(detectSSIDChange) userInfo:nil repeats:YES]; + [_ssidTimer fire]; +} + +- (void) stopSSIDTimer +{ + [_ssidTimer invalidate]; + _ssidTimer = nil; +} + +- (void) detectSSIDChange +{ + NSArray *interfaces = (__bridge_transfer id) CNCopySupportedInterfaces(); + + __block NSString *ssidName; + + [interfaces enumerateObjectsUsingBlock:^(NSString *interface, NSUInteger idx, BOOL *stop) + { + if ([interface caseInsensitiveCompare:@"en0"] != NSOrderedSame) + return; + + CFDictionaryRef cfDict = CNCopyCurrentNetworkInfo((CFStringRef)interface); + NSDictionary *info = (NSDictionary *)CFBridgingRelease(cfDict); + + if (info && [info objectForKey:@"SSID"]) + { + ssidName = [info objectForKey:@"SSID"]; + *stop = YES; + } + }]; + + if (ssidName == nil) + ssidName = @""; + + if ([ssidName caseInsensitiveCompare:_currentSSID] != NSOrderedSame) + { + if (_currentSSID != nil) + { + [self purgeDeviceList]; + + [[NSNotificationCenter defaultCenter] postNotificationName:kConnectSDKWirelessSSIDChanged object:nil]; + } + + _currentSSID = ssidName; + } +} + +- (void) purgeDeviceList +{ + [self.compatibleDevices enumerateKeysAndObjectsUsingBlock:^(id key, ConnectableDevice *device, BOOL *stop) + { + [device disconnect]; + + if (self.delegate) + [self.delegate discoveryManager:self didLoseDevice:device]; + + if (self.devicePicker) + [self.devicePicker discoveryManager:self didLoseDevice:device]; + }]; + + [_discoveryProviders enumerateObjectsUsingBlock:^(DiscoveryProvider *provider, NSUInteger idx, BOOL *stop) { + [provider stopDiscovery]; + [provider startDiscovery]; + }]; + + _allDevices = [NSMutableDictionary new]; + _compatibleDevices = [NSMutableDictionary new]; +} + +#pragma mark - Capability Filtering + +- (void)setCapabilityFilters:(NSArray *)capabilityFilters +{ + _capabilityFilters = capabilityFilters; + + @synchronized (_compatibleDevices) + { + [_compatibleDevices enumerateKeysAndObjectsUsingBlock:^(NSString *address, ConnectableDevice *device, BOOL *stop) + { + if (self.delegate) + [self.delegate discoveryManager:self didLoseDevice:device]; + }]; + } + + _compatibleDevices = [[NSMutableDictionary alloc] init]; + + NSArray *allDevices; + + @synchronized (_allDevices) { allDevices = [_allDevices allValues]; } + + [allDevices enumerateObjectsUsingBlock:^(ConnectableDevice *device, NSUInteger idx, BOOL *stop) + { + if ([self deviceIsCompatible:device]) + { + @synchronized (_compatibleDevices) { [_compatibleDevices setValue:device forKey:device.address]; } + + if (self.delegate) + [self.delegate discoveryManager:self didFindDevice:device]; + } + }]; +} + +- (BOOL) descriptionIsNetcastTV:(ServiceDescription *)description +{ + BOOL isNetcast = NO; + + if ([description.modelName.uppercaseString isEqualToString:@"LG TV"]) + { + if ([description.modelDescription.uppercaseString rangeOfString:@"WEBOS"].location == NSNotFound) + { + isNetcast = [description.serviceId isEqualToString:kConnectSDKNetcastTVServiceId]; + } + } + + return isNetcast; +} + +#pragma mark - Device lists + +- (NSDictionary *) allDevices +{ + return [NSDictionary dictionaryWithDictionary:_allDevices]; +} + +- (NSDictionary *)compatibleDevices +{ + return [NSDictionary dictionaryWithDictionary:_compatibleDevices]; +} + +- (BOOL) deviceIsCompatible:(ConnectableDevice *)device +{ + if (!_capabilityFilters || _capabilityFilters.count == 0) + return YES; + + __block BOOL isCompatible = NO; + + [self.capabilityFilters enumerateObjectsUsingBlock:^(CapabilityFilter *filter, NSUInteger idx, BOOL *stop) + { + if ([device hasCapabilities:filter.capabilities]) + { + isCompatible = YES; + *stop = YES; + } + }]; + + return isCompatible; +} + +- (void) handleDeviceAdd:(ConnectableDevice *)device +{ + if (![self deviceIsCompatible:device]) + return; + + @synchronized (_compatibleDevices) { [_compatibleDevices setValue:device forKey:device.address]; } + + if (self.delegate) + [self.delegate discoveryManager:self didFindDevice:device]; + + if (_currentPicker) + [_currentPicker discoveryManager:self didFindDevice:device]; +} + +- (void) handleDeviceUpdate:(ConnectableDevice *)device +{ + [self.deviceStore updateDevice:device]; + + if ([self deviceIsCompatible:device]) + { + @synchronized (_compatibleDevices) { + if ([_compatibleDevices objectForKey:device.address]) + { + if (self.delegate) + [self.delegate discoveryManager:self didUpdateDevice:device]; + + if (_currentPicker) + [_currentPicker discoveryManager:self didUpdateDevice:device]; + } else + { + [self handleDeviceAdd:device]; + } + } + } else + { + @synchronized (_compatibleDevices) { [_compatibleDevices removeObjectForKey:device.address]; } + + [self handleDeviceLoss:device]; + } +} + +- (void) handleDeviceLoss:(ConnectableDevice *)device +{ + if (self.delegate) + [self.delegate discoveryManager:self didLoseDevice:device]; + + if (_currentPicker) + [_currentPicker discoveryManager:self didLoseDevice:device]; +} + +- (void)setPairingLevel:(DeviceServicePairingLevel)pairingLevel +{ + NSAssert(!_searching, @"Cannot change pairing level while DiscoveryManager is running."); + + _pairingLevel = pairingLevel; +} + +#pragma mark - Control + +- (void) startDiscovery +{ + if (_searching) + return; + + if (_deviceClasses.count == 0) + [self registerDefaultServices]; + + _searching = YES; + + [_discoveryProviders enumerateObjectsUsingBlock:^(DiscoveryProvider *service, NSUInteger idx, BOOL *stop) { + [service startDiscovery]; + }]; + + [self.appStateChangeNotifier startListening]; +} + +- (void) stopDiscovery +{ + if (!_searching) + return; + + _searching = NO; + + [_discoveryProviders enumerateObjectsUsingBlock:^(DiscoveryProvider *service, NSUInteger idx, BOOL *stop) { + [service stopDiscovery]; + }]; + + if (!_shouldResumeSearch) + { + [self.appStateChangeNotifier stopListening]; + } +} + +/// Pauses all discovery providers and the SSID change timer. +- (void)pauseDiscovery { + // moved from -hAppDidEnterBackground: + [self stopSSIDTimer]; + + if (_searching) + { + _searching = NO; + _shouldResumeSearch = YES; + + [self.discoveryProviders makeObjectsPerformSelector:@selector(pauseDiscovery)]; + } +} + +/// Resumes all discovery providers and the SSID change timer. +- (void)resumeDiscovery { + // moved from -hAppDidBecomeActive: + [self startSSIDTimer]; + + if (_shouldResumeSearch) + { + _searching = YES; + _shouldResumeSearch = NO; + + [self.discoveryProviders makeObjectsPerformSelector:@selector(resumeDiscovery)]; + } +} + +#pragma mark - DiscoveryProviderDelegate methods + +- (void)discoveryProvider:(DiscoveryProvider *)provider didFindService:(ServiceDescription *)description +{ + DLog(@"%@ (%@)", description.friendlyName, description.serviceId); + + BOOL deviceIsNew = [_allDevices objectForKey:description.address] == nil; + ConnectableDevice *device; + + if (deviceIsNew) + { + if (self.useDeviceStore) + { + device = [self.deviceStore deviceForId:description.UUID]; + + if (device) + @synchronized (_allDevices) { [_allDevices setObject:device forKey:description.address]; } + } + } else + { + @synchronized (_allDevices) { device = [_allDevices objectForKey:description.address]; } + } + + if (!device) + { + device = [ConnectableDevice connectableDeviceWithDescription:description]; + @synchronized (_allDevices) { [_allDevices setObject:device forKey:description.address]; } + deviceIsNew = YES; + } + + device.lastDetection = [[NSDate date] timeIntervalSince1970]; + device.lastKnownIPAddress = description.address; + device.lastSeenOnWifi = _currentSSID; + + [self addServiceDescription:description toDevice:device]; + + if (device.services.count == 0) + { + // we get here when a non-LG DLNA TV is found + + [_allDevices removeObjectForKey:description.address]; + device = nil; + + return; + } + + if (deviceIsNew) + [self handleDeviceAdd:device]; + else + [self handleDeviceUpdate:device]; +} + +- (void)discoveryProvider:(DiscoveryProvider *)provider didLoseService:(ServiceDescription *)description +{ + DLog(@"%@ (%@)", description.friendlyName, description.serviceId); + + ConnectableDevice *device; + + @synchronized (_allDevices) { device = [_allDevices objectForKey:description.address]; } + + if (device) + { + [device removeServiceWithId:description.serviceId]; + + DLog(@"Removed service from device at address %@. Device has %lu services left", + description.address, (unsigned long)device.services.count); + + if (![device hasServices]) + { + DLog(@"Device at address %@ has been orphaned (has no services)", description.address); + + @synchronized (_allDevices) { [_allDevices removeObjectForKey:description.address]; } + @synchronized (_compatibleDevices) { [_compatibleDevices removeObjectForKey:description.address]; } + + [self handleDeviceLoss:device]; + } else + { + [self handleDeviceUpdate:device]; + } + } +} + +- (void)discoveryProvider:(DiscoveryProvider *)provider didFailWithError:(NSError *)error +{ + DLog(@"%@", error.localizedDescription); +} + +#pragma mark - Helper methods + +- (void) addServiceDescription:(ServiceDescription *)description toDevice:(ConnectableDevice *)device +{ + Class deviceServiceClass = [_deviceClasses objectForKey:description.serviceId]; + + // TODO: move this logic into DeviceService subclass init methods + if (deviceServiceClass == [DLNAService class]) + { + if (!description.locationXML) + return; + } else if (deviceServiceClass == [NetcastTVService class]) + { + if (![self descriptionIsNetcastTV:description]) + return; + } + + ServiceConfig *serviceConfig; + + if (self.useDeviceStore) + serviceConfig = [self.deviceStore serviceConfigForUUID:description.UUID]; + + if (!serviceConfig) + serviceConfig = [[ServiceConfig alloc] initWithServiceDescription:description]; + + serviceConfig.delegate = self; + + __block BOOL deviceAlreadyHasServiceType = NO; + __block BOOL deviceAlreadyHasService = NO; + + [device.services enumerateObjectsUsingBlock:^(DeviceService *obj, NSUInteger idx, BOOL *stop) { + if ([obj.serviceDescription.serviceId isEqualToString:description.serviceId]) + { + deviceAlreadyHasServiceType = YES; + + if ([obj.serviceDescription.UUID isEqualToString:description.UUID]) + deviceAlreadyHasService = YES; + + *stop = YES; + } + }]; + + if (deviceAlreadyHasServiceType) + { + if (deviceAlreadyHasService) + { + device.serviceDescription = description; + + DeviceService *alreadyAddedService = [device serviceWithName:description.serviceId]; + + if (alreadyAddedService) + alreadyAddedService.serviceDescription = description; + + return; + } + + [device removeServiceWithId:description.serviceId]; + } + + DeviceService *deviceService = [DeviceService deviceServiceWithClass:deviceServiceClass serviceConfig:serviceConfig]; + [deviceService setServiceDescription:description]; + [device addService:deviceService]; +} + +#pragma mark - ConnectableDeviceDelegate methods + +- (void) connectableDevice:(ConnectableDevice *)device capabilitiesAdded:(NSArray *)added removed:(NSArray *)removed +{ + [self handleDeviceUpdate:device]; +} + +- (void)connectableDeviceReady:(ConnectableDevice *)device { } + +- (void)connectableDeviceDisconnected:(ConnectableDevice *)device withError:(NSError *)error { } + +#pragma mark - Device Store + +- (ConnectableDevice *) lookupMatchingDeviceForDeviceStore:(ServiceConfig *)serviceConfig +{ + __block ConnectableDevice *foundDevice; + + @synchronized (_allDevices) { + [_allDevices enumerateKeysAndObjectsUsingBlock:^(id key, ConnectableDevice *device, BOOL *deviceStop) + { + [device.services enumerateObjectsUsingBlock:^(DeviceService *service, NSUInteger serviceIdx, BOOL *serviceStop) + { + if ([service.serviceConfig.UUID isEqualToString:serviceConfig.UUID]) + { + foundDevice = device; + + *serviceStop = YES; + *deviceStop = YES; + } + }]; + }]; + } + + return foundDevice; +} + +#pragma mark - Device Picker creation + +- (DevicePicker *) devicePicker +{ + if (_currentPicker == nil) + { + _currentPicker = [[DevicePicker alloc] init]; + + [self.compatibleDevices enumerateKeysAndObjectsUsingBlock:^(NSString *address, ConnectableDevice *device, BOOL *stop) + { + [_currentPicker discoveryManager:self didFindDevice:device]; + }]; + } + + return _currentPicker; +} + +#pragma mark - ServiceConfigDelegate + +- (void)serviceConfigUpdate:(ServiceConfig *)serviceConfig +{ + if (_useDeviceStore && self.deviceStore) + { + ConnectableDevice *device = [self lookupMatchingDeviceForDeviceStore:serviceConfig]; + + if (device) + [self.deviceStore updateDevice:device]; + } +} + +@end diff --git a/core/Discovery/DiscoveryManagerDelegate.h b/core/Discovery/DiscoveryManagerDelegate.h new file mode 100644 index 00000000..dc82ca36 --- /dev/null +++ b/core/Discovery/DiscoveryManagerDelegate.h @@ -0,0 +1,69 @@ +// +// DiscoveryManagerDelegate.h +// Connect SDK +// +// Created by Jeremy White on 12/4/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@class DiscoveryManager; +@class ConnectableDevice; + + +/*! + * ###Overview + * The DiscoveryManagerDelegate will receive events on the addition/removal/update of ConnectableDevice objects. + * + * ###In Depth + * It is important to note that, unless you are implementing your own device picker, this delegate is not needed in your code. Connect SDK's DevicePicker internally acts a separate delegate to the DiscoveryManager and handles all of the same method calls. + */ +@protocol DiscoveryManagerDelegate + +@optional +/*! + * This method will be fired upon the first discovery of one of a ConnectableDevice's DeviceServices. + * + * @param manager DiscoveryManager that found device + * @param device ConnectableDevice that was found + */ +- (void) discoveryManager:(DiscoveryManager *)manager didFindDevice:(ConnectableDevice *)device; + +/*! + * This method is called when connections to all of a ConnectableDevice's DeviceServices are lost. This will usually happen when a device is powered off or loses internet connectivity. + * + * @param manager DiscoveryManager that lost device + * @param device ConnectableDevice that was lost + */ +- (void) discoveryManager:(DiscoveryManager *)manager didLoseDevice:(ConnectableDevice *)device; + +/*! + * This method is called when a ConnectableDevice gains or loses a DeviceService in discovery. + * + * @param manager DiscoveryManager that updated device + * @param device ConnectableDevice that was updated + */ +- (void) discoveryManager:(DiscoveryManager *)manager didUpdateDevice:(ConnectableDevice *)device; + +/*! + * In the event of an error in the discovery phase, this method will be called. + * + * @param manager DiscoveryManager that experienced the error + * @param error NSError with a description of the failure + */ +- (void) discoveryManager:(DiscoveryManager *)manager didFailWithError:(NSError*)error; + +@end diff --git a/core/Discovery/DiscoveryManager_Private.h b/core/Discovery/DiscoveryManager_Private.h new file mode 100644 index 00000000..e8787bad --- /dev/null +++ b/core/Discovery/DiscoveryManager_Private.h @@ -0,0 +1,45 @@ +// +// DiscoveryManager_Private.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-06-16. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DiscoveryManager.h" + +@class AppStateChangeNotifier; +@class DiscoveryProvider; + +NS_ASSUME_NONNULL_BEGIN +@interface DiscoveryManager () + +/// An @c AppStateChangeNotifier that allows to track app state changes. +@property (nonatomic, readonly) AppStateChangeNotifier *appStateChangeNotifier; + + +/// Initializes the instance with the given @c AppStateChangeNotifier. Using +/// @c nil parameter will create real object. +- (instancetype)initWithAppStateChangeNotifier:(nullable AppStateChangeNotifier *)stateNotifier; + +/** + * Registers a service with the given @c deviceClass and a @c DiscoveryProvider + * created by the @c providerFactory. + */ +- (void)registerDeviceService:(Class)deviceClass + withDiscoveryProviderFactory:(DiscoveryProvider *(^)(void))providerFactory; + +@end +NS_ASSUME_NONNULL_END diff --git a/core/Discovery/DiscoveryProvider.h b/core/Discovery/DiscoveryProvider.h new file mode 100644 index 00000000..5c15730f --- /dev/null +++ b/core/Discovery/DiscoveryProvider.h @@ -0,0 +1,85 @@ +// +// DiscoveryProvider.h +// Connect SDK +// +// Created by Jeremy White on 12/2/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "DiscoveryProviderDelegate.h" + + +/*! + * ###Overview + * From a high-level perspective, DiscoveryProvider completely abstracts the functionality of discovering services of a particular protocol (SSDP, Cast, etc). The DiscoveryProvider will pass service information to the DiscoveryManager, which will then create a DeviceService object and attach it to a ConnectableDevice object. + * + * ###In Depth + * DiscoveryProvider is an abstract class that is meant to be extended. You shouldn't ever use DiscoveryProvider directly, unless extending it to provide support for another discovery protocol. + * + * By default, DiscoveryManager will set itself as a DiscoveryProvider's delegate. You should not change the delegate as it could cause unexpected inconsistencies within the discovery process. + * + * See CastDiscoveryProvider and SSDPDiscoveryProvider for implementations. + */ +@interface DiscoveryProvider : NSObject + +/*! DiscoveryProviderDelegate, which should be the DiscoveryManager */ +@property (nonatomic, weak) id delegate; + +/*! Whether or not the DiscoveryProvider is running */ +@property (nonatomic) BOOL isRunning; + +/*! + * Whether or not the DiscoveryProvider has any services it is supposed to be searching for. If YES, then the DiscoveryProvider will be stopped and de-referenced by the DiscoveryManager. + */ +@property (nonatomic, readonly) BOOL isEmpty; + +/*! + * Adds a device filter for a particular DeviceService. + * + * @param parameters Parameters to be used for discovering a particular DeviceService + */ +- (void) addDeviceFilter:(NSDictionary *)parameters; + +/*! + * Removes a device filter for a particular DeviceService. If the DiscoveryProvider has no other devices to be searching for, the DiscoveryProvider will be stopped and de-referenced. + * + * @param parameters Parameters to be used for discovering a particular DeviceService + */ +- (void) removeDeviceFilter:(NSDictionary *)parameters; + +/*! + * Starts the DiscoveryProvider. + */ +- (void) startDiscovery; + +/*! + * Stops the DiscoveryProvider. + */ +- (void) stopDiscovery; + +/** + * Pauses the discovery while the app is in background. Calls @c -stopDiscovery + * by default. + */ +- (void)pauseDiscovery; + +/** + * Resumes the discovery when the app has foregrounded. Calls @c -startDiscovery + * be default. + */ +- (void)resumeDiscovery; + +@end diff --git a/core/Discovery/DiscoveryProvider.m b/core/Discovery/DiscoveryProvider.m new file mode 100644 index 00000000..396c077e --- /dev/null +++ b/core/Discovery/DiscoveryProvider.m @@ -0,0 +1,40 @@ +// +// DiscoveryProvider.m +// Connect SDK +// +// Created by Jeremy White on 12/5/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DiscoveryProvider.h" + +@implementation DiscoveryProvider + +- (void)addDeviceFilter:(NSDictionary *)parameters { } +- (void)removeDeviceFilter:(NSDictionary *)parameters { } +- (BOOL)isEmpty { return YES; } + +- (void)startDiscovery { } +- (void)stopDiscovery { } + +- (void)pauseDiscovery { + [self stopDiscovery]; +} + +- (void)resumeDiscovery { + [self startDiscovery]; +} + +@end diff --git a/core/Discovery/DiscoveryProviderDelegate.h b/core/Discovery/DiscoveryProviderDelegate.h new file mode 100644 index 00000000..ddf0e53e --- /dev/null +++ b/core/Discovery/DiscoveryProviderDelegate.h @@ -0,0 +1,55 @@ +// +// DiscoveryProviderDelegate.h +// Connect SDK +// +// Created by Andrew Longstaff on 9/6/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@class DiscoveryProvider; +@class ServiceDescription; + +/*! + * The DiscoveryProviderDelegate is mechanism for passing service information to the DiscoveryManager. You likely will not be using the DiscoveryProviderDelegate class directly, as DiscoveryManager acts as a delegate to all of the DiscoveryProviders. + */ +@protocol DiscoveryProviderDelegate + +/*! + * This method is called when the DiscoveryProvider discovers a service that matches one of its DeviceService filters. The ServiceDescription is created and passed to the delegate (which should be the DiscoveryManager). The ServiceDescription is used to create a DeviceService, which is then attached to a ConnectableDevice object. + * + * @param provider DiscoveryProvider that found the service + * @param description ServiceDescription of the service that was found + */ +- (void) discoveryProvider:(DiscoveryProvider *)provider didFindService:(ServiceDescription *)description; + +/*! + * This method is called when the DiscoveryProvider's internal mechanism loses reference to a service that matches one of its DeviceService filters. + * + * @param provider DiscoveryProvider that lost the service + * @param description ServiceDescription of the service that was lost + */ +- (void) discoveryProvider:(DiscoveryProvider *)provider didLoseService:(ServiceDescription *)description; + +/*! + * This method is called on any error/failure within the DiscoveryProvider. + * + * @param provider DiscoveryProvider that failed + * @param error NSError providing a information about the failure + */ +- (void) discoveryProvider:(DiscoveryProvider *)provider didFailWithError:(NSError*)error; + +@end diff --git a/core/Discovery/Providers/SSDPDiscoveryProvider.h b/core/Discovery/Providers/SSDPDiscoveryProvider.h new file mode 100644 index 00000000..ed0472cb --- /dev/null +++ b/core/Discovery/Providers/SSDPDiscoveryProvider.h @@ -0,0 +1,27 @@ +// +// SSDPDiscoveryProvider.h +// Connect SDK +// +// Created by Andrew Longstaff on 9/6/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#import "DiscoveryProvider.h" + +@interface SSDPDiscoveryProvider : DiscoveryProvider + +@end diff --git a/core/Discovery/Providers/SSDPDiscoveryProvider.m b/core/Discovery/Providers/SSDPDiscoveryProvider.m new file mode 100644 index 00000000..74e57554 --- /dev/null +++ b/core/Discovery/Providers/SSDPDiscoveryProvider.m @@ -0,0 +1,606 @@ +// +// SSDPDiscoveryProvider.m +// Connect SDK +// +// Created by Andrew Longstaff on 9/6/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "SSDPDiscoveryProvider_Private.h" +#import "ServiceDescription.h" +#import "CTXMLReader.h" +#import "DeviceService.h" +#import "CommonMacros.h" + +#import + +#define kSSDP_multicast_address @"239.255.255.250" +#define kSSDP_port 1900 + +// credit: http://stackoverflow.com/a/1108927/2715 +NSString* machineName() +{ + struct utsname systemInfo; + uname(&systemInfo); + + return [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; +} + +@interface SSDPDiscoveryProvider() +{ + NSString *_ssdpHostName; + + NSArray *_serviceFilters; + NSMutableDictionary *_foundServices; + + NSTimer *_refreshTimer; + + NSMutableDictionary *_helloDevices; + NSOperationQueue *_locationLoadQueue; +} + +@end + +@implementation SSDPDiscoveryProvider + +static double refreshTime = 10.0; +static double searchAttemptsBeforeKill = 6.0; + +#pragma mark - Setup/creation + +- (instancetype) init +{ + self = [super init]; + + if (self) + { + _ssdpHostName = [NSString stringWithFormat:@"%@:%d", kSSDP_multicast_address, kSSDP_port]; + + _foundServices = [[NSMutableDictionary alloc] init]; + _serviceFilters = [[NSMutableArray alloc] init]; + + _locationLoadQueue = [[NSOperationQueue alloc] init]; + _locationLoadQueue.maxConcurrentOperationCount = 10; + + self.isRunning = NO; + } + + return self; +} + +#pragma mark - Control methods + +- (void) startDiscovery +{ + if (!self.isRunning) + { + self.isRunning = YES; + [self start]; + } +} + +- (void) stopDiscovery +{ + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + + if (_refreshTimer) + [_refreshTimer invalidate]; + + if (_searchSocket) + [_searchSocket close]; + + if (_multicastSocket) + [_multicastSocket close]; + + _foundServices = [NSMutableDictionary new]; + _helloDevices = [NSMutableDictionary new]; + [_locationLoadQueue cancelAllOperations]; + + self.isRunning = NO; + + _searchSocket = nil; + _multicastSocket = nil; + _refreshTimer = nil; +} + +- (void) start +{ + if (_refreshTimer == nil) + { + _refreshTimer = [NSTimer scheduledTimerWithTimeInterval:refreshTime target:self selector:@selector(sendSearchRequests:) userInfo:nil repeats:YES]; + + [self sendSearchRequests:NO]; + } +} + +#pragma mark - Device filter management + +- (void)addDeviceFilter:(NSDictionary *)parameters +{ + NSDictionary *ssdpInfo = [parameters objectForKey:@"ssdp"]; + _assert_state(ssdpInfo != nil, @"This device filter does not have ssdp discovery info"); + + NSString *searchFilter = [ssdpInfo objectForKey:@"filter"]; + _assert_state(searchFilter != nil, @"The ssdp info for this device filter has no search filter parameter"); + + _serviceFilters = [_serviceFilters arrayByAddingObject:parameters]; +} + +- (void)removeDeviceFilter:(NSDictionary *)parameters +{ + NSString *searchTerm = [parameters objectForKey:@"serviceId"]; + __block BOOL shouldRemove = NO; + __block NSUInteger removalIndex; + + [_serviceFilters enumerateObjectsUsingBlock:^(NSDictionary *searchFilter, NSUInteger idx, BOOL *stop) { + NSString *serviceId = [searchFilter objectForKey:@"serviceId"]; + + if ([serviceId isEqualToString:searchTerm]) + { + shouldRemove = YES; + removalIndex = idx; + *stop = YES; + } + }]; + + if (shouldRemove) + { + NSMutableArray *mutableFilters = [NSMutableArray arrayWithArray:_serviceFilters]; + [mutableFilters removeObjectAtIndex:removalIndex]; + _serviceFilters = [NSArray arrayWithArray:mutableFilters]; + } +} + +#pragma mark - SSDP M-SEARCH Request + +- (void) sendSearchRequests:(BOOL)shouldKillInactiveDevices +{ + [_serviceFilters enumerateObjectsUsingBlock:^(NSDictionary *info, NSUInteger idx, BOOL *stop) { + NSDictionary *ssdpInfo = [info objectForKey:@"ssdp"]; + NSString *searchFilter = [ssdpInfo objectForKey:@"filter"]; + NSString *userAgentToken = [ssdpInfo objectForKey:@"userAgentToken"]; + + [self sendRequestForFilter:searchFilter userAgentToken:userAgentToken killInactiveDevices:shouldKillInactiveDevices]; + }]; +} + +- (void) sendRequestForFilter:(NSString *)filter userAgentToken:(NSString *)userAgentToken killInactiveDevices:(BOOL)shouldKillInactiveDevices +{ + if (shouldKillInactiveDevices) + { + BOOL refresh = NO; + NSMutableArray *killKeys = [NSMutableArray array]; + + // 6 detection attempts, if still not present then kill it. + double killPoint = [[NSDate date] timeIntervalSince1970] - (refreshTime * searchAttemptsBeforeKill); + + @synchronized (_foundServices) + { + for (NSString *key in _foundServices) + { + ServiceDescription *service = (ServiceDescription *) [_foundServices objectForKey:key]; + + if (service.lastDetection < killPoint) + { + [killKeys addObject:key]; + refresh = YES; + } + } + + if (refresh) + { + [killKeys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop) + { + ServiceDescription *service = [_foundServices objectForKey:key]; + + [self notifyDelegateOfLostService:service]; + + [_foundServices removeObjectForKey:key]; + }]; + } + } + } + + CFHTTPMessageRef theSearchRequest = CFHTTPMessageCreateRequest(NULL, CFSTR("M-SEARCH"), + (__bridge CFURLRef)[NSURL URLWithString: @"*"], kCFHTTPVersion1_1); + CFHTTPMessageSetHeaderFieldValue(theSearchRequest, CFSTR("HOST"), (__bridge CFStringRef) _ssdpHostName); + CFHTTPMessageSetHeaderFieldValue(theSearchRequest, CFSTR("MAN"), CFSTR("\"ssdp:discover\"")); + CFHTTPMessageSetHeaderFieldValue(theSearchRequest, CFSTR("MX"), CFSTR("5")); + CFHTTPMessageSetHeaderFieldValue(theSearchRequest, CFSTR("ST"), (__bridge CFStringRef)filter); + CFHTTPMessageSetHeaderFieldValue(theSearchRequest, CFSTR("USER-AGENT"), (__bridge CFStringRef)[self userAgentForToken:userAgentToken]); + + NSData *message = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(theSearchRequest)); + + if (!_searchSocket) + { + _searchSocket = [[SSDPSocketListener alloc] initWithAddress:kSSDP_multicast_address andPort:0]; + _searchSocket.delegate = self; + [_searchSocket open]; + } + + if (!_multicastSocket) + { + _multicastSocket = [[SSDPSocketListener alloc] initWithAddress:kSSDP_multicast_address andPort:kSSDP_port]; + _multicastSocket.delegate = self; + [_multicastSocket open]; + } + + [_searchSocket sendData:message toAddress:kSSDP_multicast_address andPort:kSSDP_port]; + [self performBlock:^{ [_searchSocket sendData:message toAddress:kSSDP_multicast_address andPort:kSSDP_port]; } afterDelay:1]; + [self performBlock:^{ [_searchSocket sendData:message toAddress:kSSDP_multicast_address andPort:kSSDP_port]; } afterDelay:2]; + + CFRelease(theSearchRequest); +} + +#pragma mark - M-SEARCH Response Processing + +//* UDPSocket-delegate-method handle anew messages +//* All messages from devices handling here +- (void)socket:(SSDPSocketListener *)aSocket didReceiveData:(NSData *)aData fromAddress:(NSString *)anAddress +{ + // Try to create a HTTPMessage from received data. + + CFHTTPMessageRef theHTTPMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE); + CFHTTPMessageAppendBytes(theHTTPMessage, aData.bytes, aData.length); + + // We awaiting for receiving a complete header. If it not - just skip it. + if (CFHTTPMessageIsHeaderComplete(theHTTPMessage)) + { + + // Receive some important data from the header + NSString *theRequestMethod = CFBridgingRelease (CFHTTPMessageCopyRequestMethod(theHTTPMessage)); + NSInteger theCode = CFHTTPMessageGetResponseStatusCode(theHTTPMessage); + NSDictionary *theHeaderDictionary = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(theHTTPMessage)); + + BOOL isNotify = [theRequestMethod isEqualToString:@"NOTIFY"]; + NSString *theType = (isNotify) ? theHeaderDictionary[@"NT"] : theHeaderDictionary[@"ST"]; + + // There is 3 possible methods in SSDP: + // 1) M-SEARCH - for search requests - skip it + // 2) NOTIFY - for devices notification: advertisements ot bye-bye + // 3) * with CODE 200 - answer for M-SEARCH request + + // Obtain a unique service id ID - USN. + NSString *theUSSNKey = theHeaderDictionary[@"USN"]; + + if ((theCode == 200) && + ![theRequestMethod isEqualToString:@"M-SEARCH"] && + [self isSearchingForFilter:theType] && + (theUSSNKey.length > 0)) + { + //Extract the UUID + NSRegularExpression *reg = [[NSRegularExpression alloc] initWithPattern:@"(?:uuid:).*(?:::)" options:0 error:nil]; + NSString *theUUID; + NSTextCheckingResult *match = [reg firstMatchInString:theUSSNKey options:0 range:NSMakeRange(0, [theUSSNKey length])]; + + NSRange range = [match rangeAtIndex:0]; + range.location = range.location + 5; + range.length = MIN(range.length - 7, (theUSSNKey.length -range.location)); + theUUID = [theUSSNKey substringWithRange:range]; + + if (theUUID && theUUID.length > 0) + { + // If it is a NOTIFY - byebye message - try to find a device from a list and send him byebye + if ([theHeaderDictionary[@"NTS"] isEqualToString:@"ssdp:byebye"]) + { + @synchronized (_foundServices) + { + ServiceDescription *theService = _foundServices[theUUID]; + + if (theService != nil) + { + [self notifyDelegateOfLostService:theService]; + + [_foundServices removeObjectForKey:theUUID]; + + theService = nil; + } + } + } else + { + NSString *location = [theHeaderDictionary objectForKey:@"Location"]; + + if (location && location.length > 0) + { + // Advertising or search-respond + // Try to figure out if the device has been dicovered yet + ServiceDescription *foundService; + ServiceDescription *helloService; + + @synchronized(_foundServices) { foundService = [_foundServices objectForKey:theUUID]; } + @synchronized(_helloDevices) { helloService = [_helloDevices objectForKey:theUUID]; } + + BOOL isNew = NO; + + // If it isn't - create a new device object and add it to device list + if (foundService == nil && helloService == nil) + { + foundService = [[ServiceDescription alloc] init]; + //Check that this is what is wanted + foundService.UUID = theUUID; + foundService.type = theType; + + NSURL* url = [NSURL URLWithString:location]; + + if (url && url.scheme && url.host) + { + foundService.address = url.host; + } else + { + foundService.address = anAddress; + } + + foundService.port = 3001; + isNew = YES; + } + + foundService.lastDetection = [[NSDate date] timeIntervalSince1970]; + + // If device - newly-created one notify about it's discovering + if (isNew) + { + @synchronized (_helloDevices) + { + if (_helloDevices == nil) + _helloDevices = [NSMutableDictionary dictionary]; + + [_helloDevices setObject:foundService forKey:theUUID]; + } + + [self getLocationData:location forKey:theUUID andType:theType]; + } + } + } + } + } + } + + CFRelease(theHTTPMessage); +} + +- (void) getLocationData:(NSString*)url forKey:(NSString*)UUID andType:(NSString *)theType +{ + NSURL *req = [NSURL URLWithString:url]; + NSURLRequest *request = [NSURLRequest requestWithURL:req]; + [NSURLConnection sendAsynchronousRequest:request queue:_locationLoadQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { + NSError *xmlError; + NSDictionary *xml = [CTXMLReader dictionaryForXMLData:data error:&xmlError]; + + if (!xmlError) + { + NSDictionary *device = [self device:[xml valueForKeyPath:@"root.device"] + containingServicesWithFilter:theType]; + + if (device) + { + ServiceDescription *service; + @synchronized(_helloDevices) { service = [_helloDevices objectForKey:UUID]; } + + if (service) + { + service.type = theType; + service.friendlyName = [device valueForKeyPath:@"friendlyName.text"]; + service.modelName = [[device objectForKey:@"modelName"] objectForKey:@"text"]; + service.modelNumber = [[device objectForKey:@"modelNumber"] objectForKey:@"text"]; + service.modelDescription = [[device objectForKey:@"modelDescription"] objectForKey:@"text"]; + service.manufacturer = [[device objectForKey:@"manufacturer"] objectForKey:@"text"]; + service.locationXML = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + service.serviceList = [self serviceListForDevice:device]; + service.commandURL = response.URL; + service.locationResponseHeaders = [((NSHTTPURLResponse *)response) allHeaderFields]; + + @synchronized(_foundServices) { [_foundServices setObject:service forKey:UUID]; } + + [self notifyDelegateOfNewService:service]; + } + } + } + + @synchronized(_helloDevices) { [_helloDevices removeObjectForKey:UUID]; } + }]; +} + +- (void) notifyDelegateOfNewService:(ServiceDescription *)service +{ + NSArray *serviceIds = [self serviceIdsForFilter:service.type]; + + [serviceIds enumerateObjectsUsingBlock:^(NSString *serviceId, NSUInteger idx, BOOL *stop) { + ServiceDescription *newService = [service copy]; + newService.serviceId = serviceId; + + dispatch_on_main(^{ [self.delegate discoveryProvider:self didFindService:newService]; }); + }]; +} + +- (void) notifyDelegateOfLostService:(ServiceDescription *)service +{ + NSArray *serviceIds = [self serviceIdsForFilter:service.type]; + + [serviceIds enumerateObjectsUsingBlock:^(NSString *serviceId, NSUInteger idx, BOOL *stop) { + ServiceDescription *newService = [service copy]; + newService.serviceId = serviceId; + + dispatch_on_main(^{ [self.delegate discoveryProvider:self didLoseService:newService]; }); + }]; +} + +#pragma mark - Helper methods + +- (BOOL) isSearchingForFilter:(NSString *)filter +{ + __block BOOL containsFilter = NO; + + [_serviceFilters enumerateObjectsUsingBlock:^(NSDictionary *serviceFilter, NSUInteger idx, BOOL *stop) { + NSString *ssdpFilter = [[serviceFilter objectForKey:@"ssdp" ] objectForKey:@"filter"]; + + if ([ssdpFilter isEqualToString:filter]) + { + containsFilter = YES; + *stop = YES; + } + }]; + + return containsFilter; +} + +/// Returns the required services strings array for the given registered filter, +/// or @c nil. +- (NSArray *)requiredServicesForFilter:(NSString *)filter { + NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"%K LIKE %@", + @"ssdp.filter", filter]; + NSDictionary *serviceFilter = [_serviceFilters filteredArrayUsingPredicate:filterPredicate].firstObject; + return [serviceFilter valueForKeyPath:@"ssdp.requiredServices"]; +} + +/// Returns the discovered services strings array for the given XML device +/// description, or @c nil. +- (NSArray *)discoveredServicesInDevice:(NSDictionary *)device { + id serviceList = [device valueForKeyPath:@"serviceList.service"]; + NSArray *discoveredServices; + NSString *const kServiceTypeKeyPath = @"serviceType.text"; + + if ([serviceList isKindOfClass:[NSDictionary class]]) { + discoveredServices = [NSArray arrayWithObject:[serviceList valueForKeyPath:kServiceTypeKeyPath]]; + } else if ([serviceList isKindOfClass:[NSArray class]]) { + discoveredServices = [serviceList valueForKeyPath:kServiceTypeKeyPath]; + } + + return discoveredServices; +} + +/// Returns YES if all of the required services are available among the +/// discovered ones. +- (BOOL)allRequiredServices:(NSArray *)requiredServices + areInDiscoveredServices:(NSArray *)discoveredServices { + NSSet *requiredServicesSet = [NSSet setWithArray:requiredServices]; + NSSet *discoveredServicesSet = [NSSet setWithArray:discoveredServices]; + return [requiredServicesSet isSubsetOfSet:discoveredServicesSet]; +} + +/// Returns a device description that contains the given required services. It +/// may be the root device or any of the subdevices. If no device matches, +/// returns @c nil. +- (NSDictionary *)device:(NSDictionary *)device +containingRequiredServices:(NSArray *)requiredServices { + NSArray *discoveredServices = [self discoveredServicesInDevice:device]; + const BOOL deviceHasAllRequiredServices = [self allRequiredServices:requiredServices + areInDiscoveredServices:discoveredServices]; + + if (deviceHasAllRequiredServices) { + return device; + } + + // try to iterate through all the child devices + NSArray *subDevices = [device valueForKeyPath:@"deviceList.device"]; + if (subDevices) { + if (![subDevices isKindOfClass:[NSArray class]]) { + subDevices = [NSArray arrayWithObject:subDevices]; + } + + for (NSDictionary *subDevice in subDevices) { + NSDictionary *foundDevice = [self device:subDevice + containingRequiredServices:requiredServices]; + if (foundDevice) { + return foundDevice; + } + } + } + + return nil; +} + +/// Returns a device description that contains services for the given filter. It +/// may be the root device or any of the subdevices. If no device matches, +/// returns @c nil. +- (NSDictionary *)device:(NSDictionary *)device containingServicesWithFilter:(NSString *)filter { + NSArray *requiredServices = [self requiredServicesForFilter:filter]; + return [self device:device +containingRequiredServices:requiredServices]; +} + +- (NSArray *) serviceIdsForFilter:(NSString *)filter +{ + __block NSMutableArray *serviceIds = [NSMutableArray new]; + + [_serviceFilters enumerateObjectsUsingBlock:^(NSDictionary *serviceFilter, NSUInteger idx, BOOL *stop) { + NSString *ssdpFilter = [[serviceFilter objectForKey:@"ssdp"] objectForKey:@"filter"]; + + if ([ssdpFilter isEqualToString:filter]) + [serviceIds addObject:[serviceFilter objectForKey:@"serviceId"]]; + }]; + + return [NSArray arrayWithArray:serviceIds]; +} + +- (NSArray *) serviceListForDevice:(id)device +{ + NSMutableArray *list = [NSMutableArray new]; + + id serviceList = device[@"serviceList"][@"service"]; + + if ([serviceList isKindOfClass:[NSArray class]]) + [list addObjectsFromArray:serviceList]; + else if ([serviceList isKindOfClass:[NSDictionary class]]) + [list addObject:serviceList]; + + NSArray *devices = nil; + id devicesObject = device[@"deviceList"][@"device"]; + if ([devicesObject isKindOfClass:[NSArray class]]) { + devices = devicesObject; + } else if ([devicesObject isKindOfClass:[NSDictionary class]]) { + devices = [NSArray arrayWithObject:devicesObject]; + } + + if (devices) + { + [devices enumerateObjectsUsingBlock:^(id deviceInfo, NSUInteger idx, BOOL *stop) { + id services = deviceInfo[@"serviceList"][@"service"]; + + if ([services isKindOfClass:[NSArray class]]) + [list addObjectsFromArray:services]; + else if ([services isKindOfClass:[NSDictionary class]]) + [list addObject:services]; + }]; + } + + return [NSArray arrayWithArray:list]; +} + +- (void) performBlock:(void (^)())block afterDelay:(NSTimeInterval)delay +{ + [self performSelector:@selector(performBlock:) withObject:block afterDelay:delay]; +} + +- (void) performBlock:(void (^)())block +{ + if (block) + block(); +} + +- (NSString *) userAgentForToken:(NSString *)token +{ + if (!token) + token = @"UPnP/1.1"; + + return [NSString stringWithFormat: + @"%@/%@ %@ ConnectSDK/%@", + [UIDevice currentDevice].systemName, + [UIDevice currentDevice].systemVersion, + token, + CONNECT_SDK_VERSION]; +} + +@end diff --git a/core/Discovery/Providers/SSDPDiscoveryProvider_Private.h b/core/Discovery/Providers/SSDPDiscoveryProvider_Private.h new file mode 100644 index 00000000..d0cd90c1 --- /dev/null +++ b/core/Discovery/Providers/SSDPDiscoveryProvider_Private.h @@ -0,0 +1,34 @@ +// +// SSDPDiscoveryProvider_Private.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 11/13/14. +// Copyright (c) 2014 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "SSDPDiscoveryProvider.h" + +#import "SSDPSocketListener.h" + +@class SSDPSocketListener; + +@interface SSDPDiscoveryProvider () + +@property (nonatomic, strong) SSDPSocketListener *multicastSocket; +@property (nonatomic, strong) SSDPSocketListener *searchSocket; + +- (NSArray *) serviceListForDevice:(id)device; + +@end diff --git a/core/Discovery/Providers/ZeroConfDiscoveryProvider.h b/core/Discovery/Providers/ZeroConfDiscoveryProvider.h new file mode 100644 index 00000000..580c3a8e --- /dev/null +++ b/core/Discovery/Providers/ZeroConfDiscoveryProvider.h @@ -0,0 +1,26 @@ +// +// ZeroConfDiscoveryProvider.h +// Connect SDK +// +// Created by Jeremy White on 4/18/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "DiscoveryProvider.h" + + +@interface ZeroConfDiscoveryProvider : DiscoveryProvider +@end diff --git a/core/Discovery/Providers/ZeroConfDiscoveryProvider.m b/core/Discovery/Providers/ZeroConfDiscoveryProvider.m new file mode 100644 index 00000000..78650d06 --- /dev/null +++ b/core/Discovery/Providers/ZeroConfDiscoveryProvider.m @@ -0,0 +1,317 @@ +// +// ZeroConfDiscoveryProvider.m +// Connect SDK +// +// Created by Jeremy White on 4/18/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ZeroConfDiscoveryProvider_Private.h" +#import "ServiceDescription.h" +#import "CommonMacros.h" +#include + + +@interface ZeroConfDiscoveryProvider () +{ + NSArray *_serviceFilters; + NSTimer *_refreshTimer; + NSMutableDictionary *_resolvingDevices; + NSMutableDictionary *_discoveredDevices; +} + +@end + +@implementation ZeroConfDiscoveryProvider + +- (void) startDiscovery +{ + if (self.isRunning) + return; + + if (!_netServiceBrowser) + { + _netServiceBrowser = [[NSNetServiceBrowser alloc] init]; + _netServiceBrowser.delegate = self; + } + + if (_refreshTimer) + [_refreshTimer invalidate]; + + _resolvingDevices = [NSMutableDictionary new]; + _discoveredDevices = [NSMutableDictionary new]; + + self.isRunning = YES; + + _refreshTimer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(searchForServices) userInfo:nil repeats:YES]; + [_refreshTimer fire]; +} + +- (void) stopDiscovery +{ + if (!self.isRunning) + return; + + if (_netServiceBrowser) + [_netServiceBrowser stop]; + + if (_refreshTimer) + { + [_refreshTimer invalidate]; + _refreshTimer = nil; + } + + self.isRunning = NO; + + _resolvingDevices = [NSMutableDictionary new]; + _discoveredDevices = [NSMutableDictionary new]; +} + +- (void) searchForServices +{ + [_serviceFilters enumerateObjectsUsingBlock:^(NSDictionary *serviceFilter, NSUInteger idx, BOOL *stop) + { + NSString *filterType = serviceFilter[@"zeroconf"][@"filter"]; + + if (filterType) + [_netServiceBrowser searchForServicesOfType:filterType inDomain:@"local."]; + }]; +} + +- (void)addDeviceFilter:(NSDictionary *)parameters +{ + if (!_serviceFilters) + _serviceFilters = [NSArray new]; + + NSDictionary *ssdpInfo = [parameters objectForKey:@"zeroconf"]; + _assert_state(ssdpInfo != nil, @"This device filter does not have zeroconf discovery info"); + + NSString *searchFilter = [ssdpInfo objectForKey:@"filter"]; + _assert_state(searchFilter != nil, @"The ssdp info for this device filter has no search filter parameter"); + + _serviceFilters = [_serviceFilters arrayByAddingObject:parameters]; +} + +- (void)removeDeviceFilter:(NSDictionary *)parameters +{ + NSString *searchTerm = [parameters objectForKey:@"serviceId"]; + __block BOOL shouldRemove = NO; + __block NSUInteger removalIndex; + + [_serviceFilters enumerateObjectsUsingBlock:^(NSDictionary *searchFilter, NSUInteger idx, BOOL *stop) { + NSString *serviceId = [searchFilter objectForKey:@"serviceId"]; + + if ([serviceId isEqualToString:searchTerm]) + { + shouldRemove = YES; + removalIndex = idx; + *stop = YES; + } + }]; + + if (shouldRemove) + { + NSMutableArray *mutableFilters = [NSMutableArray arrayWithArray:_serviceFilters]; + [mutableFilters removeObjectAtIndex:removalIndex]; + _serviceFilters = [NSArray arrayWithArray:mutableFilters]; + } +} + +#pragma mark - NSNetServiceBrowserDelegate + +- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing +{ + if ([_resolvingDevices objectForKey:aNetService.name] || [_discoveredDevices objectForKey:aNetService.name]) + return; + + DLog(@"%@ : %@", aNetService.name, aNetService.domain); + + [aNetService setDelegate:self]; + [aNetService resolveWithTimeout:5.0]; + [_resolvingDevices setObject:aNetService forKey:aNetService.name]; + + if (!moreComing) + [aNetServiceBrowser stop]; +} + +- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing +{ + if (![_discoveredDevices objectForKey:aNetService.name]) + return; + + DLog(@"%@", aNetService.name); + + ServiceDescription *serviceDescription = _discoveredDevices[aNetService.name]; + + [_discoveredDevices removeObjectForKey:aNetService.name]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(discoveryProvider:didLoseService:)]) + [self.delegate discoveryProvider:self didLoseService:serviceDescription]; + + if (!moreComing) + [aNetServiceBrowser stop]; +} + +#pragma mark - NSNetServiceDelegate + +/** + * Parses the given address @c data (containing a @c sockaddr structure), + * returning the IP address and transport port. + * @note Only IPv4 addresses are supported. + * @see -[NSNetService addresses] + * @see Based on http://stackoverflow.com/a/18428117/2715 + * @param data data containing a @c sockaddr structure; must not be @c nil. + * @param address a pointer to a NSString * value where the parsed IP + * address will be placed; must not be @c nil. + * @param port a pointer to a @c uint16_t value where the parsed port will be + * placed; must not be @c nil. + * @return @c YES if the parsing is successful and the output values are filled. + */ +- (BOOL)parseAddressData:(nonnull NSData *)data + intoIPAddress:(out NSString ** __nonnull)outAddress + andPort:(out nonnull in_port_t *)outPort { + NSParameterAssert(data); + NSParameterAssert(outAddress); + NSParameterAssert(outPort); + + BOOL success = NO; + NSString *address; + in_port_t port = 0; + + struct sockaddr *addressGeneric = (struct sockaddr *) [data bytes]; + switch (addressGeneric->sa_family) { + case AF_INET: { + struct sockaddr_in *ip4; + char dest[INET_ADDRSTRLEN]; + ip4 = (struct sockaddr_in *) [data bytes]; + port = ntohs(ip4->sin_port); + address = [NSString stringWithFormat: @"%s", + inet_ntop(AF_INET, &ip4->sin_addr, dest, sizeof dest)]; + success = YES; + break; + } + + default: + break; + } + + if (success) { + *outAddress = address; + *outPort = port; + } + + return success; +} + +- (void) netServiceDidResolveAddress:(NSNetService *)sender +{ + sender.delegate = nil; + [_resolvingDevices removeObjectForKey:sender.name]; + + // according to Apple's docs, it is possible to have a service resolve with no addresses + if (!sender.addresses || sender.addresses.count == 0) + { + DLog(@"%@ resolved with 0 addresses, bailing ...", sender.name); + return; + } + + __block BOOL foundIPv4Address = NO; + __block NSString *address; + __block in_port_t port = 0; + [sender.addresses enumerateObjectsUsingBlock:^(NSData *addressData, NSUInteger idx, BOOL *stop) { + if ((foundIPv4Address = [self parseAddressData:addressData + intoIPAddress:&address + andPort:&port])) { + DLog(@"%@: resolved %@:%d", sender.name, address, port); + *stop = YES; + } + }]; + + if (!foundIPv4Address) { + DLog(@"%@: couldn't find resolved IPv4 addresses (%ld total)", + sender.name, (unsigned long)sender.addresses.count); + return; + } + + NSData *TXTRecordData = sender.TXTRecordData; + NSString *TXTRecord = [[NSString alloc] initWithData:TXTRecordData encoding:NSUTF8StringEncoding]; + NSString *uuidIdentifier = @"deviceid="; + NSRange UUIDRange = [TXTRecord rangeOfString:uuidIdentifier]; + + NSString *UUID; + + if (UUIDRange.location == NSNotFound) + UUID = sender.name; + else + { + NSUInteger uuidStartLocation = UUIDRange.location + uuidIdentifier.length; + NSUInteger macAddressLength = 14; + + UUID = [TXTRecord substringWithRange:NSMakeRange(uuidStartLocation, macAddressLength)]; + } + + NSString *serviceId = [self serviceIdFromFilter:sender.type]; + + ServiceDescription *serviceDescription = [ServiceDescription descriptionWithAddress:address UUID:sender.name]; + serviceDescription.friendlyName = sender.name; + serviceDescription.serviceId = serviceId; + serviceDescription.port = (NSUInteger) port; + serviceDescription.UUID = UUID; + + NSString *commandPath = [NSString stringWithFormat:@"http://%@:%@/", address, @(port)]; + serviceDescription.commandURL = [NSURL URLWithString:commandPath]; + + [_discoveredDevices setObject:serviceDescription forKey:sender.name]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(discoveryProvider:didFindService:)]) + [self.delegate discoveryProvider:self didFindService:serviceDescription]; +} + +- (void) netService:(NSNetService *)sender didUpdateTXTRecordData:(NSData *)data +{ + DLog(@"%@", sender.name); +} + +- (void) netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict +{ + DLog(@"%@ : %@", sender.name, errorDict); + + [_resolvingDevices removeObjectForKey:sender.name]; +} + +#pragma mark - Helper methods + +- (NSString *)serviceIdFromFilter:(NSString *)filter +{ + if (!filter || filter.length == 0) + return nil; + + __block NSString *serviceId; + + [_serviceFilters enumerateObjectsUsingBlock:^(NSDictionary *serviceFilter, NSUInteger idx, BOOL *stop) + { + NSString *serviceFilterType = serviceFilter[@"zeroconf"][@"filter"]; + + if ([filter rangeOfString:serviceFilterType].location != NSNotFound) + { + serviceId = serviceFilter[@"serviceId"]; + *stop = YES; + } + }]; + + return serviceId; +} + +@end diff --git a/core/Discovery/Providers/ZeroConfDiscoveryProvider_Private.h b/core/Discovery/Providers/ZeroConfDiscoveryProvider_Private.h new file mode 100644 index 00000000..85387190 --- /dev/null +++ b/core/Discovery/Providers/ZeroConfDiscoveryProvider_Private.h @@ -0,0 +1,28 @@ +// +// ZeroConfDiscoveryProvider_Private.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 11/18/14. +// Copyright (c) 2014 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ZeroConfDiscoveryProvider.h" + +@interface ZeroConfDiscoveryProvider () + +@property (nonatomic, strong) NSNetServiceBrowser *netServiceBrowser; + +@end diff --git a/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/Info.plist b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/Info.plist new file mode 100644 index 00000000..e0b1c6f6 --- /dev/null +++ b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/Info.plist @@ -0,0 +1,39 @@ + + + + + AvailableLibraries + + + LibraryIdentifier + ios-x86_64-simulator + LibraryPath + GStreamerForLGCast.framework + SupportedArchitectures + + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + LibraryIdentifier + ios-arm64 + LibraryPath + GStreamerForLGCast.framework + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/GStreamerForLGCast b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/GStreamerForLGCast new file mode 100755 index 00000000..f5638812 Binary files /dev/null and b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/GStreamerForLGCast differ diff --git a/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/Headers/GStreamerForLGCast.h b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/Headers/GStreamerForLGCast.h new file mode 100644 index 00000000..1e70f364 --- /dev/null +++ b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/Headers/GStreamerForLGCast.h @@ -0,0 +1,31 @@ +// +// GStreamerForLGCast.h +// GStreamerForLGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#import "LCStreamer.h" +#import "LCStreamerData.h" + +//! Project version number for LGCast_GStreamer. +FOUNDATION_EXPORT double LGCast_GStreamerVersionNumber; + +//! Project version string for LGCast_GStreamer. +FOUNDATION_EXPORT const unsigned char LGCast_GStreamerVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import diff --git a/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/Headers/LCStreamer.h b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/Headers/LCStreamer.h new file mode 100644 index 00000000..dcb51f50 --- /dev/null +++ b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/Headers/LCStreamer.h @@ -0,0 +1,52 @@ +// +// LCStreamer.h +// GStreamerForLGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +typedef enum { + Video = 0, + Audio, + AV +} LCStreamerMediaType; + +extern NSString* const LCStreamerAudioSourceBinName; +extern NSString* const LCStreamerAudioRtpBinName; +extern NSString* const LCStreamerAudioSrtpBinName; + +extern NSString* const LCStreamerVideoSourceBinName; +extern NSString* const LCStreamerVideoRtpBinName; +extern NSString* const LCStreamerVideoSrtpBinName; + +@protocol LCStreamerDelegate +- (void)gstreamerDidInitialize; +- (void)gstreamerDidSendMessage:(NSString *)message; +@end + +@interface LCStreamer: NSObject + +@property (nonatomic, weak) id delegate; + +- (id)init:(id)delegate; +- (void)setDebugLevel:(int)level; +- (BOOL)setStreamerInfo:(NSDictionary *)info; +- (void)start; +- (BOOL)sendMediaData:(int32_t)mediaType pts:(UInt64)pts data:(NSData *)data; +- (void)stop; + +@end diff --git a/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/Headers/LCStreamerData.h b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/Headers/LCStreamerData.h new file mode 100644 index 00000000..6f2e3600 --- /dev/null +++ b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/Headers/LCStreamerData.h @@ -0,0 +1,26 @@ +// +// LCStreamerData.h +// GStreamerForLGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +typedef NSString *LCStreamerDataKey; + +extern LCStreamerDataKey const LCStreamerDataKeyPipeline; +extern LCStreamerDataKey const LCStreamerDataKeySecurityMasterKeyData; +extern LCStreamerDataKey const LCStreamerDataKeySecurityMkiData; diff --git a/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/Info.plist b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/Info.plist new file mode 100644 index 00000000..66df6bdd Binary files /dev/null and b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/Info.plist differ diff --git a/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/Modules/module.modulemap b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/Modules/module.modulemap new file mode 100644 index 00000000..93fa58d1 --- /dev/null +++ b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-arm64/GStreamerForLGCast.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module GStreamerForLGCast { + umbrella header "GStreamerForLGCast.h" + + export * + module * { export * } +} diff --git a/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/GStreamerForLGCast b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/GStreamerForLGCast new file mode 100755 index 00000000..eb304947 Binary files /dev/null and b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/GStreamerForLGCast differ diff --git a/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/Headers/GStreamerForLGCast.h b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/Headers/GStreamerForLGCast.h new file mode 100644 index 00000000..1e70f364 --- /dev/null +++ b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/Headers/GStreamerForLGCast.h @@ -0,0 +1,31 @@ +// +// GStreamerForLGCast.h +// GStreamerForLGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#import "LCStreamer.h" +#import "LCStreamerData.h" + +//! Project version number for LGCast_GStreamer. +FOUNDATION_EXPORT double LGCast_GStreamerVersionNumber; + +//! Project version string for LGCast_GStreamer. +FOUNDATION_EXPORT const unsigned char LGCast_GStreamerVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import diff --git a/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/Headers/LCStreamer.h b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/Headers/LCStreamer.h new file mode 100644 index 00000000..dcb51f50 --- /dev/null +++ b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/Headers/LCStreamer.h @@ -0,0 +1,52 @@ +// +// LCStreamer.h +// GStreamerForLGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +typedef enum { + Video = 0, + Audio, + AV +} LCStreamerMediaType; + +extern NSString* const LCStreamerAudioSourceBinName; +extern NSString* const LCStreamerAudioRtpBinName; +extern NSString* const LCStreamerAudioSrtpBinName; + +extern NSString* const LCStreamerVideoSourceBinName; +extern NSString* const LCStreamerVideoRtpBinName; +extern NSString* const LCStreamerVideoSrtpBinName; + +@protocol LCStreamerDelegate +- (void)gstreamerDidInitialize; +- (void)gstreamerDidSendMessage:(NSString *)message; +@end + +@interface LCStreamer: NSObject + +@property (nonatomic, weak) id delegate; + +- (id)init:(id)delegate; +- (void)setDebugLevel:(int)level; +- (BOOL)setStreamerInfo:(NSDictionary *)info; +- (void)start; +- (BOOL)sendMediaData:(int32_t)mediaType pts:(UInt64)pts data:(NSData *)data; +- (void)stop; + +@end diff --git a/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/Headers/LCStreamerData.h b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/Headers/LCStreamerData.h new file mode 100644 index 00000000..6f2e3600 --- /dev/null +++ b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/Headers/LCStreamerData.h @@ -0,0 +1,26 @@ +// +// LCStreamerData.h +// GStreamerForLGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +typedef NSString *LCStreamerDataKey; + +extern LCStreamerDataKey const LCStreamerDataKeyPipeline; +extern LCStreamerDataKey const LCStreamerDataKeySecurityMasterKeyData; +extern LCStreamerDataKey const LCStreamerDataKeySecurityMkiData; diff --git a/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/Info.plist b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/Info.plist new file mode 100644 index 00000000..4e3b7a87 Binary files /dev/null and b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/Info.plist differ diff --git a/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/Modules/module.modulemap b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/Modules/module.modulemap new file mode 100644 index 00000000..93fa58d1 --- /dev/null +++ b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module GStreamerForLGCast { + umbrella header "GStreamerForLGCast.h" + + export * + module * { export * } +} diff --git a/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/_CodeSignature/CodeResources b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/_CodeSignature/CodeResources new file mode 100644 index 00000000..424bbd88 --- /dev/null +++ b/core/Frameworks/LGCast/GStreamerForLGCast.xcframework/ios-x86_64-simulator/GStreamerForLGCast.framework/_CodeSignature/CodeResources @@ -0,0 +1,146 @@ + + + + + files + + Headers/GStreamerForLGCast.h + + 6udDnMtJHjmGVun5IzkCzSglhfk= + + Headers/LCStreamer.h + + JKpySU8ijDUeJ0GrErZU8GzyAlM= + + Headers/LCStreamerData.h + + FtUdxB8MNeblw8Sc9YWPkI26oqY= + + Info.plist + + ftzB0e9V+1HV2A9CMS2ze9gf+OQ= + + Modules/module.modulemap + + RIdGWlAmO3EooQT4p7lQN0Q1G9A= + + + files2 + + Headers/GStreamerForLGCast.h + + hash2 + + QEcyVzlfMcWYI+gBAqEZrGeCNBU6Q4FmiD9PCHYVy+k= + + + Headers/LCStreamer.h + + hash2 + + q1MwPlkHufmtVZFU89urtj74pyFQ/Nn3ad8K1QP+WdI= + + + Headers/LCStreamerData.h + + hash2 + + PxLJInSPbIRmuyw7O5gblxQTH+UN+KHq5tsLhp4e1+0= + + + Modules/module.modulemap + + hash2 + + zO7oUJO9M2xUB0omQzmtJs2R1hvhp7IgE8aMJyGdDrw= + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/core/Frameworks/LGCast/GStreamerForLGCast.zip b/core/Frameworks/LGCast/GStreamerForLGCast.zip new file mode 100644 index 00000000..43a30781 Binary files /dev/null and b/core/Frameworks/LGCast/GStreamerForLGCast.zip differ diff --git a/core/Frameworks/LGCast/LGCast.xcframework/Info.plist b/core/Frameworks/LGCast/LGCast.xcframework/Info.plist new file mode 100644 index 00000000..a736f86c --- /dev/null +++ b/core/Frameworks/LGCast/LGCast.xcframework/Info.plist @@ -0,0 +1,39 @@ + + + + + AvailableLibraries + + + LibraryIdentifier + ios-x86_64-simulator + LibraryPath + LGCast.framework + SupportedArchitectures + + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + LibraryIdentifier + ios-arm64 + LibraryPath + LGCast.framework + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Headers/LGCast-Swift.h b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Headers/LGCast-Swift.h new file mode 100644 index 00000000..4b4e9599 --- /dev/null +++ b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Headers/LGCast-Swift.h @@ -0,0 +1,696 @@ +#if 0 +#elif defined(__arm64__) && __arm64__ +// Generated by Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51) +#ifndef LGCAST_SWIFT_H +#define LGCAST_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wduplicate-method-match" +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#else +#include +#include +#include +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif + +#if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +#else +# define SWIFT_RUNTIME_NAME(X) +#endif +#if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +#else +# define SWIFT_COMPILE_NAME(X) +#endif +#if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +#else +# define SWIFT_METHOD_FAMILY(X) +#endif +#if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +#else +# define SWIFT_NOESCAPE +#endif +#if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +#else +# define SWIFT_RELEASES_ARGUMENT +#endif +#if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +# define SWIFT_WARN_UNUSED_RESULT +#endif +#if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +#else +# define SWIFT_NORETURN +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif + +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif + +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif + +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if defined(__has_attribute) && __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +#else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if defined(__cplusplus) +#if !defined(SWIFT_NOEXCEPT) +# define SWIFT_NOEXCEPT noexcept +#endif +#else +#if !defined(SWIFT_NOEXCEPT) +# define SWIFT_NOEXCEPT +#endif +#endif +#if defined(__cplusplus) +#if !defined(SWIFT_CXX_INT_DEFINED) +#define SWIFT_CXX_INT_DEFINED +namespace swift { +using Int = ptrdiff_t; +using UInt = size_t; +} +#endif +#endif +#if defined(__OBJC__) +#if __has_feature(modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +@import AVFoundation; +@import CoreFoundation; +@import CoreMedia; +@import Foundation; +@import GStreamerForLGCast; +@import ObjectiveC; +@import ReplayKit; +@import UIKit; +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="LGCast",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) + +SWIFT_CLASS("_TtC6LGCast12CameraDevice") +@interface CameraDevice : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +@class AVCaptureOutput; +@class AVCaptureConnection; + +@interface CameraDevice (SWIFT_EXTENSION(LGCast)) +- (void)captureOutput:(AVCaptureOutput * _Nonnull)output didOutputSampleBuffer:(CMSampleBufferRef _Nonnull)sampleBuffer fromConnection:(AVCaptureConnection * _Nonnull)connection; +@end + + +SWIFT_CLASS("_TtC6LGCast13CameraService") +@interface CameraService : NSObject +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +@end + + + + +@class NSString; +@protocol LGCastCameraApiDelegate; +@class UIView; +@class LGCastCameraPreviewView; +@class LGCastSecurityKey; +@class LGCastCameraParameterInfo; +enum LGCastCameraProperty : NSInteger; +@class LGCastDeviceSettings; +@class LGCastCameraResolutionInfo; +enum LGCastCameraPosition : NSInteger; + +SWIFT_CLASS("_TtC6LGCast15LGCastCameraApi") +@interface LGCastCameraApi : NSObject +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSString * _Nonnull KEY_CAMERA_POSITION;) ++ (NSString * _Nonnull)KEY_CAMERA_POSITION SWIFT_WARN_UNUSED_RESULT; +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSString * _Nonnull KEY_PREVIEW_VIDEO_GRAVITY;) ++ (NSString * _Nonnull)KEY_PREVIEW_VIDEO_GRAVITY SWIFT_WARN_UNUSED_RESULT; +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSString * _Nonnull KEY_IS_SUPPORT_LANDSCAPE_MODE;) ++ (NSString * _Nonnull)KEY_IS_SUPPORT_LANDSCAPE_MODE SWIFT_WARN_UNUSED_RESULT; +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) LGCastCameraApi * _Nonnull shared;) ++ (LGCastCameraApi * _Nonnull)shared SWIFT_WARN_UNUSED_RESULT; +@property (nonatomic, weak) id _Nullable delegate; +@property (nonatomic, readonly) BOOL isRunning; +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +- (BOOL)isSupportedOsVersion SWIFT_WARN_UNUSED_RESULT; +- (UIView * _Nonnull)createCameraPreviewView:(NSDictionary * _Nullable)options SWIFT_WARN_UNUSED_RESULT; +- (void)setPreviewView:(LGCastCameraPreviewView * _Nonnull)view options:(NSDictionary * _Nullable)options; +- (NSArray * _Nullable)generateCameraMasterKey:(NSString * _Nonnull)publicKey SWIFT_WARN_UNUSED_RESULT; +- (LGCastCameraParameterInfo * _Nullable)getCameraProperties SWIFT_WARN_UNUSED_RESULT; +- (BOOL)setCameraPropertiesWithProperty:(enum LGCastCameraProperty)property value:(NSInteger)value SWIFT_WARN_UNUSED_RESULT; +- (void)startRemoteCamera:(LGCastDeviceSettings * _Nonnull)settings; +- (void)stopRemoteCamera; +- (NSArray * _Nonnull)getSupportedResolutions SWIFT_WARN_UNUSED_RESULT; +- (LGCastCameraResolutionInfo * _Nonnull)getCurrentResolution SWIFT_WARN_UNUSED_RESULT; +- (BOOL)setResolution:(LGCastCameraResolutionInfo * _Nonnull)resolution SWIFT_WARN_UNUSED_RESULT; +- (BOOL)changeCameraPosition:(enum LGCastCameraPosition)position SWIFT_WARN_UNUSED_RESULT; +- (BOOL)muteMicrophone:(BOOL)isMuted SWIFT_WARN_UNUSED_RESULT; +@end + + +enum LGCastCameraError : NSInteger; + +SWIFT_PROTOCOL("_TtP6LGCast23LGCastCameraApiDelegate_") +@protocol LGCastCameraApiDelegate +- (void)lgcastCameraDidPlay; +- (void)lgcastCameraDidChangeWithProperty:(enum LGCastCameraProperty)property; +- (void)lgcastCameraErrorDidOccurWithError:(enum LGCastCameraError)error; +@end + +typedef SWIFT_ENUM(NSInteger, LGCastCameraError, open) { + LGCastCameraErrorUnknown = 0, + LGCastCameraErrorConnectionClosed = 1, + LGCastCameraErrorDeviceShutdown = 2, + LGCastCameraErrorRendererTerminated = 3, +}; + +enum LGCastCamperaPropertyStatus : NSInteger; + +SWIFT_CLASS("_TtC6LGCast25LGCastCameraParameterInfo") +@interface LGCastCameraParameterInfo : NSObject +@property (nonatomic) enum LGCastCamperaPropertyStatus audio; +@property (nonatomic) enum LGCastCamperaPropertyStatus autoWhiteBalance; +@property (nonatomic) NSInteger brightness; +@property (nonatomic) NSInteger width; +@property (nonatomic) NSInteger height; +@property (nonatomic) enum LGCastCameraPosition facing; +@property (nonatomic) NSInteger whiteBalance; +@property (nonatomic) NSInteger rotation; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +typedef SWIFT_ENUM(NSInteger, LGCastCameraPosition, open) { + LGCastCameraPositionFront = 0, + LGCastCameraPositionBack = 1, +}; + +typedef SWIFT_ENUM(NSInteger, LGCastCameraPreviewVideoGravity, open) { + LGCastCameraPreviewVideoGravityResize = 0, + LGCastCameraPreviewVideoGravityResizeAspect = 1, + LGCastCameraPreviewVideoGravityResizeAspectFill = 2, +}; + +@class NSCoder; + +SWIFT_CLASS("_TtC6LGCast23LGCastCameraPreviewView") +@interface LGCastCameraPreviewView : UIView +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly) Class _Nonnull layerClass;) ++ (Class _Nonnull)layerClass SWIFT_WARN_UNUSED_RESULT; +- (nonnull instancetype)initWithFrame:(CGRect)frame OBJC_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)coder OBJC_DESIGNATED_INITIALIZER; +@end + +typedef SWIFT_ENUM(NSInteger, LGCastCameraProperty, open) { + LGCastCameraPropertyAudio = 0, + LGCastCameraPropertyAutoWhiteBalance = 1, + LGCastCameraPropertyBrightness = 2, + LGCastCameraPropertyFacing = 3, + LGCastCameraPropertyResolution = 4, + LGCastCameraPropertyWhitebalance = 5, + LGCastCameraPropertyRotation = 6, +}; + +typedef SWIFT_ENUM(NSInteger, LGCastCameraResolution, open) { + LGCastCameraResolutionLow = 0, + LGCastCameraResolutionMedium = 1, + LGCastCameraResolutionHigh = 2, + LGCastCameraResolutionCif352x288 = 3, + LGCastCameraResolutionVga640x480 = 4, + LGCastCameraResolutionHd1280x720 = 5, + LGCastCameraResolutionHd1920x1080 = 6, + LGCastCameraResolutionHd4K3840x2160 = 7, + LGCastCameraResolutionIFrame960x540 = 8, + LGCastCameraResolutionIFrame1280x720 = 9, +}; + + +SWIFT_CLASS("_TtC6LGCast26LGCastCameraResolutionInfo") +@interface LGCastCameraResolutionInfo : NSObject +@property (nonatomic) NSInteger width; +@property (nonatomic) NSInteger height; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +typedef SWIFT_ENUM(NSInteger, LGCastCamperaPropertyStatus, open) { + LGCastCamperaPropertyStatusDisable = 0, + LGCastCamperaPropertyStatusEnable = 1, +}; + + +SWIFT_CLASS("_TtC6LGCast20LGCastDeviceSettings") +@interface LGCastDeviceSettings : NSObject +@property (nonatomic, copy) NSString * _Nullable host; +@property (nonatomic) NSInteger audioPort; +@property (nonatomic) NSInteger videoPort; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +@protocol LGCastMirroringApiDelegate; +@class LGCastMirroringMediaSettings; +@class LGCastMirroringInfo; + +SWIFT_CLASS("_TtC6LGCast18LGCastMirroringApi") +@interface LGCastMirroringApi : NSObject +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) LGCastMirroringApi * _Nonnull shared;) ++ (LGCastMirroringApi * _Nonnull)shared SWIFT_WARN_UNUSED_RESULT; +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +- (BOOL)isSupportedOsVersion SWIFT_WARN_UNUSED_RESULT; +- (void)setDelegate:(id _Nonnull)delegate; +- (LGCastMirroringInfo * _Nullable)setMediaSettings:(LGCastMirroringMediaSettings * _Nullable)mediaInfo SWIFT_WARN_UNUSED_RESULT; +- (NSArray * _Nullable)generateMirroringMasterKey:(NSString * _Nonnull)publicKey SWIFT_WARN_UNUSED_RESULT; +- (void)startMirroring:(LGCastDeviceSettings * _Nullable)settings; +- (LGCastMirroringInfo * _Nullable)updateDisplayOrientationWithIsPortraitMode:(BOOL)isPortraitMode SWIFT_WARN_UNUSED_RESULT; +- (void)stopMirroring; +- (void)pushSampleBuffer:(CMSampleBufferRef _Nonnull)sampleBuffer with:(RPSampleBufferType)sampleBufferType; +@end + + +enum LGCastMirroringError : NSInteger; +enum LGCastMirroringEvent : NSInteger; + +SWIFT_PROTOCOL("_TtP6LGCast26LGCastMirroringApiDelegate_") +@protocol LGCastMirroringApiDelegate +- (void)lgcastMirroringDidStartWithResult:(BOOL)result; +- (void)lgcastMirroringDidStopWithResult:(BOOL)result; +- (void)lgcastMirroringErrorDidOccurWithError:(enum LGCastMirroringError)error; +- (void)lgcastMirroringUpdateEventWithEvent:(enum LGCastMirroringEvent)event info:(LGCastMirroringInfo * _Nullable)info; +@end + +enum MTAudioType : int32_t; +@class NSData; + +SWIFT_CLASS("_TtC6LGCast24LGCastMirroringAudioInfo") +@interface LGCastMirroringAudioInfo : NSObject +@property (nonatomic) enum MTAudioType type; +@property (nonatomic, readonly, copy) NSString * _Nonnull codec; +@property (nonatomic) NSInteger samplingRate; +@property (nonatomic) NSInteger channelCnt; +@property (nonatomic) NSInteger bitrate; +@property (nonatomic, copy) NSString * _Nonnull streamMuxConfig; +@property (nonatomic, copy) NSData * _Nullable codecData; +@property (nonatomic, copy) NSString * _Nullable host; +@property (nonatomic) NSInteger port; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast28LGCastMirroringAudioSettings") +@interface LGCastMirroringAudioSettings : NSObject +@property (nonatomic) NSInteger sampleRate; +@property (nonatomic) NSInteger bitrate; +@property (nonatomic) NSInteger channelCnt; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +typedef SWIFT_ENUM(NSInteger, LGCastMirroringError, open) { + LGCastMirroringErrorUnknown = 0, + LGCastMirroringErrorConnectionClosed = 1, + LGCastMirroringErrorDeviceShutdown = 2, + LGCastMirroringErrorRendererTerminated = 3, +}; + +typedef SWIFT_ENUM(NSInteger, LGCastMirroringEvent, open) { + LGCastMirroringEventUnknown = 0, + LGCastMirroringEventUpdateVideoInfo = 1, + LGCastMirroringEventUpdateVideoVideoSize = 2, + LGCastMirroringEventUpdateAudioInfo = 3, + LGCastMirroringEventUpdatePlayerInfo = 4, +}; + +@class LGCastMirroringVideoInfo; +@class LGCastMirroringPlayerInfo; + +SWIFT_CLASS("_TtC6LGCast19LGCastMirroringInfo") +@interface LGCastMirroringInfo : NSObject +@property (nonatomic, strong) LGCastMirroringAudioInfo * _Nullable audioInfo; +@property (nonatomic, strong) LGCastMirroringVideoInfo * _Nullable videoInfo; +@property (nonatomic, strong) LGCastMirroringPlayerInfo * _Nullable playerInfo; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +@class LGCastMirroringVideoSettings; + +SWIFT_CLASS("_TtC6LGCast28LGCastMirroringMediaSettings") +@interface LGCastMirroringMediaSettings : NSObject +@property (nonatomic, strong) LGCastMirroringAudioSettings * _Nullable audio; +@property (nonatomic, strong) LGCastMirroringVideoSettings * _Nullable video; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast25LGCastMirroringPlayerInfo") +@interface LGCastMirroringPlayerInfo : NSObject +@property (nonatomic) NSInteger width; +@property (nonatomic) NSInteger height; +@property (nonatomic) BOOL isPortraitMode; +@property (nonatomic, copy) NSString * _Nonnull ipAddress; +@property (nonatomic) NSInteger audioPort; +@property (nonatomic) NSInteger videoPort; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +enum MTVideoType : int32_t; + +SWIFT_CLASS("_TtC6LGCast24LGCastMirroringVideoInfo") +@interface LGCastMirroringVideoInfo : NSObject +@property (nonatomic) enum MTVideoType type; +@property (nonatomic, readonly, copy) NSString * _Nonnull codec; +@property (nonatomic) NSInteger width; +@property (nonatomic) NSInteger height; +@property (nonatomic) NSInteger activeWidth; +@property (nonatomic) NSInteger activeHeight; +@property (nonatomic) NSInteger framerate; +@property (nonatomic) NSInteger samplingRate; +@property (nonatomic) NSInteger bitrate; +@property (nonatomic, copy) NSString * _Nonnull supportOrientation; +@property (nonatomic) BOOL isPortraitMode; +@property (nonatomic, copy) NSString * _Nonnull screenOrientation; +@property (nonatomic, copy) NSString * _Nullable streamMuxConfig; +@property (nonatomic, copy) NSData * _Nullable codecData; +@property (nonatomic, copy) NSString * _Nullable host; +@property (nonatomic) NSInteger port; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast28LGCastMirroringVideoSettings") +@interface LGCastMirroringVideoSettings : NSObject +@property (nonatomic) NSInteger width; +@property (nonatomic) NSInteger height; +@property (nonatomic) BOOL isPortraitMode; +@property (nonatomic) NSInteger framerate; +@property (nonatomic) NSInteger bitrate; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast17LGCastSecurityKey") +@interface LGCastSecurityKey : NSObject +@property (nonatomic, copy) NSString * _Nullable masterKey; +@property (nonatomic, copy) NSString * _Nullable mki; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast3Log") +@interface Log : NSObject +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +@end + +typedef SWIFT_ENUM(NSInteger, LogLevel, closed) { + LogLevelDebug = 0, + LogLevelInfo = 1, + LogLevelNotice = 2, + LogLevelError = 3, + LogLevelFault = 4, +}; + + +@interface Log (SWIFT_EXTENSION(LGCast)) ++ (void)debugLGCast:(NSString * _Nonnull)message; ++ (void)infoLGCast:(NSString * _Nonnull)message; ++ (void)noticeLGCast:(NSString * _Nonnull)message; ++ (void)errorLGCast:(NSString * _Nonnull)message; ++ (void)faultLGCast:(NSString * _Nonnull)message; +@end + + + +SWIFT_CLASS("_TtC6LGCast11MTAudioInfo") +@interface MTAudioInfo : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +typedef SWIFT_ENUM(int32_t, MTAudioType, open) { + MTAudioTypePcm = 0, + MTAudioTypeAac = 1, +}; + + +SWIFT_CLASS("_TtC6LGCast11MTMediaInfo") +@interface MTMediaInfo : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast13MTRTPSecurity") +@interface MTRTPSecurity : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast16MTRTPSecurityKey") +@interface MTRTPSecurityKey : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast11MTVideoInfo") +@interface MTVideoInfo : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +typedef SWIFT_ENUM(int32_t, MTVideoType, open) { + MTVideoTypeRaw = 0, + MTVideoTypeH264 = 1, + MTVideoTypeMjpeg = 2, +}; + + +SWIFT_CLASS("_TtC6LGCast16MirroringService") +@interface MirroringService : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + + + +SWIFT_CLASS("_TtC6LGCast15MirroringVolume") +@interface MirroringVolume : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast11RTPStreamer") +@interface RTPStreamer : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +@interface RTPStreamer (SWIFT_EXTENSION(LGCast)) +- (void)gstreamerDidInitialize; +- (void)gstreamerDidSendMessage:(NSString * _Null_unspecified)message; +@end + + +SWIFT_CLASS("_TtC6LGCast16RTPStreamerUtils") +@interface RTPStreamerUtils : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast12RTPStreaming") +@interface RTPStreaming : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + + + +SWIFT_CLASS("_TtC6LGCast12VideoEncoder") +@interface VideoEncoder : NSObject +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +@end + + + + +#endif +#if defined(__cplusplus) +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#pragma clang diagnostic pop +#endif + +#else +#error unsupported Swift architecture +#endif diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Headers/LGCast.h b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Headers/LGCast.h new file mode 100644 index 00000000..ebae1022 --- /dev/null +++ b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Headers/LGCast.h @@ -0,0 +1,19 @@ +// +// LGCast.h +// LGCast +// +// Created by DeviceSynergyTask on 2021/04/21. +// + +#import + +#import +#import + +//! Project version number for LGCast. +FOUNDATION_EXPORT double LGCastSDKVersionNumber; + +//! Project version string for LGCast. +FOUNDATION_EXPORT const unsigned char LGCastSDKVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Info.plist b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Info.plist new file mode 100644 index 00000000..9694a9ee Binary files /dev/null and b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Info.plist differ diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/LGCast b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/LGCast new file mode 100755 index 00000000..45d49882 Binary files /dev/null and b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/LGCast differ diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Modules/LGCast.swiftmodule/arm64-apple-ios.abi.json b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Modules/LGCast.swiftmodule/arm64-apple-ios.abi.json new file mode 100644 index 00000000..aa7a183b --- /dev/null +++ b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Modules/LGCast.swiftmodule/arm64-apple-ios.abi.json @@ -0,0 +1,17725 @@ +{ + "ABIRoot": { + "kind": "Root", + "name": "TopLevel", + "printedName": "TopLevel", + "children": [ + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraError", + "printedName": "LGCastCameraError", + "children": [ + { + "kind": "Var", + "name": "unknown", + "printedName": "unknown", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraError.Type) -> LGCast.LGCastCameraError", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraError.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraError@LGCastCameraErrorUnknown", + "mangledName": "$s6LGCast0A11CameraErrorO7unknownyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "connectionClosed", + "printedName": "connectionClosed", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraError.Type) -> LGCast.LGCastCameraError", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraError.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraError@LGCastCameraErrorConnectionClosed", + "mangledName": "$s6LGCast0A11CameraErrorO16connectionClosedyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Var", + "name": "deviceShutdown", + "printedName": "deviceShutdown", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraError.Type) -> LGCast.LGCastCameraError", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraError.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraError@LGCastCameraErrorDeviceShutdown", + "mangledName": "$s6LGCast0A11CameraErrorO14deviceShutdownyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 2 + }, + { + "kind": "Var", + "name": "rendererTerminated", + "printedName": "rendererTerminated", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraError.Type) -> LGCast.LGCastCameraError", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraError.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraError@LGCastCameraErrorRendererTerminated", + "mangledName": "$s6LGCast0A11CameraErrorO18rendererTerminatedyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 3 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraError?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A11CameraErrorO8rawValueACSgSi_tcfc", + "mangledName": "$s6LGCast0A11CameraErrorO8rawValueACSgSi_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A11CameraErrorO8rawValueSivp", + "mangledName": "$s6LGCast0A11CameraErrorO8rawValueSivp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A11CameraErrorO8rawValueSivg", + "mangledName": "$s6LGCast0A11CameraErrorO8rawValueSivg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@LGCastCameraError", + "mangledName": "$s6LGCast0A11CameraErrorO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "ReplayKit", + "printedName": "ReplayKit", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringEvent", + "printedName": "LGCastMirroringEvent", + "children": [ + { + "kind": "Var", + "name": "unknown", + "printedName": "unknown", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringEvent.Type) -> LGCast.LGCastMirroringEvent", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringEvent.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent@LGCastMirroringEventUnknown", + "mangledName": "$s6LGCast0A14MirroringEventO7unknownyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "updateVideoInfo", + "printedName": "updateVideoInfo", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringEvent.Type) -> LGCast.LGCastMirroringEvent", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringEvent.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent@LGCastMirroringEventUpdateVideoInfo", + "mangledName": "$s6LGCast0A14MirroringEventO15updateVideoInfoyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Var", + "name": "updateVideoVideoSize", + "printedName": "updateVideoVideoSize", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringEvent.Type) -> LGCast.LGCastMirroringEvent", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringEvent.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent@LGCastMirroringEventUpdateVideoVideoSize", + "mangledName": "$s6LGCast0A14MirroringEventO011updateVideoE4SizeyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 2 + }, + { + "kind": "Var", + "name": "updateAudioInfo", + "printedName": "updateAudioInfo", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringEvent.Type) -> LGCast.LGCastMirroringEvent", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringEvent.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent@LGCastMirroringEventUpdateAudioInfo", + "mangledName": "$s6LGCast0A14MirroringEventO15updateAudioInfoyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 3 + }, + { + "kind": "Var", + "name": "updatePlayerInfo", + "printedName": "updatePlayerInfo", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringEvent.Type) -> LGCast.LGCastMirroringEvent", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringEvent.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent@LGCastMirroringEventUpdatePlayerInfo", + "mangledName": "$s6LGCast0A14MirroringEventO16updatePlayerInfoyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 4 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringEvent?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A14MirroringEventO8rawValueACSgSi_tcfc", + "mangledName": "$s6LGCast0A14MirroringEventO8rawValueACSgSi_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A14MirroringEventO8rawValueSivp", + "mangledName": "$s6LGCast0A14MirroringEventO8rawValueSivp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A14MirroringEventO8rawValueSivg", + "mangledName": "$s6LGCast0A14MirroringEventO8rawValueSivg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent", + "mangledName": "$s6LGCast0A14MirroringEventO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringApiDelegate", + "printedName": "LGCastMirroringApiDelegate", + "children": [ + { + "kind": "Function", + "name": "lgcastMirroringDidStart", + "printedName": "lgcastMirroringDidStart(result:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(pl)LGCastMirroringApiDelegate(im)lgcastMirroringDidStartWithResult:", + "mangledName": "$s6LGCast0A20MirroringApiDelegateP06lgcastB8DidStart6resultySb_tF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.LGCastMirroringApiDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "declAttributes": [ + "ObjC" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "lgcastMirroringDidStop", + "printedName": "lgcastMirroringDidStop(result:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(pl)LGCastMirroringApiDelegate(im)lgcastMirroringDidStopWithResult:", + "mangledName": "$s6LGCast0A20MirroringApiDelegateP06lgcastB7DidStop6resultySb_tF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.LGCastMirroringApiDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "declAttributes": [ + "ObjC" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "lgcastMirroringErrorDidOccur", + "printedName": "lgcastMirroringErrorDidOccur(error:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(pl)LGCastMirroringApiDelegate(im)lgcastMirroringErrorDidOccurWithError:", + "mangledName": "$s6LGCast0A20MirroringApiDelegateP06lgcastB13ErrorDidOccur5erroryAA0abF0O_tF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.LGCastMirroringApiDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "declAttributes": [ + "ObjC" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "lgcastMirroringUpdateEvent", + "printedName": "lgcastMirroringUpdateEvent(event:info:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringInfo", + "printedName": "LGCast.LGCastMirroringInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(pl)LGCastMirroringApiDelegate(im)lgcastMirroringUpdateEventWithEvent:info:", + "mangledName": "$s6LGCast0A20MirroringApiDelegateP06lgcastB11UpdateEvent5event4infoyAA0abG0O_AA0aB4InfoCSgtF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.LGCastMirroringApiDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "declAttributes": [ + "ObjC" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@LGCast@objc(pl)LGCastMirroringApiDelegate", + "mangledName": "$s6LGCast0A20MirroringApiDelegateP", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 : AnyObject>", + "sugared_genericSig": "", + "declAttributes": [ + "AccessControl", + "ObjC" + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringApi", + "printedName": "LGCastMirroringApi", + "children": [ + { + "kind": "Var", + "name": "shared", + "printedName": "shared", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringApi", + "printedName": "LGCast.LGCastMirroringApi", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(cpy)shared", + "mangledName": "$s6LGCast0A12MirroringApiC6sharedACvpZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "HasInitialValue", + "Final", + "HasStorage", + "AccessControl", + "ObjC" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringApi", + "printedName": "LGCast.LGCastMirroringApi", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(cm)shared", + "mangledName": "$s6LGCast0A12MirroringApiC6sharedACvgZ", + "moduleName": "LGCast", + "static": true, + "implicit": true, + "declAttributes": [ + "Final", + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "isRunning", + "printedName": "isRunning", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A12MirroringApiC9isRunningSbvp", + "mangledName": "$s6LGCast0A12MirroringApiC9isRunningSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "SetterAccess", + "AccessControl" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A12MirroringApiC9isRunningSbvg", + "mangledName": "$s6LGCast0A12MirroringApiC9isRunningSbvg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "isTvInputEnabled", + "printedName": "isTvInputEnabled", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A12MirroringApiC16isTvInputEnabledSbvp", + "mangledName": "$s6LGCast0A12MirroringApiC16isTvInputEnabledSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A12MirroringApiC16isTvInputEnabledSbvg", + "mangledName": "$s6LGCast0A12MirroringApiC16isTvInputEnabledSbvg", + "moduleName": "LGCast", + "accessorKind": "get" + } + ] + }, + { + "kind": "Function", + "name": "isSupportedOsVersion", + "printedName": "isSupportedOsVersion()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(im)isSupportedOsVersion", + "mangledName": "$s6LGCast0A12MirroringApiC20isSupportedOsVersionSbyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "setDelegate", + "printedName": "setDelegate(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastMirroringApiDelegate", + "printedName": "LGCast.LGCastMirroringApiDelegate", + "usr": "c:@M@LGCast@objc(pl)LGCastMirroringApiDelegate" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(im)setDelegate:", + "mangledName": "$s6LGCast0A12MirroringApiC11setDelegateyyAA0abcE0_pF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "setMediaSettings", + "printedName": "setMediaSettings(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringInfo", + "printedName": "LGCast.LGCastMirroringInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringMediaSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringMediaSettings", + "printedName": "LGCast.LGCastMirroringMediaSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(im)setMediaSettings:", + "mangledName": "$s6LGCast0A12MirroringApiC16setMediaSettingsyAA0aB4InfoCSgAA0abeF0CSgF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "generateMirroringMasterKey", + "printedName": "generateMirroringMasterKey(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[LGCast.LGCastSecurityKey]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[LGCast.LGCastSecurityKey]", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastSecurityKey", + "printedName": "LGCast.LGCastSecurityKey", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(im)generateMirroringMasterKey:", + "mangledName": "$s6LGCast0A12MirroringApiC08generateB9MasterKeyySayAA0a8SecurityF0CGSgSSF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "startMirroring", + "printedName": "startMirroring(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastDeviceSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastDeviceSettings", + "printedName": "LGCast.LGCastDeviceSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(im)startMirroring:", + "mangledName": "$s6LGCast0A12MirroringApiC05startB0yyAA0A14DeviceSettingsCSgF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "updateDisplayOrientation", + "printedName": "updateDisplayOrientation(isPortraitMode:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringInfo", + "printedName": "LGCast.LGCastMirroringInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(im)updateDisplayOrientationWithIsPortraitMode:", + "mangledName": "$s6LGCast0A12MirroringApiC24updateDisplayOrientation14isPortraitModeAA0aB4InfoCSgSb_tF", + "moduleName": "LGCast", + "objc_name": "updateDisplayOrientationWithIsPortraitMode:", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "stopMirroring", + "printedName": "stopMirroring()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(im)stopMirroring", + "mangledName": "$s6LGCast0A12MirroringApiC04stopB0yyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "startMirroringWithCapture", + "printedName": "startMirroringWithCapture(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastDeviceSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastDeviceSettings", + "printedName": "LGCast.LGCastDeviceSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Func", + "usr": "s:6LGCast0A12MirroringApiC05startB11WithCaptureyyAA0A14DeviceSettingsCSgF", + "mangledName": "$s6LGCast0A12MirroringApiC05startB11WithCaptureyyAA0A14DeviceSettingsCSgF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "stopMirroringWithCapture", + "printedName": "stopMirroringWithCapture()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Func", + "usr": "s:6LGCast0A12MirroringApiC04stopB11WithCaptureyyF", + "mangledName": "$s6LGCast0A12MirroringApiC04stopB11WithCaptureyyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "pushSampleBuffer", + "printedName": "pushSampleBuffer(_:with:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "CMSampleBuffer", + "printedName": "CoreMedia.CMSampleBuffer", + "usr": "c:@T@CMSampleBufferRef" + }, + { + "kind": "TypeNominal", + "name": "RPSampleBufferType", + "printedName": "ReplayKit.RPSampleBufferType", + "usr": "c:@E@RPSampleBufferType" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(im)pushSampleBuffer:with:", + "mangledName": "$s6LGCast0A12MirroringApiC16pushSampleBuffer_4withySo08CMSampleF3Refa_So08RPSampleF4TypeVtF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "pushSampleBuffer", + "printedName": "pushSampleBuffer(_:with:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "CMSampleBuffer", + "printedName": "CoreMedia.CMSampleBuffer", + "usr": "c:@T@CMSampleBufferRef" + }, + { + "kind": "TypeNominal", + "name": "RTPStreamerMediaType", + "printedName": "LGCast.RTPStreamerMediaType", + "usr": "s:6LGCast20RTPStreamerMediaTypeO" + } + ], + "declKind": "Func", + "usr": "s:6LGCast0A12MirroringApiC16pushSampleBuffer_4withySo08CMSampleF3Refa_AA20RTPStreamerMediaTypeOtF", + "mangledName": "$s6LGCast0A12MirroringApiC16pushSampleBuffer_4withySo08CMSampleF3Refa_AA20RTPStreamerMediaTypeOtF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi", + "mangledName": "$s6LGCast0A12MirroringApiC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "hasMissingDesignatedInitializers": true, + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringError", + "printedName": "LGCastMirroringError", + "children": [ + { + "kind": "Var", + "name": "unknown", + "printedName": "unknown", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringError.Type) -> LGCast.LGCastMirroringError", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringError.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringError@LGCastMirroringErrorUnknown", + "mangledName": "$s6LGCast0A14MirroringErrorO7unknownyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "connectionClosed", + "printedName": "connectionClosed", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringError.Type) -> LGCast.LGCastMirroringError", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringError.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringError@LGCastMirroringErrorConnectionClosed", + "mangledName": "$s6LGCast0A14MirroringErrorO16connectionClosedyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Var", + "name": "deviceShutdown", + "printedName": "deviceShutdown", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringError.Type) -> LGCast.LGCastMirroringError", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringError.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringError@LGCastMirroringErrorDeviceShutdown", + "mangledName": "$s6LGCast0A14MirroringErrorO14deviceShutdownyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 2 + }, + { + "kind": "Var", + "name": "rendererTerminated", + "printedName": "rendererTerminated", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringError.Type) -> LGCast.LGCastMirroringError", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringError.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringError@LGCastMirroringErrorRendererTerminated", + "mangledName": "$s6LGCast0A14MirroringErrorO18rendererTerminatedyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 3 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringError?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A14MirroringErrorO8rawValueACSgSi_tcfc", + "mangledName": "$s6LGCast0A14MirroringErrorO8rawValueACSgSi_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A14MirroringErrorO8rawValueSivp", + "mangledName": "$s6LGCast0A14MirroringErrorO8rawValueSivp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A14MirroringErrorO8rawValueSivg", + "mangledName": "$s6LGCast0A14MirroringErrorO8rawValueSivg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@LGCastMirroringError", + "mangledName": "$s6LGCast0A14MirroringErrorO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastSecurityKey", + "printedName": "LGCastSecurityKey", + "children": [ + { + "kind": "Var", + "name": "masterKey", + "printedName": "masterKey", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey(py)masterKey", + "mangledName": "$s6LGCast0A11SecurityKeyC06masterC0SSSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey(im)masterKey", + "mangledName": "$s6LGCast0A11SecurityKeyC06masterC0SSSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey(im)setMasterKey:", + "mangledName": "$s6LGCast0A11SecurityKeyC06masterC0SSSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A11SecurityKeyC06masterC0SSSgvM", + "mangledName": "$s6LGCast0A11SecurityKeyC06masterC0SSSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "mki", + "printedName": "mki", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey(py)mki", + "mangledName": "$s6LGCast0A11SecurityKeyC3mkiSSSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey(im)mki", + "mangledName": "$s6LGCast0A11SecurityKeyC3mkiSSSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey(im)setMki:", + "mangledName": "$s6LGCast0A11SecurityKeyC3mkiSSSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A11SecurityKeyC3mkiSSSgvM", + "mangledName": "$s6LGCast0A11SecurityKeyC3mkiSSSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastSecurityKey", + "printedName": "LGCast.LGCastSecurityKey", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey(im)init", + "mangledName": "$s6LGCast0A11SecurityKeyCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey", + "mangledName": "$s6LGCast0A11SecurityKeyC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastDeviceSettings", + "printedName": "LGCastDeviceSettings", + "children": [ + { + "kind": "Var", + "name": "host", + "printedName": "host", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(py)host", + "mangledName": "$s6LGCast0A14DeviceSettingsC4hostSSSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(im)host", + "mangledName": "$s6LGCast0A14DeviceSettingsC4hostSSSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(im)setHost:", + "mangledName": "$s6LGCast0A14DeviceSettingsC4hostSSSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A14DeviceSettingsC4hostSSSgvM", + "mangledName": "$s6LGCast0A14DeviceSettingsC4hostSSSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "audioPort", + "printedName": "audioPort", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(py)audioPort", + "mangledName": "$s6LGCast0A14DeviceSettingsC9audioPortSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(im)audioPort", + "mangledName": "$s6LGCast0A14DeviceSettingsC9audioPortSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(im)setAudioPort:", + "mangledName": "$s6LGCast0A14DeviceSettingsC9audioPortSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A14DeviceSettingsC9audioPortSivM", + "mangledName": "$s6LGCast0A14DeviceSettingsC9audioPortSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "videoPort", + "printedName": "videoPort", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(py)videoPort", + "mangledName": "$s6LGCast0A14DeviceSettingsC9videoPortSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(im)videoPort", + "mangledName": "$s6LGCast0A14DeviceSettingsC9videoPortSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(im)setVideoPort:", + "mangledName": "$s6LGCast0A14DeviceSettingsC9videoPortSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A14DeviceSettingsC9videoPortSivM", + "mangledName": "$s6LGCast0A14DeviceSettingsC9videoPortSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastDeviceSettings", + "printedName": "LGCast.LGCastDeviceSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(im)init", + "mangledName": "$s6LGCast0A14DeviceSettingsCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings", + "mangledName": "$s6LGCast0A14DeviceSettingsC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringAudioSettings", + "printedName": "LGCastMirroringAudioSettings", + "children": [ + { + "kind": "Var", + "name": "sampleRate", + "printedName": "sampleRate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(py)sampleRate", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC10sampleRateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(im)sampleRate", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC10sampleRateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(im)setSampleRate:", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC10sampleRateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringAudioSettingsC10sampleRateSivM", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC10sampleRateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "bitrate", + "printedName": "bitrate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(py)bitrate", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC7bitrateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(im)bitrate", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC7bitrateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(im)setBitrate:", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC7bitrateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringAudioSettingsC7bitrateSivM", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC7bitrateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "channelCnt", + "printedName": "channelCnt", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(py)channelCnt", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC10channelCntSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(im)channelCnt", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC10channelCntSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(im)setChannelCnt:", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC10channelCntSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringAudioSettingsC10channelCntSivM", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC10channelCntSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringAudioSettings", + "printedName": "LGCast.LGCastMirroringAudioSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(im)init", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringVideoSettings", + "printedName": "LGCastMirroringVideoSettings", + "children": [ + { + "kind": "Var", + "name": "width", + "printedName": "width", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(py)width", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC5widthSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)width", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC5widthSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)setWidth:", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC5widthSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringVideoSettingsC5widthSivM", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC5widthSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "height", + "printedName": "height", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(py)height", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC6heightSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)height", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC6heightSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)setHeight:", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC6heightSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringVideoSettingsC6heightSivM", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC6heightSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "isPortraitMode", + "printedName": "isPortraitMode", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(py)isPortraitMode", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC14isPortraitModeSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)isPortraitMode", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC14isPortraitModeSbvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)setIsPortraitMode:", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC14isPortraitModeSbvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringVideoSettingsC14isPortraitModeSbvM", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC14isPortraitModeSbvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "framerate", + "printedName": "framerate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(py)framerate", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC9framerateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)framerate", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC9framerateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)setFramerate:", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC9framerateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringVideoSettingsC9framerateSivM", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC9framerateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "bitrate", + "printedName": "bitrate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(py)bitrate", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC7bitrateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)bitrate", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC7bitrateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)setBitrate:", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC7bitrateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringVideoSettingsC7bitrateSivM", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC7bitrateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringVideoSettings", + "printedName": "LGCast.LGCastMirroringVideoSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)init", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringMediaSettings", + "printedName": "LGCastMirroringMediaSettings", + "children": [ + { + "kind": "Var", + "name": "audio", + "printedName": "audio", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringAudioSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringAudioSettings", + "printedName": "LGCast.LGCastMirroringAudioSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings(py)audio", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC5audioAA0ab5AudioD0CSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringAudioSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringAudioSettings", + "printedName": "LGCast.LGCastMirroringAudioSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings(im)audio", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC5audioAA0ab5AudioD0CSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringAudioSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringAudioSettings", + "printedName": "LGCast.LGCastMirroringAudioSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings(im)setAudio:", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC5audioAA0ab5AudioD0CSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringMediaSettingsC5audioAA0ab5AudioD0CSgvM", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC5audioAA0ab5AudioD0CSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "video", + "printedName": "video", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringVideoSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringVideoSettings", + "printedName": "LGCast.LGCastMirroringVideoSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings(py)video", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC5videoAA0ab5VideoD0CSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringVideoSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringVideoSettings", + "printedName": "LGCast.LGCastMirroringVideoSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings(im)video", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC5videoAA0ab5VideoD0CSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringVideoSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringVideoSettings", + "printedName": "LGCast.LGCastMirroringVideoSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings(im)setVideo:", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC5videoAA0ab5VideoD0CSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringMediaSettingsC5videoAA0ab5VideoD0CSgvM", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC5videoAA0ab5VideoD0CSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringMediaSettings", + "printedName": "LGCast.LGCastMirroringMediaSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings(im)init", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringAudioInfo", + "printedName": "LGCastMirroringAudioInfo", + "children": [ + { + "kind": "Var", + "name": "type", + "printedName": "type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)type", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4typeAA11MTAudioTypeOvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)type", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4typeAA11MTAudioTypeOvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)setType:", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4typeAA11MTAudioTypeOvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringAudioInfoC4typeAA11MTAudioTypeOvM", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4typeAA11MTAudioTypeOvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "codec", + "printedName": "codec", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)codec", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC5codecSSvp", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)codec", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC5codecSSvg", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "samplingRate", + "printedName": "samplingRate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)samplingRate", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC12samplingRateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)samplingRate", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC12samplingRateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)setSamplingRate:", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC12samplingRateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringAudioInfoC12samplingRateSivM", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC12samplingRateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "channelCnt", + "printedName": "channelCnt", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)channelCnt", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC10channelCntSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)channelCnt", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC10channelCntSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)setChannelCnt:", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC10channelCntSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringAudioInfoC10channelCntSivM", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC10channelCntSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "bitrate", + "printedName": "bitrate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)bitrate", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC7bitrateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)bitrate", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC7bitrateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)setBitrate:", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC7bitrateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringAudioInfoC7bitrateSivM", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC7bitrateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "streamMuxConfig", + "printedName": "streamMuxConfig", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)streamMuxConfig", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC15streamMuxConfigSSvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)streamMuxConfig", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC15streamMuxConfigSSvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)setStreamMuxConfig:", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC15streamMuxConfigSSvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringAudioInfoC15streamMuxConfigSSvM", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC15streamMuxConfigSSvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "codecData", + "printedName": "codecData", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Foundation.Data?", + "children": [ + { + "kind": "TypeNominal", + "name": "Data", + "printedName": "Foundation.Data", + "usr": "s:10Foundation4DataV" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)codecData", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC9codecData10Foundation0F0VSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC", + "RawDocComment" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Foundation.Data?", + "children": [ + { + "kind": "TypeNominal", + "name": "Data", + "printedName": "Foundation.Data", + "usr": "s:10Foundation4DataV" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)codecData", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC9codecData10Foundation0F0VSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Foundation.Data?", + "children": [ + { + "kind": "TypeNominal", + "name": "Data", + "printedName": "Foundation.Data", + "usr": "s:10Foundation4DataV" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)setCodecData:", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC9codecData10Foundation0F0VSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringAudioInfoC9codecData10Foundation0F0VSgvM", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC9codecData10Foundation0F0VSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "host", + "printedName": "host", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)host", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4hostSSSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC", + "RawDocComment" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)host", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4hostSSSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)setHost:", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4hostSSSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringAudioInfoC4hostSSSgvM", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4hostSSSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "port", + "printedName": "port", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)port", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4portSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)port", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4portSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)setPort:", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4portSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringAudioInfoC4portSivM", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4portSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringAudioInfo", + "printedName": "LGCast.LGCastMirroringAudioInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)init", + "mangledName": "$s6LGCast0A18MirroringAudioInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringVideoInfo", + "printedName": "LGCastMirroringVideoInfo", + "children": [ + { + "kind": "Var", + "name": "type", + "printedName": "type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)type", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4typeAA11MTVideoTypeOvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)type", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4typeAA11MTVideoTypeOvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setType:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4typeAA11MTVideoTypeOvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC4typeAA11MTVideoTypeOvM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4typeAA11MTVideoTypeOvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "codec", + "printedName": "codec", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)codec", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC5codecSSvp", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)codec", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC5codecSSvg", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "width", + "printedName": "width", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)width", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC5widthSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)width", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC5widthSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setWidth:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC5widthSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC5widthSivM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC5widthSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "height", + "printedName": "height", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)height", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC6heightSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC", + "RawDocComment" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)height", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC6heightSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setHeight:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC6heightSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC6heightSivM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC6heightSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "activeWidth", + "printedName": "activeWidth", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)activeWidth", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC11activeWidthSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC", + "RawDocComment" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)activeWidth", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC11activeWidthSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setActiveWidth:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC11activeWidthSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC11activeWidthSivM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC11activeWidthSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "activeHeight", + "printedName": "activeHeight", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)activeHeight", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC12activeHeightSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC", + "RawDocComment" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)activeHeight", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC12activeHeightSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setActiveHeight:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC12activeHeightSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC12activeHeightSivM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC12activeHeightSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "framerate", + "printedName": "framerate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)framerate", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC9framerateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC", + "RawDocComment" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)framerate", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC9framerateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setFramerate:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC9framerateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC9framerateSivM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC9framerateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "samplingRate", + "printedName": "samplingRate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)samplingRate", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC12samplingRateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)samplingRate", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC12samplingRateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setSamplingRate:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC12samplingRateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC12samplingRateSivM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC12samplingRateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "bitrate", + "printedName": "bitrate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)bitrate", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC7bitrateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)bitrate", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC7bitrateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setBitrate:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC7bitrateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC7bitrateSivM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC7bitrateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "supportOrientation", + "printedName": "supportOrientation", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)supportOrientation", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC18supportOrientationSSvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)supportOrientation", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC18supportOrientationSSvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setSupportOrientation:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC18supportOrientationSSvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC18supportOrientationSSvM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC18supportOrientationSSvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "isPortraitMode", + "printedName": "isPortraitMode", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)isPortraitMode", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC14isPortraitModeSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)isPortraitMode", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC14isPortraitModeSbvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setIsPortraitMode:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC14isPortraitModeSbvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC14isPortraitModeSbvM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC14isPortraitModeSbvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "screenOrientation", + "printedName": "screenOrientation", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)screenOrientation", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC17screenOrientationSSvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)screenOrientation", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC17screenOrientationSSvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setScreenOrientation:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC17screenOrientationSSvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC17screenOrientationSSvM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC17screenOrientationSSvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "streamMuxConfig", + "printedName": "streamMuxConfig", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)streamMuxConfig", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC15streamMuxConfigSSSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC", + "RawDocComment" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)streamMuxConfig", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC15streamMuxConfigSSSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setStreamMuxConfig:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC15streamMuxConfigSSSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC15streamMuxConfigSSSgvM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC15streamMuxConfigSSSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "codecData", + "printedName": "codecData", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Foundation.Data?", + "children": [ + { + "kind": "TypeNominal", + "name": "Data", + "printedName": "Foundation.Data", + "usr": "s:10Foundation4DataV" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)codecData", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC9codecData10Foundation0F0VSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Foundation.Data?", + "children": [ + { + "kind": "TypeNominal", + "name": "Data", + "printedName": "Foundation.Data", + "usr": "s:10Foundation4DataV" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)codecData", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC9codecData10Foundation0F0VSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Foundation.Data?", + "children": [ + { + "kind": "TypeNominal", + "name": "Data", + "printedName": "Foundation.Data", + "usr": "s:10Foundation4DataV" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setCodecData:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC9codecData10Foundation0F0VSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC9codecData10Foundation0F0VSgvM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC9codecData10Foundation0F0VSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "host", + "printedName": "host", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)host", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4hostSSSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC", + "RawDocComment" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)host", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4hostSSSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setHost:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4hostSSSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC4hostSSSgvM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4hostSSSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "port", + "printedName": "port", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)port", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4portSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)port", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4portSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setPort:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4portSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC4portSivM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4portSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringVideoInfo", + "printedName": "LGCast.LGCastMirroringVideoInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)init", + "mangledName": "$s6LGCast0A18MirroringVideoInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringPlayerInfo", + "printedName": "LGCastMirroringPlayerInfo", + "children": [ + { + "kind": "Var", + "name": "width", + "printedName": "width", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(py)width", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC5widthSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)width", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC5widthSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)setWidth:", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC5widthSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19MirroringPlayerInfoC5widthSivM", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC5widthSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "height", + "printedName": "height", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(py)height", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC6heightSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)height", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC6heightSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)setHeight:", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC6heightSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19MirroringPlayerInfoC6heightSivM", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC6heightSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "isPortraitMode", + "printedName": "isPortraitMode", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(py)isPortraitMode", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC14isPortraitModeSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)isPortraitMode", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC14isPortraitModeSbvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)setIsPortraitMode:", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC14isPortraitModeSbvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19MirroringPlayerInfoC14isPortraitModeSbvM", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC14isPortraitModeSbvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "ipAddress", + "printedName": "ipAddress", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(py)ipAddress", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9ipAddressSSvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)ipAddress", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9ipAddressSSvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)setIpAddress:", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9ipAddressSSvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19MirroringPlayerInfoC9ipAddressSSvM", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9ipAddressSSvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "audioPort", + "printedName": "audioPort", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(py)audioPort", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9audioPortSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)audioPort", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9audioPortSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)setAudioPort:", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9audioPortSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19MirroringPlayerInfoC9audioPortSivM", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9audioPortSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "videoPort", + "printedName": "videoPort", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(py)videoPort", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9videoPortSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)videoPort", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9videoPortSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)setVideoPort:", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9videoPortSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19MirroringPlayerInfoC9videoPortSivM", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9videoPortSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringPlayerInfo", + "printedName": "LGCast.LGCastMirroringPlayerInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)init", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringInfo", + "printedName": "LGCastMirroringInfo", + "children": [ + { + "kind": "Var", + "name": "audioInfo", + "printedName": "audioInfo", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringAudioInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringAudioInfo", + "printedName": "LGCast.LGCastMirroringAudioInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(py)audioInfo", + "mangledName": "$s6LGCast0A13MirroringInfoC05audioC0AA0ab5AudioC0CSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringAudioInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringAudioInfo", + "printedName": "LGCast.LGCastMirroringAudioInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(im)audioInfo", + "mangledName": "$s6LGCast0A13MirroringInfoC05audioC0AA0ab5AudioC0CSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringAudioInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringAudioInfo", + "printedName": "LGCast.LGCastMirroringAudioInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(im)setAudioInfo:", + "mangledName": "$s6LGCast0A13MirroringInfoC05audioC0AA0ab5AudioC0CSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A13MirroringInfoC05audioC0AA0ab5AudioC0CSgvM", + "mangledName": "$s6LGCast0A13MirroringInfoC05audioC0AA0ab5AudioC0CSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "videoInfo", + "printedName": "videoInfo", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringVideoInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringVideoInfo", + "printedName": "LGCast.LGCastMirroringVideoInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(py)videoInfo", + "mangledName": "$s6LGCast0A13MirroringInfoC05videoC0AA0ab5VideoC0CSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringVideoInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringVideoInfo", + "printedName": "LGCast.LGCastMirroringVideoInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(im)videoInfo", + "mangledName": "$s6LGCast0A13MirroringInfoC05videoC0AA0ab5VideoC0CSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringVideoInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringVideoInfo", + "printedName": "LGCast.LGCastMirroringVideoInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(im)setVideoInfo:", + "mangledName": "$s6LGCast0A13MirroringInfoC05videoC0AA0ab5VideoC0CSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A13MirroringInfoC05videoC0AA0ab5VideoC0CSgvM", + "mangledName": "$s6LGCast0A13MirroringInfoC05videoC0AA0ab5VideoC0CSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "playerInfo", + "printedName": "playerInfo", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringPlayerInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringPlayerInfo", + "printedName": "LGCast.LGCastMirroringPlayerInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(py)playerInfo", + "mangledName": "$s6LGCast0A13MirroringInfoC06playerC0AA0ab6PlayerC0CSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringPlayerInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringPlayerInfo", + "printedName": "LGCast.LGCastMirroringPlayerInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(im)playerInfo", + "mangledName": "$s6LGCast0A13MirroringInfoC06playerC0AA0ab6PlayerC0CSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringPlayerInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringPlayerInfo", + "printedName": "LGCast.LGCastMirroringPlayerInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(im)setPlayerInfo:", + "mangledName": "$s6LGCast0A13MirroringInfoC06playerC0AA0ab6PlayerC0CSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A13MirroringInfoC06playerC0AA0ab6PlayerC0CSgvM", + "mangledName": "$s6LGCast0A13MirroringInfoC06playerC0AA0ab6PlayerC0CSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringInfo", + "printedName": "LGCast.LGCastMirroringInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(im)init", + "mangledName": "$s6LGCast0A13MirroringInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo", + "mangledName": "$s6LGCast0A13MirroringInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraParameterInfo", + "printedName": "LGCastCameraParameterInfo", + "children": [ + { + "kind": "Var", + "name": "audio", + "printedName": "audio", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(py)audio", + "mangledName": "$s6LGCast0A19CameraParameterInfoC5audioAA0A21CamperaPropertyStatusOvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)audio", + "mangledName": "$s6LGCast0A19CameraParameterInfoC5audioAA0A21CamperaPropertyStatusOvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)setAudio:", + "mangledName": "$s6LGCast0A19CameraParameterInfoC5audioAA0A21CamperaPropertyStatusOvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19CameraParameterInfoC5audioAA0A21CamperaPropertyStatusOvM", + "mangledName": "$s6LGCast0A19CameraParameterInfoC5audioAA0A21CamperaPropertyStatusOvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "autoWhiteBalance", + "printedName": "autoWhiteBalance", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(py)autoWhiteBalance", + "mangledName": "$s6LGCast0A19CameraParameterInfoC16autoWhiteBalanceAA0A21CamperaPropertyStatusOvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)autoWhiteBalance", + "mangledName": "$s6LGCast0A19CameraParameterInfoC16autoWhiteBalanceAA0A21CamperaPropertyStatusOvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)setAutoWhiteBalance:", + "mangledName": "$s6LGCast0A19CameraParameterInfoC16autoWhiteBalanceAA0A21CamperaPropertyStatusOvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19CameraParameterInfoC16autoWhiteBalanceAA0A21CamperaPropertyStatusOvM", + "mangledName": "$s6LGCast0A19CameraParameterInfoC16autoWhiteBalanceAA0A21CamperaPropertyStatusOvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "brightness", + "printedName": "brightness", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(py)brightness", + "mangledName": "$s6LGCast0A19CameraParameterInfoC10brightnessSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)brightness", + "mangledName": "$s6LGCast0A19CameraParameterInfoC10brightnessSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)setBrightness:", + "mangledName": "$s6LGCast0A19CameraParameterInfoC10brightnessSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19CameraParameterInfoC10brightnessSivM", + "mangledName": "$s6LGCast0A19CameraParameterInfoC10brightnessSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "width", + "printedName": "width", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(py)width", + "mangledName": "$s6LGCast0A19CameraParameterInfoC5widthSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)width", + "mangledName": "$s6LGCast0A19CameraParameterInfoC5widthSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)setWidth:", + "mangledName": "$s6LGCast0A19CameraParameterInfoC5widthSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19CameraParameterInfoC5widthSivM", + "mangledName": "$s6LGCast0A19CameraParameterInfoC5widthSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "height", + "printedName": "height", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(py)height", + "mangledName": "$s6LGCast0A19CameraParameterInfoC6heightSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)height", + "mangledName": "$s6LGCast0A19CameraParameterInfoC6heightSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)setHeight:", + "mangledName": "$s6LGCast0A19CameraParameterInfoC6heightSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19CameraParameterInfoC6heightSivM", + "mangledName": "$s6LGCast0A19CameraParameterInfoC6heightSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "facing", + "printedName": "facing", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(py)facing", + "mangledName": "$s6LGCast0A19CameraParameterInfoC6facingAA0aB8PositionOvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)facing", + "mangledName": "$s6LGCast0A19CameraParameterInfoC6facingAA0aB8PositionOvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)setFacing:", + "mangledName": "$s6LGCast0A19CameraParameterInfoC6facingAA0aB8PositionOvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19CameraParameterInfoC6facingAA0aB8PositionOvM", + "mangledName": "$s6LGCast0A19CameraParameterInfoC6facingAA0aB8PositionOvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "whiteBalance", + "printedName": "whiteBalance", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(py)whiteBalance", + "mangledName": "$s6LGCast0A19CameraParameterInfoC12whiteBalanceSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)whiteBalance", + "mangledName": "$s6LGCast0A19CameraParameterInfoC12whiteBalanceSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)setWhiteBalance:", + "mangledName": "$s6LGCast0A19CameraParameterInfoC12whiteBalanceSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19CameraParameterInfoC12whiteBalanceSivM", + "mangledName": "$s6LGCast0A19CameraParameterInfoC12whiteBalanceSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "rotation", + "printedName": "rotation", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(py)rotation", + "mangledName": "$s6LGCast0A19CameraParameterInfoC8rotationSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)rotation", + "mangledName": "$s6LGCast0A19CameraParameterInfoC8rotationSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)setRotation:", + "mangledName": "$s6LGCast0A19CameraParameterInfoC8rotationSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19CameraParameterInfoC8rotationSivM", + "mangledName": "$s6LGCast0A19CameraParameterInfoC8rotationSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraParameterInfo", + "printedName": "LGCast.LGCastCameraParameterInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)init", + "mangledName": "$s6LGCast0A19CameraParameterInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo", + "mangledName": "$s6LGCast0A19CameraParameterInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraApiDelegate", + "printedName": "LGCastCameraApiDelegate", + "children": [ + { + "kind": "Function", + "name": "lgcastCameraDidPlay", + "printedName": "lgcastCameraDidPlay()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(pl)LGCastCameraApiDelegate(im)lgcastCameraDidPlay", + "mangledName": "$s6LGCast0A17CameraApiDelegateP06lgcastB7DidPlayyyF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.LGCastCameraApiDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "declAttributes": [ + "ObjC" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "lgcastCameraDidChange", + "printedName": "lgcastCameraDidChange(property:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(pl)LGCastCameraApiDelegate(im)lgcastCameraDidChangeWithProperty:", + "mangledName": "$s6LGCast0A17CameraApiDelegateP06lgcastB9DidChange8propertyyAA0aB8PropertyO_tF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.LGCastCameraApiDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "declAttributes": [ + "ObjC" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "lgcastCameraErrorDidOccur", + "printedName": "lgcastCameraErrorDidOccur(error:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(pl)LGCastCameraApiDelegate(im)lgcastCameraErrorDidOccurWithError:", + "mangledName": "$s6LGCast0A17CameraApiDelegateP06lgcastB13ErrorDidOccur5erroryAA0abF0O_tF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.LGCastCameraApiDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "declAttributes": [ + "ObjC" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@LGCast@objc(pl)LGCastCameraApiDelegate", + "mangledName": "$s6LGCast0A17CameraApiDelegateP", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 : AnyObject>", + "sugared_genericSig": "", + "declAttributes": [ + "AccessControl", + "ObjC" + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraResolutionInfo", + "printedName": "LGCastCameraResolutionInfo", + "children": [ + { + "kind": "Var", + "name": "width", + "printedName": "width", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo(py)width", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC5widthSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo(im)width", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC5widthSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo(im)setWidth:", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC5widthSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A20CameraResolutionInfoC5widthSivM", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC5widthSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "height", + "printedName": "height", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo(py)height", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC6heightSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo(im)height", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC6heightSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo(im)setHeight:", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC6heightSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A20CameraResolutionInfoC6heightSivM", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC6heightSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolutionInfo", + "printedName": "LGCast.LGCastCameraResolutionInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo(im)init", + "mangledName": "$s6LGCast0A20CameraResolutionInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraApi", + "printedName": "LGCastCameraApi", + "children": [ + { + "kind": "Var", + "name": "KEY_CAMERA_POSITION", + "printedName": "KEY_CAMERA_POSITION", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(cpy)KEY_CAMERA_POSITION", + "mangledName": "$s6LGCast0A9CameraApiC19KEY_CAMERA_POSITIONSSvpZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "HasInitialValue", + "Final", + "HasStorage", + "AccessControl", + "ObjC" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(cm)KEY_CAMERA_POSITION", + "mangledName": "$s6LGCast0A9CameraApiC19KEY_CAMERA_POSITIONSSvgZ", + "moduleName": "LGCast", + "static": true, + "implicit": true, + "declAttributes": [ + "Final", + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "KEY_PREVIEW_VIDEO_GRAVITY", + "printedName": "KEY_PREVIEW_VIDEO_GRAVITY", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(cpy)KEY_PREVIEW_VIDEO_GRAVITY", + "mangledName": "$s6LGCast0A9CameraApiC25KEY_PREVIEW_VIDEO_GRAVITYSSvpZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "HasInitialValue", + "Final", + "HasStorage", + "AccessControl", + "ObjC" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(cm)KEY_PREVIEW_VIDEO_GRAVITY", + "mangledName": "$s6LGCast0A9CameraApiC25KEY_PREVIEW_VIDEO_GRAVITYSSvgZ", + "moduleName": "LGCast", + "static": true, + "implicit": true, + "declAttributes": [ + "Final", + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "KEY_IS_SUPPORT_LANDSCAPE_MODE", + "printedName": "KEY_IS_SUPPORT_LANDSCAPE_MODE", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(cpy)KEY_IS_SUPPORT_LANDSCAPE_MODE", + "mangledName": "$s6LGCast0A9CameraApiC29KEY_IS_SUPPORT_LANDSCAPE_MODESSvpZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "HasInitialValue", + "Final", + "HasStorage", + "AccessControl", + "ObjC" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(cm)KEY_IS_SUPPORT_LANDSCAPE_MODE", + "mangledName": "$s6LGCast0A9CameraApiC29KEY_IS_SUPPORT_LANDSCAPE_MODESSvgZ", + "moduleName": "LGCast", + "static": true, + "implicit": true, + "declAttributes": [ + "Final", + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "shared", + "printedName": "shared", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraApi", + "printedName": "LGCast.LGCastCameraApi", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(cpy)shared", + "mangledName": "$s6LGCast0A9CameraApiC6sharedACvpZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "HasInitialValue", + "Final", + "HasStorage", + "AccessControl", + "ObjC" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraApi", + "printedName": "LGCast.LGCastCameraApi", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(cm)shared", + "mangledName": "$s6LGCast0A9CameraApiC6sharedACvgZ", + "moduleName": "LGCast", + "static": true, + "implicit": true, + "declAttributes": [ + "Final", + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "delegate", + "printedName": "delegate", + "children": [ + { + "kind": "TypeNominal", + "name": "WeakStorage", + "printedName": "LGCast.LGCastCameraApiDelegate?" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(py)delegate", + "mangledName": "$s6LGCast0A9CameraApiC8delegateAA0abC8Delegate_pSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "ReferenceOwnership", + "AccessControl", + "ObjC" + ], + "ownership": 1, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraApiDelegate?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraApiDelegate", + "printedName": "LGCast.LGCastCameraApiDelegate", + "usr": "c:@M@LGCast@objc(pl)LGCastCameraApiDelegate" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)delegate", + "mangledName": "$s6LGCast0A9CameraApiC8delegateAA0abC8Delegate_pSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraApiDelegate?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraApiDelegate", + "printedName": "LGCast.LGCastCameraApiDelegate", + "usr": "c:@M@LGCast@objc(pl)LGCastCameraApiDelegate" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)setDelegate:", + "mangledName": "$s6LGCast0A9CameraApiC8delegateAA0abC8Delegate_pSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A9CameraApiC8delegateAA0abC8Delegate_pSgvM", + "mangledName": "$s6LGCast0A9CameraApiC8delegateAA0abC8Delegate_pSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "isRunning", + "printedName": "isRunning", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(py)isRunning", + "mangledName": "$s6LGCast0A9CameraApiC9isRunningSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "SetterAccess", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)isRunning", + "mangledName": "$s6LGCast0A9CameraApiC9isRunningSbvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "isTvInputEnabled", + "printedName": "isTvInputEnabled", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A9CameraApiC16isTvInputEnabledSbvp", + "mangledName": "$s6LGCast0A9CameraApiC16isTvInputEnabledSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A9CameraApiC16isTvInputEnabledSbvg", + "mangledName": "$s6LGCast0A9CameraApiC16isTvInputEnabledSbvg", + "moduleName": "LGCast", + "accessorKind": "get" + } + ] + }, + { + "kind": "Function", + "name": "isSupportedOsVersion", + "printedName": "isSupportedOsVersion()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)isSupportedOsVersion", + "mangledName": "$s6LGCast0A9CameraApiC20isSupportedOsVersionSbyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "createCameraPreviewView", + "printedName": "createCameraPreviewView(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "UIView", + "printedName": "UIKit.UIView", + "usr": "c:objc(cs)UIView" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)createCameraPreviewView:", + "mangledName": "$s6LGCast0A9CameraApiC06createB11PreviewViewySo6UIViewCSDySSypGSgF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "setPreviewView", + "printedName": "setPreviewView(_:options:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewView", + "printedName": "LGCast.LGCastCameraPreviewView", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraPreviewView" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)setPreviewView:options:", + "mangledName": "$s6LGCast0A9CameraApiC14setPreviewView_7optionsyAA0abeF0C_SDySSypGSgtF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "generateCameraMasterKey", + "printedName": "generateCameraMasterKey(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[LGCast.LGCastSecurityKey]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[LGCast.LGCastSecurityKey]", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastSecurityKey", + "printedName": "LGCast.LGCastSecurityKey", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)generateCameraMasterKey:", + "mangledName": "$s6LGCast0A9CameraApiC08generateB9MasterKeyySayAA0a8SecurityF0CGSgSSF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "getCameraProperties", + "printedName": "getCameraProperties()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraParameterInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraParameterInfo", + "printedName": "LGCast.LGCastCameraParameterInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)getCameraProperties", + "mangledName": "$s6LGCast0A9CameraApiC03getB10PropertiesAA0aB13ParameterInfoCSgyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "setCameraProperties", + "printedName": "setCameraProperties(property:value:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + }, + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)setCameraPropertiesWithProperty:value:", + "mangledName": "$s6LGCast0A9CameraApiC03setB10Properties8property5valueSbAA0aB8PropertyO_SitF", + "moduleName": "LGCast", + "objc_name": "setCameraPropertiesWithProperty:value:", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "startRemoteCamera", + "printedName": "startRemoteCamera(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastDeviceSettings", + "printedName": "LGCast.LGCastDeviceSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)startRemoteCamera:", + "mangledName": "$s6LGCast0A9CameraApiC011startRemoteB0yyAA0A14DeviceSettingsCF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "stopRemoteCamera", + "printedName": "stopRemoteCamera()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)stopRemoteCamera", + "mangledName": "$s6LGCast0A9CameraApiC010stopRemoteB0yyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "getSupportedResolutions", + "printedName": "getSupportedResolutions()", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[LGCast.LGCastCameraResolutionInfo]", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolutionInfo", + "printedName": "LGCast.LGCastCameraResolutionInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo" + } + ], + "usr": "s:Sa" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)getSupportedResolutions", + "mangledName": "$s6LGCast0A9CameraApiC23getSupportedResolutionsSayAA0aB14ResolutionInfoCGyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "getCurrentResolution", + "printedName": "getCurrentResolution()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolutionInfo", + "printedName": "LGCast.LGCastCameraResolutionInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)getCurrentResolution", + "mangledName": "$s6LGCast0A9CameraApiC20getCurrentResolutionAA0abF4InfoCyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "setResolution", + "printedName": "setResolution(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + }, + { + "kind": "TypeNominal", + "name": "LGCastCameraResolutionInfo", + "printedName": "LGCast.LGCastCameraResolutionInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)setResolution:", + "mangledName": "$s6LGCast0A9CameraApiC13setResolutionySbAA0abE4InfoCF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "changeCameraPosition", + "printedName": "changeCameraPosition(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + }, + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)changeCameraPosition:", + "mangledName": "$s6LGCast0A9CameraApiC06changeB8PositionySbAA0abE0OF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "muteMicrophone", + "printedName": "muteMicrophone(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)muteMicrophone:", + "mangledName": "$s6LGCast0A9CameraApiC14muteMicrophoneyS2bF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi", + "mangledName": "$s6LGCast0A9CameraApiC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "hasMissingDesignatedInitializers": true, + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "AVFoundation", + "printedName": "AVFoundation", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraPreviewView", + "printedName": "LGCastCameraPreviewView", + "children": [ + { + "kind": "Var", + "name": "layerClass", + "printedName": "layerClass", + "children": [ + { + "kind": "TypeNominal", + "name": "ExistentialMetatype", + "printedName": "AnyObject.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "AnyObject" + } + ] + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraPreviewView(cpy)layerClass", + "mangledName": "$s6LGCast0A17CameraPreviewViewC10layerClassyXlXpvpZ", + "moduleName": "LGCast", + "static": true, + "overriding": true, + "objc_name": "layerClass", + "declAttributes": [ + "Dynamic", + "ObjC", + "Custom", + "Override", + "AccessControl" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "ExistentialMetatype", + "printedName": "AnyObject.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "AnyObject" + } + ] + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraPreviewView(cm)layerClass", + "mangledName": "$s6LGCast0A17CameraPreviewViewC10layerClassyXlXpvgZ", + "moduleName": "LGCast", + "static": true, + "overriding": true, + "objc_name": "layerClass", + "declAttributes": [ + "Dynamic", + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(frame:)", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewView", + "printedName": "LGCast.LGCastCameraPreviewView", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraPreviewView" + }, + { + "kind": "TypeNominal", + "name": "CGRect", + "printedName": "CoreFoundation.CGRect", + "usr": "c:@S@CGRect" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraPreviewView(im)initWithFrame:", + "mangledName": "$s6LGCast0A17CameraPreviewViewC5frameACSo6CGRectV_tcfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "initWithFrame:", + "declAttributes": [ + "Dynamic", + "ObjC", + "Custom", + "Override" + ], + "init_kind": "Designated" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(coder:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraPreviewView?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewView", + "printedName": "LGCast.LGCastCameraPreviewView", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraPreviewView" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "NSCoder", + "printedName": "Foundation.NSCoder", + "usr": "c:objc(cs)NSCoder" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraPreviewView(im)initWithCoder:", + "mangledName": "$s6LGCast0A17CameraPreviewViewC5coderACSgSo7NSCoderC_tcfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "initWithCoder:", + "declAttributes": [ + "Dynamic", + "ObjC", + "Custom", + "Required" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraPreviewView", + "mangledName": "$s6LGCast0A17CameraPreviewViewC", + "moduleName": "LGCast", + "declAttributes": [ + "Custom", + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)UIView", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "UIKit.UIView", + "UIKit.UIResponder", + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "__DefaultCustomPlaygroundQuickLookable", + "printedName": "__DefaultCustomPlaygroundQuickLookable", + "usr": "s:s38__DefaultCustomPlaygroundQuickLookableP", + "mangledName": "$ss38__DefaultCustomPlaygroundQuickLookableP" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraProperty", + "printedName": "LGCastCameraProperty", + "children": [ + { + "kind": "Var", + "name": "audio", + "printedName": "audio", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraProperty.Type) -> LGCast.LGCastCameraProperty", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraProperty.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraProperty@LGCastCameraPropertyAudio", + "mangledName": "$s6LGCast0A14CameraPropertyO5audioyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "autoWhiteBalance", + "printedName": "autoWhiteBalance", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraProperty.Type) -> LGCast.LGCastCameraProperty", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraProperty.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraProperty@LGCastCameraPropertyAutoWhiteBalance", + "mangledName": "$s6LGCast0A14CameraPropertyO16autoWhiteBalanceyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Var", + "name": "brightness", + "printedName": "brightness", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraProperty.Type) -> LGCast.LGCastCameraProperty", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraProperty.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraProperty@LGCastCameraPropertyBrightness", + "mangledName": "$s6LGCast0A14CameraPropertyO10brightnessyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 2 + }, + { + "kind": "Var", + "name": "facing", + "printedName": "facing", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraProperty.Type) -> LGCast.LGCastCameraProperty", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraProperty.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraProperty@LGCastCameraPropertyFacing", + "mangledName": "$s6LGCast0A14CameraPropertyO6facingyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 3 + }, + { + "kind": "Var", + "name": "resolution", + "printedName": "resolution", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraProperty.Type) -> LGCast.LGCastCameraProperty", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraProperty.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraProperty@LGCastCameraPropertyResolution", + "mangledName": "$s6LGCast0A14CameraPropertyO10resolutionyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 4 + }, + { + "kind": "Var", + "name": "whitebalance", + "printedName": "whitebalance", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraProperty.Type) -> LGCast.LGCastCameraProperty", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraProperty.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraProperty@LGCastCameraPropertyWhitebalance", + "mangledName": "$s6LGCast0A14CameraPropertyO12whitebalanceyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 5 + }, + { + "kind": "Var", + "name": "rotation", + "printedName": "rotation", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraProperty.Type) -> LGCast.LGCastCameraProperty", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraProperty.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraProperty@LGCastCameraPropertyRotation", + "mangledName": "$s6LGCast0A14CameraPropertyO8rotationyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 6 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraProperty?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A14CameraPropertyO8rawValueACSgSi_tcfc", + "mangledName": "$s6LGCast0A14CameraPropertyO8rawValueACSgSi_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A14CameraPropertyO8rawValueSivp", + "mangledName": "$s6LGCast0A14CameraPropertyO8rawValueSivp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A14CameraPropertyO8rawValueSivg", + "mangledName": "$s6LGCast0A14CameraPropertyO8rawValueSivg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@LGCastCameraProperty", + "mangledName": "$s6LGCast0A14CameraPropertyO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraPosition", + "printedName": "LGCastCameraPosition", + "children": [ + { + "kind": "Var", + "name": "front", + "printedName": "front", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraPosition.Type) -> LGCast.LGCastCameraPosition", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraPosition.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraPosition@LGCastCameraPositionFront", + "mangledName": "$s6LGCast0A14CameraPositionO5frontyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "back", + "printedName": "back", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraPosition.Type) -> LGCast.LGCastCameraPosition", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraPosition.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraPosition@LGCastCameraPositionBack", + "mangledName": "$s6LGCast0A14CameraPositionO4backyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraPosition?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A14CameraPositionO8rawValueACSgSi_tcfc", + "mangledName": "$s6LGCast0A14CameraPositionO8rawValueACSgSi_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A14CameraPositionO8rawValueSivp", + "mangledName": "$s6LGCast0A14CameraPositionO8rawValueSivp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A14CameraPositionO8rawValueSivg", + "mangledName": "$s6LGCast0A14CameraPositionO8rawValueSivg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@LGCastCameraPosition", + "mangledName": "$s6LGCast0A14CameraPositionO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraPreviewVideoGravity", + "printedName": "LGCastCameraPreviewVideoGravity", + "children": [ + { + "kind": "Var", + "name": "resize", + "printedName": "resize", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraPreviewVideoGravity.Type) -> LGCast.LGCastCameraPreviewVideoGravity", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewVideoGravity", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewVideoGravity", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity@LGCastCameraPreviewVideoGravityResize", + "mangledName": "$s6LGCast0A25CameraPreviewVideoGravityO6resizeyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "resizeAspect", + "printedName": "resizeAspect", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraPreviewVideoGravity.Type) -> LGCast.LGCastCameraPreviewVideoGravity", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewVideoGravity", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewVideoGravity", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity@LGCastCameraPreviewVideoGravityResizeAspect", + "mangledName": "$s6LGCast0A25CameraPreviewVideoGravityO12resizeAspectyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Var", + "name": "resizeAspectFill", + "printedName": "resizeAspectFill", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraPreviewVideoGravity.Type) -> LGCast.LGCastCameraPreviewVideoGravity", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewVideoGravity", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewVideoGravity", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity@LGCastCameraPreviewVideoGravityResizeAspectFill", + "mangledName": "$s6LGCast0A25CameraPreviewVideoGravityO16resizeAspectFillyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 2 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewVideoGravity", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A25CameraPreviewVideoGravityO8rawValueACSgSi_tcfc", + "mangledName": "$s6LGCast0A25CameraPreviewVideoGravityO8rawValueACSgSi_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A25CameraPreviewVideoGravityO8rawValueSivp", + "mangledName": "$s6LGCast0A25CameraPreviewVideoGravityO8rawValueSivp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A25CameraPreviewVideoGravityO8rawValueSivg", + "mangledName": "$s6LGCast0A25CameraPreviewVideoGravityO8rawValueSivg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity", + "mangledName": "$s6LGCast0A25CameraPreviewVideoGravityO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCastCamperaPropertyStatus", + "children": [ + { + "kind": "Var", + "name": "disable", + "printedName": "disable", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCamperaPropertyStatus.Type) -> LGCast.LGCastCamperaPropertyStatus", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCamperaPropertyStatus.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus@LGCastCamperaPropertyStatusDisable", + "mangledName": "$s6LGCast0A21CamperaPropertyStatusO7disableyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "enable", + "printedName": "enable", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCamperaPropertyStatus.Type) -> LGCast.LGCastCamperaPropertyStatus", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCamperaPropertyStatus.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus@LGCastCamperaPropertyStatusEnable", + "mangledName": "$s6LGCast0A21CamperaPropertyStatusO6enableyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCamperaPropertyStatus?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A21CamperaPropertyStatusO8rawValueACSgSi_tcfc", + "mangledName": "$s6LGCast0A21CamperaPropertyStatusO8rawValueACSgSi_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A21CamperaPropertyStatusO8rawValueSivp", + "mangledName": "$s6LGCast0A21CamperaPropertyStatusO8rawValueSivp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A21CamperaPropertyStatusO8rawValueSivg", + "mangledName": "$s6LGCast0A21CamperaPropertyStatusO8rawValueSivg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus", + "mangledName": "$s6LGCast0A21CamperaPropertyStatusO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraResolution", + "printedName": "LGCastCameraResolution", + "children": [ + { + "kind": "Var", + "name": "low", + "printedName": "low", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionLow", + "mangledName": "$s6LGCast0A16CameraResolutionO3lowyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "medium", + "printedName": "medium", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionMedium", + "mangledName": "$s6LGCast0A16CameraResolutionO6mediumyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment", + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Var", + "name": "high", + "printedName": "high", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionHigh", + "mangledName": "$s6LGCast0A16CameraResolutionO4highyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment", + "ObjC" + ], + "fixedbinaryorder": 2 + }, + { + "kind": "Var", + "name": "cif352x288", + "printedName": "cif352x288", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionCif352x288", + "mangledName": "$s6LGCast0A16CameraResolutionO10cif352x288yA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment", + "ObjC" + ], + "fixedbinaryorder": 3 + }, + { + "kind": "Var", + "name": "vga640x480", + "printedName": "vga640x480", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionVga640x480", + "mangledName": "$s6LGCast0A16CameraResolutionO10vga640x480yA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 4 + }, + { + "kind": "Var", + "name": "hd1280x720", + "printedName": "hd1280x720", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionHd1280x720", + "mangledName": "$s6LGCast0A16CameraResolutionO10hd1280x720yA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 5 + }, + { + "kind": "Var", + "name": "hd1920x1080", + "printedName": "hd1920x1080", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionHd1920x1080", + "mangledName": "$s6LGCast0A16CameraResolutionO11hd1920x1080yA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 6 + }, + { + "kind": "Var", + "name": "hd4K3840x2160", + "printedName": "hd4K3840x2160", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionHd4K3840x2160", + "mangledName": "$s6LGCast0A16CameraResolutionO13hd4K3840x2160yA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 7 + }, + { + "kind": "Var", + "name": "iFrame960x540", + "printedName": "iFrame960x540", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionIFrame960x540", + "mangledName": "$s6LGCast0A16CameraResolutionO13iFrame960x540yA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 8 + }, + { + "kind": "Var", + "name": "iFrame1280x720", + "printedName": "iFrame1280x720", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionIFrame1280x720", + "mangledName": "$s6LGCast0A16CameraResolutionO14iFrame1280x720yA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 9 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(width:height:)", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A16CameraResolutionO5width6heightACSi_Sitcfc", + "mangledName": "$s6LGCast0A16CameraResolutionO5width6heightACSi_Sitcfc", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "description", + "printedName": "description", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A16CameraResolutionO11descriptionSSvp", + "mangledName": "$s6LGCast0A16CameraResolutionO11descriptionSSvp", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A16CameraResolutionO11descriptionSSvg", + "mangledName": "$s6LGCast0A16CameraResolutionO11descriptionSSvg", + "moduleName": "LGCast", + "accessorKind": "get" + } + ] + }, + { + "kind": "Function", + "name": "getSize", + "printedName": "getSize()", + "children": [ + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "(width: Swift.Int, height: Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "declKind": "Func", + "usr": "s:6LGCast0A16CameraResolutionO7getSizeSi5width_Si6heighttyF", + "mangledName": "$s6LGCast0A16CameraResolutionO7getSizeSi5width_Si6heighttyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "getWidth", + "printedName": "getWidth()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "s:6LGCast0A16CameraResolutionO8getWidthSiyF", + "mangledName": "$s6LGCast0A16CameraResolutionO8getWidthSiyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "getHeight", + "printedName": "getHeight()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "s:6LGCast0A16CameraResolutionO9getHeightSiyF", + "mangledName": "$s6LGCast0A16CameraResolutionO9getHeightSiyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraResolution?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A16CameraResolutionO8rawValueACSgSi_tcfc", + "mangledName": "$s6LGCast0A16CameraResolutionO8rawValueACSgSi_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A16CameraResolutionO8rawValueSivp", + "mangledName": "$s6LGCast0A16CameraResolutionO8rawValueSivp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A16CameraResolutionO8rawValueSivg", + "mangledName": "$s6LGCast0A16CameraResolutionO8rawValueSivg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@LGCastCameraResolution", + "mangledName": "$s6LGCast0A16CameraResolutionO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastUserDefaults", + "printedName": "LGCastUserDefaults", + "children": [ + { + "kind": "Var", + "name": "wrappedValue", + "printedName": "wrappedValue", + "children": [ + { + "kind": "TypeNominal", + "name": "GenericTypeParam", + "printedName": "τ_0_0" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A12UserDefaultsV12wrappedValuexvp", + "mangledName": "$s6LGCast0A12UserDefaultsV12wrappedValuexvp", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "GenericTypeParam", + "printedName": "τ_0_0" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A12UserDefaultsV12wrappedValuexvg", + "mangledName": "$s6LGCast0A12UserDefaultsV12wrappedValuexvg", + "moduleName": "LGCast", + "genericSig": "<τ_0_0>", + "sugared_genericSig": "", + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "GenericTypeParam", + "printedName": "τ_0_0" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A12UserDefaultsV12wrappedValuexvs", + "mangledName": "$s6LGCast0A12UserDefaultsV12wrappedValuexvs", + "moduleName": "LGCast", + "genericSig": "<τ_0_0>", + "sugared_genericSig": "", + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A12UserDefaultsV12wrappedValuexvM", + "mangledName": "$s6LGCast0A12UserDefaultsV12wrappedValuexvM", + "moduleName": "LGCast", + "genericSig": "<τ_0_0>", + "sugared_genericSig": "", + "implicit": true, + "accessorKind": "_modify" + } + ] + } + ], + "declKind": "Struct", + "usr": "s:6LGCast0A12UserDefaultsV", + "mangledName": "$s6LGCast0A12UserDefaultsV", + "moduleName": "LGCast", + "genericSig": "<τ_0_0>", + "sugared_genericSig": "", + "declAttributes": [ + "AccessControl", + "PropertyWrapper" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "VideoToolbox", + "printedName": "VideoToolbox", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "CoreImage", + "printedName": "CoreImage", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "ReplayKit", + "printedName": "ReplayKit", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "AVFoundation", + "printedName": "AVFoundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "AudioToolbox", + "printedName": "AudioToolbox", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "TypeDecl", + "name": "AudioEncoderDelegate", + "printedName": "AudioEncoderDelegate", + "children": [ + { + "kind": "Function", + "name": "audioEncoder", + "printedName": "audioEncoder(_:didPrepare:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "AudioEncoder", + "printedName": "LGCast.AudioEncoder", + "usr": "s:6LGCast12AudioEncoderC" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Func", + "usr": "s:6LGCast20AudioEncoderDelegateP05audioC0_10didPrepareyAA0bC0C_SbtF", + "mangledName": "$s6LGCast20AudioEncoderDelegateP05audioC0_10didPrepareyAA0bC0C_SbtF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.AudioEncoderDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "audioEncoder", + "printedName": "audioEncoder(_:didStart:startPts:startTime:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "AudioEncoder", + "printedName": "LGCast.AudioEncoder", + "usr": "s:6LGCast12AudioEncoderC" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + }, + { + "kind": "TypeNominal", + "name": "CMTime", + "printedName": "CoreMedia.CMTime", + "usr": "c:@SA@CMTime" + }, + { + "kind": "TypeNominal", + "name": "DispatchTime", + "printedName": "Dispatch.DispatchTime", + "usr": "s:8Dispatch0A4TimeV" + } + ], + "declKind": "Func", + "usr": "s:6LGCast20AudioEncoderDelegateP05audioC0_8didStart8startPts0H4TimeyAA0bC0C_SbSo6CMTimea8Dispatch0lJ0VtF", + "mangledName": "$s6LGCast20AudioEncoderDelegateP05audioC0_8didStart8startPts0H4TimeyAA0bC0C_SbSo6CMTimea8Dispatch0lJ0VtF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.AudioEncoderDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "audioEncoder", + "printedName": "audioEncoder(_:with:didEncode:pts:priority:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "AudioEncoder", + "printedName": "LGCast.AudioEncoder", + "usr": "s:6LGCast12AudioEncoderC" + }, + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + }, + { + "kind": "TypeNominal", + "name": "Data", + "printedName": "Foundation.Data", + "usr": "s:10Foundation4DataV" + }, + { + "kind": "TypeNominal", + "name": "CMTime", + "printedName": "CoreMedia.CMTime", + "usr": "c:@SA@CMTime" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "s:6LGCast20AudioEncoderDelegateP05audioC0_4with9didEncode3pts8priorityyAA0bC0C_AA11MTAudioTypeO10Foundation4DataVSo6CMTimeaSitF", + "mangledName": "$s6LGCast20AudioEncoderDelegateP05audioC0_4with9didEncode3pts8priorityyAA0bC0C_AA11MTAudioTypeO10Foundation4DataVSo6CMTimeaSitF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.AudioEncoderDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "s:6LGCast20AudioEncoderDelegateP", + "mangledName": "$s6LGCast20AudioEncoderDelegateP", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 : AnyObject>", + "sugared_genericSig": "", + "declAttributes": [ + "AccessControl" + ] + }, + { + "kind": "TypeDecl", + "name": "AudioEncoder", + "printedName": "AudioEncoder", + "children": [ + { + "kind": "Function", + "name": "prepareAudioEncoder", + "printedName": "prepareAudioEncoder(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "CMSampleBuffer", + "printedName": "CoreMedia.CMSampleBuffer", + "usr": "c:@T@CMSampleBufferRef" + } + ], + "declKind": "Func", + "usr": "s:6LGCast12AudioEncoderC07preparebC0yySo17CMSampleBufferRefaF", + "mangledName": "$s6LGCast12AudioEncoderC07preparebC0yySo17CMSampleBufferRefaF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "processAudioSampleBuffer", + "printedName": "processAudioSampleBuffer(_:baseTime:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "CMSampleBuffer", + "printedName": "CoreMedia.CMSampleBuffer", + "usr": "c:@T@CMSampleBufferRef" + }, + { + "kind": "TypeNominal", + "name": "CMTime", + "printedName": "CoreMedia.CMTime", + "usr": "c:@SA@CMTime" + } + ], + "declKind": "Func", + "usr": "s:6LGCast12AudioEncoderC07processB12SampleBuffer_8baseTimeySo08CMSampleF3Refa_So6CMTimeatF", + "mangledName": "$s6LGCast12AudioEncoderC07processB12SampleBuffer_8baseTimeySo08CMSampleF3Refa_So6CMTimeatF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "RawDocComment" + ], + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Class", + "usr": "s:6LGCast12AudioEncoderC", + "mangledName": "$s6LGCast12AudioEncoderC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "hasMissingDesignatedInitializers": true + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "GStreamerForLGCast", + "printedName": "GStreamerForLGCast", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "QuartzCore", + "printedName": "QuartzCore", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "RTPStreamerMediaType", + "printedName": "RTPStreamerMediaType", + "children": [ + { + "kind": "Var", + "name": "VIDEO", + "printedName": "VIDEO", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerMediaType.Type) -> LGCast.RTPStreamerMediaType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerMediaType", + "printedName": "LGCast.RTPStreamerMediaType", + "usr": "s:6LGCast20RTPStreamerMediaTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerMediaType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerMediaType", + "printedName": "LGCast.RTPStreamerMediaType", + "usr": "s:6LGCast20RTPStreamerMediaTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast20RTPStreamerMediaTypeO5VIDEOyA2CmF", + "mangledName": "$s6LGCast20RTPStreamerMediaTypeO5VIDEOyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "AUDIO", + "printedName": "AUDIO", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerMediaType.Type) -> LGCast.RTPStreamerMediaType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerMediaType", + "printedName": "LGCast.RTPStreamerMediaType", + "usr": "s:6LGCast20RTPStreamerMediaTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerMediaType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerMediaType", + "printedName": "LGCast.RTPStreamerMediaType", + "usr": "s:6LGCast20RTPStreamerMediaTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast20RTPStreamerMediaTypeO5AUDIOyA2CmF", + "mangledName": "$s6LGCast20RTPStreamerMediaTypeO5AUDIOyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "AV", + "printedName": "AV", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerMediaType.Type) -> LGCast.RTPStreamerMediaType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerMediaType", + "printedName": "LGCast.RTPStreamerMediaType", + "usr": "s:6LGCast20RTPStreamerMediaTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerMediaType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerMediaType", + "printedName": "LGCast.RTPStreamerMediaType", + "usr": "s:6LGCast20RTPStreamerMediaTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast20RTPStreamerMediaTypeO2AVyA2CmF", + "mangledName": "$s6LGCast20RTPStreamerMediaTypeO2AVyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.RTPStreamerMediaType?", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerMediaType", + "printedName": "LGCast.RTPStreamerMediaType", + "usr": "s:6LGCast20RTPStreamerMediaTypeO" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast20RTPStreamerMediaTypeO8rawValueACSgs5Int32V_tcfc", + "mangledName": "$s6LGCast20RTPStreamerMediaTypeO8rawValueACSgs5Int32V_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Var", + "usr": "s:6LGCast20RTPStreamerMediaTypeO8rawValues5Int32Vvp", + "mangledName": "$s6LGCast20RTPStreamerMediaTypeO8rawValues5Int32Vvp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast20RTPStreamerMediaTypeO8rawValues5Int32Vvg", + "mangledName": "$s6LGCast20RTPStreamerMediaTypeO8rawValues5Int32Vvg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "s:6LGCast20RTPStreamerMediaTypeO", + "mangledName": "$s6LGCast20RTPStreamerMediaTypeO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "enumRawTypeName": "Int32", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "RTPStreamerRscType", + "printedName": "RTPStreamerRscType", + "children": [ + { + "kind": "Var", + "name": "APP", + "printedName": "APP", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerRscType.Type) -> LGCast.RTPStreamerRscType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerRscType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast18RTPStreamerRscTypeO3APPyA2CmF", + "mangledName": "$s6LGCast18RTPStreamerRscTypeO3APPyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "FILE", + "printedName": "FILE", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerRscType.Type) -> LGCast.RTPStreamerRscType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerRscType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast18RTPStreamerRscTypeO4FILEyA2CmF", + "mangledName": "$s6LGCast18RTPStreamerRscTypeO4FILEyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "SOCKET", + "printedName": "SOCKET", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerRscType.Type) -> LGCast.RTPStreamerRscType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerRscType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast18RTPStreamerRscTypeO6SOCKETyA2CmF", + "mangledName": "$s6LGCast18RTPStreamerRscTypeO6SOCKETyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "CAMERA", + "printedName": "CAMERA", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerRscType.Type) -> LGCast.RTPStreamerRscType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerRscType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast18RTPStreamerRscTypeO6CAMERAyA2CmF", + "mangledName": "$s6LGCast18RTPStreamerRscTypeO6CAMERAyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.RTPStreamerRscType?", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast18RTPStreamerRscTypeO8rawValueACSgs5Int32V_tcfc", + "mangledName": "$s6LGCast18RTPStreamerRscTypeO8rawValueACSgs5Int32V_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Var", + "usr": "s:6LGCast18RTPStreamerRscTypeO8rawValues5Int32Vvp", + "mangledName": "$s6LGCast18RTPStreamerRscTypeO8rawValues5Int32Vvp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast18RTPStreamerRscTypeO8rawValues5Int32Vvg", + "mangledName": "$s6LGCast18RTPStreamerRscTypeO8rawValues5Int32Vvg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "s:6LGCast18RTPStreamerRscTypeO", + "mangledName": "$s6LGCast18RTPStreamerRscTypeO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "enumRawTypeName": "Int32", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "RTPStreamerSRTPAuthType", + "printedName": "RTPStreamerSRTPAuthType", + "children": [ + { + "kind": "Var", + "name": "NONE", + "printedName": "NONE", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerSRTPAuthType.Type) -> LGCast.RTPStreamerSRTPAuthType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPAuthType", + "printedName": "LGCast.RTPStreamerSRTPAuthType", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerSRTPAuthType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPAuthType", + "printedName": "LGCast.RTPStreamerSRTPAuthType", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO4NONEyA2CmF", + "mangledName": "$s6LGCast23RTPStreamerSRTPAuthTypeO4NONEyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "HMAC_SHA1_32", + "printedName": "HMAC_SHA1_32", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerSRTPAuthType.Type) -> LGCast.RTPStreamerSRTPAuthType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPAuthType", + "printedName": "LGCast.RTPStreamerSRTPAuthType", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerSRTPAuthType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPAuthType", + "printedName": "LGCast.RTPStreamerSRTPAuthType", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO12HMAC_SHA1_32yA2CmF", + "mangledName": "$s6LGCast23RTPStreamerSRTPAuthTypeO12HMAC_SHA1_32yA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "HMAC_SHA1_80", + "printedName": "HMAC_SHA1_80", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerSRTPAuthType.Type) -> LGCast.RTPStreamerSRTPAuthType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPAuthType", + "printedName": "LGCast.RTPStreamerSRTPAuthType", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerSRTPAuthType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPAuthType", + "printedName": "LGCast.RTPStreamerSRTPAuthType", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO12HMAC_SHA1_80yA2CmF", + "mangledName": "$s6LGCast23RTPStreamerSRTPAuthTypeO12HMAC_SHA1_80yA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.RTPStreamerSRTPAuthType?", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPAuthType", + "printedName": "LGCast.RTPStreamerSRTPAuthType", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO8rawValueACSgs5Int32V_tcfc", + "mangledName": "$s6LGCast23RTPStreamerSRTPAuthTypeO8rawValueACSgs5Int32V_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Var", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO8rawValues5Int32Vvp", + "mangledName": "$s6LGCast23RTPStreamerSRTPAuthTypeO8rawValues5Int32Vvp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO8rawValues5Int32Vvg", + "mangledName": "$s6LGCast23RTPStreamerSRTPAuthTypeO8rawValues5Int32Vvg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO", + "mangledName": "$s6LGCast23RTPStreamerSRTPAuthTypeO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "enumRawTypeName": "Int32", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "RTPStreamerSRTPCipherType", + "printedName": "RTPStreamerSRTPCipherType", + "children": [ + { + "kind": "Var", + "name": "NONE", + "printedName": "NONE", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerSRTPCipherType.Type) -> LGCast.RTPStreamerSRTPCipherType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerSRTPCipherType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO4NONEyA2CmF", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO4NONEyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "AES_128_ICM", + "printedName": "AES_128_ICM", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerSRTPCipherType.Type) -> LGCast.RTPStreamerSRTPCipherType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerSRTPCipherType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO11AES_128_ICMyA2CmF", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO11AES_128_ICMyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "AES_256_ICM", + "printedName": "AES_256_ICM", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerSRTPCipherType.Type) -> LGCast.RTPStreamerSRTPCipherType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerSRTPCipherType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO11AES_256_ICMyA2CmF", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO11AES_256_ICMyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "AES_128_GCM", + "printedName": "AES_128_GCM", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerSRTPCipherType.Type) -> LGCast.RTPStreamerSRTPCipherType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerSRTPCipherType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO11AES_128_GCMyA2CmF", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO11AES_128_GCMyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "AES_256_GCM", + "printedName": "AES_256_GCM", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerSRTPCipherType.Type) -> LGCast.RTPStreamerSRTPCipherType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerSRTPCipherType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO11AES_256_GCMyA2CmF", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO11AES_256_GCMyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.RTPStreamerSRTPCipherType?", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO8rawValueACSgs5Int32V_tcfc", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO8rawValueACSgs5Int32V_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Var", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO8rawValues5Int32Vvp", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO8rawValues5Int32Vvp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO8rawValues5Int32Vvg", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO8rawValues5Int32Vvg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "enumRawTypeName": "Int32", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "GStreamerForLGCast", + "printedName": "GStreamerForLGCast", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "GStreamerForLGCast", + "printedName": "GStreamerForLGCast", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "TypeDecl", + "name": "MTRTPPipeline", + "printedName": "MTRTPPipeline", + "children": [ + { + "kind": "Function", + "name": "getPipeline", + "printedName": "getPipeline(_:info:isSecure:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "UInt32", + "printedName": "Swift.UInt32", + "usr": "s:s6UInt32V" + }, + { + "kind": "TypeNominal", + "name": "MTMediaInfo", + "printedName": "LGCast.MTMediaInfo", + "usr": "c:@M@LGCast@objc(cs)MTMediaInfo" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Func", + "usr": "s:6LGCast13MTRTPPipelineC11getPipeline_4info8isSecureSSSgs6UInt32V_AA11MTMediaInfoCSbtFZ", + "mangledName": "$s6LGCast13MTRTPPipelineC11getPipeline_4info8isSecureSSSgs6UInt32V_AA11MTMediaInfoCSbtFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl" + ], + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Class", + "usr": "s:6LGCast13MTRTPPipelineC", + "mangledName": "$s6LGCast13MTRTPPipelineC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "hasMissingDesignatedInitializers": true + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "GStreamerForLGCast", + "printedName": "GStreamerForLGCast", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "TypeDecl", + "name": "MTRTPSecurity", + "printedName": "MTRTPSecurity", + "children": [ + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "MTRTPSecurity", + "printedName": "LGCast.MTRTPSecurity", + "usr": "c:@M@LGCast@objc(cs)MTRTPSecurity" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)MTRTPSecurity(im)init", + "mangledName": "$s6LGCast13MTRTPSecurityCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)MTRTPSecurity", + "mangledName": "$s6LGCast13MTRTPSecurityC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "MTRTPSecurityKey", + "printedName": "MTRTPSecurityKey", + "children": [ + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "MTRTPSecurityKey", + "printedName": "LGCast.MTRTPSecurityKey", + "usr": "c:@M@LGCast@objc(cs)MTRTPSecurityKey" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)MTRTPSecurityKey(im)init", + "mangledName": "$s6LGCast16MTRTPSecurityKeyCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)MTRTPSecurityKey", + "mangledName": "$s6LGCast16MTRTPSecurityKeyC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "MTMediaType", + "printedName": "MTMediaType", + "children": [ + { + "kind": "Var", + "name": "video", + "printedName": "video", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.MTMediaType.Type) -> LGCast.MTMediaType", + "children": [ + { + "kind": "TypeNominal", + "name": "MTMediaType", + "printedName": "LGCast.MTMediaType", + "usr": "s:6LGCast11MTMediaTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.MTMediaType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTMediaType", + "printedName": "LGCast.MTMediaType", + "usr": "s:6LGCast11MTMediaTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast11MTMediaTypeO5videoyA2CmF", + "mangledName": "$s6LGCast11MTMediaTypeO5videoyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "audio", + "printedName": "audio", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.MTMediaType.Type) -> LGCast.MTMediaType", + "children": [ + { + "kind": "TypeNominal", + "name": "MTMediaType", + "printedName": "LGCast.MTMediaType", + "usr": "s:6LGCast11MTMediaTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.MTMediaType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTMediaType", + "printedName": "LGCast.MTMediaType", + "usr": "s:6LGCast11MTMediaTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast11MTMediaTypeO5audioyA2CmF", + "mangledName": "$s6LGCast11MTMediaTypeO5audioyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "av", + "printedName": "av", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.MTMediaType.Type) -> LGCast.MTMediaType", + "children": [ + { + "kind": "TypeNominal", + "name": "MTMediaType", + "printedName": "LGCast.MTMediaType", + "usr": "s:6LGCast11MTMediaTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.MTMediaType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTMediaType", + "printedName": "LGCast.MTMediaType", + "usr": "s:6LGCast11MTMediaTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast11MTMediaTypeO2avyA2CmF", + "mangledName": "$s6LGCast11MTMediaTypeO2avyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.MTMediaType?", + "children": [ + { + "kind": "TypeNominal", + "name": "MTMediaType", + "printedName": "LGCast.MTMediaType", + "usr": "s:6LGCast11MTMediaTypeO" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast11MTMediaTypeO8rawValueACSgs5Int32V_tcfc", + "mangledName": "$s6LGCast11MTMediaTypeO8rawValueACSgs5Int32V_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Var", + "usr": "s:6LGCast11MTMediaTypeO8rawValues5Int32Vvp", + "mangledName": "$s6LGCast11MTMediaTypeO8rawValues5Int32Vvp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast11MTMediaTypeO8rawValues5Int32Vvg", + "mangledName": "$s6LGCast11MTMediaTypeO8rawValues5Int32Vvg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "s:6LGCast11MTMediaTypeO", + "mangledName": "$s6LGCast11MTMediaTypeO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "enumRawTypeName": "Int32", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "MTMediaInfo", + "printedName": "MTMediaInfo", + "children": [ + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "MTMediaInfo", + "printedName": "LGCast.MTMediaInfo", + "usr": "c:@M@LGCast@objc(cs)MTMediaInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)MTMediaInfo(im)init", + "mangledName": "$s6LGCast11MTMediaInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)MTMediaInfo", + "mangledName": "$s6LGCast11MTMediaInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "MTVideoType", + "printedName": "MTVideoType", + "children": [ + { + "kind": "Var", + "name": "raw", + "printedName": "raw", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.MTVideoType.Type) -> LGCast.MTVideoType", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.MTVideoType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@MTVideoType@MTVideoTypeRaw", + "mangledName": "$s6LGCast11MTVideoTypeO3rawyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "h264", + "printedName": "h264", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.MTVideoType.Type) -> LGCast.MTVideoType", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.MTVideoType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@MTVideoType@MTVideoTypeH264", + "mangledName": "$s6LGCast11MTVideoTypeO4h264yA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Var", + "name": "mjpeg", + "printedName": "mjpeg", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.MTVideoType.Type) -> LGCast.MTVideoType", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.MTVideoType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@MTVideoType@MTVideoTypeMjpeg", + "mangledName": "$s6LGCast11MTVideoTypeO5mjpegyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 2 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.MTVideoType?", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast11MTVideoTypeO8rawValueACSgs5Int32V_tcfc", + "mangledName": "$s6LGCast11MTVideoTypeO8rawValueACSgs5Int32V_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Var", + "usr": "s:6LGCast11MTVideoTypeO8rawValues5Int32Vvp", + "mangledName": "$s6LGCast11MTVideoTypeO8rawValues5Int32Vvp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast11MTVideoTypeO8rawValues5Int32Vvg", + "mangledName": "$s6LGCast11MTVideoTypeO8rawValues5Int32Vvg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@MTVideoType", + "mangledName": "$s6LGCast11MTVideoTypeO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int32", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "MTVideoInfo", + "printedName": "MTVideoInfo", + "children": [ + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoInfo", + "printedName": "LGCast.MTVideoInfo", + "usr": "c:@M@LGCast@objc(cs)MTVideoInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)MTVideoInfo(im)init", + "mangledName": "$s6LGCast11MTVideoInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)MTVideoInfo", + "mangledName": "$s6LGCast11MTVideoInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "MTAudioType", + "printedName": "MTAudioType", + "children": [ + { + "kind": "Var", + "name": "pcm", + "printedName": "pcm", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.MTAudioType.Type) -> LGCast.MTAudioType", + "children": [ + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.MTAudioType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@MTAudioType@MTAudioTypePcm", + "mangledName": "$s6LGCast11MTAudioTypeO3pcmyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "aac", + "printedName": "aac", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.MTAudioType.Type) -> LGCast.MTAudioType", + "children": [ + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.MTAudioType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@MTAudioType@MTAudioTypeAac", + "mangledName": "$s6LGCast11MTAudioTypeO3aacyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.MTAudioType?", + "children": [ + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast11MTAudioTypeO8rawValueACSgs5Int32V_tcfc", + "mangledName": "$s6LGCast11MTAudioTypeO8rawValueACSgs5Int32V_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Var", + "usr": "s:6LGCast11MTAudioTypeO8rawValues5Int32Vvp", + "mangledName": "$s6LGCast11MTAudioTypeO8rawValues5Int32Vvp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast11MTAudioTypeO8rawValues5Int32Vvg", + "mangledName": "$s6LGCast11MTAudioTypeO8rawValues5Int32Vvg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@MTAudioType", + "mangledName": "$s6LGCast11MTAudioTypeO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int32", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "MTAudioInfo", + "printedName": "MTAudioInfo", + "children": [ + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "MTAudioInfo", + "printedName": "LGCast.MTAudioInfo", + "usr": "c:@M@LGCast@objc(cs)MTAudioInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)MTAudioInfo(im)init", + "mangledName": "$s6LGCast11MTAudioInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)MTAudioInfo", + "mangledName": "$s6LGCast11MTAudioInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "AVFoundation", + "printedName": "AVFoundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "OSLog", + "printedName": "OSLog", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "TypeDecl", + "name": "Log", + "printedName": "Log", + "children": [ + { + "kind": "Function", + "name": "debug", + "printedName": "debug(_:file:function:line:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:Sa" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "hasDefaultArg": true, + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "s:6LGCast3LogC5debug_4file8function4lineyypd_S2SSitFZ", + "mangledName": "$s6LGCast3LogC5debug_4file8function4lineyypd_S2SSitFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "info", + "printedName": "info(_:file:function:line:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:Sa" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "hasDefaultArg": true, + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "s:6LGCast3LogC4info_4file8function4lineyypd_S2SSitFZ", + "mangledName": "$s6LGCast3LogC4info_4file8function4lineyypd_S2SSitFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "notice", + "printedName": "notice(_:file:function:line:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:Sa" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "hasDefaultArg": true, + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "s:6LGCast3LogC6notice_4file8function4lineyypd_S2SSitFZ", + "mangledName": "$s6LGCast3LogC6notice_4file8function4lineyypd_S2SSitFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "error", + "printedName": "error(_:file:function:line:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:Sa" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "hasDefaultArg": true, + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "s:6LGCast3LogC5error_4file8function4lineyypd_S2SSitFZ", + "mangledName": "$s6LGCast3LogC5error_4file8function4lineyypd_S2SSitFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "fault", + "printedName": "fault(_:file:function:line:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:Sa" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "hasDefaultArg": true, + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "s:6LGCast3LogC5fault_4file8function4lineyypd_S2SSitFZ", + "mangledName": "$s6LGCast3LogC5fault_4file8function4lineyypd_S2SSitFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "debugLGCast", + "printedName": "debugLGCast(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "c:@CM@LGCast@objc(cs)Log(cm)debugLGCast:", + "mangledName": "$s6LGCast3LogC05debugA0yySSFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl", + "ObjC" + ], + "isFromExtension": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "infoLGCast", + "printedName": "infoLGCast(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "c:@CM@LGCast@objc(cs)Log(cm)infoLGCast:", + "mangledName": "$s6LGCast3LogC04infoA0yySSFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl", + "ObjC" + ], + "isFromExtension": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "noticeLGCast", + "printedName": "noticeLGCast(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "c:@CM@LGCast@objc(cs)Log(cm)noticeLGCast:", + "mangledName": "$s6LGCast3LogC06noticeA0yySSFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl", + "ObjC" + ], + "isFromExtension": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "errorLGCast", + "printedName": "errorLGCast(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "c:@CM@LGCast@objc(cs)Log(cm)errorLGCast:", + "mangledName": "$s6LGCast3LogC05errorA0yySSFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl", + "ObjC" + ], + "isFromExtension": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "faultLGCast", + "printedName": "faultLGCast(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "c:@CM@LGCast@objc(cs)Log(cm)faultLGCast:", + "mangledName": "$s6LGCast3LogC05faultA0yySSFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl", + "ObjC" + ], + "isFromExtension": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)Log", + "mangledName": "$s6LGCast3LogC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "RawDocComment", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "hasMissingDesignatedInitializers": true, + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "VideoToolbox", + "printedName": "VideoToolbox", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "CoreImage", + "printedName": "CoreImage", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "ReplayKit", + "printedName": "ReplayKit", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "VideoToolbox", + "printedName": "VideoToolbox", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "MediaPlayer", + "printedName": "MediaPlayer", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "AVFoundation", + "printedName": "AVFoundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "TypeDecl", + "name": "AudioStreamBasicDescription", + "printedName": "AudioStreamBasicDescription", + "children": [ + { + "kind": "Function", + "name": "==", + "printedName": "==(_:_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + }, + { + "kind": "TypeNominal", + "name": "AudioStreamBasicDescription", + "printedName": "CoreAudioTypes.AudioStreamBasicDescription", + "usr": "c:@S@AudioStreamBasicDescription" + }, + { + "kind": "TypeNominal", + "name": "AudioStreamBasicDescription", + "printedName": "CoreAudioTypes.AudioStreamBasicDescription", + "usr": "c:@S@AudioStreamBasicDescription" + } + ], + "declKind": "Func", + "usr": "s:So27AudioStreamBasicDescriptionV6LGCastE2eeoiySbAB_ABtFZ", + "mangledName": "$sSo27AudioStreamBasicDescriptionV6LGCastE2eeoiySbAB_ABtFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "isFromExtension": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Struct", + "usr": "c:@S@AudioStreamBasicDescription", + "moduleName": "CoreAudioTypes", + "isExternal": true, + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + } + ] + }, + { + "kind": "TypeDecl", + "name": "UIDevice", + "printedName": "UIDevice", + "children": [ + { + "kind": "Var", + "name": "isPhoneDevice", + "printedName": "isPhoneDevice", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "s:So8UIDeviceC6LGCastE13isPhoneDeviceSbvp", + "mangledName": "$sSo8UIDeviceC6LGCastE13isPhoneDeviceSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "Custom", + "AccessControl" + ], + "isFromExtension": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "s:So8UIDeviceC6LGCastE13isPhoneDeviceSbvg", + "mangledName": "$sSo8UIDeviceC6LGCastE13isPhoneDeviceSbvg", + "moduleName": "LGCast", + "isFromExtension": true, + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "isPadDevice", + "printedName": "isPadDevice", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "s:So8UIDeviceC6LGCastE11isPadDeviceSbvp", + "mangledName": "$sSo8UIDeviceC6LGCastE11isPadDeviceSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "Custom", + "AccessControl" + ], + "isFromExtension": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "s:So8UIDeviceC6LGCastE11isPadDeviceSbvg", + "mangledName": "$sSo8UIDeviceC6LGCastE11isPadDeviceSbvg", + "moduleName": "LGCast", + "isFromExtension": true, + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "modelName", + "printedName": "modelName", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "s:So8UIDeviceC6LGCastE9modelNameSSvp", + "mangledName": "$sSo8UIDeviceC6LGCastE9modelNameSSvp", + "moduleName": "LGCast", + "declAttributes": [ + "Custom", + "AccessControl" + ], + "isFromExtension": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "s:So8UIDeviceC6LGCastE9modelNameSSvg", + "mangledName": "$sSo8UIDeviceC6LGCastE9modelNameSSvg", + "moduleName": "LGCast", + "isFromExtension": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Class", + "usr": "c:objc(cs)UIDevice", + "moduleName": "UIKit", + "isOpen": true, + "intro_iOS": "2.0", + "objc_name": "UIDevice", + "declAttributes": [ + "Available", + "ObjC", + "NonSendable", + "Custom", + "Dynamic" + ], + "superclassUsr": "c:objc(cs)NSObject", + "isExternal": true, + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + } + ], + "json_format_version": 8 + }, + "ConstValues": [ + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraError.swift", + "kind": "IntegerLiteral", + "offset": 190, + "length": 16, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraError.swift", + "kind": "IntegerLiteral", + "offset": 216, + "length": 14, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraError.swift", + "kind": "IntegerLiteral", + "offset": 240, + "length": 18, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraError.swift", + "kind": "IntegerLiteral", + "offset": 190, + "length": 16, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraError.swift", + "kind": "IntegerLiteral", + "offset": 216, + "length": 14, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraError.swift", + "kind": "IntegerLiteral", + "offset": 240, + "length": 18, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "IntegerLiteral", + "offset": 207, + "length": 15, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "IntegerLiteral", + "offset": 232, + "length": 20, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "IntegerLiteral", + "offset": 262, + "length": 15, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "IntegerLiteral", + "offset": 287, + "length": 16, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "IntegerLiteral", + "offset": 207, + "length": 15, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "IntegerLiteral", + "offset": 232, + "length": 20, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "IntegerLiteral", + "offset": 262, + "length": 15, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "IntegerLiteral", + "offset": 287, + "length": 16, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "BooleanLiteral", + "offset": 825, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringError.swift", + "kind": "IntegerLiteral", + "offset": 191, + "length": 16, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringError.swift", + "kind": "IntegerLiteral", + "offset": 217, + "length": 14, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringError.swift", + "kind": "IntegerLiteral", + "offset": 241, + "length": 18, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringError.swift", + "kind": "IntegerLiteral", + "offset": 191, + "length": 16, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringError.swift", + "kind": "IntegerLiteral", + "offset": 217, + "length": 14, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringError.swift", + "kind": "IntegerLiteral", + "offset": 241, + "length": 18, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 364, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 403, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 506, + "length": 5, + "value": "44100" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 547, + "length": 2, + "value": "64" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 552, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 596, + "length": 1, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 694, + "length": 4, + "value": "1920" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 733, + "length": 4, + "value": "1080" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "BooleanLiteral", + "offset": 781, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 824, + "length": 2, + "value": "60" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 862, + "length": 1, + "value": "6" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 866, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 873, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 1309, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 1349, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 1386, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "StringLiteral", + "offset": 1434, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 1574, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 1824, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 1889, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 1958, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2038, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2119, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2162, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2199, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "StringLiteral", + "offset": 2250, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "BooleanLiteral", + "offset": 2296, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "StringLiteral", + "offset": 2348, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2533, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2628, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2664, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "BooleanLiteral", + "offset": 2709, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "StringLiteral", + "offset": 2760, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2800, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2839, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 3366, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 3402, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 3439, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 3561, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 3600, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraApi.swift", + "kind": "IntegerLiteral", + "offset": 431, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraApi.swift", + "kind": "IntegerLiteral", + "offset": 463, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraApi.swift", + "kind": "StringLiteral", + "offset": 559, + "length": 21, + "value": "\"KEY_CAMERA_POSITION\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraApi.swift", + "kind": "StringLiteral", + "offset": 637, + "length": 27, + "value": "\"KEY_PREVIEW_VIDEO_GRAVITY\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraApi.swift", + "kind": "StringLiteral", + "offset": 725, + "length": 31, + "value": "\"KEY_IS_SUPPORT_LANDSCAPE_MODE\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraApi.swift", + "kind": "BooleanLiteral", + "offset": 925, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 196, + "length": 16, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 222, + "length": 10, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 242, + "length": 6, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 258, + "length": 10, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 278, + "length": 12, + "value": "5" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 300, + "length": 8, + "value": "6" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 196, + "length": 16, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 222, + "length": 10, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 242, + "length": 6, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 258, + "length": 10, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 278, + "length": 12, + "value": "5" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 300, + "length": 8, + "value": "6" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 375, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 393, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 375, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 393, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 480, + "length": 12, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 502, + "length": 16, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 480, + "length": 12, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 502, + "length": 16, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 594, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 614, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 594, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 614, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 700, + "length": 6, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 728, + "length": 4, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 755, + "length": 10, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 775, + "length": 10, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 795, + "length": 10, + "value": "5" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 815, + "length": 11, + "value": "6" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 836, + "length": 13, + "value": "7" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 859, + "length": 13, + "value": "8" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 882, + "length": 14, + "value": "9" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 700, + "length": 6, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 728, + "length": 4, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 755, + "length": 10, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 775, + "length": 10, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 795, + "length": 10, + "value": "5" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 815, + "length": 11, + "value": "6" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 836, + "length": 13, + "value": "7" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 859, + "length": 13, + "value": "8" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 882, + "length": 14, + "value": "9" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 179, + "length": 19, + "value": "\"KEY_USE_PC_PLAYER\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "BooleanLiteral", + "offset": 214, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 299, + "length": 16, + "value": "\"KEY_VIDEO_TYPE\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 432, + "length": 16, + "value": "\"KEY_AUDIO_TYPE\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 564, + "length": 17, + "value": "\"KEY_ENABLE_SRTP\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "BooleanLiteral", + "offset": 597, + "length": 4, + "value": "true" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 680, + "length": 19, + "value": "\"KEY_PC_IP_ADDRESS\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 715, + "length": 13, + "value": "\"192.168.1.1\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 810, + "length": 19, + "value": "\"KEY_PC_VIDEO_PORT\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "IntegerLiteral", + "offset": 845, + "length": 4, + "value": "5000" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 928, + "length": 19, + "value": "\"KEY_PC_AUDIO_PORT\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "IntegerLiteral", + "offset": 963, + "length": 4, + "value": "5002" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1046, + "length": 18, + "value": "\"KEY_TV_DEVICE_ID\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1080, + "length": 9, + "value": "\"Unknown\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1171, + "length": 26, + "value": "\"KEY_IS_RUNNING_EXTENSION\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "BooleanLiteral", + "offset": 1213, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1305, + "length": 19, + "value": "\"KEY_VIDEO_BITRATE\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "IntegerLiteral", + "offset": 1340, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1421, + "length": 22, + "value": "\"KEY_VIDEO_RESOLUTION\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "IntegerLiteral", + "offset": 1459, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1549, + "length": 28, + "value": "\"KEY_VIDEO_RESOLUTION_WITDH\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "IntegerLiteral", + "offset": 1593, + "length": 4, + "value": "1920" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1688, + "length": 29, + "value": "\"KEY_VIDEO_RESOLUTION_HEIGHT\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "IntegerLiteral", + "offset": 1733, + "length": 4, + "value": "1080" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1821, + "length": 19, + "value": "\"KEY_AUDIO_BITRATE\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "IntegerLiteral", + "offset": 1856, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1945, + "length": 21, + "value": "\"KEY_ENABLE_RIVISION\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "BooleanLiteral", + "offset": 1982, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 2078, + "length": 26, + "value": "\"KEY_VIDEO_IFRAME_LATENCY\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "IntegerLiteral", + "offset": 2120, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/MasterKeyFactory.swift", + "kind": "IntegerLiteral", + "offset": 527, + "length": 1, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/MasterKeyFactory.swift", + "kind": "IntegerLiteral", + "offset": 568, + "length": 2, + "value": "30" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/MasterKeyFactory.swift", + "kind": "IntegerLiteral", + "offset": 611, + "length": 2, + "value": "10" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "BooleanLiteral", + "offset": 893, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "IntegerLiteral", + "offset": 926, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "IntegerLiteral", + "offset": 957, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "BooleanLiteral", + "offset": 1212, + "length": 4, + "value": "true" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "StringLiteral", + "offset": 1238, + "length": 51, + "value": "\"change TV orientation mode \"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "StringLiteral", + "offset": 1288, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "BooleanLiteral", + "offset": 1442, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "BooleanLiteral", + "offset": 1529, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "Array", + "offset": 2467, + "length": 24, + "value": "[0x00, 0x00, 0x00, 0x01]" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "IntegerLiteral", + "offset": 2523, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "IntegerLiteral", + "offset": 2556, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "IntegerLiteral", + "offset": 2932, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "IntegerLiteral", + "offset": 2965, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "BooleanLiteral", + "offset": 3018, + "length": 4, + "value": "true" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "StringLiteral", + "offset": 3044, + "length": 34, + "value": "\"change Screen mode \"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "StringLiteral", + "offset": 3077, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "StringLiteral", + "offset": 501, + "length": 12, + "value": "\"LGCast.VideoEncoder\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/AudioEncoder.swift", + "kind": "IntegerLiteral", + "offset": 539, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/AudioEncoder.swift", + "kind": "IntegerLiteral", + "offset": 718, + "length": 5, + "value": "44100" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/AudioEncoder.swift", + "kind": "IntegerLiteral", + "offset": 775, + "length": 1, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/AudioEncoder.swift", + "kind": "IntegerLiteral", + "offset": 825, + "length": 2, + "value": "64" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/AudioEncoder.swift", + "kind": "IntegerLiteral", + "offset": 830, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/AudioEncoder.swift", + "kind": "IntegerLiteral", + "offset": 883, + "length": 2, + "value": "32" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/AudioEncoder.swift", + "kind": "IntegerLiteral", + "offset": 888, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/AudioEncoder.swift", + "kind": "BooleanLiteral", + "offset": 1102, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 253, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 272, + "length": 7, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 293, + "length": 5, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 312, + "length": 4, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 330, + "length": 5, + "value": "5" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 253, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 272, + "length": 7, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 293, + "length": 5, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 312, + "length": 4, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 330, + "length": 5, + "value": "5" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 253, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 272, + "length": 7, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 293, + "length": 5, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 312, + "length": 4, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 330, + "length": 5, + "value": "5" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "BooleanLiteral", + "offset": 246, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 283, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 545, + "length": 4, + "value": "1920" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 573, + "length": 4, + "value": "1080" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 604, + "length": 2, + "value": "30" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 631, + "length": 6, + "value": "200000" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "BooleanLiteral", + "offset": 670, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 776, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 878, + "length": 5, + "value": "44100" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 911, + "length": 1, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 937, + "length": 2, + "value": "64" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 942, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 1047, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 1100, + "length": 10, + "value": "1356955624" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "BooleanLiteral", + "offset": 1206, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "BooleanLiteral", + "offset": 1241, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 169, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 180, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 195, + "length": 2, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 169, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 180, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 195, + "length": 2, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 169, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 180, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 195, + "length": 2, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 536, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 547, + "length": 4, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 561, + "length": 6, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 577, + "length": 6, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 536, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 547, + "length": 4, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 561, + "length": 6, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 577, + "length": 6, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 536, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 547, + "length": 4, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 561, + "length": 6, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 577, + "length": 6, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 714, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 725, + "length": 12, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 747, + "length": 12, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 714, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 725, + "length": 12, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 747, + "length": 12, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 714, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 725, + "length": 12, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 747, + "length": 12, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 892, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 903, + "length": 11, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 924, + "length": 11, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 945, + "length": 11, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 966, + "length": 11, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 892, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 903, + "length": 11, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 924, + "length": 11, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 945, + "length": 11, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 966, + "length": 11, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 892, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 903, + "length": 11, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 924, + "length": 11, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 945, + "length": 11, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 966, + "length": 11, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTP\/MTRTPSecurity.swift", + "kind": "IntegerLiteral", + "offset": 298, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTMediaInfo.swift", + "kind": "IntegerLiteral", + "offset": 152, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTMediaInfo.swift", + "kind": "IntegerLiteral", + "offset": 167, + "length": 2, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTMediaInfo.swift", + "kind": "IntegerLiteral", + "offset": 152, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTMediaInfo.swift", + "kind": "IntegerLiteral", + "offset": 167, + "length": 2, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTMediaInfo.swift", + "kind": "IntegerLiteral", + "offset": 152, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTMediaInfo.swift", + "kind": "IntegerLiteral", + "offset": 167, + "length": 2, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 156, + "length": 4, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 170, + "length": 5, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 156, + "length": 4, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 170, + "length": 5, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 750, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 775, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 801, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 830, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 857, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "StringLiteral", + "offset": 923, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 959, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTAudioInfo.swift", + "kind": "IntegerLiteral", + "offset": 156, + "length": 3, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTAudioInfo.swift", + "kind": "IntegerLiteral", + "offset": 156, + "length": 3, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTAudioInfo.swift", + "kind": "IntegerLiteral", + "offset": 640, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTAudioInfo.swift", + "kind": "IntegerLiteral", + "offset": 669, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTAudioInfo.swift", + "kind": "IntegerLiteral", + "offset": 701, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTAudioInfo.swift", + "kind": "StringLiteral", + "offset": 767, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTAudioInfo.swift", + "kind": "IntegerLiteral", + "offset": 803, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraDevice.swift", + "kind": "StringLiteral", + "offset": 1167, + "length": 22, + "value": "\"videoDataOutputQueue\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraDevice.swift", + "kind": "StringLiteral", + "offset": 1251, + "length": 22, + "value": "\"audioDataOutputQueue\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraDevice.swift", + "kind": "BooleanLiteral", + "offset": 1303, + "length": 4, + "value": "true" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraDevice.swift", + "kind": "BooleanLiteral", + "offset": 1335, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/Log.swift", + "kind": "IntegerLiteral", + "offset": 666, + "length": 4, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/Log.swift", + "kind": "IntegerLiteral", + "offset": 684, + "length": 6, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/Log.swift", + "kind": "IntegerLiteral", + "offset": 704, + "length": 5, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/Log.swift", + "kind": "IntegerLiteral", + "offset": 723, + "length": 5, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/Log.swift", + "kind": "IntegerLiteral", + "offset": 666, + "length": 4, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/Log.swift", + "kind": "IntegerLiteral", + "offset": 684, + "length": 6, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/Log.swift", + "kind": "IntegerLiteral", + "offset": 704, + "length": 5, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/Log.swift", + "kind": "IntegerLiteral", + "offset": 723, + "length": 5, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 201, + "length": 4, + "value": "1920" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 243, + "length": 4, + "value": "1080" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 282, + "length": 5, + "value": "90000" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 321, + "length": 2, + "value": "60" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 355, + "length": 1, + "value": "6" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 359, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 366, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 454, + "length": 2, + "value": "16" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 497, + "length": 1, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 536, + "length": 5, + "value": "44100" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 582, + "length": 2, + "value": "64" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 587, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "StringLiteral", + "offset": 633, + "length": 10, + "value": "\"40002420\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "StringLiteral", + "offset": 691, + "length": 20, + "value": "\"landscape|portrait\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "Array", + "offset": 748, + "length": 285, + "value": "[0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89]" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "BooleanLiteral", + "offset": 800, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "BooleanLiteral", + "offset": 842, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "BooleanLiteral", + "offset": 986, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "BooleanLiteral", + "offset": 1028, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "BooleanLiteral", + "offset": 1110, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "IntegerLiteral", + "offset": 1319, + "length": 13, + "value": "1000000000" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "IntegerLiteral", + "offset": 1374, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "IntegerLiteral", + "offset": 1413, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "IntegerLiteral", + "offset": 1537, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "IntegerLiteral", + "offset": 1564, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "StringLiteral", + "offset": 1905, + "length": 73, + "value": "\"change device orientation mode \"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "StringLiteral", + "offset": 1952, + "length": 10, + "value": "\"portrait\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "StringLiteral", + "offset": 1965, + "length": 11, + "value": "\"landscape\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "StringLiteral", + "offset": 1977, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "BooleanLiteral", + "offset": 2148, + "length": 4, + "value": "true" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "BooleanLiteral", + "offset": 1449, + "length": 4, + "value": "true" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "IntegerLiteral", + "offset": 9103, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "IntegerLiteral", + "offset": 9585, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringVolume.swift", + "kind": "IntegerLiteral", + "offset": 202, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreaming.swift", + "kind": "StringLiteral", + "offset": 242, + "length": 19, + "value": "\"videoSendingQueue\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreaming.swift", + "kind": "StringLiteral", + "offset": 341, + "length": 19, + "value": "\"audioSendingQueue\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreaming.swift", + "kind": "IntegerLiteral", + "offset": 442, + "length": 10, + "value": "30000000" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreaming.swift", + "kind": "BooleanLiteral", + "offset": 516, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreaming.swift", + "kind": "BooleanLiteral", + "offset": 561, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreaming.swift", + "kind": "IntegerLiteral", + "offset": 606, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreaming.swift", + "kind": "IntegerLiteral", + "offset": 647, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 150, + "length": 4, + "value": "1280" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 187, + "length": 3, + "value": "720" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 228, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "BooleanLiteral", + "offset": 274, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "BooleanLiteral", + "offset": 311, + "length": 4, + "value": "true" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 352, + "length": 2, + "value": "50" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 389, + "length": 2, + "value": "90" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 424, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 458, + "length": 3, + "value": "100" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 501, + "length": 4, + "value": "7500" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 541, + "length": 4, + "value": "2300" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 581, + "length": 5, + "value": "10000" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 721, + "length": 2, + "value": "30" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 755, + "length": 1, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 759, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 766, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 1033, + "length": 2, + "value": "16" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 1076, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 1115, + "length": 5, + "value": "44100" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 1161, + "length": 2, + "value": "64" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 1166, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "Array", + "offset": 1210, + "length": 285, + "value": "[0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89]" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "BooleanLiteral", + "offset": 1210, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "BooleanLiteral", + "offset": 1252, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "BooleanLiteral", + "offset": 1392, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "BooleanLiteral", + "offset": 1434, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "BooleanLiteral", + "offset": 1513, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "BooleanLiteral", + "offset": 1556, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "IntegerLiteral", + "offset": 8188, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "IntegerLiteral", + "offset": 8670, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "StringLiteral", + "offset": 800, + "length": 13, + "value": "\"LGCast.CameraService\"" + } + ] +} \ No newline at end of file diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Modules/LGCast.swiftmodule/arm64-apple-ios.private.swiftinterface b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Modules/LGCast.swiftmodule/arm64-apple-ios.private.swiftinterface new file mode 100644 index 00000000..e60f5828 --- /dev/null +++ b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Modules/LGCast.swiftmodule/arm64-apple-ios.private.swiftinterface @@ -0,0 +1,479 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51) +// swift-module-flags: -target arm64-apple-ios11.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name LGCast +// swift-module-flags-ignorable: -enable-bare-slash-regex +import AVFoundation +import AudioToolbox +import CoreImage +import Foundation +import GStreamerForLGCast +@_exported import LGCast +import MediaPlayer +import OSLog +import QuartzCore +import ReplayKit +import Swift +import UIKit +import VideoToolbox +import _Concurrency +import _StringProcessing +@objc public enum LGCastCameraError : Swift.Int { + case unknown + case connectionClosed + case deviceShutdown + case rendererTerminated + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastMirroringEvent : Swift.Int { + case unknown + case updateVideoInfo + case updateVideoVideoSize + case updateAudioInfo + case updatePlayerInfo + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public protocol LGCastMirroringApiDelegate { + @objc func lgcastMirroringDidStart(result: Swift.Bool) + @objc func lgcastMirroringDidStop(result: Swift.Bool) + @objc func lgcastMirroringErrorDidOccur(error: LGCast.LGCastMirroringError) + @objc func lgcastMirroringUpdateEvent(event: LGCast.LGCastMirroringEvent, info: LGCast.LGCastMirroringInfo?) +} +@_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers @objc public class LGCastMirroringApi : ObjectiveC.NSObject { + @objc public static let shared: LGCast.LGCastMirroringApi + public var isRunning: Swift.Bool { + get + } + public var isTvInputEnabled: Swift.Bool { + get + } + @objc public func isSupportedOsVersion() -> Swift.Bool + @objc public func setDelegate(_ delegate: LGCast.LGCastMirroringApiDelegate) + @objc public func setMediaSettings(_ mediaInfo: LGCast.LGCastMirroringMediaSettings?) -> LGCast.LGCastMirroringInfo? + @objc public func generateMirroringMasterKey(_ publicKey: Swift.String) -> Swift.Array? + @objc public func startMirroring(_ settings: LGCast.LGCastDeviceSettings?) + @objc public func updateDisplayOrientation(isPortraitMode: Swift.Bool) -> LGCast.LGCastMirroringInfo? + @objc public func stopMirroring() + public func startMirroringWithCapture(_ settings: LGCast.LGCastDeviceSettings?) + public func stopMirroringWithCapture() + @objc public func pushSampleBuffer(_ sampleBuffer: CoreMedia.CMSampleBuffer, with sampleBufferType: ReplayKit.RPSampleBufferType) + public func pushSampleBuffer(_ sampleBuffer: CoreMedia.CMSampleBuffer, with mediaType: LGCast.RTPStreamerMediaType) + @objc deinit +} +@objc public enum LGCastMirroringError : Swift.Int { + case unknown + case connectionClosed + case deviceShutdown + case rendererTerminated + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@_inheritsConvenienceInitializers @objc public class LGCastSecurityKey : ObjectiveC.NSObject { + @objc public var masterKey: Swift.String? + @objc public var mki: Swift.String? + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastDeviceSettings : ObjectiveC.NSObject { + @objc public var host: Swift.String? + @objc public var audioPort: Swift.Int + @objc public var videoPort: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringAudioSettings : ObjectiveC.NSObject { + @objc public var sampleRate: Swift.Int + @objc public var bitrate: Swift.Int + @objc public var channelCnt: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringVideoSettings : ObjectiveC.NSObject { + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc public var isPortraitMode: Swift.Bool + @objc public var framerate: Swift.Int + @objc public var bitrate: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringMediaSettings : ObjectiveC.NSObject { + @objc public var audio: LGCast.LGCastMirroringAudioSettings? + @objc public var video: LGCast.LGCastMirroringVideoSettings? + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringAudioInfo : ObjectiveC.NSObject { + @objc public var type: LGCast.MTAudioType + @objc public var codec: Swift.String { + @objc get + } + @objc public var samplingRate: Swift.Int + @objc public var channelCnt: Swift.Int + @objc public var bitrate: Swift.Int + @objc public var streamMuxConfig: Swift.String + @objc public var codecData: Foundation.Data? + @objc public var host: Swift.String? + @objc public var port: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringVideoInfo : ObjectiveC.NSObject { + @objc public var type: LGCast.MTVideoType + @objc public var codec: Swift.String { + @objc get + } + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc public var activeWidth: Swift.Int + @objc public var activeHeight: Swift.Int + @objc public var framerate: Swift.Int + @objc public var samplingRate: Swift.Int + @objc public var bitrate: Swift.Int + @objc public var supportOrientation: Swift.String + @objc public var isPortraitMode: Swift.Bool + @objc public var screenOrientation: Swift.String + @objc public var streamMuxConfig: Swift.String? + @objc public var codecData: Foundation.Data? + @objc public var host: Swift.String? + @objc public var port: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringPlayerInfo : ObjectiveC.NSObject { + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc public var isPortraitMode: Swift.Bool + @objc public var ipAddress: Swift.String + @objc public var audioPort: Swift.Int + @objc public var videoPort: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringInfo : ObjectiveC.NSObject { + @objc public var audioInfo: LGCast.LGCastMirroringAudioInfo? + @objc public var videoInfo: LGCast.LGCastMirroringVideoInfo? + @objc public var playerInfo: LGCast.LGCastMirroringPlayerInfo? + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastCameraParameterInfo : ObjectiveC.NSObject { + @objc public var audio: LGCast.LGCastCamperaPropertyStatus + @objc public var autoWhiteBalance: LGCast.LGCastCamperaPropertyStatus + @objc public var brightness: Swift.Int + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc public var facing: LGCast.LGCastCameraPosition + @objc public var whiteBalance: Swift.Int + @objc public var rotation: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@objc public protocol LGCastCameraApiDelegate { + @objc func lgcastCameraDidPlay() + @objc func lgcastCameraDidChange(property: LGCast.LGCastCameraProperty) + @objc func lgcastCameraErrorDidOccur(error: LGCast.LGCastCameraError) +} +@_inheritsConvenienceInitializers @objc public class LGCastCameraResolutionInfo : ObjectiveC.NSObject { + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@objc @_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers public class LGCastCameraApi : ObjectiveC.NSObject { + @objc public static let KEY_CAMERA_POSITION: Swift.String + @objc public static let KEY_PREVIEW_VIDEO_GRAVITY: Swift.String + @objc public static let KEY_IS_SUPPORT_LANDSCAPE_MODE: Swift.String + @objc public static let shared: LGCast.LGCastCameraApi + @objc weak public var delegate: LGCast.LGCastCameraApiDelegate? + @objc public var isRunning: Swift.Bool { + get + } + public var isTvInputEnabled: Swift.Bool { + get + } + @objc public func isSupportedOsVersion() -> Swift.Bool + @objc public func createCameraPreviewView(_ options: [Swift.String : Any]? = nil) -> UIKit.UIView + @objc public func setPreviewView(_ view: LGCast.LGCastCameraPreviewView, options: [Swift.String : Any]? = nil) + @objc public func generateCameraMasterKey(_ publicKey: Swift.String) -> Swift.Array? + @objc public func getCameraProperties() -> LGCast.LGCastCameraParameterInfo? + @objc public func setCameraProperties(property: LGCast.LGCastCameraProperty, value: Swift.Int) -> Swift.Bool + @objc public func startRemoteCamera(_ settings: LGCast.LGCastDeviceSettings) + @objc public func stopRemoteCamera() + @objc public func getSupportedResolutions() -> [LGCast.LGCastCameraResolutionInfo] + @objc public func getCurrentResolution() -> LGCast.LGCastCameraResolutionInfo + @objc public func setResolution(_ resolution: LGCast.LGCastCameraResolutionInfo) -> Swift.Bool + @objc public func changeCameraPosition(_ position: LGCast.LGCastCameraPosition) -> Swift.Bool + @objc public func muteMicrophone(_ isMuted: Swift.Bool) -> Swift.Bool + @objc deinit +} +@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) public class LGCastCameraPreviewView : UIKit.UIView { + @_Concurrency.MainActor(unsafe) @objc override dynamic public class var layerClass: Swift.AnyClass { + @objc get + } + @_Concurrency.MainActor(unsafe) @objc override dynamic public init(frame: CoreFoundation.CGRect) + @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder: Foundation.NSCoder) + @objc deinit +} +@objc public enum LGCastCameraProperty : Swift.Int { + case audio + case autoWhiteBalance + case brightness + case facing + case resolution + case whitebalance + case rotation + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastCameraPosition : Swift.Int { + case front = 0 + case back = 1 + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastCameraPreviewVideoGravity : Swift.Int { + case resize + case resizeAspect + case resizeAspectFill + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastCamperaPropertyStatus : Swift.Int { + case disable = 0 + case enable = 1 + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastCameraResolution : Swift.Int { + case low + case medium + case high + case cif352x288 + case vga640x480 + case hd1280x720 + case hd1920x1080 + case hd4K3840x2160 + case iFrame960x540 + case iFrame1280x720 + public init(width: Swift.Int, height: Swift.Int) + public var description: Swift.String { + get + } + public func getSize() -> (width: Swift.Int, height: Swift.Int) + public func getWidth() -> Swift.Int + public func getHeight() -> Swift.Int + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@propertyWrapper public struct LGCastUserDefaults { + public var wrappedValue: T { + get + set + } +} +public protocol AudioEncoderDelegate : AnyObject { + func audioEncoder(_ encoder: LGCast.AudioEncoder, didPrepare result: Swift.Bool) + func audioEncoder(_ encoder: LGCast.AudioEncoder, didStart result: Swift.Bool, startPts: CoreMedia.CMTime, startTime: Dispatch.DispatchTime) + func audioEncoder(_ encoder: LGCast.AudioEncoder, with type: LGCast.MTAudioType, didEncode data: Foundation.Data, pts: CoreMedia.CMTime, priority: Swift.Int) +} +@_hasMissingDesignatedInitializers public class AudioEncoder { + public func prepareAudioEncoder(_ sampleBuffer: CoreMedia.CMSampleBuffer) + public func processAudioSampleBuffer(_ sampleBuffer: CoreMedia.CMSampleBuffer, baseTime: CoreMedia.CMTime) + @objc deinit +} +extension CoreAudioTypes.AudioStreamBasicDescription : Swift.Equatable { + public static func == (lhs: CoreAudioTypes.AudioStreamBasicDescription, rhs: CoreAudioTypes.AudioStreamBasicDescription) -> Swift.Bool +} +public enum RTPStreamerMediaType : Swift.Int32 { + case VIDEO + case AUDIO + case AV + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +public enum RTPStreamerRscType : Swift.Int32 { + case APP + case FILE + case SOCKET + case CAMERA + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +public enum RTPStreamerSRTPAuthType : Swift.Int32 { + case NONE + case HMAC_SHA1_32 + case HMAC_SHA1_80 + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +public enum RTPStreamerSRTPCipherType : Swift.Int32 { + case NONE + case AES_128_ICM + case AES_256_ICM + case AES_128_GCM + case AES_256_GCM + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +@_hasMissingDesignatedInitializers public class MTRTPPipeline { + public static func getPipeline(_ ssrc: Swift.UInt32, info: LGCast.MTMediaInfo, isSecure: Swift.Bool) -> Swift.String? + @objc deinit +} +@objc @_inheritsConvenienceInitializers public class MTRTPSecurity : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +@objc @_inheritsConvenienceInitializers public class MTRTPSecurityKey : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +public enum MTMediaType : Swift.Int32 { + case video + case audio + case av + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +@objc @_inheritsConvenienceInitializers public class MTMediaInfo : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +@objc public enum MTVideoType : Swift.Int32 { + case raw + case h264 + case mjpeg + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +@objc @_inheritsConvenienceInitializers public class MTVideoInfo : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +@objc public enum MTAudioType : Swift.Int32 { + case pcm + case aac + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +@objc @_inheritsConvenienceInitializers public class MTAudioInfo : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +@objc @_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers public class Log : ObjectiveC.NSObject { + public static func debug(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + public static func info(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + public static func notice(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + public static func error(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + public static func fault(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + @objc deinit +} +extension UIKit.UIDevice { + @_Concurrency.MainActor(unsafe) public var isPhoneDevice: Swift.Bool { + get + } + @_Concurrency.MainActor(unsafe) public var isPadDevice: Swift.Bool { + get + } + @_Concurrency.MainActor(unsafe) public var modelName: Swift.String { + get + } +} +extension LGCast.Log { + @objc public static func debugLGCast(_ message: Swift.String) + @objc public static func infoLGCast(_ message: Swift.String) + @objc public static func noticeLGCast(_ message: Swift.String) + @objc public static func errorLGCast(_ message: Swift.String) + @objc public static func faultLGCast(_ message: Swift.String) +} +extension LGCast.LGCastCameraError : Swift.Equatable {} +extension LGCast.LGCastCameraError : Swift.Hashable {} +extension LGCast.LGCastCameraError : Swift.RawRepresentable {} +extension LGCast.LGCastMirroringEvent : Swift.Equatable {} +extension LGCast.LGCastMirroringEvent : Swift.Hashable {} +extension LGCast.LGCastMirroringEvent : Swift.RawRepresentable {} +extension LGCast.LGCastMirroringError : Swift.Equatable {} +extension LGCast.LGCastMirroringError : Swift.Hashable {} +extension LGCast.LGCastMirroringError : Swift.RawRepresentable {} +extension LGCast.LGCastCameraProperty : Swift.Equatable {} +extension LGCast.LGCastCameraProperty : Swift.Hashable {} +extension LGCast.LGCastCameraProperty : Swift.RawRepresentable {} +extension LGCast.LGCastCameraPosition : Swift.Equatable {} +extension LGCast.LGCastCameraPosition : Swift.Hashable {} +extension LGCast.LGCastCameraPosition : Swift.RawRepresentable {} +extension LGCast.LGCastCameraPreviewVideoGravity : Swift.Equatable {} +extension LGCast.LGCastCameraPreviewVideoGravity : Swift.Hashable {} +extension LGCast.LGCastCameraPreviewVideoGravity : Swift.RawRepresentable {} +extension LGCast.LGCastCamperaPropertyStatus : Swift.Equatable {} +extension LGCast.LGCastCamperaPropertyStatus : Swift.Hashable {} +extension LGCast.LGCastCamperaPropertyStatus : Swift.RawRepresentable {} +extension LGCast.LGCastCameraResolution : Swift.Equatable {} +extension LGCast.LGCastCameraResolution : Swift.Hashable {} +extension LGCast.LGCastCameraResolution : Swift.RawRepresentable {} +extension LGCast.RTPStreamerMediaType : Swift.Equatable {} +extension LGCast.RTPStreamerMediaType : Swift.Hashable {} +extension LGCast.RTPStreamerMediaType : Swift.RawRepresentable {} +extension LGCast.RTPStreamerRscType : Swift.Equatable {} +extension LGCast.RTPStreamerRscType : Swift.Hashable {} +extension LGCast.RTPStreamerRscType : Swift.RawRepresentable {} +extension LGCast.RTPStreamerSRTPAuthType : Swift.Equatable {} +extension LGCast.RTPStreamerSRTPAuthType : Swift.Hashable {} +extension LGCast.RTPStreamerSRTPAuthType : Swift.RawRepresentable {} +extension LGCast.RTPStreamerSRTPCipherType : Swift.Equatable {} +extension LGCast.RTPStreamerSRTPCipherType : Swift.Hashable {} +extension LGCast.RTPStreamerSRTPCipherType : Swift.RawRepresentable {} +extension LGCast.MTMediaType : Swift.Equatable {} +extension LGCast.MTMediaType : Swift.Hashable {} +extension LGCast.MTMediaType : Swift.RawRepresentable {} +extension LGCast.MTVideoType : Swift.Equatable {} +extension LGCast.MTVideoType : Swift.Hashable {} +extension LGCast.MTVideoType : Swift.RawRepresentable {} +extension LGCast.MTAudioType : Swift.Equatable {} +extension LGCast.MTAudioType : Swift.Hashable {} +extension LGCast.MTAudioType : Swift.RawRepresentable {} diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Modules/LGCast.swiftmodule/arm64-apple-ios.swiftdoc b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Modules/LGCast.swiftmodule/arm64-apple-ios.swiftdoc new file mode 100644 index 00000000..08c2661e Binary files /dev/null and b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Modules/LGCast.swiftmodule/arm64-apple-ios.swiftdoc differ diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Modules/LGCast.swiftmodule/arm64-apple-ios.swiftinterface b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Modules/LGCast.swiftmodule/arm64-apple-ios.swiftinterface new file mode 100644 index 00000000..e60f5828 --- /dev/null +++ b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Modules/LGCast.swiftmodule/arm64-apple-ios.swiftinterface @@ -0,0 +1,479 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51) +// swift-module-flags: -target arm64-apple-ios11.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name LGCast +// swift-module-flags-ignorable: -enable-bare-slash-regex +import AVFoundation +import AudioToolbox +import CoreImage +import Foundation +import GStreamerForLGCast +@_exported import LGCast +import MediaPlayer +import OSLog +import QuartzCore +import ReplayKit +import Swift +import UIKit +import VideoToolbox +import _Concurrency +import _StringProcessing +@objc public enum LGCastCameraError : Swift.Int { + case unknown + case connectionClosed + case deviceShutdown + case rendererTerminated + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastMirroringEvent : Swift.Int { + case unknown + case updateVideoInfo + case updateVideoVideoSize + case updateAudioInfo + case updatePlayerInfo + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public protocol LGCastMirroringApiDelegate { + @objc func lgcastMirroringDidStart(result: Swift.Bool) + @objc func lgcastMirroringDidStop(result: Swift.Bool) + @objc func lgcastMirroringErrorDidOccur(error: LGCast.LGCastMirroringError) + @objc func lgcastMirroringUpdateEvent(event: LGCast.LGCastMirroringEvent, info: LGCast.LGCastMirroringInfo?) +} +@_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers @objc public class LGCastMirroringApi : ObjectiveC.NSObject { + @objc public static let shared: LGCast.LGCastMirroringApi + public var isRunning: Swift.Bool { + get + } + public var isTvInputEnabled: Swift.Bool { + get + } + @objc public func isSupportedOsVersion() -> Swift.Bool + @objc public func setDelegate(_ delegate: LGCast.LGCastMirroringApiDelegate) + @objc public func setMediaSettings(_ mediaInfo: LGCast.LGCastMirroringMediaSettings?) -> LGCast.LGCastMirroringInfo? + @objc public func generateMirroringMasterKey(_ publicKey: Swift.String) -> Swift.Array? + @objc public func startMirroring(_ settings: LGCast.LGCastDeviceSettings?) + @objc public func updateDisplayOrientation(isPortraitMode: Swift.Bool) -> LGCast.LGCastMirroringInfo? + @objc public func stopMirroring() + public func startMirroringWithCapture(_ settings: LGCast.LGCastDeviceSettings?) + public func stopMirroringWithCapture() + @objc public func pushSampleBuffer(_ sampleBuffer: CoreMedia.CMSampleBuffer, with sampleBufferType: ReplayKit.RPSampleBufferType) + public func pushSampleBuffer(_ sampleBuffer: CoreMedia.CMSampleBuffer, with mediaType: LGCast.RTPStreamerMediaType) + @objc deinit +} +@objc public enum LGCastMirroringError : Swift.Int { + case unknown + case connectionClosed + case deviceShutdown + case rendererTerminated + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@_inheritsConvenienceInitializers @objc public class LGCastSecurityKey : ObjectiveC.NSObject { + @objc public var masterKey: Swift.String? + @objc public var mki: Swift.String? + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastDeviceSettings : ObjectiveC.NSObject { + @objc public var host: Swift.String? + @objc public var audioPort: Swift.Int + @objc public var videoPort: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringAudioSettings : ObjectiveC.NSObject { + @objc public var sampleRate: Swift.Int + @objc public var bitrate: Swift.Int + @objc public var channelCnt: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringVideoSettings : ObjectiveC.NSObject { + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc public var isPortraitMode: Swift.Bool + @objc public var framerate: Swift.Int + @objc public var bitrate: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringMediaSettings : ObjectiveC.NSObject { + @objc public var audio: LGCast.LGCastMirroringAudioSettings? + @objc public var video: LGCast.LGCastMirroringVideoSettings? + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringAudioInfo : ObjectiveC.NSObject { + @objc public var type: LGCast.MTAudioType + @objc public var codec: Swift.String { + @objc get + } + @objc public var samplingRate: Swift.Int + @objc public var channelCnt: Swift.Int + @objc public var bitrate: Swift.Int + @objc public var streamMuxConfig: Swift.String + @objc public var codecData: Foundation.Data? + @objc public var host: Swift.String? + @objc public var port: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringVideoInfo : ObjectiveC.NSObject { + @objc public var type: LGCast.MTVideoType + @objc public var codec: Swift.String { + @objc get + } + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc public var activeWidth: Swift.Int + @objc public var activeHeight: Swift.Int + @objc public var framerate: Swift.Int + @objc public var samplingRate: Swift.Int + @objc public var bitrate: Swift.Int + @objc public var supportOrientation: Swift.String + @objc public var isPortraitMode: Swift.Bool + @objc public var screenOrientation: Swift.String + @objc public var streamMuxConfig: Swift.String? + @objc public var codecData: Foundation.Data? + @objc public var host: Swift.String? + @objc public var port: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringPlayerInfo : ObjectiveC.NSObject { + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc public var isPortraitMode: Swift.Bool + @objc public var ipAddress: Swift.String + @objc public var audioPort: Swift.Int + @objc public var videoPort: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringInfo : ObjectiveC.NSObject { + @objc public var audioInfo: LGCast.LGCastMirroringAudioInfo? + @objc public var videoInfo: LGCast.LGCastMirroringVideoInfo? + @objc public var playerInfo: LGCast.LGCastMirroringPlayerInfo? + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastCameraParameterInfo : ObjectiveC.NSObject { + @objc public var audio: LGCast.LGCastCamperaPropertyStatus + @objc public var autoWhiteBalance: LGCast.LGCastCamperaPropertyStatus + @objc public var brightness: Swift.Int + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc public var facing: LGCast.LGCastCameraPosition + @objc public var whiteBalance: Swift.Int + @objc public var rotation: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@objc public protocol LGCastCameraApiDelegate { + @objc func lgcastCameraDidPlay() + @objc func lgcastCameraDidChange(property: LGCast.LGCastCameraProperty) + @objc func lgcastCameraErrorDidOccur(error: LGCast.LGCastCameraError) +} +@_inheritsConvenienceInitializers @objc public class LGCastCameraResolutionInfo : ObjectiveC.NSObject { + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@objc @_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers public class LGCastCameraApi : ObjectiveC.NSObject { + @objc public static let KEY_CAMERA_POSITION: Swift.String + @objc public static let KEY_PREVIEW_VIDEO_GRAVITY: Swift.String + @objc public static let KEY_IS_SUPPORT_LANDSCAPE_MODE: Swift.String + @objc public static let shared: LGCast.LGCastCameraApi + @objc weak public var delegate: LGCast.LGCastCameraApiDelegate? + @objc public var isRunning: Swift.Bool { + get + } + public var isTvInputEnabled: Swift.Bool { + get + } + @objc public func isSupportedOsVersion() -> Swift.Bool + @objc public func createCameraPreviewView(_ options: [Swift.String : Any]? = nil) -> UIKit.UIView + @objc public func setPreviewView(_ view: LGCast.LGCastCameraPreviewView, options: [Swift.String : Any]? = nil) + @objc public func generateCameraMasterKey(_ publicKey: Swift.String) -> Swift.Array? + @objc public func getCameraProperties() -> LGCast.LGCastCameraParameterInfo? + @objc public func setCameraProperties(property: LGCast.LGCastCameraProperty, value: Swift.Int) -> Swift.Bool + @objc public func startRemoteCamera(_ settings: LGCast.LGCastDeviceSettings) + @objc public func stopRemoteCamera() + @objc public func getSupportedResolutions() -> [LGCast.LGCastCameraResolutionInfo] + @objc public func getCurrentResolution() -> LGCast.LGCastCameraResolutionInfo + @objc public func setResolution(_ resolution: LGCast.LGCastCameraResolutionInfo) -> Swift.Bool + @objc public func changeCameraPosition(_ position: LGCast.LGCastCameraPosition) -> Swift.Bool + @objc public func muteMicrophone(_ isMuted: Swift.Bool) -> Swift.Bool + @objc deinit +} +@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) public class LGCastCameraPreviewView : UIKit.UIView { + @_Concurrency.MainActor(unsafe) @objc override dynamic public class var layerClass: Swift.AnyClass { + @objc get + } + @_Concurrency.MainActor(unsafe) @objc override dynamic public init(frame: CoreFoundation.CGRect) + @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder: Foundation.NSCoder) + @objc deinit +} +@objc public enum LGCastCameraProperty : Swift.Int { + case audio + case autoWhiteBalance + case brightness + case facing + case resolution + case whitebalance + case rotation + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastCameraPosition : Swift.Int { + case front = 0 + case back = 1 + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastCameraPreviewVideoGravity : Swift.Int { + case resize + case resizeAspect + case resizeAspectFill + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastCamperaPropertyStatus : Swift.Int { + case disable = 0 + case enable = 1 + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastCameraResolution : Swift.Int { + case low + case medium + case high + case cif352x288 + case vga640x480 + case hd1280x720 + case hd1920x1080 + case hd4K3840x2160 + case iFrame960x540 + case iFrame1280x720 + public init(width: Swift.Int, height: Swift.Int) + public var description: Swift.String { + get + } + public func getSize() -> (width: Swift.Int, height: Swift.Int) + public func getWidth() -> Swift.Int + public func getHeight() -> Swift.Int + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@propertyWrapper public struct LGCastUserDefaults { + public var wrappedValue: T { + get + set + } +} +public protocol AudioEncoderDelegate : AnyObject { + func audioEncoder(_ encoder: LGCast.AudioEncoder, didPrepare result: Swift.Bool) + func audioEncoder(_ encoder: LGCast.AudioEncoder, didStart result: Swift.Bool, startPts: CoreMedia.CMTime, startTime: Dispatch.DispatchTime) + func audioEncoder(_ encoder: LGCast.AudioEncoder, with type: LGCast.MTAudioType, didEncode data: Foundation.Data, pts: CoreMedia.CMTime, priority: Swift.Int) +} +@_hasMissingDesignatedInitializers public class AudioEncoder { + public func prepareAudioEncoder(_ sampleBuffer: CoreMedia.CMSampleBuffer) + public func processAudioSampleBuffer(_ sampleBuffer: CoreMedia.CMSampleBuffer, baseTime: CoreMedia.CMTime) + @objc deinit +} +extension CoreAudioTypes.AudioStreamBasicDescription : Swift.Equatable { + public static func == (lhs: CoreAudioTypes.AudioStreamBasicDescription, rhs: CoreAudioTypes.AudioStreamBasicDescription) -> Swift.Bool +} +public enum RTPStreamerMediaType : Swift.Int32 { + case VIDEO + case AUDIO + case AV + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +public enum RTPStreamerRscType : Swift.Int32 { + case APP + case FILE + case SOCKET + case CAMERA + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +public enum RTPStreamerSRTPAuthType : Swift.Int32 { + case NONE + case HMAC_SHA1_32 + case HMAC_SHA1_80 + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +public enum RTPStreamerSRTPCipherType : Swift.Int32 { + case NONE + case AES_128_ICM + case AES_256_ICM + case AES_128_GCM + case AES_256_GCM + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +@_hasMissingDesignatedInitializers public class MTRTPPipeline { + public static func getPipeline(_ ssrc: Swift.UInt32, info: LGCast.MTMediaInfo, isSecure: Swift.Bool) -> Swift.String? + @objc deinit +} +@objc @_inheritsConvenienceInitializers public class MTRTPSecurity : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +@objc @_inheritsConvenienceInitializers public class MTRTPSecurityKey : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +public enum MTMediaType : Swift.Int32 { + case video + case audio + case av + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +@objc @_inheritsConvenienceInitializers public class MTMediaInfo : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +@objc public enum MTVideoType : Swift.Int32 { + case raw + case h264 + case mjpeg + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +@objc @_inheritsConvenienceInitializers public class MTVideoInfo : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +@objc public enum MTAudioType : Swift.Int32 { + case pcm + case aac + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +@objc @_inheritsConvenienceInitializers public class MTAudioInfo : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +@objc @_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers public class Log : ObjectiveC.NSObject { + public static func debug(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + public static func info(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + public static func notice(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + public static func error(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + public static func fault(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + @objc deinit +} +extension UIKit.UIDevice { + @_Concurrency.MainActor(unsafe) public var isPhoneDevice: Swift.Bool { + get + } + @_Concurrency.MainActor(unsafe) public var isPadDevice: Swift.Bool { + get + } + @_Concurrency.MainActor(unsafe) public var modelName: Swift.String { + get + } +} +extension LGCast.Log { + @objc public static func debugLGCast(_ message: Swift.String) + @objc public static func infoLGCast(_ message: Swift.String) + @objc public static func noticeLGCast(_ message: Swift.String) + @objc public static func errorLGCast(_ message: Swift.String) + @objc public static func faultLGCast(_ message: Swift.String) +} +extension LGCast.LGCastCameraError : Swift.Equatable {} +extension LGCast.LGCastCameraError : Swift.Hashable {} +extension LGCast.LGCastCameraError : Swift.RawRepresentable {} +extension LGCast.LGCastMirroringEvent : Swift.Equatable {} +extension LGCast.LGCastMirroringEvent : Swift.Hashable {} +extension LGCast.LGCastMirroringEvent : Swift.RawRepresentable {} +extension LGCast.LGCastMirroringError : Swift.Equatable {} +extension LGCast.LGCastMirroringError : Swift.Hashable {} +extension LGCast.LGCastMirroringError : Swift.RawRepresentable {} +extension LGCast.LGCastCameraProperty : Swift.Equatable {} +extension LGCast.LGCastCameraProperty : Swift.Hashable {} +extension LGCast.LGCastCameraProperty : Swift.RawRepresentable {} +extension LGCast.LGCastCameraPosition : Swift.Equatable {} +extension LGCast.LGCastCameraPosition : Swift.Hashable {} +extension LGCast.LGCastCameraPosition : Swift.RawRepresentable {} +extension LGCast.LGCastCameraPreviewVideoGravity : Swift.Equatable {} +extension LGCast.LGCastCameraPreviewVideoGravity : Swift.Hashable {} +extension LGCast.LGCastCameraPreviewVideoGravity : Swift.RawRepresentable {} +extension LGCast.LGCastCamperaPropertyStatus : Swift.Equatable {} +extension LGCast.LGCastCamperaPropertyStatus : Swift.Hashable {} +extension LGCast.LGCastCamperaPropertyStatus : Swift.RawRepresentable {} +extension LGCast.LGCastCameraResolution : Swift.Equatable {} +extension LGCast.LGCastCameraResolution : Swift.Hashable {} +extension LGCast.LGCastCameraResolution : Swift.RawRepresentable {} +extension LGCast.RTPStreamerMediaType : Swift.Equatable {} +extension LGCast.RTPStreamerMediaType : Swift.Hashable {} +extension LGCast.RTPStreamerMediaType : Swift.RawRepresentable {} +extension LGCast.RTPStreamerRscType : Swift.Equatable {} +extension LGCast.RTPStreamerRscType : Swift.Hashable {} +extension LGCast.RTPStreamerRscType : Swift.RawRepresentable {} +extension LGCast.RTPStreamerSRTPAuthType : Swift.Equatable {} +extension LGCast.RTPStreamerSRTPAuthType : Swift.Hashable {} +extension LGCast.RTPStreamerSRTPAuthType : Swift.RawRepresentable {} +extension LGCast.RTPStreamerSRTPCipherType : Swift.Equatable {} +extension LGCast.RTPStreamerSRTPCipherType : Swift.Hashable {} +extension LGCast.RTPStreamerSRTPCipherType : Swift.RawRepresentable {} +extension LGCast.MTMediaType : Swift.Equatable {} +extension LGCast.MTMediaType : Swift.Hashable {} +extension LGCast.MTMediaType : Swift.RawRepresentable {} +extension LGCast.MTVideoType : Swift.Equatable {} +extension LGCast.MTVideoType : Swift.Hashable {} +extension LGCast.MTVideoType : Swift.RawRepresentable {} +extension LGCast.MTAudioType : Swift.Equatable {} +extension LGCast.MTAudioType : Swift.Hashable {} +extension LGCast.MTAudioType : Swift.RawRepresentable {} diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Modules/module.modulemap b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Modules/module.modulemap new file mode 100644 index 00000000..e109f7d4 --- /dev/null +++ b/core/Frameworks/LGCast/LGCast.xcframework/ios-arm64/LGCast.framework/Modules/module.modulemap @@ -0,0 +1,11 @@ +framework module LGCast { + umbrella header "LGCast.h" + + export * + module * { export * } +} + +module LGCast.Swift { + header "LGCast-Swift.h" + requires objc +} diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Headers/LGCast-Swift.h b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Headers/LGCast-Swift.h new file mode 100644 index 00000000..44e947b7 --- /dev/null +++ b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Headers/LGCast-Swift.h @@ -0,0 +1,696 @@ +#if 0 +#elif defined(__x86_64__) && __x86_64__ +// Generated by Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51) +#ifndef LGCAST_SWIFT_H +#define LGCAST_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wduplicate-method-match" +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#else +#include +#include +#include +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif + +#if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +#else +# define SWIFT_RUNTIME_NAME(X) +#endif +#if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +#else +# define SWIFT_COMPILE_NAME(X) +#endif +#if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +#else +# define SWIFT_METHOD_FAMILY(X) +#endif +#if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +#else +# define SWIFT_NOESCAPE +#endif +#if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +#else +# define SWIFT_RELEASES_ARGUMENT +#endif +#if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +# define SWIFT_WARN_UNUSED_RESULT +#endif +#if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +#else +# define SWIFT_NORETURN +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif + +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif + +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif + +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if defined(__has_attribute) && __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +#else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if defined(__cplusplus) +#if !defined(SWIFT_NOEXCEPT) +# define SWIFT_NOEXCEPT noexcept +#endif +#else +#if !defined(SWIFT_NOEXCEPT) +# define SWIFT_NOEXCEPT +#endif +#endif +#if defined(__cplusplus) +#if !defined(SWIFT_CXX_INT_DEFINED) +#define SWIFT_CXX_INT_DEFINED +namespace swift { +using Int = ptrdiff_t; +using UInt = size_t; +} +#endif +#endif +#if defined(__OBJC__) +#if __has_feature(modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +@import AVFoundation; +@import CoreFoundation; +@import CoreMedia; +@import Foundation; +@import GStreamerForLGCast; +@import ObjectiveC; +@import ReplayKit; +@import UIKit; +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="LGCast",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) + +SWIFT_CLASS("_TtC6LGCast12CameraDevice") +@interface CameraDevice : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +@class AVCaptureOutput; +@class AVCaptureConnection; + +@interface CameraDevice (SWIFT_EXTENSION(LGCast)) +- (void)captureOutput:(AVCaptureOutput * _Nonnull)output didOutputSampleBuffer:(CMSampleBufferRef _Nonnull)sampleBuffer fromConnection:(AVCaptureConnection * _Nonnull)connection; +@end + + +SWIFT_CLASS("_TtC6LGCast13CameraService") +@interface CameraService : NSObject +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +@end + + + + +@class NSString; +@protocol LGCastCameraApiDelegate; +@class UIView; +@class LGCastCameraPreviewView; +@class LGCastSecurityKey; +@class LGCastCameraParameterInfo; +enum LGCastCameraProperty : NSInteger; +@class LGCastDeviceSettings; +@class LGCastCameraResolutionInfo; +enum LGCastCameraPosition : NSInteger; + +SWIFT_CLASS("_TtC6LGCast15LGCastCameraApi") +@interface LGCastCameraApi : NSObject +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSString * _Nonnull KEY_CAMERA_POSITION;) ++ (NSString * _Nonnull)KEY_CAMERA_POSITION SWIFT_WARN_UNUSED_RESULT; +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSString * _Nonnull KEY_PREVIEW_VIDEO_GRAVITY;) ++ (NSString * _Nonnull)KEY_PREVIEW_VIDEO_GRAVITY SWIFT_WARN_UNUSED_RESULT; +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSString * _Nonnull KEY_IS_SUPPORT_LANDSCAPE_MODE;) ++ (NSString * _Nonnull)KEY_IS_SUPPORT_LANDSCAPE_MODE SWIFT_WARN_UNUSED_RESULT; +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) LGCastCameraApi * _Nonnull shared;) ++ (LGCastCameraApi * _Nonnull)shared SWIFT_WARN_UNUSED_RESULT; +@property (nonatomic, weak) id _Nullable delegate; +@property (nonatomic, readonly) BOOL isRunning; +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +- (BOOL)isSupportedOsVersion SWIFT_WARN_UNUSED_RESULT; +- (UIView * _Nonnull)createCameraPreviewView:(NSDictionary * _Nullable)options SWIFT_WARN_UNUSED_RESULT; +- (void)setPreviewView:(LGCastCameraPreviewView * _Nonnull)view options:(NSDictionary * _Nullable)options; +- (NSArray * _Nullable)generateCameraMasterKey:(NSString * _Nonnull)publicKey SWIFT_WARN_UNUSED_RESULT; +- (LGCastCameraParameterInfo * _Nullable)getCameraProperties SWIFT_WARN_UNUSED_RESULT; +- (BOOL)setCameraPropertiesWithProperty:(enum LGCastCameraProperty)property value:(NSInteger)value SWIFT_WARN_UNUSED_RESULT; +- (void)startRemoteCamera:(LGCastDeviceSettings * _Nonnull)settings; +- (void)stopRemoteCamera; +- (NSArray * _Nonnull)getSupportedResolutions SWIFT_WARN_UNUSED_RESULT; +- (LGCastCameraResolutionInfo * _Nonnull)getCurrentResolution SWIFT_WARN_UNUSED_RESULT; +- (BOOL)setResolution:(LGCastCameraResolutionInfo * _Nonnull)resolution SWIFT_WARN_UNUSED_RESULT; +- (BOOL)changeCameraPosition:(enum LGCastCameraPosition)position SWIFT_WARN_UNUSED_RESULT; +- (BOOL)muteMicrophone:(BOOL)isMuted SWIFT_WARN_UNUSED_RESULT; +@end + + +enum LGCastCameraError : NSInteger; + +SWIFT_PROTOCOL("_TtP6LGCast23LGCastCameraApiDelegate_") +@protocol LGCastCameraApiDelegate +- (void)lgcastCameraDidPlay; +- (void)lgcastCameraDidChangeWithProperty:(enum LGCastCameraProperty)property; +- (void)lgcastCameraErrorDidOccurWithError:(enum LGCastCameraError)error; +@end + +typedef SWIFT_ENUM(NSInteger, LGCastCameraError, open) { + LGCastCameraErrorUnknown = 0, + LGCastCameraErrorConnectionClosed = 1, + LGCastCameraErrorDeviceShutdown = 2, + LGCastCameraErrorRendererTerminated = 3, +}; + +enum LGCastCamperaPropertyStatus : NSInteger; + +SWIFT_CLASS("_TtC6LGCast25LGCastCameraParameterInfo") +@interface LGCastCameraParameterInfo : NSObject +@property (nonatomic) enum LGCastCamperaPropertyStatus audio; +@property (nonatomic) enum LGCastCamperaPropertyStatus autoWhiteBalance; +@property (nonatomic) NSInteger brightness; +@property (nonatomic) NSInteger width; +@property (nonatomic) NSInteger height; +@property (nonatomic) enum LGCastCameraPosition facing; +@property (nonatomic) NSInteger whiteBalance; +@property (nonatomic) NSInteger rotation; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +typedef SWIFT_ENUM(NSInteger, LGCastCameraPosition, open) { + LGCastCameraPositionFront = 0, + LGCastCameraPositionBack = 1, +}; + +typedef SWIFT_ENUM(NSInteger, LGCastCameraPreviewVideoGravity, open) { + LGCastCameraPreviewVideoGravityResize = 0, + LGCastCameraPreviewVideoGravityResizeAspect = 1, + LGCastCameraPreviewVideoGravityResizeAspectFill = 2, +}; + +@class NSCoder; + +SWIFT_CLASS("_TtC6LGCast23LGCastCameraPreviewView") +@interface LGCastCameraPreviewView : UIView +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly) Class _Nonnull layerClass;) ++ (Class _Nonnull)layerClass SWIFT_WARN_UNUSED_RESULT; +- (nonnull instancetype)initWithFrame:(CGRect)frame OBJC_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)coder OBJC_DESIGNATED_INITIALIZER; +@end + +typedef SWIFT_ENUM(NSInteger, LGCastCameraProperty, open) { + LGCastCameraPropertyAudio = 0, + LGCastCameraPropertyAutoWhiteBalance = 1, + LGCastCameraPropertyBrightness = 2, + LGCastCameraPropertyFacing = 3, + LGCastCameraPropertyResolution = 4, + LGCastCameraPropertyWhitebalance = 5, + LGCastCameraPropertyRotation = 6, +}; + +typedef SWIFT_ENUM(NSInteger, LGCastCameraResolution, open) { + LGCastCameraResolutionLow = 0, + LGCastCameraResolutionMedium = 1, + LGCastCameraResolutionHigh = 2, + LGCastCameraResolutionCif352x288 = 3, + LGCastCameraResolutionVga640x480 = 4, + LGCastCameraResolutionHd1280x720 = 5, + LGCastCameraResolutionHd1920x1080 = 6, + LGCastCameraResolutionHd4K3840x2160 = 7, + LGCastCameraResolutionIFrame960x540 = 8, + LGCastCameraResolutionIFrame1280x720 = 9, +}; + + +SWIFT_CLASS("_TtC6LGCast26LGCastCameraResolutionInfo") +@interface LGCastCameraResolutionInfo : NSObject +@property (nonatomic) NSInteger width; +@property (nonatomic) NSInteger height; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +typedef SWIFT_ENUM(NSInteger, LGCastCamperaPropertyStatus, open) { + LGCastCamperaPropertyStatusDisable = 0, + LGCastCamperaPropertyStatusEnable = 1, +}; + + +SWIFT_CLASS("_TtC6LGCast20LGCastDeviceSettings") +@interface LGCastDeviceSettings : NSObject +@property (nonatomic, copy) NSString * _Nullable host; +@property (nonatomic) NSInteger audioPort; +@property (nonatomic) NSInteger videoPort; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +@protocol LGCastMirroringApiDelegate; +@class LGCastMirroringMediaSettings; +@class LGCastMirroringInfo; + +SWIFT_CLASS("_TtC6LGCast18LGCastMirroringApi") +@interface LGCastMirroringApi : NSObject +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) LGCastMirroringApi * _Nonnull shared;) ++ (LGCastMirroringApi * _Nonnull)shared SWIFT_WARN_UNUSED_RESULT; +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +- (BOOL)isSupportedOsVersion SWIFT_WARN_UNUSED_RESULT; +- (void)setDelegate:(id _Nonnull)delegate; +- (LGCastMirroringInfo * _Nullable)setMediaSettings:(LGCastMirroringMediaSettings * _Nullable)mediaInfo SWIFT_WARN_UNUSED_RESULT; +- (NSArray * _Nullable)generateMirroringMasterKey:(NSString * _Nonnull)publicKey SWIFT_WARN_UNUSED_RESULT; +- (void)startMirroring:(LGCastDeviceSettings * _Nullable)settings; +- (LGCastMirroringInfo * _Nullable)updateDisplayOrientationWithIsPortraitMode:(BOOL)isPortraitMode SWIFT_WARN_UNUSED_RESULT; +- (void)stopMirroring; +- (void)pushSampleBuffer:(CMSampleBufferRef _Nonnull)sampleBuffer with:(RPSampleBufferType)sampleBufferType; +@end + + +enum LGCastMirroringError : NSInteger; +enum LGCastMirroringEvent : NSInteger; + +SWIFT_PROTOCOL("_TtP6LGCast26LGCastMirroringApiDelegate_") +@protocol LGCastMirroringApiDelegate +- (void)lgcastMirroringDidStartWithResult:(BOOL)result; +- (void)lgcastMirroringDidStopWithResult:(BOOL)result; +- (void)lgcastMirroringErrorDidOccurWithError:(enum LGCastMirroringError)error; +- (void)lgcastMirroringUpdateEventWithEvent:(enum LGCastMirroringEvent)event info:(LGCastMirroringInfo * _Nullable)info; +@end + +enum MTAudioType : int32_t; +@class NSData; + +SWIFT_CLASS("_TtC6LGCast24LGCastMirroringAudioInfo") +@interface LGCastMirroringAudioInfo : NSObject +@property (nonatomic) enum MTAudioType type; +@property (nonatomic, readonly, copy) NSString * _Nonnull codec; +@property (nonatomic) NSInteger samplingRate; +@property (nonatomic) NSInteger channelCnt; +@property (nonatomic) NSInteger bitrate; +@property (nonatomic, copy) NSString * _Nonnull streamMuxConfig; +@property (nonatomic, copy) NSData * _Nullable codecData; +@property (nonatomic, copy) NSString * _Nullable host; +@property (nonatomic) NSInteger port; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast28LGCastMirroringAudioSettings") +@interface LGCastMirroringAudioSettings : NSObject +@property (nonatomic) NSInteger sampleRate; +@property (nonatomic) NSInteger bitrate; +@property (nonatomic) NSInteger channelCnt; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +typedef SWIFT_ENUM(NSInteger, LGCastMirroringError, open) { + LGCastMirroringErrorUnknown = 0, + LGCastMirroringErrorConnectionClosed = 1, + LGCastMirroringErrorDeviceShutdown = 2, + LGCastMirroringErrorRendererTerminated = 3, +}; + +typedef SWIFT_ENUM(NSInteger, LGCastMirroringEvent, open) { + LGCastMirroringEventUnknown = 0, + LGCastMirroringEventUpdateVideoInfo = 1, + LGCastMirroringEventUpdateVideoVideoSize = 2, + LGCastMirroringEventUpdateAudioInfo = 3, + LGCastMirroringEventUpdatePlayerInfo = 4, +}; + +@class LGCastMirroringVideoInfo; +@class LGCastMirroringPlayerInfo; + +SWIFT_CLASS("_TtC6LGCast19LGCastMirroringInfo") +@interface LGCastMirroringInfo : NSObject +@property (nonatomic, strong) LGCastMirroringAudioInfo * _Nullable audioInfo; +@property (nonatomic, strong) LGCastMirroringVideoInfo * _Nullable videoInfo; +@property (nonatomic, strong) LGCastMirroringPlayerInfo * _Nullable playerInfo; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +@class LGCastMirroringVideoSettings; + +SWIFT_CLASS("_TtC6LGCast28LGCastMirroringMediaSettings") +@interface LGCastMirroringMediaSettings : NSObject +@property (nonatomic, strong) LGCastMirroringAudioSettings * _Nullable audio; +@property (nonatomic, strong) LGCastMirroringVideoSettings * _Nullable video; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast25LGCastMirroringPlayerInfo") +@interface LGCastMirroringPlayerInfo : NSObject +@property (nonatomic) NSInteger width; +@property (nonatomic) NSInteger height; +@property (nonatomic) BOOL isPortraitMode; +@property (nonatomic, copy) NSString * _Nonnull ipAddress; +@property (nonatomic) NSInteger audioPort; +@property (nonatomic) NSInteger videoPort; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +enum MTVideoType : int32_t; + +SWIFT_CLASS("_TtC6LGCast24LGCastMirroringVideoInfo") +@interface LGCastMirroringVideoInfo : NSObject +@property (nonatomic) enum MTVideoType type; +@property (nonatomic, readonly, copy) NSString * _Nonnull codec; +@property (nonatomic) NSInteger width; +@property (nonatomic) NSInteger height; +@property (nonatomic) NSInteger activeWidth; +@property (nonatomic) NSInteger activeHeight; +@property (nonatomic) NSInteger framerate; +@property (nonatomic) NSInteger samplingRate; +@property (nonatomic) NSInteger bitrate; +@property (nonatomic, copy) NSString * _Nonnull supportOrientation; +@property (nonatomic) BOOL isPortraitMode; +@property (nonatomic, copy) NSString * _Nonnull screenOrientation; +@property (nonatomic, copy) NSString * _Nullable streamMuxConfig; +@property (nonatomic, copy) NSData * _Nullable codecData; +@property (nonatomic, copy) NSString * _Nullable host; +@property (nonatomic) NSInteger port; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast28LGCastMirroringVideoSettings") +@interface LGCastMirroringVideoSettings : NSObject +@property (nonatomic) NSInteger width; +@property (nonatomic) NSInteger height; +@property (nonatomic) BOOL isPortraitMode; +@property (nonatomic) NSInteger framerate; +@property (nonatomic) NSInteger bitrate; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast17LGCastSecurityKey") +@interface LGCastSecurityKey : NSObject +@property (nonatomic, copy) NSString * _Nullable masterKey; +@property (nonatomic, copy) NSString * _Nullable mki; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast3Log") +@interface Log : NSObject +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +@end + +typedef SWIFT_ENUM(NSInteger, LogLevel, closed) { + LogLevelDebug = 0, + LogLevelInfo = 1, + LogLevelNotice = 2, + LogLevelError = 3, + LogLevelFault = 4, +}; + + +@interface Log (SWIFT_EXTENSION(LGCast)) ++ (void)debugLGCast:(NSString * _Nonnull)message; ++ (void)infoLGCast:(NSString * _Nonnull)message; ++ (void)noticeLGCast:(NSString * _Nonnull)message; ++ (void)errorLGCast:(NSString * _Nonnull)message; ++ (void)faultLGCast:(NSString * _Nonnull)message; +@end + + + +SWIFT_CLASS("_TtC6LGCast11MTAudioInfo") +@interface MTAudioInfo : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +typedef SWIFT_ENUM(int32_t, MTAudioType, open) { + MTAudioTypePcm = 0, + MTAudioTypeAac = 1, +}; + + +SWIFT_CLASS("_TtC6LGCast11MTMediaInfo") +@interface MTMediaInfo : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast13MTRTPSecurity") +@interface MTRTPSecurity : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast16MTRTPSecurityKey") +@interface MTRTPSecurityKey : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast11MTVideoInfo") +@interface MTVideoInfo : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +typedef SWIFT_ENUM(int32_t, MTVideoType, open) { + MTVideoTypeRaw = 0, + MTVideoTypeH264 = 1, + MTVideoTypeMjpeg = 2, +}; + + +SWIFT_CLASS("_TtC6LGCast16MirroringService") +@interface MirroringService : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + + + +SWIFT_CLASS("_TtC6LGCast15MirroringVolume") +@interface MirroringVolume : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast11RTPStreamer") +@interface RTPStreamer : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +@interface RTPStreamer (SWIFT_EXTENSION(LGCast)) +- (void)gstreamerDidInitialize; +- (void)gstreamerDidSendMessage:(NSString * _Null_unspecified)message; +@end + + +SWIFT_CLASS("_TtC6LGCast16RTPStreamerUtils") +@interface RTPStreamerUtils : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + +SWIFT_CLASS("_TtC6LGCast12RTPStreaming") +@interface RTPStreaming : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + + + + +SWIFT_CLASS("_TtC6LGCast12VideoEncoder") +@interface VideoEncoder : NSObject +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +@end + + + + +#endif +#if defined(__cplusplus) +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#pragma clang diagnostic pop +#endif + +#else +#error unsupported Swift architecture +#endif diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Headers/LGCast.h b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Headers/LGCast.h new file mode 100644 index 00000000..ebae1022 --- /dev/null +++ b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Headers/LGCast.h @@ -0,0 +1,19 @@ +// +// LGCast.h +// LGCast +// +// Created by DeviceSynergyTask on 2021/04/21. +// + +#import + +#import +#import + +//! Project version number for LGCast. +FOUNDATION_EXPORT double LGCastSDKVersionNumber; + +//! Project version string for LGCast. +FOUNDATION_EXPORT const unsigned char LGCastSDKVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Info.plist b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Info.plist new file mode 100644 index 00000000..332a7a92 Binary files /dev/null and b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Info.plist differ diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/LGCast b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/LGCast new file mode 100755 index 00000000..4ed113c0 Binary files /dev/null and b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/LGCast differ diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.abi.json b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.abi.json new file mode 100644 index 00000000..aa7a183b --- /dev/null +++ b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.abi.json @@ -0,0 +1,17725 @@ +{ + "ABIRoot": { + "kind": "Root", + "name": "TopLevel", + "printedName": "TopLevel", + "children": [ + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraError", + "printedName": "LGCastCameraError", + "children": [ + { + "kind": "Var", + "name": "unknown", + "printedName": "unknown", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraError.Type) -> LGCast.LGCastCameraError", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraError.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraError@LGCastCameraErrorUnknown", + "mangledName": "$s6LGCast0A11CameraErrorO7unknownyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "connectionClosed", + "printedName": "connectionClosed", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraError.Type) -> LGCast.LGCastCameraError", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraError.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraError@LGCastCameraErrorConnectionClosed", + "mangledName": "$s6LGCast0A11CameraErrorO16connectionClosedyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Var", + "name": "deviceShutdown", + "printedName": "deviceShutdown", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraError.Type) -> LGCast.LGCastCameraError", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraError.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraError@LGCastCameraErrorDeviceShutdown", + "mangledName": "$s6LGCast0A11CameraErrorO14deviceShutdownyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 2 + }, + { + "kind": "Var", + "name": "rendererTerminated", + "printedName": "rendererTerminated", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraError.Type) -> LGCast.LGCastCameraError", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraError.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraError@LGCastCameraErrorRendererTerminated", + "mangledName": "$s6LGCast0A11CameraErrorO18rendererTerminatedyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 3 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraError?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A11CameraErrorO8rawValueACSgSi_tcfc", + "mangledName": "$s6LGCast0A11CameraErrorO8rawValueACSgSi_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A11CameraErrorO8rawValueSivp", + "mangledName": "$s6LGCast0A11CameraErrorO8rawValueSivp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A11CameraErrorO8rawValueSivg", + "mangledName": "$s6LGCast0A11CameraErrorO8rawValueSivg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@LGCastCameraError", + "mangledName": "$s6LGCast0A11CameraErrorO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "ReplayKit", + "printedName": "ReplayKit", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringEvent", + "printedName": "LGCastMirroringEvent", + "children": [ + { + "kind": "Var", + "name": "unknown", + "printedName": "unknown", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringEvent.Type) -> LGCast.LGCastMirroringEvent", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringEvent.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent@LGCastMirroringEventUnknown", + "mangledName": "$s6LGCast0A14MirroringEventO7unknownyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "updateVideoInfo", + "printedName": "updateVideoInfo", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringEvent.Type) -> LGCast.LGCastMirroringEvent", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringEvent.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent@LGCastMirroringEventUpdateVideoInfo", + "mangledName": "$s6LGCast0A14MirroringEventO15updateVideoInfoyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Var", + "name": "updateVideoVideoSize", + "printedName": "updateVideoVideoSize", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringEvent.Type) -> LGCast.LGCastMirroringEvent", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringEvent.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent@LGCastMirroringEventUpdateVideoVideoSize", + "mangledName": "$s6LGCast0A14MirroringEventO011updateVideoE4SizeyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 2 + }, + { + "kind": "Var", + "name": "updateAudioInfo", + "printedName": "updateAudioInfo", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringEvent.Type) -> LGCast.LGCastMirroringEvent", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringEvent.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent@LGCastMirroringEventUpdateAudioInfo", + "mangledName": "$s6LGCast0A14MirroringEventO15updateAudioInfoyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 3 + }, + { + "kind": "Var", + "name": "updatePlayerInfo", + "printedName": "updatePlayerInfo", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringEvent.Type) -> LGCast.LGCastMirroringEvent", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringEvent.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent@LGCastMirroringEventUpdatePlayerInfo", + "mangledName": "$s6LGCast0A14MirroringEventO16updatePlayerInfoyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 4 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringEvent?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A14MirroringEventO8rawValueACSgSi_tcfc", + "mangledName": "$s6LGCast0A14MirroringEventO8rawValueACSgSi_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A14MirroringEventO8rawValueSivp", + "mangledName": "$s6LGCast0A14MirroringEventO8rawValueSivp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A14MirroringEventO8rawValueSivg", + "mangledName": "$s6LGCast0A14MirroringEventO8rawValueSivg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent", + "mangledName": "$s6LGCast0A14MirroringEventO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringApiDelegate", + "printedName": "LGCastMirroringApiDelegate", + "children": [ + { + "kind": "Function", + "name": "lgcastMirroringDidStart", + "printedName": "lgcastMirroringDidStart(result:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(pl)LGCastMirroringApiDelegate(im)lgcastMirroringDidStartWithResult:", + "mangledName": "$s6LGCast0A20MirroringApiDelegateP06lgcastB8DidStart6resultySb_tF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.LGCastMirroringApiDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "declAttributes": [ + "ObjC" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "lgcastMirroringDidStop", + "printedName": "lgcastMirroringDidStop(result:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(pl)LGCastMirroringApiDelegate(im)lgcastMirroringDidStopWithResult:", + "mangledName": "$s6LGCast0A20MirroringApiDelegateP06lgcastB7DidStop6resultySb_tF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.LGCastMirroringApiDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "declAttributes": [ + "ObjC" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "lgcastMirroringErrorDidOccur", + "printedName": "lgcastMirroringErrorDidOccur(error:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(pl)LGCastMirroringApiDelegate(im)lgcastMirroringErrorDidOccurWithError:", + "mangledName": "$s6LGCast0A20MirroringApiDelegateP06lgcastB13ErrorDidOccur5erroryAA0abF0O_tF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.LGCastMirroringApiDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "declAttributes": [ + "ObjC" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "lgcastMirroringUpdateEvent", + "printedName": "lgcastMirroringUpdateEvent(event:info:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastMirroringEvent", + "printedName": "LGCast.LGCastMirroringEvent", + "usr": "c:@M@LGCast@E@LGCastMirroringEvent" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringInfo", + "printedName": "LGCast.LGCastMirroringInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(pl)LGCastMirroringApiDelegate(im)lgcastMirroringUpdateEventWithEvent:info:", + "mangledName": "$s6LGCast0A20MirroringApiDelegateP06lgcastB11UpdateEvent5event4infoyAA0abG0O_AA0aB4InfoCSgtF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.LGCastMirroringApiDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "declAttributes": [ + "ObjC" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@LGCast@objc(pl)LGCastMirroringApiDelegate", + "mangledName": "$s6LGCast0A20MirroringApiDelegateP", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 : AnyObject>", + "sugared_genericSig": "", + "declAttributes": [ + "AccessControl", + "ObjC" + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringApi", + "printedName": "LGCastMirroringApi", + "children": [ + { + "kind": "Var", + "name": "shared", + "printedName": "shared", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringApi", + "printedName": "LGCast.LGCastMirroringApi", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(cpy)shared", + "mangledName": "$s6LGCast0A12MirroringApiC6sharedACvpZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "HasInitialValue", + "Final", + "HasStorage", + "AccessControl", + "ObjC" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringApi", + "printedName": "LGCast.LGCastMirroringApi", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(cm)shared", + "mangledName": "$s6LGCast0A12MirroringApiC6sharedACvgZ", + "moduleName": "LGCast", + "static": true, + "implicit": true, + "declAttributes": [ + "Final", + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "isRunning", + "printedName": "isRunning", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A12MirroringApiC9isRunningSbvp", + "mangledName": "$s6LGCast0A12MirroringApiC9isRunningSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "SetterAccess", + "AccessControl" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A12MirroringApiC9isRunningSbvg", + "mangledName": "$s6LGCast0A12MirroringApiC9isRunningSbvg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "isTvInputEnabled", + "printedName": "isTvInputEnabled", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A12MirroringApiC16isTvInputEnabledSbvp", + "mangledName": "$s6LGCast0A12MirroringApiC16isTvInputEnabledSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A12MirroringApiC16isTvInputEnabledSbvg", + "mangledName": "$s6LGCast0A12MirroringApiC16isTvInputEnabledSbvg", + "moduleName": "LGCast", + "accessorKind": "get" + } + ] + }, + { + "kind": "Function", + "name": "isSupportedOsVersion", + "printedName": "isSupportedOsVersion()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(im)isSupportedOsVersion", + "mangledName": "$s6LGCast0A12MirroringApiC20isSupportedOsVersionSbyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "setDelegate", + "printedName": "setDelegate(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastMirroringApiDelegate", + "printedName": "LGCast.LGCastMirroringApiDelegate", + "usr": "c:@M@LGCast@objc(pl)LGCastMirroringApiDelegate" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(im)setDelegate:", + "mangledName": "$s6LGCast0A12MirroringApiC11setDelegateyyAA0abcE0_pF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "setMediaSettings", + "printedName": "setMediaSettings(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringInfo", + "printedName": "LGCast.LGCastMirroringInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringMediaSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringMediaSettings", + "printedName": "LGCast.LGCastMirroringMediaSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(im)setMediaSettings:", + "mangledName": "$s6LGCast0A12MirroringApiC16setMediaSettingsyAA0aB4InfoCSgAA0abeF0CSgF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "generateMirroringMasterKey", + "printedName": "generateMirroringMasterKey(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[LGCast.LGCastSecurityKey]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[LGCast.LGCastSecurityKey]", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastSecurityKey", + "printedName": "LGCast.LGCastSecurityKey", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(im)generateMirroringMasterKey:", + "mangledName": "$s6LGCast0A12MirroringApiC08generateB9MasterKeyySayAA0a8SecurityF0CGSgSSF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "startMirroring", + "printedName": "startMirroring(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastDeviceSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastDeviceSettings", + "printedName": "LGCast.LGCastDeviceSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(im)startMirroring:", + "mangledName": "$s6LGCast0A12MirroringApiC05startB0yyAA0A14DeviceSettingsCSgF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "updateDisplayOrientation", + "printedName": "updateDisplayOrientation(isPortraitMode:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringInfo", + "printedName": "LGCast.LGCastMirroringInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(im)updateDisplayOrientationWithIsPortraitMode:", + "mangledName": "$s6LGCast0A12MirroringApiC24updateDisplayOrientation14isPortraitModeAA0aB4InfoCSgSb_tF", + "moduleName": "LGCast", + "objc_name": "updateDisplayOrientationWithIsPortraitMode:", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "stopMirroring", + "printedName": "stopMirroring()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(im)stopMirroring", + "mangledName": "$s6LGCast0A12MirroringApiC04stopB0yyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "startMirroringWithCapture", + "printedName": "startMirroringWithCapture(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastDeviceSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastDeviceSettings", + "printedName": "LGCast.LGCastDeviceSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Func", + "usr": "s:6LGCast0A12MirroringApiC05startB11WithCaptureyyAA0A14DeviceSettingsCSgF", + "mangledName": "$s6LGCast0A12MirroringApiC05startB11WithCaptureyyAA0A14DeviceSettingsCSgF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "stopMirroringWithCapture", + "printedName": "stopMirroringWithCapture()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Func", + "usr": "s:6LGCast0A12MirroringApiC04stopB11WithCaptureyyF", + "mangledName": "$s6LGCast0A12MirroringApiC04stopB11WithCaptureyyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "pushSampleBuffer", + "printedName": "pushSampleBuffer(_:with:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "CMSampleBuffer", + "printedName": "CoreMedia.CMSampleBuffer", + "usr": "c:@T@CMSampleBufferRef" + }, + { + "kind": "TypeNominal", + "name": "RPSampleBufferType", + "printedName": "ReplayKit.RPSampleBufferType", + "usr": "c:@E@RPSampleBufferType" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi(im)pushSampleBuffer:with:", + "mangledName": "$s6LGCast0A12MirroringApiC16pushSampleBuffer_4withySo08CMSampleF3Refa_So08RPSampleF4TypeVtF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "pushSampleBuffer", + "printedName": "pushSampleBuffer(_:with:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "CMSampleBuffer", + "printedName": "CoreMedia.CMSampleBuffer", + "usr": "c:@T@CMSampleBufferRef" + }, + { + "kind": "TypeNominal", + "name": "RTPStreamerMediaType", + "printedName": "LGCast.RTPStreamerMediaType", + "usr": "s:6LGCast20RTPStreamerMediaTypeO" + } + ], + "declKind": "Func", + "usr": "s:6LGCast0A12MirroringApiC16pushSampleBuffer_4withySo08CMSampleF3Refa_AA20RTPStreamerMediaTypeOtF", + "mangledName": "$s6LGCast0A12MirroringApiC16pushSampleBuffer_4withySo08CMSampleF3Refa_AA20RTPStreamerMediaTypeOtF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringApi", + "mangledName": "$s6LGCast0A12MirroringApiC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "hasMissingDesignatedInitializers": true, + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringError", + "printedName": "LGCastMirroringError", + "children": [ + { + "kind": "Var", + "name": "unknown", + "printedName": "unknown", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringError.Type) -> LGCast.LGCastMirroringError", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringError.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringError@LGCastMirroringErrorUnknown", + "mangledName": "$s6LGCast0A14MirroringErrorO7unknownyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "connectionClosed", + "printedName": "connectionClosed", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringError.Type) -> LGCast.LGCastMirroringError", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringError.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringError@LGCastMirroringErrorConnectionClosed", + "mangledName": "$s6LGCast0A14MirroringErrorO16connectionClosedyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Var", + "name": "deviceShutdown", + "printedName": "deviceShutdown", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringError.Type) -> LGCast.LGCastMirroringError", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringError.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringError@LGCastMirroringErrorDeviceShutdown", + "mangledName": "$s6LGCast0A14MirroringErrorO14deviceShutdownyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 2 + }, + { + "kind": "Var", + "name": "rendererTerminated", + "printedName": "rendererTerminated", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastMirroringError.Type) -> LGCast.LGCastMirroringError", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastMirroringError.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastMirroringError@LGCastMirroringErrorRendererTerminated", + "mangledName": "$s6LGCast0A14MirroringErrorO18rendererTerminatedyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 3 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringError?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringError", + "printedName": "LGCast.LGCastMirroringError", + "usr": "c:@M@LGCast@E@LGCastMirroringError" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A14MirroringErrorO8rawValueACSgSi_tcfc", + "mangledName": "$s6LGCast0A14MirroringErrorO8rawValueACSgSi_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A14MirroringErrorO8rawValueSivp", + "mangledName": "$s6LGCast0A14MirroringErrorO8rawValueSivp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A14MirroringErrorO8rawValueSivg", + "mangledName": "$s6LGCast0A14MirroringErrorO8rawValueSivg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@LGCastMirroringError", + "mangledName": "$s6LGCast0A14MirroringErrorO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastSecurityKey", + "printedName": "LGCastSecurityKey", + "children": [ + { + "kind": "Var", + "name": "masterKey", + "printedName": "masterKey", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey(py)masterKey", + "mangledName": "$s6LGCast0A11SecurityKeyC06masterC0SSSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey(im)masterKey", + "mangledName": "$s6LGCast0A11SecurityKeyC06masterC0SSSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey(im)setMasterKey:", + "mangledName": "$s6LGCast0A11SecurityKeyC06masterC0SSSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A11SecurityKeyC06masterC0SSSgvM", + "mangledName": "$s6LGCast0A11SecurityKeyC06masterC0SSSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "mki", + "printedName": "mki", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey(py)mki", + "mangledName": "$s6LGCast0A11SecurityKeyC3mkiSSSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey(im)mki", + "mangledName": "$s6LGCast0A11SecurityKeyC3mkiSSSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey(im)setMki:", + "mangledName": "$s6LGCast0A11SecurityKeyC3mkiSSSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A11SecurityKeyC3mkiSSSgvM", + "mangledName": "$s6LGCast0A11SecurityKeyC3mkiSSSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastSecurityKey", + "printedName": "LGCast.LGCastSecurityKey", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey(im)init", + "mangledName": "$s6LGCast0A11SecurityKeyCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey", + "mangledName": "$s6LGCast0A11SecurityKeyC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastDeviceSettings", + "printedName": "LGCastDeviceSettings", + "children": [ + { + "kind": "Var", + "name": "host", + "printedName": "host", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(py)host", + "mangledName": "$s6LGCast0A14DeviceSettingsC4hostSSSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(im)host", + "mangledName": "$s6LGCast0A14DeviceSettingsC4hostSSSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(im)setHost:", + "mangledName": "$s6LGCast0A14DeviceSettingsC4hostSSSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A14DeviceSettingsC4hostSSSgvM", + "mangledName": "$s6LGCast0A14DeviceSettingsC4hostSSSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "audioPort", + "printedName": "audioPort", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(py)audioPort", + "mangledName": "$s6LGCast0A14DeviceSettingsC9audioPortSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(im)audioPort", + "mangledName": "$s6LGCast0A14DeviceSettingsC9audioPortSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(im)setAudioPort:", + "mangledName": "$s6LGCast0A14DeviceSettingsC9audioPortSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A14DeviceSettingsC9audioPortSivM", + "mangledName": "$s6LGCast0A14DeviceSettingsC9audioPortSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "videoPort", + "printedName": "videoPort", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(py)videoPort", + "mangledName": "$s6LGCast0A14DeviceSettingsC9videoPortSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(im)videoPort", + "mangledName": "$s6LGCast0A14DeviceSettingsC9videoPortSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(im)setVideoPort:", + "mangledName": "$s6LGCast0A14DeviceSettingsC9videoPortSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A14DeviceSettingsC9videoPortSivM", + "mangledName": "$s6LGCast0A14DeviceSettingsC9videoPortSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastDeviceSettings", + "printedName": "LGCast.LGCastDeviceSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings(im)init", + "mangledName": "$s6LGCast0A14DeviceSettingsCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings", + "mangledName": "$s6LGCast0A14DeviceSettingsC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringAudioSettings", + "printedName": "LGCastMirroringAudioSettings", + "children": [ + { + "kind": "Var", + "name": "sampleRate", + "printedName": "sampleRate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(py)sampleRate", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC10sampleRateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(im)sampleRate", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC10sampleRateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(im)setSampleRate:", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC10sampleRateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringAudioSettingsC10sampleRateSivM", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC10sampleRateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "bitrate", + "printedName": "bitrate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(py)bitrate", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC7bitrateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(im)bitrate", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC7bitrateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(im)setBitrate:", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC7bitrateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringAudioSettingsC7bitrateSivM", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC7bitrateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "channelCnt", + "printedName": "channelCnt", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(py)channelCnt", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC10channelCntSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(im)channelCnt", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC10channelCntSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(im)setChannelCnt:", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC10channelCntSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringAudioSettingsC10channelCntSivM", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC10channelCntSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringAudioSettings", + "printedName": "LGCast.LGCastMirroringAudioSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings(im)init", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings", + "mangledName": "$s6LGCast0A22MirroringAudioSettingsC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringVideoSettings", + "printedName": "LGCastMirroringVideoSettings", + "children": [ + { + "kind": "Var", + "name": "width", + "printedName": "width", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(py)width", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC5widthSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)width", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC5widthSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)setWidth:", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC5widthSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringVideoSettingsC5widthSivM", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC5widthSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "height", + "printedName": "height", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(py)height", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC6heightSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)height", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC6heightSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)setHeight:", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC6heightSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringVideoSettingsC6heightSivM", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC6heightSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "isPortraitMode", + "printedName": "isPortraitMode", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(py)isPortraitMode", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC14isPortraitModeSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)isPortraitMode", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC14isPortraitModeSbvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)setIsPortraitMode:", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC14isPortraitModeSbvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringVideoSettingsC14isPortraitModeSbvM", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC14isPortraitModeSbvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "framerate", + "printedName": "framerate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(py)framerate", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC9framerateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)framerate", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC9framerateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)setFramerate:", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC9framerateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringVideoSettingsC9framerateSivM", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC9framerateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "bitrate", + "printedName": "bitrate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(py)bitrate", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC7bitrateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)bitrate", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC7bitrateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)setBitrate:", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC7bitrateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringVideoSettingsC7bitrateSivM", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC7bitrateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringVideoSettings", + "printedName": "LGCast.LGCastMirroringVideoSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings(im)init", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings", + "mangledName": "$s6LGCast0A22MirroringVideoSettingsC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringMediaSettings", + "printedName": "LGCastMirroringMediaSettings", + "children": [ + { + "kind": "Var", + "name": "audio", + "printedName": "audio", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringAudioSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringAudioSettings", + "printedName": "LGCast.LGCastMirroringAudioSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings(py)audio", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC5audioAA0ab5AudioD0CSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringAudioSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringAudioSettings", + "printedName": "LGCast.LGCastMirroringAudioSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings(im)audio", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC5audioAA0ab5AudioD0CSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringAudioSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringAudioSettings", + "printedName": "LGCast.LGCastMirroringAudioSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings(im)setAudio:", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC5audioAA0ab5AudioD0CSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringMediaSettingsC5audioAA0ab5AudioD0CSgvM", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC5audioAA0ab5AudioD0CSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "video", + "printedName": "video", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringVideoSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringVideoSettings", + "printedName": "LGCast.LGCastMirroringVideoSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings(py)video", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC5videoAA0ab5VideoD0CSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringVideoSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringVideoSettings", + "printedName": "LGCast.LGCastMirroringVideoSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings(im)video", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC5videoAA0ab5VideoD0CSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringVideoSettings?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringVideoSettings", + "printedName": "LGCast.LGCastMirroringVideoSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoSettings" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings(im)setVideo:", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC5videoAA0ab5VideoD0CSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A22MirroringMediaSettingsC5videoAA0ab5VideoD0CSgvM", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC5videoAA0ab5VideoD0CSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringMediaSettings", + "printedName": "LGCast.LGCastMirroringMediaSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings(im)init", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringMediaSettings", + "mangledName": "$s6LGCast0A22MirroringMediaSettingsC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringAudioInfo", + "printedName": "LGCastMirroringAudioInfo", + "children": [ + { + "kind": "Var", + "name": "type", + "printedName": "type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)type", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4typeAA11MTAudioTypeOvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)type", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4typeAA11MTAudioTypeOvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)setType:", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4typeAA11MTAudioTypeOvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringAudioInfoC4typeAA11MTAudioTypeOvM", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4typeAA11MTAudioTypeOvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "codec", + "printedName": "codec", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)codec", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC5codecSSvp", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)codec", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC5codecSSvg", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "samplingRate", + "printedName": "samplingRate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)samplingRate", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC12samplingRateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)samplingRate", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC12samplingRateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)setSamplingRate:", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC12samplingRateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringAudioInfoC12samplingRateSivM", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC12samplingRateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "channelCnt", + "printedName": "channelCnt", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)channelCnt", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC10channelCntSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)channelCnt", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC10channelCntSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)setChannelCnt:", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC10channelCntSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringAudioInfoC10channelCntSivM", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC10channelCntSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "bitrate", + "printedName": "bitrate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)bitrate", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC7bitrateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)bitrate", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC7bitrateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)setBitrate:", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC7bitrateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringAudioInfoC7bitrateSivM", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC7bitrateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "streamMuxConfig", + "printedName": "streamMuxConfig", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)streamMuxConfig", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC15streamMuxConfigSSvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)streamMuxConfig", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC15streamMuxConfigSSvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)setStreamMuxConfig:", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC15streamMuxConfigSSvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringAudioInfoC15streamMuxConfigSSvM", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC15streamMuxConfigSSvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "codecData", + "printedName": "codecData", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Foundation.Data?", + "children": [ + { + "kind": "TypeNominal", + "name": "Data", + "printedName": "Foundation.Data", + "usr": "s:10Foundation4DataV" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)codecData", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC9codecData10Foundation0F0VSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC", + "RawDocComment" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Foundation.Data?", + "children": [ + { + "kind": "TypeNominal", + "name": "Data", + "printedName": "Foundation.Data", + "usr": "s:10Foundation4DataV" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)codecData", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC9codecData10Foundation0F0VSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Foundation.Data?", + "children": [ + { + "kind": "TypeNominal", + "name": "Data", + "printedName": "Foundation.Data", + "usr": "s:10Foundation4DataV" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)setCodecData:", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC9codecData10Foundation0F0VSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringAudioInfoC9codecData10Foundation0F0VSgvM", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC9codecData10Foundation0F0VSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "host", + "printedName": "host", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)host", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4hostSSSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC", + "RawDocComment" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)host", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4hostSSSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)setHost:", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4hostSSSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringAudioInfoC4hostSSSgvM", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4hostSSSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "port", + "printedName": "port", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(py)port", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4portSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)port", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4portSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)setPort:", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4portSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringAudioInfoC4portSivM", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC4portSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringAudioInfo", + "printedName": "LGCast.LGCastMirroringAudioInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo(im)init", + "mangledName": "$s6LGCast0A18MirroringAudioInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo", + "mangledName": "$s6LGCast0A18MirroringAudioInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringVideoInfo", + "printedName": "LGCastMirroringVideoInfo", + "children": [ + { + "kind": "Var", + "name": "type", + "printedName": "type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)type", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4typeAA11MTVideoTypeOvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)type", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4typeAA11MTVideoTypeOvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setType:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4typeAA11MTVideoTypeOvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC4typeAA11MTVideoTypeOvM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4typeAA11MTVideoTypeOvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "codec", + "printedName": "codec", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)codec", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC5codecSSvp", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)codec", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC5codecSSvg", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "width", + "printedName": "width", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)width", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC5widthSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)width", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC5widthSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setWidth:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC5widthSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC5widthSivM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC5widthSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "height", + "printedName": "height", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)height", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC6heightSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC", + "RawDocComment" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)height", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC6heightSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setHeight:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC6heightSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC6heightSivM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC6heightSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "activeWidth", + "printedName": "activeWidth", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)activeWidth", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC11activeWidthSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC", + "RawDocComment" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)activeWidth", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC11activeWidthSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setActiveWidth:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC11activeWidthSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC11activeWidthSivM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC11activeWidthSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "activeHeight", + "printedName": "activeHeight", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)activeHeight", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC12activeHeightSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC", + "RawDocComment" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)activeHeight", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC12activeHeightSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setActiveHeight:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC12activeHeightSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC12activeHeightSivM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC12activeHeightSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "framerate", + "printedName": "framerate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)framerate", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC9framerateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC", + "RawDocComment" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)framerate", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC9framerateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setFramerate:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC9framerateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC9framerateSivM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC9framerateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "samplingRate", + "printedName": "samplingRate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)samplingRate", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC12samplingRateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)samplingRate", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC12samplingRateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setSamplingRate:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC12samplingRateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC12samplingRateSivM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC12samplingRateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "bitrate", + "printedName": "bitrate", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)bitrate", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC7bitrateSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)bitrate", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC7bitrateSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setBitrate:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC7bitrateSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC7bitrateSivM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC7bitrateSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "supportOrientation", + "printedName": "supportOrientation", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)supportOrientation", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC18supportOrientationSSvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)supportOrientation", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC18supportOrientationSSvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setSupportOrientation:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC18supportOrientationSSvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC18supportOrientationSSvM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC18supportOrientationSSvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "isPortraitMode", + "printedName": "isPortraitMode", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)isPortraitMode", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC14isPortraitModeSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)isPortraitMode", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC14isPortraitModeSbvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setIsPortraitMode:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC14isPortraitModeSbvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC14isPortraitModeSbvM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC14isPortraitModeSbvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "screenOrientation", + "printedName": "screenOrientation", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)screenOrientation", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC17screenOrientationSSvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)screenOrientation", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC17screenOrientationSSvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setScreenOrientation:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC17screenOrientationSSvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC17screenOrientationSSvM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC17screenOrientationSSvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "streamMuxConfig", + "printedName": "streamMuxConfig", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)streamMuxConfig", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC15streamMuxConfigSSSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC", + "RawDocComment" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)streamMuxConfig", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC15streamMuxConfigSSSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setStreamMuxConfig:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC15streamMuxConfigSSSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC15streamMuxConfigSSSgvM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC15streamMuxConfigSSSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "codecData", + "printedName": "codecData", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Foundation.Data?", + "children": [ + { + "kind": "TypeNominal", + "name": "Data", + "printedName": "Foundation.Data", + "usr": "s:10Foundation4DataV" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)codecData", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC9codecData10Foundation0F0VSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Foundation.Data?", + "children": [ + { + "kind": "TypeNominal", + "name": "Data", + "printedName": "Foundation.Data", + "usr": "s:10Foundation4DataV" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)codecData", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC9codecData10Foundation0F0VSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Foundation.Data?", + "children": [ + { + "kind": "TypeNominal", + "name": "Data", + "printedName": "Foundation.Data", + "usr": "s:10Foundation4DataV" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setCodecData:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC9codecData10Foundation0F0VSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC9codecData10Foundation0F0VSgvM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC9codecData10Foundation0F0VSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "host", + "printedName": "host", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)host", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4hostSSSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC", + "RawDocComment" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)host", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4hostSSSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setHost:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4hostSSSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC4hostSSSgvM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4hostSSSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "port", + "printedName": "port", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(py)port", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4portSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)port", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4portSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)setPort:", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4portSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A18MirroringVideoInfoC4portSivM", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC4portSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringVideoInfo", + "printedName": "LGCast.LGCastMirroringVideoInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo(im)init", + "mangledName": "$s6LGCast0A18MirroringVideoInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo", + "mangledName": "$s6LGCast0A18MirroringVideoInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringPlayerInfo", + "printedName": "LGCastMirroringPlayerInfo", + "children": [ + { + "kind": "Var", + "name": "width", + "printedName": "width", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(py)width", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC5widthSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)width", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC5widthSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)setWidth:", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC5widthSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19MirroringPlayerInfoC5widthSivM", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC5widthSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "height", + "printedName": "height", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(py)height", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC6heightSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)height", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC6heightSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)setHeight:", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC6heightSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19MirroringPlayerInfoC6heightSivM", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC6heightSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "isPortraitMode", + "printedName": "isPortraitMode", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(py)isPortraitMode", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC14isPortraitModeSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)isPortraitMode", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC14isPortraitModeSbvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)setIsPortraitMode:", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC14isPortraitModeSbvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19MirroringPlayerInfoC14isPortraitModeSbvM", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC14isPortraitModeSbvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "ipAddress", + "printedName": "ipAddress", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(py)ipAddress", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9ipAddressSSvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)ipAddress", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9ipAddressSSvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)setIpAddress:", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9ipAddressSSvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19MirroringPlayerInfoC9ipAddressSSvM", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9ipAddressSSvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "audioPort", + "printedName": "audioPort", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(py)audioPort", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9audioPortSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)audioPort", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9audioPortSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)setAudioPort:", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9audioPortSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19MirroringPlayerInfoC9audioPortSivM", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9audioPortSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "videoPort", + "printedName": "videoPort", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(py)videoPort", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9videoPortSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)videoPort", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9videoPortSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)setVideoPort:", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9videoPortSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19MirroringPlayerInfoC9videoPortSivM", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC9videoPortSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringPlayerInfo", + "printedName": "LGCast.LGCastMirroringPlayerInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo(im)init", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo", + "mangledName": "$s6LGCast0A19MirroringPlayerInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastMirroringInfo", + "printedName": "LGCastMirroringInfo", + "children": [ + { + "kind": "Var", + "name": "audioInfo", + "printedName": "audioInfo", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringAudioInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringAudioInfo", + "printedName": "LGCast.LGCastMirroringAudioInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(py)audioInfo", + "mangledName": "$s6LGCast0A13MirroringInfoC05audioC0AA0ab5AudioC0CSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringAudioInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringAudioInfo", + "printedName": "LGCast.LGCastMirroringAudioInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(im)audioInfo", + "mangledName": "$s6LGCast0A13MirroringInfoC05audioC0AA0ab5AudioC0CSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringAudioInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringAudioInfo", + "printedName": "LGCast.LGCastMirroringAudioInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringAudioInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(im)setAudioInfo:", + "mangledName": "$s6LGCast0A13MirroringInfoC05audioC0AA0ab5AudioC0CSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A13MirroringInfoC05audioC0AA0ab5AudioC0CSgvM", + "mangledName": "$s6LGCast0A13MirroringInfoC05audioC0AA0ab5AudioC0CSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "videoInfo", + "printedName": "videoInfo", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringVideoInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringVideoInfo", + "printedName": "LGCast.LGCastMirroringVideoInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(py)videoInfo", + "mangledName": "$s6LGCast0A13MirroringInfoC05videoC0AA0ab5VideoC0CSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringVideoInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringVideoInfo", + "printedName": "LGCast.LGCastMirroringVideoInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(im)videoInfo", + "mangledName": "$s6LGCast0A13MirroringInfoC05videoC0AA0ab5VideoC0CSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringVideoInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringVideoInfo", + "printedName": "LGCast.LGCastMirroringVideoInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringVideoInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(im)setVideoInfo:", + "mangledName": "$s6LGCast0A13MirroringInfoC05videoC0AA0ab5VideoC0CSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A13MirroringInfoC05videoC0AA0ab5VideoC0CSgvM", + "mangledName": "$s6LGCast0A13MirroringInfoC05videoC0AA0ab5VideoC0CSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "playerInfo", + "printedName": "playerInfo", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringPlayerInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringPlayerInfo", + "printedName": "LGCast.LGCastMirroringPlayerInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(py)playerInfo", + "mangledName": "$s6LGCast0A13MirroringInfoC06playerC0AA0ab6PlayerC0CSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringPlayerInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringPlayerInfo", + "printedName": "LGCast.LGCastMirroringPlayerInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(im)playerInfo", + "mangledName": "$s6LGCast0A13MirroringInfoC06playerC0AA0ab6PlayerC0CSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastMirroringPlayerInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringPlayerInfo", + "printedName": "LGCast.LGCastMirroringPlayerInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringPlayerInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(im)setPlayerInfo:", + "mangledName": "$s6LGCast0A13MirroringInfoC06playerC0AA0ab6PlayerC0CSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A13MirroringInfoC06playerC0AA0ab6PlayerC0CSgvM", + "mangledName": "$s6LGCast0A13MirroringInfoC06playerC0AA0ab6PlayerC0CSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastMirroringInfo", + "printedName": "LGCast.LGCastMirroringInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo(im)init", + "mangledName": "$s6LGCast0A13MirroringInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastMirroringInfo", + "mangledName": "$s6LGCast0A13MirroringInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraParameterInfo", + "printedName": "LGCastCameraParameterInfo", + "children": [ + { + "kind": "Var", + "name": "audio", + "printedName": "audio", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(py)audio", + "mangledName": "$s6LGCast0A19CameraParameterInfoC5audioAA0A21CamperaPropertyStatusOvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)audio", + "mangledName": "$s6LGCast0A19CameraParameterInfoC5audioAA0A21CamperaPropertyStatusOvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)setAudio:", + "mangledName": "$s6LGCast0A19CameraParameterInfoC5audioAA0A21CamperaPropertyStatusOvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19CameraParameterInfoC5audioAA0A21CamperaPropertyStatusOvM", + "mangledName": "$s6LGCast0A19CameraParameterInfoC5audioAA0A21CamperaPropertyStatusOvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "autoWhiteBalance", + "printedName": "autoWhiteBalance", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(py)autoWhiteBalance", + "mangledName": "$s6LGCast0A19CameraParameterInfoC16autoWhiteBalanceAA0A21CamperaPropertyStatusOvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)autoWhiteBalance", + "mangledName": "$s6LGCast0A19CameraParameterInfoC16autoWhiteBalanceAA0A21CamperaPropertyStatusOvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)setAutoWhiteBalance:", + "mangledName": "$s6LGCast0A19CameraParameterInfoC16autoWhiteBalanceAA0A21CamperaPropertyStatusOvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19CameraParameterInfoC16autoWhiteBalanceAA0A21CamperaPropertyStatusOvM", + "mangledName": "$s6LGCast0A19CameraParameterInfoC16autoWhiteBalanceAA0A21CamperaPropertyStatusOvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "brightness", + "printedName": "brightness", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(py)brightness", + "mangledName": "$s6LGCast0A19CameraParameterInfoC10brightnessSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)brightness", + "mangledName": "$s6LGCast0A19CameraParameterInfoC10brightnessSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)setBrightness:", + "mangledName": "$s6LGCast0A19CameraParameterInfoC10brightnessSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19CameraParameterInfoC10brightnessSivM", + "mangledName": "$s6LGCast0A19CameraParameterInfoC10brightnessSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "width", + "printedName": "width", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(py)width", + "mangledName": "$s6LGCast0A19CameraParameterInfoC5widthSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)width", + "mangledName": "$s6LGCast0A19CameraParameterInfoC5widthSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)setWidth:", + "mangledName": "$s6LGCast0A19CameraParameterInfoC5widthSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19CameraParameterInfoC5widthSivM", + "mangledName": "$s6LGCast0A19CameraParameterInfoC5widthSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "height", + "printedName": "height", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(py)height", + "mangledName": "$s6LGCast0A19CameraParameterInfoC6heightSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)height", + "mangledName": "$s6LGCast0A19CameraParameterInfoC6heightSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)setHeight:", + "mangledName": "$s6LGCast0A19CameraParameterInfoC6heightSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19CameraParameterInfoC6heightSivM", + "mangledName": "$s6LGCast0A19CameraParameterInfoC6heightSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "facing", + "printedName": "facing", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(py)facing", + "mangledName": "$s6LGCast0A19CameraParameterInfoC6facingAA0aB8PositionOvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)facing", + "mangledName": "$s6LGCast0A19CameraParameterInfoC6facingAA0aB8PositionOvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)setFacing:", + "mangledName": "$s6LGCast0A19CameraParameterInfoC6facingAA0aB8PositionOvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19CameraParameterInfoC6facingAA0aB8PositionOvM", + "mangledName": "$s6LGCast0A19CameraParameterInfoC6facingAA0aB8PositionOvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "whiteBalance", + "printedName": "whiteBalance", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(py)whiteBalance", + "mangledName": "$s6LGCast0A19CameraParameterInfoC12whiteBalanceSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)whiteBalance", + "mangledName": "$s6LGCast0A19CameraParameterInfoC12whiteBalanceSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)setWhiteBalance:", + "mangledName": "$s6LGCast0A19CameraParameterInfoC12whiteBalanceSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19CameraParameterInfoC12whiteBalanceSivM", + "mangledName": "$s6LGCast0A19CameraParameterInfoC12whiteBalanceSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "rotation", + "printedName": "rotation", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(py)rotation", + "mangledName": "$s6LGCast0A19CameraParameterInfoC8rotationSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)rotation", + "mangledName": "$s6LGCast0A19CameraParameterInfoC8rotationSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)setRotation:", + "mangledName": "$s6LGCast0A19CameraParameterInfoC8rotationSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A19CameraParameterInfoC8rotationSivM", + "mangledName": "$s6LGCast0A19CameraParameterInfoC8rotationSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraParameterInfo", + "printedName": "LGCast.LGCastCameraParameterInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo(im)init", + "mangledName": "$s6LGCast0A19CameraParameterInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo", + "mangledName": "$s6LGCast0A19CameraParameterInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraApiDelegate", + "printedName": "LGCastCameraApiDelegate", + "children": [ + { + "kind": "Function", + "name": "lgcastCameraDidPlay", + "printedName": "lgcastCameraDidPlay()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(pl)LGCastCameraApiDelegate(im)lgcastCameraDidPlay", + "mangledName": "$s6LGCast0A17CameraApiDelegateP06lgcastB7DidPlayyyF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.LGCastCameraApiDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "declAttributes": [ + "ObjC" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "lgcastCameraDidChange", + "printedName": "lgcastCameraDidChange(property:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(pl)LGCastCameraApiDelegate(im)lgcastCameraDidChangeWithProperty:", + "mangledName": "$s6LGCast0A17CameraApiDelegateP06lgcastB9DidChange8propertyyAA0aB8PropertyO_tF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.LGCastCameraApiDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "declAttributes": [ + "ObjC" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "lgcastCameraErrorDidOccur", + "printedName": "lgcastCameraErrorDidOccur(error:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastCameraError", + "printedName": "LGCast.LGCastCameraError", + "usr": "c:@M@LGCast@E@LGCastCameraError" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(pl)LGCastCameraApiDelegate(im)lgcastCameraErrorDidOccurWithError:", + "mangledName": "$s6LGCast0A17CameraApiDelegateP06lgcastB13ErrorDidOccur5erroryAA0abF0O_tF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.LGCastCameraApiDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "declAttributes": [ + "ObjC" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@LGCast@objc(pl)LGCastCameraApiDelegate", + "mangledName": "$s6LGCast0A17CameraApiDelegateP", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 : AnyObject>", + "sugared_genericSig": "", + "declAttributes": [ + "AccessControl", + "ObjC" + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraResolutionInfo", + "printedName": "LGCastCameraResolutionInfo", + "children": [ + { + "kind": "Var", + "name": "width", + "printedName": "width", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo(py)width", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC5widthSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo(im)width", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC5widthSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo(im)setWidth:", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC5widthSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A20CameraResolutionInfoC5widthSivM", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC5widthSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "height", + "printedName": "height", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo(py)height", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC6heightSivp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo(im)height", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC6heightSivg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo(im)setHeight:", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC6heightSivs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A20CameraResolutionInfoC6heightSivM", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC6heightSivM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolutionInfo", + "printedName": "LGCast.LGCastCameraResolutionInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo(im)init", + "mangledName": "$s6LGCast0A20CameraResolutionInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo", + "mangledName": "$s6LGCast0A20CameraResolutionInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraApi", + "printedName": "LGCastCameraApi", + "children": [ + { + "kind": "Var", + "name": "KEY_CAMERA_POSITION", + "printedName": "KEY_CAMERA_POSITION", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(cpy)KEY_CAMERA_POSITION", + "mangledName": "$s6LGCast0A9CameraApiC19KEY_CAMERA_POSITIONSSvpZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "HasInitialValue", + "Final", + "HasStorage", + "AccessControl", + "ObjC" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(cm)KEY_CAMERA_POSITION", + "mangledName": "$s6LGCast0A9CameraApiC19KEY_CAMERA_POSITIONSSvgZ", + "moduleName": "LGCast", + "static": true, + "implicit": true, + "declAttributes": [ + "Final", + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "KEY_PREVIEW_VIDEO_GRAVITY", + "printedName": "KEY_PREVIEW_VIDEO_GRAVITY", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(cpy)KEY_PREVIEW_VIDEO_GRAVITY", + "mangledName": "$s6LGCast0A9CameraApiC25KEY_PREVIEW_VIDEO_GRAVITYSSvpZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "HasInitialValue", + "Final", + "HasStorage", + "AccessControl", + "ObjC" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(cm)KEY_PREVIEW_VIDEO_GRAVITY", + "mangledName": "$s6LGCast0A9CameraApiC25KEY_PREVIEW_VIDEO_GRAVITYSSvgZ", + "moduleName": "LGCast", + "static": true, + "implicit": true, + "declAttributes": [ + "Final", + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "KEY_IS_SUPPORT_LANDSCAPE_MODE", + "printedName": "KEY_IS_SUPPORT_LANDSCAPE_MODE", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(cpy)KEY_IS_SUPPORT_LANDSCAPE_MODE", + "mangledName": "$s6LGCast0A9CameraApiC29KEY_IS_SUPPORT_LANDSCAPE_MODESSvpZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "HasInitialValue", + "Final", + "HasStorage", + "AccessControl", + "ObjC" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(cm)KEY_IS_SUPPORT_LANDSCAPE_MODE", + "mangledName": "$s6LGCast0A9CameraApiC29KEY_IS_SUPPORT_LANDSCAPE_MODESSvgZ", + "moduleName": "LGCast", + "static": true, + "implicit": true, + "declAttributes": [ + "Final", + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "shared", + "printedName": "shared", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraApi", + "printedName": "LGCast.LGCastCameraApi", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(cpy)shared", + "mangledName": "$s6LGCast0A9CameraApiC6sharedACvpZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "HasInitialValue", + "Final", + "HasStorage", + "AccessControl", + "ObjC" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraApi", + "printedName": "LGCast.LGCastCameraApi", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(cm)shared", + "mangledName": "$s6LGCast0A9CameraApiC6sharedACvgZ", + "moduleName": "LGCast", + "static": true, + "implicit": true, + "declAttributes": [ + "Final", + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "delegate", + "printedName": "delegate", + "children": [ + { + "kind": "TypeNominal", + "name": "WeakStorage", + "printedName": "LGCast.LGCastCameraApiDelegate?" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(py)delegate", + "mangledName": "$s6LGCast0A9CameraApiC8delegateAA0abC8Delegate_pSgvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "ReferenceOwnership", + "AccessControl", + "ObjC" + ], + "ownership": 1, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraApiDelegate?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraApiDelegate", + "printedName": "LGCast.LGCastCameraApiDelegate", + "usr": "c:@M@LGCast@objc(pl)LGCastCameraApiDelegate" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)delegate", + "mangledName": "$s6LGCast0A9CameraApiC8delegateAA0abC8Delegate_pSgvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraApiDelegate?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraApiDelegate", + "printedName": "LGCast.LGCastCameraApiDelegate", + "usr": "c:@M@LGCast@objc(pl)LGCastCameraApiDelegate" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)setDelegate:", + "mangledName": "$s6LGCast0A9CameraApiC8delegateAA0abC8Delegate_pSgvs", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A9CameraApiC8delegateAA0abC8Delegate_pSgvM", + "mangledName": "$s6LGCast0A9CameraApiC8delegateAA0abC8Delegate_pSgvM", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "_modify" + } + ] + }, + { + "kind": "Var", + "name": "isRunning", + "printedName": "isRunning", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(py)isRunning", + "mangledName": "$s6LGCast0A9CameraApiC9isRunningSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "HasInitialValue", + "HasStorage", + "SetterAccess", + "AccessControl", + "ObjC" + ], + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)isRunning", + "mangledName": "$s6LGCast0A9CameraApiC9isRunningSbvg", + "moduleName": "LGCast", + "implicit": true, + "declAttributes": [ + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "isTvInputEnabled", + "printedName": "isTvInputEnabled", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A9CameraApiC16isTvInputEnabledSbvp", + "mangledName": "$s6LGCast0A9CameraApiC16isTvInputEnabledSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A9CameraApiC16isTvInputEnabledSbvg", + "mangledName": "$s6LGCast0A9CameraApiC16isTvInputEnabledSbvg", + "moduleName": "LGCast", + "accessorKind": "get" + } + ] + }, + { + "kind": "Function", + "name": "isSupportedOsVersion", + "printedName": "isSupportedOsVersion()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)isSupportedOsVersion", + "mangledName": "$s6LGCast0A9CameraApiC20isSupportedOsVersionSbyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "createCameraPreviewView", + "printedName": "createCameraPreviewView(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "UIView", + "printedName": "UIKit.UIView", + "usr": "c:objc(cs)UIView" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)createCameraPreviewView:", + "mangledName": "$s6LGCast0A9CameraApiC06createB11PreviewViewySo6UIViewCSDySSypGSgF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "setPreviewView", + "printedName": "setPreviewView(_:options:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewView", + "printedName": "LGCast.LGCastCameraPreviewView", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraPreviewView" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)setPreviewView:options:", + "mangledName": "$s6LGCast0A9CameraApiC14setPreviewView_7optionsyAA0abeF0C_SDySSypGSgtF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "generateCameraMasterKey", + "printedName": "generateCameraMasterKey(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[LGCast.LGCastSecurityKey]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[LGCast.LGCastSecurityKey]", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastSecurityKey", + "printedName": "LGCast.LGCastSecurityKey", + "usr": "c:@M@LGCast@objc(cs)LGCastSecurityKey" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)generateCameraMasterKey:", + "mangledName": "$s6LGCast0A9CameraApiC08generateB9MasterKeyySayAA0a8SecurityF0CGSgSSF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "getCameraProperties", + "printedName": "getCameraProperties()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraParameterInfo?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraParameterInfo", + "printedName": "LGCast.LGCastCameraParameterInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraParameterInfo" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)getCameraProperties", + "mangledName": "$s6LGCast0A9CameraApiC03getB10PropertiesAA0aB13ParameterInfoCSgyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "setCameraProperties", + "printedName": "setCameraProperties(property:value:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + }, + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)setCameraPropertiesWithProperty:value:", + "mangledName": "$s6LGCast0A9CameraApiC03setB10Properties8property5valueSbAA0aB8PropertyO_SitF", + "moduleName": "LGCast", + "objc_name": "setCameraPropertiesWithProperty:value:", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "startRemoteCamera", + "printedName": "startRemoteCamera(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "LGCastDeviceSettings", + "printedName": "LGCast.LGCastDeviceSettings", + "usr": "c:@M@LGCast@objc(cs)LGCastDeviceSettings" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)startRemoteCamera:", + "mangledName": "$s6LGCast0A9CameraApiC011startRemoteB0yyAA0A14DeviceSettingsCF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "stopRemoteCamera", + "printedName": "stopRemoteCamera()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)stopRemoteCamera", + "mangledName": "$s6LGCast0A9CameraApiC010stopRemoteB0yyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "getSupportedResolutions", + "printedName": "getSupportedResolutions()", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[LGCast.LGCastCameraResolutionInfo]", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolutionInfo", + "printedName": "LGCast.LGCastCameraResolutionInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo" + } + ], + "usr": "s:Sa" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)getSupportedResolutions", + "mangledName": "$s6LGCast0A9CameraApiC23getSupportedResolutionsSayAA0aB14ResolutionInfoCGyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "getCurrentResolution", + "printedName": "getCurrentResolution()", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolutionInfo", + "printedName": "LGCast.LGCastCameraResolutionInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)getCurrentResolution", + "mangledName": "$s6LGCast0A9CameraApiC20getCurrentResolutionAA0abF4InfoCyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "setResolution", + "printedName": "setResolution(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + }, + { + "kind": "TypeNominal", + "name": "LGCastCameraResolutionInfo", + "printedName": "LGCast.LGCastCameraResolutionInfo", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraResolutionInfo" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)setResolution:", + "mangledName": "$s6LGCast0A9CameraApiC13setResolutionySbAA0abE4InfoCF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "changeCameraPosition", + "printedName": "changeCameraPosition(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + }, + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)changeCameraPosition:", + "mangledName": "$s6LGCast0A9CameraApiC06changeB8PositionySbAA0abE0OF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "muteMicrophone", + "printedName": "muteMicrophone(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Func", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi(im)muteMicrophone:", + "mangledName": "$s6LGCast0A9CameraApiC14muteMicrophoneyS2bF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraApi", + "mangledName": "$s6LGCast0A9CameraApiC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "hasMissingDesignatedInitializers": true, + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "AVFoundation", + "printedName": "AVFoundation", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraPreviewView", + "printedName": "LGCastCameraPreviewView", + "children": [ + { + "kind": "Var", + "name": "layerClass", + "printedName": "layerClass", + "children": [ + { + "kind": "TypeNominal", + "name": "ExistentialMetatype", + "printedName": "AnyObject.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "AnyObject" + } + ] + } + ], + "declKind": "Var", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraPreviewView(cpy)layerClass", + "mangledName": "$s6LGCast0A17CameraPreviewViewC10layerClassyXlXpvpZ", + "moduleName": "LGCast", + "static": true, + "overriding": true, + "objc_name": "layerClass", + "declAttributes": [ + "Dynamic", + "ObjC", + "Custom", + "Override", + "AccessControl" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "ExistentialMetatype", + "printedName": "AnyObject.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "AnyObject" + } + ] + } + ], + "declKind": "Accessor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraPreviewView(cm)layerClass", + "mangledName": "$s6LGCast0A17CameraPreviewViewC10layerClassyXlXpvgZ", + "moduleName": "LGCast", + "static": true, + "overriding": true, + "objc_name": "layerClass", + "declAttributes": [ + "Dynamic", + "ObjC" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(frame:)", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewView", + "printedName": "LGCast.LGCastCameraPreviewView", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraPreviewView" + }, + { + "kind": "TypeNominal", + "name": "CGRect", + "printedName": "CoreFoundation.CGRect", + "usr": "c:@S@CGRect" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraPreviewView(im)initWithFrame:", + "mangledName": "$s6LGCast0A17CameraPreviewViewC5frameACSo6CGRectV_tcfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "initWithFrame:", + "declAttributes": [ + "Dynamic", + "ObjC", + "Custom", + "Override" + ], + "init_kind": "Designated" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(coder:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraPreviewView?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewView", + "printedName": "LGCast.LGCastCameraPreviewView", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraPreviewView" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "NSCoder", + "printedName": "Foundation.NSCoder", + "usr": "c:objc(cs)NSCoder" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraPreviewView(im)initWithCoder:", + "mangledName": "$s6LGCast0A17CameraPreviewViewC5coderACSgSo7NSCoderC_tcfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "initWithCoder:", + "declAttributes": [ + "Dynamic", + "ObjC", + "Custom", + "Required" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)LGCastCameraPreviewView", + "mangledName": "$s6LGCast0A17CameraPreviewViewC", + "moduleName": "LGCast", + "declAttributes": [ + "Custom", + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)UIView", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "UIKit.UIView", + "UIKit.UIResponder", + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "__DefaultCustomPlaygroundQuickLookable", + "printedName": "__DefaultCustomPlaygroundQuickLookable", + "usr": "s:s38__DefaultCustomPlaygroundQuickLookableP", + "mangledName": "$ss38__DefaultCustomPlaygroundQuickLookableP" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraProperty", + "printedName": "LGCastCameraProperty", + "children": [ + { + "kind": "Var", + "name": "audio", + "printedName": "audio", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraProperty.Type) -> LGCast.LGCastCameraProperty", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraProperty.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraProperty@LGCastCameraPropertyAudio", + "mangledName": "$s6LGCast0A14CameraPropertyO5audioyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "autoWhiteBalance", + "printedName": "autoWhiteBalance", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraProperty.Type) -> LGCast.LGCastCameraProperty", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraProperty.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraProperty@LGCastCameraPropertyAutoWhiteBalance", + "mangledName": "$s6LGCast0A14CameraPropertyO16autoWhiteBalanceyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Var", + "name": "brightness", + "printedName": "brightness", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraProperty.Type) -> LGCast.LGCastCameraProperty", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraProperty.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraProperty@LGCastCameraPropertyBrightness", + "mangledName": "$s6LGCast0A14CameraPropertyO10brightnessyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 2 + }, + { + "kind": "Var", + "name": "facing", + "printedName": "facing", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraProperty.Type) -> LGCast.LGCastCameraProperty", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraProperty.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraProperty@LGCastCameraPropertyFacing", + "mangledName": "$s6LGCast0A14CameraPropertyO6facingyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 3 + }, + { + "kind": "Var", + "name": "resolution", + "printedName": "resolution", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraProperty.Type) -> LGCast.LGCastCameraProperty", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraProperty.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraProperty@LGCastCameraPropertyResolution", + "mangledName": "$s6LGCast0A14CameraPropertyO10resolutionyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 4 + }, + { + "kind": "Var", + "name": "whitebalance", + "printedName": "whitebalance", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraProperty.Type) -> LGCast.LGCastCameraProperty", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraProperty.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraProperty@LGCastCameraPropertyWhitebalance", + "mangledName": "$s6LGCast0A14CameraPropertyO12whitebalanceyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 5 + }, + { + "kind": "Var", + "name": "rotation", + "printedName": "rotation", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraProperty.Type) -> LGCast.LGCastCameraProperty", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraProperty.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraProperty@LGCastCameraPropertyRotation", + "mangledName": "$s6LGCast0A14CameraPropertyO8rotationyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 6 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraProperty?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraProperty", + "printedName": "LGCast.LGCastCameraProperty", + "usr": "c:@M@LGCast@E@LGCastCameraProperty" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A14CameraPropertyO8rawValueACSgSi_tcfc", + "mangledName": "$s6LGCast0A14CameraPropertyO8rawValueACSgSi_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A14CameraPropertyO8rawValueSivp", + "mangledName": "$s6LGCast0A14CameraPropertyO8rawValueSivp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A14CameraPropertyO8rawValueSivg", + "mangledName": "$s6LGCast0A14CameraPropertyO8rawValueSivg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@LGCastCameraProperty", + "mangledName": "$s6LGCast0A14CameraPropertyO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraPosition", + "printedName": "LGCastCameraPosition", + "children": [ + { + "kind": "Var", + "name": "front", + "printedName": "front", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraPosition.Type) -> LGCast.LGCastCameraPosition", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraPosition.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraPosition@LGCastCameraPositionFront", + "mangledName": "$s6LGCast0A14CameraPositionO5frontyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "back", + "printedName": "back", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraPosition.Type) -> LGCast.LGCastCameraPosition", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraPosition.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraPosition@LGCastCameraPositionBack", + "mangledName": "$s6LGCast0A14CameraPositionO4backyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraPosition?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPosition", + "printedName": "LGCast.LGCastCameraPosition", + "usr": "c:@M@LGCast@E@LGCastCameraPosition" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A14CameraPositionO8rawValueACSgSi_tcfc", + "mangledName": "$s6LGCast0A14CameraPositionO8rawValueACSgSi_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A14CameraPositionO8rawValueSivp", + "mangledName": "$s6LGCast0A14CameraPositionO8rawValueSivp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A14CameraPositionO8rawValueSivg", + "mangledName": "$s6LGCast0A14CameraPositionO8rawValueSivg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@LGCastCameraPosition", + "mangledName": "$s6LGCast0A14CameraPositionO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraPreviewVideoGravity", + "printedName": "LGCastCameraPreviewVideoGravity", + "children": [ + { + "kind": "Var", + "name": "resize", + "printedName": "resize", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraPreviewVideoGravity.Type) -> LGCast.LGCastCameraPreviewVideoGravity", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewVideoGravity", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewVideoGravity", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity@LGCastCameraPreviewVideoGravityResize", + "mangledName": "$s6LGCast0A25CameraPreviewVideoGravityO6resizeyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "resizeAspect", + "printedName": "resizeAspect", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraPreviewVideoGravity.Type) -> LGCast.LGCastCameraPreviewVideoGravity", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewVideoGravity", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewVideoGravity", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity@LGCastCameraPreviewVideoGravityResizeAspect", + "mangledName": "$s6LGCast0A25CameraPreviewVideoGravityO12resizeAspectyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Var", + "name": "resizeAspectFill", + "printedName": "resizeAspectFill", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraPreviewVideoGravity.Type) -> LGCast.LGCastCameraPreviewVideoGravity", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewVideoGravity", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewVideoGravity", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity@LGCastCameraPreviewVideoGravityResizeAspectFill", + "mangledName": "$s6LGCast0A25CameraPreviewVideoGravityO16resizeAspectFillyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 2 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraPreviewVideoGravity", + "printedName": "LGCast.LGCastCameraPreviewVideoGravity", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A25CameraPreviewVideoGravityO8rawValueACSgSi_tcfc", + "mangledName": "$s6LGCast0A25CameraPreviewVideoGravityO8rawValueACSgSi_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A25CameraPreviewVideoGravityO8rawValueSivp", + "mangledName": "$s6LGCast0A25CameraPreviewVideoGravityO8rawValueSivp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A25CameraPreviewVideoGravityO8rawValueSivg", + "mangledName": "$s6LGCast0A25CameraPreviewVideoGravityO8rawValueSivg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@LGCastCameraPreviewVideoGravity", + "mangledName": "$s6LGCast0A25CameraPreviewVideoGravityO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCastCamperaPropertyStatus", + "children": [ + { + "kind": "Var", + "name": "disable", + "printedName": "disable", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCamperaPropertyStatus.Type) -> LGCast.LGCastCamperaPropertyStatus", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCamperaPropertyStatus.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus@LGCastCamperaPropertyStatusDisable", + "mangledName": "$s6LGCast0A21CamperaPropertyStatusO7disableyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "enable", + "printedName": "enable", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCamperaPropertyStatus.Type) -> LGCast.LGCastCamperaPropertyStatus", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCamperaPropertyStatus.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus@LGCastCamperaPropertyStatusEnable", + "mangledName": "$s6LGCast0A21CamperaPropertyStatusO6enableyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCamperaPropertyStatus?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCamperaPropertyStatus", + "printedName": "LGCast.LGCastCamperaPropertyStatus", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A21CamperaPropertyStatusO8rawValueACSgSi_tcfc", + "mangledName": "$s6LGCast0A21CamperaPropertyStatusO8rawValueACSgSi_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A21CamperaPropertyStatusO8rawValueSivp", + "mangledName": "$s6LGCast0A21CamperaPropertyStatusO8rawValueSivp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A21CamperaPropertyStatusO8rawValueSivg", + "mangledName": "$s6LGCast0A21CamperaPropertyStatusO8rawValueSivg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@LGCastCamperaPropertyStatus", + "mangledName": "$s6LGCast0A21CamperaPropertyStatusO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastCameraResolution", + "printedName": "LGCastCameraResolution", + "children": [ + { + "kind": "Var", + "name": "low", + "printedName": "low", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionLow", + "mangledName": "$s6LGCast0A16CameraResolutionO3lowyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "medium", + "printedName": "medium", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionMedium", + "mangledName": "$s6LGCast0A16CameraResolutionO6mediumyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment", + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Var", + "name": "high", + "printedName": "high", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionHigh", + "mangledName": "$s6LGCast0A16CameraResolutionO4highyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment", + "ObjC" + ], + "fixedbinaryorder": 2 + }, + { + "kind": "Var", + "name": "cif352x288", + "printedName": "cif352x288", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionCif352x288", + "mangledName": "$s6LGCast0A16CameraResolutionO10cif352x288yA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment", + "ObjC" + ], + "fixedbinaryorder": 3 + }, + { + "kind": "Var", + "name": "vga640x480", + "printedName": "vga640x480", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionVga640x480", + "mangledName": "$s6LGCast0A16CameraResolutionO10vga640x480yA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 4 + }, + { + "kind": "Var", + "name": "hd1280x720", + "printedName": "hd1280x720", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionHd1280x720", + "mangledName": "$s6LGCast0A16CameraResolutionO10hd1280x720yA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 5 + }, + { + "kind": "Var", + "name": "hd1920x1080", + "printedName": "hd1920x1080", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionHd1920x1080", + "mangledName": "$s6LGCast0A16CameraResolutionO11hd1920x1080yA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 6 + }, + { + "kind": "Var", + "name": "hd4K3840x2160", + "printedName": "hd4K3840x2160", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionHd4K3840x2160", + "mangledName": "$s6LGCast0A16CameraResolutionO13hd4K3840x2160yA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 7 + }, + { + "kind": "Var", + "name": "iFrame960x540", + "printedName": "iFrame960x540", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionIFrame960x540", + "mangledName": "$s6LGCast0A16CameraResolutionO13iFrame960x540yA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 8 + }, + { + "kind": "Var", + "name": "iFrame1280x720", + "printedName": "iFrame1280x720", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.LGCastCameraResolution.Type) -> LGCast.LGCastCameraResolution", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.LGCastCameraResolution.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@LGCastCameraResolution@LGCastCameraResolutionIFrame1280x720", + "mangledName": "$s6LGCast0A16CameraResolutionO14iFrame1280x720yA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 9 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(width:height:)", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A16CameraResolutionO5width6heightACSi_Sitcfc", + "mangledName": "$s6LGCast0A16CameraResolutionO5width6heightACSi_Sitcfc", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "description", + "printedName": "description", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A16CameraResolutionO11descriptionSSvp", + "mangledName": "$s6LGCast0A16CameraResolutionO11descriptionSSvp", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A16CameraResolutionO11descriptionSSvg", + "mangledName": "$s6LGCast0A16CameraResolutionO11descriptionSSvg", + "moduleName": "LGCast", + "accessorKind": "get" + } + ] + }, + { + "kind": "Function", + "name": "getSize", + "printedName": "getSize()", + "children": [ + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "(width: Swift.Int, height: Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "declKind": "Func", + "usr": "s:6LGCast0A16CameraResolutionO7getSizeSi5width_Si6heighttyF", + "mangledName": "$s6LGCast0A16CameraResolutionO7getSizeSi5width_Si6heighttyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "getWidth", + "printedName": "getWidth()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "s:6LGCast0A16CameraResolutionO8getWidthSiyF", + "mangledName": "$s6LGCast0A16CameraResolutionO8getWidthSiyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "getHeight", + "printedName": "getHeight()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "s:6LGCast0A16CameraResolutionO9getHeightSiyF", + "mangledName": "$s6LGCast0A16CameraResolutionO9getHeightSiyF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.LGCastCameraResolution?", + "children": [ + { + "kind": "TypeNominal", + "name": "LGCastCameraResolution", + "printedName": "LGCast.LGCastCameraResolution", + "usr": "c:@M@LGCast@E@LGCastCameraResolution" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast0A16CameraResolutionO8rawValueACSgSi_tcfc", + "mangledName": "$s6LGCast0A16CameraResolutionO8rawValueACSgSi_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A16CameraResolutionO8rawValueSivp", + "mangledName": "$s6LGCast0A16CameraResolutionO8rawValueSivp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A16CameraResolutionO8rawValueSivg", + "mangledName": "$s6LGCast0A16CameraResolutionO8rawValueSivg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@LGCastCameraResolution", + "mangledName": "$s6LGCast0A16CameraResolutionO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "LGCastUserDefaults", + "printedName": "LGCastUserDefaults", + "children": [ + { + "kind": "Var", + "name": "wrappedValue", + "printedName": "wrappedValue", + "children": [ + { + "kind": "TypeNominal", + "name": "GenericTypeParam", + "printedName": "τ_0_0" + } + ], + "declKind": "Var", + "usr": "s:6LGCast0A12UserDefaultsV12wrappedValuexvp", + "mangledName": "$s6LGCast0A12UserDefaultsV12wrappedValuexvp", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "GenericTypeParam", + "printedName": "τ_0_0" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A12UserDefaultsV12wrappedValuexvg", + "mangledName": "$s6LGCast0A12UserDefaultsV12wrappedValuexvg", + "moduleName": "LGCast", + "genericSig": "<τ_0_0>", + "sugared_genericSig": "", + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "GenericTypeParam", + "printedName": "τ_0_0" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A12UserDefaultsV12wrappedValuexvs", + "mangledName": "$s6LGCast0A12UserDefaultsV12wrappedValuexvs", + "moduleName": "LGCast", + "genericSig": "<τ_0_0>", + "sugared_genericSig": "", + "accessorKind": "set" + }, + { + "kind": "Accessor", + "name": "Modify", + "printedName": "Modify()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast0A12UserDefaultsV12wrappedValuexvM", + "mangledName": "$s6LGCast0A12UserDefaultsV12wrappedValuexvM", + "moduleName": "LGCast", + "genericSig": "<τ_0_0>", + "sugared_genericSig": "", + "implicit": true, + "accessorKind": "_modify" + } + ] + } + ], + "declKind": "Struct", + "usr": "s:6LGCast0A12UserDefaultsV", + "mangledName": "$s6LGCast0A12UserDefaultsV", + "moduleName": "LGCast", + "genericSig": "<τ_0_0>", + "sugared_genericSig": "", + "declAttributes": [ + "AccessControl", + "PropertyWrapper" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "VideoToolbox", + "printedName": "VideoToolbox", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "CoreImage", + "printedName": "CoreImage", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "ReplayKit", + "printedName": "ReplayKit", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "AVFoundation", + "printedName": "AVFoundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "AudioToolbox", + "printedName": "AudioToolbox", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "TypeDecl", + "name": "AudioEncoderDelegate", + "printedName": "AudioEncoderDelegate", + "children": [ + { + "kind": "Function", + "name": "audioEncoder", + "printedName": "audioEncoder(_:didPrepare:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "AudioEncoder", + "printedName": "LGCast.AudioEncoder", + "usr": "s:6LGCast12AudioEncoderC" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Func", + "usr": "s:6LGCast20AudioEncoderDelegateP05audioC0_10didPrepareyAA0bC0C_SbtF", + "mangledName": "$s6LGCast20AudioEncoderDelegateP05audioC0_10didPrepareyAA0bC0C_SbtF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.AudioEncoderDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "audioEncoder", + "printedName": "audioEncoder(_:didStart:startPts:startTime:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "AudioEncoder", + "printedName": "LGCast.AudioEncoder", + "usr": "s:6LGCast12AudioEncoderC" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + }, + { + "kind": "TypeNominal", + "name": "CMTime", + "printedName": "CoreMedia.CMTime", + "usr": "c:@SA@CMTime" + }, + { + "kind": "TypeNominal", + "name": "DispatchTime", + "printedName": "Dispatch.DispatchTime", + "usr": "s:8Dispatch0A4TimeV" + } + ], + "declKind": "Func", + "usr": "s:6LGCast20AudioEncoderDelegateP05audioC0_8didStart8startPts0H4TimeyAA0bC0C_SbSo6CMTimea8Dispatch0lJ0VtF", + "mangledName": "$s6LGCast20AudioEncoderDelegateP05audioC0_8didStart8startPts0H4TimeyAA0bC0C_SbSo6CMTimea8Dispatch0lJ0VtF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.AudioEncoderDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "audioEncoder", + "printedName": "audioEncoder(_:with:didEncode:pts:priority:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "AudioEncoder", + "printedName": "LGCast.AudioEncoder", + "usr": "s:6LGCast12AudioEncoderC" + }, + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + }, + { + "kind": "TypeNominal", + "name": "Data", + "printedName": "Foundation.Data", + "usr": "s:10Foundation4DataV" + }, + { + "kind": "TypeNominal", + "name": "CMTime", + "printedName": "CoreMedia.CMTime", + "usr": "c:@SA@CMTime" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "s:6LGCast20AudioEncoderDelegateP05audioC0_4with9didEncode3pts8priorityyAA0bC0C_AA11MTAudioTypeO10Foundation4DataVSo6CMTimeaSitF", + "mangledName": "$s6LGCast20AudioEncoderDelegateP05audioC0_4with9didEncode3pts8priorityyAA0bC0C_AA11MTAudioTypeO10Foundation4DataVSo6CMTimeaSitF", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 where τ_0_0 : LGCast.AudioEncoderDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "s:6LGCast20AudioEncoderDelegateP", + "mangledName": "$s6LGCast20AudioEncoderDelegateP", + "moduleName": "LGCast", + "genericSig": "<τ_0_0 : AnyObject>", + "sugared_genericSig": "", + "declAttributes": [ + "AccessControl" + ] + }, + { + "kind": "TypeDecl", + "name": "AudioEncoder", + "printedName": "AudioEncoder", + "children": [ + { + "kind": "Function", + "name": "prepareAudioEncoder", + "printedName": "prepareAudioEncoder(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "CMSampleBuffer", + "printedName": "CoreMedia.CMSampleBuffer", + "usr": "c:@T@CMSampleBufferRef" + } + ], + "declKind": "Func", + "usr": "s:6LGCast12AudioEncoderC07preparebC0yySo17CMSampleBufferRefaF", + "mangledName": "$s6LGCast12AudioEncoderC07preparebC0yySo17CMSampleBufferRefaF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "processAudioSampleBuffer", + "printedName": "processAudioSampleBuffer(_:baseTime:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "CMSampleBuffer", + "printedName": "CoreMedia.CMSampleBuffer", + "usr": "c:@T@CMSampleBufferRef" + }, + { + "kind": "TypeNominal", + "name": "CMTime", + "printedName": "CoreMedia.CMTime", + "usr": "c:@SA@CMTime" + } + ], + "declKind": "Func", + "usr": "s:6LGCast12AudioEncoderC07processB12SampleBuffer_8baseTimeySo08CMSampleF3Refa_So6CMTimeatF", + "mangledName": "$s6LGCast12AudioEncoderC07processB12SampleBuffer_8baseTimeySo08CMSampleF3Refa_So6CMTimeatF", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "RawDocComment" + ], + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Class", + "usr": "s:6LGCast12AudioEncoderC", + "mangledName": "$s6LGCast12AudioEncoderC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "hasMissingDesignatedInitializers": true + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "GStreamerForLGCast", + "printedName": "GStreamerForLGCast", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "QuartzCore", + "printedName": "QuartzCore", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "RTPStreamerMediaType", + "printedName": "RTPStreamerMediaType", + "children": [ + { + "kind": "Var", + "name": "VIDEO", + "printedName": "VIDEO", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerMediaType.Type) -> LGCast.RTPStreamerMediaType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerMediaType", + "printedName": "LGCast.RTPStreamerMediaType", + "usr": "s:6LGCast20RTPStreamerMediaTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerMediaType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerMediaType", + "printedName": "LGCast.RTPStreamerMediaType", + "usr": "s:6LGCast20RTPStreamerMediaTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast20RTPStreamerMediaTypeO5VIDEOyA2CmF", + "mangledName": "$s6LGCast20RTPStreamerMediaTypeO5VIDEOyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "AUDIO", + "printedName": "AUDIO", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerMediaType.Type) -> LGCast.RTPStreamerMediaType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerMediaType", + "printedName": "LGCast.RTPStreamerMediaType", + "usr": "s:6LGCast20RTPStreamerMediaTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerMediaType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerMediaType", + "printedName": "LGCast.RTPStreamerMediaType", + "usr": "s:6LGCast20RTPStreamerMediaTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast20RTPStreamerMediaTypeO5AUDIOyA2CmF", + "mangledName": "$s6LGCast20RTPStreamerMediaTypeO5AUDIOyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "AV", + "printedName": "AV", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerMediaType.Type) -> LGCast.RTPStreamerMediaType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerMediaType", + "printedName": "LGCast.RTPStreamerMediaType", + "usr": "s:6LGCast20RTPStreamerMediaTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerMediaType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerMediaType", + "printedName": "LGCast.RTPStreamerMediaType", + "usr": "s:6LGCast20RTPStreamerMediaTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast20RTPStreamerMediaTypeO2AVyA2CmF", + "mangledName": "$s6LGCast20RTPStreamerMediaTypeO2AVyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.RTPStreamerMediaType?", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerMediaType", + "printedName": "LGCast.RTPStreamerMediaType", + "usr": "s:6LGCast20RTPStreamerMediaTypeO" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast20RTPStreamerMediaTypeO8rawValueACSgs5Int32V_tcfc", + "mangledName": "$s6LGCast20RTPStreamerMediaTypeO8rawValueACSgs5Int32V_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Var", + "usr": "s:6LGCast20RTPStreamerMediaTypeO8rawValues5Int32Vvp", + "mangledName": "$s6LGCast20RTPStreamerMediaTypeO8rawValues5Int32Vvp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast20RTPStreamerMediaTypeO8rawValues5Int32Vvg", + "mangledName": "$s6LGCast20RTPStreamerMediaTypeO8rawValues5Int32Vvg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "s:6LGCast20RTPStreamerMediaTypeO", + "mangledName": "$s6LGCast20RTPStreamerMediaTypeO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "enumRawTypeName": "Int32", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "RTPStreamerRscType", + "printedName": "RTPStreamerRscType", + "children": [ + { + "kind": "Var", + "name": "APP", + "printedName": "APP", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerRscType.Type) -> LGCast.RTPStreamerRscType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerRscType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast18RTPStreamerRscTypeO3APPyA2CmF", + "mangledName": "$s6LGCast18RTPStreamerRscTypeO3APPyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "FILE", + "printedName": "FILE", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerRscType.Type) -> LGCast.RTPStreamerRscType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerRscType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast18RTPStreamerRscTypeO4FILEyA2CmF", + "mangledName": "$s6LGCast18RTPStreamerRscTypeO4FILEyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "SOCKET", + "printedName": "SOCKET", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerRscType.Type) -> LGCast.RTPStreamerRscType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerRscType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast18RTPStreamerRscTypeO6SOCKETyA2CmF", + "mangledName": "$s6LGCast18RTPStreamerRscTypeO6SOCKETyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "CAMERA", + "printedName": "CAMERA", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerRscType.Type) -> LGCast.RTPStreamerRscType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerRscType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast18RTPStreamerRscTypeO6CAMERAyA2CmF", + "mangledName": "$s6LGCast18RTPStreamerRscTypeO6CAMERAyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.RTPStreamerRscType?", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerRscType", + "printedName": "LGCast.RTPStreamerRscType", + "usr": "s:6LGCast18RTPStreamerRscTypeO" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast18RTPStreamerRscTypeO8rawValueACSgs5Int32V_tcfc", + "mangledName": "$s6LGCast18RTPStreamerRscTypeO8rawValueACSgs5Int32V_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Var", + "usr": "s:6LGCast18RTPStreamerRscTypeO8rawValues5Int32Vvp", + "mangledName": "$s6LGCast18RTPStreamerRscTypeO8rawValues5Int32Vvp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast18RTPStreamerRscTypeO8rawValues5Int32Vvg", + "mangledName": "$s6LGCast18RTPStreamerRscTypeO8rawValues5Int32Vvg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "s:6LGCast18RTPStreamerRscTypeO", + "mangledName": "$s6LGCast18RTPStreamerRscTypeO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "enumRawTypeName": "Int32", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "RTPStreamerSRTPAuthType", + "printedName": "RTPStreamerSRTPAuthType", + "children": [ + { + "kind": "Var", + "name": "NONE", + "printedName": "NONE", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerSRTPAuthType.Type) -> LGCast.RTPStreamerSRTPAuthType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPAuthType", + "printedName": "LGCast.RTPStreamerSRTPAuthType", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerSRTPAuthType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPAuthType", + "printedName": "LGCast.RTPStreamerSRTPAuthType", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO4NONEyA2CmF", + "mangledName": "$s6LGCast23RTPStreamerSRTPAuthTypeO4NONEyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "HMAC_SHA1_32", + "printedName": "HMAC_SHA1_32", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerSRTPAuthType.Type) -> LGCast.RTPStreamerSRTPAuthType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPAuthType", + "printedName": "LGCast.RTPStreamerSRTPAuthType", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerSRTPAuthType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPAuthType", + "printedName": "LGCast.RTPStreamerSRTPAuthType", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO12HMAC_SHA1_32yA2CmF", + "mangledName": "$s6LGCast23RTPStreamerSRTPAuthTypeO12HMAC_SHA1_32yA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "HMAC_SHA1_80", + "printedName": "HMAC_SHA1_80", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerSRTPAuthType.Type) -> LGCast.RTPStreamerSRTPAuthType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPAuthType", + "printedName": "LGCast.RTPStreamerSRTPAuthType", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerSRTPAuthType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPAuthType", + "printedName": "LGCast.RTPStreamerSRTPAuthType", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO12HMAC_SHA1_80yA2CmF", + "mangledName": "$s6LGCast23RTPStreamerSRTPAuthTypeO12HMAC_SHA1_80yA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.RTPStreamerSRTPAuthType?", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPAuthType", + "printedName": "LGCast.RTPStreamerSRTPAuthType", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO8rawValueACSgs5Int32V_tcfc", + "mangledName": "$s6LGCast23RTPStreamerSRTPAuthTypeO8rawValueACSgs5Int32V_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Var", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO8rawValues5Int32Vvp", + "mangledName": "$s6LGCast23RTPStreamerSRTPAuthTypeO8rawValues5Int32Vvp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO8rawValues5Int32Vvg", + "mangledName": "$s6LGCast23RTPStreamerSRTPAuthTypeO8rawValues5Int32Vvg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "s:6LGCast23RTPStreamerSRTPAuthTypeO", + "mangledName": "$s6LGCast23RTPStreamerSRTPAuthTypeO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "enumRawTypeName": "Int32", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "RTPStreamerSRTPCipherType", + "printedName": "RTPStreamerSRTPCipherType", + "children": [ + { + "kind": "Var", + "name": "NONE", + "printedName": "NONE", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerSRTPCipherType.Type) -> LGCast.RTPStreamerSRTPCipherType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerSRTPCipherType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO4NONEyA2CmF", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO4NONEyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "AES_128_ICM", + "printedName": "AES_128_ICM", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerSRTPCipherType.Type) -> LGCast.RTPStreamerSRTPCipherType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerSRTPCipherType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO11AES_128_ICMyA2CmF", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO11AES_128_ICMyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "AES_256_ICM", + "printedName": "AES_256_ICM", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerSRTPCipherType.Type) -> LGCast.RTPStreamerSRTPCipherType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerSRTPCipherType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO11AES_256_ICMyA2CmF", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO11AES_256_ICMyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "AES_128_GCM", + "printedName": "AES_128_GCM", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerSRTPCipherType.Type) -> LGCast.RTPStreamerSRTPCipherType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerSRTPCipherType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO11AES_128_GCMyA2CmF", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO11AES_128_GCMyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "AES_256_GCM", + "printedName": "AES_256_GCM", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.RTPStreamerSRTPCipherType.Type) -> LGCast.RTPStreamerSRTPCipherType", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.RTPStreamerSRTPCipherType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO11AES_256_GCMyA2CmF", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO11AES_256_GCMyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.RTPStreamerSRTPCipherType?", + "children": [ + { + "kind": "TypeNominal", + "name": "RTPStreamerSRTPCipherType", + "printedName": "LGCast.RTPStreamerSRTPCipherType", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO8rawValueACSgs5Int32V_tcfc", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO8rawValueACSgs5Int32V_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Var", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO8rawValues5Int32Vvp", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO8rawValues5Int32Vvp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO8rawValues5Int32Vvg", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO8rawValues5Int32Vvg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "s:6LGCast25RTPStreamerSRTPCipherTypeO", + "mangledName": "$s6LGCast25RTPStreamerSRTPCipherTypeO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "enumRawTypeName": "Int32", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "GStreamerForLGCast", + "printedName": "GStreamerForLGCast", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "GStreamerForLGCast", + "printedName": "GStreamerForLGCast", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "TypeDecl", + "name": "MTRTPPipeline", + "printedName": "MTRTPPipeline", + "children": [ + { + "kind": "Function", + "name": "getPipeline", + "printedName": "getPipeline(_:info:isSecure:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "UInt32", + "printedName": "Swift.UInt32", + "usr": "s:s6UInt32V" + }, + { + "kind": "TypeNominal", + "name": "MTMediaInfo", + "printedName": "LGCast.MTMediaInfo", + "usr": "c:@M@LGCast@objc(cs)MTMediaInfo" + }, + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Func", + "usr": "s:6LGCast13MTRTPPipelineC11getPipeline_4info8isSecureSSSgs6UInt32V_AA11MTMediaInfoCSbtFZ", + "mangledName": "$s6LGCast13MTRTPPipelineC11getPipeline_4info8isSecureSSSgs6UInt32V_AA11MTMediaInfoCSbtFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl" + ], + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Class", + "usr": "s:6LGCast13MTRTPPipelineC", + "mangledName": "$s6LGCast13MTRTPPipelineC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "hasMissingDesignatedInitializers": true + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "GStreamerForLGCast", + "printedName": "GStreamerForLGCast", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "TypeDecl", + "name": "MTRTPSecurity", + "printedName": "MTRTPSecurity", + "children": [ + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "MTRTPSecurity", + "printedName": "LGCast.MTRTPSecurity", + "usr": "c:@M@LGCast@objc(cs)MTRTPSecurity" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)MTRTPSecurity(im)init", + "mangledName": "$s6LGCast13MTRTPSecurityCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)MTRTPSecurity", + "mangledName": "$s6LGCast13MTRTPSecurityC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "MTRTPSecurityKey", + "printedName": "MTRTPSecurityKey", + "children": [ + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "MTRTPSecurityKey", + "printedName": "LGCast.MTRTPSecurityKey", + "usr": "c:@M@LGCast@objc(cs)MTRTPSecurityKey" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)MTRTPSecurityKey(im)init", + "mangledName": "$s6LGCast16MTRTPSecurityKeyCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)MTRTPSecurityKey", + "mangledName": "$s6LGCast16MTRTPSecurityKeyC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "MTMediaType", + "printedName": "MTMediaType", + "children": [ + { + "kind": "Var", + "name": "video", + "printedName": "video", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.MTMediaType.Type) -> LGCast.MTMediaType", + "children": [ + { + "kind": "TypeNominal", + "name": "MTMediaType", + "printedName": "LGCast.MTMediaType", + "usr": "s:6LGCast11MTMediaTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.MTMediaType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTMediaType", + "printedName": "LGCast.MTMediaType", + "usr": "s:6LGCast11MTMediaTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast11MTMediaTypeO5videoyA2CmF", + "mangledName": "$s6LGCast11MTMediaTypeO5videoyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "audio", + "printedName": "audio", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.MTMediaType.Type) -> LGCast.MTMediaType", + "children": [ + { + "kind": "TypeNominal", + "name": "MTMediaType", + "printedName": "LGCast.MTMediaType", + "usr": "s:6LGCast11MTMediaTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.MTMediaType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTMediaType", + "printedName": "LGCast.MTMediaType", + "usr": "s:6LGCast11MTMediaTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast11MTMediaTypeO5audioyA2CmF", + "mangledName": "$s6LGCast11MTMediaTypeO5audioyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Var", + "name": "av", + "printedName": "av", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.MTMediaType.Type) -> LGCast.MTMediaType", + "children": [ + { + "kind": "TypeNominal", + "name": "MTMediaType", + "printedName": "LGCast.MTMediaType", + "usr": "s:6LGCast11MTMediaTypeO" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.MTMediaType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTMediaType", + "printedName": "LGCast.MTMediaType", + "usr": "s:6LGCast11MTMediaTypeO" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "s:6LGCast11MTMediaTypeO2avyA2CmF", + "mangledName": "$s6LGCast11MTMediaTypeO2avyA2CmF", + "moduleName": "LGCast" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.MTMediaType?", + "children": [ + { + "kind": "TypeNominal", + "name": "MTMediaType", + "printedName": "LGCast.MTMediaType", + "usr": "s:6LGCast11MTMediaTypeO" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast11MTMediaTypeO8rawValueACSgs5Int32V_tcfc", + "mangledName": "$s6LGCast11MTMediaTypeO8rawValueACSgs5Int32V_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Var", + "usr": "s:6LGCast11MTMediaTypeO8rawValues5Int32Vvp", + "mangledName": "$s6LGCast11MTMediaTypeO8rawValues5Int32Vvp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast11MTMediaTypeO8rawValues5Int32Vvg", + "mangledName": "$s6LGCast11MTMediaTypeO8rawValues5Int32Vvg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "s:6LGCast11MTMediaTypeO", + "mangledName": "$s6LGCast11MTMediaTypeO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl" + ], + "enumRawTypeName": "Int32", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "MTMediaInfo", + "printedName": "MTMediaInfo", + "children": [ + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "MTMediaInfo", + "printedName": "LGCast.MTMediaInfo", + "usr": "c:@M@LGCast@objc(cs)MTMediaInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)MTMediaInfo(im)init", + "mangledName": "$s6LGCast11MTMediaInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)MTMediaInfo", + "mangledName": "$s6LGCast11MTMediaInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "MTVideoType", + "printedName": "MTVideoType", + "children": [ + { + "kind": "Var", + "name": "raw", + "printedName": "raw", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.MTVideoType.Type) -> LGCast.MTVideoType", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.MTVideoType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@MTVideoType@MTVideoTypeRaw", + "mangledName": "$s6LGCast11MTVideoTypeO3rawyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "h264", + "printedName": "h264", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.MTVideoType.Type) -> LGCast.MTVideoType", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.MTVideoType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@MTVideoType@MTVideoTypeH264", + "mangledName": "$s6LGCast11MTVideoTypeO4h264yA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Var", + "name": "mjpeg", + "printedName": "mjpeg", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.MTVideoType.Type) -> LGCast.MTVideoType", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.MTVideoType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@MTVideoType@MTVideoTypeMjpeg", + "mangledName": "$s6LGCast11MTVideoTypeO5mjpegyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 2 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.MTVideoType?", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoType", + "printedName": "LGCast.MTVideoType", + "usr": "c:@M@LGCast@E@MTVideoType" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast11MTVideoTypeO8rawValueACSgs5Int32V_tcfc", + "mangledName": "$s6LGCast11MTVideoTypeO8rawValueACSgs5Int32V_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Var", + "usr": "s:6LGCast11MTVideoTypeO8rawValues5Int32Vvp", + "mangledName": "$s6LGCast11MTVideoTypeO8rawValues5Int32Vvp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast11MTVideoTypeO8rawValues5Int32Vvg", + "mangledName": "$s6LGCast11MTVideoTypeO8rawValues5Int32Vvg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@MTVideoType", + "mangledName": "$s6LGCast11MTVideoTypeO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int32", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "MTVideoInfo", + "printedName": "MTVideoInfo", + "children": [ + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "MTVideoInfo", + "printedName": "LGCast.MTVideoInfo", + "usr": "c:@M@LGCast@objc(cs)MTVideoInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)MTVideoInfo(im)init", + "mangledName": "$s6LGCast11MTVideoInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)MTVideoInfo", + "mangledName": "$s6LGCast11MTVideoInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "MTAudioType", + "printedName": "MTAudioType", + "children": [ + { + "kind": "Var", + "name": "pcm", + "printedName": "pcm", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.MTAudioType.Type) -> LGCast.MTAudioType", + "children": [ + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.MTAudioType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@MTAudioType@MTAudioTypePcm", + "mangledName": "$s6LGCast11MTAudioTypeO3pcmyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 0 + }, + { + "kind": "Var", + "name": "aac", + "printedName": "aac", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "(LGCast.MTAudioType.Type) -> LGCast.MTAudioType", + "children": [ + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + }, + { + "kind": "TypeNominal", + "name": "Metatype", + "printedName": "LGCast.MTAudioType.Type", + "children": [ + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + } + ] + } + ] + } + ], + "declKind": "EnumElement", + "usr": "c:@M@LGCast@E@MTAudioType@MTAudioTypeAac", + "mangledName": "$s6LGCast11MTAudioTypeO3aacyA2CmF", + "moduleName": "LGCast", + "declAttributes": [ + "ObjC" + ], + "fixedbinaryorder": 1 + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(rawValue:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "LGCast.MTAudioType?", + "children": [ + { + "kind": "TypeNominal", + "name": "MTAudioType", + "printedName": "LGCast.MTAudioType", + "usr": "c:@M@LGCast@E@MTAudioType" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Constructor", + "usr": "s:6LGCast11MTAudioTypeO8rawValueACSgs5Int32V_tcfc", + "mangledName": "$s6LGCast11MTAudioTypeO8rawValueACSgs5Int32V_tcfc", + "moduleName": "LGCast", + "implicit": true, + "init_kind": "Designated" + }, + { + "kind": "Var", + "name": "rawValue", + "printedName": "rawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Var", + "usr": "s:6LGCast11MTAudioTypeO8rawValues5Int32Vvp", + "mangledName": "$s6LGCast11MTAudioTypeO8rawValues5Int32Vvp", + "moduleName": "LGCast", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ], + "declKind": "Accessor", + "usr": "s:6LGCast11MTAudioTypeO8rawValues5Int32Vvg", + "mangledName": "$s6LGCast11MTAudioTypeO8rawValues5Int32Vvg", + "moduleName": "LGCast", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Enum", + "usr": "c:@M@LGCast@E@MTAudioType", + "mangledName": "$s6LGCast11MTAudioTypeO", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "enumRawTypeName": "Int32", + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "RawRepresentable", + "printedName": "RawRepresentable", + "children": [ + { + "kind": "TypeWitness", + "name": "RawValue", + "printedName": "RawValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int32", + "printedName": "Swift.Int32", + "usr": "s:s5Int32V" + } + ] + } + ], + "usr": "s:SY", + "mangledName": "$sSY" + } + ] + }, + { + "kind": "TypeDecl", + "name": "MTAudioInfo", + "printedName": "MTAudioInfo", + "children": [ + { + "kind": "Constructor", + "name": "init", + "printedName": "init()", + "children": [ + { + "kind": "TypeNominal", + "name": "MTAudioInfo", + "printedName": "LGCast.MTAudioInfo", + "usr": "c:@M@LGCast@objc(cs)MTAudioInfo" + } + ], + "declKind": "Constructor", + "usr": "c:@M@LGCast@objc(cs)MTAudioInfo(im)init", + "mangledName": "$s6LGCast11MTAudioInfoCACycfc", + "moduleName": "LGCast", + "overriding": true, + "implicit": true, + "objc_name": "init", + "declAttributes": [ + "Dynamic", + "ObjC", + "Override" + ], + "init_kind": "Designated" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)MTAudioInfo", + "mangledName": "$s6LGCast11MTAudioInfoC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "AVFoundation", + "printedName": "AVFoundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "OSLog", + "printedName": "OSLog", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "TypeDecl", + "name": "Log", + "printedName": "Log", + "children": [ + { + "kind": "Function", + "name": "debug", + "printedName": "debug(_:file:function:line:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:Sa" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "hasDefaultArg": true, + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "s:6LGCast3LogC5debug_4file8function4lineyypd_S2SSitFZ", + "mangledName": "$s6LGCast3LogC5debug_4file8function4lineyypd_S2SSitFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "info", + "printedName": "info(_:file:function:line:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:Sa" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "hasDefaultArg": true, + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "s:6LGCast3LogC4info_4file8function4lineyypd_S2SSitFZ", + "mangledName": "$s6LGCast3LogC4info_4file8function4lineyypd_S2SSitFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "notice", + "printedName": "notice(_:file:function:line:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:Sa" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "hasDefaultArg": true, + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "s:6LGCast3LogC6notice_4file8function4lineyypd_S2SSitFZ", + "mangledName": "$s6LGCast3LogC6notice_4file8function4lineyypd_S2SSitFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "error", + "printedName": "error(_:file:function:line:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:Sa" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "hasDefaultArg": true, + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "s:6LGCast3LogC5error_4file8function4lineyypd_S2SSitFZ", + "mangledName": "$s6LGCast3LogC5error_4file8function4lineyypd_S2SSitFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "fault", + "printedName": "fault(_:file:function:line:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:Sa" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "hasDefaultArg": true, + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "hasDefaultArg": true, + "usr": "s:Si" + } + ], + "declKind": "Func", + "usr": "s:6LGCast3LogC5fault_4file8function4lineyypd_S2SSitFZ", + "mangledName": "$s6LGCast3LogC5fault_4file8function4lineyypd_S2SSitFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "debugLGCast", + "printedName": "debugLGCast(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "c:@CM@LGCast@objc(cs)Log(cm)debugLGCast:", + "mangledName": "$s6LGCast3LogC05debugA0yySSFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl", + "ObjC" + ], + "isFromExtension": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "infoLGCast", + "printedName": "infoLGCast(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "c:@CM@LGCast@objc(cs)Log(cm)infoLGCast:", + "mangledName": "$s6LGCast3LogC04infoA0yySSFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl", + "ObjC" + ], + "isFromExtension": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "noticeLGCast", + "printedName": "noticeLGCast(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "c:@CM@LGCast@objc(cs)Log(cm)noticeLGCast:", + "mangledName": "$s6LGCast3LogC06noticeA0yySSFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl", + "ObjC" + ], + "isFromExtension": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "errorLGCast", + "printedName": "errorLGCast(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "c:@CM@LGCast@objc(cs)Log(cm)errorLGCast:", + "mangledName": "$s6LGCast3LogC05errorA0yySSFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl", + "ObjC" + ], + "isFromExtension": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "faultLGCast", + "printedName": "faultLGCast(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "c:@CM@LGCast@objc(cs)Log(cm)faultLGCast:", + "mangledName": "$s6LGCast3LogC05faultA0yySSFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "Final", + "AccessControl", + "ObjC" + ], + "isFromExtension": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Class", + "usr": "c:@M@LGCast@objc(cs)Log", + "mangledName": "$s6LGCast3LogC", + "moduleName": "LGCast", + "declAttributes": [ + "AccessControl", + "RawDocComment", + "ObjC" + ], + "superclassUsr": "c:objc(cs)NSObject", + "hasMissingDesignatedInitializers": true, + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "VideoToolbox", + "printedName": "VideoToolbox", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "CoreImage", + "printedName": "CoreImage", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "ReplayKit", + "printedName": "ReplayKit", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "VideoToolbox", + "printedName": "VideoToolbox", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "MediaPlayer", + "printedName": "MediaPlayer", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "AVFoundation", + "printedName": "AVFoundation", + "declKind": "Import", + "moduleName": "LGCast", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "Import", + "name": "UIKit", + "printedName": "UIKit", + "declKind": "Import", + "moduleName": "LGCast" + }, + { + "kind": "TypeDecl", + "name": "AudioStreamBasicDescription", + "printedName": "AudioStreamBasicDescription", + "children": [ + { + "kind": "Function", + "name": "==", + "printedName": "==(_:_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + }, + { + "kind": "TypeNominal", + "name": "AudioStreamBasicDescription", + "printedName": "CoreAudioTypes.AudioStreamBasicDescription", + "usr": "c:@S@AudioStreamBasicDescription" + }, + { + "kind": "TypeNominal", + "name": "AudioStreamBasicDescription", + "printedName": "CoreAudioTypes.AudioStreamBasicDescription", + "usr": "c:@S@AudioStreamBasicDescription" + } + ], + "declKind": "Func", + "usr": "s:So27AudioStreamBasicDescriptionV6LGCastE2eeoiySbAB_ABtFZ", + "mangledName": "$sSo27AudioStreamBasicDescriptionV6LGCastE2eeoiySbAB_ABtFZ", + "moduleName": "LGCast", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "isFromExtension": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Struct", + "usr": "c:@S@AudioStreamBasicDescription", + "moduleName": "CoreAudioTypes", + "isExternal": true, + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + } + ] + }, + { + "kind": "TypeDecl", + "name": "UIDevice", + "printedName": "UIDevice", + "children": [ + { + "kind": "Var", + "name": "isPhoneDevice", + "printedName": "isPhoneDevice", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "s:So8UIDeviceC6LGCastE13isPhoneDeviceSbvp", + "mangledName": "$sSo8UIDeviceC6LGCastE13isPhoneDeviceSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "Custom", + "AccessControl" + ], + "isFromExtension": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "s:So8UIDeviceC6LGCastE13isPhoneDeviceSbvg", + "mangledName": "$sSo8UIDeviceC6LGCastE13isPhoneDeviceSbvg", + "moduleName": "LGCast", + "isFromExtension": true, + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "isPadDevice", + "printedName": "isPadDevice", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Var", + "usr": "s:So8UIDeviceC6LGCastE11isPadDeviceSbvp", + "mangledName": "$sSo8UIDeviceC6LGCastE11isPadDeviceSbvp", + "moduleName": "LGCast", + "declAttributes": [ + "Custom", + "AccessControl" + ], + "isFromExtension": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + } + ], + "declKind": "Accessor", + "usr": "s:So8UIDeviceC6LGCastE11isPadDeviceSbvg", + "mangledName": "$sSo8UIDeviceC6LGCastE11isPadDeviceSbvg", + "moduleName": "LGCast", + "isFromExtension": true, + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "modelName", + "printedName": "modelName", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "s:So8UIDeviceC6LGCastE9modelNameSSvp", + "mangledName": "$sSo8UIDeviceC6LGCastE9modelNameSSvp", + "moduleName": "LGCast", + "declAttributes": [ + "Custom", + "AccessControl" + ], + "isFromExtension": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "s:So8UIDeviceC6LGCastE9modelNameSSvg", + "mangledName": "$sSo8UIDeviceC6LGCastE9modelNameSSvg", + "moduleName": "LGCast", + "isFromExtension": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Class", + "usr": "c:objc(cs)UIDevice", + "moduleName": "UIKit", + "isOpen": true, + "intro_iOS": "2.0", + "objc_name": "UIDevice", + "declAttributes": [ + "Available", + "ObjC", + "NonSendable", + "Custom", + "Dynamic" + ], + "superclassUsr": "c:objc(cs)NSObject", + "isExternal": true, + "inheritsConvenienceInitializers": true, + "superclassNames": [ + "ObjectiveC.NSObject" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + }, + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "CVarArg", + "printedName": "CVarArg", + "usr": "s:s7CVarArgP", + "mangledName": "$ss7CVarArgP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObservingPublishing", + "printedName": "_KeyValueCodingAndObservingPublishing", + "usr": "s:10Foundation37_KeyValueCodingAndObservingPublishingP", + "mangledName": "$s10Foundation37_KeyValueCodingAndObservingPublishingP" + }, + { + "kind": "Conformance", + "name": "_KeyValueCodingAndObserving", + "printedName": "_KeyValueCodingAndObserving", + "usr": "s:10Foundation27_KeyValueCodingAndObservingP", + "mangledName": "$s10Foundation27_KeyValueCodingAndObservingP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + } + ] + } + ], + "json_format_version": 8 + }, + "ConstValues": [ + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraError.swift", + "kind": "IntegerLiteral", + "offset": 190, + "length": 16, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraError.swift", + "kind": "IntegerLiteral", + "offset": 216, + "length": 14, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraError.swift", + "kind": "IntegerLiteral", + "offset": 240, + "length": 18, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraError.swift", + "kind": "IntegerLiteral", + "offset": 190, + "length": 16, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraError.swift", + "kind": "IntegerLiteral", + "offset": 216, + "length": 14, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraError.swift", + "kind": "IntegerLiteral", + "offset": 240, + "length": 18, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "IntegerLiteral", + "offset": 207, + "length": 15, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "IntegerLiteral", + "offset": 232, + "length": 20, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "IntegerLiteral", + "offset": 262, + "length": 15, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "IntegerLiteral", + "offset": 287, + "length": 16, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "IntegerLiteral", + "offset": 207, + "length": 15, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "IntegerLiteral", + "offset": 232, + "length": 20, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "IntegerLiteral", + "offset": 262, + "length": 15, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "IntegerLiteral", + "offset": 287, + "length": 16, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringApi.swift", + "kind": "BooleanLiteral", + "offset": 825, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringError.swift", + "kind": "IntegerLiteral", + "offset": 191, + "length": 16, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringError.swift", + "kind": "IntegerLiteral", + "offset": 217, + "length": 14, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringError.swift", + "kind": "IntegerLiteral", + "offset": 241, + "length": 18, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringError.swift", + "kind": "IntegerLiteral", + "offset": 191, + "length": 16, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringError.swift", + "kind": "IntegerLiteral", + "offset": 217, + "length": 14, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastMirroring\/LGCastMirroringError.swift", + "kind": "IntegerLiteral", + "offset": 241, + "length": 18, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 364, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 403, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 506, + "length": 5, + "value": "44100" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 547, + "length": 2, + "value": "64" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 552, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 596, + "length": 1, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 694, + "length": 4, + "value": "1920" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 733, + "length": 4, + "value": "1080" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "BooleanLiteral", + "offset": 781, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 824, + "length": 2, + "value": "60" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 862, + "length": 1, + "value": "6" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 866, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 873, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 1309, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 1349, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 1386, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "StringLiteral", + "offset": 1434, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 1574, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 1824, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 1889, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 1958, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2038, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2119, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2162, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2199, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "StringLiteral", + "offset": 2250, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "BooleanLiteral", + "offset": 2296, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "StringLiteral", + "offset": 2348, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2533, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2628, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2664, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "BooleanLiteral", + "offset": 2709, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "StringLiteral", + "offset": 2760, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2800, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 2839, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 3366, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 3402, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 3439, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 3561, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastInfo.swift", + "kind": "IntegerLiteral", + "offset": 3600, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraApi.swift", + "kind": "IntegerLiteral", + "offset": 431, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraApi.swift", + "kind": "IntegerLiteral", + "offset": 463, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraApi.swift", + "kind": "StringLiteral", + "offset": 559, + "length": 21, + "value": "\"KEY_CAMERA_POSITION\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraApi.swift", + "kind": "StringLiteral", + "offset": 637, + "length": 27, + "value": "\"KEY_PREVIEW_VIDEO_GRAVITY\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraApi.swift", + "kind": "StringLiteral", + "offset": 725, + "length": 31, + "value": "\"KEY_IS_SUPPORT_LANDSCAPE_MODE\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraApi.swift", + "kind": "BooleanLiteral", + "offset": 925, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 196, + "length": 16, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 222, + "length": 10, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 242, + "length": 6, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 258, + "length": 10, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 278, + "length": 12, + "value": "5" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 300, + "length": 8, + "value": "6" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 196, + "length": 16, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 222, + "length": 10, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 242, + "length": 6, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 258, + "length": 10, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 278, + "length": 12, + "value": "5" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 300, + "length": 8, + "value": "6" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 375, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 393, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 375, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 393, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 480, + "length": 12, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 502, + "length": 16, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 480, + "length": 12, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 502, + "length": 16, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 594, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 614, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 594, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 614, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 700, + "length": 6, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 728, + "length": 4, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 755, + "length": 10, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 775, + "length": 10, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 795, + "length": 10, + "value": "5" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 815, + "length": 11, + "value": "6" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 836, + "length": 13, + "value": "7" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 859, + "length": 13, + "value": "8" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 882, + "length": 14, + "value": "9" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 700, + "length": 6, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 728, + "length": 4, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 755, + "length": 10, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 775, + "length": 10, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 795, + "length": 10, + "value": "5" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 815, + "length": 11, + "value": "6" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 836, + "length": 13, + "value": "7" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 859, + "length": 13, + "value": "8" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Interface\/LGCastCamera\/LGCastCameraProperties.swift", + "kind": "IntegerLiteral", + "offset": 882, + "length": 14, + "value": "9" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 179, + "length": 19, + "value": "\"KEY_USE_PC_PLAYER\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "BooleanLiteral", + "offset": 214, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 299, + "length": 16, + "value": "\"KEY_VIDEO_TYPE\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 432, + "length": 16, + "value": "\"KEY_AUDIO_TYPE\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 564, + "length": 17, + "value": "\"KEY_ENABLE_SRTP\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "BooleanLiteral", + "offset": 597, + "length": 4, + "value": "true" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 680, + "length": 19, + "value": "\"KEY_PC_IP_ADDRESS\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 715, + "length": 13, + "value": "\"192.168.1.1\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 810, + "length": 19, + "value": "\"KEY_PC_VIDEO_PORT\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "IntegerLiteral", + "offset": 845, + "length": 4, + "value": "5000" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 928, + "length": 19, + "value": "\"KEY_PC_AUDIO_PORT\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "IntegerLiteral", + "offset": 963, + "length": 4, + "value": "5002" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1046, + "length": 18, + "value": "\"KEY_TV_DEVICE_ID\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1080, + "length": 9, + "value": "\"Unknown\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1171, + "length": 26, + "value": "\"KEY_IS_RUNNING_EXTENSION\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "BooleanLiteral", + "offset": 1213, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1305, + "length": 19, + "value": "\"KEY_VIDEO_BITRATE\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "IntegerLiteral", + "offset": 1340, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1421, + "length": 22, + "value": "\"KEY_VIDEO_RESOLUTION\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "IntegerLiteral", + "offset": 1459, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1549, + "length": 28, + "value": "\"KEY_VIDEO_RESOLUTION_WITDH\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "IntegerLiteral", + "offset": 1593, + "length": 4, + "value": "1920" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1688, + "length": 29, + "value": "\"KEY_VIDEO_RESOLUTION_HEIGHT\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "IntegerLiteral", + "offset": 1733, + "length": 4, + "value": "1080" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1821, + "length": 19, + "value": "\"KEY_AUDIO_BITRATE\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "IntegerLiteral", + "offset": 1856, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 1945, + "length": 21, + "value": "\"KEY_ENABLE_RIVISION\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "BooleanLiteral", + "offset": 1982, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "StringLiteral", + "offset": 2078, + "length": 26, + "value": "\"KEY_VIDEO_IFRAME_LATENCY\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Preference\/UserDefaults+LGCast.swift", + "kind": "IntegerLiteral", + "offset": 2120, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/MasterKeyFactory.swift", + "kind": "IntegerLiteral", + "offset": 527, + "length": 1, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/MasterKeyFactory.swift", + "kind": "IntegerLiteral", + "offset": 568, + "length": 2, + "value": "30" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/MasterKeyFactory.swift", + "kind": "IntegerLiteral", + "offset": 611, + "length": 2, + "value": "10" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "BooleanLiteral", + "offset": 893, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "IntegerLiteral", + "offset": 926, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "IntegerLiteral", + "offset": 957, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "BooleanLiteral", + "offset": 1212, + "length": 4, + "value": "true" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "StringLiteral", + "offset": 1238, + "length": 51, + "value": "\"change TV orientation mode \"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "StringLiteral", + "offset": 1288, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "BooleanLiteral", + "offset": 1442, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "BooleanLiteral", + "offset": 1529, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "Array", + "offset": 2467, + "length": 24, + "value": "[0x00, 0x00, 0x00, 0x01]" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "IntegerLiteral", + "offset": 2523, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "IntegerLiteral", + "offset": 2556, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "IntegerLiteral", + "offset": 2932, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "IntegerLiteral", + "offset": 2965, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "BooleanLiteral", + "offset": 3018, + "length": 4, + "value": "true" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "StringLiteral", + "offset": 3044, + "length": 34, + "value": "\"change Screen mode \"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "StringLiteral", + "offset": 3077, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/VideoEncoder.swift", + "kind": "StringLiteral", + "offset": 501, + "length": 12, + "value": "\"LGCast.VideoEncoder\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/AudioEncoder.swift", + "kind": "IntegerLiteral", + "offset": 539, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/AudioEncoder.swift", + "kind": "IntegerLiteral", + "offset": 718, + "length": 5, + "value": "44100" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/AudioEncoder.swift", + "kind": "IntegerLiteral", + "offset": 775, + "length": 1, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/AudioEncoder.swift", + "kind": "IntegerLiteral", + "offset": 825, + "length": 2, + "value": "64" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/AudioEncoder.swift", + "kind": "IntegerLiteral", + "offset": 830, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/AudioEncoder.swift", + "kind": "IntegerLiteral", + "offset": 883, + "length": 2, + "value": "32" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/AudioEncoder.swift", + "kind": "IntegerLiteral", + "offset": 888, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Encoder\/AudioEncoder.swift", + "kind": "BooleanLiteral", + "offset": 1102, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 253, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 272, + "length": 7, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 293, + "length": 5, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 312, + "length": 4, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 330, + "length": 5, + "value": "5" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 253, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 272, + "length": 7, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 293, + "length": 5, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 312, + "length": 4, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 330, + "length": 5, + "value": "5" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 253, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 272, + "length": 7, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 293, + "length": 5, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 312, + "length": 4, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamer.swift", + "kind": "IntegerLiteral", + "offset": 330, + "length": 5, + "value": "5" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "BooleanLiteral", + "offset": 246, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 283, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 545, + "length": 4, + "value": "1920" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 573, + "length": 4, + "value": "1080" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 604, + "length": 2, + "value": "30" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 631, + "length": 6, + "value": "200000" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "BooleanLiteral", + "offset": 670, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 776, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 878, + "length": 5, + "value": "44100" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 911, + "length": 1, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 937, + "length": 2, + "value": "64" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 942, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 1047, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "IntegerLiteral", + "offset": 1100, + "length": 10, + "value": "1356955624" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "BooleanLiteral", + "offset": 1206, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerConfig.swift", + "kind": "BooleanLiteral", + "offset": 1241, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 169, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 180, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 195, + "length": 2, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 169, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 180, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 195, + "length": 2, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 169, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 180, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 195, + "length": 2, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 536, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 547, + "length": 4, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 561, + "length": 6, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 577, + "length": 6, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 536, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 547, + "length": 4, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 561, + "length": 6, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 577, + "length": 6, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 536, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 547, + "length": 4, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 561, + "length": 6, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 577, + "length": 6, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 714, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 725, + "length": 12, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 747, + "length": 12, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 714, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 725, + "length": 12, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 747, + "length": 12, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 714, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 725, + "length": 12, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 747, + "length": 12, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 892, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 903, + "length": 11, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 924, + "length": 11, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 945, + "length": 11, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 966, + "length": 11, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 892, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 903, + "length": 11, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 924, + "length": 11, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 945, + "length": 11, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 966, + "length": 11, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 892, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 903, + "length": 11, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 924, + "length": 11, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 945, + "length": 11, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreamerType.swift", + "kind": "IntegerLiteral", + "offset": 966, + "length": 11, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTP\/MTRTPSecurity.swift", + "kind": "IntegerLiteral", + "offset": 298, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTMediaInfo.swift", + "kind": "IntegerLiteral", + "offset": 152, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTMediaInfo.swift", + "kind": "IntegerLiteral", + "offset": 167, + "length": 2, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTMediaInfo.swift", + "kind": "IntegerLiteral", + "offset": 152, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTMediaInfo.swift", + "kind": "IntegerLiteral", + "offset": 167, + "length": 2, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTMediaInfo.swift", + "kind": "IntegerLiteral", + "offset": 152, + "length": 5, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTMediaInfo.swift", + "kind": "IntegerLiteral", + "offset": 167, + "length": 2, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 156, + "length": 4, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 170, + "length": 5, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 156, + "length": 4, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 170, + "length": 5, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 750, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 775, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 801, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 830, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 857, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "StringLiteral", + "offset": 923, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTVideoInfo.swift", + "kind": "IntegerLiteral", + "offset": 959, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTAudioInfo.swift", + "kind": "IntegerLiteral", + "offset": 156, + "length": 3, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTAudioInfo.swift", + "kind": "IntegerLiteral", + "offset": 156, + "length": 3, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTAudioInfo.swift", + "kind": "IntegerLiteral", + "offset": 640, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTAudioInfo.swift", + "kind": "IntegerLiteral", + "offset": 669, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTAudioInfo.swift", + "kind": "IntegerLiteral", + "offset": 701, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTAudioInfo.swift", + "kind": "StringLiteral", + "offset": 767, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/Media\/MTAudioInfo.swift", + "kind": "IntegerLiteral", + "offset": 803, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraDevice.swift", + "kind": "StringLiteral", + "offset": 1167, + "length": 22, + "value": "\"videoDataOutputQueue\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraDevice.swift", + "kind": "StringLiteral", + "offset": 1251, + "length": 22, + "value": "\"audioDataOutputQueue\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraDevice.swift", + "kind": "BooleanLiteral", + "offset": 1303, + "length": 4, + "value": "true" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraDevice.swift", + "kind": "BooleanLiteral", + "offset": 1335, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/Log.swift", + "kind": "IntegerLiteral", + "offset": 666, + "length": 4, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/Log.swift", + "kind": "IntegerLiteral", + "offset": 684, + "length": 6, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/Log.swift", + "kind": "IntegerLiteral", + "offset": 704, + "length": 5, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/Log.swift", + "kind": "IntegerLiteral", + "offset": 723, + "length": 5, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/Log.swift", + "kind": "IntegerLiteral", + "offset": 666, + "length": 4, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/Log.swift", + "kind": "IntegerLiteral", + "offset": 684, + "length": 6, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/Log.swift", + "kind": "IntegerLiteral", + "offset": 704, + "length": 5, + "value": "3" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Utils\/Log.swift", + "kind": "IntegerLiteral", + "offset": 723, + "length": 5, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 201, + "length": 4, + "value": "1920" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 243, + "length": 4, + "value": "1080" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 282, + "length": 5, + "value": "90000" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 321, + "length": 2, + "value": "60" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 355, + "length": 1, + "value": "6" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 359, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 366, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 454, + "length": 2, + "value": "16" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 497, + "length": 1, + "value": "2" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 536, + "length": 5, + "value": "44100" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 582, + "length": 2, + "value": "64" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "IntegerLiteral", + "offset": 587, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "StringLiteral", + "offset": 633, + "length": 10, + "value": "\"40002420\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "StringLiteral", + "offset": 691, + "length": 20, + "value": "\"landscape|portrait\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringConfig.swift", + "kind": "Array", + "offset": 748, + "length": 285, + "value": "[0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89]" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "BooleanLiteral", + "offset": 800, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "BooleanLiteral", + "offset": 842, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "BooleanLiteral", + "offset": 986, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "BooleanLiteral", + "offset": 1028, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "BooleanLiteral", + "offset": 1110, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "IntegerLiteral", + "offset": 1319, + "length": 13, + "value": "1000000000" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "IntegerLiteral", + "offset": 1374, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "IntegerLiteral", + "offset": 1413, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "IntegerLiteral", + "offset": 1537, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "IntegerLiteral", + "offset": 1564, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "StringLiteral", + "offset": 1905, + "length": 73, + "value": "\"change device orientation mode \"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "StringLiteral", + "offset": 1952, + "length": 10, + "value": "\"portrait\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "StringLiteral", + "offset": 1965, + "length": 11, + "value": "\"landscape\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "StringLiteral", + "offset": 1977, + "length": 2, + "value": "\"\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "BooleanLiteral", + "offset": 2148, + "length": 4, + "value": "true" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "BooleanLiteral", + "offset": 1449, + "length": 4, + "value": "true" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "IntegerLiteral", + "offset": 9103, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringService.swift", + "kind": "IntegerLiteral", + "offset": 9585, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Mirroring\/MirroringVolume.swift", + "kind": "IntegerLiteral", + "offset": 202, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreaming.swift", + "kind": "StringLiteral", + "offset": 242, + "length": 19, + "value": "\"videoSendingQueue\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreaming.swift", + "kind": "StringLiteral", + "offset": 341, + "length": 19, + "value": "\"audioSendingQueue\"" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreaming.swift", + "kind": "IntegerLiteral", + "offset": 442, + "length": 10, + "value": "30000000" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreaming.swift", + "kind": "BooleanLiteral", + "offset": 516, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreaming.swift", + "kind": "BooleanLiteral", + "offset": 561, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreaming.swift", + "kind": "IntegerLiteral", + "offset": 606, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Common\/Transfer\/RTPStreaming.swift", + "kind": "IntegerLiteral", + "offset": 647, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 150, + "length": 4, + "value": "1280" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 187, + "length": 3, + "value": "720" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 228, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "BooleanLiteral", + "offset": 274, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "BooleanLiteral", + "offset": 311, + "length": 4, + "value": "true" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 352, + "length": 2, + "value": "50" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 389, + "length": 2, + "value": "90" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 424, + "length": 1, + "value": "0" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 458, + "length": 3, + "value": "100" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 501, + "length": 4, + "value": "7500" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 541, + "length": 4, + "value": "2300" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 581, + "length": 5, + "value": "10000" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 721, + "length": 2, + "value": "30" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 755, + "length": 1, + "value": "4" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 759, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 766, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 1033, + "length": 2, + "value": "16" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 1076, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 1115, + "length": 5, + "value": "44100" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 1161, + "length": 2, + "value": "64" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "IntegerLiteral", + "offset": 1166, + "length": 4, + "value": "1024" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraConfig.swift", + "kind": "Array", + "offset": 1210, + "length": 285, + "value": "[0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89]" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "BooleanLiteral", + "offset": 1210, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "BooleanLiteral", + "offset": 1252, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "BooleanLiteral", + "offset": 1392, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "BooleanLiteral", + "offset": 1434, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "BooleanLiteral", + "offset": 1513, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "BooleanLiteral", + "offset": 1556, + "length": 5, + "value": "false" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "IntegerLiteral", + "offset": 8188, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "IntegerLiteral", + "offset": 8670, + "length": 1, + "value": "1" + }, + { + "filePath": "\/Users\/hyojin\/Source\/appcast_ios\/appcast_ios\/3.ConnectSDKIntegration\/LGCast\/LGCast\/Camera\/CameraService.swift", + "kind": "StringLiteral", + "offset": 800, + "length": 13, + "value": "\"LGCast.CameraService\"" + } + ] +} \ No newline at end of file diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface new file mode 100644 index 00000000..cc51b8be --- /dev/null +++ b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface @@ -0,0 +1,479 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51) +// swift-module-flags: -target x86_64-apple-ios11.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name LGCast +// swift-module-flags-ignorable: -enable-bare-slash-regex +import AVFoundation +import AudioToolbox +import CoreImage +import Foundation +import GStreamerForLGCast +@_exported import LGCast +import MediaPlayer +import OSLog +import QuartzCore +import ReplayKit +import Swift +import UIKit +import VideoToolbox +import _Concurrency +import _StringProcessing +@objc public enum LGCastCameraError : Swift.Int { + case unknown + case connectionClosed + case deviceShutdown + case rendererTerminated + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastMirroringEvent : Swift.Int { + case unknown + case updateVideoInfo + case updateVideoVideoSize + case updateAudioInfo + case updatePlayerInfo + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public protocol LGCastMirroringApiDelegate { + @objc func lgcastMirroringDidStart(result: Swift.Bool) + @objc func lgcastMirroringDidStop(result: Swift.Bool) + @objc func lgcastMirroringErrorDidOccur(error: LGCast.LGCastMirroringError) + @objc func lgcastMirroringUpdateEvent(event: LGCast.LGCastMirroringEvent, info: LGCast.LGCastMirroringInfo?) +} +@_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers @objc public class LGCastMirroringApi : ObjectiveC.NSObject { + @objc public static let shared: LGCast.LGCastMirroringApi + public var isRunning: Swift.Bool { + get + } + public var isTvInputEnabled: Swift.Bool { + get + } + @objc public func isSupportedOsVersion() -> Swift.Bool + @objc public func setDelegate(_ delegate: LGCast.LGCastMirroringApiDelegate) + @objc public func setMediaSettings(_ mediaInfo: LGCast.LGCastMirroringMediaSettings?) -> LGCast.LGCastMirroringInfo? + @objc public func generateMirroringMasterKey(_ publicKey: Swift.String) -> Swift.Array? + @objc public func startMirroring(_ settings: LGCast.LGCastDeviceSettings?) + @objc public func updateDisplayOrientation(isPortraitMode: Swift.Bool) -> LGCast.LGCastMirroringInfo? + @objc public func stopMirroring() + public func startMirroringWithCapture(_ settings: LGCast.LGCastDeviceSettings?) + public func stopMirroringWithCapture() + @objc public func pushSampleBuffer(_ sampleBuffer: CoreMedia.CMSampleBuffer, with sampleBufferType: ReplayKit.RPSampleBufferType) + public func pushSampleBuffer(_ sampleBuffer: CoreMedia.CMSampleBuffer, with mediaType: LGCast.RTPStreamerMediaType) + @objc deinit +} +@objc public enum LGCastMirroringError : Swift.Int { + case unknown + case connectionClosed + case deviceShutdown + case rendererTerminated + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@_inheritsConvenienceInitializers @objc public class LGCastSecurityKey : ObjectiveC.NSObject { + @objc public var masterKey: Swift.String? + @objc public var mki: Swift.String? + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastDeviceSettings : ObjectiveC.NSObject { + @objc public var host: Swift.String? + @objc public var audioPort: Swift.Int + @objc public var videoPort: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringAudioSettings : ObjectiveC.NSObject { + @objc public var sampleRate: Swift.Int + @objc public var bitrate: Swift.Int + @objc public var channelCnt: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringVideoSettings : ObjectiveC.NSObject { + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc public var isPortraitMode: Swift.Bool + @objc public var framerate: Swift.Int + @objc public var bitrate: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringMediaSettings : ObjectiveC.NSObject { + @objc public var audio: LGCast.LGCastMirroringAudioSettings? + @objc public var video: LGCast.LGCastMirroringVideoSettings? + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringAudioInfo : ObjectiveC.NSObject { + @objc public var type: LGCast.MTAudioType + @objc public var codec: Swift.String { + @objc get + } + @objc public var samplingRate: Swift.Int + @objc public var channelCnt: Swift.Int + @objc public var bitrate: Swift.Int + @objc public var streamMuxConfig: Swift.String + @objc public var codecData: Foundation.Data? + @objc public var host: Swift.String? + @objc public var port: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringVideoInfo : ObjectiveC.NSObject { + @objc public var type: LGCast.MTVideoType + @objc public var codec: Swift.String { + @objc get + } + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc public var activeWidth: Swift.Int + @objc public var activeHeight: Swift.Int + @objc public var framerate: Swift.Int + @objc public var samplingRate: Swift.Int + @objc public var bitrate: Swift.Int + @objc public var supportOrientation: Swift.String + @objc public var isPortraitMode: Swift.Bool + @objc public var screenOrientation: Swift.String + @objc public var streamMuxConfig: Swift.String? + @objc public var codecData: Foundation.Data? + @objc public var host: Swift.String? + @objc public var port: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringPlayerInfo : ObjectiveC.NSObject { + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc public var isPortraitMode: Swift.Bool + @objc public var ipAddress: Swift.String + @objc public var audioPort: Swift.Int + @objc public var videoPort: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringInfo : ObjectiveC.NSObject { + @objc public var audioInfo: LGCast.LGCastMirroringAudioInfo? + @objc public var videoInfo: LGCast.LGCastMirroringVideoInfo? + @objc public var playerInfo: LGCast.LGCastMirroringPlayerInfo? + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastCameraParameterInfo : ObjectiveC.NSObject { + @objc public var audio: LGCast.LGCastCamperaPropertyStatus + @objc public var autoWhiteBalance: LGCast.LGCastCamperaPropertyStatus + @objc public var brightness: Swift.Int + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc public var facing: LGCast.LGCastCameraPosition + @objc public var whiteBalance: Swift.Int + @objc public var rotation: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@objc public protocol LGCastCameraApiDelegate { + @objc func lgcastCameraDidPlay() + @objc func lgcastCameraDidChange(property: LGCast.LGCastCameraProperty) + @objc func lgcastCameraErrorDidOccur(error: LGCast.LGCastCameraError) +} +@_inheritsConvenienceInitializers @objc public class LGCastCameraResolutionInfo : ObjectiveC.NSObject { + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@objc @_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers public class LGCastCameraApi : ObjectiveC.NSObject { + @objc public static let KEY_CAMERA_POSITION: Swift.String + @objc public static let KEY_PREVIEW_VIDEO_GRAVITY: Swift.String + @objc public static let KEY_IS_SUPPORT_LANDSCAPE_MODE: Swift.String + @objc public static let shared: LGCast.LGCastCameraApi + @objc weak public var delegate: LGCast.LGCastCameraApiDelegate? + @objc public var isRunning: Swift.Bool { + get + } + public var isTvInputEnabled: Swift.Bool { + get + } + @objc public func isSupportedOsVersion() -> Swift.Bool + @objc public func createCameraPreviewView(_ options: [Swift.String : Any]? = nil) -> UIKit.UIView + @objc public func setPreviewView(_ view: LGCast.LGCastCameraPreviewView, options: [Swift.String : Any]? = nil) + @objc public func generateCameraMasterKey(_ publicKey: Swift.String) -> Swift.Array? + @objc public func getCameraProperties() -> LGCast.LGCastCameraParameterInfo? + @objc public func setCameraProperties(property: LGCast.LGCastCameraProperty, value: Swift.Int) -> Swift.Bool + @objc public func startRemoteCamera(_ settings: LGCast.LGCastDeviceSettings) + @objc public func stopRemoteCamera() + @objc public func getSupportedResolutions() -> [LGCast.LGCastCameraResolutionInfo] + @objc public func getCurrentResolution() -> LGCast.LGCastCameraResolutionInfo + @objc public func setResolution(_ resolution: LGCast.LGCastCameraResolutionInfo) -> Swift.Bool + @objc public func changeCameraPosition(_ position: LGCast.LGCastCameraPosition) -> Swift.Bool + @objc public func muteMicrophone(_ isMuted: Swift.Bool) -> Swift.Bool + @objc deinit +} +@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) public class LGCastCameraPreviewView : UIKit.UIView { + @_Concurrency.MainActor(unsafe) @objc override dynamic public class var layerClass: Swift.AnyClass { + @objc get + } + @_Concurrency.MainActor(unsafe) @objc override dynamic public init(frame: CoreFoundation.CGRect) + @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder: Foundation.NSCoder) + @objc deinit +} +@objc public enum LGCastCameraProperty : Swift.Int { + case audio + case autoWhiteBalance + case brightness + case facing + case resolution + case whitebalance + case rotation + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastCameraPosition : Swift.Int { + case front = 0 + case back = 1 + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastCameraPreviewVideoGravity : Swift.Int { + case resize + case resizeAspect + case resizeAspectFill + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastCamperaPropertyStatus : Swift.Int { + case disable = 0 + case enable = 1 + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastCameraResolution : Swift.Int { + case low + case medium + case high + case cif352x288 + case vga640x480 + case hd1280x720 + case hd1920x1080 + case hd4K3840x2160 + case iFrame960x540 + case iFrame1280x720 + public init(width: Swift.Int, height: Swift.Int) + public var description: Swift.String { + get + } + public func getSize() -> (width: Swift.Int, height: Swift.Int) + public func getWidth() -> Swift.Int + public func getHeight() -> Swift.Int + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@propertyWrapper public struct LGCastUserDefaults { + public var wrappedValue: T { + get + set + } +} +public protocol AudioEncoderDelegate : AnyObject { + func audioEncoder(_ encoder: LGCast.AudioEncoder, didPrepare result: Swift.Bool) + func audioEncoder(_ encoder: LGCast.AudioEncoder, didStart result: Swift.Bool, startPts: CoreMedia.CMTime, startTime: Dispatch.DispatchTime) + func audioEncoder(_ encoder: LGCast.AudioEncoder, with type: LGCast.MTAudioType, didEncode data: Foundation.Data, pts: CoreMedia.CMTime, priority: Swift.Int) +} +@_hasMissingDesignatedInitializers public class AudioEncoder { + public func prepareAudioEncoder(_ sampleBuffer: CoreMedia.CMSampleBuffer) + public func processAudioSampleBuffer(_ sampleBuffer: CoreMedia.CMSampleBuffer, baseTime: CoreMedia.CMTime) + @objc deinit +} +extension CoreAudioTypes.AudioStreamBasicDescription : Swift.Equatable { + public static func == (lhs: CoreAudioTypes.AudioStreamBasicDescription, rhs: CoreAudioTypes.AudioStreamBasicDescription) -> Swift.Bool +} +public enum RTPStreamerMediaType : Swift.Int32 { + case VIDEO + case AUDIO + case AV + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +public enum RTPStreamerRscType : Swift.Int32 { + case APP + case FILE + case SOCKET + case CAMERA + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +public enum RTPStreamerSRTPAuthType : Swift.Int32 { + case NONE + case HMAC_SHA1_32 + case HMAC_SHA1_80 + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +public enum RTPStreamerSRTPCipherType : Swift.Int32 { + case NONE + case AES_128_ICM + case AES_256_ICM + case AES_128_GCM + case AES_256_GCM + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +@_hasMissingDesignatedInitializers public class MTRTPPipeline { + public static func getPipeline(_ ssrc: Swift.UInt32, info: LGCast.MTMediaInfo, isSecure: Swift.Bool) -> Swift.String? + @objc deinit +} +@objc @_inheritsConvenienceInitializers public class MTRTPSecurity : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +@objc @_inheritsConvenienceInitializers public class MTRTPSecurityKey : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +public enum MTMediaType : Swift.Int32 { + case video + case audio + case av + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +@objc @_inheritsConvenienceInitializers public class MTMediaInfo : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +@objc public enum MTVideoType : Swift.Int32 { + case raw + case h264 + case mjpeg + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +@objc @_inheritsConvenienceInitializers public class MTVideoInfo : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +@objc public enum MTAudioType : Swift.Int32 { + case pcm + case aac + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +@objc @_inheritsConvenienceInitializers public class MTAudioInfo : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +@objc @_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers public class Log : ObjectiveC.NSObject { + public static func debug(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + public static func info(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + public static func notice(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + public static func error(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + public static func fault(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + @objc deinit +} +extension UIKit.UIDevice { + @_Concurrency.MainActor(unsafe) public var isPhoneDevice: Swift.Bool { + get + } + @_Concurrency.MainActor(unsafe) public var isPadDevice: Swift.Bool { + get + } + @_Concurrency.MainActor(unsafe) public var modelName: Swift.String { + get + } +} +extension LGCast.Log { + @objc public static func debugLGCast(_ message: Swift.String) + @objc public static func infoLGCast(_ message: Swift.String) + @objc public static func noticeLGCast(_ message: Swift.String) + @objc public static func errorLGCast(_ message: Swift.String) + @objc public static func faultLGCast(_ message: Swift.String) +} +extension LGCast.LGCastCameraError : Swift.Equatable {} +extension LGCast.LGCastCameraError : Swift.Hashable {} +extension LGCast.LGCastCameraError : Swift.RawRepresentable {} +extension LGCast.LGCastMirroringEvent : Swift.Equatable {} +extension LGCast.LGCastMirroringEvent : Swift.Hashable {} +extension LGCast.LGCastMirroringEvent : Swift.RawRepresentable {} +extension LGCast.LGCastMirroringError : Swift.Equatable {} +extension LGCast.LGCastMirroringError : Swift.Hashable {} +extension LGCast.LGCastMirroringError : Swift.RawRepresentable {} +extension LGCast.LGCastCameraProperty : Swift.Equatable {} +extension LGCast.LGCastCameraProperty : Swift.Hashable {} +extension LGCast.LGCastCameraProperty : Swift.RawRepresentable {} +extension LGCast.LGCastCameraPosition : Swift.Equatable {} +extension LGCast.LGCastCameraPosition : Swift.Hashable {} +extension LGCast.LGCastCameraPosition : Swift.RawRepresentable {} +extension LGCast.LGCastCameraPreviewVideoGravity : Swift.Equatable {} +extension LGCast.LGCastCameraPreviewVideoGravity : Swift.Hashable {} +extension LGCast.LGCastCameraPreviewVideoGravity : Swift.RawRepresentable {} +extension LGCast.LGCastCamperaPropertyStatus : Swift.Equatable {} +extension LGCast.LGCastCamperaPropertyStatus : Swift.Hashable {} +extension LGCast.LGCastCamperaPropertyStatus : Swift.RawRepresentable {} +extension LGCast.LGCastCameraResolution : Swift.Equatable {} +extension LGCast.LGCastCameraResolution : Swift.Hashable {} +extension LGCast.LGCastCameraResolution : Swift.RawRepresentable {} +extension LGCast.RTPStreamerMediaType : Swift.Equatable {} +extension LGCast.RTPStreamerMediaType : Swift.Hashable {} +extension LGCast.RTPStreamerMediaType : Swift.RawRepresentable {} +extension LGCast.RTPStreamerRscType : Swift.Equatable {} +extension LGCast.RTPStreamerRscType : Swift.Hashable {} +extension LGCast.RTPStreamerRscType : Swift.RawRepresentable {} +extension LGCast.RTPStreamerSRTPAuthType : Swift.Equatable {} +extension LGCast.RTPStreamerSRTPAuthType : Swift.Hashable {} +extension LGCast.RTPStreamerSRTPAuthType : Swift.RawRepresentable {} +extension LGCast.RTPStreamerSRTPCipherType : Swift.Equatable {} +extension LGCast.RTPStreamerSRTPCipherType : Swift.Hashable {} +extension LGCast.RTPStreamerSRTPCipherType : Swift.RawRepresentable {} +extension LGCast.MTMediaType : Swift.Equatable {} +extension LGCast.MTMediaType : Swift.Hashable {} +extension LGCast.MTMediaType : Swift.RawRepresentable {} +extension LGCast.MTVideoType : Swift.Equatable {} +extension LGCast.MTVideoType : Swift.Hashable {} +extension LGCast.MTVideoType : Swift.RawRepresentable {} +extension LGCast.MTAudioType : Swift.Equatable {} +extension LGCast.MTAudioType : Swift.Hashable {} +extension LGCast.MTAudioType : Swift.RawRepresentable {} diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.swiftdoc b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.swiftdoc new file mode 100644 index 00000000..ee746bda Binary files /dev/null and b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.swiftdoc differ diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.swiftinterface b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.swiftinterface new file mode 100644 index 00000000..cc51b8be --- /dev/null +++ b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.swiftinterface @@ -0,0 +1,479 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51) +// swift-module-flags: -target x86_64-apple-ios11.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name LGCast +// swift-module-flags-ignorable: -enable-bare-slash-regex +import AVFoundation +import AudioToolbox +import CoreImage +import Foundation +import GStreamerForLGCast +@_exported import LGCast +import MediaPlayer +import OSLog +import QuartzCore +import ReplayKit +import Swift +import UIKit +import VideoToolbox +import _Concurrency +import _StringProcessing +@objc public enum LGCastCameraError : Swift.Int { + case unknown + case connectionClosed + case deviceShutdown + case rendererTerminated + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastMirroringEvent : Swift.Int { + case unknown + case updateVideoInfo + case updateVideoVideoSize + case updateAudioInfo + case updatePlayerInfo + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public protocol LGCastMirroringApiDelegate { + @objc func lgcastMirroringDidStart(result: Swift.Bool) + @objc func lgcastMirroringDidStop(result: Swift.Bool) + @objc func lgcastMirroringErrorDidOccur(error: LGCast.LGCastMirroringError) + @objc func lgcastMirroringUpdateEvent(event: LGCast.LGCastMirroringEvent, info: LGCast.LGCastMirroringInfo?) +} +@_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers @objc public class LGCastMirroringApi : ObjectiveC.NSObject { + @objc public static let shared: LGCast.LGCastMirroringApi + public var isRunning: Swift.Bool { + get + } + public var isTvInputEnabled: Swift.Bool { + get + } + @objc public func isSupportedOsVersion() -> Swift.Bool + @objc public func setDelegate(_ delegate: LGCast.LGCastMirroringApiDelegate) + @objc public func setMediaSettings(_ mediaInfo: LGCast.LGCastMirroringMediaSettings?) -> LGCast.LGCastMirroringInfo? + @objc public func generateMirroringMasterKey(_ publicKey: Swift.String) -> Swift.Array? + @objc public func startMirroring(_ settings: LGCast.LGCastDeviceSettings?) + @objc public func updateDisplayOrientation(isPortraitMode: Swift.Bool) -> LGCast.LGCastMirroringInfo? + @objc public func stopMirroring() + public func startMirroringWithCapture(_ settings: LGCast.LGCastDeviceSettings?) + public func stopMirroringWithCapture() + @objc public func pushSampleBuffer(_ sampleBuffer: CoreMedia.CMSampleBuffer, with sampleBufferType: ReplayKit.RPSampleBufferType) + public func pushSampleBuffer(_ sampleBuffer: CoreMedia.CMSampleBuffer, with mediaType: LGCast.RTPStreamerMediaType) + @objc deinit +} +@objc public enum LGCastMirroringError : Swift.Int { + case unknown + case connectionClosed + case deviceShutdown + case rendererTerminated + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@_inheritsConvenienceInitializers @objc public class LGCastSecurityKey : ObjectiveC.NSObject { + @objc public var masterKey: Swift.String? + @objc public var mki: Swift.String? + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastDeviceSettings : ObjectiveC.NSObject { + @objc public var host: Swift.String? + @objc public var audioPort: Swift.Int + @objc public var videoPort: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringAudioSettings : ObjectiveC.NSObject { + @objc public var sampleRate: Swift.Int + @objc public var bitrate: Swift.Int + @objc public var channelCnt: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringVideoSettings : ObjectiveC.NSObject { + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc public var isPortraitMode: Swift.Bool + @objc public var framerate: Swift.Int + @objc public var bitrate: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringMediaSettings : ObjectiveC.NSObject { + @objc public var audio: LGCast.LGCastMirroringAudioSettings? + @objc public var video: LGCast.LGCastMirroringVideoSettings? + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringAudioInfo : ObjectiveC.NSObject { + @objc public var type: LGCast.MTAudioType + @objc public var codec: Swift.String { + @objc get + } + @objc public var samplingRate: Swift.Int + @objc public var channelCnt: Swift.Int + @objc public var bitrate: Swift.Int + @objc public var streamMuxConfig: Swift.String + @objc public var codecData: Foundation.Data? + @objc public var host: Swift.String? + @objc public var port: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringVideoInfo : ObjectiveC.NSObject { + @objc public var type: LGCast.MTVideoType + @objc public var codec: Swift.String { + @objc get + } + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc public var activeWidth: Swift.Int + @objc public var activeHeight: Swift.Int + @objc public var framerate: Swift.Int + @objc public var samplingRate: Swift.Int + @objc public var bitrate: Swift.Int + @objc public var supportOrientation: Swift.String + @objc public var isPortraitMode: Swift.Bool + @objc public var screenOrientation: Swift.String + @objc public var streamMuxConfig: Swift.String? + @objc public var codecData: Foundation.Data? + @objc public var host: Swift.String? + @objc public var port: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringPlayerInfo : ObjectiveC.NSObject { + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc public var isPortraitMode: Swift.Bool + @objc public var ipAddress: Swift.String + @objc public var audioPort: Swift.Int + @objc public var videoPort: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastMirroringInfo : ObjectiveC.NSObject { + @objc public var audioInfo: LGCast.LGCastMirroringAudioInfo? + @objc public var videoInfo: LGCast.LGCastMirroringVideoInfo? + @objc public var playerInfo: LGCast.LGCastMirroringPlayerInfo? + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @objc public class LGCastCameraParameterInfo : ObjectiveC.NSObject { + @objc public var audio: LGCast.LGCastCamperaPropertyStatus + @objc public var autoWhiteBalance: LGCast.LGCastCamperaPropertyStatus + @objc public var brightness: Swift.Int + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc public var facing: LGCast.LGCastCameraPosition + @objc public var whiteBalance: Swift.Int + @objc public var rotation: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@objc public protocol LGCastCameraApiDelegate { + @objc func lgcastCameraDidPlay() + @objc func lgcastCameraDidChange(property: LGCast.LGCastCameraProperty) + @objc func lgcastCameraErrorDidOccur(error: LGCast.LGCastCameraError) +} +@_inheritsConvenienceInitializers @objc public class LGCastCameraResolutionInfo : ObjectiveC.NSObject { + @objc public var width: Swift.Int + @objc public var height: Swift.Int + @objc override dynamic public init() + @objc deinit +} +@objc @_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers public class LGCastCameraApi : ObjectiveC.NSObject { + @objc public static let KEY_CAMERA_POSITION: Swift.String + @objc public static let KEY_PREVIEW_VIDEO_GRAVITY: Swift.String + @objc public static let KEY_IS_SUPPORT_LANDSCAPE_MODE: Swift.String + @objc public static let shared: LGCast.LGCastCameraApi + @objc weak public var delegate: LGCast.LGCastCameraApiDelegate? + @objc public var isRunning: Swift.Bool { + get + } + public var isTvInputEnabled: Swift.Bool { + get + } + @objc public func isSupportedOsVersion() -> Swift.Bool + @objc public func createCameraPreviewView(_ options: [Swift.String : Any]? = nil) -> UIKit.UIView + @objc public func setPreviewView(_ view: LGCast.LGCastCameraPreviewView, options: [Swift.String : Any]? = nil) + @objc public func generateCameraMasterKey(_ publicKey: Swift.String) -> Swift.Array? + @objc public func getCameraProperties() -> LGCast.LGCastCameraParameterInfo? + @objc public func setCameraProperties(property: LGCast.LGCastCameraProperty, value: Swift.Int) -> Swift.Bool + @objc public func startRemoteCamera(_ settings: LGCast.LGCastDeviceSettings) + @objc public func stopRemoteCamera() + @objc public func getSupportedResolutions() -> [LGCast.LGCastCameraResolutionInfo] + @objc public func getCurrentResolution() -> LGCast.LGCastCameraResolutionInfo + @objc public func setResolution(_ resolution: LGCast.LGCastCameraResolutionInfo) -> Swift.Bool + @objc public func changeCameraPosition(_ position: LGCast.LGCastCameraPosition) -> Swift.Bool + @objc public func muteMicrophone(_ isMuted: Swift.Bool) -> Swift.Bool + @objc deinit +} +@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) public class LGCastCameraPreviewView : UIKit.UIView { + @_Concurrency.MainActor(unsafe) @objc override dynamic public class var layerClass: Swift.AnyClass { + @objc get + } + @_Concurrency.MainActor(unsafe) @objc override dynamic public init(frame: CoreFoundation.CGRect) + @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder: Foundation.NSCoder) + @objc deinit +} +@objc public enum LGCastCameraProperty : Swift.Int { + case audio + case autoWhiteBalance + case brightness + case facing + case resolution + case whitebalance + case rotation + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastCameraPosition : Swift.Int { + case front = 0 + case back = 1 + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastCameraPreviewVideoGravity : Swift.Int { + case resize + case resizeAspect + case resizeAspectFill + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastCamperaPropertyStatus : Swift.Int { + case disable = 0 + case enable = 1 + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@objc public enum LGCastCameraResolution : Swift.Int { + case low + case medium + case high + case cif352x288 + case vga640x480 + case hd1280x720 + case hd1920x1080 + case hd4K3840x2160 + case iFrame960x540 + case iFrame1280x720 + public init(width: Swift.Int, height: Swift.Int) + public var description: Swift.String { + get + } + public func getSize() -> (width: Swift.Int, height: Swift.Int) + public func getWidth() -> Swift.Int + public func getHeight() -> Swift.Int + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@propertyWrapper public struct LGCastUserDefaults { + public var wrappedValue: T { + get + set + } +} +public protocol AudioEncoderDelegate : AnyObject { + func audioEncoder(_ encoder: LGCast.AudioEncoder, didPrepare result: Swift.Bool) + func audioEncoder(_ encoder: LGCast.AudioEncoder, didStart result: Swift.Bool, startPts: CoreMedia.CMTime, startTime: Dispatch.DispatchTime) + func audioEncoder(_ encoder: LGCast.AudioEncoder, with type: LGCast.MTAudioType, didEncode data: Foundation.Data, pts: CoreMedia.CMTime, priority: Swift.Int) +} +@_hasMissingDesignatedInitializers public class AudioEncoder { + public func prepareAudioEncoder(_ sampleBuffer: CoreMedia.CMSampleBuffer) + public func processAudioSampleBuffer(_ sampleBuffer: CoreMedia.CMSampleBuffer, baseTime: CoreMedia.CMTime) + @objc deinit +} +extension CoreAudioTypes.AudioStreamBasicDescription : Swift.Equatable { + public static func == (lhs: CoreAudioTypes.AudioStreamBasicDescription, rhs: CoreAudioTypes.AudioStreamBasicDescription) -> Swift.Bool +} +public enum RTPStreamerMediaType : Swift.Int32 { + case VIDEO + case AUDIO + case AV + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +public enum RTPStreamerRscType : Swift.Int32 { + case APP + case FILE + case SOCKET + case CAMERA + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +public enum RTPStreamerSRTPAuthType : Swift.Int32 { + case NONE + case HMAC_SHA1_32 + case HMAC_SHA1_80 + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +public enum RTPStreamerSRTPCipherType : Swift.Int32 { + case NONE + case AES_128_ICM + case AES_256_ICM + case AES_128_GCM + case AES_256_GCM + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +@_hasMissingDesignatedInitializers public class MTRTPPipeline { + public static func getPipeline(_ ssrc: Swift.UInt32, info: LGCast.MTMediaInfo, isSecure: Swift.Bool) -> Swift.String? + @objc deinit +} +@objc @_inheritsConvenienceInitializers public class MTRTPSecurity : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +@objc @_inheritsConvenienceInitializers public class MTRTPSecurityKey : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +public enum MTMediaType : Swift.Int32 { + case video + case audio + case av + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +@objc @_inheritsConvenienceInitializers public class MTMediaInfo : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +@objc public enum MTVideoType : Swift.Int32 { + case raw + case h264 + case mjpeg + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +@objc @_inheritsConvenienceInitializers public class MTVideoInfo : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +@objc public enum MTAudioType : Swift.Int32 { + case pcm + case aac + public init?(rawValue: Swift.Int32) + public typealias RawValue = Swift.Int32 + public var rawValue: Swift.Int32 { + get + } +} +@objc @_inheritsConvenienceInitializers public class MTAudioInfo : ObjectiveC.NSObject { + @objc override dynamic public init() + @objc deinit +} +@objc @_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers public class Log : ObjectiveC.NSObject { + public static func debug(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + public static func info(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + public static func notice(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + public static func error(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + public static func fault(_ items: Any..., file: Swift.String = #file, function: Swift.String = #function, line: Swift.Int = #line) + @objc deinit +} +extension UIKit.UIDevice { + @_Concurrency.MainActor(unsafe) public var isPhoneDevice: Swift.Bool { + get + } + @_Concurrency.MainActor(unsafe) public var isPadDevice: Swift.Bool { + get + } + @_Concurrency.MainActor(unsafe) public var modelName: Swift.String { + get + } +} +extension LGCast.Log { + @objc public static func debugLGCast(_ message: Swift.String) + @objc public static func infoLGCast(_ message: Swift.String) + @objc public static func noticeLGCast(_ message: Swift.String) + @objc public static func errorLGCast(_ message: Swift.String) + @objc public static func faultLGCast(_ message: Swift.String) +} +extension LGCast.LGCastCameraError : Swift.Equatable {} +extension LGCast.LGCastCameraError : Swift.Hashable {} +extension LGCast.LGCastCameraError : Swift.RawRepresentable {} +extension LGCast.LGCastMirroringEvent : Swift.Equatable {} +extension LGCast.LGCastMirroringEvent : Swift.Hashable {} +extension LGCast.LGCastMirroringEvent : Swift.RawRepresentable {} +extension LGCast.LGCastMirroringError : Swift.Equatable {} +extension LGCast.LGCastMirroringError : Swift.Hashable {} +extension LGCast.LGCastMirroringError : Swift.RawRepresentable {} +extension LGCast.LGCastCameraProperty : Swift.Equatable {} +extension LGCast.LGCastCameraProperty : Swift.Hashable {} +extension LGCast.LGCastCameraProperty : Swift.RawRepresentable {} +extension LGCast.LGCastCameraPosition : Swift.Equatable {} +extension LGCast.LGCastCameraPosition : Swift.Hashable {} +extension LGCast.LGCastCameraPosition : Swift.RawRepresentable {} +extension LGCast.LGCastCameraPreviewVideoGravity : Swift.Equatable {} +extension LGCast.LGCastCameraPreviewVideoGravity : Swift.Hashable {} +extension LGCast.LGCastCameraPreviewVideoGravity : Swift.RawRepresentable {} +extension LGCast.LGCastCamperaPropertyStatus : Swift.Equatable {} +extension LGCast.LGCastCamperaPropertyStatus : Swift.Hashable {} +extension LGCast.LGCastCamperaPropertyStatus : Swift.RawRepresentable {} +extension LGCast.LGCastCameraResolution : Swift.Equatable {} +extension LGCast.LGCastCameraResolution : Swift.Hashable {} +extension LGCast.LGCastCameraResolution : Swift.RawRepresentable {} +extension LGCast.RTPStreamerMediaType : Swift.Equatable {} +extension LGCast.RTPStreamerMediaType : Swift.Hashable {} +extension LGCast.RTPStreamerMediaType : Swift.RawRepresentable {} +extension LGCast.RTPStreamerRscType : Swift.Equatable {} +extension LGCast.RTPStreamerRscType : Swift.Hashable {} +extension LGCast.RTPStreamerRscType : Swift.RawRepresentable {} +extension LGCast.RTPStreamerSRTPAuthType : Swift.Equatable {} +extension LGCast.RTPStreamerSRTPAuthType : Swift.Hashable {} +extension LGCast.RTPStreamerSRTPAuthType : Swift.RawRepresentable {} +extension LGCast.RTPStreamerSRTPCipherType : Swift.Equatable {} +extension LGCast.RTPStreamerSRTPCipherType : Swift.Hashable {} +extension LGCast.RTPStreamerSRTPCipherType : Swift.RawRepresentable {} +extension LGCast.MTMediaType : Swift.Equatable {} +extension LGCast.MTMediaType : Swift.Hashable {} +extension LGCast.MTMediaType : Swift.RawRepresentable {} +extension LGCast.MTVideoType : Swift.Equatable {} +extension LGCast.MTVideoType : Swift.Hashable {} +extension LGCast.MTVideoType : Swift.RawRepresentable {} +extension LGCast.MTAudioType : Swift.Equatable {} +extension LGCast.MTAudioType : Swift.Hashable {} +extension LGCast.MTAudioType : Swift.RawRepresentable {} diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Modules/module.modulemap b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Modules/module.modulemap new file mode 100644 index 00000000..e109f7d4 --- /dev/null +++ b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/Modules/module.modulemap @@ -0,0 +1,11 @@ +framework module LGCast { + umbrella header "LGCast.h" + + export * + module * { export * } +} + +module LGCast.Swift { + header "LGCast-Swift.h" + requires objc +} diff --git a/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/_CodeSignature/CodeResources b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/_CodeSignature/CodeResources new file mode 100644 index 00000000..81ed79d0 --- /dev/null +++ b/core/Frameworks/LGCast/LGCast.xcframework/ios-x86_64-simulator/LGCast.framework/_CodeSignature/CodeResources @@ -0,0 +1,190 @@ + + + + + files + + Headers/LGCast-Swift.h + + BcEg6ToBthYxQahP6gaHoNEUTN0= + + Headers/LGCast.h + + 7nME2AUhE/ME0I81nulxFzGI0sI= + + Info.plist + + NqbKeqwCcUmKQa4k5QEx9LQnQsQ= + + Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.abi.json + + DUHkZiQ7BSFaJC+ld/riWnTSc8M= + + Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface + + 3M9ZQSxVFGbyWdNmB0/mR3XvZTc= + + Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.swiftdoc + + voX8JClB639WMoKI+Oqrwv6YfV4= + + Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.swiftinterface + + 3M9ZQSxVFGbyWdNmB0/mR3XvZTc= + + Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.swiftmodule + + 8eeDYPo+6BNPqnmN1ZYrqH3GpEc= + + Modules/module.modulemap + + gTGjU3dap4EejAmqzU8KOGOyNwo= + + + files2 + + Headers/LGCast-Swift.h + + hash2 + + r34SmRjY50avS9NXwyXE8muNbAuzgpID7R13P1NTNTc= + + + Headers/LGCast.h + + hash2 + + Fg93HowLWnlEVhWZtwLx4uGJmZiKE94z+mImz8FlV54= + + + Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.abi.json + + hash2 + + BKOXnVL14OcyCP5Ng8+ABtdC4lfM9CqmsyobBPnE46s= + + + Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface + + hash2 + + svdTno6biCyM8uqgWyHHEBAfjUlArY2HI6NGFTGal00= + + + Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.swiftdoc + + hash2 + + Arssno/Jft4uCaxaUcYsH8VdeR1rfCsjOHomdQPeKF8= + + + Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.swiftinterface + + hash2 + + svdTno6biCyM8uqgWyHHEBAfjUlArY2HI6NGFTGal00= + + + Modules/LGCast.swiftmodule/x86_64-apple-ios-simulator.swiftmodule + + hash2 + + wMquUCNs+nZwfYB48fihN08WTIBQwvpHLwlhh/ij1pY= + + + Modules/module.modulemap + + hash2 + + etobu8/2pTW3nAIWj7WFbm28/wcV0X1Prrgs8gVJj8I= + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/core/Frameworks/SocketRocket/LGSRWebSocket.h b/core/Frameworks/SocketRocket/LGSRWebSocket.h new file mode 100644 index 00000000..c6615cac --- /dev/null +++ b/core/Frameworks/SocketRocket/LGSRWebSocket.h @@ -0,0 +1,129 @@ +// +// Copyright 2013-2014 LG Electronics. +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +typedef enum { + LGSR_CONNECTING = 0, + LGSR_OPEN = 1, + LGSR_CLOSING = 2, + LGSR_CLOSED = 3, +} LGSRReadyState; + +typedef enum { + LGSRStatusCodeNormal = 1000, + LGSRStatusCodeGoingAway = 1001, + LGSRStatusCodeProtocolError = 1002, + LGSRStatusCodeUnhandledType = 1003, + // 1004 reserved. + LGSRStatusNoStatusReceived = 1005, + // 1004-1006 reserved. + LGSRStatusCodeInvalidUTF8 = 1007, + LGSRStatusCodePolicyViolated = 1008, + LGSRStatusCodeMessageTooBig = 1009, +} LGSRStatusCode; + +@class LGSRWebSocket; + +extern NSString *const LGSRWebSocketErrorDomain; + +#pragma mark - LGSRWebSocketDelegate + +@protocol LGSRWebSocketDelegate; + +#pragma mark - LGSRWebSocket + +@interface LGSRWebSocket : NSObject + +@property (nonatomic, weak) id delegate; + +@property (nonatomic, readonly) LGSRReadyState readyState; +@property (nonatomic, readonly, retain) NSURL *url; + +// This returns the negotiated protocol. +// It will be nil until after the handshake completes. +@property (nonatomic, readonly, copy) NSString *protocol; + +// Protocols should be an array of strings that turn into Sec-WebSocket-Protocol. +- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; +- (id)initWithURLRequest:(NSURLRequest *)request; + +// Some helper constructors. +- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; +- (id)initWithURL:(NSURL *)url; + +// Delegate queue will be dispatch_main_queue by default. +// You cannot set both OperationQueue and dispatch_queue. +- (void)setDelegateOperationQueue:(NSOperationQueue*) queue; +- (void)setDelegateDispatchQueue:(dispatch_queue_t) queue; + +// By default, it will schedule itself on +[NSRunLoop LGSR_networkRunLoop] using defaultModes. +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; + +// LGSRWebSockets are intended for one-time-use only. Open should be called once and only once. +- (void)open; + +- (void)close; +- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; + +// Send a UTF8 String or Data. +- (void)send:(id)data; + +@end + +#pragma mark - LGSRWebSocketDelegate + +@protocol LGSRWebSocketDelegate + +// message will either be an NSString if the server is using text +// or NSData if the server is using binary. +- (void)webSocket:(LGSRWebSocket *)webSocket didReceiveMessage:(id)message; + +@optional + +- (void)webSocketDidOpen:(LGSRWebSocket *)webSocket; +- (void)webSocket:(LGSRWebSocket *)webSocket didFailWithError:(NSError *)error; +- (void)webSocket:(LGSRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; +- (void)webSocket:(LGSRWebSocket*)webSocket didGetCertificates:(NSArray*)certs; + +@end + +#pragma mark - NSURLRequest (CertificateAdditions) + +@interface NSURLRequest (CertificateAdditions) + +@property (nonatomic, retain, readonly) NSArray *LGSR_SSLPinnedCertificates; + +@end + +#pragma mark - NSMutableURLRequest (CertificateAdditions) + +@interface NSMutableURLRequest (CertificateAdditions) + +@property (nonatomic, retain) NSArray *LGSR_SSLPinnedCertificates; + +@end + +#pragma mark - NSRunLoop (LGSRWebSocket) + +@interface NSRunLoop (LGSRWebSocket) + ++ (NSRunLoop *)LGSR_networkRunLoop; + +@end diff --git a/core/Frameworks/SocketRocket/LGSRWebSocket.m b/core/Frameworks/SocketRocket/LGSRWebSocket.m new file mode 100644 index 00000000..03830f45 --- /dev/null +++ b/core/Frameworks/SocketRocket/LGSRWebSocket.m @@ -0,0 +1,2180 @@ +// +// Copyright 2013-2014 LG Electronics. +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import "LGSRWebSocket.h" + +#if TARGET_OS_IPHONE +#define HAS_ICU +#endif + +#ifdef HAS_ICU +#import +#endif + +#if TARGET_OS_IPHONE +#import +#else +#import +#endif + +#import +#import + +#if OS_OBJECT_USE_OBJC_RETAIN_RELEASE +#define lgsr_dispatch_retain(x) +#define lgsr_dispatch_release(x) +#else +#define lgsr_dispatch_retain(x) dispatch_retain(x) +#define lgsr_dispatch_release(x) dispatch_release(x) +#endif + +#if !__has_feature(objc_arc) +#error SocketRocket must be compiled with ARC enabled +#endif + + +typedef enum { + LGSROpCodeTextFrame = 0x1, + LGSROpCodeBinaryFrame = 0x2, + // 3-7 reserved. + LGSROpCodeConnectionClose = 0x8, + LGSROpCodePing = 0x9, + LGSROpCodePong = 0xA, + // B-F reserved. +} LGSROpCode; + +typedef struct { + BOOL fin; +// BOOL rsv1; +// BOOL rsv2; +// BOOL rsv3; + uint8_t opcode; + BOOL masked; + uint64_t payload_length; +} frame_header; + +static NSString *const LGSRWebSocketAppendToSecKeyString = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + +static NSString *const LGServerCertificatePublicKeyString = {@"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2At7fSUHuMw6bm/z3Q+X4oY9KpDa1s06\n" +@"mht9vNmSkZE5xMo9asOtZAWLLbJLxifY6qz6LWKgNw4Pyk6HVTLFdj4jrV//gNGQvYtCp3HRriqg\n" +@"2YoceBNG59+SW3xNzuhUqy5/nerQPfNQiz9z9RqtGj/YWItlJcKrNOBecNmHc7Xmu+3yPN6kD1G2\n" +@"6uU8wPBqzMdqFpPcubedIOmh4nNa2sNkfvMkbR4Pk/YupsDpic56dMxX0Twvg6SiaKGjv8NO9Lcv\n" +@"hLt2dR2XXi/z2F6uVjP5oYPvlSAK9GHVo96khpafKGPvIwPSSGtlHI4is/yT7WEeLuQs5FD/vAs9\n" +@"eqQNkQIDAQAB\n"}; + +static NSString *const LGIntermediateCAPublicKeyString ={@"MIMAAiMwDQYJKoZIhvcNAQEBBQADgwACDwAwggIKAoICAQDl5xeN+UkKyfHifIGtO4igsl/UxXosQ0qeAMadDSdI8XLHAsfEnlCDVWwYSFD76A7+GO6mttN5MTlsdVJPQ/lyy032cPkz38R9MHS6sOxqOCXurkX0wN/7fTODKVkS/Q3dCqYzeluWaMrZQBAW6vGQVgoilBxf5Z9jpV9Dj6IxXThomUlyM8HVCWmg38eht8ItiuSe1Bzpr8Bqv35wLEnPPK1QhKu9oxU10GS6Yn1GNQ6xMvpayf1jPuRK443tdR+IA2mDy/N8h623yvIbBxMt/rLmvzjWevLC8wyTlrEw5ei/GOfMlJq8fK3TK1S9CMqqe6uSO9YHJo5/ibg+aFKkJEDO4RNzK2W0A7F+a2eJIk8lz49rvZNC+X/waRUfso8YrAcXvQo4EbYDWTlFDNa15rYiZEE3lzqROto440Wb6v5ZavyvkRyQbUNbodUh42Wvo616kD6GG5/Le1QUpO6I66Hjs0MyJQApFuOR2OZhS8FjHPRONSNZJl1rAP7xtZ7fDikad0oqU9hHKOKQSb8e8QTLUyQGCW17FB8pgC8Du7ZZquRU/0RhK002yEdlWY+yDWs18aLqKl64P5GiipYufRTWVS3Ev8e4W/ycdI3n4gOWNAUuHUkO+LRXxm1fimPV1rigQrth9KtRc1g9FsDr+s2ZfxPft3LTozt1HRSBeQIDAQAB"}; + +/*static NSString *const LGSSCPublicKeyString ={@"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2qdDinEsRPX/5XigbSzzO4PtTq3ZizLH\n" + @"ywwmRobnix/Ajn8LGSYQfrV3+HBg5Zdcc0SWxx66i7Guy1UOFXV7q28roLSui94nS9jyjsgiXqZL\n" + @"57bcj8HrNBfRIiBUvE7FfBn8dKnVO7+61qXw4Eal7CR3602vHg4INpWt/xA8HPKrlnqrW03lAu22\n" + @"nz9bij+3VHAdJ1kb9txGJ4yxXmseS3LwcoV/6NuHcP5ZGccTo1Wglw9RKWYUFqyKh7i1P6jalA3U\n" + @"kFZ6wNJZKayFGPMeBRIZdH5E61bb5lgnTcSHWi0HrBIUql/KC4Gxh4PDQn3QrPZJKp/Ne+/8ReMn\n" + @"nA4XNwIDAQAB\n"};*/ + +static inline int32_t validate_dispatch_data_partial_string(NSData *data); +static inline void LGSRFastLog(NSString *format, ...); + +@interface NSData (LGSRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; + +@end + + +@interface NSString (LGSRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; + +@end + + +@interface NSURL (LGSRWebSocket) + +// The origin isn't really applicable for a native application. +// So instead, just map ws -> http and wss -> https. +- (NSString *)LGSR_origin; + +@end + + +@interface _LGSRRunLoopThread : NSThread + +@property (nonatomic, readonly) NSRunLoop *runLoop; + +@end + + +static inline NSString *base64EncodedData(NSData *data) { + // based on https://github.com/square/SocketRocket/pull/168 + if ([data respondsToSelector:@selector(base64EncodedStringWithOptions:)]) { + return [data base64EncodedStringWithOptions:0]; + } + + _Pragma("clang diagnostic push"); + _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\""); + // legacy, before iOS 7 + return [data base64Encoding]; + _Pragma("clang diagnostic pop"); +} + +static NSString *newSHA1String(const char *bytes, size_t length) { + uint8_t md[CC_SHA1_DIGEST_LENGTH]; + + assert(length >= 0); + assert(length <= UINT32_MAX); + CC_SHA1(bytes, (CC_LONG)length, md); + + return base64EncodedData([NSData dataWithBytes:md length:CC_SHA1_DIGEST_LENGTH]); +} + +@implementation NSData (LGSRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; +{ + return newSHA1String(self.bytes, self.length); +} + +@end + + +@implementation NSString (LGSRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; +{ + return newSHA1String(self.UTF8String, self.length); +} + +@end + +NSString *const LGSRWebSocketErrorDomain = @"LGSRWebSocketErrorDomain"; + +// Returns number of bytes consumed. Returning 0 means you didn't match. +// Sends bytes to callback handler; +typedef size_t (^stream_scanner)(NSData *collected_data); + +typedef void (^data_callback)(LGSRWebSocket *webSocket, NSData *data); + +@interface LGSRIOConsumer : NSObject { + stream_scanner _scanner; + data_callback _handler; + size_t _bytesNeeded; + BOOL _readToCurrentFrame; + BOOL _unmaskBytes; +} +@property (nonatomic, copy, readonly) stream_scanner consumer; +@property (nonatomic, copy, readonly) data_callback handler; +@property (nonatomic, assign) size_t bytesNeeded; +@property (nonatomic, assign, readonly) BOOL readToCurrentFrame; +@property (nonatomic, assign, readonly) BOOL unmaskBytes; + +@end + +// This class is not thread-safe, and is expected to always be run on the same queue. +@interface LGSRIOConsumerPool : NSObject + +- (id)initWithBufferCapacity:(NSUInteger)poolSize; + +- (LGSRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +- (void)returnConsumer:(LGSRIOConsumer *)consumer; + +@end + +@interface LGSRWebSocket () + +- (void)_writeData:(NSData *)data; +- (void)_closeWithProtocolError:(NSString *)message; +- (void)_failWithError:(NSError *)error; + +- (void)_readFrameNew; +- (void)_readFrameContinue; + +- (void)_pumpScanner; + +- (void)_pumpWriting; + +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback; +- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength; +- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler; +- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; + +- (void)_sendFrameWithOpcode:(LGSROpCode)opcode data:(id)data; + +- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; +- (void)_LGSR_commonInit; + +- (void)_initializeStreams; + +- (size_t) encodeLength:(unsigned char *) buf :(size_t)length; //newly added for connectSDKPorting +- (NSString *) getPublicKeyAsBase64:(SecKeyRef) publicKey ; //newly added for ConnectSDkPorting +@property (nonatomic) LGSRReadyState readyState; + +@property (nonatomic) NSOperationQueue *delegateOperationQueue; +@property (nonatomic) dispatch_queue_t delegateDispatchQueue; +@property (nonatomic) NSArray *sslData; + +@end + + +@implementation LGSRWebSocket { + NSInteger _webSocketVersion; + + NSOperationQueue *_delegateOperationQueue; + dispatch_queue_t _delegateDispatchQueue; + + dispatch_queue_t _workQueue; + NSMutableArray *_consumers; + + NSInputStream *_inputStream; + NSOutputStream *_outputStream; + + NSMutableData *_readBuffer; + NSUInteger _readBufferOffset; + + NSMutableData *_outputBuffer; + NSUInteger _outputBufferOffset; + + uint8_t _currentFrameOpcode; + size_t _currentFrameCount; + size_t _readOpCount; + uint32_t _currentStringScanPosition; + NSMutableData *_currentFrameData; + + NSString *_closeReason; + + NSString *_secKey; + + BOOL _pinnedCertFound; + + uint8_t _currentReadMaskKey[4]; + size_t _currentReadMaskOffset; + + BOOL _consumerStopped; + + BOOL _closeWhenFinishedWriting; + BOOL _failed; + + BOOL _secure; + NSMutableURLRequest *_urlRequest; + + CFHTTPMessageRef _receivedHTTPHeaders; + + BOOL _sentClose; + BOOL _didFail; + BOOL _cleanupScheduled; + int _closeCode; + + BOOL _isPumping; + + NSMutableSet *_scheduledRunloops; + + // We use this to retain ourselves. + __strong LGSRWebSocket *_selfRetain; + + NSArray *_requestedProtocols; + LGSRIOConsumerPool *_consumerPool; + int _ipublicKeyvalue; + int _icertificateValidity; +} + +@synthesize delegate = _delegate; +@synthesize url = _url; +@synthesize readyState = _readyState; +@synthesize protocol = _protocol; + +static __strong NSData *CRLFCRLF; + ++ (void)initialize; +{ + CRLFCRLF = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; +} + +- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; +{ + self = [super init]; + if (self) { + assert(request.URL); + _url = request.URL; + _urlRequest = request; + + _requestedProtocols = [protocols copy]; + + [self _LGSR_commonInit]; + } + + return self; +} + +- (id)initWithURLRequest:(NSURLRequest *)request; +{ + return [self initWithURLRequest:request protocols:nil]; +} + +- (id)initWithURL:(NSURL *)url; +{ + return [self initWithURL:url protocols:nil]; +} + +- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; +{ + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; + return [self initWithURLRequest:request protocols:protocols]; +} + +- (void)_LGSR_commonInit; +{ + + NSString *scheme = _url.scheme.lowercaseString; + assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]); + + if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) { + _secure = YES; + } + + _readyState = LGSR_CONNECTING; + _consumerStopped = YES; + _webSocketVersion = 13; + + _workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + + // Going to set a specific on the queue so we can validate we're on the work queue + dispatch_queue_set_specific(_workQueue, (__bridge void *)self, (__bridge void *)(_workQueue), NULL); + + _delegateDispatchQueue = dispatch_get_main_queue(); + lgsr_dispatch_retain(_delegateDispatchQueue); + + _readBuffer = [[NSMutableData alloc] init]; + _outputBuffer = [[NSMutableData alloc] init]; + + _currentFrameData = [[NSMutableData alloc] init]; + + _consumers = [[NSMutableArray alloc] init]; + + _consumerPool = [[LGSRIOConsumerPool alloc] init]; + + _scheduledRunloops = [[NSMutableSet alloc] init]; + + _ipublicKeyvalue =-1; + _icertificateValidity=-1; + + [self _initializeStreams]; + + // default handlers +} + +- (void)assertOnWorkQueue; +{ + assert(dispatch_get_specific((__bridge void *)self) == (__bridge void *)_workQueue); +} + +- (void)dealloc +{ + _inputStream.delegate = nil; + _outputStream.delegate = nil; + + [_inputStream close]; + [_outputStream close]; + + if (_workQueue) + { + lgsr_dispatch_release(_workQueue); + _workQueue = NULL; + } + + if (_receivedHTTPHeaders) { + CFRelease(_receivedHTTPHeaders); + _receivedHTTPHeaders = NULL; + } + + if (_delegateDispatchQueue) { + lgsr_dispatch_release(_delegateDispatchQueue); + _delegateDispatchQueue = NULL; + } +} + +#ifndef NDEBUG + +- (void)setReadyState:(LGSRReadyState)aReadyState; +{ + [self willChangeValueForKey:@"readyState"]; + assert(aReadyState > _readyState); + _readyState = aReadyState; + [self didChangeValueForKey:@"readyState"]; +} + +#endif + +- (void)open; +{ + assert(_url); + NSAssert(_readyState == LGSR_CONNECTING, @"Cannot call -(void)open on LGSRWebSocket more than once"); + + _selfRetain = self; + + [self openConnection]; +} + +// Calls block on delegate queue +- (void)_performDelegateBlock:(dispatch_block_t)block; +{ + if (_delegateOperationQueue) { + [_delegateOperationQueue addOperationWithBlock:block]; + } else { + assert(_delegateDispatchQueue); + dispatch_async(_delegateDispatchQueue, block); + } +} + +- (void)setDelegateDispatchQueue:(dispatch_queue_t)queue; +{ + if (queue) { + lgsr_dispatch_retain(queue); + } + + if (_delegateDispatchQueue) { + lgsr_dispatch_release(_delegateDispatchQueue); + } + + _delegateDispatchQueue = queue; +} + +- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; +{ + NSString *acceptHeader = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(httpMessage, CFSTR("Sec-WebSocket-Accept"))); + + if (acceptHeader == nil) { + return NO; + } + + NSString *concattedString = [_secKey stringByAppendingString:LGSRWebSocketAppendToSecKeyString]; + NSString *expectedAccept = [concattedString stringBySHA1ThenBase64Encoding]; + + return [acceptHeader isEqualToString:expectedAccept]; +} + +- (void)_HTTPHeadersDidFinish; +{ + NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHeaders); + + if (responseCode >= 400) { + LGSRFastLog(@"Request failed with response code %d", responseCode); + [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:2132 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"received bad response code from server %ld", (long)responseCode] forKey:NSLocalizedDescriptionKey]]]; + return; + + } + + if(![self _checkHandshake:_receivedHTTPHeaders]) { + [self _failWithError:[NSError errorWithDomain:LGSRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Invalid Sec-WebSocket-Accept response"] forKey:NSLocalizedDescriptionKey]]]; + return; + } + + NSString *negotiatedProtocol = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(_receivedHTTPHeaders, CFSTR("Sec-WebSocket-Protocol"))); + if (negotiatedProtocol) { + // Make sure we requested the protocol + if ([_requestedProtocols indexOfObject:negotiatedProtocol] == NSNotFound) { + [self _failWithError:[NSError errorWithDomain:LGSRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Server specified Sec-WebSocket-Protocol that wasn't requested"] forKey:NSLocalizedDescriptionKey]]]; + return; + } + + _protocol = negotiatedProtocol; + } + + self.readyState = LGSR_OPEN; + + if (!_didFail) { + [self _readFrameNew]; + } + + [self _performDelegateBlock:^{ + if (self.delegate && [self.delegate respondsToSelector:@selector(webSocketDidOpen:)]) { + [self.delegate webSocketDidOpen:self]; + }; + }]; +} + + +- (void)_readHTTPHeader; +{ + if (_receivedHTTPHeaders == NULL) { + _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO); + } + + [self _readUntilHeaderCompleteWithCallback:^(LGSRWebSocket *self, NSData *data) { + CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length); + + if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) { + LGSRFastLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders))); + [self _HTTPHeadersDidFinish]; + } else { + [self _readHTTPHeader]; + } + }]; +} + +- (void)didConnect +{ + LGSRFastLog(@"Connected"); + CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), (__bridge CFURLRef)_url, kCFHTTPVersion1_1); + + // Set host first so it defaults + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Host"), (__bridge CFStringRef)(_url.port ? [NSString stringWithFormat:@"%@:%@", _url.host, _url.port] : _url.host)); + + NSMutableData *keyBytes = [[NSMutableData alloc] initWithLength:16]; + SecRandomCopyBytes(kSecRandomDefault, keyBytes.length, keyBytes.mutableBytes); + _secKey = base64EncodedData(keyBytes); + + assert([_secKey length] == 24); + + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Upgrade"), CFSTR("websocket")); + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Upgrade")); + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), (__bridge CFStringRef)_secKey); + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", (long)_webSocketVersion]); + + //CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)_url.SR_origin); + + if (_requestedProtocols) { + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Protocol"), (__bridge CFStringRef)[_requestedProtocols componentsJoinedByString:@", "]); + } + + [_urlRequest.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)key, (__bridge CFStringRef)obj); + }]; + + NSData *message = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request)); + + CFRelease(request); + + [self _writeData:message]; + [self _readHTTPHeader]; +} + +- (void)_initializeStreams; +{ + assert(_url.port.unsignedIntValue <= UINT32_MAX); + uint32_t port = _url.port.unsignedIntValue; + if (port == 0) { + if (!_secure) { + port = 80; + } else { + port = 443; + } + } + NSString *host = _url.host; + + CFReadStreamRef readStream = NULL; + CFWriteStreamRef writeStream = NULL; + + CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, (UInt32)port, &readStream, &writeStream); + + _outputStream = CFBridgingRelease(writeStream); + _inputStream = CFBridgingRelease(readStream); + + + if (_secure) { + NSMutableDictionary *SSLOptions = [[NSMutableDictionary alloc] init]; + + [_outputStream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; + + // If we're using pinned certs, don't validate the certificate chain + /* + if ([_urlRequest SR_SSLPinnedCertificates].count) { + [SSLOptions setValue:[NSNumber numberWithBool:NO] forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; + } + */ + [SSLOptions setValue:[NSNumber numberWithBool:NO] forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; + + [_outputStream setProperty:SSLOptions + forKey:(__bridge id)kCFStreamPropertySSLSettings]; + } + + _inputStream.delegate = self; + _outputStream.delegate = self; +} + +- (void)openConnection; +{ + if (!_scheduledRunloops.count) { + [self scheduleInRunLoop:[NSRunLoop LGSR_networkRunLoop] forMode:NSDefaultRunLoopMode]; + } + + + [_outputStream open]; + [_inputStream open]; +} + +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +{ + [_outputStream scheduleInRunLoop:aRunLoop forMode:mode]; + [_inputStream scheduleInRunLoop:aRunLoop forMode:mode]; + + [_scheduledRunloops addObject:@[aRunLoop, mode]]; +} + +- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +{ + [_outputStream removeFromRunLoop:aRunLoop forMode:mode]; + [_inputStream removeFromRunLoop:aRunLoop forMode:mode]; + + [_scheduledRunloops removeObject:@[aRunLoop, mode]]; +} + +- (void)close; +{ + [self closeWithCode:0 reason:nil]; +} + +- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; +{ + //assert(code); + [self _scheduleCleanup]; + dispatch_async(_workQueue, ^{ + if (self.readyState == LGSR_CLOSING || self.readyState == LGSR_CLOSED) { + return; + } + + BOOL wasConnecting = self.readyState == LGSR_CONNECTING; + + self.readyState = LGSR_CLOSING; + + LGSRFastLog(@"Closing with code %d reason %@", code, reason); + + if (wasConnecting) { + [self closeConnection]; + return; + } + + size_t maxMsgSize = [reason maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + NSMutableData *mutablePayload = [[NSMutableData alloc] initWithLength:sizeof(uint16_t) + maxMsgSize]; + NSData *payload = mutablePayload; + + ((uint16_t *)mutablePayload.mutableBytes)[0] = EndianU16_BtoN(code); + + if (reason) { + NSRange remainingRange = {0}; + + NSUInteger usedLength = 0; + + BOOL success = [reason getBytes:(char *)mutablePayload.mutableBytes + sizeof(uint16_t) maxLength:payload.length - sizeof(uint16_t) usedLength:&usedLength encoding:NSUTF8StringEncoding options:NSStringEncodingConversionExternalRepresentation range:NSMakeRange(0, reason.length) remainingRange:&remainingRange]; + + assert(success); + assert(remainingRange.length == 0); + + if (usedLength != maxMsgSize) { + payload = [payload subdataWithRange:NSMakeRange(0, usedLength + sizeof(uint16_t))]; + } + } + + + [self _sendFrameWithOpcode:LGSROpCodeConnectionClose data:payload]; + }); +} + +- (void)_closeWithProtocolError:(NSString *)message; +{ + // Need to shunt this on the _callbackQueue first to see if they received any messages + [self _performDelegateBlock:^{ + [self closeWithCode:LGSRStatusCodeProtocolError reason:message]; + dispatch_async(_workQueue, ^{ + [self closeConnection]; + }); + }]; +} + +- (void)_failWithError:(NSError *)error; +{ + dispatch_async(_workQueue, ^{ + if (self.readyState != LGSR_CLOSED) { + _failed = YES; + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didFailWithError:)]) { + [self.delegate webSocket:self didFailWithError:error]; + } + }]; + + self.readyState = LGSR_CLOSED; + + LGSRFastLog(@"Failing with error %@", error.localizedDescription); + + [self closeConnection]; + [self _scheduleCleanup]; + } + }); +} + +- (void)_writeData:(NSData *)data; +{ + [self assertOnWorkQueue]; + + if (_closeWhenFinishedWriting) { + return; + } + [_outputBuffer appendData:data]; + [self _pumpWriting]; +} +- (void)send:(id)data; +{ + NSAssert(self.readyState != LGSR_CONNECTING, @"Invalid State: Cannot call send: until connection is open"); + // TODO: maybe not copy this for performance + + data = [data copy]; + + dispatch_async(_workQueue, ^{ + if ([data isKindOfClass:[NSString class]]) { + LGSRFastLog(@"LGSRWebSocket::send string: %@", data); + [self _sendFrameWithOpcode:LGSROpCodeTextFrame data:[(NSString *)data dataUsingEncoding:NSUTF8StringEncoding]]; + } else if ([data isKindOfClass:[NSData class]]) { + NSData *theData = (NSData *)data; + LGSRFastLog(@"LGSRWebSocket::send data of length %d", (int)theData.length); + [self _sendFrameWithOpcode:LGSROpCodeBinaryFrame data:data]; + } else if (data == nil) {; + LGSRFastLog(@"LGSRWebSocket::send nil"); + [self _sendFrameWithOpcode:LGSROpCodeTextFrame data:data]; + } else { + LGSRFastLog(@"LGSRWebSocket::send unknown object type"); + assert(NO); + } + }); +} + +- (void)handlePing:(NSData *)pingData; +{ + // Need to pingpong this off _callbackQueue first to make sure messages happen in order + [self _performDelegateBlock:^{ + dispatch_async(_workQueue, ^{ + [self _sendFrameWithOpcode:LGSROpCodePong data:pingData]; + }); + }]; +} + +- (void)handlePong; +{ + // NOOP +} + +- (void)_handleMessage:(id)message +{ + LGSRFastLog(@"Received message"); + [self _performDelegateBlock:^{ + [self.delegate webSocket:self didReceiveMessage:message]; + }]; +} + + +static inline BOOL closeCodeIsValid(int closeCode) { + if (closeCode < 1000) { + return NO; + } + + if (closeCode >= 1000 && closeCode <= 1011) { + if (closeCode == 1004 || + closeCode == 1005 || + closeCode == 1006) { + return NO; + } + return YES; + } + + if (closeCode >= 3000 && closeCode <= 3999) { + return YES; + } + + if (closeCode >= 4000 && closeCode <= 4999) { + return YES; + } + + return NO; +} + +// Note from RFC: +// +// If there is a body, the first two +// bytes of the body MUST be a 2-byte unsigned integer (in network byte +// order) representing a status code with value /code/ defined in +// Section 7.4. Following the 2-byte integer the body MAY contain UTF-8 +// encoded data with value /reason/, the interpretation of which is not +// defined by this specification. + +- (void)handleCloseWithData:(NSData *)data; +{ + size_t dataSize = data.length; + __block uint16_t closeCode = 0; + + LGSRFastLog(@"Received close frame"); + + if (dataSize == 1) { + // TODO handle error + [self _closeWithProtocolError:@"Payload for close must be larger than 2 bytes"]; + return; + } else if (dataSize >= 2) { + [data getBytes:&closeCode length:sizeof(closeCode)]; + _closeCode = EndianU16_BtoN(closeCode); + if (!closeCodeIsValid(_closeCode)) { + [self _closeWithProtocolError:[NSString stringWithFormat:@"Cannot have close code of %d", _closeCode]]; + return; + } + if (dataSize > 2) { + _closeReason = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(2, dataSize - 2)] encoding:NSUTF8StringEncoding]; + if (!_closeReason) { + [self _closeWithProtocolError:@"Close reason MUST be valid UTF-8"]; + return; + } + } + } else { + _closeCode = LGSRStatusNoStatusReceived; + } + + [self assertOnWorkQueue]; + + if (self.readyState == LGSR_OPEN) { + [self closeWithCode:1000 reason:nil]; + } + dispatch_async(_workQueue, ^{ + [self closeConnection]; + }); +} + +- (void)closeConnection; +{ + [self assertOnWorkQueue]; + LGSRFastLog(@"Trying to disconnect"); + _closeWhenFinishedWriting = YES; + [self _pumpWriting]; +} + +- (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode; +{ + // Check that the current data is valid UTF8 + + BOOL isControlFrame = (opcode == LGSROpCodePing || opcode == LGSROpCodePong || opcode == LGSROpCodeConnectionClose); + if (!isControlFrame) { + [self _readFrameNew]; + } else { + dispatch_async(_workQueue, ^{ + [self _readFrameContinue]; + }); + } + + switch (opcode) { + case LGSROpCodeTextFrame: { + NSString *str = [[NSString alloc] initWithData:frameData encoding:NSUTF8StringEncoding]; + if (str == nil && frameData) { + [self closeWithCode:LGSRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; + dispatch_async(_workQueue, ^{ + [self closeConnection]; + }); + + return; + } + [self _handleMessage:str]; + break; + } + case LGSROpCodeBinaryFrame: + [self _handleMessage:[frameData copy]]; + break; + case LGSROpCodeConnectionClose: + [self handleCloseWithData:frameData]; + break; + case LGSROpCodePing: + [self handlePing:frameData]; + break; + case LGSROpCodePong: + [self handlePong]; + break; + default: + [self _closeWithProtocolError:[NSString stringWithFormat:@"Unknown opcode %ld", (long)opcode]]; + // TODO: Handle invalid opcode + break; + } +} + +- (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData; +{ + assert(frame_header.opcode != 0); + + if (self.readyState != LGSR_OPEN) { + return; + } + + + BOOL isControlFrame = (frame_header.opcode == LGSROpCodePing || frame_header.opcode == LGSROpCodePong || frame_header.opcode == LGSROpCodeConnectionClose); + + if (isControlFrame && !frame_header.fin) { + [self _closeWithProtocolError:@"Fragmented control frames not allowed"]; + return; + } + + if (isControlFrame && frame_header.payload_length >= 126) { + [self _closeWithProtocolError:@"Control frames cannot have payloads larger than 126 bytes"]; + return; + } + + if (!isControlFrame) { + _currentFrameOpcode = frame_header.opcode; + _currentFrameCount += 1; + } + + if (frame_header.payload_length == 0) { + if (isControlFrame) { + [self _handleFrameWithData:curData opCode:frame_header.opcode]; + } else { + if (frame_header.fin) { + [self _handleFrameWithData:_currentFrameData opCode:frame_header.opcode]; + } else { + // TODO add assert that opcode is not a control; + [self _readFrameContinue]; + } + } + } else { + assert(frame_header.payload_length <= SIZE_T_MAX); + [self _addConsumerWithDataLength:(size_t)frame_header.payload_length callback:^(LGSRWebSocket *self, NSData *newData) { + if (isControlFrame) { + [self _handleFrameWithData:newData opCode:frame_header.opcode]; + } else { + if (frame_header.fin) { + [self _handleFrameWithData:self->_currentFrameData opCode:frame_header.opcode]; + } else { + // TODO add assert that opcode is not a control; + [self _readFrameContinue]; + } + + } + } readToCurrentFrame:!isControlFrame unmaskBytes:frame_header.masked]; + } +} + +/* From RFC: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-------+-+-------------+-------------------------------+ + |F|R|R|R| opcode|M| Payload len | Extended payload length | + |I|S|S|S| (4) |A| (7) | (16/64) | + |N|V|V|V| |S| | (if payload len==126/127) | + | |1|2|3| |K| | | + +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + | Extended payload length continued, if payload len == 127 | + + - - - - - - - - - - - - - - - +-------------------------------+ + | |Masking-key, if MASK set to 1 | + +-------------------------------+-------------------------------+ + | Masking-key (continued) | Payload Data | + +-------------------------------- - - - - - - - - - - - - - - - + + : Payload Data continued ... : + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + | Payload Data continued ... | + +---------------------------------------------------------------+ + */ + +static const uint8_t LGSRFinMask = 0x80; +static const uint8_t LGSROpCodeMask = 0x0F; +static const uint8_t LGSRRsvMask = 0x70; +static const uint8_t LGSRMaskMask = 0x80; +static const uint8_t LGSRPayloadLenMask = 0x7F; + + +- (void)_readFrameContinue; +{ + assert((_currentFrameCount == 0 && _currentFrameOpcode == 0) || (_currentFrameCount > 0 && _currentFrameOpcode > 0)); + + [self _addConsumerWithDataLength:2 callback:^(LGSRWebSocket *self, NSData *data) { + __block frame_header header = {0}; + + const uint8_t *headerBuffer = data.bytes; + assert(data.length >= 2); + + if (headerBuffer[0] & LGSRRsvMask) { + [self _closeWithProtocolError:@"Server used RSV bits"]; + return; + } + + uint8_t receivedOpcode = (LGSROpCodeMask & headerBuffer[0]); + + BOOL isControlFrame = (receivedOpcode == LGSROpCodePing || receivedOpcode == LGSROpCodePong || receivedOpcode == LGSROpCodeConnectionClose); + + if (!isControlFrame && receivedOpcode != 0 && self->_currentFrameCount > 0) { + [self _closeWithProtocolError:@"all data frames after the initial data frame must have opcode 0"]; + return; + } + + if (receivedOpcode == 0 && self->_currentFrameCount == 0) { + [self _closeWithProtocolError:@"cannot continue a message"]; + return; + } + + header.opcode = receivedOpcode == 0 ? self->_currentFrameOpcode : receivedOpcode; + + header.fin = !!(LGSRFinMask & headerBuffer[0]); + + + header.masked = !!(LGSRMaskMask & headerBuffer[1]); + header.payload_length = LGSRPayloadLenMask & headerBuffer[1]; + + headerBuffer = NULL; + + if (header.masked) { + [self _closeWithProtocolError:@"Client must receive unmasked data"]; + } + + size_t extra_bytes_needed = header.masked ? sizeof(_currentReadMaskKey) : 0; + + if (header.payload_length == 126) { + extra_bytes_needed += sizeof(uint16_t); + } else if (header.payload_length == 127) { + extra_bytes_needed += sizeof(uint64_t); + } + + if (extra_bytes_needed == 0) { + [self _handleFrameHeader:header curData:self->_currentFrameData]; + } else { + [self _addConsumerWithDataLength:extra_bytes_needed callback:^(LGSRWebSocket *self, NSData *data) { + size_t mapped_size = data.length; + const void *mapped_buffer = data.bytes; + size_t offset = 0; + + if (header.payload_length == 126) { + assert(mapped_size >= sizeof(uint16_t)); + uint16_t newLen = EndianU16_BtoN(*(uint16_t *)(mapped_buffer)); + header.payload_length = newLen; + offset += sizeof(uint16_t); + } else if (header.payload_length == 127) { + assert(mapped_size >= sizeof(uint64_t)); + header.payload_length = EndianU64_BtoN(*(uint64_t *)(mapped_buffer)); + offset += sizeof(uint64_t); + } else { + assert(header.payload_length < 126 && header.payload_length >= 0); + } + + + if (header.masked) { + assert(mapped_size >= sizeof(_currentReadMaskOffset) + offset); + memcpy(self->_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(self->_currentReadMaskKey)); + } + + [self _handleFrameHeader:header curData:self->_currentFrameData]; + } readToCurrentFrame:NO unmaskBytes:NO]; + } + } readToCurrentFrame:NO unmaskBytes:NO]; +} + +- (void)_readFrameNew; +{ + dispatch_async(_workQueue, ^{ + [_currentFrameData setLength:0]; + + _currentFrameOpcode = 0; + _currentFrameCount = 0; + _readOpCount = 0; + _currentStringScanPosition = 0; + + [self _readFrameContinue]; + }); +} + +- (void)_pumpWriting; +{ + [self assertOnWorkQueue]; + + NSUInteger dataLength = _outputBuffer.length; + if (dataLength - _outputBufferOffset > 0 && _outputStream.hasSpaceAvailable) { + NSInteger bytesWritten = [_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset]; + if (bytesWritten == -1) { + [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:2145 userInfo:[NSDictionary dictionaryWithObject:@"Error writing to stream" forKey:NSLocalizedDescriptionKey]]]; + return; + } + + _outputBufferOffset += bytesWritten; + + if (_outputBufferOffset > 4096 && _outputBufferOffset > (_outputBuffer.length >> 1)) { + _outputBuffer = [[NSMutableData alloc] initWithBytes:(char *)_outputBuffer.bytes + _outputBufferOffset length:_outputBuffer.length - _outputBufferOffset]; + _outputBufferOffset = 0; + } + } + + if (_closeWhenFinishedWriting && + _outputBuffer.length - _outputBufferOffset == 0 && + (_inputStream.streamStatus != NSStreamStatusNotOpen && + _inputStream.streamStatus != NSStreamStatusClosed) && + !_sentClose) { + _sentClose = YES; + @synchronized(self) { + [_outputStream close]; + [_inputStream close]; + + for (NSArray *runLoop in [_scheduledRunloops copy]) { + [self unscheduleFromRunLoop:[runLoop objectAtIndex:0] forMode:[runLoop objectAtIndex:1]]; + } + } + + if (!_failed) { + [self _performDelegateBlock:^{ + if (self.delegate && [self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { + [self.delegate webSocket:self didCloseWithCode:_closeCode reason:_closeReason wasClean:YES]; + } + }]; + } + + [self _scheduleCleanup]; + } +} + +- (void)_scheduleCleanup +{ + @synchronized(self) { + if (_cleanupScheduled) { + return; + } + + _cleanupScheduled = YES; + + // Cleanup NSStream delegate's in the same RunLoop used by the streams themselves: + // This way we'll prevent race conditions between handleEvent and SRWebsocket's dealloc + NSTimer *timer = [NSTimer timerWithTimeInterval:(0.0f) target:self selector:@selector(_cleanupSelfReference:) userInfo:nil repeats:NO]; + [[NSRunLoop LGSR_networkRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; + } +} + +- (void)_cleanupSelfReference:(NSTimer *)timer +{ + @synchronized(self) { + // Nuke NSStream delegate's + _inputStream.delegate = nil; + _outputStream.delegate = nil; + + // Remove the streams, right now, from the networkRunLoop + [_inputStream close]; + [_outputStream close]; + } + + // Cleanup selfRetain in the same GCD queue as usual + dispatch_async(_workQueue, ^{ + _selfRetain = nil; + }); +} + +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback; +{ + [self assertOnWorkQueue]; + [self _addConsumerWithScanner:consumer callback:callback dataLength:0]; +} + +- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + [self assertOnWorkQueue]; + assert(dataLength); + + [_consumers addObject:[_consumerPool consumerWithScanner:nil handler:callback bytesNeeded:dataLength readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]]; + [self _pumpScanner]; +} + +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength; +{ + [self assertOnWorkQueue]; + [_consumers addObject:[_consumerPool consumerWithScanner:consumer handler:callback bytesNeeded:dataLength readToCurrentFrame:NO unmaskBytes:NO]]; + [self _pumpScanner]; +} + + +static const char CRLFCRLFBytes[] = {'\r', '\n', '\r', '\n'}; + +- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; +{ + [self _readUntilBytes:CRLFCRLFBytes length:sizeof(CRLFCRLFBytes) callback:dataHandler]; +} + +- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler; +{ + // TODO optimize so this can continue from where we last searched + stream_scanner consumer = ^size_t(NSData *data) { + __block size_t found_size = 0; + __block size_t match_count = 0; + + size_t size = data.length; + const unsigned char *buffer = data.bytes; + for (size_t i = 0; i < size; i++ ) { + if (((const unsigned char *)buffer)[i] == ((const unsigned char *)bytes)[match_count]) { + match_count += 1; + if (match_count == length) { + found_size = i + 1; + break; + } + } else { + match_count = 0; + } + } + return found_size; + }; + [self _addConsumerWithScanner:consumer callback:dataHandler]; +} + + +// Returns true if did work +- (BOOL)_innerPumpScanner { + + BOOL didWork = NO; + + if (self.readyState >= LGSR_CLOSING) { + dispatch_async(_workQueue, ^{ + _closeCode = LGSRStatusCodeNormal; + [self closeConnection]; + }); + return didWork; + } + + if (!_consumers.count) { + return didWork; + } + + size_t curSize = _readBuffer.length - _readBufferOffset; + if (!curSize) { + return didWork; + } + + LGSRIOConsumer *consumer = [_consumers objectAtIndex:0]; + + size_t bytesNeeded = consumer.bytesNeeded; + + size_t foundSize = 0; + if (consumer.consumer) { + NSData *tempView = [NSData dataWithBytesNoCopy:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset freeWhenDone:NO]; + foundSize = consumer.consumer(tempView); + } else { + assert(consumer.bytesNeeded); + if (curSize >= bytesNeeded) { + foundSize = bytesNeeded; + } else if (consumer.readToCurrentFrame) { + foundSize = curSize; + } + } + + NSData *slice = nil; + if (consumer.readToCurrentFrame || foundSize) { + NSRange sliceRange = NSMakeRange(_readBufferOffset, foundSize); + slice = [_readBuffer subdataWithRange:sliceRange]; + + _readBufferOffset += foundSize; + + if (_readBufferOffset > 4096 && _readBufferOffset > (_readBuffer.length >> 1)) { + _readBuffer = [[NSMutableData alloc] initWithBytes:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset]; _readBufferOffset = 0; + } + + if (consumer.unmaskBytes) { + NSMutableData *mutableSlice = [slice mutableCopy]; + + NSUInteger len = mutableSlice.length; + uint8_t *bytes = mutableSlice.mutableBytes; + + for (NSUInteger i = 0; i < len; i++) { + bytes[i] = bytes[i] ^ _currentReadMaskKey[_currentReadMaskOffset % sizeof(_currentReadMaskKey)]; + _currentReadMaskOffset += 1; + } + + slice = mutableSlice; + } + + if (consumer.readToCurrentFrame) { + [_currentFrameData appendData:slice]; + + _readOpCount += 1; + + if (_currentFrameOpcode == LGSROpCodeTextFrame) { + // Validate UTF8 stuff. + size_t currentDataSize = _currentFrameData.length; + if (_currentFrameOpcode == LGSROpCodeTextFrame && currentDataSize > 0) { + // TODO: Optimize the crap out of this. Don't really have to copy all the data each time + + size_t scanSize = currentDataSize - _currentStringScanPosition; + + NSData *scan_data = [_currentFrameData subdataWithRange:NSMakeRange(_currentStringScanPosition, scanSize)]; + int32_t valid_utf8_size = validate_dispatch_data_partial_string(scan_data); + + if (valid_utf8_size == -1) { + [self closeWithCode:LGSRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; + dispatch_async(_workQueue, ^{ + [self closeConnection]; + }); + return didWork; + } else { + _currentStringScanPosition += valid_utf8_size; + } + } + + } + + consumer.bytesNeeded -= foundSize; + + if (consumer.bytesNeeded == 0) { + [_consumers removeObjectAtIndex:0]; + consumer.handler(self, nil); + [_consumerPool returnConsumer:consumer]; + didWork = YES; + } + } else if (foundSize) { + [_consumers removeObjectAtIndex:0]; + consumer.handler(self, slice); + [_consumerPool returnConsumer:consumer]; + didWork = YES; + } + } + return didWork; +} + +-(void)_pumpScanner; +{ + [self assertOnWorkQueue]; + + if (!_isPumping) { + _isPumping = YES; + } else { + return; + } + + while ([self _innerPumpScanner]) { + + } + + _isPumping = NO; +} + +//#define NOMASK + +static const size_t LGSRFrameHeaderOverhead = 32; + +- (void)_sendFrameWithOpcode:(LGSROpCode)opcode data:(id)data; +{ + [self assertOnWorkQueue]; + + NSAssert(data == nil || [data isKindOfClass:[NSData class]] || [data isKindOfClass:[NSString class]], @"Function expects nil, NSString or NSData"); + + size_t payloadLength = [data isKindOfClass:[NSString class]] ? [(NSString *)data lengthOfBytesUsingEncoding:NSUTF8StringEncoding] : [data length]; + + NSMutableData *frame = [[NSMutableData alloc] initWithLength:payloadLength + LGSRFrameHeaderOverhead]; + if (!frame) { + [self closeWithCode:LGSRStatusCodeMessageTooBig reason:@"Message too big"]; + return; + } + uint8_t *frame_buffer = (uint8_t *)[frame mutableBytes]; + + // set fin + frame_buffer[0] = LGSRFinMask | opcode; + + BOOL useMask = YES; +#ifdef NOMASK + useMask = NO; +#endif + + if (useMask) { + // set the mask and header + frame_buffer[1] |= LGSRMaskMask; + } + + size_t frame_buffer_size = 2; + + const uint8_t *unmasked_payload = NULL; + if ([data isKindOfClass:[NSData class]]) { + unmasked_payload = (uint8_t *)[data bytes]; + } else if ([data isKindOfClass:[NSString class]]) { + unmasked_payload = (const uint8_t *)[data UTF8String]; + } else { + assert(NO); + } + + if (payloadLength < 126) { + frame_buffer[1] |= payloadLength; + } else if (payloadLength <= UINT16_MAX) { + frame_buffer[1] |= 126; + *((uint16_t *)(frame_buffer + frame_buffer_size)) = EndianU16_BtoN((uint16_t)payloadLength); + frame_buffer_size += sizeof(uint16_t); + } else { + frame_buffer[1] |= 127; + *((uint64_t *)(frame_buffer + frame_buffer_size)) = EndianU64_BtoN((uint64_t)payloadLength); + frame_buffer_size += sizeof(uint64_t); + } + + if (!useMask) { + for (size_t i = 0; i < payloadLength; i++) { + frame_buffer[frame_buffer_size] = unmasked_payload[i]; + frame_buffer_size += 1; + } + } else { + uint8_t *mask_key = frame_buffer + frame_buffer_size; + SecRandomCopyBytes(kSecRandomDefault, sizeof(uint32_t), (uint8_t *)mask_key); + frame_buffer_size += sizeof(uint32_t); + + // TODO: could probably optimize this with SIMD + for (size_t i = 0; i < payloadLength; i++) { + frame_buffer[frame_buffer_size] = unmasked_payload[i] ^ mask_key[i % sizeof(uint32_t)]; + frame_buffer_size += 1; + } + } + + assert(frame_buffer_size <= [frame length]); + frame.length = frame_buffer_size; + + [self _writeData:frame]; +} + +- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; +{ + __weak typeof(self) weakSelf = self; + NSMutableArray *certs; + if ([_urlRequest LGSR_SSLPinnedCertificates] == nil && _sslData == nil && (eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) { + + SecTrustRef secTrust = (__bridge SecTrustRef)[aStream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust]; + + if (secTrust) { + certs = [NSMutableArray array]; + NSInteger numCerts = SecTrustGetCertificateCount(secTrust); + _Pragma("clang diagnostic push"); + _Pragma("clang diagnostic ignored \"-Wunreachable-code-loop-increment\""); + for (NSInteger i = 0; i < numCerts; i++) { + SecCertificateRef cert = SecTrustGetCertificateAtIndex(secTrust, i); + //NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(cert)); + //NSString *certSummaryString = (__bridge NSString *)(SecCertificateCopySubjectSummary(cert)); + NSString *certString = CFBridgingRelease(SecCertificateCopySubjectSummary((cert))); + + //NSString *convString = [trustedCertData base64EncodedStringWithOptions:0]; + //NSLog(@"CERT>>>%@",convString); + + ///////////************** handle CA Certificates changes ****** + + SecKeyRef publicKey =SecCertificateCopyPublicKey((cert)); + + NSString *certficatePublicKeyString = nil; + certficatePublicKeyString = [weakSelf getPublicKeyAsBase64:(publicKey)]; + if(certficatePublicKeyString == nil) + { + _ipublicKeyvalue =-1; + [certs addObject:(@"_ipublicKeyvalue =-1")]; + break; + } + + if ([certString rangeOfString:@"Intermediate CA"].length == 0 && [certString rangeOfString:@"SSG"].length == 0) { + // since both the keys do not exist Self signed Server certificate use its public key + // we are not verifying this self signed certificate, because there are several different verfions + // of Self signed certificates obeserved, also anyways we need not check and verify the cases for + //self signed as this is the case in Android side implementation also. + // if required for verification then we can uncomment the below part and the boabe stored self + //signed public key LGSSCPublicKeyString provide we get same self signed certificates + //Verifying the Received Certificate Publickey is Valid and both Same + //Verifying the Received Certificate Publickey is Valid and both Same + /*if((LGSSCPublicKeyString == nil)||(certficatePublicKeyString == nil)|| ([[certficatePublicKeyString stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]] caseInsensitiveCompare:[LGSSCPublicKeyString stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]]])){ + LGSRFastLog(@"Failed Certificate Verification"); + // logging the got data. + LGSRFastLog(@"PUBLICKEY-RSAPublicKey This is what is needed>>>%@",certficatePublicKeyString); + LGSRFastLog(@"LGSSCPublicKeyString>>>%@",LGSSCPublicKeyString); + _ipublicKeyvalue =-1; + [certs addObject:(@"_ipublicKeyvalue =-1")]; + break; + }*/ + }else { if ([certString rangeOfString:@"Intermediate CA"].length == 0) { + // case for ServerCertificate found and use its Publickey + if((LGServerCertificatePublicKeyString == nil)||(certficatePublicKeyString == nil)|| ([[certficatePublicKeyString stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]] caseInsensitiveCompare:[LGServerCertificatePublicKeyString stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]]])){ + LGSRFastLog(@"Failed Certificate Verification"); + // logging the got data. + LGSRFastLog(@"PUBLICKEY-RSAPublicKey This is what is needed>>>%@",certficatePublicKeyString); + LGSRFastLog(@"LGServerCertificatePublicKeyString>>>%@",LGServerCertificatePublicKeyString); + _ipublicKeyvalue =-1; + [certs addObject:(@"_ipublicKeyvalue =-1")]; + break; + } + } else {// IntermediateCA found use its public key + //Verifying the Received Certificate Publickey is Valid and both Same + if((LGIntermediateCAPublicKeyString == nil)||(certficatePublicKeyString == nil)|| ([[certficatePublicKeyString stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]] caseInsensitiveCompare:[LGIntermediateCAPublicKeyString stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]]])){ + LGSRFastLog(@"Failed Certificate Verification"); + // logging the got data. + LGSRFastLog(@"PUBLICKEY-RSAPublicKey This is what is needed>>>%@",certficatePublicKeyString); + LGSRFastLog(@"LGIntermediateCAPublicKeyString>>>%@",LGIntermediateCAPublicKeyString); + _ipublicKeyvalue =-1; + [certs addObject:(@"_ipublicKeyvalue =-1")]; + break; + } + } + } + + LGSRFastLog(@"Passed Certificate Verification"); + _ipublicKeyvalue =1; + + //check for validity + OSStatus err; + BOOL allowConnection; + CFArrayRef policies; + NSMutableArray * certificates; + CFIndex certCount; + CFIndex certIndex; + SecTrustRef newTrust; + SecTrustResultType newTrustResult; + + allowConnection = NO; + + policies = NULL; + newTrust = NULL; + + err = SecTrustCopyPolicies(secTrust, &policies); + if (err == errSecSuccess) { + certificates = [NSMutableArray array]; + + certCount = SecTrustGetCertificateCount(secTrust); + for (certIndex = 0; certIndex < certCount; certIndex++) { + SecCertificateRef thisCertificate; + + thisCertificate = SecTrustGetCertificateAtIndex(secTrust, certIndex); + [certificates addObject:(__bridge id)thisCertificate]; + } + + //extraIntermediate = ... the extra intermediate certificate to use ...; + // [certificates addObject:(__bridge id)extraIntermediate]; + + err = SecTrustCreateWithCertificates( + (__bridge CFArrayRef) certificates, + policies, + &newTrust + ); + if (err == noErr) { + SecPolicyRef policy; + policy = SecPolicyCreateBasicX509();//SecPolicyCreateSSL(true, CFSTR("0" )); + err = SecTrustSetPolicies(newTrust, policy); + if (err != noErr) { + LGSRFastLog(@"Policy Setting failed"); + [certs addObject:(@"_icertificateValidity=-1")]; + break; + } + CFRelease(policy); + SecTrustSetAnchorCertificates(newTrust, (__bridge CFArrayRef) certificates); + err = SecTrustEvaluate(newTrust, &newTrustResult); + } + if (err == noErr) { + allowConnection = (newTrustResult == kSecTrustResultProceed) || + (newTrustResult == kSecTrustResultUnspecified); + + if(newTrustResult == kSecTrustEvaluationDate){ + LGSRFastLog(@"*******Failed - Trust evaluation as the Certificate Expired******"); + _icertificateValidity=-1; + [certs addObject:(@"_icertificateValidity=-1")]; + break; + } + _icertificateValidity=1; + /* From SecTrust.h: + * + * SecTrustResultType results have two dimensions. They specify both whether + * evaluation suceeded and whether this is because of a user decision. + * + * In practice the commonly expected result is kSecTrustResultUnspecified, + * which indicates a positive result that wasn't decided by the user. + * + * The common failure is kSecTrustResultRecoverableTrustFailure, which means a + * negative result. kSecTrustResultProceed and kSecTrustResultDeny are the + * positive and negative result respectively when decided by the user. User + * decisions are persisted through the use of SecTrustCopyExceptions() and + * SecTrustSetExceptions(). Finally kSecTrustResultFatalTrustFailure is a + * negative result that should not be circumvented. In fact only in the case + * of kSecTrustResultRecoverableTrustFailure should a user ever be asked. + */ + switch (newTrustResult) { + case kSecTrustResultProceed: // 1 + case kSecTrustResultConfirm: // 2 depricated in ios 7.0 + LGSRFastLog(@"Passed Trust evaluation"); + _icertificateValidity=1; + break; + case kSecTrustResultUnspecified: // 4 + case kSecTrustResultRecoverableTrustFailure: // 5 + case kSecTrustResultDeny: // 3 + case kSecTrustResultFatalTrustFailure: // 6 + case kSecTrustResultOtherError: // 7 + case kSecTrustResultInvalid: // 0 + { + LGSRFastLog(@"Trust evaluation Policy Applied"); + CFDataRef errDataRef = SecTrustCopyExceptions(newTrust); + LGSRFastLog(@"errDataRef=%@", errDataRef); + SecTrustSetExceptions(newTrust, errDataRef); + err = SecTrustEvaluate(newTrust, &newTrustResult); + if (err == noErr) { + allowConnection = (newTrustResult == kSecTrustResultProceed) || + (newTrustResult == kSecTrustResultUnspecified); + LGSRFastLog(@"Passed Trust evaluation"); + _icertificateValidity=1; + } + } + default: + { +#ifdef __IPHONE_7_0 //|| __IPHONE_9_0 + if((newTrustResult == kSecPropertyTypeTitle) ||(newTrustResult == kSecPropertyTypeError) ||(newTrustResult == kSecTrustExtendedValidation)|| + (newTrustResult == kSecTrustOrganizationName)||(newTrustResult == kSecTrustResultValue)||(newTrustResult == kSecTrustRevocationChecked) + || (newTrustResult == kSecTrustRevocationValidUntilDate)||(newTrustResult == kSecTrustCertificateTransparency)) +#endif + { + LGSRFastLog(@"Trust evaluation Policy Applied"); + CFDataRef errDataRef = SecTrustCopyExceptions(newTrust); + LGSRFastLog(@"errDataRef=%@", errDataRef); + SecTrustSetExceptions(newTrust, errDataRef); + err = SecTrustEvaluate(newTrust, &newTrustResult); + if (err == noErr) { + allowConnection = (newTrustResult == kSecTrustResultProceed) || + (newTrustResult == kSecTrustResultUnspecified); + LGSRFastLog(@"Passed Trust evaluation"); + _icertificateValidity=1; + } + + } + // return; + break; + } + } + + } else { + LGSRFastLog(@"************* Trust evaluation Failed***************"); + _ipublicKeyvalue =-1; + [certs addObject:(@"_ipublicKeyvalue =-1")]; + break; + } + } + + if (newTrust != NULL) { + CFRelease(newTrust); + } + if (policies != NULL) { + CFRelease(policies); + } + + if(publicKey != NULL){ + CFRelease(publicKey); + } + + //////////// + if ( _icertificateValidity== 1 && _ipublicKeyvalue== 1 ) + { + [certs addObject:certString]; + // break; // to be confirmed for SSL. + } + + } + _Pragma("clang diagnostic pop"); + } + else { + LGSRFastLog(@"************* Trust evaluation Failed***************"); + _ipublicKeyvalue =-1; + [certs addObject:(@"_ipublicKeyvalue =-1")]; + } + + if ( _icertificateValidity== 1&&_ipublicKeyvalue== 1 ) + { + + [_urlRequest setLGSR_SSLPinnedCertificates:certs]; + + } + + } + + if (_secure && !_pinnedCertFound && (eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) { + + NSArray *sslCerts = [_urlRequest LGSR_SSLPinnedCertificates]; + if (sslCerts) { + SecTrustRef secTrust = (__bridge SecTrustRef)[aStream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust]; + if (secTrust) { + NSInteger numCerts = SecTrustGetCertificateCount(secTrust); + for (NSInteger i = 0; i < numCerts && !_pinnedCertFound; i++) { + SecCertificateRef cert = SecTrustGetCertificateAtIndex(secTrust, i); + //NSData *certData = CFBridgingRelease(SecCertificateCopyData(cert)); + //NSString *certData = (__bridge NSString *)(SecCertificateCopySubjectSummary(cert)); + NSString *certData = CFBridgingRelease(SecCertificateCopySubjectSummary((cert))); + + for (id ref in sslCerts) { + //if ([ref isEqualToData:certData]) { + if ([ref isEqualToString:certData]) { + _pinnedCertFound = YES; + LGSRFastLog(@"*************_secure && _pinnedCertFound ***************" ); + // break;// to be confirmed for SSL. + + } + } + } + } + else { + LGSRFastLog(@"************* secTrust evaluation is nil ***************"); + } + }else { + LGSRFastLog(@"************* LGSR_SSLPinnedCertificates is nil ***************"); + _icertificateValidity=-1; + [certs addObject:(@"_icertificateValidity=-1")]; + } + _sslData = certs; + + [weakSelf _performDelegateBlock:^{ + if ([weakSelf.delegate respondsToSelector:@selector(webSocket:didGetCertificates:)]) { + [weakSelf.delegate webSocket:weakSelf didGetCertificates:_sslData]; + } + }]; + + if (!_pinnedCertFound || (certs && (_icertificateValidity== -1 || _ipublicKeyvalue== -1))) { + NSLog(@"*************TV CERTIFICATE FAILURE ****************"); + dispatch_async(_workQueue, ^{ + [weakSelf _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:23556 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Invalid server cert"] forKey:NSLocalizedDescriptionKey]]]; + }); + return; + } + } + dispatch_async(_workQueue, ^{ + [weakSelf safeHandleEvent:eventCode stream:aStream]; + }); +} + +- (void)safeHandleEvent:(NSStreamEvent)eventCode stream:(NSStream *)aStream +{ + + dispatch_async(_workQueue, ^{ + switch (eventCode) { + case NSStreamEventOpenCompleted: { + LGSRFastLog(@"NSStreamEventOpenCompleted %@", aStream); + if (self.readyState >= LGSR_CLOSING) { + return; + } + assert(_readBuffer); + + if (self.readyState == LGSR_CONNECTING && aStream == _inputStream) { + [self didConnect]; + } + [self _pumpWriting]; + [self _pumpScanner]; + break; + } + + case NSStreamEventErrorOccurred: { + LGSRFastLog(@"NSStreamEventErrorOccurred %@ %@", aStream, [[aStream streamError] copy]); + /// TODO specify error better! + [self _failWithError:aStream.streamError]; + _readBufferOffset = 0; + [_readBuffer setLength:0]; + break; + + } + + case NSStreamEventEndEncountered: { + [self _pumpScanner]; + LGSRFastLog(@"NSStreamEventEndEncountered %@", aStream); + if (aStream.streamError) { + [self _failWithError:aStream.streamError]; + } else { + if (self.readyState != LGSR_CLOSED) { + self.readyState = LGSR_CLOSED; + [self _scheduleCleanup]; + } + + if (!_sentClose && !_failed) { + _sentClose = YES; + // If we get closed in this state it's probably not clean because we should be sending this when we send messages + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { + [self.delegate webSocket:self didCloseWithCode:0 reason:@"Stream end encountered" wasClean:NO]; + } + }]; + } + } + + break; + } + + case NSStreamEventHasBytesAvailable: { + LGSRFastLog(@"NSStreamEventHasBytesAvailable %@", aStream); + const int bufferSize = 2048; + uint8_t buffer[bufferSize]; + + while (_inputStream.hasBytesAvailable) { + NSInteger bytes_read = [_inputStream read:buffer maxLength:bufferSize]; + + if (bytes_read > 0) { + [_readBuffer appendBytes:buffer length:bytes_read]; + } else if (bytes_read < 0) { + [self _failWithError:_inputStream.streamError]; + } + + if (bytes_read != bufferSize) { + break; + } + }; + [self _pumpScanner]; + break; + } + + case NSStreamEventHasSpaceAvailable: { + LGSRFastLog(@"NSStreamEventHasSpaceAvailable %@", aStream); + [self _pumpWriting]; + break; + } + + default: + LGSRFastLog(@"(default) %@", aStream); + break; + } + }); +} + +// Helper function for ASN.1 encoding +-(size_t) encodeLength:(unsigned char *)buf:(size_t)length { + + // encode length in ASN.1 DER format + if (length < 128) { + buf[0] = length; + return 1; + } + + size_t i = (length / 256) + 1; + buf[0] = i + 0x80; + for (size_t j = 0 ; j < i; ++j) { + buf[i - j] = length & 0xFF; + length = length >> 8; + } + + return i + 1; +} + +#define _MY_PUBLIC_KEY_TAG "com.lge.remote-app.publickey" + +- (NSString *) getPublicKeyAsBase64 :(SecKeyRef)publicKey { + + static const unsigned char _encodedRSAEncryptionOID[15] = { + + /* Sequence of length 0xd made up of OID followed by NULL */ + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00 + + }; + + NSData * publicTag = [NSData dataWithBytes:_MY_PUBLIC_KEY_TAG + length:strlen((const char *) _MY_PUBLIC_KEY_TAG)]; + + // Now lets extract the public key - build query to get bits + NSMutableDictionary * queryPublicKey = + [[NSMutableDictionary alloc] init]; + + [queryPublicKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass]; + [queryPublicKey setObject:publicTag forKey:(__bridge id)kSecAttrApplicationTag]; + [queryPublicKey setObject:(__bridge id)publicKey forKey:(__bridge id)kSecValueRef]; + [queryPublicKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType]; + //[queryPublicKey setObject:@2048 forKey:(__bridge id)kSecAttrKeySizeInBits]; + [queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnData]; + + CFTypeRef result1; + NSData * publicKeyBits; + OSStatus sanityCheck = SecItemAdd((__bridge CFDictionaryRef) queryPublicKey, &result1); + sanityCheck = SecItemCopyMatching((CFDictionaryRef)queryPublicKey, &result1); + if (sanityCheck == errSecSuccess) { + publicKeyBits = CFBridgingRelease(result1); + + (void)SecItemDelete((__bridge CFDictionaryRef) queryPublicKey); + + } + + // OK - that gives us the "BITSTRING component of a full DER + // encoded RSA public key - we now need to build the rest + + unsigned char builder[15]; + NSMutableData * encKey = [[NSMutableData alloc] init]; + int bitstringEncLength; + + // When we get to the bitstring - how will we encode it? + if ([publicKeyBits length ] + 1 < 128 ) + bitstringEncLength = 1 ; + else + bitstringEncLength = (([publicKeyBits length ] +1 ) / 256 ) + 2 ; + + // Overall we have a sequence of a certain length + builder[0] = 0x30; // ASN.1 encoding representing a SEQUENCE + // Build up overall size made up of - + // size of OID + size of bitstring encoding + size of actual key + size_t i = sizeof(_encodedRSAEncryptionOID) + 2 + bitstringEncLength + + [publicKeyBits length]; + size_t j = [self encodeLength :&builder[1]: i]; + [encKey appendBytes:builder length:j +1]; + + // First part of the sequence is the OID + [encKey appendBytes:_encodedRSAEncryptionOID + length:sizeof(_encodedRSAEncryptionOID)]; + + // Now add the bitstring + builder[0] = 0x03; + j = [self encodeLength : &builder[1]: [publicKeyBits length] + 1]; + builder[j+1] = 0x00; + [encKey appendBytes:builder length:j + 2]; + + // Now the actual key + [encKey appendData:publicKeyBits]; + + // Now translate the result to a Base64 string + NSString * ret = [encKey base64EncodedStringWithOptions:[encKey length]]; + CFRelease(CFBridgingRetain(encKey)); + return ret; +} +@end + + +@implementation LGSRIOConsumer + +@synthesize bytesNeeded = _bytesNeeded; +@synthesize consumer = _scanner; +@synthesize handler = _handler; +@synthesize readToCurrentFrame = _readToCurrentFrame; +@synthesize unmaskBytes = _unmaskBytes; + +- (void)setupWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + _scanner = [scanner copy]; + _handler = [handler copy]; + _bytesNeeded = bytesNeeded; + _readToCurrentFrame = readToCurrentFrame; + _unmaskBytes = unmaskBytes; + assert(_scanner || _bytesNeeded); +} + + +@end + + +@implementation LGSRIOConsumerPool { + NSUInteger _poolSize; + NSMutableArray *_bufferedConsumers; +} + +- (id)initWithBufferCapacity:(NSUInteger)poolSize; +{ + self = [super init]; + if (self) { + _poolSize = poolSize; + _bufferedConsumers = [[NSMutableArray alloc] initWithCapacity:poolSize]; + } + return self; +} + +- (id)init +{ + return [self initWithBufferCapacity:8]; +} + +- (LGSRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + LGSRIOConsumer *consumer = nil; + if (_bufferedConsumers.count) { + consumer = [_bufferedConsumers lastObject]; + [_bufferedConsumers removeLastObject]; + } else { + consumer = [[LGSRIOConsumer alloc] init]; + } + + [consumer setupWithScanner:scanner handler:handler bytesNeeded:bytesNeeded readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]; + + return consumer; +} + +- (void)returnConsumer:(LGSRIOConsumer *)consumer; +{ + if (_bufferedConsumers.count < _poolSize) { + [_bufferedConsumers addObject:consumer]; + } +} + +@end + + +@implementation NSURLRequest (CertificateAdditions) + +- (NSArray *)LGSR_SSLPinnedCertificates; +{ + return [NSURLProtocol propertyForKey:@"LGSR_SSLPinnedCertificates" inRequest:self]; +} + +@end + +@implementation NSMutableURLRequest (CertificateAdditions) + +- (NSArray *)LGSR_SSLPinnedCertificates; +{ + return [NSURLProtocol propertyForKey:@"LGSR_SSLPinnedCertificates" inRequest:self]; +} + +- (void)setLGSR_SSLPinnedCertificates:(NSArray *)LGSR_SSLPinnedCertificates; +{ + [NSURLProtocol setProperty:LGSR_SSLPinnedCertificates forKey:@"LGSR_SSLPinnedCertificates" inRequest:self]; +} + +@end + +@implementation NSURL (LGSRWebSocket) + +- (NSString *)LGSR_origin; +{ + NSString *scheme = [self.scheme lowercaseString]; + + if ([scheme isEqualToString:@"wss"]) { + scheme = @"https"; + } else if ([scheme isEqualToString:@"ws"]) { + scheme = @"http"; + } + + if (self.port) { + return [NSString stringWithFormat:@"%@://%@:%@/", scheme, self.host, self.port]; + } else { + return [NSString stringWithFormat:@"%@://%@/", scheme, self.host]; + } +} + +@end + +//#define LGSR_ENABLE_LOG + +static inline void LGSRFastLog(NSString *format, ...) { +#ifdef LGSR_ENABLE_LOG + __block va_list arg_list; + va_start (arg_list, format); + + NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list]; + + va_end(arg_list); + + DLog(@"[LGSR] %@", formattedString); +#endif +} + + +#ifdef HAS_ICU + +static inline int32_t validate_dispatch_data_partial_string(NSData *data) { + if ([data length] > INT32_MAX) { + // INT32_MAX is the limit so long as this Framework is using 32 bit ints everywhere. + return -1; + } + + int32_t size = (int32_t)[data length]; + + const void * contents = [data bytes]; + const uint8_t *str = (const uint8_t *)contents; + + UChar32 codepoint = 1; + int32_t offset = 0; + int32_t lastOffset = 0; + while(offset < size && codepoint > 0) { + lastOffset = offset; + U8_NEXT(str, offset, size, codepoint); + } + + if (codepoint == -1) { + // Check to see if the last byte is valid or whether it was just continuing + if (!U8_IS_LEAD(str[lastOffset]) || U8_COUNT_TRAIL_BYTES(str[lastOffset]) + lastOffset < (int32_t)size) { + + size = -1; + } else { + uint8_t leadByte = str[lastOffset]; + U8_MASK_LEAD_BYTE(leadByte, U8_COUNT_TRAIL_BYTES(leadByte)); + + for (int i = lastOffset + 1; i < offset; i++) { + if (U8_IS_SINGLE(str[i]) || U8_IS_LEAD(str[i]) || !U8_IS_TRAIL(str[i])) { + size = -1; + } + } + + if (size != -1) { + size = lastOffset; + } + } + } + + if (size != -1 && ![[NSString alloc] initWithBytesNoCopy:(char *)[data bytes] length:size encoding:NSUTF8StringEncoding freeWhenDone:NO]) { + size = -1; + } + + return (int32_t)size; +} + +#else + +// This is a hack, and probably not optimal +static inline int32_t validate_dispatch_data_partial_string(NSData *data) { + static const int maxCodepointSize = 3; + + for (int i = 0; i < maxCodepointSize; i++) { + NSString *str = [[NSString alloc] initWithBytesNoCopy:(char *)data.bytes length:data.length - i encoding:NSUTF8StringEncoding freeWhenDone:NO]; + if (str) { + return data.length - i; + } + } + + return -1; +} + +#endif + +static _LGSRRunLoopThread *networkThread = nil; +static NSRunLoop *networkRunLoop = nil; + +@implementation NSRunLoop (LGSRWebSocket) + ++ (NSRunLoop *)LGSR_networkRunLoop { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + networkThread = [[_LGSRRunLoopThread alloc] init]; + networkThread.name = @"com.squareup.SocketRocket.NetworkThread"; + [networkThread start]; + networkRunLoop = networkThread.runLoop; + }); + + return networkRunLoop; +} + +@end + + +@implementation _LGSRRunLoopThread { + dispatch_group_t _waitGroup; +} + +@synthesize runLoop = _runLoop; + +- (void)dealloc +{ + lgsr_dispatch_release(_waitGroup); +} + +- (id)init +{ + self = [super init]; + if (self) { + _waitGroup = dispatch_group_create(); + dispatch_group_enter(_waitGroup); + } + return self; +} + +- (void)main; +{ + @autoreleasepool { + _runLoop = [NSRunLoop currentRunLoop]; + dispatch_group_leave(_waitGroup); + + NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture] interval:0.0 target:nil selector:nil userInfo:nil repeats:NO]; + [_runLoop addTimer:timer forMode:NSDefaultRunLoopMode]; + + while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) { + + } + assert(NO); + } +} + +- (NSRunLoop *)runLoop; +{ + dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER); + return _runLoop; +} + +@end diff --git a/core/Frameworks/SocketRocket/LICENSE b/core/Frameworks/SocketRocket/LICENSE new file mode 100644 index 00000000..c01a79c3 --- /dev/null +++ b/core/Frameworks/SocketRocket/LICENSE @@ -0,0 +1,15 @@ + + Copyright 2012 Square Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/core/Frameworks/XMLReader/CTXMLReader.h b/core/Frameworks/XMLReader/CTXMLReader.h new file mode 100644 index 00000000..04efdc0d --- /dev/null +++ b/core/Frameworks/XMLReader/CTXMLReader.h @@ -0,0 +1,29 @@ +// +// XMLReader.h +// +// Created by Troy Brant on 9/18/10. +// Updated by Antoine Marcadet on 9/23/11. +// Updated by Divan Visagie on 2012-08-26 +// Source: https://github.com/amarcadet/XMLReader +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +#import + +enum { + CTXMLReaderOptionsProcessNamespaces = 1 << 0, // Specifies whether the receiver reports the namespace and the qualified name of an element. + CTXMLReaderOptionsReportNamespacePrefixes = 1 << 1, // Specifies whether the receiver reports the scope of namespace declarations. + CTXMLReaderOptionsResolveExternalEntities = 1 << 2, // Specifies whether the receiver reports declarations of external entities. +}; +typedef NSUInteger CTXMLReaderOptions; + +@interface CTXMLReader : NSObject + ++ (NSDictionary *)dictionaryForXMLData:(NSData *)data error:(NSError **)errorPointer; ++ (NSDictionary *)dictionaryForXMLString:(NSString *)string error:(NSError **)errorPointer; ++ (NSDictionary *)dictionaryForXMLData:(NSData *)data options:(CTXMLReaderOptions)options error:(NSError **)errorPointer; ++ (NSDictionary *)dictionaryForXMLString:(NSString *)string options:(CTXMLReaderOptions)options error:(NSError **)errorPointer; + +@end diff --git a/core/Frameworks/XMLReader/CTXMLReader.m b/core/Frameworks/XMLReader/CTXMLReader.m new file mode 100644 index 00000000..bfd1864b --- /dev/null +++ b/core/Frameworks/XMLReader/CTXMLReader.m @@ -0,0 +1,179 @@ +// +// XMLReader.m +// +// Created by Troy Brant on 9/18/10. +// Updated by Antoine Marcadet on 9/23/11. +// Updated by Divan Visagie on 2012-08-26 +// Source: https://github.com/amarcadet/XMLReader +// + +#import "CTXMLReader.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "CTXMLReader requires ARC support." +#endif + +NSString *const kCTXMLReaderTextNodeKey = @"text"; +NSString *const kCTXMLReaderAttributePrefix = @"@"; + +@interface CTXMLReader () + +@property (nonatomic, strong) NSMutableArray *dictionaryStack; +@property (nonatomic, strong) NSMutableString *textInProgress; +@property (nonatomic, strong) NSError *errorPointer; + +@end + + +@implementation CTXMLReader + +#pragma mark - Public methods + ++ (NSDictionary *)dictionaryForXMLData:(NSData *)data error:(NSError **)error +{ + return [[self class] dictionaryForXMLData:data options:0 error:error]; +} + ++ (NSDictionary *)dictionaryForXMLString:(NSString *)string error:(NSError **)error +{ + NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; + return [CTXMLReader dictionaryForXMLData:data options:0 error:error]; +} + ++ (NSDictionary *)dictionaryForXMLData:(NSData *)data options:(CTXMLReaderOptions)options error:(NSError **)error +{ + CTXMLReader *reader = [[CTXMLReader alloc] initWithError:error]; + NSDictionary *rootDictionary = [reader objectWithData:data options:options]; + if (!rootDictionary && error) + { + *error = reader.errorPointer; + } + return rootDictionary; +} + ++ (NSDictionary *)dictionaryForXMLString:(NSString *)string options:(CTXMLReaderOptions)options error:(NSError **)error +{ + NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; + return [CTXMLReader dictionaryForXMLData:data options:options error:error]; +} + + +#pragma mark - Parsing + +- (id)initWithError:(NSError **)error +{ + self = [super init]; + if (self) + { + self.errorPointer = (error ? *error : nil); + } + return self; +} + +- (NSDictionary *)objectWithData:(NSData *)data options:(CTXMLReaderOptions)options +{ + // Clear out any old data + self.dictionaryStack = [[NSMutableArray alloc] init]; + self.textInProgress = [[NSMutableString alloc] init]; + + // Initialize the stack with a fresh dictionary + [self.dictionaryStack addObject:[NSMutableDictionary dictionary]]; + + // Parse the XML + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; + + [parser setShouldProcessNamespaces:(options & CTXMLReaderOptionsProcessNamespaces)]; + [parser setShouldReportNamespacePrefixes:(options & CTXMLReaderOptionsReportNamespacePrefixes)]; + [parser setShouldResolveExternalEntities:(options & CTXMLReaderOptionsResolveExternalEntities)]; + + parser.delegate = self; + BOOL success = [parser parse]; + + // Return the stack's root dictionary on success + if (success) + { + NSDictionary *resultDict = [self.dictionaryStack objectAtIndex:0]; + return resultDict; + } + + return nil; +} + + +#pragma mark - NSXMLParserDelegate methods + +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict +{ + // Get the dictionary for the current level in the stack + NSMutableDictionary *parentDict = [self.dictionaryStack lastObject]; + + // Create the child dictionary for the new element, and initilaize it with the attributes + NSMutableDictionary *childDict = [NSMutableDictionary dictionary]; + [childDict addEntriesFromDictionary:attributeDict]; + + // If there's already an item for this key, it means we need to create an array + id existingValue = [parentDict objectForKey:elementName]; + if (existingValue) + { + NSMutableArray *array = nil; + if ([existingValue isKindOfClass:[NSMutableArray class]]) + { + // The array exists, so use it + array = (NSMutableArray *) existingValue; + } + else + { + // Create an array if it doesn't exist + array = [NSMutableArray array]; + [array addObject:existingValue]; + + // Replace the child dictionary with an array of children dictionaries + [parentDict setObject:array forKey:elementName]; + } + + // Add the new child dictionary to the array + [array addObject:childDict]; + } + else + { + // No existing value, so update the dictionary + [parentDict setObject:childDict forKey:elementName]; + } + + // Update the stack + [self.dictionaryStack addObject:childDict]; +} + +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName +{ + // Update the parent dict with text info + NSMutableDictionary *dictInProgress = [self.dictionaryStack lastObject]; + + // Set the text property + if ([self.textInProgress length] > 0) + { + // trim after concatenating + NSString *trimmedString = [self.textInProgress stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + [dictInProgress setObject:[trimmedString mutableCopy] forKey:kCTXMLReaderTextNodeKey]; + + // Reset the text + self.textInProgress = [[NSMutableString alloc] init]; + } + + // Pop the current dict + [self.dictionaryStack removeLastObject]; +} + +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string +{ + // Build the text value + [self.textInProgress appendString:string]; +} + +- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError +{ + // Set the error pointer to the parser's error object + self.errorPointer = parseError; +} + +@end \ No newline at end of file diff --git a/core/Frameworks/XMLReader/LICENSE b/core/Frameworks/XMLReader/LICENSE new file mode 100644 index 00000000..2ae015af --- /dev/null +++ b/core/Frameworks/XMLReader/LICENSE @@ -0,0 +1,7 @@ +Copyright (C) 2012 Antoine Marcadet + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/core/Frameworks/asi-http-request/Classes/CTASIAuthenticationDialog.h b/core/Frameworks/asi-http-request/Classes/CTASIAuthenticationDialog.h new file mode 100644 index 00000000..03fb0305 --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASIAuthenticationDialog.h @@ -0,0 +1,39 @@ +// +// CTASIAuthenticationDialog.h +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 21/08/2009. +// Copyright 2009 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +#import +#import +@class CTASIHTTPRequest; + +typedef enum _CTASIAuthenticationType +{ + CTASIStandardAuthenticationType = 0, + CTASIProxyAuthenticationType = 1 +} CTASIAuthenticationType; + +@interface CTASIAutorotatingViewController : UIViewController +@end + +@interface CTASIAuthenticationDialog : CTASIAutorotatingViewController { + CTASIHTTPRequest *request; + CTASIAuthenticationType type; + UITableView *tableView; + UIViewController *presentingController; + BOOL didEnableRotationNotifications; +} ++ (void)presentAuthenticationDialogForRequest:(CTASIHTTPRequest *)request; ++ (void)dismiss; + +@property (atomic, retain) CTASIHTTPRequest *request; +@property (atomic, assign) CTASIAuthenticationType type; +@property (atomic, assign) BOOL didEnableRotationNotifications; +@property (retain, nonatomic) UIViewController *presentingController; +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASIAuthenticationDialog.m b/core/Frameworks/asi-http-request/Classes/CTASIAuthenticationDialog.m new file mode 100644 index 00000000..ecad526c --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASIAuthenticationDialog.m @@ -0,0 +1,525 @@ +// +// CTASIAuthenticationDialog.m +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 21/08/2009. +// Copyright 2009 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +#import "CTASIAuthenticationDialog.h" +#import "CTASIHTTPRequest.h" +#import + +static CTASIAuthenticationDialog *sharedDialog = nil; +BOOL isDismissing = NO; +static NSMutableArray *requestsNeedingAuthentication = nil; + +static const NSUInteger kUsernameRow = 0; +static const NSUInteger kUsernameSection = 0; +static const NSUInteger kPasswordRow = 1; +static const NSUInteger kPasswordSection = 0; +static const NSUInteger kDomainRow = 0; +static const NSUInteger kDomainSection = 1; + + +@implementation CTASIAutorotatingViewController + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation +{ + return YES; +} + +@end + + +@interface CTASIAuthenticationDialog () +- (void)showTitle; +- (void)show; +- (NSArray *)requestsRequiringTheseCredentials; +- (void)presentNextDialog; +- (void)keyboardWillShow:(NSNotification *)notification; +- (void)orientationChanged:(NSNotification *)notification; +- (void)cancelAuthenticationFromDialog:(id)sender; +- (void)loginWithCredentialsFromDialog:(id)sender; +@property (atomic, retain) UITableView *tableView; +@end + +@implementation CTASIAuthenticationDialog + +#pragma mark init / dealloc + ++ (void)initialize +{ + if (self == [CTASIAuthenticationDialog class]) { + requestsNeedingAuthentication = [[NSMutableArray array] retain]; + } +} + ++ (void)presentAuthenticationDialogForRequest:(CTASIHTTPRequest *)theRequest +{ + // No need for a lock here, this will always be called on the main thread + if (!sharedDialog) { + sharedDialog = [[self alloc] init]; + [sharedDialog setRequest:theRequest]; + if ([theRequest authenticationNeeded] == ASIProxyAuthenticationNeeded) { + [sharedDialog setType:CTASIProxyAuthenticationType]; + } else { + [sharedDialog setType:CTASIStandardAuthenticationType]; + } + [sharedDialog show]; + } else { + [requestsNeedingAuthentication addObject:theRequest]; + } +} + +- (id)init +{ + if ((self = [self initWithNibName:nil bundle:nil])) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { +#endif + if (![UIDevice currentDevice].generatesDeviceOrientationNotifications) { + [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; + [self setDidEnableRotationNotifications:YES]; + } + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil]; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 + } +#endif + } + return self; +} + +- (void)dealloc +{ + if ([self didEnableRotationNotifications]) { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; + } + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; + + [request release]; + [tableView release]; + [presentingController.view removeFromSuperview]; + [presentingController release]; + [super dealloc]; +} + +#pragma mark keyboard notifications + +- (void)keyboardWillShow:(NSNotification *)notification +{ +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { +#endif +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_2 + NSValue *keyboardBoundsValue = [[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey]; +#else + NSValue *keyboardBoundsValue = [[notification userInfo] objectForKey:UIKeyboardBoundsUserInfoKey]; +#endif + CGRect keyboardBounds; + [keyboardBoundsValue getValue:&keyboardBounds]; + UIEdgeInsets e = UIEdgeInsetsMake(0, 0, keyboardBounds.size.height, 0); + [[self tableView] setScrollIndicatorInsets:e]; + [[self tableView] setContentInset:e]; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 + } +#endif +} + +// Manually handles orientation changes on iPhone +- (void)orientationChanged:(NSNotification *)notification +{ + [self showTitle]; + + UIInterfaceOrientation o = (UIInterfaceOrientation)[[UIApplication sharedApplication] statusBarOrientation]; + CGFloat angle = 0; + switch (o) { + case UIDeviceOrientationLandscapeLeft: angle = 90; break; + case UIDeviceOrientationLandscapeRight: angle = -90; break; + case UIDeviceOrientationPortraitUpsideDown: angle = 180; break; + default: break; + } + + CGRect f = [[UIScreen mainScreen] applicationFrame]; + + // Swap the frame height and width if necessary + if (UIDeviceOrientationIsLandscape(o)) { + CGFloat t; + t = f.size.width; + f.size.width = f.size.height; + f.size.height = t; + } + + CGAffineTransform previousTransform = self.view.layer.affineTransform; + CGAffineTransform newTransform = CGAffineTransformMakeRotation((CGFloat)(angle * M_PI / 180.0)); + + // Reset the transform so we can set the size + self.view.layer.affineTransform = CGAffineTransformIdentity; + self.view.frame = (CGRect){ { 0, 0 }, f.size}; + + // Revert to the previous transform for correct animation + self.view.layer.affineTransform = previousTransform; + + [UIView beginAnimations:nil context:NULL]; + [UIView setAnimationDuration:0.3]; + + // Set the new transform + self.view.layer.affineTransform = newTransform; + + // Fix the view origin + self.view.frame = (CGRect){ { f.origin.x, f.origin.y },self.view.frame.size}; + [UIView commitAnimations]; +} + +#pragma mark utilities + +- (UIViewController *)presentingController +{ + if (!presentingController) { + presentingController = [[CTASIAutorotatingViewController alloc] initWithNibName:nil bundle:nil]; + + // Attach to the window, but don't interfere. + UIWindow *window = [[[UIApplication sharedApplication] windows] objectAtIndex:0]; + [window addSubview:[presentingController view]]; + [[presentingController view] setFrame:CGRectZero]; + [[presentingController view] setUserInteractionEnabled:NO]; + } + + return presentingController; +} + +- (UITextField *)textFieldInRow:(NSUInteger)row section:(NSUInteger)section +{ + return [[[[[self tableView] cellForRowAtIndexPath: + [NSIndexPath indexPathForRow:(NSInteger)row inSection:(NSInteger)section]] + contentView] subviews] objectAtIndex:0]; +} + +- (UITextField *)usernameField +{ + return [self textFieldInRow:kUsernameRow section:kUsernameSection]; +} + +- (UITextField *)passwordField +{ + return [self textFieldInRow:kPasswordRow section:kPasswordSection]; +} + +- (UITextField *)domainField +{ + return [self textFieldInRow:kDomainRow section:kDomainSection]; +} + +#pragma mark show / dismiss + ++ (void)dismiss +{ + UIViewController* dismisser = nil; + if ([sharedDialog respondsToSelector:@selector(presentingViewController)]){ + dismisser = [sharedDialog presentingViewController]; + }else{ + dismisser = [sharedDialog parentViewController]; + } + if([dismisser respondsToSelector:@selector(dismissViewControllerAnimated:completion:)]){ + [dismisser dismissViewControllerAnimated:YES completion:nil]; + }else{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [dismisser dismissModalViewControllerAnimated:YES]; +#pragma clang diagnostic pop + } +} + +- (void)viewDidDisappear:(BOOL)animated +{ + [super viewDidDisappear:animated]; + + [self retain]; + [sharedDialog release]; + sharedDialog = nil; + [self performSelector:@selector(presentNextDialog) withObject:nil afterDelay:0]; + [self release]; +} + +- (void)dismiss +{ + if (self == sharedDialog) { + [[self class] dismiss]; + } else { + UIViewController* dismisser = nil; + if ([self respondsToSelector:@selector(presentingViewController)]){ + dismisser = [self presentingViewController]; + }else{ + dismisser = [self parentViewController]; + } + if([dismisser respondsToSelector:@selector(dismissViewControllerAnimated:completion:)]){ + [dismisser dismissViewControllerAnimated:YES completion:nil]; + }else{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [dismisser dismissModalViewControllerAnimated:YES]; +#pragma clang diagnostic pop + } + } +} + +- (void)showTitle +{ + UINavigationBar *navigationBar = [[[self view] subviews] objectAtIndex:0]; + UINavigationItem *navItem = [[navigationBar items] objectAtIndex:0]; + if (UIInterfaceOrientationIsPortrait([[UIDevice currentDevice] orientation])) { + // Setup the title + if ([self type] == CTASIProxyAuthenticationType) { + [navItem setPrompt:@"Login to this secure proxy server."]; + } else { + [navItem setPrompt:@"Login to this secure server."]; + } + } else { + [navItem setPrompt:nil]; + } + [navigationBar sizeToFit]; + CGRect f = [[self view] bounds]; + f.origin.y = [navigationBar frame].size.height; + f.size.height -= f.origin.y; + [[self tableView] setFrame:f]; +} + +- (void)show +{ + // Remove all subviews + UIView *v; + while ((v = [[[self view] subviews] lastObject])) { + [v removeFromSuperview]; + } + + // Setup toolbar + UINavigationBar *bar = [[[UINavigationBar alloc] init] autorelease]; + [bar setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; + + UINavigationItem *navItem = [[[UINavigationItem alloc] init] autorelease]; + bar.items = [NSArray arrayWithObject:navItem]; + + [[self view] addSubview:bar]; + + [self showTitle]; + + // Setup toolbar buttons + if ([self type] == CTASIProxyAuthenticationType) { + [navItem setTitle:[[self request] proxyHost]]; + } else { + [navItem setTitle:[[[self request] url] host]]; + } + + [navItem setLeftBarButtonItem:[[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelAuthenticationFromDialog:)] autorelease]]; + [navItem setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:@"Login" style:UIBarButtonItemStyleDone target:self action:@selector(loginWithCredentialsFromDialog:)] autorelease]]; + + // We show the login form in a table view, similar to Safari's authentication dialog + [bar sizeToFit]; + CGRect f = [[self view] bounds]; + f.origin.y = [bar frame].size.height; + f.size.height -= f.origin.y; + + [self setTableView:[[[UITableView alloc] initWithFrame:f style:UITableViewStyleGrouped] autorelease]]; + [[self tableView] setDelegate:self]; + [[self tableView] setDataSource:self]; + [[self tableView] setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; + [[self view] addSubview:[self tableView]]; + + // Force reload the table content, and focus the first field to show the keyboard + [[self tableView] reloadData]; + [[[[[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].contentView subviews] objectAtIndex:0] becomeFirstResponder]; + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self setModalPresentationStyle:UIModalPresentationFormSheet]; + } +#endif + + if([[self presentingController] respondsToSelector:@selector(presentViewController:animated:completion:)]){ + [[self presentingController] presentViewController:self animated:YES completion:nil]; + }else{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [[self presentingController] presentModalViewController:self animated:YES]; +#pragma clang diagnostic pop + } +} + +#pragma mark button callbacks + +- (void)cancelAuthenticationFromDialog:(id)sender +{ + for (CTASIHTTPRequest *theRequest in [self requestsRequiringTheseCredentials]) { + [theRequest cancelAuthentication]; + [requestsNeedingAuthentication removeObject:theRequest]; + } + [self dismiss]; +} + +- (NSArray *)requestsRequiringTheseCredentials +{ + NSMutableArray *requestsRequiringTheseCredentials = [NSMutableArray array]; + NSURL *requestURL = [[self request] url]; + for (CTASIHTTPRequest *otherRequest in requestsNeedingAuthentication) { + NSURL *theURL = [otherRequest url]; + if (([otherRequest authenticationNeeded] == [[self request] authenticationNeeded]) && [[theURL host] isEqualToString:[requestURL host]] && ([theURL port] == [requestURL port] || ([requestURL port] && [[theURL port] isEqualToNumber:[requestURL port]])) && [[theURL scheme] isEqualToString:[requestURL scheme]] && ((![otherRequest authenticationRealm] && ![[self request] authenticationRealm]) || ([otherRequest authenticationRealm] && [[self request] authenticationRealm] && [[[self request] authenticationRealm] isEqualToString:[otherRequest authenticationRealm]]))) { + [requestsRequiringTheseCredentials addObject:otherRequest]; + } + } + [requestsRequiringTheseCredentials addObject:[self request]]; + return requestsRequiringTheseCredentials; +} + +- (void)presentNextDialog +{ + if ([requestsNeedingAuthentication count]) { + CTASIHTTPRequest *nextRequest = [requestsNeedingAuthentication objectAtIndex:0]; + [requestsNeedingAuthentication removeObjectAtIndex:0]; + [[self class] presentAuthenticationDialogForRequest:nextRequest]; + } +} + + +- (void)loginWithCredentialsFromDialog:(id)sender +{ + for (CTASIHTTPRequest *theRequest in [self requestsRequiringTheseCredentials]) { + + NSString *username = [[self usernameField] text]; + NSString *password = [[self passwordField] text]; + + if (username == nil) { username = @""; } + if (password == nil) { password = @""; } + + if ([self type] == CTASIProxyAuthenticationType) { + [theRequest setProxyUsername:username]; + [theRequest setProxyPassword:password]; + } else { + [theRequest setUsername:username]; + [theRequest setPassword:password]; + } + + // Handle NTLM domains + NSString *scheme = ([self type] == CTASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme]; + if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) { + NSString *domain = [[self domainField] text]; + if ([self type] == CTASIProxyAuthenticationType) { + [theRequest setProxyDomain:domain]; + } else { + [theRequest setDomain:domain]; + } + } + + [theRequest retryUsingSuppliedCredentials]; + [requestsNeedingAuthentication removeObject:theRequest]; + } + [self dismiss]; +} + +#pragma mark table view data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView +{ + NSString *scheme = ([self type] == CTASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme]; + if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) { + return 2; + } + return 1; +} + +- (CGFloat)tableView:(UITableView *)aTableView heightForFooterInSection:(NSInteger)section +{ + if (section == [self numberOfSectionsInTableView:aTableView]-1) { + return 30; + } + return 0; +} + +- (CGFloat)tableView:(UITableView *)aTableView heightForHeaderInSection:(NSInteger)section +{ + if (section == 0) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + return 54; + } +#endif + return 30; + } + return 0; +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section +{ + if (section == 0) { + return [[self request] authenticationRealm]; + } + return nil; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_0 + UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease]; +#else + UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0,0,0,0) reuseIdentifier:nil] autorelease]; +#endif + + [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; + + CGRect f = CGRectInset([cell bounds], 10, 10); + UITextField *textField = [[[UITextField alloc] initWithFrame:f] autorelease]; + [textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; + [textField setAutocapitalizationType:UITextAutocapitalizationTypeNone]; + [textField setAutocorrectionType:UITextAutocorrectionTypeNo]; + + NSInteger s = [indexPath section]; + NSInteger r = [indexPath row]; + + if (s == kUsernameSection && r == kUsernameRow) { + [textField setPlaceholder:@"User"]; + } else if (s == kPasswordSection && r == kPasswordRow) { + [textField setPlaceholder:@"Password"]; + [textField setSecureTextEntry:YES]; + } else if (s == kDomainSection && r == kDomainRow) { + [textField setPlaceholder:@"Domain"]; + } + [cell.contentView addSubview:textField]; + + return cell; +} + +- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section +{ + if (section == 0) { + return 2; + } else { + return 1; + } +} + +- (NSString *)tableView:(UITableView *)aTableView titleForFooterInSection:(NSInteger)section +{ + if (section == [self numberOfSectionsInTableView:aTableView]-1) { + // If we're using Basic authentication and the connection is not using SSL, we'll show the plain text message + if ([[[self request] authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic] && ![[[[self request] url] scheme] isEqualToString:@"https"]) { + return @"Password will be sent in the clear."; + // We are using Digest, NTLM, or any scheme over SSL + } else { + return @"Password will be sent securely."; + } + } + return nil; +} + +#pragma mark - + +@synthesize request; +@synthesize type; +@synthesize tableView; +@synthesize didEnableRotationNotifications; +@synthesize presentingController; +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASICacheDelegate.h b/core/Frameworks/asi-http-request/Classes/CTASICacheDelegate.h new file mode 100644 index 00000000..014b5bd6 --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASICacheDelegate.h @@ -0,0 +1,108 @@ +// +// CTASICacheDelegate.h +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 01/05/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +#import +@class CTASIHTTPRequest; + +// Cache policies control the behaviour of a cache and how requests use the cache +// When setting a cache policy, you can use a combination of these values as a bitmask +// For example: [request setCachePolicy:CTASIAskServerIfModifiedCachePolicy|CTASIFallbackToCacheIfLoadFailsCachePolicy|CTASIDoNotWriteToCacheCachePolicy]; +// Note that some of the behaviours below are mutally exclusive - you cannot combine CTASIAskServerIfModifiedWhenStaleCachePolicy and CTASIAskServerIfModifiedCachePolicy, for example. +typedef enum _CTASICachePolicy +{ + + // The default cache policy. When you set a request to use this, it will use the cache's defaultCachePolicy + // CTASIDownloadCache's default cache policy is 'CTASIAskServerIfModifiedWhenStaleCachePolicy' + CTASIUseDefaultCachePolicy = 0, + + // Tell the request not to read from the cache + CTASIDoNotReadFromCacheCachePolicy = 1, + + // The the request not to write to the cache + CTASIDoNotWriteToCacheCachePolicy = 2, + + // Ask the server if there is an updated version of this resource (using a conditional GET) ONLY when the cached data is stale + CTASIAskServerIfModifiedWhenStaleCachePolicy = 4, + + // Always ask the server if there is an updated version of this resource (using a conditional GET) + CTASIAskServerIfModifiedCachePolicy = 8, + + // If cached data exists, use it even if it is stale. This means requests will not talk to the server unless the resource they are requesting is not in the cache + CTASIOnlyLoadIfNotCachedCachePolicy = 16, + + // If cached data exists, use it even if it is stale. If cached data does not exist, stop (will not set an error on the request) + CTASIDontLoadCachePolicy = 32, + + // Specifies that cached data may be used if the request fails. If cached data is used, the request will succeed without error. Usually used in combination with other options above. + CTASIFallbackToCacheIfLoadFailsCachePolicy = 64 +} CTASICachePolicy; + +// Cache storage policies control whether cached data persists between application launches (CTASICachePermanentlyCacheStoragePolicy) or not (CTASICacheForSessionDurationCacheStoragePolicy) +// Calling [CTASIHTTPRequest clearSession] will remove any data stored using CTASICacheForSessionDurationCacheStoragePolicy +typedef enum _CTASICacheStoragePolicy +{ + CTASICacheForSessionDurationCacheStoragePolicy = 0, + CTASICachePermanentlyCacheStoragePolicy = 1 +} CTASICacheStoragePolicy; + + +@protocol CTASICacheDelegate + +@required + +// Should return the cache policy that will be used when requests have their cache policy set to CTASIUseDefaultCachePolicy +- (CTASICachePolicy)defaultCachePolicy; + +// Returns the date a cached response should expire on. Pass a non-zero max age to specify a custom date. +- (NSDate *)expiryDateForRequest:(CTASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge; + +// Updates cached response headers with a new expiry date. Pass a non-zero max age to specify a custom date. +- (void)updateExpiryForRequest:(CTASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge; + +// Looks at the request's cache policy and any cached headers to determine if the cache data is still valid +- (BOOL)canUseCachedDataForRequest:(CTASIHTTPRequest *)request; + +// Removes cached data for a particular request +- (void)removeCachedDataForRequest:(CTASIHTTPRequest *)request; + +// Should return YES if the cache considers its cached response current for the request +// Should return NO is the data is not cached, or (for example) if the cached headers state the request should have expired +- (BOOL)isCachedDataCurrentForRequest:(CTASIHTTPRequest *)request; + +// Should store the response for the passed request in the cache +// When a non-zero maxAge is passed, it should be used as the expiry time for the cached response +- (void)storeResponseForRequest:(CTASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge; + +// Removes cached data for a particular url +- (void)removeCachedDataForURL:(NSURL *)url; + +// Should return an NSDictionary of cached headers for the passed URL, if it is stored in the cache +- (NSDictionary *)cachedResponseHeadersForURL:(NSURL *)url; + +// Should return the cached body of a response for the passed URL, if it is stored in the cache +- (NSData *)cachedResponseDataForURL:(NSURL *)url; + +// Returns a path to the cached response data, if it exists +- (NSString *)pathToCachedResponseDataForURL:(NSURL *)url; + +// Returns a path to the cached response headers, if they url +- (NSString *)pathToCachedResponseHeadersForURL:(NSURL *)url; + +// Returns the location to use to store cached response headers for a particular request +- (NSString *)pathToStoreCachedResponseHeadersForRequest:(CTASIHTTPRequest *)request; + +// Returns the location to use to store a cached response body for a particular request +- (NSString *)pathToStoreCachedResponseDataForRequest:(CTASIHTTPRequest *)request; + +// Clear cached data stored for the passed storage policy +- (void)clearCachedResponsesForStoragePolicy:(CTASICacheStoragePolicy)cachePolicy; + +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASIDataCompressor.h b/core/Frameworks/asi-http-request/Classes/CTASIDataCompressor.h new file mode 100644 index 00000000..72e7495b --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASIDataCompressor.h @@ -0,0 +1,45 @@ +// +// ASIDataCompressor.h +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 17/08/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +// This is a helper class used by CTASIHTTPRequest to handle deflating (compressing) data in memory and on disk +// You may also find it helpful if you need to deflate data and files yourself - see the class methods below +// Most of the zlib stuff is based on the sample code by Mark Adler available at http://zlib.net + +#import +#import + +@interface CTASIDataCompressor : NSObject { + BOOL streamReady; + z_stream zStream; +} + +// Convenience constructor will call setupStream for you ++ (id)compressor; + +// Compress the passed chunk of data +// Passing YES for shouldFinish will finalize the deflated data - you must pass YES when you are on the last chunk of data +- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish; + +// Convenience method - pass it some data, and you'll get deflated data back ++ (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err; + +// Convenience method - pass it a file containing the data to compress in sourcePath, and it will write deflated data to destinationPath ++ (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err; + +// Sets up zlib to handle the inflating. You only need to call this yourself if you aren't using the convenience constructor 'compressor' +- (NSError *)setupStream; + +// Tells zlib to clean up. You need to call this if you need to cancel deflating part way through +// If deflating finishes or fails, this method will be called automatically +- (NSError *)closeStream; + +@property (atomic, assign, readonly) BOOL streamReady; +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASIDataCompressor.m b/core/Frameworks/asi-http-request/Classes/CTASIDataCompressor.m new file mode 100644 index 00000000..4b593997 --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASIDataCompressor.m @@ -0,0 +1,222 @@ +// +// ASIDataCompressor.m +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 17/08/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +#import "CTASIDataCompressor.h" +#import "CTASIHTTPRequest.h" + +#define DATA_CHUNK_SIZE 262144 // Deal with gzipped data in 256KB chunks +#define COMPRESSION_AMOUNT Z_DEFAULT_COMPRESSION + +@interface CTASIDataCompressor () ++ (NSError *)deflateErrorWithCode:(int)code; +@end + +@implementation CTASIDataCompressor + ++ (id)compressor +{ + CTASIDataCompressor *compressor = [[[self alloc] init] autorelease]; + [compressor setupStream]; + return compressor; +} + +- (void)dealloc +{ + if (streamReady) { + [self closeStream]; + } + [super dealloc]; +} + +- (NSError *)setupStream +{ + if (streamReady) { + return nil; + } + // Setup the inflate stream + zStream.zalloc = Z_NULL; + zStream.zfree = Z_NULL; + zStream.opaque = Z_NULL; + zStream.avail_in = 0; + zStream.next_in = 0; + int status = deflateInit2(&zStream, COMPRESSION_AMOUNT, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY); + if (status != Z_OK) { + return [[self class] deflateErrorWithCode:status]; + } + streamReady = YES; + return nil; +} + +- (NSError *)closeStream +{ + if (!streamReady) { + return nil; + } + // Close the deflate stream + streamReady = NO; + int status = deflateEnd(&zStream); + if (status != Z_OK) { + return [[self class] deflateErrorWithCode:status]; + } + return nil; +} + +- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish +{ + if (length == 0) return nil; + + NSUInteger halfLength = length/2; + + // We'll take a guess that the compressed data will fit in half the size of the original (ie the max to compress at once is half DATA_CHUNK_SIZE), if not, we'll increase it below + NSMutableData *outputData = [NSMutableData dataWithLength:length/2]; + + int status; + + zStream.next_in = bytes; + zStream.avail_in = (unsigned int)length; + zStream.avail_out = 0; + + NSUInteger bytesProcessedAlready = zStream.total_out; + while (zStream.avail_out == 0) { + + if (zStream.total_out-bytesProcessedAlready >= [outputData length]) { + [outputData increaseLengthBy:halfLength]; + } + + zStream.next_out = (Bytef*)[outputData mutableBytes] + zStream.total_out-bytesProcessedAlready; + zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready)); + status = deflate(&zStream, shouldFinish ? Z_FINISH : Z_NO_FLUSH); + + if (status == Z_STREAM_END) { + break; + } else if (status != Z_OK) { + if (err) { + *err = [[self class] deflateErrorWithCode:status]; + } + return NO; + } + } + + // Set real length + [outputData setLength: zStream.total_out-bytesProcessedAlready]; + return outputData; +} + + ++ (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err +{ + NSError *theError = nil; + NSData *outputData = [[CTASIDataCompressor compressor] compressBytes:(Bytef *)[uncompressedData bytes] length:[uncompressedData length] error:&theError shouldFinish:YES]; + if (theError) { + if (err) { + *err = theError; + } + return nil; + } + return outputData; +} + + + ++ (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err +{ + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + // Create an empty file at the destination path + if (![fileManager createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) { + if (err) { + *err = [NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were to create a file at %@", sourcePath, destinationPath], NSLocalizedDescriptionKey, nil]]; + } + return NO; + } + + // Ensure the source file exists + if (![fileManager fileExistsAtPath:sourcePath]) { + if (err) { + *err = [NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed the file does not exist", sourcePath], NSLocalizedDescriptionKey, nil]]; + } + return NO; + } + + UInt8 inputData[DATA_CHUNK_SIZE]; + NSData *outputData; + NSInteger readLength; + NSError *theError = nil; + + CTASIDataCompressor *compressor = [CTASIDataCompressor compressor]; + + NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath]; + [inputStream open]; + NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO]; + [outputStream open]; + + while ([compressor streamReady]) { + + // Read some data from the file + readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE]; + + // Make sure nothing went wrong + if ([inputStream streamStatus] == NSStreamStatusError) { + if (err) { + *err = [NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to read from the source data file", sourcePath], NSLocalizedDescriptionKey, [inputStream streamError], NSUnderlyingErrorKey, nil]]; + } + [compressor closeStream]; + return NO; + } + // Have we reached the end of the input data? + if (!readLength) { + break; + } + + // Attempt to deflate the chunk of data + outputData = [compressor compressBytes:inputData length:(NSUInteger)readLength error:&theError shouldFinish:readLength < DATA_CHUNK_SIZE]; + if (theError) { + if (err) { + *err = theError; + } + [compressor closeStream]; + return NO; + } + + // Write the deflated data out to the destination file + [outputStream write:(const uint8_t *)[outputData bytes] maxLength:[outputData length]]; + + // Make sure nothing went wrong + if ([inputStream streamStatus] == NSStreamStatusError) { + if (err) { + *err = [NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to write to the destination data file at %@", sourcePath, destinationPath], NSLocalizedDescriptionKey, [outputStream streamError], NSUnderlyingErrorKey, nil]]; + } + [compressor closeStream]; + return NO; + } + + } + [inputStream close]; + [outputStream close]; + + NSError *error = [compressor closeStream]; + if (error) { + if (err) { + *err = error; + } + return NO; + } + + return YES; +} + ++ (NSError *)deflateErrorWithCode:(int)code +{ + return [NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of data failed with code %d", code], NSLocalizedDescriptionKey, nil]]; +} + +@synthesize streamReady; +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASIDataDecompressor.h b/core/Frameworks/asi-http-request/Classes/CTASIDataDecompressor.h new file mode 100644 index 00000000..0fb7ca3c --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASIDataDecompressor.h @@ -0,0 +1,44 @@ +// +// CTASIDataDecompressor.h +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 17/08/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +// This is a helper class used by CTASIHTTPRequest to handle inflating (decompressing) data in memory and on disk +// You may also find it helpful if you need to inflate data and files yourself - see the class methods below +// Most of the zlib stuff is based on the sample code by Mark Adler available at http://zlib.net + +#import +#import + +@interface CTASIDataDecompressor : NSObject { + BOOL streamReady; + z_stream zStream; +} + +// Convenience constructor will call setupStream for you ++ (id)decompressor; + +// Uncompress the passed chunk of data +- (NSData *)uncompressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err; + +// Convenience method - pass it some deflated data, and you'll get inflated data back ++ (NSData *)uncompressData:(NSData*)compressedData error:(NSError **)err; + +// Convenience method - pass it a file containing deflated data in sourcePath, and it will write inflated data to destinationPath ++ (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err; + +// Sets up zlib to handle the inflating. You only need to call this yourself if you aren't using the convenience constructor 'decompressor' +- (NSError *)setupStream; + +// Tells zlib to clean up. You need to call this if you need to cancel inflating part way through +// If inflating finishes or fails, this method will be called automatically +- (NSError *)closeStream; + +@property (atomic, assign, readonly) BOOL streamReady; +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASIDataDecompressor.m b/core/Frameworks/asi-http-request/Classes/CTASIDataDecompressor.m new file mode 100644 index 00000000..6082df25 --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASIDataDecompressor.m @@ -0,0 +1,221 @@ +// +// CTASIDataDecompressor.m +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 17/08/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +#import "CTASIDataDecompressor.h" +#import "CTASIHTTPRequest.h" + +#define DATA_CHUNK_SIZE 262144 // Deal with gzipped data in 256KB chunks + +@interface CTASIDataDecompressor () ++ (NSError *)inflateErrorWithCode:(int)code; +@end; + +@implementation CTASIDataDecompressor + ++ (id)decompressor +{ + CTASIDataDecompressor *decompressor = [[[self alloc] init] autorelease]; + [decompressor setupStream]; + return decompressor; +} + +- (void)dealloc +{ + if (streamReady) { + [self closeStream]; + } + [super dealloc]; +} + +- (NSError *)setupStream +{ + if (streamReady) { + return nil; + } + // Setup the inflate stream + zStream.zalloc = Z_NULL; + zStream.zfree = Z_NULL; + zStream.opaque = Z_NULL; + zStream.avail_in = 0; + zStream.next_in = 0; + int status = inflateInit2(&zStream, (15+32)); + if (status != Z_OK) { + return [[self class] inflateErrorWithCode:status]; + } + streamReady = YES; + return nil; +} + +- (NSError *)closeStream +{ + if (!streamReady) { + return nil; + } + // Close the inflate stream + streamReady = NO; + int status = inflateEnd(&zStream); + if (status != Z_OK) { + return [[self class] inflateErrorWithCode:status]; + } + return nil; +} + +- (NSData *)uncompressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err +{ + if (length == 0) return nil; + + NSUInteger halfLength = length/2; + NSMutableData *outputData = [NSMutableData dataWithLength:length+halfLength]; + + int status; + + zStream.next_in = bytes; + zStream.avail_in = (unsigned int)length; + zStream.avail_out = 0; + + NSUInteger bytesProcessedAlready = zStream.total_out; + while (zStream.avail_in != 0) { + + if (zStream.total_out-bytesProcessedAlready >= [outputData length]) { + [outputData increaseLengthBy:halfLength]; + } + + zStream.next_out = (Bytef*)[outputData mutableBytes] + zStream.total_out-bytesProcessedAlready; + zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready)); + + status = inflate(&zStream, Z_NO_FLUSH); + + if (status == Z_STREAM_END) { + break; + } else if (status != Z_OK) { + if (err) { + *err = [[self class] inflateErrorWithCode:status]; + } + return nil; + } + } + + // Set real length + [outputData setLength: zStream.total_out-bytesProcessedAlready]; + return outputData; +} + + ++ (NSData *)uncompressData:(NSData*)compressedData error:(NSError **)err +{ + NSError *theError = nil; + NSData *outputData = [[CTASIDataDecompressor decompressor] uncompressBytes:(Bytef *)[compressedData bytes] length:[compressedData length] error:&theError]; + if (theError) { + if (err) { + *err = theError; + } + return nil; + } + return outputData; +} + ++ (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err +{ + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + // Create an empty file at the destination path + if (![fileManager createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) { + if (err) { + *err = [NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were to create a file at %@", sourcePath, destinationPath], NSLocalizedDescriptionKey, nil]]; + } + return NO; + } + + // Ensure the source file exists + if (![fileManager fileExistsAtPath:sourcePath]) { + if (err) { + *err = [NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed the file does not exist", sourcePath], NSLocalizedDescriptionKey, nil]]; + } + return NO; + } + + UInt8 inputData[DATA_CHUNK_SIZE]; + NSData *outputData; + NSInteger readLength; + NSError *theError = nil; + + + CTASIDataDecompressor *decompressor = [CTASIDataDecompressor decompressor]; + + NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath]; + [inputStream open]; + NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO]; + [outputStream open]; + + while ([decompressor streamReady]) { + + // Read some data from the file + readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE]; + + // Make sure nothing went wrong + if ([inputStream streamStatus] == NSStreamStatusError) { + if (err) { + *err = [NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to read from the source data file", sourcePath], NSLocalizedDescriptionKey, [inputStream streamError], NSUnderlyingErrorKey, nil]]; + } + [decompressor closeStream]; + return NO; + } + // Have we reached the end of the input data? + if (!readLength) { + break; + } + + // Attempt to inflate the chunk of data + outputData = [decompressor uncompressBytes:inputData length:(NSUInteger)readLength error:&theError]; + if (theError) { + if (err) { + *err = theError; + } + [decompressor closeStream]; + return NO; + } + + // Write the inflated data out to the destination file + [outputStream write:(Bytef*)[outputData bytes] maxLength:[outputData length]]; + + // Make sure nothing went wrong + if ([inputStream streamStatus] == NSStreamStatusError) { + if (err) { + *err = [NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to write to the destination data file at %@", sourcePath, destinationPath], NSLocalizedDescriptionKey, [outputStream streamError], NSUnderlyingErrorKey, nil]]; + } + [decompressor closeStream]; + return NO; + } + + } + + [inputStream close]; + [outputStream close]; + + NSError *error = [decompressor closeStream]; + if (error) { + if (err) { + *err = error; + } + return NO; + } + + return YES; +} + + ++ (NSError *)inflateErrorWithCode:(int)code +{ + return [NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of data failed with code %d", code], NSLocalizedDescriptionKey, nil]]; +} + +@synthesize streamReady; +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASIDownloadCache.h b/core/Frameworks/asi-http-request/Classes/CTASIDownloadCache.h new file mode 100644 index 00000000..a6cc42c2 --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASIDownloadCache.h @@ -0,0 +1,49 @@ +// +// CTASIDownloadCache.h +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 01/05/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +#import +#import "CTASICacheDelegate.h" + +@interface CTASIDownloadCache : NSObject { + + // The default cache policy for this cache + // Requests that store data in the cache will use this cache policy if their cache policy is set to CTASIUseDefaultCachePolicy + // Defaults to CTASIAskServerIfModifiedWhenStaleCachePolicy + CTASICachePolicy defaultCachePolicy; + + // The directory in which cached data will be stored + // Defaults to a directory called 'ASIHTTPRequestCache' in the temporary directory + NSString *storagePath; + + // Mediates access to the cache + NSRecursiveLock *accessLock; + + // When YES, the cache will look for cache-control / pragma: no-cache headers, and won't reuse store responses if it finds them + BOOL shouldRespectCacheControlHeaders; +} + +// Returns a static instance of an CTASIDownloadCache +// In most circumstances, it will make sense to use this as a global cache, rather than creating your own cache +// To make ASIHTTPRequests use it automatically, use [CTASIHTTPRequest setDefaultCache:[CTASIDownloadCache sharedCache]]; ++ (id)sharedCache; + +// A helper function that determines if the server has requested data should not be cached by looking at the request's response headers ++ (BOOL)serverAllowsResponseCachingForRequest:(CTASIHTTPRequest *)request; + +// A list of file extensions that we know won't be readable by a webview when accessed locally +// If we're asking for a path to cache a particular url and it has one of these extensions, we change it to '.html' ++ (NSArray *)fileExtensionsToHandleAsHTML; + +@property (assign, nonatomic) CTASICachePolicy defaultCachePolicy; +@property (retain, nonatomic) NSString *storagePath; +@property (atomic, retain) NSRecursiveLock *accessLock; +@property (atomic, assign) BOOL shouldRespectCacheControlHeaders; +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASIDownloadCache.m b/core/Frameworks/asi-http-request/Classes/CTASIDownloadCache.m new file mode 100644 index 00000000..bc50b540 --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASIDownloadCache.m @@ -0,0 +1,517 @@ +// +// CTASIDownloadCache.m +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 01/05/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +#import "CTASIDownloadCache.h" +#import "CTASIHTTPRequest.h" +#import + +static CTASIDownloadCache *sharedCache = nil; + +static NSString *sessionCacheFolder = @"SessionStore"; +static NSString *permanentCacheFolder = @"PermanentStore"; +static NSArray *fileExtensionsToHandleAsHTML = nil; + +@interface CTASIDownloadCache () ++ (NSString *)keyForURL:(NSURL *)url; +- (NSString *)pathToFile:(NSString *)file; +@end + +@implementation CTASIDownloadCache + ++ (void)initialize +{ + if (self == [CTASIDownloadCache class]) { + // Obviously this is not an exhaustive list, but hopefully these are the most commonly used and this will 'just work' for the widest range of people + // I imagine many web developers probably use url rewriting anyway + fileExtensionsToHandleAsHTML = [[NSArray alloc] initWithObjects:@"asp",@"aspx",@"jsp",@"php",@"rb",@"py",@"pl",@"cgi", nil]; + } +} + +- (id)init +{ + self = [super init]; + [self setShouldRespectCacheControlHeaders:YES]; + [self setDefaultCachePolicy:CTASIUseDefaultCachePolicy]; + [self setAccessLock:[[[NSRecursiveLock alloc] init] autorelease]]; + return self; +} + ++ (id)sharedCache +{ + if (!sharedCache) { + @synchronized(self) { + if (!sharedCache) { + sharedCache = [[self alloc] init]; + [sharedCache setStoragePath:[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"ASIHTTPRequestCache"]]; + } + } + } + return sharedCache; +} + +- (void)dealloc +{ + [storagePath release]; + [accessLock release]; + [super dealloc]; +} + +- (NSString *)storagePath +{ + [[self accessLock] lock]; + NSString *p = [[storagePath retain] autorelease]; + [[self accessLock] unlock]; + return p; +} + + +- (void)setStoragePath:(NSString *)path +{ + [[self accessLock] lock]; + [self clearCachedResponsesForStoragePolicy:CTASICacheForSessionDurationCacheStoragePolicy]; + [storagePath release]; + storagePath = [path retain]; + + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + BOOL isDirectory = NO; + NSArray *directories = [NSArray arrayWithObjects:path,[path stringByAppendingPathComponent:sessionCacheFolder],[path stringByAppendingPathComponent:permanentCacheFolder],nil]; + for (NSString *directory in directories) { + BOOL exists = [fileManager fileExistsAtPath:directory isDirectory:&isDirectory]; + if (exists && !isDirectory) { + [[self accessLock] unlock]; + [NSException raise:@"FileExistsAtCachePath" format:@"Cannot create a directory for the cache at '%@', because a file already exists",directory]; + } else if (!exists) { + [fileManager createDirectoryAtPath:directory withIntermediateDirectories:NO attributes:nil error:nil]; + if (![fileManager fileExistsAtPath:directory]) { + [[self accessLock] unlock]; + [NSException raise:@"FailedToCreateCacheDirectory" format:@"Failed to create a directory for the cache at '%@'",directory]; + } + } + } + [self clearCachedResponsesForStoragePolicy:CTASICacheForSessionDurationCacheStoragePolicy]; + [[self accessLock] unlock]; +} + +- (void)updateExpiryForRequest:(CTASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge +{ + NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request]; + NSMutableDictionary *cachedHeaders = [NSMutableDictionary dictionaryWithContentsOfFile:headerPath]; + if (!cachedHeaders) { + return; + } + NSDate *expires = [self expiryDateForRequest:request maxAge:maxAge]; + if (!expires) { + return; + } + [cachedHeaders setObject:[NSNumber numberWithDouble:[expires timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"]; + [cachedHeaders writeToFile:headerPath atomically:NO]; +} + +- (NSDate *)expiryDateForRequest:(CTASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge +{ + return [CTASIHTTPRequest expiryDateForRequest:request maxAge:maxAge]; +} + +- (void)storeResponseForRequest:(CTASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge +{ + [[self accessLock] lock]; + + if ([request error] || ![request responseHeaders] || ([request cachePolicy] & CTASIDoNotWriteToCacheCachePolicy)) { + [[self accessLock] unlock]; + return; + } + + // We only cache 200/OK or redirect reponses (redirect responses are cached so the cache works better with no internet connection) + int responseCode = [request responseStatusCode]; + if (responseCode != 200 && responseCode != 301 && responseCode != 302 && responseCode != 303 && responseCode != 307) { + [[self accessLock] unlock]; + return; + } + + if ([self shouldRespectCacheControlHeaders] && ![[self class] serverAllowsResponseCachingForRequest:request]) { + [[self accessLock] unlock]; + return; + } + + NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request]; + NSString *dataPath = [self pathToStoreCachedResponseDataForRequest:request]; + + NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionaryWithDictionary:[request responseHeaders]]; + if ([request isResponseCompressed]) { + [responseHeaders removeObjectForKey:@"Content-Encoding"]; + } + + // Create a special 'X-CTASIHTTPRequest-Expires' header + // This is what we use for deciding if cached data is current, rather than parsing the expires / max-age headers individually each time + // We store this as a timestamp to make reading it easier as NSDateFormatter is quite expensive + + NSDate *expires = [self expiryDateForRequest:request maxAge:maxAge]; + if (expires) { + [responseHeaders setObject:[NSNumber numberWithDouble:[expires timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"]; + } + + // Store the response code in a custom header so we can reuse it later + + // We'll change 304/Not Modified to 200/OK because this is likely to be us updating the cached headers with a conditional GET + int statusCode = [request responseStatusCode]; + if (statusCode == 304) { + statusCode = 200; + } + [responseHeaders setObject:[NSNumber numberWithInt:statusCode] forKey:@"X-ASIHTTPRequest-Response-Status-Code"]; + + [responseHeaders writeToFile:headerPath atomically:NO]; + + if ([request responseData]) { + [[request responseData] writeToFile:dataPath atomically:NO]; + } else if ([request downloadDestinationPath] && ![[request downloadDestinationPath] isEqualToString:dataPath]) { + NSError *error = nil; + NSFileManager* manager = [[NSFileManager alloc] init]; + if ([manager fileExistsAtPath:dataPath]) { + [manager removeItemAtPath:dataPath error:&error]; + } + [manager copyItemAtPath:[request downloadDestinationPath] toPath:dataPath error:&error]; + [manager release]; + } + [[self accessLock] unlock]; +} + +- (NSDictionary *)cachedResponseHeadersForURL:(NSURL *)url +{ + NSString *path = [self pathToCachedResponseHeadersForURL:url]; + if (path) { + return [NSDictionary dictionaryWithContentsOfFile:path]; + } + return nil; +} + +- (NSData *)cachedResponseDataForURL:(NSURL *)url +{ + NSString *path = [self pathToCachedResponseDataForURL:url]; + if (path) { + return [NSData dataWithContentsOfFile:path]; + } + return nil; +} + +- (NSString *)pathToCachedResponseDataForURL:(NSURL *)url +{ + // Grab the file extension, if there is one. We do this so we can save the cached response with the same file extension - this is important if you want to display locally cached data in a web view + NSString *extension = [[url path] pathExtension]; + + // If the url doesn't have an extension, we'll add one so a webview can read it when locally cached + // If the url has the extension of a common web scripting language, we'll change the extension on the cached path to html for the same reason + if (![extension length] || [[[self class] fileExtensionsToHandleAsHTML] containsObject:[extension lowercaseString]]) { + extension = @"html"; + } + return [self pathToFile:[[[self class] keyForURL:url] stringByAppendingPathExtension:extension]]; +} + ++ (NSArray *)fileExtensionsToHandleAsHTML +{ + return fileExtensionsToHandleAsHTML; +} + + +- (NSString *)pathToCachedResponseHeadersForURL:(NSURL *)url +{ + return [self pathToFile:[[[self class] keyForURL:url] stringByAppendingPathExtension:@"cachedheaders"]]; +} + +- (NSString *)pathToFile:(NSString *)file +{ + [[self accessLock] lock]; + if (![self storagePath]) { + [[self accessLock] unlock]; + return nil; + } + + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + // Look in the session store + NSString *dataPath = [[[self storagePath] stringByAppendingPathComponent:sessionCacheFolder] stringByAppendingPathComponent:file]; + if ([fileManager fileExistsAtPath:dataPath]) { + [[self accessLock] unlock]; + return dataPath; + } + // Look in the permanent store + dataPath = [[[self storagePath] stringByAppendingPathComponent:permanentCacheFolder] stringByAppendingPathComponent:file]; + if ([fileManager fileExistsAtPath:dataPath]) { + [[self accessLock] unlock]; + return dataPath; + } + [[self accessLock] unlock]; + return nil; +} + + +- (NSString *)pathToStoreCachedResponseDataForRequest:(CTASIHTTPRequest *)request +{ + [[self accessLock] lock]; + if (![self storagePath]) { + [[self accessLock] unlock]; + return nil; + } + + NSString *path = [[self storagePath] stringByAppendingPathComponent:([request cacheStoragePolicy] == CTASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)]; + + // Grab the file extension, if there is one. We do this so we can save the cached response with the same file extension - this is important if you want to display locally cached data in a web view + NSString *extension = [[[request url] path] pathExtension]; + + // If the url doesn't have an extension, we'll add one so a webview can read it when locally cached + // If the url has the extension of a common web scripting language, we'll change the extension on the cached path to html for the same reason + if (![extension length] || [[[self class] fileExtensionsToHandleAsHTML] containsObject:[extension lowercaseString]]) { + extension = @"html"; + } + path = [path stringByAppendingPathComponent:[[[self class] keyForURL:[request url]] stringByAppendingPathExtension:extension]]; + [[self accessLock] unlock]; + return path; +} + +- (NSString *)pathToStoreCachedResponseHeadersForRequest:(CTASIHTTPRequest *)request +{ + [[self accessLock] lock]; + if (![self storagePath]) { + [[self accessLock] unlock]; + return nil; + } + NSString *path = [[self storagePath] stringByAppendingPathComponent:([request cacheStoragePolicy] == CTASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)]; + path = [path stringByAppendingPathComponent:[[[self class] keyForURL:[request url]] stringByAppendingPathExtension:@"cachedheaders"]]; + [[self accessLock] unlock]; + return path; +} + +- (void)removeCachedDataForURL:(NSURL *)url +{ + [[self accessLock] lock]; + if (![self storagePath]) { + [[self accessLock] unlock]; + return; + } + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + NSString *path = [self pathToCachedResponseHeadersForURL:url]; + if (path) { + [fileManager removeItemAtPath:path error:NULL]; + } + + path = [self pathToCachedResponseDataForURL:url]; + if (path) { + [fileManager removeItemAtPath:path error:NULL]; + } + [[self accessLock] unlock]; +} + +- (void)removeCachedDataForRequest:(CTASIHTTPRequest *)request +{ + [self removeCachedDataForURL:[request url]]; +} + +- (BOOL)isCachedDataCurrentForRequest:(CTASIHTTPRequest *)request +{ + [[self accessLock] lock]; + if (![self storagePath]) { + [[self accessLock] unlock]; + return NO; + } + NSDictionary *cachedHeaders = [self cachedResponseHeadersForURL:[request url]]; + if (!cachedHeaders) { + [[self accessLock] unlock]; + return NO; + } + NSString *dataPath = [self pathToCachedResponseDataForURL:[request url]]; + if (!dataPath) { + [[self accessLock] unlock]; + return NO; + } + + // New content is not different + if ([request responseStatusCode] == 304) { + [[self accessLock] unlock]; + return YES; + } + + // If we already have response headers for this request, check to see if the new content is different + // We check [request complete] so that we don't end up comparing response headers from a redirection with these + if ([request responseHeaders] && [request complete]) { + + // If the Etag or Last-Modified date are different from the one we have, we'll have to fetch this resource again + NSArray *headersToCompare = [NSArray arrayWithObjects:@"Etag",@"Last-Modified",nil]; + for (NSString *header in headersToCompare) { + if (![[[request responseHeaders] objectForKey:header] isEqualToString:[cachedHeaders objectForKey:header]]) { + [[self accessLock] unlock]; + return NO; + } + } + } + + if ([self shouldRespectCacheControlHeaders]) { + + // Look for X-CTASIHTTPRequest-Expires header to see if the content is out of date + NSNumber *expires = [cachedHeaders objectForKey:@"X-ASIHTTPRequest-Expires"]; + if (expires) { + if ([[NSDate dateWithTimeIntervalSince1970:[expires doubleValue]] timeIntervalSinceNow] >= 0) { + [[self accessLock] unlock]; + return YES; + } + } + + // No explicit expiration time sent by the server + [[self accessLock] unlock]; + return NO; + } + + + [[self accessLock] unlock]; + return YES; +} + +- (CTASICachePolicy)defaultCachePolicy +{ + [[self accessLock] lock]; + CTASICachePolicy cp = defaultCachePolicy; + [[self accessLock] unlock]; + return cp; +} + + +- (void)setDefaultCachePolicy:(CTASICachePolicy)cachePolicy +{ + [[self accessLock] lock]; + if (!cachePolicy) { + defaultCachePolicy = CTASIAskServerIfModifiedWhenStaleCachePolicy; + } else { + defaultCachePolicy = cachePolicy; + } + [[self accessLock] unlock]; +} + +- (void)clearCachedResponsesForStoragePolicy:(CTASICacheStoragePolicy)storagePolicy +{ + [[self accessLock] lock]; + if (![self storagePath]) { + [[self accessLock] unlock]; + return; + } + NSString *path = [[self storagePath] stringByAppendingPathComponent:(storagePolicy == CTASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)]; + + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + BOOL isDirectory = NO; + BOOL exists = [fileManager fileExistsAtPath:path isDirectory:&isDirectory]; + if (!exists || !isDirectory) { + [[self accessLock] unlock]; + return; + } + NSError *error = nil; + NSArray *cacheFiles = [fileManager contentsOfDirectoryAtPath:path error:&error]; + if (error) { + [[self accessLock] unlock]; + [NSException raise:@"FailedToTraverseCacheDirectory" format:@"Listing cache directory failed at path '%@'",path]; + } + for (NSString *file in cacheFiles) { + [fileManager removeItemAtPath:[path stringByAppendingPathComponent:file] error:&error]; + if (error) { + [[self accessLock] unlock]; + [NSException raise:@"FailedToRemoveCacheFile" format:@"Failed to remove cached data at path '%@'",path]; + } + } + [[self accessLock] unlock]; +} + ++ (BOOL)serverAllowsResponseCachingForRequest:(CTASIHTTPRequest *)request +{ + NSString *cacheControl = [[[request responseHeaders] objectForKey:@"Cache-Control"] lowercaseString]; + if (cacheControl) { + if ([cacheControl isEqualToString:@"no-cache"] || [cacheControl isEqualToString:@"no-store"]) { + return NO; + } + } + NSString *pragma = [[[request responseHeaders] objectForKey:@"Pragma"] lowercaseString]; + if (pragma) { + if ([pragma isEqualToString:@"no-cache"]) { + return NO; + } + } + return YES; +} + ++ (NSString *)keyForURL:(NSURL *)url +{ + NSString *urlString = [url absoluteString]; + if ([urlString length] == 0) { + return nil; + } + + // Strip trailing slashes so http://allseeing-i.com/CTASIHTTPRequest/ is cached the same as http://allseeing-i.com/CTASIHTTPRequest + if ([[urlString substringFromIndex:[urlString length]-1] isEqualToString:@"/"]) { + urlString = [urlString substringToIndex:[urlString length]-1]; + } + + // Borrowed from: http://stackoverflow.com/questions/652300/using-md5-hash-on-a-string-in-cocoa + const char *cStr = [urlString UTF8String]; + unsigned char result[16]; + CC_MD5(cStr, (CC_LONG)strlen(cStr), result); + return [NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7],result[8], result[9], result[10], result[11],result[12], result[13], result[14], result[15]]; +} + +- (BOOL)canUseCachedDataForRequest:(CTASIHTTPRequest *)request +{ + // Ensure the request is allowed to read from the cache + if ([request cachePolicy] & CTASIDoNotReadFromCacheCachePolicy) { + return NO; + + // If we don't want to load the request whatever happens, always pretend we have cached data even if we don't + } else if ([request cachePolicy] & CTASIDontLoadCachePolicy) { + return YES; + } + + NSDictionary *headers = [self cachedResponseHeadersForURL:[request url]]; + if (!headers) { + return NO; + } + NSString *dataPath = [self pathToCachedResponseDataForURL:[request url]]; + if (!dataPath) { + return NO; + } + + // If we get here, we have cached data + + // If we have cached data, we can use it + if ([request cachePolicy] & CTASIOnlyLoadIfNotCachedCachePolicy) { + return YES; + + // If we want to fallback to the cache after an error + } else if ([request complete] && [request cachePolicy] & CTASIFallbackToCacheIfLoadFailsCachePolicy) { + return YES; + + // If we have cached data that is current, we can use it + } else if ([request cachePolicy] & CTASIAskServerIfModifiedWhenStaleCachePolicy) { + if ([self isCachedDataCurrentForRequest:request]) { + return YES; + } + + // If we've got headers from a conditional GET and the cached data is still current, we can use it + } else if ([request cachePolicy] & CTASIAskServerIfModifiedCachePolicy) { + if (![request responseHeaders]) { + return NO; + } else if ([self isCachedDataCurrentForRequest:request]) { + return YES; + } + } + return NO; +} + +@synthesize storagePath; +@synthesize defaultCachePolicy; +@synthesize accessLock; +@synthesize shouldRespectCacheControlHeaders; +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASIFormDataRequest.h b/core/Frameworks/asi-http-request/Classes/CTASIFormDataRequest.h new file mode 100644 index 00000000..db1b6785 --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASIFormDataRequest.h @@ -0,0 +1,79 @@ +// +// CTASIFormDataRequest.h +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 07/11/2008. +// Copyright 2008-2009 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +#import +#import "CTASIHTTPRequest.h" +#import "CTASIHTTPRequestConfig.h" + +typedef enum _CTASIPostFormat +{ + CTASIMultipartFormDataPostFormat = 0, + CTASIURLEncodedPostFormat = 1 + +} CTASIPostFormat; + +@interface CTASIFormDataRequest : CTASIHTTPRequest { + + // Parameters that will be POSTed to the url + NSMutableArray *postData; + + // Files that will be POSTed to the url + NSMutableArray *fileData; + + CTASIPostFormat postFormat; + + NSStringEncoding stringEncoding; + +#if DEBUG_FORM_DATA_REQUEST + // Will store a string version of the request body that will be printed to the console when ASIHTTPREQUEST_DEBUG is set in GCC_PREPROCESSOR_DEFINITIONS + NSString *debugBodyString; +#endif + +} + +#pragma mark utilities +- (NSString*)encodeURL:(NSString *)string; + +#pragma mark setup request + +// Add a POST variable to the request +- (void)addPostValue:(id )value forKey:(NSString *)key; + +// Set a POST variable for this request, clearing any others with the same key +- (void)setPostValue:(id )value forKey:(NSString *)key; + +// Add the contents of a local file to the request +- (void)addFile:(NSString *)filePath forKey:(NSString *)key; + +// Same as above, but you can specify the content-type and file name +- (void)addFile:(NSString *)filePath withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key; + +// Add the contents of a local file to the request, clearing any others with the same key +- (void)setFile:(NSString *)filePath forKey:(NSString *)key; + +// Same as above, but you can specify the content-type and file name +- (void)setFile:(NSString *)filePath withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key; + +// Add the contents of an NSData object to the request +- (void)addData:(NSData *)data forKey:(NSString *)key; + +// Same as above, but you can specify the content-type and file name +- (void)addData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key; + +// Add the contents of an NSData object to the request, clearing any others with the same key +- (void)setData:(NSData *)data forKey:(NSString *)key; + +// Same as above, but you can specify the content-type and file name +- (void)setData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key; + +@property (atomic, assign) CTASIPostFormat postFormat; +@property (atomic, assign) NSStringEncoding stringEncoding; +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASIFormDataRequest.m b/core/Frameworks/asi-http-request/Classes/CTASIFormDataRequest.m new file mode 100644 index 00000000..9b2185b8 --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASIFormDataRequest.m @@ -0,0 +1,365 @@ +// +// CTASIFormDataRequest.m +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 07/11/2008. +// Copyright 2008-2009 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +#import "CTASIFormDataRequest.h" + + +// Private stuff +@interface CTASIFormDataRequest () +- (void)buildMultipartFormDataPostBody; +- (void)buildURLEncodedPostBody; +- (void)appendPostString:(NSString *)string; + +@property (atomic, retain) NSMutableArray *postData; +@property (atomic, retain) NSMutableArray *fileData; + +#if DEBUG_FORM_DATA_REQUEST +- (void)addToDebugBody:(NSString *)string; +@property (retain, nonatomic) NSString *debugBodyString; +#endif + +@end + +@implementation CTASIFormDataRequest + +#pragma mark utilities +- (NSString*)encodeURL:(NSString *)string +{ + NSString *newString = [NSMakeCollectable(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding([self stringEncoding]))) autorelease]; + if (newString) { + return newString; + } + return @""; +} + +#pragma mark init / dealloc + ++ (id)requestWithURL:(NSURL *)newURL +{ + return [[[self alloc] initWithURL:newURL] autorelease]; +} + +- (id)initWithURL:(NSURL *)newURL +{ + self = [super initWithURL:newURL]; + [self setPostFormat:CTASIURLEncodedPostFormat]; + [self setStringEncoding:NSUTF8StringEncoding]; + [self setRequestMethod:@"POST"]; + return self; +} + +- (void)dealloc +{ +#if DEBUG_FORM_DATA_REQUEST + [debugBodyString release]; +#endif + + [postData release]; + [fileData release]; + [super dealloc]; +} + +#pragma mark setup request + +- (void)addPostValue:(id )value forKey:(NSString *)key +{ + if (!key) { + return; + } + if (![self postData]) { + [self setPostData:[NSMutableArray array]]; + } + NSMutableDictionary *keyValuePair = [NSMutableDictionary dictionaryWithCapacity:2]; + [keyValuePair setValue:key forKey:@"key"]; + [keyValuePair setValue:[value description] forKey:@"value"]; + [[self postData] addObject:keyValuePair]; +} + +- (void)setPostValue:(id )value forKey:(NSString *)key +{ + // Remove any existing value + NSUInteger i; + for (i=0; i<[[self postData] count]; i++) { + NSDictionary *val = [[self postData] objectAtIndex:i]; + if ([[val objectForKey:@"key"] isEqualToString:key]) { + [[self postData] removeObjectAtIndex:i]; + i--; + } + } + [self addPostValue:value forKey:key]; +} + + +- (void)addFile:(NSString *)filePath forKey:(NSString *)key +{ + [self addFile:filePath withFileName:nil andContentType:nil forKey:key]; +} + +- (void)addFile:(NSString *)filePath withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key +{ + BOOL isDirectory = NO; + BOOL fileExists = [[[[NSFileManager alloc] init] autorelease] fileExistsAtPath:filePath isDirectory:&isDirectory]; + if (!fileExists || isDirectory) { + [self failWithError:[NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"No file exists at %@", filePath], NSLocalizedDescriptionKey, nil]]]; + } + + // If the caller didn't specify a custom file name, we'll use the file name of the file we were passed + if (!fileName) { + fileName = [filePath lastPathComponent]; + } + + // If we were given the path to a file, and the user didn't specify a mime type, we can detect it from the file extension + if (!contentType) { + contentType = [CTASIHTTPRequest mimeTypeForFileAtPath:filePath]; + } + [self addData:filePath withFileName:fileName andContentType:contentType forKey:key]; +} + +- (void)setFile:(NSString *)filePath forKey:(NSString *)key +{ + [self setFile:filePath withFileName:nil andContentType:nil forKey:key]; +} + +- (void)setFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key +{ + // Remove any existing value + NSUInteger i; + for (i=0; i<[[self fileData] count]; i++) { + NSDictionary *val = [[self fileData] objectAtIndex:i]; + if ([[val objectForKey:@"key"] isEqualToString:key]) { + [[self fileData] removeObjectAtIndex:i]; + i--; + } + } + [self addFile:data withFileName:fileName andContentType:contentType forKey:key]; +} + +- (void)addData:(NSData *)data forKey:(NSString *)key +{ + [self addData:data withFileName:@"file" andContentType:nil forKey:key]; +} + +- (void)addData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key +{ + if (![self fileData]) { + [self setFileData:[NSMutableArray array]]; + } + if (!contentType) { + contentType = @"application/octet-stream"; + } + + NSMutableDictionary *fileInfo = [NSMutableDictionary dictionaryWithCapacity:4]; + [fileInfo setValue:key forKey:@"key"]; + [fileInfo setValue:fileName forKey:@"fileName"]; + [fileInfo setValue:contentType forKey:@"contentType"]; + [fileInfo setValue:data forKey:@"data"]; + + [[self fileData] addObject:fileInfo]; +} + +- (void)setData:(NSData *)data forKey:(NSString *)key +{ + [self setData:data withFileName:@"file" andContentType:nil forKey:key]; +} + +- (void)setData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key +{ + // Remove any existing value + NSUInteger i; + for (i=0; i<[[self fileData] count]; i++) { + NSDictionary *val = [[self fileData] objectAtIndex:i]; + if ([[val objectForKey:@"key"] isEqualToString:key]) { + [[self fileData] removeObjectAtIndex:i]; + i--; + } + } + [self addData:data withFileName:fileName andContentType:contentType forKey:key]; +} + +- (void)buildPostBody +{ + if ([self haveBuiltPostBody]) { + return; + } + +#if DEBUG_FORM_DATA_REQUEST + [self setDebugBodyString:@""]; +#endif + + if (![self postData] && ![self fileData]) { + [super buildPostBody]; + return; + } + if ([[self fileData] count] > 0) { + [self setShouldStreamPostDataFromDisk:YES]; + } + + if ([self postFormat] == CTASIURLEncodedPostFormat) { + [self buildURLEncodedPostBody]; + } else { + [self buildMultipartFormDataPostBody]; + } + + [super buildPostBody]; + +#if DEBUG_FORM_DATA_REQUEST + ASI_DEBUG_LOG(@"%@",[self debugBodyString]); + [self setDebugBodyString:nil]; +#endif +} + + +- (void)buildMultipartFormDataPostBody +{ +#if DEBUG_FORM_DATA_REQUEST + [self addToDebugBody:@"\r\n==== Building a multipart/form-data body ====\r\n"]; +#endif + + NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding])); + + // We don't bother to check if post data contains the boundary, since it's pretty unlikely that it does. + CFUUIDRef uuid = CFUUIDCreate(nil); + NSString *uuidString = [(NSString*)CFUUIDCreateString(nil, uuid) autorelease]; + CFRelease(uuid); + NSString *stringBoundary = [NSString stringWithFormat:@"0xKhTmLbOuNdArY-%@",uuidString]; + + [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"multipart/form-data; charset=%@; boundary=%@", charset, stringBoundary]]; + + [self appendPostString:[NSString stringWithFormat:@"--%@\r\n",stringBoundary]]; + + // Adds post data + NSString *endItemBoundary = [NSString stringWithFormat:@"\r\n--%@\r\n",stringBoundary]; + NSUInteger i=0; + for (NSDictionary *val in [self postData]) { + [self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",[val objectForKey:@"key"]]]; + [self appendPostString:[val objectForKey:@"value"]]; + i++; + if (i != [[self postData] count] || [[self fileData] count] > 0) { //Only add the boundary if this is not the last item in the post body + [self appendPostString:endItemBoundary]; + } + } + + // Adds files to upload + i=0; + for (NSDictionary *val in [self fileData]) { + + [self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", [val objectForKey:@"key"], [val objectForKey:@"fileName"]]]; + [self appendPostString:[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", [val objectForKey:@"contentType"]]]; + + id data = [val objectForKey:@"data"]; + if ([data isKindOfClass:[NSString class]]) { + [self appendPostDataFromFile:data]; + } else { + [self appendPostData:data]; + } + i++; + // Only add the boundary if this is not the last item in the post body + if (i != [[self fileData] count]) { + [self appendPostString:endItemBoundary]; + } + } + + [self appendPostString:[NSString stringWithFormat:@"\r\n--%@--\r\n",stringBoundary]]; + +#if DEBUG_FORM_DATA_REQUEST + [self addToDebugBody:@"==== End of multipart/form-data body ====\r\n"]; +#endif +} + +- (void)buildURLEncodedPostBody +{ + + // We can't post binary data using application/x-www-form-urlencoded + if ([[self fileData] count] > 0) { + [self setPostFormat:CTASIMultipartFormDataPostFormat]; + [self buildMultipartFormDataPostBody]; + return; + } + +#if DEBUG_FORM_DATA_REQUEST + [self addToDebugBody:@"\r\n==== Building an application/x-www-form-urlencoded body ====\r\n"]; +#endif + + + NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding])); + + [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@",charset]]; + + + NSUInteger i=0; + NSUInteger count = [[self postData] count]-1; + for (NSDictionary *val in [self postData]) { + NSString *data = [NSString stringWithFormat:@"%@=%@%@", [self encodeURL:[val objectForKey:@"key"]], [self encodeURL:[val objectForKey:@"value"]],(i +#if TARGET_OS_IPHONE + #import + #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 + #import // Necessary for background task support + #endif +#endif + +#import +#import "CTASIHTTPRequestConfig.h" +#import "CTASIHTTPRequestDelegate.h" +#import "CTASIProgressDelegate.h" +#import "CTASICacheDelegate.h" + +@class CTASIDataDecompressor; + +extern NSString *ASIHTTPRequestVersion; + +// Make targeting different platforms more reliable +// See: http://www.blumtnwerx.com/blog/2009/06/cross-sdk-code-hygiene-in-xcode/ +#ifndef __IPHONE_3_2 + #define __IPHONE_3_2 30200 +#endif +#ifndef __IPHONE_4_0 + #define __IPHONE_4_0 40000 +#endif +#ifndef __MAC_10_5 + #define __MAC_10_5 1050 +#endif +#ifndef __MAC_10_6 + #define __MAC_10_6 1060 +#endif + +typedef enum _ASIAuthenticationState { + ASINoAuthenticationNeededYet = 0, + ASIHTTPAuthenticationNeeded = 1, + ASIProxyAuthenticationNeeded = 2 +} ASIAuthenticationState; + +typedef enum _ASINetworkErrorType { + ASIConnectionFailureErrorType = 1, + ASIRequestTimedOutErrorType = 2, + ASIAuthenticationErrorType = 3, + ASIRequestCancelledErrorType = 4, + ASIUnableToCreateRequestErrorType = 5, + ASIInternalErrorWhileBuildingRequestType = 6, + ASIInternalErrorWhileApplyingCredentialsType = 7, + ASIFileManagementError = 8, + ASITooMuchRedirectionErrorType = 9, + ASIUnhandledExceptionError = 10, + ASICompressionError = 11 + +} ASINetworkErrorType; + + +// The error domain that all errors generated by CTASIHTTPRequest use +extern NSString* const CTNetworkRequestErrorDomain; + +// You can use this number to throttle upload and download bandwidth in iPhone OS apps send or receive a large amount of data +// This may help apps that might otherwise be rejected for inclusion into the app store for using excessive bandwidth +// This number is not official, as far as I know there is no officially documented bandwidth limit +extern unsigned long const ASIWWANBandwidthThrottleAmount; + +#if NS_BLOCKS_AVAILABLE +typedef void (^ASIBasicBlock)(void); +typedef void (^ASIHeadersBlock)(NSDictionary *responseHeaders); +typedef void (^ASISizeBlock)(long long size); +typedef void (^ASIProgressBlock)(unsigned long long size, unsigned long long total); +typedef void (^ASIDataBlock)(NSData *data); +#endif + +@interface CTASIHTTPRequest : NSOperation { + + // The url for this operation, should include GET params in the query string where appropriate + NSURL *url; + + // Will always contain the original url used for making the request (the value of url can change when a request is redirected) + NSURL *originalURL; + + // Temporarily stores the url we are about to redirect to. Will be nil again when we do redirect + NSURL *redirectURL; + + // The delegate - will be notified of various changes in state via the CTASIHTTPRequestDelegate protocol + id delegate; + + // Another delegate that is also notified of request status changes and progress updates + // Generally, you won't use this directly, but CTASINetworkQueue sets itself as the queue so it can proxy updates to its own delegates + // NOTE: WILL BE RETAINED BY THE REQUEST + id queue; + + // HTTP method to use (eg: GET / POST / PUT / DELETE / HEAD etc). Defaults to GET + NSString *requestMethod; + + // Request body - only used when the whole body is stored in memory (shouldStreamPostDataFromDisk is false) + NSMutableData *postBody; + + // gzipped request body used when shouldCompressRequestBody is YES + NSData *compressedPostBody; + + // When true, post body will be streamed from a file on disk, rather than loaded into memory at once (useful for large uploads) + // Automatically set to true in ASIFormDataRequests when using setFile:forKey: + BOOL shouldStreamPostDataFromDisk; + + // Path to file used to store post body (when shouldStreamPostDataFromDisk is true) + // You can set this yourself - useful if you want to PUT a file from local disk + NSString *postBodyFilePath; + + // Path to a temporary file used to store a deflated post body (when shouldCompressPostBody is YES) + NSString *compressedPostBodyFilePath; + + // Set to true when CTASIHTTPRequest automatically created a temporary file containing the request body (when true, the file at postBodyFilePath will be deleted at the end of the request) + BOOL didCreateTemporaryPostDataFile; + + // Used when writing to the post body when shouldStreamPostDataFromDisk is true (via appendPostData: or appendPostDataFromFile:) + NSOutputStream *postBodyWriteStream; + + // Used for reading from the post body when sending the request + NSInputStream *postBodyReadStream; + + // Dictionary for custom HTTP request headers + NSMutableDictionary *requestHeaders; + + // Set to YES when the request header dictionary has been populated, used to prevent this happening more than once + BOOL haveBuiltRequestHeaders; + + // Will be populated with HTTP response headers from the server + NSDictionary *responseHeaders; + + // Can be used to manually insert cookie headers to a request, but it's more likely that sessionCookies will do this for you + NSMutableArray *requestCookies; + + // Will be populated with cookies + NSArray *responseCookies; + + // If use useCookiePersistence is true, network requests will present valid cookies from previous requests + BOOL useCookiePersistence; + + // If useKeychainPersistence is true, network requests will attempt to read credentials from the keychain, and will save them in the keychain when they are successfully presented + BOOL useKeychainPersistence; + + // If useSessionPersistence is true, network requests will save credentials and reuse for the duration of the session (until clearSession is called) + BOOL useSessionPersistence; + + // If allowCompressedResponse is true, requests will inform the server they can accept compressed data, and will automatically decompress gzipped responses. Default is true. + BOOL allowCompressedResponse; + + // If shouldCompressRequestBody is true, the request body will be gzipped. Default is false. + // You will probably need to enable this feature on your webserver to make this work. Tested with apache only. + BOOL shouldCompressRequestBody; + + // When downloadDestinationPath is set, the result of this request will be downloaded to the file at this location + // If downloadDestinationPath is not set, download data will be stored in memory + NSString *downloadDestinationPath; + + // The location that files will be downloaded to. Once a download is complete, files will be decompressed (if necessary) and moved to downloadDestinationPath + NSString *temporaryFileDownloadPath; + + // If the response is gzipped and shouldWaitToInflateCompressedResponses is NO, a file will be created at this path containing the inflated response as it comes in + NSString *temporaryUncompressedDataDownloadPath; + + // Used for writing data to a file when downloadDestinationPath is set + NSOutputStream *fileDownloadOutputStream; + + NSOutputStream *inflatedFileDownloadOutputStream; + + // When the request fails or completes successfully, complete will be true + BOOL complete; + + // external "finished" indicator, subject of KVO notifications; updates after 'complete' + BOOL finished; + + // True if our 'cancel' selector has been called + BOOL cancelled; + + // If an error occurs, error will contain an NSError + // If error code is = ASIConnectionFailureErrorType (1, Connection failure occurred) - inspect [[error userInfo] objectForKey:NSUnderlyingErrorKey] for more information + NSError *error; + + // Username and password used for authentication + NSString *username; + NSString *password; + + // User-Agent for this request + NSString *userAgentString; + + // Domain used for NTLM authentication + NSString *domain; + + // Username and password used for proxy authentication + NSString *proxyUsername; + NSString *proxyPassword; + + // Domain used for NTLM proxy authentication + NSString *proxyDomain; + + // Delegate for displaying upload progress (usually an NSProgressIndicator, but you can supply a different object and handle this yourself) + id uploadProgressDelegate; + + // Delegate for displaying download progress (usually an NSProgressIndicator, but you can supply a different object and handle this yourself) + id downloadProgressDelegate; + + // Whether we've seen the headers of the response yet + BOOL haveExaminedHeaders; + + // Data we receive will be stored here. Data may be compressed unless allowCompressedResponse is false - you should use [request responseData] instead in most cases + NSMutableData *rawResponseData; + + // Used for sending and receiving data + CFHTTPMessageRef request; + NSInputStream *readStream; + + // Used for authentication + CFHTTPAuthenticationRef requestAuthentication; + NSDictionary *requestCredentials; + + // Used during NTLM authentication + int authenticationRetryCount; + + // Authentication scheme (Basic, Digest, NTLM) + // If you are using Basic authentication and want to force CTASIHTTPRequest to send an authorization header without waiting for a 401, you must set this to (NSString *)kCFHTTPAuthenticationSchemeBasic + NSString *authenticationScheme; + + // Realm for authentication when credentials are required + NSString *authenticationRealm; + + // When YES, CTASIHTTPRequest will present a dialog allowing users to enter credentials when no-matching credentials were found for a server that requires authentication + // The dialog will not be shown if your delegate responds to authenticationNeededForRequest: + // Default is NO. + BOOL shouldPresentAuthenticationDialog; + + // When YES, CTASIHTTPRequest will present a dialog allowing users to enter credentials when no-matching credentials were found for a proxy server that requires authentication + // The dialog will not be shown if your delegate responds to proxyAuthenticationNeededForRequest: + // Default is YES (basically, because most people won't want the hassle of adding support for authenticating proxies to their apps) + BOOL shouldPresentProxyAuthenticationDialog; + + // Used for proxy authentication + CFHTTPAuthenticationRef proxyAuthentication; + NSDictionary *proxyCredentials; + + // Used during authentication with an NTLM proxy + int proxyAuthenticationRetryCount; + + // Authentication scheme for the proxy (Basic, Digest, NTLM) + NSString *proxyAuthenticationScheme; + + // Realm for proxy authentication when credentials are required + NSString *proxyAuthenticationRealm; + + // HTTP status code, eg: 200 = OK, 404 = Not found etc + int responseStatusCode; + + // Description of the HTTP status code + NSString *responseStatusMessage; + + // Size of the response + unsigned long long contentLength; + + // Size of the partially downloaded content + unsigned long long partialDownloadSize; + + // Size of the POST payload + unsigned long long postLength; + + // The total amount of downloaded data + unsigned long long totalBytesRead; + + // The total amount of uploaded data + unsigned long long totalBytesSent; + + // Last amount of data read (used for incrementing progress) + unsigned long long lastBytesRead; + + // Last amount of data sent (used for incrementing progress) + unsigned long long lastBytesSent; + + // This lock prevents the operation from being cancelled at an inopportune moment + NSRecursiveLock *cancelledLock; + + // Called on the delegate (if implemented) when the request starts. Default is requestStarted: + SEL didStartSelector; + + // Called on the delegate (if implemented) when the request receives response headers. Default is request:didReceiveResponseHeaders: + SEL didReceiveResponseHeadersSelector; + + // Called on the delegate (if implemented) when the request receives a Location header and shouldRedirect is YES + // The delegate can then change the url if needed, and can restart the request by calling [request redirectToURL:], or simply cancel it + SEL willRedirectSelector; + + // Called on the delegate (if implemented) when the request completes successfully. Default is requestFinished: + SEL didFinishSelector; + + // Called on the delegate (if implemented) when the request fails. Default is requestFailed: + SEL didFailSelector; + + // Called on the delegate (if implemented) when the request receives data. Default is request:didReceiveData: + // If you set this and implement the method in your delegate, you must handle the data yourself - CTASIHTTPRequest will not populate responseData or write the data to downloadDestinationPath + SEL didReceiveDataSelector; + + // Used for recording when something last happened during the request, we will compare this value with the current date to time out requests when appropriate + NSDate *lastActivityTime; + + // Number of seconds to wait before timing out - default is 10 + NSTimeInterval timeOutSeconds; + + // Will be YES when a HEAD request will handle the content-length before this request starts + BOOL shouldResetUploadProgress; + BOOL shouldResetDownloadProgress; + + // Used by HEAD requests when showAccurateProgress is YES to preset the content-length for this request + CTASIHTTPRequest *mainRequest; + + // When NO, this request will only update the progress indicator when it completes + // When YES, this request will update the progress indicator according to how much data it has received so far + // The default for requests is YES + // Also see the comments in CTASINetworkQueue.h + BOOL showAccurateProgress; + + // Used to ensure the progress indicator is only incremented once when showAccurateProgress = NO + BOOL updatedProgress; + + // Prevents the body of the post being built more than once (largely for subclasses) + BOOL haveBuiltPostBody; + + // Used internally, may reflect the size of the internal buffer used by CFNetwork + // POST / PUT operations with body sizes greater than uploadBufferSize will not timeout unless more than uploadBufferSize bytes have been sent + // Likely to be 32KB on iPhone 3.0, 128KB on Mac OS X Leopard and iPhone 2.2.x + unsigned long long uploadBufferSize; + + // Text encoding for responses that do not send a Content-Type with a charset value. Defaults to NSISOLatin1StringEncoding + NSStringEncoding defaultResponseEncoding; + + // The text encoding of the response, will be defaultResponseEncoding if the server didn't specify. Can't be set. + NSStringEncoding responseEncoding; + + // Tells CTASIHTTPRequest not to delete partial downloads, and allows it to use an existing file to resume a download. Defaults to NO. + BOOL allowResumeForFileDownloads; + + // Custom user information associated with the request (not sent to the server) + NSDictionary *userInfo; + NSInteger tag; + + // Use HTTP 1.0 rather than 1.1 (defaults to false) + BOOL useHTTPVersionOne; + + // When YES, requests will automatically redirect when they get a HTTP 30x header (defaults to YES) + BOOL shouldRedirect; + + // Used internally to tell the main loop we need to stop and retry with a new url + BOOL needsRedirect; + + // Incremented every time this request redirects. When it reaches 5, we give up + int redirectCount; + + // When NO, requests will not check the secure certificate is valid (use for self-signed certificates during development, DO NOT USE IN PRODUCTION) Default is YES + BOOL validatesSecureCertificate; + + // If not nil and the URL scheme is https, CFNetwork configured to supply a client certificate + SecIdentityRef clientCertificateIdentity; + NSArray *clientCertificates; + + // Details on the proxy to use - you could set these yourself, but it's probably best to let CTASIHTTPRequest detect the system proxy settings + NSString *proxyHost; + int proxyPort; + + // CTASIHTTPRequest will assume kCFProxyTypeHTTP if the proxy type could not be automatically determined + // Set to kCFProxyTypeSOCKS if you are manually configuring a SOCKS proxy + NSString *proxyType; + + // URL for a PAC (Proxy Auto Configuration) file. If you want to set this yourself, it's probably best if you use a local file + NSURL *PACurl; + + // See ASIAuthenticationState values above. 0 == default == No authentication needed yet + ASIAuthenticationState authenticationNeeded; + + // When YES, ASIHTTPRequests will present credentials from the session store for requests to the same server before being asked for them + // This avoids an extra round trip for requests after authentication has succeeded, which is much for efficient for authenticated requests with large bodies, or on slower connections + // Set to NO to only present credentials when explicitly asked for them + // This only affects credentials stored in the session cache when useSessionPersistence is YES. Credentials from the keychain are never presented unless the server asks for them + // Default is YES + // For requests using Basic authentication, set authenticationScheme to (NSString *)kCFHTTPAuthenticationSchemeBasic, and credentials can be sent on the very first request when shouldPresentCredentialsBeforeChallenge is YES + BOOL shouldPresentCredentialsBeforeChallenge; + + // YES when the request hasn't finished yet. Will still be YES even if the request isn't doing anything (eg it's waiting for delegate authentication). READ-ONLY + BOOL inProgress; + + // Used internally to track whether the stream is scheduled on the run loop or not + // Bandwidth throttling can unschedule the stream to slow things down while a request is in progress + BOOL readStreamIsScheduled; + + // Set to allow a request to automatically retry itself on timeout + // Default is zero - timeout will stop the request + int numberOfTimesToRetryOnTimeout; + + // The number of times this request has retried (when numberOfTimesToRetryOnTimeout > 0) + int retryCount; + + // Temporarily set to YES when a closed connection forces a retry (internally, this stops CTASIHTTPRequest cleaning up a temporary post body) + BOOL willRetryRequest; + + // When YES, requests will keep the connection to the server alive for a while to allow subsequent requests to re-use it for a substantial speed-boost + // Persistent connections will not be used if the server explicitly closes the connection + // Default is YES + BOOL shouldAttemptPersistentConnection; + + // Number of seconds to keep an inactive persistent connection open on the client side + // Default is 60 + // If we get a keep-alive header, this is this value is replaced with how long the server told us to keep the connection around + // A future date is created from this and used for expiring the connection, this is stored in connectionInfo's expires value + NSTimeInterval persistentConnectionTimeoutSeconds; + + // Set to yes when an appropriate keep-alive header is found + BOOL connectionCanBeReused; + + // Stores information about the persistent connection that is currently in use. + // It may contain: + // * The id we set for a particular connection, incremented every time we want to specify that we need a new connection + // * The date that connection should expire + // * A host, port and scheme for the connection. These are used to determine whether that connection can be reused by a subsequent request (all must match the new request) + // * An id for the request that is currently using the connection. This is used for determining if a connection is available or not (we store a number rather than a reference to the request so we don't need to hang onto a request until the connection expires) + // * A reference to the stream that is currently using the connection. This is necessary because we need to keep the old stream open until we've opened a new one. + // The stream will be closed + released either when another request comes to use the connection, or when the timer fires to tell the connection to expire + NSMutableDictionary *connectionInfo; + + // When set to YES, 301 and 302 automatic redirects will use the original method and and body, according to the HTTP 1.1 standard + // Default is NO (to follow the behaviour of most browsers) + BOOL shouldUseRFC2616RedirectBehaviour; + + // Used internally to record when a request has finished downloading data + BOOL downloadComplete; + + // An ID that uniquely identifies this request - primarily used for debugging persistent connections + NSNumber *requestID; + + // Will be ASIHTTPRequestRunLoopMode for synchronous requests, NSDefaultRunLoopMode for all other requests + NSString *runLoopMode; + + // This timer checks up on the request every 0.25 seconds, and updates progress + NSTimer *statusTimer; + + // The download cache that will be used for this request (use [CTASIHTTPRequest setDefaultCache:cache] to configure a default cache + id downloadCache; + + // The cache policy that will be used for this request - See CTASICacheDelegate.h for possible values + CTASICachePolicy cachePolicy; + + // The cache storage policy that will be used for this request - See CTASICacheDelegate.h for possible values + CTASICacheStoragePolicy cacheStoragePolicy; + + // Will be true when the response was pulled from the cache rather than downloaded + BOOL didUseCachedResponse; + + // Set secondsToCache to use a custom time interval for expiring the response when it is stored in a cache + NSTimeInterval secondsToCache; + + #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 + BOOL shouldContinueWhenAppEntersBackground; + UIBackgroundTaskIdentifier backgroundTask; + #endif + + // When downloading a gzipped response, the request will use this helper object to inflate the response + CTASIDataDecompressor *dataDecompressor; + + // Controls how responses with a gzipped encoding are inflated (decompressed) + // When set to YES (This is the default): + // * gzipped responses for requests without a downloadDestinationPath will be inflated only when [request responseData] / [request responseString] is called + // * gzipped responses for requests with a downloadDestinationPath set will be inflated only when the request completes + // + // When set to NO + // All requests will inflate the response as it comes in + // * If the request has no downloadDestinationPath set, the raw (compressed) response is discarded and rawResponseData will contain the decompressed response + // * If the request has a downloadDestinationPath, the raw response will be stored in temporaryFileDownloadPath as normal, the inflated response will be stored in temporaryUncompressedDataDownloadPath + // Once the request completes successfully, the contents of temporaryUncompressedDataDownloadPath are moved into downloadDestinationPath + // + // Setting this to NO may be especially useful for users using CTASIHTTPRequest in conjunction with a streaming parser, as it will allow partial gzipped responses to be inflated and passed on to the parser while the request is still running + BOOL shouldWaitToInflateCompressedResponses; + + // Will be YES if this is a request created behind the scenes to download a PAC file - these requests do not attempt to configure their own proxies + BOOL isPACFileRequest; + + // Used for downloading PAC files from http / https webservers + CTASIHTTPRequest *PACFileRequest; + + // Used for asynchronously reading PAC files from file:// URLs + NSInputStream *PACFileReadStream; + + // Used for storing PAC data from file URLs as it is downloaded + NSMutableData *PACFileData; + + // Set to YES in startSynchronous. Currently used by proxy detection to download PAC files synchronously when appropriate + BOOL isSynchronous; + + #if NS_BLOCKS_AVAILABLE + //block to execute when request starts + ASIBasicBlock startedBlock; + + //block to execute when headers are received + ASIHeadersBlock headersReceivedBlock; + + //block to execute when request completes successfully + ASIBasicBlock completionBlock; + + //block to execute when request fails + ASIBasicBlock failureBlock; + + //block for when bytes are received + ASIProgressBlock bytesReceivedBlock; + + //block for when bytes are sent + ASIProgressBlock bytesSentBlock; + + //block for when download size is incremented + ASISizeBlock downloadSizeIncrementedBlock; + + //block for when upload size is incremented + ASISizeBlock uploadSizeIncrementedBlock; + + //block for handling raw bytes received + ASIDataBlock dataReceivedBlock; + + //block for handling authentication + ASIBasicBlock authenticationNeededBlock; + + //block for handling proxy authentication + ASIBasicBlock proxyAuthenticationNeededBlock; + + //block for handling redirections, if you want to + ASIBasicBlock requestRedirectedBlock; + #endif +} + +#pragma mark init / dealloc + +// Should be an HTTP or HTTPS url, may include username and password if appropriate +- (id)initWithURL:(NSURL *)newURL; + +// Convenience constructor ++ (id)requestWithURL:(NSURL *)newURL; + ++ (id)requestWithURL:(NSURL *)newURL usingCache:(id )cache; ++ (id)requestWithURL:(NSURL *)newURL usingCache:(id )cache andCachePolicy:(CTASICachePolicy)policy; + +#if NS_BLOCKS_AVAILABLE +- (void)setStartedBlock:(ASIBasicBlock)aStartedBlock; +- (void)setHeadersReceivedBlock:(ASIHeadersBlock)aReceivedBlock; +- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock; +- (void)setFailedBlock:(ASIBasicBlock)aFailedBlock; +- (void)setBytesReceivedBlock:(ASIProgressBlock)aBytesReceivedBlock; +- (void)setBytesSentBlock:(ASIProgressBlock)aBytesSentBlock; +- (void)setDownloadSizeIncrementedBlock:(ASISizeBlock) aDownloadSizeIncrementedBlock; +- (void)setUploadSizeIncrementedBlock:(ASISizeBlock) anUploadSizeIncrementedBlock; +- (void)setDataReceivedBlock:(ASIDataBlock)aReceivedBlock; +- (void)setAuthenticationNeededBlock:(ASIBasicBlock)anAuthenticationBlock; +- (void)setProxyAuthenticationNeededBlock:(ASIBasicBlock)aProxyAuthenticationBlock; +- (void)setRequestRedirectedBlock:(ASIBasicBlock)aRedirectBlock; +#endif + +#pragma mark setup request + +// Add a custom header to the request +- (void)addRequestHeader:(NSString *)header value:(NSString *)value; + +// Called during buildRequestHeaders and after a redirect to create a cookie header from request cookies and the global store +- (void)applyCookieHeader; + +// Populate the request headers dictionary. Called before a request is started, or by a HEAD request that needs to borrow them +- (void)buildRequestHeaders; + +// Used to apply authorization header to a request before it is sent (when shouldPresentCredentialsBeforeChallenge is YES) +- (void)applyAuthorizationHeader; + + +// Create the post body +- (void)buildPostBody; + +// Called to add data to the post body. Will append to postBody when shouldStreamPostDataFromDisk is false, or write to postBodyWriteStream when true +- (void)appendPostData:(NSData *)data; +- (void)appendPostDataFromFile:(NSString *)file; + +#pragma mark get information about this request + +// Returns the contents of the result as an NSString (not appropriate for binary data - used responseData instead) +- (NSString *)responseString; + +// Response data, automatically uncompressed where appropriate +- (NSData *)responseData; + +// Returns true if the response was gzip compressed +- (BOOL)isResponseCompressed; + +#pragma mark running a request + + +// Run a request synchronously, and return control when the request completes or fails +- (void)startSynchronous; + +// Run request in the background +- (void)startAsynchronous; + +// Clears all delegates and blocks, then cancels the request +- (void)clearDelegatesAndCancel; + +#pragma mark HEAD request + +// Used by CTASINetworkQueue to create a HEAD request appropriate for this request with the same headers (though you can use it yourself) +- (CTASIHTTPRequest *)HEADRequest; + +#pragma mark upload/download progress + +// Called approximately every 0.25 seconds to update the progress delegates +- (void)updateProgressIndicators; + +// Updates upload progress (notifies the queue and/or uploadProgressDelegate of this request) +- (void)updateUploadProgress; + +// Updates download progress (notifies the queue and/or uploadProgressDelegate of this request) +- (void)updateDownloadProgress; + +// Called when authorisation is needed, as we only find out we don't have permission to something when the upload is complete +- (void)removeUploadProgressSoFar; + +// Called when we get a content-length header and shouldResetDownloadProgress is true +- (void)incrementDownloadSizeBy:(long long)length; + +// Called when a request starts and shouldResetUploadProgress is true +// Also called (with a negative length) to remove the size of the underlying buffer used for uploading +- (void)incrementUploadSizeBy:(long long)length; + +// Helper method for interacting with progress indicators to abstract the details of different APIS (NSProgressIndicator and UIProgressView) ++ (void)updateProgressIndicator:(id *)indicator withProgress:(unsigned long long)progress ofTotal:(unsigned long long)total; + +// Helper method used for performing invocations on the main thread (used for progress) ++ (void)performSelector:(SEL)selector onTarget:(id *)target withObject:(id)object amount:(void *)amount callerToRetain:(id)caller; + +#pragma mark talking to delegates + +// Called when a request starts, lets the delegate know via didStartSelector +- (void)requestStarted; + +// Called when a request receives response headers, lets the delegate know via didReceiveResponseHeadersSelector +- (void)requestReceivedResponseHeaders:(NSDictionary *)newHeaders; + +// Called when a request completes successfully, lets the delegate know via didFinishSelector +- (void)requestFinished; + +// Called when a request fails, and lets the delegate know via didFailSelector +- (void)failWithError:(NSError *)theError; + +// Called to retry our request when our persistent connection is closed +// Returns YES if we haven't already retried, and connection will be restarted +// Otherwise, returns NO, and nothing will happen +- (BOOL)retryUsingNewConnection; + +// Can be called by delegates from inside their willRedirectSelector implementations to restart the request with a new url +- (void)redirectToURL:(NSURL *)newURL; + +#pragma mark parsing HTTP response headers + +// Reads the response headers to find the content length, encoding, cookies for the session +// Also initiates request redirection when shouldRedirect is true +// And works out if HTTP auth is required +- (void)readResponseHeaders; + +// Attempts to set the correct encoding by looking at the Content-Type header, if this is one +- (void)parseStringEncodingFromHeaders; + ++ (void)parseMimeType:(NSString **)mimeType andResponseEncoding:(NSStringEncoding *)stringEncoding fromContentType:(NSString *)contentType; + +#pragma mark http authentication stuff + +// Apply credentials to this request +- (BOOL)applyCredentials:(NSDictionary *)newCredentials; +- (BOOL)applyProxyCredentials:(NSDictionary *)newCredentials; + +// Attempt to obtain credentials for this request from the URL, username and password or keychain +- (NSMutableDictionary *)findCredentials; +- (NSMutableDictionary *)findProxyCredentials; + +// Unlock (unpause) the request thread so it can resume the request +// Should be called by delegates when they have populated the authentication information after an authentication challenge +- (void)retryUsingSuppliedCredentials; + +// Should be called by delegates when they wish to cancel authentication and stop +- (void)cancelAuthentication; + +// Apply authentication information and resume the request after an authentication challenge +- (void)attemptToApplyCredentialsAndResume; +- (void)attemptToApplyProxyCredentialsAndResume; + +// Attempt to show the built-in authentication dialog, returns YES if credentials were supplied, NO if user cancelled dialog / dialog is disabled / running on main thread +// Currently only used on iPhone OS +- (BOOL)showProxyAuthenticationDialog; +- (BOOL)showAuthenticationDialog; + +// Construct a basic authentication header from the username and password supplied, and add it to the request headers +// Used when shouldPresentCredentialsBeforeChallenge is YES +- (void)addBasicAuthenticationHeaderWithUsername:(NSString *)theUsername andPassword:(NSString *)thePassword; + +#pragma mark stream status handlers + +// CFnetwork event handlers +- (void)handleNetworkEvent:(CFStreamEventType)type; +- (void)handleBytesAvailable; +- (void)handleStreamComplete; +- (void)handleStreamError; + +#pragma mark cleanup + +// Cleans up and lets the queue know this operation is finished. +// Appears in this header for subclassing only, do not call this method from outside your request! +- (void)markAsFinished; + +// Cleans up temporary files. There's normally no reason to call these yourself, they are called automatically when a request completes or fails + +// Clean up the temporary file used to store the downloaded data when it comes in (if downloadDestinationPath is set) +- (BOOL)removeTemporaryDownloadFile; + +// Clean up the temporary file used to store data that is inflated (decompressed) as it comes in +- (BOOL)removeTemporaryUncompressedDownloadFile; + +// Clean up the temporary file used to store the request body (when shouldStreamPostDataFromDisk is YES) +- (BOOL)removeTemporaryUploadFile; + +// Clean up the temporary file used to store a deflated (compressed) request body when shouldStreamPostDataFromDisk is YES +- (BOOL)removeTemporaryCompressedUploadFile; + +// Remove a file on disk, returning NO and populating the passed error pointer if it fails ++ (BOOL)removeFileAtPath:(NSString *)path error:(NSError **)err; + +#pragma mark persistent connections + +// Get the ID of the connection this request used (only really useful in tests and debugging) +- (NSNumber *)connectionID; + +// Called automatically when a request is started to clean up any persistent connections that have expired ++ (void)expirePersistentConnections; + +#pragma mark default time out + ++ (NSTimeInterval)defaultTimeOutSeconds; ++ (void)setDefaultTimeOutSeconds:(NSTimeInterval)newTimeOutSeconds; + +#pragma mark client certificate + +- (void)setClientCertificateIdentity:(SecIdentityRef)anIdentity; + +#pragma mark session credentials + ++ (NSMutableArray *)sessionProxyCredentialsStore; ++ (NSMutableArray *)sessionCredentialsStore; + ++ (void)storeProxyAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials; ++ (void)storeAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials; + ++ (void)removeProxyAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials; ++ (void)removeAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials; + +- (NSDictionary *)findSessionProxyAuthenticationCredentials; +- (NSDictionary *)findSessionAuthenticationCredentials; + +#pragma mark keychain storage + +// Save credentials for this request to the keychain +- (void)saveCredentialsToKeychain:(NSDictionary *)newCredentials; + +// Save credentials to the keychain ++ (void)saveCredentials:(NSURLCredential *)credentials forHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm; ++ (void)saveCredentials:(NSURLCredential *)credentials forProxy:(NSString *)host port:(int)port realm:(NSString *)realm; + +// Return credentials from the keychain ++ (NSURLCredential *)savedCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm; ++ (NSURLCredential *)savedCredentialsForProxy:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm; + +// Remove credentials from the keychain ++ (void)removeCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm; ++ (void)removeCredentialsForProxy:(NSString *)host port:(int)port realm:(NSString *)realm; + +// We keep track of any cookies we accept, so that we can remove them from the persistent store later ++ (void)setSessionCookies:(NSMutableArray *)newSessionCookies; ++ (NSMutableArray *)sessionCookies; + +// Adds a cookie to our list of cookies we've accepted, checking first for an old version of the same cookie and removing that ++ (void)addSessionCookie:(NSHTTPCookie *)newCookie; + +// Dump all session data (authentication and cookies) ++ (void)clearSession; + +#pragma mark get user agent + +// Will be used as a user agent if requests do not specify a custom user agent +// Is only used when you have specified a Bundle Display Name (CFDisplayBundleName) or Bundle Name (CFBundleName) in your plist ++ (NSString *)defaultUserAgentString; ++ (void)setDefaultUserAgentString:(NSString *)agent; + +#pragma mark mime-type detection + +// Return the mime type for a file ++ (NSString *)mimeTypeForFileAtPath:(NSString *)path; + +#pragma mark bandwidth measurement / throttling + +// The maximum number of bytes ALL requests can send / receive in a second +// This is a rough figure. The actual amount used will be slightly more, this does not include HTTP headers ++ (unsigned long)maxBandwidthPerSecond; ++ (void)setMaxBandwidthPerSecond:(unsigned long)bytes; + +// Get a rough average (for the last 5 seconds) of how much bandwidth is being used, in bytes ++ (unsigned long)averageBandwidthUsedPerSecond; + +- (void)performThrottling; + +// Will return YES is bandwidth throttling is currently in use ++ (BOOL)isBandwidthThrottled; + +// Used internally to record bandwidth use, and by ASIInputStreams when uploading. It's probably best if you don't mess with this. ++ (void)incrementBandwidthUsedInLastSecond:(unsigned long)bytes; + +// On iPhone, CTASIHTTPRequest can automatically turn throttling on and off as the connection type changes between WWAN and WiFi + +#if TARGET_OS_IPHONE +// Set to YES to automatically turn on throttling when WWAN is connected, and automatically turn it off when it isn't ++ (void)setShouldThrottleBandwidthForWWAN:(BOOL)throttle; + +// Turns on throttling automatically when WWAN is connected using a custom limit, and turns it off automatically when it isn't ++ (void)throttleBandwidthForWWANUsingLimit:(unsigned long)limit; + +#pragma mark reachability + +// Returns YES when an iPhone OS device is connected via WWAN, false when connected via WIFI or not connected ++ (BOOL)isNetworkReachableViaWWAN; + +#endif + +#pragma mark queue + +// Returns the shared queue ++ (NSOperationQueue *)sharedQueue; + +#pragma mark cache + ++ (void)setDefaultCache:(id )cache; ++ (id )defaultCache; + +// Returns the maximum amount of data we can read as part of the current measurement period, and sleeps this thread if our allowance is used up ++ (unsigned long)maxUploadReadLength; + +#pragma mark network activity + ++ (BOOL)isNetworkInUse; + ++ (void)setShouldUpdateNetworkActivityIndicator:(BOOL)shouldUpdate; + +// Shows the network activity spinner thing on iOS. You may wish to override this to do something else in Mac projects ++ (void)showNetworkActivityIndicator; + +// Hides the network activity spinner thing on iOS ++ (void)hideNetworkActivityIndicator; + +#pragma mark miscellany + +// Used for generating Authorization header when using basic authentication when shouldPresentCredentialsBeforeChallenge is true +// And also by ASIS3Request ++ (NSString *)base64forData:(NSData *)theData; + +// Returns the expiration date for the request. +// Calculated from the Expires response header property, unless maxAge is non-zero or +// there exists a non-zero max-age property in the Cache-Control response header. ++ (NSDate *)expiryDateForRequest:(CTASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge; + +// Returns a date from a string in RFC1123 format ++ (NSDate *)dateFromRFC1123String:(NSString *)string; + + +// Used for detecting multitasking support at runtime (for backgrounding requests) +#if TARGET_OS_IPHONE ++ (BOOL)isMultitaskingSupported; +#endif + +#pragma mark threading behaviour + +// In the default implementation, all requests run in a single background thread +// Advanced users only: Override this method in a subclass for a different threading behaviour +// Eg: return [NSThread mainThread] to run all requests in the main thread +// Alternatively, you can create a thread on demand, or manage a pool of threads +// Threads returned by this method will need to run the runloop in default mode (eg CFRunLoopRun()) +// Requests will stop the runloop when they complete +// If you have multiple requests sharing the thread you'll need to restart the runloop when this happens ++ (NSThread *)threadForRequest:(CTASIHTTPRequest *)request; + + +#pragma mark === + +@property (atomic, retain) NSString *username; +@property (atomic, retain) NSString *password; +@property (atomic, retain) NSString *userAgentString; +@property (atomic, retain) NSString *domain; + +@property (atomic, retain) NSString *proxyUsername; +@property (atomic, retain) NSString *proxyPassword; +@property (atomic, retain) NSString *proxyDomain; + +@property (atomic, retain) NSString *proxyHost; +@property (atomic, assign) int proxyPort; +@property (atomic, retain) NSString *proxyType; + +@property (retain,setter=setURL:, nonatomic) NSURL *url; +@property (atomic, retain) NSURL *originalURL; +@property (assign, nonatomic) id delegate; +@property (retain, nonatomic) id queue; +@property (assign, nonatomic) id uploadProgressDelegate; +@property (assign, nonatomic) id downloadProgressDelegate; +@property (atomic, assign) BOOL useKeychainPersistence; +@property (atomic, assign) BOOL useSessionPersistence; +@property (atomic, retain) NSString *downloadDestinationPath; +@property (atomic, retain) NSString *temporaryFileDownloadPath; +@property (atomic, retain) NSString *temporaryUncompressedDataDownloadPath; +@property (atomic, assign) SEL didStartSelector; +@property (atomic, assign) SEL didReceiveResponseHeadersSelector; +@property (atomic, assign) SEL willRedirectSelector; +@property (atomic, assign) SEL didFinishSelector; +@property (atomic, assign) SEL didFailSelector; +@property (atomic, assign) SEL didReceiveDataSelector; +@property (atomic, retain,readonly) NSString *authenticationRealm; +@property (atomic, retain,readonly) NSString *proxyAuthenticationRealm; +@property (atomic, retain) NSError *error; +@property (atomic, assign,readonly) BOOL complete; +@property (atomic, retain) NSDictionary *responseHeaders; +@property (atomic, retain) NSMutableDictionary *requestHeaders; +@property (atomic, retain) NSMutableArray *requestCookies; +@property (atomic, retain,readonly) NSArray *responseCookies; +@property (atomic, assign) BOOL useCookiePersistence; +@property (atomic, retain) NSDictionary *requestCredentials; +@property (atomic, retain) NSDictionary *proxyCredentials; +@property (atomic, assign,readonly) int responseStatusCode; +@property (atomic, retain,readonly) NSString *responseStatusMessage; +@property (atomic, retain) NSMutableData *rawResponseData; +@property (atomic, assign) NSTimeInterval timeOutSeconds; +@property (retain, nonatomic) NSString *requestMethod; +@property (atomic, retain) NSMutableData *postBody; +@property (atomic, assign) unsigned long long contentLength; +@property (atomic, assign) unsigned long long postLength; +@property (atomic, assign) BOOL shouldResetDownloadProgress; +@property (atomic, assign) BOOL shouldResetUploadProgress; +@property (atomic, assign) CTASIHTTPRequest *mainRequest; +@property (atomic, assign) BOOL showAccurateProgress; +@property (atomic, assign) unsigned long long totalBytesRead; +@property (atomic, assign) unsigned long long totalBytesSent; +@property (atomic, assign) NSStringEncoding defaultResponseEncoding; +@property (atomic, assign) NSStringEncoding responseEncoding; +@property (atomic, assign) BOOL allowCompressedResponse; +@property (atomic, assign) BOOL allowResumeForFileDownloads; +@property (atomic, retain) NSDictionary *userInfo; +@property (atomic, assign) NSInteger tag; +@property (atomic, retain) NSString *postBodyFilePath; +@property (atomic, assign) BOOL shouldStreamPostDataFromDisk; +@property (atomic, assign) BOOL didCreateTemporaryPostDataFile; +@property (atomic, assign) BOOL useHTTPVersionOne; +@property (atomic, assign, readonly) unsigned long long partialDownloadSize; +@property (atomic, assign) BOOL shouldRedirect; +@property (atomic, assign) BOOL validatesSecureCertificate; +@property (atomic, assign) BOOL shouldCompressRequestBody; +@property (atomic, retain) NSURL *PACurl; +@property (atomic, retain) NSString *authenticationScheme; +@property (atomic, retain) NSString *proxyAuthenticationScheme; +@property (atomic, assign) BOOL shouldPresentAuthenticationDialog; +@property (atomic, assign) BOOL shouldPresentProxyAuthenticationDialog; +@property (atomic, assign, readonly) ASIAuthenticationState authenticationNeeded; +@property (atomic, assign) BOOL shouldPresentCredentialsBeforeChallenge; +@property (atomic, assign, readonly) int authenticationRetryCount; +@property (atomic, assign, readonly) int proxyAuthenticationRetryCount; +@property (atomic, assign) BOOL haveBuiltRequestHeaders; +@property (assign, nonatomic) BOOL haveBuiltPostBody; +@property (atomic, assign, readonly) BOOL inProgress; +@property (atomic, assign) int numberOfTimesToRetryOnTimeout; +@property (atomic, assign, readonly) int retryCount; +@property (atomic, assign) BOOL shouldAttemptPersistentConnection; +@property (atomic, atomic, assign) NSTimeInterval persistentConnectionTimeoutSeconds; +@property (atomic, assign) BOOL shouldUseRFC2616RedirectBehaviour; +@property (atomic, assign, readonly) BOOL connectionCanBeReused; +@property (atomic, retain, readonly) NSNumber *requestID; +@property (atomic, assign) id downloadCache; +@property (atomic, assign) CTASICachePolicy cachePolicy; +@property (atomic, assign) CTASICacheStoragePolicy cacheStoragePolicy; +@property (atomic, assign, readonly) BOOL didUseCachedResponse; +@property (atomic, assign) NSTimeInterval secondsToCache; +@property (atomic, retain) NSArray *clientCertificates; +#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 +@property (atomic, assign) BOOL shouldContinueWhenAppEntersBackground; +#endif +@property (atomic, retain) CTASIDataDecompressor *dataDecompressor; +@property (atomic, assign) BOOL shouldWaitToInflateCompressedResponses; + +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASIHTTPRequest.m b/core/Frameworks/asi-http-request/Classes/CTASIHTTPRequest.m new file mode 100644 index 00000000..304bbfe3 --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASIHTTPRequest.m @@ -0,0 +1,5141 @@ +// +// CTASIHTTPRequest.m +// +// Created by Ben Copsey on 04/10/2007. +// Copyright 2007-2011 All-Seeing Interactive. All rights reserved. +// +// A guide to the main features is available at: +// http://allseeing-i.com/CTASIHTTPRequest +// +// Portions are based on the ImageClient example from Apple: +// See: http://developer.apple.com/samplecode/ImageClient/listing37.html +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions + +#import "CTASIHTTPRequest.h" + +#if TARGET_OS_IPHONE +#import "CTReachability.h" +#import "CTASIAuthenticationDialog.h" +#import +#else +#import +#endif +#import "CTASIInputStream.h" +#import "CTASIDataDecompressor.h" +#import "CTASIDataCompressor.h" + +// Automatically set on build +NSString *ASIHTTPRequestVersion = @"v1.8.1-61 2011-09-19"; + +static NSString *defaultUserAgent = nil; + +NSString* const CTNetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain"; + +static NSString *ASIHTTPRequestRunLoopMode = @"ASIHTTPRequestRunLoopMode"; + +static const CFOptionFlags kNetworkEvents = kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred; + +// In memory caches of credentials, used on when useSessionPersistence is YES +static NSMutableArray *sessionCredentialsStore = nil; +static NSMutableArray *sessionProxyCredentialsStore = nil; + +// This lock mediates access to session credentials +static NSRecursiveLock *sessionCredentialsLock = nil; + +// We keep track of cookies we have received here so we can remove them from the sharedHTTPCookieStorage later +static NSMutableArray *sessionCookies = nil; + +// The number of times we will allow requests to redirect before we fail with a redirection error +const int RedirectionLimit = 5; + +// The default number of seconds to use for a timeout +static NSTimeInterval defaultTimeOutSeconds = 10; + +static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventType type, void *clientCallBackInfo) { + [((CTASIHTTPRequest *)clientCallBackInfo) handleNetworkEvent: type]; +} + +// This lock prevents the operation from being cancelled while it is trying to update the progress, and vice versa +static NSRecursiveLock *progressLock; + +static NSError *ASIRequestCancelledError; +static NSError *ASIRequestTimedOutError; +static NSError *ASIAuthenticationError; +static NSError *ASIUnableToCreateRequestError; +static NSError *ASITooMuchRedirectionError; + +static NSMutableArray *bandwidthUsageTracker = nil; +static unsigned long averageBandwidthUsedPerSecond = 0; + +// These are used for queuing persistent connections on the same connection + +// Incremented every time we specify we want a new connection +static unsigned int nextConnectionNumberToCreate = 0; + +// An array of connectionInfo dictionaries. +// When attempting a persistent connection, we look here to try to find an existing connection to the same server that is currently not in use +static NSMutableArray *persistentConnectionsPool = nil; + +// Mediates access to the persistent connections pool +static NSRecursiveLock *connectionsLock = nil; + +// Each request gets a new id, we store this rather than a ref to the request itself in the connectionInfo dictionary. +// We do this so we don't have to keep the request around while we wait for the connection to expire +static unsigned int nextRequestID = 0; + +// Records how much bandwidth all requests combined have used in the last second +static unsigned long bandwidthUsedInLastSecond = 0; + +// A date one second in the future from the time it was created +static NSDate *bandwidthMeasurementDate = nil; + +// Since throttling variables are shared among all requests, we'll use a lock to mediate access +static NSLock *bandwidthThrottlingLock = nil; + +// the maximum number of bytes that can be transmitted in one second +static unsigned long maxBandwidthPerSecond = 0; + +// A default figure for throttling bandwidth on mobile devices +unsigned long const ASIWWANBandwidthThrottleAmount = 14800; + +#if TARGET_OS_IPHONE +// YES when bandwidth throttling is active +// This flag does not denote whether throttling is turned on - rather whether it is currently in use +// It will be set to NO when throttling was turned on with setShouldThrottleBandwidthForWWAN, but a WI-FI connection is active +static BOOL isBandwidthThrottled = NO; + +// When YES, bandwidth will be automatically throttled when using WWAN (3G/Edge/GPRS) +// Wifi will not be throttled +static BOOL shouldThrottleBandwidthForWWANOnly = NO; +#endif + +// Mediates access to the session cookies so requests +static NSRecursiveLock *sessionCookiesLock = nil; + +// This lock ensures delegates only receive one notification that authentication is required at once +// When using ASIAuthenticationDialogs, it also ensures only one dialog is shown at once +// If a request can't acquire the lock immediately, it means a dialog is being shown or a delegate is handling the authentication challenge +// Once it gets the lock, it will try to look for existing credentials again rather than showing the dialog / notifying the delegate +// This is so it can make use of any credentials supplied for the other request, if they are appropriate +static NSRecursiveLock *delegateAuthenticationLock = nil; + +// When throttling bandwidth, Set to a date in future that we will allow all requests to wake up and reschedule their streams +static NSDate *throttleWakeUpTime = nil; + +static id defaultCache = nil; + +// Used for tracking when requests are using the network +static unsigned int runningRequestCount = 0; + +// You can use [CTASIHTTPRequest setShouldUpdateNetworkActivityIndicator:NO] if you want to manage it yourself +// Alternatively, override showNetworkActivityIndicator / hideNetworkActivityIndicator +// By default this does nothing on Mac OS X, but again override the above methods for a different behaviour +static BOOL shouldUpdateNetworkActivityIndicator = YES; + +// The thread all requests will run on +// Hangs around forever, but will be blocked unless there are requests underway +static NSThread *networkThread = nil; + +static NSOperationQueue *sharedQueue = nil; + +// Private stuff +@interface CTASIHTTPRequest () + +- (void)cancelLoad; + +- (void)destroyReadStream; +- (void)scheduleReadStream; +- (void)unscheduleReadStream; + +- (BOOL)willAskDelegateForCredentials; +- (BOOL)willAskDelegateForProxyCredentials; +- (void)askDelegateForProxyCredentials; +- (void)askDelegateForCredentials; +- (void)failAuthentication; + ++ (void)measureBandwidthUsage; ++ (void)recordBandwidthUsage; + +- (void)startRequest; +- (void)updateStatus:(NSTimer *)timer; +- (void)checkRequestStatus; +- (void)reportFailure; +- (void)reportFinished; +- (void)markAsFinished; +- (void)performRedirect; +- (BOOL)shouldTimeOut; +- (BOOL)willRedirect; +- (BOOL)willAskDelegateToConfirmRedirect; + ++ (void)performInvocation:(NSInvocation *)invocation onTarget:(id *)target releasingObject:(id)objectToRelease; ++ (void)hideNetworkActivityIndicatorAfterDelay; ++ (void)hideNetworkActivityIndicatorIfNeeeded; ++ (void)runRequests; + +// Handling Proxy autodetection and PAC file downloads +- (BOOL)configureProxies; +- (void)fetchPACFile; +- (void)finishedDownloadingPACFile:(CTASIHTTPRequest *)theRequest; +- (void)runPACScript:(NSString *)script; +- (void)timeOutPACRead; + +- (void)useDataFromCache; + +// Called to update the size of a partial download when starting a request, or retrying after a timeout +- (void)updatePartialDownloadSize; + +#if TARGET_OS_IPHONE ++ (void)registerForNetworkReachabilityNotifications; ++ (void)unsubscribeFromNetworkReachabilityNotifications; +// Called when the status of the network changes ++ (void)reachabilityChanged:(NSNotification *)note; +#endif + +#if NS_BLOCKS_AVAILABLE +- (void)performBlockOnMainThread:(ASIBasicBlock)block; +- (void)releaseBlocksOnMainThread; ++ (void)releaseBlocks:(NSArray *)blocks; +- (void)callBlock:(ASIBasicBlock)block; +#endif + + + + + +@property (assign) BOOL complete; +@property (retain) NSArray *responseCookies; +@property (assign) int responseStatusCode; +@property (retain, nonatomic) NSDate *lastActivityTime; + +@property (assign) unsigned long long partialDownloadSize; +@property (assign, nonatomic) unsigned long long uploadBufferSize; +@property (retain, nonatomic) NSOutputStream *postBodyWriteStream; +@property (retain, nonatomic) NSInputStream *postBodyReadStream; +@property (assign, nonatomic) unsigned long long lastBytesRead; +@property (assign, nonatomic) unsigned long long lastBytesSent; +@property (atomic, retain) NSRecursiveLock *cancelledLock; +@property (retain, nonatomic) NSOutputStream *fileDownloadOutputStream; +@property (retain, nonatomic) NSOutputStream *inflatedFileDownloadOutputStream; +@property (assign) int authenticationRetryCount; +@property (assign) int proxyAuthenticationRetryCount; +@property (assign, nonatomic) BOOL updatedProgress; +@property (assign, nonatomic) BOOL needsRedirect; +@property (assign, nonatomic) int redirectCount; +@property (retain, nonatomic) NSData *compressedPostBody; +@property (retain, nonatomic) NSString *compressedPostBodyFilePath; +@property (retain) NSString *authenticationRealm; +@property (retain) NSString *proxyAuthenticationRealm; +@property (retain) NSString *responseStatusMessage; +@property (assign) BOOL inProgress; +@property (assign) int retryCount; +@property (atomic, assign) BOOL willRetryRequest; +@property (assign) BOOL connectionCanBeReused; +@property (retain, nonatomic) NSMutableDictionary *connectionInfo; +@property (retain, nonatomic) NSInputStream *readStream; +@property (assign) ASIAuthenticationState authenticationNeeded; +@property (assign, nonatomic) BOOL readStreamIsScheduled; +@property (assign, nonatomic) BOOL downloadComplete; +@property (retain) NSNumber *requestID; +@property (assign, nonatomic) NSString *runLoopMode; +@property (retain, nonatomic) NSTimer *statusTimer; +@property (assign) BOOL didUseCachedResponse; +@property (retain, nonatomic) NSURL *redirectURL; + +@property (assign, nonatomic) BOOL isPACFileRequest; +@property (retain, nonatomic) CTASIHTTPRequest *PACFileRequest; +@property (retain, nonatomic) NSInputStream *PACFileReadStream; +@property (retain, nonatomic) NSMutableData *PACFileData; + +@property (assign, nonatomic, setter=setSynchronous:) BOOL isSynchronous; +@end + + +@implementation CTASIHTTPRequest + +#pragma mark init / dealloc + ++ (void)initialize +{ + if (self == [CTASIHTTPRequest class]) { + persistentConnectionsPool = [[NSMutableArray alloc] init]; + connectionsLock = [[NSRecursiveLock alloc] init]; + progressLock = [[NSRecursiveLock alloc] init]; + bandwidthThrottlingLock = [[NSLock alloc] init]; + sessionCookiesLock = [[NSRecursiveLock alloc] init]; + sessionCredentialsLock = [[NSRecursiveLock alloc] init]; + delegateAuthenticationLock = [[NSRecursiveLock alloc] init]; + bandwidthUsageTracker = [[NSMutableArray alloc] initWithCapacity:5]; + ASIRequestTimedOutError = [[NSError alloc] initWithDomain:CTNetworkRequestErrorDomain code:ASIRequestTimedOutErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request timed out", NSLocalizedDescriptionKey, nil]]; + ASIAuthenticationError = [[NSError alloc] initWithDomain:CTNetworkRequestErrorDomain code:ASIAuthenticationErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Authentication needed", NSLocalizedDescriptionKey, nil]]; + ASIRequestCancelledError = [[NSError alloc] initWithDomain:CTNetworkRequestErrorDomain code:ASIRequestCancelledErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request was cancelled", NSLocalizedDescriptionKey, nil]]; + ASIUnableToCreateRequestError = [[NSError alloc] initWithDomain:CTNetworkRequestErrorDomain code:ASIUnableToCreateRequestErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create request (bad url?)", NSLocalizedDescriptionKey, nil]]; + ASITooMuchRedirectionError = [[NSError alloc] initWithDomain:CTNetworkRequestErrorDomain code:ASITooMuchRedirectionErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request failed because it redirected too many times", NSLocalizedDescriptionKey, nil]]; + sharedQueue = [[NSOperationQueue alloc] init]; + [sharedQueue setMaxConcurrentOperationCount:4]; + + } +} + + +- (id)initWithURL:(NSURL *)newURL +{ + self = [self init]; + [self setRequestMethod:@"GET"]; + + [self setRunLoopMode:NSDefaultRunLoopMode]; + [self setShouldAttemptPersistentConnection:YES]; + [self setPersistentConnectionTimeoutSeconds:60.0]; + [self setShouldPresentCredentialsBeforeChallenge:YES]; + [self setShouldRedirect:YES]; + [self setShowAccurateProgress:YES]; + [self setShouldResetDownloadProgress:YES]; + [self setShouldResetUploadProgress:YES]; + [self setAllowCompressedResponse:YES]; + [self setShouldWaitToInflateCompressedResponses:YES]; + [self setDefaultResponseEncoding:NSISOLatin1StringEncoding]; + [self setShouldPresentProxyAuthenticationDialog:YES]; + + [self setTimeOutSeconds:[CTASIHTTPRequest defaultTimeOutSeconds]]; + [self setUseSessionPersistence:YES]; + [self setUseCookiePersistence:YES]; + [self setValidatesSecureCertificate:YES]; + [self setRequestCookies:[[[NSMutableArray alloc] init] autorelease]]; + [self setDidStartSelector:@selector(requestStarted:)]; + [self setDidReceiveResponseHeadersSelector:@selector(request:didReceiveResponseHeaders:)]; + [self setWillRedirectSelector:@selector(request:willRedirectToURL:)]; + [self setDidFinishSelector:@selector(requestFinished:)]; + [self setDidFailSelector:@selector(requestFailed:)]; + [self setDidReceiveDataSelector:@selector(request:didReceiveData:)]; + [self setURL:newURL]; + [self setCancelledLock:[[[NSRecursiveLock alloc] init] autorelease]]; + [self setDownloadCache:[[self class] defaultCache]]; + return self; +} + ++ (id)requestWithURL:(NSURL *)newURL +{ + return [[[self alloc] initWithURL:newURL] autorelease]; +} + ++ (id)requestWithURL:(NSURL *)newURL usingCache:(id )cache +{ + return [self requestWithURL:newURL usingCache:cache andCachePolicy:CTASIUseDefaultCachePolicy]; +} + ++ (id)requestWithURL:(NSURL *)newURL usingCache:(id )cache andCachePolicy:(CTASICachePolicy)policy +{ + CTASIHTTPRequest *request = [[[self alloc] initWithURL:newURL] autorelease]; + [request setDownloadCache:cache]; + [request setCachePolicy:policy]; + return request; +} + +- (void)dealloc +{ + [self setAuthenticationNeeded:ASINoAuthenticationNeededYet]; + if (requestAuthentication) { + CFRelease(requestAuthentication); + } + if (proxyAuthentication) { + CFRelease(proxyAuthentication); + } + if (request) { + CFRelease(request); + } + if (clientCertificateIdentity) { + CFRelease(clientCertificateIdentity); + } + [self cancelLoad]; + [redirectURL release]; + [statusTimer invalidate]; + [statusTimer release]; + [queue release]; + [userInfo release]; + [postBody release]; + [compressedPostBody release]; + [error release]; + [requestHeaders release]; + [requestCookies release]; + [downloadDestinationPath release]; + [temporaryFileDownloadPath release]; + [temporaryUncompressedDataDownloadPath release]; + [fileDownloadOutputStream release]; + [inflatedFileDownloadOutputStream release]; + [username release]; + [password release]; + [domain release]; + [authenticationRealm release]; + [authenticationScheme release]; + [requestCredentials release]; + [proxyHost release]; + [proxyType release]; + [proxyUsername release]; + [proxyPassword release]; + [proxyDomain release]; + [proxyAuthenticationRealm release]; + [proxyAuthenticationScheme release]; + [proxyCredentials release]; + [url release]; + [originalURL release]; + [lastActivityTime release]; + [responseCookies release]; + [rawResponseData release]; + [responseHeaders release]; + [requestMethod release]; + [cancelledLock release]; + [postBodyFilePath release]; + [compressedPostBodyFilePath release]; + [postBodyWriteStream release]; + [postBodyReadStream release]; + [PACurl release]; + [clientCertificates release]; + [responseStatusMessage release]; + [connectionInfo release]; + [requestID release]; + [dataDecompressor release]; + [userAgentString release]; + + #if NS_BLOCKS_AVAILABLE + [self releaseBlocksOnMainThread]; + #endif + + [super dealloc]; +} + +#if NS_BLOCKS_AVAILABLE +- (void)releaseBlocksOnMainThread +{ + NSMutableArray *blocks = [NSMutableArray array]; + if (completionBlock) { + [blocks addObject:completionBlock]; + [completionBlock release]; + completionBlock = nil; + } + if (failureBlock) { + [blocks addObject:failureBlock]; + [failureBlock release]; + failureBlock = nil; + } + if (startedBlock) { + [blocks addObject:startedBlock]; + [startedBlock release]; + startedBlock = nil; + } + if (headersReceivedBlock) { + [blocks addObject:headersReceivedBlock]; + [headersReceivedBlock release]; + headersReceivedBlock = nil; + } + if (bytesReceivedBlock) { + [blocks addObject:bytesReceivedBlock]; + [bytesReceivedBlock release]; + bytesReceivedBlock = nil; + } + if (bytesSentBlock) { + [blocks addObject:bytesSentBlock]; + [bytesSentBlock release]; + bytesSentBlock = nil; + } + if (downloadSizeIncrementedBlock) { + [blocks addObject:downloadSizeIncrementedBlock]; + [downloadSizeIncrementedBlock release]; + downloadSizeIncrementedBlock = nil; + } + if (uploadSizeIncrementedBlock) { + [blocks addObject:uploadSizeIncrementedBlock]; + [uploadSizeIncrementedBlock release]; + uploadSizeIncrementedBlock = nil; + } + if (dataReceivedBlock) { + [blocks addObject:dataReceivedBlock]; + [dataReceivedBlock release]; + dataReceivedBlock = nil; + } + if (proxyAuthenticationNeededBlock) { + [blocks addObject:proxyAuthenticationNeededBlock]; + [proxyAuthenticationNeededBlock release]; + proxyAuthenticationNeededBlock = nil; + } + if (authenticationNeededBlock) { + [blocks addObject:authenticationNeededBlock]; + [authenticationNeededBlock release]; + authenticationNeededBlock = nil; + } + if (requestRedirectedBlock) { + [blocks addObject:requestRedirectedBlock]; + [requestRedirectedBlock release]; + requestRedirectedBlock = nil; + } + [[self class] performSelectorOnMainThread:@selector(releaseBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]]; +} +// Always called on main thread ++ (void)releaseBlocks:(NSArray *)blocks +{ + // Blocks will be released when this method exits +} +#endif + + +#pragma mark setup request + +- (void)addRequestHeader:(NSString *)header value:(NSString *)value +{ + if (!requestHeaders) { + [self setRequestHeaders:[NSMutableDictionary dictionaryWithCapacity:1]]; + } + [requestHeaders setObject:value forKey:header]; +} + +// This function will be called either just before a request starts, or when postLength is needed, whichever comes first +// postLength must be set by the time this function is complete +- (void)buildPostBody +{ + + if ([self haveBuiltPostBody]) { + return; + } + + // Are we submitting the request body from a file on disk + if ([self postBodyFilePath]) { + + // If we were writing to the post body via appendPostData or appendPostDataFromFile, close the write stream + if ([self postBodyWriteStream]) { + [[self postBodyWriteStream] close]; + [self setPostBodyWriteStream:nil]; + } + + + NSString *path; + if ([self shouldCompressRequestBody]) { + if (![self compressedPostBodyFilePath]) { + [self setCompressedPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]]; + + NSError *err = nil; + if (![CTASIDataCompressor compressDataFromFile:[self postBodyFilePath] toFile:[self compressedPostBodyFilePath] error:&err]) { + [self failWithError:err]; + return; + } + } + path = [self compressedPostBodyFilePath]; + } else { + path = [self postBodyFilePath]; + } + NSError *err = nil; + [self setPostLength:[[[[[NSFileManager alloc] init] autorelease] attributesOfItemAtPath:path error:&err] fileSize]]; + if (err) { + [self failWithError:[NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to get attributes for file at path '%@'", path], NSLocalizedDescriptionKey, error, NSUnderlyingErrorKey, nil]]]; + return; + } + + // Otherwise, we have an in-memory request body + } else { + if ([self shouldCompressRequestBody]) { + NSError *err = nil; + NSData *compressedBody = [CTASIDataCompressor compressData:[self postBody] error:&err]; + if (err) { + [self failWithError:err]; + return; + } + [self setCompressedPostBody:compressedBody]; + [self setPostLength:[[self compressedPostBody] length]]; + } else { + [self setPostLength:[[self postBody] length]]; + } + } + + if ([self postLength] > 0) { + if ([requestMethod isEqualToString:@"GET"] || [requestMethod isEqualToString:@"DELETE"] || [requestMethod isEqualToString:@"HEAD"]) { + [self setRequestMethod:@"POST"]; + } + [self addRequestHeader:@"Content-Length" value:[NSString stringWithFormat:@"%llu",[self postLength]]]; + } + [self setHaveBuiltPostBody:YES]; + +} + +// Sets up storage for the post body +- (void)setupPostBody +{ + if ([self shouldStreamPostDataFromDisk]) { + if (![self postBodyFilePath]) { + [self setPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]]; + [self setDidCreateTemporaryPostDataFile:YES]; + } + if (![self postBodyWriteStream]) { + [self setPostBodyWriteStream:[[[NSOutputStream alloc] initToFileAtPath:[self postBodyFilePath] append:NO] autorelease]]; + [[self postBodyWriteStream] open]; + } + } else { + if (![self postBody]) { + [self setPostBody:[[[NSMutableData alloc] init] autorelease]]; + } + } +} + +- (void)appendPostData:(NSData *)data +{ + [self setupPostBody]; + if ([data length] == 0) { + return; + } + if ([self shouldStreamPostDataFromDisk]) { + [[self postBodyWriteStream] write:[data bytes] maxLength:[data length]]; + } else { + [[self postBody] appendData:data]; + } +} + +- (void)appendPostDataFromFile:(NSString *)file +{ + [self setupPostBody]; + NSInputStream *stream = [[[NSInputStream alloc] initWithFileAtPath:file] autorelease]; + [stream open]; + while ([stream hasBytesAvailable]) { + + unsigned char buffer[1024*256]; + NSInteger bytesRead = [stream read:buffer maxLength:sizeof(buffer)]; + if (bytesRead == 0) { + // 0 indicates that the end of the buffer was reached. + break; + } else if (bytesRead < 0) { + // A negative number means that the operation failed. + break; + } + if ([self shouldStreamPostDataFromDisk]) { + [[self postBodyWriteStream] write:buffer maxLength:(NSUInteger)bytesRead]; + } else { + [[self postBody] appendData:[NSData dataWithBytes:buffer length:(NSUInteger)bytesRead]]; + } + } + [stream close]; +} + +- (NSString *)requestMethod +{ + [[self cancelledLock] lock]; + NSString *m = requestMethod; + [[self cancelledLock] unlock]; + return m; +} + +- (void)setRequestMethod:(NSString *)newRequestMethod +{ + [[self cancelledLock] lock]; + if (requestMethod != newRequestMethod) { + [requestMethod release]; + requestMethod = [newRequestMethod retain]; + if ([requestMethod isEqualToString:@"POST"] || [requestMethod isEqualToString:@"PUT"] || [postBody length] || postBodyFilePath) { + [self setShouldAttemptPersistentConnection:NO]; + } + } + [[self cancelledLock] unlock]; +} + +- (NSURL *)url +{ + [[self cancelledLock] lock]; + NSURL *u = url; + [[self cancelledLock] unlock]; + return u; +} + + +- (void)setURL:(NSURL *)newURL +{ + [[self cancelledLock] lock]; + if ([newURL isEqual:[self url]]) { + [[self cancelledLock] unlock]; + return; + } + [url release]; + url = [newURL retain]; + if (requestAuthentication) { + CFRelease(requestAuthentication); + requestAuthentication = NULL; + } + if (proxyAuthentication) { + CFRelease(proxyAuthentication); + proxyAuthentication = NULL; + } + if (request) { + CFRelease(request); + request = NULL; + } + [self setRedirectURL:nil]; + [[self cancelledLock] unlock]; +} + +- (id)delegate +{ + [[self cancelledLock] lock]; + id d = delegate; + [[self cancelledLock] unlock]; + return d; +} + +- (void)setDelegate:(id)newDelegate +{ + [[self cancelledLock] lock]; + delegate = newDelegate; + [[self cancelledLock] unlock]; +} + +- (id)queue +{ + [[self cancelledLock] lock]; + id q = queue; + [[self cancelledLock] unlock]; + return q; +} + + +- (void)setQueue:(id)newQueue +{ + [[self cancelledLock] lock]; + if (newQueue != queue) { + [queue release]; + queue = [newQueue retain]; + } + [[self cancelledLock] unlock]; +} + +#pragma mark get information about this request + +// cancel the request - this must be run on the same thread as the request is running on +- (void)cancelOnRequestThread +{ + #if DEBUG_REQUEST_STATUS + ASI_DEBUG_LOG(@"[STATUS] Request cancelled: %@",self); + #endif + + [[self cancelledLock] lock]; + + if ([self isCancelled] || [self complete]) { + [[self cancelledLock] unlock]; + return; + } + [self failWithError:ASIRequestCancelledError]; + [self setComplete:YES]; + [self cancelLoad]; + + CFRetain(self); + [self willChangeValueForKey:@"isCancelled"]; + cancelled = YES; + [self didChangeValueForKey:@"isCancelled"]; + + [[self cancelledLock] unlock]; + CFRelease(self); +} + +- (void)cancel +{ + [self performSelector:@selector(cancelOnRequestThread) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO]; +} + +- (void)clearDelegatesAndCancel +{ + [[self cancelledLock] lock]; + + // Clear delegates + [self setDelegate:nil]; + [self setQueue:nil]; + [self setDownloadProgressDelegate:nil]; + [self setUploadProgressDelegate:nil]; + + #if NS_BLOCKS_AVAILABLE + // Clear blocks + [self releaseBlocksOnMainThread]; + #endif + + [[self cancelledLock] unlock]; + [self cancel]; +} + + +- (BOOL)isCancelled +{ + BOOL result; + + [[self cancelledLock] lock]; + result = cancelled; + [[self cancelledLock] unlock]; + + return result; +} + +// Call this method to get the received data as an NSString. Don't use for binary data! +- (NSString *)responseString +{ + NSData *data = [self responseData]; + if (!data) { + return nil; + } + + return [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:[self responseEncoding]] autorelease]; +} + +- (BOOL)isResponseCompressed +{ + NSString *encoding = [[self responseHeaders] objectForKey:@"Content-Encoding"]; + return encoding && [encoding rangeOfString:@"gzip"].location != NSNotFound; +} + +- (NSData *)responseData +{ + if ([self isResponseCompressed] && [self shouldWaitToInflateCompressedResponses]) { + return [CTASIDataDecompressor uncompressData:[self rawResponseData] error:NULL]; + } else { + return [self rawResponseData]; + } + return nil; +} + +#pragma mark running a request + +- (void)startSynchronous +{ +#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING + ASI_DEBUG_LOG(@"[STATUS] Starting synchronous request %@",self); +#endif + [self setSynchronous:YES]; + [self setRunLoopMode:ASIHTTPRequestRunLoopMode]; + [self setInProgress:YES]; + + if (![self isCancelled] && ![self complete]) { + [self main]; + while (!complete) { + [[NSRunLoop currentRunLoop] runMode:[self runLoopMode] beforeDate:[NSDate distantFuture]]; + } + } + + [self setInProgress:NO]; +} + +- (void)start +{ + [self setInProgress:YES]; + [self performSelector:@selector(main) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO]; +} + +- (void)startAsynchronous +{ +#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING + ASI_DEBUG_LOG(@"[STATUS] Starting asynchronous request %@",self); +#endif + [sharedQueue addOperation:self]; +} + +#pragma mark concurrency + +- (BOOL)isConcurrent +{ + return YES; +} + +- (BOOL)isFinished +{ + return finished; +} + +- (BOOL)isExecuting { + return [self inProgress]; +} + +#pragma mark request logic + +// Create the request +- (void)main +{ + @try { + + [[self cancelledLock] lock]; + + #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 + if ([CTASIHTTPRequest isMultitaskingSupported] && [self shouldContinueWhenAppEntersBackground]) { + if (!backgroundTask || backgroundTask == UIBackgroundTaskInvalid) { + backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ + // Synchronize the cleanup call on the main thread in case + // the task actually finishes at around the same time. + dispatch_async(dispatch_get_main_queue(), ^{ + if (backgroundTask != UIBackgroundTaskInvalid) + { + [[UIApplication sharedApplication] endBackgroundTask:backgroundTask]; + backgroundTask = UIBackgroundTaskInvalid; + [self cancel]; + } + }); + }]; + } + } + #endif + + + // A HEAD request generated by an CTASINetworkQueue may have set the error already. If so, we should not proceed. + if ([self error]) { + [self setComplete:YES]; + [self markAsFinished]; + return; + } + + [self setComplete:NO]; + [self setDidUseCachedResponse:NO]; + + if (![self url]) { + [self failWithError:ASIUnableToCreateRequestError]; + return; + } + + // Must call before we create the request so that the request method can be set if needs be + if (![self mainRequest]) { + [self buildPostBody]; + } + + if (![[self requestMethod] isEqualToString:@"GET"]) { + [self setDownloadCache:nil]; + } + + + // If we're redirecting, we'll already have a CFHTTPMessageRef + if (request) { + CFRelease(request); + } + + // Create a new HTTP request. + request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)[self requestMethod], (CFURLRef)[self url], [self useHTTPVersionOne] ? kCFHTTPVersion1_0 : kCFHTTPVersion1_1); + if (!request) { + [self failWithError:ASIUnableToCreateRequestError]; + return; + } + + //If this is a HEAD request generated by an CTASINetworkQueue, we need to let the main request generate its headers first so we can use them + if ([self mainRequest]) { + [[self mainRequest] buildRequestHeaders]; + } + + // Even if this is a HEAD request with a mainRequest, we still need to call to give subclasses a chance to add their own to HEAD requests (ASIS3Request does this) + [self buildRequestHeaders]; + + if ([self downloadCache]) { + + // If this request should use the default policy, set its policy to the download cache's default policy + if (![self cachePolicy]) { + [self setCachePolicy:[[self downloadCache] defaultCachePolicy]]; + } + + // If have have cached data that is valid for this request, use that and stop + if ([[self downloadCache] canUseCachedDataForRequest:self]) { + [self useDataFromCache]; + return; + } + + // If cached data is stale, or we have been told to ask the server if it has been modified anyway, we need to add headers for a conditional GET + if ([self cachePolicy] & (CTASIAskServerIfModifiedWhenStaleCachePolicy | CTASIAskServerIfModifiedCachePolicy)) { + + NSDictionary *cachedHeaders = [[self downloadCache] cachedResponseHeadersForURL:[self url]]; + if (cachedHeaders) { + NSString *etag = [cachedHeaders objectForKey:@"Etag"]; + if (etag) { + [[self requestHeaders] setObject:etag forKey:@"If-None-Match"]; + } + NSString *lastModified = [cachedHeaders objectForKey:@"Last-Modified"]; + if (lastModified) { + [[self requestHeaders] setObject:lastModified forKey:@"If-Modified-Since"]; + } + } + } + } + + [self applyAuthorizationHeader]; + + + NSString *header; + for (header in [self requestHeaders]) { + CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)header, (CFStringRef)[[self requestHeaders] objectForKey:header]); + } + + // If we immediately have access to proxy settings, start the request + // Otherwise, we'll start downloading the proxy PAC file, and call startRequest once that process is complete + if ([self configureProxies]) { + [self startRequest]; + } + + } @catch (NSException *exception) { + NSError *underlyingError = [NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[exception userInfo]]; + [self failWithError:[NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[exception name], NSLocalizedDescriptionKey, [exception reason], NSLocalizedFailureReasonErrorKey, underlyingError, NSUnderlyingErrorKey, nil]]]; + + } @finally { + [[self cancelledLock] unlock]; + } +} + +- (void)applyAuthorizationHeader +{ + // Do we want to send credentials before we are asked for them? + if (![self shouldPresentCredentialsBeforeChallenge]) { + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will not send credentials to the server until it asks for them",self); + #endif + return; + } + + NSDictionary *credentials = nil; + + // Do we already have an auth header? + if (![[self requestHeaders] objectForKey:@"Authorization"]) { + + // If we have basic authentication explicitly set and a username and password set on the request, add a basic auth header + if ([self username] && [self password] && [[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) { + [self addBasicAuthenticationHeaderWithUsername:[self username] andPassword:[self password]]; + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ has a username and password set, and was manually configured to use BASIC. Will send credentials without waiting for an authentication challenge",self); + #endif + + } else { + + // See if we have any cached credentials we can use in the session store + if ([self useSessionPersistence]) { + credentials = [self findSessionAuthenticationCredentials]; + + if (credentials) { + + // When the Authentication key is set, the credentials were stored after an authentication challenge, so we can let CFNetwork apply them + // (credentials for Digest and NTLM will always be stored like this) + if ([credentials objectForKey:@"Authentication"]) { + + // If we've already talked to this server and have valid credentials, let's apply them to the request + if (CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) { + [self setAuthenticationScheme:[credentials objectForKey:@"AuthenticationScheme"]]; + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ found cached credentials (%@), will reuse without waiting for an authentication challenge",self,[credentials objectForKey:@"AuthenticationScheme"]); + #endif + } else { + [[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]]; + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Failed to apply cached credentials to request %@. These will be removed from the session store, and this request will wait for an authentication challenge",self); + #endif + } + + // If the Authentication key is not set, these credentials were stored after a username and password set on a previous request passed basic authentication + // When this happens, we'll need to create the Authorization header ourselves + } else { + NSDictionary *usernameAndPassword = [credentials objectForKey:@"Credentials"]; + [self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]]; + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ found cached BASIC credentials from a previous request. Will send credentials without waiting for an authentication challenge",self); + #endif + } + } + } + } + } + + // Apply proxy authentication credentials + if ([self useSessionPersistence]) { + credentials = [self findSessionProxyAuthenticationCredentials]; + if (credentials) { + if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) { + [[self class] removeProxyAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]]; + } + } + } +} + +- (void)applyCookieHeader +{ + // Add cookies from the persistent (mac os global) store + if ([self useCookiePersistence]) { + NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[[self url] absoluteURL]]; + if (cookies) { + [[self requestCookies] addObjectsFromArray:cookies]; + } + } + + // Apply request cookies + NSArray *cookies; + if ([self mainRequest]) { + cookies = [[self mainRequest] requestCookies]; + } else { + cookies = [self requestCookies]; + } + if ([cookies count] > 0) { + NSHTTPCookie *cookie; + NSString *cookieHeader = nil; + for (cookie in cookies) { + if (!cookieHeader) { + cookieHeader = [NSString stringWithFormat: @"%@=%@",[cookie name],[cookie value]]; + } else { + cookieHeader = [NSString stringWithFormat: @"%@; %@=%@",cookieHeader,[cookie name],[cookie value]]; + } + } + if (cookieHeader) { + [self addRequestHeader:@"Cookie" value:cookieHeader]; + } + } +} + +- (void)buildRequestHeaders +{ + if ([self haveBuiltRequestHeaders]) { + return; + } + [self setHaveBuiltRequestHeaders:YES]; + + if ([self mainRequest]) { + for (NSString *header in [[self mainRequest] requestHeaders]) { + [self addRequestHeader:header value:[[[self mainRequest] requestHeaders] valueForKey:header]]; + } + return; + } + + [self applyCookieHeader]; + + // Build and set the user agent string if the request does not already have a custom user agent specified + if (![[self requestHeaders] objectForKey:@"User-Agent"]) { + NSString *tempUserAgentString = [self userAgentString]; + if (!tempUserAgentString) { + tempUserAgentString = [CTASIHTTPRequest defaultUserAgentString]; + } + if (tempUserAgentString) { + [self addRequestHeader:@"User-Agent" value:tempUserAgentString]; + } + } + + + // Accept a compressed response + if ([self allowCompressedResponse]) { + [self addRequestHeader:@"Accept-Encoding" value:@"gzip"]; + } + + // Configure a compressed request body + if ([self shouldCompressRequestBody]) { + [self addRequestHeader:@"Content-Encoding" value:@"gzip"]; + } + + // Should this request resume an existing download? + [self updatePartialDownloadSize]; + if ([self partialDownloadSize]) { + [self addRequestHeader:@"Range" value:[NSString stringWithFormat:@"bytes=%llu-",[self partialDownloadSize]]]; + } +} + +- (void)updatePartialDownloadSize +{ + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + if ([self allowResumeForFileDownloads] && [self downloadDestinationPath] && [self temporaryFileDownloadPath] && [fileManager fileExistsAtPath:[self temporaryFileDownloadPath]]) { + NSError *err = nil; + [self setPartialDownloadSize:[[fileManager attributesOfItemAtPath:[self temporaryFileDownloadPath] error:&err] fileSize]]; + if (err) { + [self failWithError:[NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to get attributes for file at path '%@'", [self temporaryFileDownloadPath]], NSLocalizedDescriptionKey, error, NSUnderlyingErrorKey, nil]]]; + return; + } + } +} + +- (void)startRequest +{ + if ([self isCancelled]) { + return; + } + + [self performSelectorOnMainThread:@selector(requestStarted) withObject:nil waitUntilDone:[NSThread isMainThread]]; + + [self setDownloadComplete:NO]; + [self setComplete:NO]; + [self setTotalBytesRead:0]; + [self setLastBytesRead:0]; + + if ([self redirectCount] == 0) { + [self setOriginalURL:[self url]]; + } + + // If we're retrying a request, let's remove any progress we made + if ([self lastBytesSent] > 0) { + [self removeUploadProgressSoFar]; + } + + [self setLastBytesSent:0]; + [self setContentLength:0]; + [self setResponseHeaders:nil]; + if (![self downloadDestinationPath]) { + [self setRawResponseData:[[[NSMutableData alloc] init] autorelease]]; + } + + + // + // Create the stream for the request + // + + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + [self setReadStreamIsScheduled:NO]; + + // Do we need to stream the request body from disk + if ([self shouldStreamPostDataFromDisk] && [self postBodyFilePath] && [fileManager fileExistsAtPath:[self postBodyFilePath]]) { + + // Are we gzipping the request body? + if ([self compressedPostBodyFilePath] && [fileManager fileExistsAtPath:[self compressedPostBodyFilePath]]) { + [self setPostBodyReadStream:[CTASIInputStream inputStreamWithFileAtPath:[self compressedPostBodyFilePath] request:self]]; + } else { + [self setPostBodyReadStream:[CTASIInputStream inputStreamWithFileAtPath:[self postBodyFilePath] request:self]]; + } + [self setReadStream:[NSMakeCollectable(CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream])) autorelease]]; + } else { + + // If we have a request body, we'll stream it from memory using our custom stream, so that we can measure bandwidth use and it can be bandwidth-throttled if necessary + if ([self postBody] && [[self postBody] length] > 0) { + if ([self shouldCompressRequestBody] && [self compressedPostBody]) { + [self setPostBodyReadStream:[CTASIInputStream inputStreamWithData:[self compressedPostBody] request:self]]; + } else if ([self postBody]) { + [self setPostBodyReadStream:[CTASIInputStream inputStreamWithData:[self postBody] request:self]]; + } + [self setReadStream:[NSMakeCollectable(CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream])) autorelease]]; + + } else { + [self setReadStream:[NSMakeCollectable(CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request)) autorelease]]; + } + } + + if (![self readStream]) { + [self failWithError:[NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create read stream", NSLocalizedDescriptionKey, nil]]]; + return; + } + + + + + // + // Handle SSL certificate settings + // + + if([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) { + + // Tell CFNetwork not to validate SSL certificates + if (![self validatesSecureCertificate]) { + // see: http://iphonedevelopment.blogspot.com/2010/05/nsstream-tcp-and-ssl.html + + NSDictionary *sslProperties = [[NSDictionary alloc] initWithObjectsAndKeys: + [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates, + [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot, + [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain, + kCFNull,kCFStreamSSLPeerName, + nil]; + + CFReadStreamSetProperty((CFReadStreamRef)[self readStream], + kCFStreamPropertySSLSettings, + (CFTypeRef)sslProperties); + [sslProperties release]; + } + + // Tell CFNetwork to use a client certificate + if (clientCertificateIdentity) { + NSMutableDictionary *sslProperties = [NSMutableDictionary dictionaryWithCapacity:1]; + + NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[clientCertificates count]+1]; + + // The first object in the array is our SecIdentityRef + [certificates addObject:(id)clientCertificateIdentity]; + + // If we've added any additional certificates, add them too + for (id cert in clientCertificates) { + [certificates addObject:cert]; + } + + [sslProperties setObject:certificates forKey:(NSString *)kCFStreamSSLCertificates]; + + CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings, sslProperties); + } + + } + + // + // Handle proxy settings + // + + if ([self proxyHost] && [self proxyPort]) { + NSString *hostKey; + NSString *portKey; + + if (![self proxyType]) { + [self setProxyType:(NSString *)kCFProxyTypeHTTP]; + } + + if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) { + hostKey = (NSString *)kCFStreamPropertySOCKSProxyHost; + portKey = (NSString *)kCFStreamPropertySOCKSProxyPort; + } else { + hostKey = (NSString *)kCFStreamPropertyHTTPProxyHost; + portKey = (NSString *)kCFStreamPropertyHTTPProxyPort; + if ([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) { + hostKey = (NSString *)kCFStreamPropertyHTTPSProxyHost; + portKey = (NSString *)kCFStreamPropertyHTTPSProxyPort; + } + } + NSMutableDictionary *proxyToUse = [NSMutableDictionary dictionaryWithObjectsAndKeys:[self proxyHost],hostKey,[NSNumber numberWithInt:[self proxyPort]],portKey,nil]; + + if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) { + CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySOCKSProxy, proxyToUse); + } else { + CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPProxy, proxyToUse); + } + } + + + // + // Handle persistent connections + // + + [CTASIHTTPRequest expirePersistentConnections]; + + [connectionsLock lock]; + + + if (![[self url] host] || ![[self url] scheme]) { + [self setConnectionInfo:nil]; + [self setShouldAttemptPersistentConnection:NO]; + } + + // Will store the old stream that was using this connection (if there was one) so we can clean it up once we've opened our own stream + NSInputStream *oldStream = nil; + + // Use a persistent connection if possible + if ([self shouldAttemptPersistentConnection]) { + + + // If we are redirecting, we will re-use the current connection only if we are connecting to the same server + if ([self connectionInfo]) { + + if (![[[self connectionInfo] objectForKey:@"host"] isEqualToString:[[self url] host]] || ![[[self connectionInfo] objectForKey:@"scheme"] isEqualToString:[[self url] scheme]] || [(NSNumber *)[[self connectionInfo] objectForKey:@"port"] intValue] != [[[self url] port] intValue]) { + [self setConnectionInfo:nil]; + + // Check if we should have expired this connection + } else if ([[[self connectionInfo] objectForKey:@"expires"] timeIntervalSinceNow] < 0) { + #if DEBUG_PERSISTENT_CONNECTIONS + ASI_DEBUG_LOG(@"[CONNECTION] Not re-using connection #%i because it has expired",[[[self connectionInfo] objectForKey:@"id"] intValue]); + #endif + [persistentConnectionsPool removeObject:[self connectionInfo]]; + [self setConnectionInfo:nil]; + + } else if ([[self connectionInfo] objectForKey:@"request"] != nil) { + //Some other request reused this connection already - we'll have to create a new one + #if DEBUG_PERSISTENT_CONNECTIONS + ASI_DEBUG_LOG(@"%@ - Not re-using connection #%i for request #%i because it is already used by request #%i",self,[[[self connectionInfo] objectForKey:@"id"] intValue],[[self requestID] intValue],[[[self connectionInfo] objectForKey:@"request"] intValue]); + #endif + [self setConnectionInfo:nil]; + } + } + + + + if (![self connectionInfo] && [[self url] host] && [[self url] scheme]) { // We must have a proper url with a host and scheme, or this will explode + + // Look for a connection to the same server in the pool + for (NSMutableDictionary *existingConnection in persistentConnectionsPool) { + if (![existingConnection objectForKey:@"request"] && [[existingConnection objectForKey:@"host"] isEqualToString:[[self url] host]] && [[existingConnection objectForKey:@"scheme"] isEqualToString:[[self url] scheme]] && [(NSNumber *)[existingConnection objectForKey:@"port"] intValue] == [[[self url] port] intValue]) { + [self setConnectionInfo:existingConnection]; + } + } + } + + if ([[self connectionInfo] objectForKey:@"stream"]) { + oldStream = [[[self connectionInfo] objectForKey:@"stream"] retain]; + + } + + // No free connection was found in the pool matching the server/scheme/port we're connecting to, we'll need to create a new one + if (![self connectionInfo]) { + [self setConnectionInfo:[NSMutableDictionary dictionary]]; + nextConnectionNumberToCreate++; + [[self connectionInfo] setObject:[NSNumber numberWithInt:(int)nextConnectionNumberToCreate] forKey:@"id"]; + [[self connectionInfo] setObject:[[self url] host] forKey:@"host"]; + [[self connectionInfo] setObject:[NSNumber numberWithInt:[[[self url] port] intValue]] forKey:@"port"]; + [[self connectionInfo] setObject:[[self url] scheme] forKey:@"scheme"]; + [persistentConnectionsPool addObject:[self connectionInfo]]; + } + + // If we are retrying this request, it will already have a requestID + if (![self requestID]) { + nextRequestID++; + [self setRequestID:[NSNumber numberWithUnsignedInt:nextRequestID]]; + } + [[self connectionInfo] setObject:[self requestID] forKey:@"request"]; + [[self connectionInfo] setObject:[self readStream] forKey:@"stream"]; + CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue); + + #if DEBUG_PERSISTENT_CONNECTIONS + ASI_DEBUG_LOG(@"[CONNECTION] Request #%@ will use connection #%i",[self requestID],[[[self connectionInfo] objectForKey:@"id"] intValue]); + #endif + + + // Tag the stream with an id that tells it which connection to use behind the scenes + // See http://lists.apple.com/archives/macnetworkprog/2008/Dec/msg00001.html for details on this approach + + CFReadStreamSetProperty((CFReadStreamRef)[self readStream], CFSTR("ASIStreamID"), [[self connectionInfo] objectForKey:@"id"]); + + } else { + #if DEBUG_PERSISTENT_CONNECTIONS + ASI_DEBUG_LOG(@"[CONNECTION] Request %@ will not use a persistent connection",self); + #endif + } + + [connectionsLock unlock]; + + // Schedule the stream + if (![self readStreamIsScheduled] && (!throttleWakeUpTime || [throttleWakeUpTime timeIntervalSinceDate:[NSDate date]] < 0)) { + [self scheduleReadStream]; + } + + BOOL streamSuccessfullyOpened = NO; + + + // Start the HTTP connection + CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL}; + if (CFReadStreamSetClient((CFReadStreamRef)[self readStream], kNetworkEvents, ReadStreamClientCallBack, &ctxt)) { + if (CFReadStreamOpen((CFReadStreamRef)[self readStream])) { + streamSuccessfullyOpened = YES; + } + } + + // Here, we'll close the stream that was previously using this connection, if there was one + // We've kept it open until now (when we've just opened a new stream) so that the new stream can make use of the old connection + // http://lists.apple.com/archives/Macnetworkprog/2006/Mar/msg00119.html + if (oldStream) { + [oldStream close]; + [oldStream release]; + oldStream = nil; + } + + if (!streamSuccessfullyOpened) { + [self setConnectionCanBeReused:NO]; + [self destroyReadStream]; + [self failWithError:[NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to start HTTP connection", NSLocalizedDescriptionKey, nil]]]; + return; + } + + if (![self mainRequest]) { + if ([self shouldResetUploadProgress]) { + if ([self showAccurateProgress]) { + [self incrementUploadSizeBy:(long long)[self postLength]]; + } else { + [self incrementUploadSizeBy:1]; + } + [CTASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:0 ofTotal:1]; + } + if ([self shouldResetDownloadProgress] && ![self partialDownloadSize]) { + [CTASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:0 ofTotal:1]; + } + } + + + // Record when the request started, so we can timeout if nothing happens + [self setLastActivityTime:[NSDate date]]; + [self setStatusTimer:[NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(updateStatus:) userInfo:nil repeats:YES]]; + [[NSRunLoop currentRunLoop] addTimer:[self statusTimer] forMode:[self runLoopMode]]; +} + +- (void)setStatusTimer:(NSTimer *)timer +{ + CFRetain(self); + // We must invalidate the old timer here, not before we've created and scheduled a new timer + // This is because the timer may be the only thing retaining an asynchronous request + if (statusTimer && timer != statusTimer) { + [statusTimer invalidate]; + [statusTimer release]; + } + statusTimer = [timer retain]; + CFRelease(self); +} + +// This gets fired every 1/4 of a second to update the progress and work out if we need to timeout +- (void)updateStatus:(NSTimer*)timer +{ + [self checkRequestStatus]; + if (![self inProgress]) { + [self setStatusTimer:nil]; + } +} + +- (void)performRedirect +{ + [self setURL:[self redirectURL]]; + [self setComplete:YES]; + [self setNeedsRedirect:NO]; + [self setRedirectCount:[self redirectCount]+1]; + + if ([self redirectCount] > RedirectionLimit) { + // Some naughty / badly coded website is trying to force us into a redirection loop. This is not cool. + [self failWithError:ASITooMuchRedirectionError]; + [self setComplete:YES]; + } else { + // Go all the way back to the beginning and build the request again, so that we can apply any new cookies + [self main]; + } +} + +// Called by delegate to resume loading with a new url after the delegate received request:willRedirectToURL: +- (void)redirectToURL:(NSURL *)newURL +{ + [self setRedirectURL:newURL]; + [self performSelector:@selector(performRedirect) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO]; +} + +- (BOOL)shouldTimeOut +{ + NSTimeInterval secondsSinceLastActivity = [[NSDate date] timeIntervalSinceDate:lastActivityTime]; + // See if we need to timeout + if ([self readStream] && [self readStreamIsScheduled] && [self lastActivityTime] && [self timeOutSeconds] > 0 && secondsSinceLastActivity > [self timeOutSeconds]) { + + // We have no body, or we've sent more than the upload buffer size,so we can safely time out here + if ([self postLength] == 0 || ([self uploadBufferSize] > 0 && [self totalBytesSent] > [self uploadBufferSize])) { + return YES; + + // ***Black magic warning*** + // We have a body, but we've taken longer than timeOutSeconds to upload the first small chunk of data + // Since there's no reliable way to track upload progress for the first 32KB (iPhone) or 128KB (Mac) with CFNetwork, we'll be slightly more forgiving on the timeout, as there's a strong chance our connection is just very slow. + } else if (secondsSinceLastActivity > [self timeOutSeconds]*1.5) { + return YES; + } + } + return NO; +} + +- (void)checkRequestStatus +{ + // We won't let the request cancel while we're updating progress / checking for a timeout + [[self cancelledLock] lock]; + // See if our NSOperationQueue told us to cancel + if ([self isCancelled] || [self complete]) { + [[self cancelledLock] unlock]; + return; + } + + [self performThrottling]; + + if ([self shouldTimeOut]) { + // Do we need to auto-retry this request? + if ([self numberOfTimesToRetryOnTimeout] > [self retryCount]) { + + // If we are resuming a download, we may need to update the Range header to take account of data we've just downloaded + [self updatePartialDownloadSize]; + if ([self partialDownloadSize]) { + CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)@"Range", (CFStringRef)[NSString stringWithFormat:@"bytes=%llu-",[self partialDownloadSize]]); + } + [self setRetryCount:[self retryCount]+1]; + [self unscheduleReadStream]; + [[self cancelledLock] unlock]; + [self startRequest]; + return; + } + [self failWithError:ASIRequestTimedOutError]; + [self cancelLoad]; + [self setComplete:YES]; + [[self cancelledLock] unlock]; + return; + } + + // readStream will be null if we aren't currently running (perhaps we're waiting for a delegate to supply credentials) + if ([self readStream]) { + + // If we have a post body + if ([self postLength]) { + + [self setLastBytesSent:totalBytesSent]; + + // Find out how much data we've uploaded so far + [self setTotalBytesSent:[[NSMakeCollectable(CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPRequestBytesWrittenCount)) autorelease] unsignedLongLongValue]]; + if (totalBytesSent > lastBytesSent) { + + // We've uploaded more data, reset the timeout + [self setLastActivityTime:[NSDate date]]; + [CTASIHTTPRequest incrementBandwidthUsedInLastSecond:(unsigned long) (totalBytesSent - lastBytesSent)]; + + #if DEBUG_REQUEST_STATUS + if ([self totalBytesSent] == [self postLength]) { + ASI_DEBUG_LOG(@"[STATUS] Request %@ finished uploading data",self); + } + #endif + } + } + + [self updateProgressIndicators]; + + } + + [[self cancelledLock] unlock]; +} + + +// Cancel loading and clean up. DO NOT USE THIS TO CANCEL REQUESTS - use [request cancel] instead +- (void)cancelLoad +{ + // If we're in the middle of downloading a PAC file, let's stop that first + if (PACFileReadStream) { + [PACFileReadStream setDelegate:nil]; + [PACFileReadStream close]; + [self setPACFileReadStream:nil]; + [self setPACFileData:nil]; + } else if (PACFileRequest) { + [PACFileRequest setDelegate:nil]; + [PACFileRequest cancel]; + [self setPACFileRequest:nil]; + } + + [self destroyReadStream]; + + [[self postBodyReadStream] close]; + [self setPostBodyReadStream:nil]; + + if ([self rawResponseData]) { + if (![self complete]) { + [self setRawResponseData:nil]; + } + // If we were downloading to a file + } else if ([self temporaryFileDownloadPath]) { + [[self fileDownloadOutputStream] close]; + [self setFileDownloadOutputStream:nil]; + + [[self inflatedFileDownloadOutputStream] close]; + [self setInflatedFileDownloadOutputStream:nil]; + + // If we haven't said we might want to resume, let's remove the temporary file too + if (![self complete]) { + if (![self allowResumeForFileDownloads]) { + [self removeTemporaryDownloadFile]; + } + [self removeTemporaryUncompressedDownloadFile]; + } + } + + // Clean up any temporary file used to store request body for streaming + if (![self authenticationNeeded] && ![self willRetryRequest] && [self didCreateTemporaryPostDataFile]) { + [self removeTemporaryUploadFile]; + [self removeTemporaryCompressedUploadFile]; + [self setDidCreateTemporaryPostDataFile:NO]; + } +} + +#pragma mark HEAD request + +// Used by CTASINetworkQueue to create a HEAD request appropriate for this request with the same headers (though you can use it yourself) +- (CTASIHTTPRequest *)HEADRequest +{ + CTASIHTTPRequest *headRequest = [[self class] requestWithURL:[self url]]; + + // Copy the properties that make sense for a HEAD request + [headRequest setRequestHeaders:[[[self requestHeaders] mutableCopy] autorelease]]; + [headRequest setRequestCookies:[[[self requestCookies] mutableCopy] autorelease]]; + [headRequest setUseCookiePersistence:[self useCookiePersistence]]; + [headRequest setUseKeychainPersistence:[self useKeychainPersistence]]; + [headRequest setUseSessionPersistence:[self useSessionPersistence]]; + [headRequest setAllowCompressedResponse:[self allowCompressedResponse]]; + [headRequest setUsername:[self username]]; + [headRequest setPassword:[self password]]; + [headRequest setDomain:[self domain]]; + [headRequest setProxyUsername:[self proxyUsername]]; + [headRequest setProxyPassword:[self proxyPassword]]; + [headRequest setProxyDomain:[self proxyDomain]]; + [headRequest setProxyHost:[self proxyHost]]; + [headRequest setProxyPort:[self proxyPort]]; + [headRequest setProxyType:[self proxyType]]; + [headRequest setShouldPresentAuthenticationDialog:[self shouldPresentAuthenticationDialog]]; + [headRequest setShouldPresentProxyAuthenticationDialog:[self shouldPresentProxyAuthenticationDialog]]; + [headRequest setTimeOutSeconds:[self timeOutSeconds]]; + [headRequest setUseHTTPVersionOne:[self useHTTPVersionOne]]; + [headRequest setValidatesSecureCertificate:[self validatesSecureCertificate]]; + [headRequest setClientCertificateIdentity:clientCertificateIdentity]; + [headRequest setClientCertificates:[[clientCertificates copy] autorelease]]; + [headRequest setPACurl:[self PACurl]]; + [headRequest setShouldPresentCredentialsBeforeChallenge:[self shouldPresentCredentialsBeforeChallenge]]; + [headRequest setNumberOfTimesToRetryOnTimeout:[self numberOfTimesToRetryOnTimeout]]; + [headRequest setShouldUseRFC2616RedirectBehaviour:[self shouldUseRFC2616RedirectBehaviour]]; + [headRequest setShouldAttemptPersistentConnection:[self shouldAttemptPersistentConnection]]; + [headRequest setPersistentConnectionTimeoutSeconds:[self persistentConnectionTimeoutSeconds]]; + + [headRequest setMainRequest:self]; + [headRequest setRequestMethod:@"HEAD"]; + return headRequest; +} + + +#pragma mark upload/download progress + + +- (void)updateProgressIndicators +{ + //Only update progress if this isn't a HEAD request used to preset the content-length + if (![self mainRequest]) { + if ([self showAccurateProgress] || ([self complete] && ![self updatedProgress])) { + [self updateUploadProgress]; + [self updateDownloadProgress]; + } + } +} + +- (id)uploadProgressDelegate +{ + [[self cancelledLock] lock]; + id d = [[uploadProgressDelegate retain] autorelease]; + [[self cancelledLock] unlock]; + return d; +} + +- (void)setUploadProgressDelegate:(id)newDelegate +{ + [[self cancelledLock] lock]; + uploadProgressDelegate = newDelegate; + + #if !TARGET_OS_IPHONE + // If the uploadProgressDelegate is an NSProgressIndicator, we set its MaxValue to 1.0 so we can update it as if it were a UIProgressView + double max = 1.0; + [CTASIHTTPRequest performSelector:@selector(setMaxValue:) onTarget:&uploadProgressDelegate withObject:nil amount:&max callerToRetain:nil]; + #endif + [[self cancelledLock] unlock]; +} + +- (id)downloadProgressDelegate +{ + [[self cancelledLock] lock]; + id d = [[downloadProgressDelegate retain] autorelease]; + [[self cancelledLock] unlock]; + return d; +} + +- (void)setDownloadProgressDelegate:(id)newDelegate +{ + [[self cancelledLock] lock]; + downloadProgressDelegate = newDelegate; + + #if !TARGET_OS_IPHONE + // If the downloadProgressDelegate is an NSProgressIndicator, we set its MaxValue to 1.0 so we can update it as if it were a UIProgressView + double max = 1.0; + [CTASIHTTPRequest performSelector:@selector(setMaxValue:) onTarget:&downloadProgressDelegate withObject:nil amount:&max callerToRetain:nil]; + #endif + [[self cancelledLock] unlock]; +} + + +- (void)updateDownloadProgress +{ + // We won't update download progress until we've examined the headers, since we might need to authenticate + if (![self responseHeaders] || [self needsRedirect] || !([self contentLength] || [self complete])) { + return; + } + + unsigned long long bytesReadSoFar = [self totalBytesRead]+[self partialDownloadSize]; + unsigned long long value = 0; + + if ([self showAccurateProgress] && [self contentLength]) { + value = bytesReadSoFar-[self lastBytesRead]; + if (value == 0) { + return; + } + } else { + value = 1; + [self setUpdatedProgress:YES]; + } + if (!value) { + return; + } + + [CTASIHTTPRequest performSelector:@selector(request:didReceiveBytes:) onTarget:&queue withObject:self amount:&value callerToRetain:self]; + [CTASIHTTPRequest performSelector:@selector(request:didReceiveBytes:) onTarget:&downloadProgressDelegate withObject:self amount:&value callerToRetain:self]; + + [CTASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:[self totalBytesRead] + [self partialDownloadSize] ofTotal:[self contentLength] + [self partialDownloadSize]]; + + #if NS_BLOCKS_AVAILABLE + if (bytesReceivedBlock) { + unsigned long long totalSize = [self contentLength] + [self partialDownloadSize]; + [self performBlockOnMainThread:^{ if (bytesReceivedBlock) { bytesReceivedBlock(value, totalSize); }}]; + } + #endif + [self setLastBytesRead:bytesReadSoFar]; +} + +- (void)updateUploadProgress +{ + if ([self isCancelled] || [self totalBytesSent] == 0) { + return; + } + + // If this is the first time we've written to the buffer, totalBytesSent will be the size of the buffer (currently seems to be 128KB on both Leopard and iPhone 2.2.1, 32KB on iPhone 3.0) + // If request body is less than the buffer size, totalBytesSent will be the total size of the request body + // We will remove this from any progress display, as kCFStreamPropertyHTTPRequestBytesWrittenCount does not tell us how much data has actually be written + if ([self uploadBufferSize] == 0 && [self totalBytesSent] != [self postLength]) { + [self setUploadBufferSize:[self totalBytesSent]]; + [self incrementUploadSizeBy:-(long long)[self uploadBufferSize]]; + } + + unsigned long long value = 0; + + if ([self showAccurateProgress]) { + if ([self totalBytesSent] == [self postLength] || [self lastBytesSent] > 0) { + value = [self totalBytesSent]-[self lastBytesSent]; + } else { + return; + } + } else { + value = 1; + [self setUpdatedProgress:YES]; + } + + if (!value) { + return; + } + + [CTASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&queue withObject:self amount:&value callerToRetain:self]; + [CTASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&uploadProgressDelegate withObject:self amount:&value callerToRetain:self]; + [CTASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:[self totalBytesSent] - [self uploadBufferSize] ofTotal:[self postLength] - [self uploadBufferSize]]; + + #if NS_BLOCKS_AVAILABLE + if(bytesSentBlock){ + unsigned long long totalSize = [self postLength]; + [self performBlockOnMainThread:^{ if (bytesSentBlock) { bytesSentBlock(value, totalSize); }}]; + } + #endif +} + + +- (void)incrementDownloadSizeBy:(long long)length +{ + [CTASIHTTPRequest performSelector:@selector(request:incrementDownloadSizeBy:) onTarget:&queue withObject:self amount:&length callerToRetain:self]; + [CTASIHTTPRequest performSelector:@selector(request:incrementDownloadSizeBy:) onTarget:&downloadProgressDelegate withObject:self amount:&length callerToRetain:self]; + + #if NS_BLOCKS_AVAILABLE + if(downloadSizeIncrementedBlock){ + [self performBlockOnMainThread:^{ if (downloadSizeIncrementedBlock) { downloadSizeIncrementedBlock(length); }}]; + } + #endif +} + +- (void)incrementUploadSizeBy:(long long)length +{ + [CTASIHTTPRequest performSelector:@selector(request:incrementUploadSizeBy:) onTarget:&queue withObject:self amount:&length callerToRetain:self]; + [CTASIHTTPRequest performSelector:@selector(request:incrementUploadSizeBy:) onTarget:&uploadProgressDelegate withObject:self amount:&length callerToRetain:self]; + + #if NS_BLOCKS_AVAILABLE + if(uploadSizeIncrementedBlock) { + [self performBlockOnMainThread:^{ if (uploadSizeIncrementedBlock) { uploadSizeIncrementedBlock(length); }}]; + } + #endif +} + + +-(void)removeUploadProgressSoFar +{ + long long progressToRemove = -(long long)[self totalBytesSent]; + [CTASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&queue withObject:self amount:&progressToRemove callerToRetain:self]; + [CTASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&uploadProgressDelegate withObject:self amount:&progressToRemove callerToRetain:self]; + [CTASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:0 ofTotal:[self postLength]]; + + #if NS_BLOCKS_AVAILABLE + if(bytesSentBlock){ + unsigned long long totalSize = [self postLength]; + [self performBlockOnMainThread:^{ if (bytesSentBlock) { bytesSentBlock((unsigned long long)progressToRemove, totalSize); }}]; + } + #endif +} + +#if NS_BLOCKS_AVAILABLE +- (void)performBlockOnMainThread:(ASIBasicBlock)block +{ + [self performSelectorOnMainThread:@selector(callBlock:) withObject:[[block copy] autorelease] waitUntilDone:[NSThread isMainThread]]; +} + +- (void)callBlock:(ASIBasicBlock)block +{ + block(); +} +#endif + + ++ (void)performSelector:(SEL)selector onTarget:(id *)target withObject:(id)object amount:(void *)amount callerToRetain:(id)callerToRetain +{ + if ([*target respondsToSelector:selector]) { + NSMethodSignature *signature = nil; + signature = [*target methodSignatureForSelector:selector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + + [invocation setSelector:selector]; + + int argumentNumber = 2; + + // If we got an object parameter, we pass a pointer to the object pointer + if (object) { + [invocation setArgument:&object atIndex:argumentNumber]; + argumentNumber++; + } + + // For the amount we'll just pass the pointer directly so NSInvocation will call the method using the number itself rather than a pointer to it + if (amount) { + [invocation setArgument:amount atIndex:argumentNumber]; + } + + SEL callback = @selector(performInvocation:onTarget:releasingObject:); + NSMethodSignature *cbSignature = [CTASIHTTPRequest methodSignatureForSelector:callback]; + NSInvocation *cbInvocation = [NSInvocation invocationWithMethodSignature:cbSignature]; + [cbInvocation setSelector:callback]; + [cbInvocation setTarget:self]; + [cbInvocation setArgument:&invocation atIndex:2]; + [cbInvocation setArgument:&target atIndex:3]; + if (callerToRetain) { + [cbInvocation setArgument:&callerToRetain atIndex:4]; + } + + CFRetain(invocation); + + // Used to pass in a request that we must retain until after the call + // We're using CFRetain rather than [callerToRetain retain] so things to avoid earthquakes when using garbage collection + if (callerToRetain) { + CFRetain(callerToRetain); + } + [cbInvocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:[NSThread isMainThread]]; + } +} + ++ (void)performInvocation:(NSInvocation *)invocation onTarget:(id *)target releasingObject:(id)objectToRelease +{ + if (*target && [*target respondsToSelector:[invocation selector]]) { + [invocation invokeWithTarget:*target]; + } + CFRelease(invocation); + if (objectToRelease) { + CFRelease(objectToRelease); + } +} + + ++ (void)updateProgressIndicator:(id *)indicator withProgress:(unsigned long long)progress ofTotal:(unsigned long long)total +{ + #if TARGET_OS_IPHONE + // Cocoa Touch: UIProgressView + SEL selector = @selector(setProgress:); + float progressAmount = (float)((progress*1.0)/(total*1.0)); + + #else + // Cocoa: NSProgressIndicator + double progressAmount = progressAmount = (progress*1.0)/(total*1.0); + SEL selector = @selector(setDoubleValue:); + #endif + + if (![*indicator respondsToSelector:selector]) { + return; + } + + [progressLock lock]; + [CTASIHTTPRequest performSelector:selector onTarget:indicator withObject:nil amount:&progressAmount callerToRetain:nil]; + [progressLock unlock]; +} + + +#pragma mark talking to delegates / calling blocks + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)requestStarted +{ + if ([self error] || [self mainRequest]) { + return; + } + if (delegate && [delegate respondsToSelector:didStartSelector]) { + [delegate performSelector:didStartSelector withObject:self]; + } + #if NS_BLOCKS_AVAILABLE + if(startedBlock){ + startedBlock(); + } + #endif + if (queue && [queue respondsToSelector:@selector(requestStarted:)]) { + [queue performSelector:@selector(requestStarted:) withObject:self]; + } +} + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)requestRedirected +{ + if ([self error] || [self mainRequest]) { + return; + } + + if([[self delegate] respondsToSelector:@selector(requestRedirected:)]){ + [[self delegate] performSelector:@selector(requestRedirected:) withObject:self]; + } + + #if NS_BLOCKS_AVAILABLE + if(requestRedirectedBlock){ + requestRedirectedBlock(); + } + #endif +} + + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)requestReceivedResponseHeaders:(NSMutableDictionary *)newResponseHeaders +{ + if ([self error] || [self mainRequest]) { + return; + } + + if (delegate && [delegate respondsToSelector:didReceiveResponseHeadersSelector]) { + [delegate performSelector:didReceiveResponseHeadersSelector withObject:self withObject:newResponseHeaders]; + } + + #if NS_BLOCKS_AVAILABLE + if(headersReceivedBlock){ + headersReceivedBlock(newResponseHeaders); + } + #endif + + if (queue && [queue respondsToSelector:@selector(request:didReceiveResponseHeaders:)]) { + [queue performSelector:@selector(request:didReceiveResponseHeaders:) withObject:self withObject:newResponseHeaders]; + } +} + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)requestWillRedirectToURL:(NSURL *)newURL +{ + if ([self error] || [self mainRequest]) { + return; + } + if (delegate && [delegate respondsToSelector:willRedirectSelector]) { + [delegate performSelector:willRedirectSelector withObject:self withObject:newURL]; + } + if (queue && [queue respondsToSelector:@selector(request:willRedirectToURL:)]) { + [queue performSelector:@selector(request:willRedirectToURL:) withObject:self withObject:newURL]; + } +} + +// Subclasses might override this method to process the result in the same thread +// If you do this, don't forget to call [super requestFinished] to let the queue / delegate know we're done +- (void)requestFinished +{ +#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING + ASI_DEBUG_LOG(@"[STATUS] Request finished: %@",self); +#endif + if ([self error] || [self mainRequest]) { + return; + } + if ([self isPACFileRequest]) { + [self reportFinished]; + } else { + [self performSelectorOnMainThread:@selector(reportFinished) withObject:nil waitUntilDone:[NSThread isMainThread]]; + } +} + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)reportFinished +{ + if (delegate && [delegate respondsToSelector:didFinishSelector]) { + [delegate performSelector:didFinishSelector withObject:self]; + } + + #if NS_BLOCKS_AVAILABLE + if(completionBlock){ + completionBlock(); + } + #endif + + if (queue && [queue respondsToSelector:@selector(requestFinished:)]) { + [queue performSelector:@selector(requestFinished:) withObject:self]; + } +} + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)reportFailure +{ + if (delegate && [delegate respondsToSelector:didFailSelector]) { + [delegate performSelector:didFailSelector withObject:self]; + } + + #if NS_BLOCKS_AVAILABLE + if(failureBlock){ + failureBlock(); + } + #endif + + if (queue && [queue respondsToSelector:@selector(requestFailed:)]) { + [queue performSelector:@selector(requestFailed:) withObject:self]; + } +} + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)passOnReceivedData:(NSData *)data +{ + if (delegate && [delegate respondsToSelector:didReceiveDataSelector]) { + [delegate performSelector:didReceiveDataSelector withObject:self withObject:data]; + } + + #if NS_BLOCKS_AVAILABLE + if (dataReceivedBlock) { + dataReceivedBlock(data); + } + #endif +} + +// Subclasses might override this method to perform error handling in the same thread +// If you do this, don't forget to call [super failWithError:] to let the queue / delegate know we're done +- (void)failWithError:(NSError *)theError +{ +#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING + ASI_DEBUG_LOG(@"[STATUS] Request %@: %@",self,(theError == ASIRequestCancelledError ? @"Cancelled" : @"Failed")); +#endif + [self setComplete:YES]; + + // Invalidate the current connection so subsequent requests don't attempt to reuse it + if (theError && [theError code] != ASIAuthenticationErrorType && [theError code] != ASITooMuchRedirectionErrorType) { + [connectionsLock lock]; + #if DEBUG_PERSISTENT_CONNECTIONS + ASI_DEBUG_LOG(@"[CONNECTION] Request #%@ failed and will invalidate connection #%@",[self requestID],[[self connectionInfo] objectForKey:@"id"]); + #endif + [[self connectionInfo] removeObjectForKey:@"request"]; + [persistentConnectionsPool removeObject:[self connectionInfo]]; + [connectionsLock unlock]; + [self destroyReadStream]; + } + if ([self connectionCanBeReused]) { + [[self connectionInfo] setObject:[NSDate dateWithTimeIntervalSinceNow:[self persistentConnectionTimeoutSeconds]] forKey:@"expires"]; + } + + if ([self isCancelled] || [self error]) { + return; + } + + // If we have cached data, use it and ignore the error when using CTASIFallbackToCacheIfLoadFailsCachePolicy + if ([self downloadCache] && ([self cachePolicy] & CTASIFallbackToCacheIfLoadFailsCachePolicy)) { + if ([[self downloadCache] canUseCachedDataForRequest:self]) { + [self useDataFromCache]; + return; + } + } + + + [self setError:theError]; + + CTASIHTTPRequest *failedRequest = self; + + // If this is a HEAD request created by an CTASINetworkQueue or compatible queue delegate, make the main request fail + if ([self mainRequest]) { + failedRequest = [self mainRequest]; + [failedRequest setError:theError]; + } + + if ([self isPACFileRequest]) { + [failedRequest reportFailure]; + } else { + [failedRequest performSelectorOnMainThread:@selector(reportFailure) withObject:nil waitUntilDone:[NSThread isMainThread]]; + } + + if (!inProgress) + { + // if we're not in progress, we can't notify the queue we've finished (doing so can cause a crash later on) + // "markAsFinished" will be at the start of main() when we are started + return; + } + [self markAsFinished]; +} + +#pragma mark parsing HTTP response headers + +- (void)readResponseHeaders +{ + [self setAuthenticationNeeded:ASINoAuthenticationNeededYet]; + + CFHTTPMessageRef message = (CFHTTPMessageRef)CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPResponseHeader); + if (!message) { + return; + } + + // Make sure we've received all the headers + if (!CFHTTPMessageIsHeaderComplete(message)) { + CFRelease(message); + return; + } + + #if DEBUG_REQUEST_STATUS + if ([self totalBytesSent] == [self postLength]) { + ASI_DEBUG_LOG(@"[STATUS] Request %@ received response headers",self); + } + #endif + + [self setResponseHeaders:[NSMakeCollectable(CFHTTPMessageCopyAllHeaderFields(message)) autorelease]]; + [self setResponseStatusCode:(int)CFHTTPMessageGetResponseStatusCode(message)]; + [self setResponseStatusMessage:[NSMakeCollectable(CFHTTPMessageCopyResponseStatusLine(message)) autorelease]]; + + if ([self downloadCache] && ([[self downloadCache] canUseCachedDataForRequest:self])) { + + // Update the expiry date + [[self downloadCache] updateExpiryForRequest:self maxAge:[self secondsToCache]]; + + // Read the response from the cache + [self useDataFromCache]; + + CFRelease(message); + return; + } + + // Is the server response a challenge for credentials? + if ([self responseStatusCode] == 401) { + [self setAuthenticationNeeded:ASIHTTPAuthenticationNeeded]; + } else if ([self responseStatusCode] == 407) { + [self setAuthenticationNeeded:ASIProxyAuthenticationNeeded]; + } else { + #if DEBUG_HTTP_AUTHENTICATION + if ([self authenticationScheme]) { + ASI_DEBUG_LOG(@"[AUTH] Request %@ has passed %@ authentication",self,[self authenticationScheme]); + } + #endif + } + + // Authentication succeeded, or no authentication was required + if (![self authenticationNeeded]) { + + // Did we get here without an authentication challenge? (which can happen when shouldPresentCredentialsBeforeChallenge is YES and basic auth was successful) + if (!requestAuthentication && [[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic] && [self username] && [self password] && [self useSessionPersistence]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ passed BASIC authentication, and will save credentials in the session store for future use",self); + #endif + + NSMutableDictionary *newCredentials = [NSMutableDictionary dictionaryWithCapacity:2]; + [newCredentials setObject:[self username] forKey:(NSString *)kCFHTTPAuthenticationUsername]; + [newCredentials setObject:[self password] forKey:(NSString *)kCFHTTPAuthenticationPassword]; + + // Store the credentials in the session + NSMutableDictionary *sessionCredentials = [NSMutableDictionary dictionary]; + [sessionCredentials setObject:newCredentials forKey:@"Credentials"]; + [sessionCredentials setObject:[self url] forKey:@"URL"]; + [sessionCredentials setObject:(NSString *)kCFHTTPAuthenticationSchemeBasic forKey:@"AuthenticationScheme"]; + [[self class] storeAuthenticationCredentialsInSessionStore:sessionCredentials]; + } + } + + // Read response textEncoding + [self parseStringEncodingFromHeaders]; + + // Handle cookies + NSArray *newCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:[self responseHeaders] forURL:[self url]]; + [self setResponseCookies:newCookies]; + + if ([self useCookiePersistence]) { + + // Store cookies in global persistent store + [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:newCookies forURL:[self url] mainDocumentURL:nil]; + + // We also keep any cookies in the sessionCookies array, so that we have a reference to them if we need to remove them later + NSHTTPCookie *cookie; + for (cookie in newCookies) { + [CTASIHTTPRequest addSessionCookie:cookie]; + } + } + + // Do we need to redirect? + if (![self willRedirect]) { + // See if we got a Content-length header + NSString *cLength = [responseHeaders valueForKey:@"Content-Length"]; + CTASIHTTPRequest *theRequest = self; + if ([self mainRequest]) { + theRequest = [self mainRequest]; + } + + if (cLength) { + unsigned long long length = strtoull([cLength UTF8String], NULL, 0); + + // Workaround for Apache HEAD requests for dynamically generated content returning the wrong Content-Length when using gzip + if ([self mainRequest] && [self allowCompressedResponse] && length == 20 && [self showAccurateProgress] && [self shouldResetDownloadProgress]) { + [[self mainRequest] setShowAccurateProgress:NO]; + [[self mainRequest] incrementDownloadSizeBy:1]; + + } else { + [theRequest setContentLength:length]; + if ([self showAccurateProgress] && [self shouldResetDownloadProgress]) { + [theRequest incrementDownloadSizeBy:(long long)[theRequest contentLength]+(long long)[theRequest partialDownloadSize]]; + } + } + + } else if ([self showAccurateProgress] && [self shouldResetDownloadProgress]) { + [theRequest setShowAccurateProgress:NO]; + [theRequest incrementDownloadSizeBy:1]; + } + } + + // Handle connection persistence + if ([self shouldAttemptPersistentConnection]) { + + NSString *connectionHeader = [[[self responseHeaders] objectForKey:@"Connection"] lowercaseString]; + + NSString *httpVersion = [NSMakeCollectable(CFHTTPMessageCopyVersion(message)) autorelease]; + + // Don't re-use the connection if the server is HTTP 1.0 and didn't send Connection: Keep-Alive + if (![httpVersion isEqualToString:(NSString *)kCFHTTPVersion1_0] || [connectionHeader isEqualToString:@"keep-alive"]) { + + // See if server explicitly told us to close the connection + if (![connectionHeader isEqualToString:@"close"]) { + + NSString *keepAliveHeader = [[self responseHeaders] objectForKey:@"Keep-Alive"]; + + // If we got a keep alive header, we'll reuse the connection for as long as the server tells us + if (keepAliveHeader) { + int timeout = 0; + int max = 0; + NSScanner *scanner = [NSScanner scannerWithString:keepAliveHeader]; + [scanner scanString:@"timeout=" intoString:NULL]; + [scanner scanInt:&timeout]; + [scanner scanUpToString:@"max=" intoString:NULL]; + [scanner scanString:@"max=" intoString:NULL]; + [scanner scanInt:&max]; + if (max > 5) { + [self setConnectionCanBeReused:YES]; + [self setPersistentConnectionTimeoutSeconds:timeout]; + #if DEBUG_PERSISTENT_CONNECTIONS + ASI_DEBUG_LOG(@"[CONNECTION] Got a keep-alive header, will keep this connection open for %f seconds", [self persistentConnectionTimeoutSeconds]); + #endif + } + + // Otherwise, we'll assume we can keep this connection open + } else { + [self setConnectionCanBeReused:YES]; + #if DEBUG_PERSISTENT_CONNECTIONS + ASI_DEBUG_LOG(@"[CONNECTION] Got no keep-alive header, will keep this connection open for %f seconds", [self persistentConnectionTimeoutSeconds]); + #endif + } + } + } + } + + CFRelease(message); + [self performSelectorOnMainThread:@selector(requestReceivedResponseHeaders:) withObject:[[[self responseHeaders] copy] autorelease] waitUntilDone:[NSThread isMainThread]]; +} + +- (BOOL)willRedirect +{ + // Do we need to redirect? + if (![self shouldRedirect] || ![responseHeaders valueForKey:@"Location"]) { + return NO; + } + + // Note that CTASIHTTPRequest does not currently support 305 Use Proxy + int responseCode = [self responseStatusCode]; + if (responseCode != 301 && responseCode != 302 && responseCode != 303 && responseCode != 307) { + return NO; + } + + [self performSelectorOnMainThread:@selector(requestRedirected) withObject:nil waitUntilDone:[NSThread isMainThread]]; + + // By default, we redirect 301 and 302 response codes as GET requests + // According to RFC 2616 this is wrong, but this is what most browsers do, so it's probably what you're expecting to happen + // See also: + // http://allseeing-i.lighthouseapp.com/projects/27881/tickets/27-302-redirection-issue + + if (responseCode != 307 && (![self shouldUseRFC2616RedirectBehaviour] || responseCode == 303)) { + [self setRequestMethod:@"GET"]; + [self setPostBody:nil]; + [self setPostLength:0]; + + // Perhaps there are other headers we should be preserving, but it's hard to know what we need to keep and what to throw away. + NSString *userAgentHeader = [[self requestHeaders] objectForKey:@"User-Agent"]; + NSString *acceptHeader = [[self requestHeaders] objectForKey:@"Accept"]; + [self setRequestHeaders:nil]; + if (userAgentHeader) { + [self addRequestHeader:@"User-Agent" value:userAgentHeader]; + } + if (acceptHeader) { + [self addRequestHeader:@"Accept" value:acceptHeader]; + } + [self setHaveBuiltRequestHeaders:NO]; + + } else { + // Force rebuild the cookie header incase we got some new cookies from this request + // All other request headers will remain as they are for 301 / 302 redirects + [self applyCookieHeader]; + } + + // Force the redirected request to rebuild the request headers (if not a 303, it will re-use old ones, and add any new ones) + [self setRedirectURL:[[NSURL URLWithString:[responseHeaders valueForKey:@"Location"] relativeToURL:[self url]] absoluteURL]]; + [self setNeedsRedirect:YES]; + + // Clear the request cookies + // This means manually added cookies will not be added to the redirect request - only those stored in the global persistent store + // But, this is probably the safest option - we might be redirecting to a different domain + [self setRequestCookies:[NSMutableArray array]]; + + #if DEBUG_REQUEST_STATUS + ASI_DEBUG_LOG(@"[STATUS] Request will redirect (code: %i): %@",responseCode,self); + #endif + + return YES; +} + +- (void)parseStringEncodingFromHeaders +{ + // Handle response text encoding + NSStringEncoding charset = 0; + NSString *mimeType = nil; + [[self class] parseMimeType:&mimeType andResponseEncoding:&charset fromContentType:[[self responseHeaders] valueForKey:@"Content-Type"]]; + if (charset != 0) { + [self setResponseEncoding:charset]; + } else { + [self setResponseEncoding:[self defaultResponseEncoding]]; + } +} + +#pragma mark http authentication + +- (void)saveProxyCredentialsToKeychain:(NSDictionary *)newCredentials +{ + NSURLCredential *authenticationCredentials = [NSURLCredential credentialWithUser:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationUsername] password:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationPassword] persistence:NSURLCredentialPersistencePermanent]; + if (authenticationCredentials) { + [CTASIHTTPRequest saveCredentials:authenticationCredentials forProxy:[self proxyHost] port:[self proxyPort] realm:[self proxyAuthenticationRealm]]; + } +} + + +- (void)saveCredentialsToKeychain:(NSDictionary *)newCredentials +{ + NSURLCredential *authenticationCredentials = [NSURLCredential credentialWithUser:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationUsername] password:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationPassword] persistence:NSURLCredentialPersistencePermanent]; + + if (authenticationCredentials) { + [CTASIHTTPRequest saveCredentials:authenticationCredentials forHost:[[self url] host] port:[[[self url] port] intValue] protocol:[[self url] scheme] realm:[self authenticationRealm]]; + } +} + +- (BOOL)applyProxyCredentials:(NSDictionary *)newCredentials +{ + [self setProxyAuthenticationRetryCount:[self proxyAuthenticationRetryCount]+1]; + + if (newCredentials && proxyAuthentication && request) { + + // Apply whatever credentials we've built up to the old request + if (CFHTTPMessageApplyCredentialDictionary(request, proxyAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) { + + //If we have credentials and they're ok, let's save them to the keychain + if (useKeychainPersistence) { + [self saveProxyCredentialsToKeychain:newCredentials]; + } + if (useSessionPersistence) { + NSMutableDictionary *sessionProxyCredentials = [NSMutableDictionary dictionary]; + [sessionProxyCredentials setObject:(id)proxyAuthentication forKey:@"Authentication"]; + [sessionProxyCredentials setObject:newCredentials forKey:@"Credentials"]; + [sessionProxyCredentials setObject:[self proxyHost] forKey:@"Host"]; + [sessionProxyCredentials setObject:[NSNumber numberWithInt:[self proxyPort]] forKey:@"Port"]; + [sessionProxyCredentials setObject:[self proxyAuthenticationScheme] forKey:@"AuthenticationScheme"]; + [[self class] storeProxyAuthenticationCredentialsInSessionStore:sessionProxyCredentials]; + } + [self setProxyCredentials:newCredentials]; + return YES; + } else { + [[self class] removeProxyAuthenticationCredentialsFromSessionStore:newCredentials]; + } + } + return NO; +} + +- (BOOL)applyCredentials:(NSDictionary *)newCredentials +{ + [self setAuthenticationRetryCount:[self authenticationRetryCount]+1]; + + if (newCredentials && requestAuthentication && request) { + // Apply whatever credentials we've built up to the old request + if (CFHTTPMessageApplyCredentialDictionary(request, requestAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) { + + //If we have credentials and they're ok, let's save them to the keychain + if (useKeychainPersistence) { + [self saveCredentialsToKeychain:newCredentials]; + } + if (useSessionPersistence) { + + NSMutableDictionary *sessionCredentials = [NSMutableDictionary dictionary]; + [sessionCredentials setObject:(id)requestAuthentication forKey:@"Authentication"]; + [sessionCredentials setObject:newCredentials forKey:@"Credentials"]; + [sessionCredentials setObject:[self url] forKey:@"URL"]; + [sessionCredentials setObject:[self authenticationScheme] forKey:@"AuthenticationScheme"]; + if ([self authenticationRealm]) { + [sessionCredentials setObject:[self authenticationRealm] forKey:@"AuthenticationRealm"]; + } + [[self class] storeAuthenticationCredentialsInSessionStore:sessionCredentials]; + + } + [self setRequestCredentials:newCredentials]; + return YES; + } else { + [[self class] removeAuthenticationCredentialsFromSessionStore:newCredentials]; + } + } + return NO; +} + +- (NSMutableDictionary *)findProxyCredentials +{ + NSMutableDictionary *newCredentials = [[[NSMutableDictionary alloc] init] autorelease]; + + NSString *user = nil; + NSString *pass = nil; + + CTASIHTTPRequest *theRequest = [self mainRequest]; + // If this is a HEAD request generated by an CTASINetworkQueue, we'll try to use the details from the main request + if ([theRequest proxyUsername] && [theRequest proxyPassword]) { + user = [theRequest proxyUsername]; + pass = [theRequest proxyPassword]; + + // Let's try to use the ones set in this object + } else if ([self proxyUsername] && [self proxyPassword]) { + user = [self proxyUsername]; + pass = [self proxyPassword]; + } + + // When we connect to a website using NTLM via a proxy, we will use the main credentials + if ((!user || !pass) && [self proxyAuthenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM) { + user = [self username]; + pass = [self password]; + } + + + + // Ok, that didn't work, let's try the keychain + // For authenticating proxies, we'll look in the keychain regardless of the value of useKeychainPersistence + if ((!user || !pass)) { + NSURLCredential *authenticationCredentials = [CTASIHTTPRequest savedCredentialsForProxy:[self proxyHost] port:[self proxyPort] protocol:[[self url] scheme] realm:[self proxyAuthenticationRealm]]; + if (authenticationCredentials) { + user = [authenticationCredentials user]; + pass = [authenticationCredentials password]; + } + + } + + // Handle NTLM, which requires a domain to be set too + if (CFHTTPAuthenticationRequiresAccountDomain(proxyAuthentication)) { + + NSString *ntlmDomain = [self proxyDomain]; + + // If we have no domain yet + if (!ntlmDomain || [ntlmDomain length] == 0) { + + // Let's try to extract it from the username + NSArray* ntlmComponents = [user componentsSeparatedByString:@"\\"]; + if ([ntlmComponents count] == 2) { + ntlmDomain = [ntlmComponents objectAtIndex:0]; + user = [ntlmComponents objectAtIndex:1]; + + // If we are connecting to a website using NTLM, but we are connecting via a proxy, the string we need may be in the domain property + } else { + ntlmDomain = [self domain]; + } + if (!ntlmDomain) { + ntlmDomain = @""; + } + } + [newCredentials setObject:ntlmDomain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain]; + } + + + // If we have a username and password, let's apply them to the request and continue + if (user && pass) { + [newCredentials setObject:user forKey:(NSString *)kCFHTTPAuthenticationUsername]; + [newCredentials setObject:pass forKey:(NSString *)kCFHTTPAuthenticationPassword]; + return newCredentials; + } + return nil; +} + + +- (NSMutableDictionary *)findCredentials +{ + NSMutableDictionary *newCredentials = [[[NSMutableDictionary alloc] init] autorelease]; + + // First, let's look at the url to see if the username and password were included + NSString *user = [[self url] user]; + NSString *pass = [[self url] password]; + + if (user && pass) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will use credentials set on its url",self); + #endif + + } else { + + // If this is a HEAD request generated by an CTASINetworkQueue, we'll try to use the details from the main request + if ([self mainRequest] && [[self mainRequest] username] && [[self mainRequest] password]) { + user = [[self mainRequest] username]; + pass = [[self mainRequest] password]; + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will use credentials from its parent request",self); + #endif + + // Let's try to use the ones set in this object + } else if ([self username] && [self password]) { + user = [self username]; + pass = [self password]; + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will use username and password properties as credentials",self); + #endif + } + } + + // Ok, that didn't work, let's try the keychain + if ((!user || !pass) && useKeychainPersistence) { + NSURLCredential *authenticationCredentials = [CTASIHTTPRequest savedCredentialsForHost:[[self url] host] port:[[[self url] port] intValue] protocol:[[self url] scheme] realm:[self authenticationRealm]]; + if (authenticationCredentials) { + user = [authenticationCredentials user]; + pass = [authenticationCredentials password]; + #if DEBUG_HTTP_AUTHENTICATION + if (user && pass) { + ASI_DEBUG_LOG(@"[AUTH] Request %@ will use credentials from the keychain",self); + } + #endif + } + } + + // Handle NTLM, which requires a domain to be set too + if (CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) { + + NSString *ntlmDomain = [self domain]; + + // If we have no domain yet, let's try to extract it from the username + if (!ntlmDomain || [ntlmDomain length] == 0) { + ntlmDomain = @""; + NSArray* ntlmComponents = [user componentsSeparatedByString:@"\\"]; + if ([ntlmComponents count] == 2) { + ntlmDomain = [ntlmComponents objectAtIndex:0]; + user = [ntlmComponents objectAtIndex:1]; + } + } + [newCredentials setObject:ntlmDomain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain]; + } + + // If we have a username and password, let's apply them to the request and continue + if (user && pass) { + [newCredentials setObject:user forKey:(NSString *)kCFHTTPAuthenticationUsername]; + [newCredentials setObject:pass forKey:(NSString *)kCFHTTPAuthenticationPassword]; + return newCredentials; + } + return nil; +} + +// Called by delegate or authentication dialog to resume loading once authentication info has been populated +- (void)retryUsingSuppliedCredentials +{ + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@CTASIAuthenticationDialogy",self); + #endif + //If the url was changed by the delegate, our CFHTTPMessageRef will be NULL and we'll go back to the start + if (!request) { + [self performSelector:@selector(main) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO]; + return; + } + [self performSelector:@selector(attemptToApplyCredentialsAndResume) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO]; +} + +// Called by delegate or authentication dialog to cancel authentication +- (void)cancelAuthentication +{ + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@CTASIAuthenticationDialogg",self); + #endif + [self performSelector:@selector(failAuthentication) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO]; +} + +- (void)failAuthentication +{ + [self failWithError:ASIAuthenticationError]; +} + +- (BOOL)showProxyAuthenticationDialog +{ + if ([self isSynchronous]) { + return NO; + } + + // Mac authentication dialog coming soon! + #if TARGET_OS_IPHONE + if ([self shouldPresentProxyAuthenticationDialog]) { + [CTASIAuthenticationDialog performSelectorOnMainThread:@selector(presentAuthenticationDialogForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]]; + return YES; + } + return NO; + #else + return NO; + #endif +} + + +- (BOOL)willAskDelegateForProxyCredentials +{ + if ([self isSynchronous]) { + return NO; + } + + // If we have a delegate, we'll see if it can handle proxyAuthenticationNeededForRequest:. + // Otherwise, we'll try the queue (if this request is part of one) and it will pass the message on to its own delegate + id authenticationDelegate = [self delegate]; + if (!authenticationDelegate) { + authenticationDelegate = [self queue]; + } + + BOOL delegateOrBlockWillHandleAuthentication = NO; + + if ([authenticationDelegate respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) { + delegateOrBlockWillHandleAuthentication = YES; + } + + #if NS_BLOCKS_AVAILABLE + if(proxyAuthenticationNeededBlock){ + delegateOrBlockWillHandleAuthentication = YES; + } + #endif + + if (delegateOrBlockWillHandleAuthentication) { + [self performSelectorOnMainThread:@selector(askDelegateForProxyCredentials) withObject:nil waitUntilDone:NO]; + } + + return delegateOrBlockWillHandleAuthentication; +} + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)askDelegateForProxyCredentials +{ + id authenticationDelegate = [self delegate]; + if (!authenticationDelegate) { + authenticationDelegate = [self queue]; + } + if ([authenticationDelegate respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) { + [authenticationDelegate performSelector:@selector(proxyAuthenticationNeededForRequest:) withObject:self]; + return; + } + #if NS_BLOCKS_AVAILABLE + if(proxyAuthenticationNeededBlock){ + proxyAuthenticationNeededBlock(); + } + #endif +} + + +- (BOOL)willAskDelegateForCredentials +{ + if ([self isSynchronous]) { + return NO; + } + + // If we have a delegate, we'll see if it can handle proxyAuthenticationNeededForRequest:. + // Otherwise, we'll try the queue (if this request is part of one) and it will pass the message on to its own delegate + id authenticationDelegate = [self delegate]; + if (!authenticationDelegate) { + authenticationDelegate = [self queue]; + } + + BOOL delegateOrBlockWillHandleAuthentication = NO; + + if ([authenticationDelegate respondsToSelector:@selector(authenticationNeededForRequest:)]) { + delegateOrBlockWillHandleAuthentication = YES; + } + + #if NS_BLOCKS_AVAILABLE + if (authenticationNeededBlock) { + delegateOrBlockWillHandleAuthentication = YES; + } + #endif + + if (delegateOrBlockWillHandleAuthentication) { + [self performSelectorOnMainThread:@selector(askDelegateForCredentials) withObject:nil waitUntilDone:NO]; + } + return delegateOrBlockWillHandleAuthentication; +} + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)askDelegateForCredentials +{ + id authenticationDelegate = [self delegate]; + if (!authenticationDelegate) { + authenticationDelegate = [self queue]; + } + + if ([authenticationDelegate respondsToSelector:@selector(authenticationNeededForRequest:)]) { + [authenticationDelegate performSelector:@selector(authenticationNeededForRequest:) withObject:self]; + return; + } + + #if NS_BLOCKS_AVAILABLE + if (authenticationNeededBlock) { + authenticationNeededBlock(); + } + #endif +} + +- (void)attemptToApplyProxyCredentialsAndResume +{ + + if ([self error] || [self isCancelled]) { + return; + } + + // Read authentication data + if (!proxyAuthentication) { + CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty((CFReadStreamRef)[self readStream],kCFStreamPropertyHTTPResponseHeader); + proxyAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader); + CFRelease(responseHeader); + [self setProxyAuthenticationScheme:[NSMakeCollectable(CFHTTPAuthenticationCopyMethod(proxyAuthentication)) autorelease]]; + } + + // If we haven't got a CFHTTPAuthenticationRef by now, something is badly wrong, so we'll have to give up + if (!proxyAuthentication) { + [self cancelLoad]; + [self failWithError:[NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to get authentication object from response headers", NSLocalizedDescriptionKey, nil]]]; + return; + } + + // Get the authentication realm + [self setProxyAuthenticationRealm:nil]; + if (!CFHTTPAuthenticationRequiresAccountDomain(proxyAuthentication)) { + [self setProxyAuthenticationRealm:[NSMakeCollectable(CFHTTPAuthenticationCopyRealm(proxyAuthentication)) autorelease]]; + } + + // See if authentication is valid + CFStreamError err; + if (!CFHTTPAuthenticationIsValid(proxyAuthentication, &err)) { + + CFRelease(proxyAuthentication); + proxyAuthentication = NULL; + + // check for bad credentials, so we can give the delegate a chance to replace them + if (err.domain == kCFStreamErrorDomainHTTP && (err.error == kCFStreamErrorHTTPAuthenticationBadUserName || err.error == kCFStreamErrorHTTPAuthenticationBadPassword)) { + + // Prevent more than one request from asking for credentials at once + [delegateAuthenticationLock lock]; + + // We know the credentials we just presented are bad, we should remove them from the session store too + [[self class] removeProxyAuthenticationCredentialsFromSessionStore:proxyCredentials]; + [self setProxyCredentials:nil]; + + + // If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us + if ([self error] || [self isCancelled]) { + [delegateAuthenticationLock unlock]; + return; + } + + + // Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request + if ([self useSessionPersistence]) { + NSDictionary *credentials = [self findSessionProxyAuthenticationCredentials]; + if (credentials && [self applyProxyCredentials:[credentials objectForKey:@"Credentials"]]) { + [delegateAuthenticationLock unlock]; + [self startRequest]; + return; + } + } + + [self setLastActivityTime:nil]; + + if ([self willAskDelegateForProxyCredentials]) { + [self attemptToApplyProxyCredentialsAndResume]; + [delegateAuthenticationLock unlock]; + return; + } + if ([self showProxyAuthenticationDialog]) { + [self attemptToApplyProxyCredentialsAndResume]; + [delegateAuthenticationLock unlock]; + return; + } + [delegateAuthenticationLock unlock]; + } + [self cancelLoad]; + [self failWithError:ASIAuthenticationError]; + return; + } + + [self cancelLoad]; + + if (proxyCredentials) { + + // We use startRequest rather than starting all over again in load request because NTLM requires we reuse the request + if ((([self proxyAuthenticationScheme] != (NSString *)kCFHTTPAuthenticationSchemeNTLM) || [self proxyAuthenticationRetryCount] < 2) && [self applyProxyCredentials:proxyCredentials]) { + [self startRequest]; + + // We've failed NTLM authentication twice, we should assume our credentials are wrong + } else if ([self proxyAuthenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM && [self proxyAuthenticationRetryCount] == 2) { + [self failWithError:ASIAuthenticationError]; + + // Something went wrong, we'll have to give up + } else { + [self failWithError:[NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply proxy credentials to request", NSLocalizedDescriptionKey, nil]]]; + } + + // Are a user name & password needed? + } else if (CFHTTPAuthenticationRequiresUserNameAndPassword(proxyAuthentication)) { + + // Prevent more than one request from asking for credentials at once + [delegateAuthenticationLock lock]; + + // If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us + if ([self error] || [self isCancelled]) { + [delegateAuthenticationLock unlock]; + return; + } + + // Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request + if ([self useSessionPersistence]) { + NSDictionary *credentials = [self findSessionProxyAuthenticationCredentials]; + if (credentials && [self applyProxyCredentials:[credentials objectForKey:@"Credentials"]]) { + [delegateAuthenticationLock unlock]; + [self startRequest]; + return; + } + } + + NSMutableDictionary *newCredentials = [self findProxyCredentials]; + + //If we have some credentials to use let's apply them to the request and continue + if (newCredentials) { + + if ([self applyProxyCredentials:newCredentials]) { + [delegateAuthenticationLock unlock]; + [self startRequest]; + } else { + [delegateAuthenticationLock unlock]; + [self failWithError:[NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply proxy credentials to request", NSLocalizedDescriptionKey, nil]]]; + } + + return; + } + + if ([self willAskDelegateForProxyCredentials]) { + [delegateAuthenticationLock unlock]; + return; + } + + if ([self showProxyAuthenticationDialog]) { + [delegateAuthenticationLock unlock]; + return; + } + [delegateAuthenticationLock unlock]; + + // The delegate isn't interested and we aren't showing the authentication dialog, we'll have to give up + [self failWithError:ASIAuthenticationError]; + return; + } + +} + +- (BOOL)showAuthenticationDialog +{ + if ([self isSynchronous]) { + return NO; + } + // Mac authentication dialog coming soon! + #if TARGET_OS_IPHONE + if ([self shouldPresentAuthenticationDialog]) { + [CTASIAuthenticationDialog performSelectorOnMainThread:@selector(presentAuthenticationDialogForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]]; + return YES; + } + return NO; + #else + return NO; + #endif +} + +- (void)attemptToApplyCredentialsAndResume +{ + if ([self error] || [self isCancelled]) { + return; + } + + // Do we actually need to authenticate with a proxy? + if ([self authenticationNeeded] == ASIProxyAuthenticationNeeded) { + [self attemptToApplyProxyCredentialsAndResume]; + return; + } + + // Read authentication data + if (!requestAuthentication) { + CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty((CFReadStreamRef)[self readStream],kCFStreamPropertyHTTPResponseHeader); + requestAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader); + CFRelease(responseHeader); + [self setAuthenticationScheme:[NSMakeCollectable(CFHTTPAuthenticationCopyMethod(requestAuthentication)) autorelease]]; + } + + if (!requestAuthentication) { + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ failed to read authentication information from response headers",self); + #endif + + [self cancelLoad]; + [self failWithError:[NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to get authentication object from response headers", NSLocalizedDescriptionKey, nil]]]; + return; + } + + // Get the authentication realm + [self setAuthenticationRealm:nil]; + if (!CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) { + [self setAuthenticationRealm:[NSMakeCollectable(CFHTTPAuthenticationCopyRealm(requestAuthentication)) autorelease]]; + } + + #if DEBUG_HTTP_AUTHENTICATION + NSString *realm = [self authenticationRealm]; + if (realm) { + realm = [NSString stringWithFormat:@" (Realm: %@)",realm]; + } else { + realm = @""; + } + if ([self authenticationScheme] != (NSString *)kCFHTTPAuthenticationSchemeNTLM || [self authenticationRetryCount] == 0) { + ASI_DEBUG_LOG(@"[AUTH] Request %@ received 401 challenge and must authenticate using %@%@",self,[self authenticationScheme],realm); + } else { + ASI_DEBUG_LOG(@"[AUTH] Request %@ NTLM handshake step %i",self,[self authenticationRetryCount]+1); + } + #endif + + // See if authentication is valid + CFStreamError err; + if (!CFHTTPAuthenticationIsValid(requestAuthentication, &err)) { + + CFRelease(requestAuthentication); + requestAuthentication = NULL; + + // check for bad credentials, so we can give the delegate a chance to replace them + if (err.domain == kCFStreamErrorDomainHTTP && (err.error == kCFStreamErrorHTTPAuthenticationBadUserName || err.error == kCFStreamErrorHTTPAuthenticationBadPassword)) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ had bad credentials, will remove them from the session store if they are cached",self); + #endif + + // Prevent more than one request from asking for credentials at once + [delegateAuthenticationLock lock]; + + // We know the credentials we just presented are bad, we should remove them from the session store too + [[self class] removeAuthenticationCredentialsFromSessionStore:requestCredentials]; + [self setRequestCredentials:nil]; + + // If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us + if ([self error] || [self isCancelled]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ failed or was cancelled while waiting to access credentials",self); + #endif + + [delegateAuthenticationLock unlock]; + return; + } + + // Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request + if ([self useSessionPersistence]) { + NSDictionary *credentials = [self findSessionAuthenticationCredentials]; + if (credentials && [self applyCredentials:[credentials objectForKey:@"Credentials"]]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will reuse cached credentials from the session (%@)",self,[credentials objectForKey:@"AuthenticationScheme"]); + #endif + + [delegateAuthenticationLock unlock]; + [self startRequest]; + return; + } + } + + [self setLastActivityTime:nil]; + + if ([self willAskDelegateForCredentials]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will ask its delegate for credentials to use",self); + #endif + + [delegateAuthenticationLock unlock]; + return; + } + if ([self showAuthenticationDialog]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@CTASIAuthenticationDialogs",self); + #endif + + [delegateAuthenticationLock unlock]; + return; + } + [delegateAuthenticationLock unlock]; + } + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ has no credentials to present and must give up",self); + #endif + + [self cancelLoad]; + [self failWithError:ASIAuthenticationError]; + return; + } + + [self cancelLoad]; + + if (requestCredentials) { + + if ((([self authenticationScheme] != (NSString *)kCFHTTPAuthenticationSchemeNTLM) || [self authenticationRetryCount] < 2) && [self applyCredentials:requestCredentials]) { + [self startRequest]; + + // We've failed NTLM authentication twice, we should assume our credentials are wrong + } else if ([self authenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM && [self authenticationRetryCount ] == 2) { + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ has failed NTLM authentication",self); + #endif + + [self failWithError:ASIAuthenticationError]; + + } else { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ had credentials and they were not marked as bad, but we got a 401 all the same.",self); + #endif + + [self failWithError:[NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request", NSLocalizedDescriptionKey, nil]]]; + } + + // Are a user name & password needed? + } else if (CFHTTPAuthenticationRequiresUserNameAndPassword(requestAuthentication)) { + + // Prevent more than one request from asking for credentials at once + [delegateAuthenticationLock lock]; + + // If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us + if ([self error] || [self isCancelled]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ failed or was cancelled while waiting to access credentials",self); + #endif + + [delegateAuthenticationLock unlock]; + return; + } + + // Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request + if ([self useSessionPersistence]) { + NSDictionary *credentials = [self findSessionAuthenticationCredentials]; + if (credentials && [self applyCredentials:[credentials objectForKey:@"Credentials"]]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will reuse cached credentials from the session (%@)",self,[credentials objectForKey:@"AuthenticationScheme"]); + #endif + + [delegateAuthenticationLock unlock]; + [self startRequest]; + return; + } + } + + + NSMutableDictionary *newCredentials = [self findCredentials]; + + //If we have some credentials to use let's apply them to the request and continue + if (newCredentials) { + + if ([self applyCredentials:newCredentials]) { + [delegateAuthenticationLock unlock]; + [self startRequest]; + } else { + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ failed to apply credentials",self); + #endif + [delegateAuthenticationLock unlock]; + [self failWithError:[NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request", NSLocalizedDescriptionKey, nil]]]; + } + return; + } + if ([self willAskDelegateForCredentials]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will ask its delegate for credentials to use",self); + #endif + + [delegateAuthenticationLock unlock]; + return; + } + if ([self showAuthenticationDialog]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@CTASIAuthenticationDialogs",self); + #endif + + [delegateAuthenticationLock unlock]; + return; + } + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ has no credentials to present and must give up",self); + #endif + [delegateAuthenticationLock unlock]; + [self failWithError:ASIAuthenticationError]; + return; + } + +} + +- (void)addBasicAuthenticationHeaderWithUsername:(NSString *)theUsername andPassword:(NSString *)thePassword +{ + [self addRequestHeader:@"Authorization" value:[NSString stringWithFormat:@"Basic %@",[CTASIHTTPRequest base64forData:[[NSString stringWithFormat:@"%@:%@", theUsername, thePassword] dataUsingEncoding:NSUTF8StringEncoding]]]]; + [self setAuthenticationScheme:(NSString *)kCFHTTPAuthenticationSchemeBasic]; + +} + + +#pragma mark stream status handlers + +- (void)handleNetworkEvent:(CFStreamEventType)type +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [[self cancelledLock] lock]; + + if ([self complete] || [self isCancelled]) { + [[self cancelledLock] unlock]; + [pool drain]; + return; + } + + CFRetain(self); + + // Dispatch the stream events. + switch (type) { + case kCFStreamEventHasBytesAvailable: + [self handleBytesAvailable]; + break; + + case kCFStreamEventEndEncountered: + [self handleStreamComplete]; + break; + + case kCFStreamEventErrorOccurred: + [self handleStreamError]; + break; + + default: + break; + } + + [self performThrottling]; + + [[self cancelledLock] unlock]; + + if ([self downloadComplete] && [self needsRedirect]) { + if (![self willAskDelegateToConfirmRedirect]) { + [self performRedirect]; + } + } else if ([self downloadComplete] && [self authenticationNeeded]) { + [self attemptToApplyCredentialsAndResume]; + } + + CFRelease(self); + [pool drain]; +} + +- (BOOL)willAskDelegateToConfirmRedirect +{ + // We must lock to ensure delegate / queue aren't changed while we check them + [[self cancelledLock] lock]; + + // Here we perform an initial check to see if either the delegate or the queue wants to be asked about the redirect, because if not we should redirect straight away + // We will check again on the main thread later + BOOL needToAskDelegateAboutRedirect = (([self delegate] && [[self delegate] respondsToSelector:[self willRedirectSelector]]) || ([self queue] && [[self queue] respondsToSelector:@selector(request:willRedirectToURL:)])); + + [[self cancelledLock] unlock]; + + // Either the delegate or the queue's delegate is interested in being told when we are about to redirect + if (needToAskDelegateAboutRedirect) { + NSURL *newURL = [[[self redirectURL] copy] autorelease]; + [self setRedirectURL:nil]; + [self performSelectorOnMainThread:@selector(requestWillRedirectToURL:) withObject:newURL waitUntilDone:[NSThread isMainThread]]; + return true; + } + return false; +} + +- (void)handleBytesAvailable +{ + if (![self responseHeaders]) { + [self readResponseHeaders]; + } + + // If we've cancelled the load part way through (for example, after deciding to use a cached version) + if ([self complete]) { + return; + } + + // In certain (presumably very rare) circumstances, handleBytesAvailable seems to be called when there isn't actually any data available + // We'll check that there is actually data available to prevent blocking on CFReadStreamRead() + // So far, I've only seen this in the stress tests, so it might never happen in real-world situations. + if (!CFReadStreamHasBytesAvailable((CFReadStreamRef)[self readStream])) { + return; + } + + long long bufferSize = 16384; + if (contentLength > 262144) { + bufferSize = 262144; + } else if (contentLength > 65536) { + bufferSize = 65536; + } + + // Reduce the buffer size if we're receiving data too quickly when bandwidth throttling is active + // This just augments the throttling done in measureBandwidthUsage to reduce the amount we go over the limit + + if ([[self class] isBandwidthThrottled]) { + [bandwidthThrottlingLock lock]; + if (maxBandwidthPerSecond > 0) { + long long maxiumumSize = (long long)maxBandwidthPerSecond-(long long)bandwidthUsedInLastSecond; + if (maxiumumSize < 0) { + // We aren't supposed to read any more data right now, but we'll read a single byte anyway so the CFNetwork's buffer isn't full + bufferSize = 1; + } else if (maxiumumSize/4 < bufferSize) { + // We were going to fetch more data that we should be allowed, so we'll reduce the size of our read + bufferSize = maxiumumSize/4; + } + } + if (bufferSize < 1) { + bufferSize = 1; + } + [bandwidthThrottlingLock unlock]; + } + + + UInt8 buffer[bufferSize]; + NSInteger bytesRead = [[self readStream] read:buffer maxLength:sizeof(buffer)]; + + // Less than zero is an error + if (bytesRead < 0) { + [self handleStreamError]; + + // If zero bytes were read, wait for the EOF to come. + } else if (bytesRead) { + + // If we are inflating the response on the fly + NSData *inflatedData = nil; + if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) { + if (![self dataDecompressor]) { + [self setDataDecompressor:[CTASIDataDecompressor decompressor]]; + } + NSError *err = nil; + inflatedData = [[self dataDecompressor] uncompressBytes:buffer length:(NSUInteger)bytesRead error:&err]; + if (err) { + [self failWithError:err]; + return; + } + } + + [self setTotalBytesRead:[self totalBytesRead]+(NSUInteger)bytesRead]; + [self setLastActivityTime:[NSDate date]]; + + // For bandwidth measurement / throttling + [CTASIHTTPRequest incrementBandwidthUsedInLastSecond:(NSUInteger) bytesRead]; + + // If we need to redirect, and have automatic redirect on, and might be resuming a download, let's do nothing with the content + if ([self needsRedirect] && [self shouldRedirect] && [self allowResumeForFileDownloads]) { + return; + } + + BOOL dataWillBeHandledExternally = NO; + if ([[self delegate] respondsToSelector:[self didReceiveDataSelector]]) { + dataWillBeHandledExternally = YES; + } + #if NS_BLOCKS_AVAILABLE + if (dataReceivedBlock) { + dataWillBeHandledExternally = YES; + } + #endif + // Does the delegate want to handle the data manually? + if (dataWillBeHandledExternally) { + + NSData *data = nil; + if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) { + data = inflatedData; + } else { + data = [NSData dataWithBytes:buffer length:(NSUInteger)bytesRead]; + } + [self performSelectorOnMainThread:@selector(passOnReceivedData:) withObject:data waitUntilDone:[NSThread isMainThread]]; + + // Are we downloading to a file? + } else if ([self downloadDestinationPath]) { + BOOL append = NO; + if (![self fileDownloadOutputStream]) { + if (![self temporaryFileDownloadPath]) { + [self setTemporaryFileDownloadPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]]; + } else if ([self allowResumeForFileDownloads] && [[self requestHeaders] objectForKey:@"Range"]) { + if ([[self responseHeaders] objectForKey:@"Content-Range"]) { + append = YES; + } else { + [self incrementDownloadSizeBy:-(long long)[self partialDownloadSize]]; + [self setPartialDownloadSize:0]; + } + } + + [self setFileDownloadOutputStream:[[[NSOutputStream alloc] initToFileAtPath:[self temporaryFileDownloadPath] append:append] autorelease]]; + [[self fileDownloadOutputStream] open]; + + } + [[self fileDownloadOutputStream] write:buffer maxLength:(NSUInteger)bytesRead]; + + if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) { + + if (![self inflatedFileDownloadOutputStream]) { + if (![self temporaryUncompressedDataDownloadPath]) { + [self setTemporaryUncompressedDataDownloadPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]]; + } + + [self setInflatedFileDownloadOutputStream:[[[NSOutputStream alloc] initToFileAtPath:[self temporaryUncompressedDataDownloadPath] append:append] autorelease]]; + [[self inflatedFileDownloadOutputStream] open]; + } + + [[self inflatedFileDownloadOutputStream] write:[inflatedData bytes] maxLength:[inflatedData length]]; + } + + + //Otherwise, let's add the data to our in-memory store + } else { + if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) { + [rawResponseData appendData:inflatedData]; + } else { + [rawResponseData appendBytes:buffer length:(NSUInteger)bytesRead]; + } + } + } +} + +- (void)handleStreamComplete +{ + +#if DEBUG_REQUEST_STATUS + ASI_DEBUG_LOG(@"[STATUS] Request %@ finished downloading data (%qu bytes)",self, [self totalBytesRead]); +#endif + [self setStatusTimer:nil]; + [self setDownloadComplete:YES]; + + if (![self responseHeaders]) { + [self readResponseHeaders]; + } + + [progressLock lock]; + // Find out how much data we've uploaded so far + [self setLastBytesSent:totalBytesSent]; + [self setTotalBytesSent:[[NSMakeCollectable(CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPRequestBytesWrittenCount)) autorelease] unsignedLongLongValue]]; + [self setComplete:YES]; + if (![self contentLength]) { + [self setContentLength:[self totalBytesRead]]; + } + [self updateProgressIndicators]; + + + [[self postBodyReadStream] close]; + [self setPostBodyReadStream:nil]; + + [self setDataDecompressor:nil]; + + NSError *fileError = nil; + + // Delete up the request body temporary file, if it exists + if ([self didCreateTemporaryPostDataFile] && ![self authenticationNeeded]) { + [self removeTemporaryUploadFile]; + [self removeTemporaryCompressedUploadFile]; + } + + // Close the output stream as we're done writing to the file + if ([self temporaryFileDownloadPath]) { + + [[self fileDownloadOutputStream] close]; + [self setFileDownloadOutputStream:nil]; + + [[self inflatedFileDownloadOutputStream] close]; + [self setInflatedFileDownloadOutputStream:nil]; + + // If we are going to redirect and we are resuming, let's ignore this download + if ([self shouldRedirect] && [self needsRedirect] && [self allowResumeForFileDownloads]) { + + } else if ([self isResponseCompressed]) { + + // Decompress the file directly to the destination path + if ([self shouldWaitToInflateCompressedResponses]) { + [CTASIDataDecompressor uncompressDataFromFile:[self temporaryFileDownloadPath] toFile:[self downloadDestinationPath] error:&fileError]; + + // Response should already have been inflated, move the temporary file to the destination path + } else { + NSError *moveError = nil; + [[[[NSFileManager alloc] init] autorelease] moveItemAtPath:[self temporaryUncompressedDataDownloadPath] toPath:[self downloadDestinationPath] error:&moveError]; + if (moveError) { + fileError = [NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to move file from '%@' to '%@'", [self temporaryFileDownloadPath], [self downloadDestinationPath]], NSLocalizedDescriptionKey, moveError, NSUnderlyingErrorKey, nil]]; + } + [self setTemporaryUncompressedDataDownloadPath:nil]; + + } + [self removeTemporaryDownloadFile]; + + } else { + + //Remove any file at the destination path + NSError *moveError = nil; + if (![[self class] removeFileAtPath:[self downloadDestinationPath] error:&moveError]) { + fileError = moveError; + + } + + //Move the temporary file to the destination path + if (!fileError) { + [[[[NSFileManager alloc] init] autorelease] moveItemAtPath:[self temporaryFileDownloadPath] toPath:[self downloadDestinationPath] error:&moveError]; + if (moveError) { + fileError = [NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to move file from '%@' to '%@'", [self temporaryFileDownloadPath], [self downloadDestinationPath]], NSLocalizedDescriptionKey, moveError, NSUnderlyingErrorKey, nil]]; + } + [self setTemporaryFileDownloadPath:nil]; + } + + } + } + + // Save to the cache + if ([self downloadCache] && ![self didUseCachedResponse]) { + [[self downloadCache] storeResponseForRequest:self maxAge:[self secondsToCache]]; + } + + [progressLock unlock]; + + + [connectionsLock lock]; + if (![self connectionCanBeReused]) { + [self unscheduleReadStream]; + } + #if DEBUG_PERSISTENT_CONNECTIONS + if ([self requestID]) { + ASI_DEBUG_LOG(@"[CONNECTION] Request #%@ finished using connection #%@",[self requestID], [[self connectionInfo] objectForKey:@"id"]); + } + #endif + [[self connectionInfo] removeObjectForKey:@"request"]; + [[self connectionInfo] setObject:[NSDate dateWithTimeIntervalSinceNow:[self persistentConnectionTimeoutSeconds]] forKey:@"expires"]; + [connectionsLock unlock]; + + if (![self authenticationNeeded]) { + [self destroyReadStream]; + } + + + if (![self needsRedirect] && ![self authenticationNeeded] && ![self didUseCachedResponse]) { + + if (fileError) { + [self failWithError:fileError]; + } else { + [self requestFinished]; + } + + [self markAsFinished]; + + // If request has asked delegate or CTASIAuthenticationDialog for credentials + } else if ([self authenticationNeeded]) { + // Do nothing. + } + +} + +- (void)markAsFinished +{ + // Autoreleased requests may well be dealloced here otherwise + CFRetain(self); + + // dealloc won't be called when running with GC, so we'll clean these up now + if (request) { + CFRelease(request); + request = nil; + } + if (requestAuthentication) { + CFRelease(requestAuthentication); + requestAuthentication = nil; + } + if (proxyAuthentication) { + CFRelease(proxyAuthentication); + proxyAuthentication = nil; + } + + BOOL wasInProgress = inProgress; + BOOL wasFinished = finished; + + if (!wasFinished) + [self willChangeValueForKey:@"isFinished"]; + if (wasInProgress) + [self willChangeValueForKey:@"isExecuting"]; + + [self setInProgress:NO]; + finished = YES; + + if (wasInProgress) + [self didChangeValueForKey:@"isExecuting"]; + if (!wasFinished) + [self didChangeValueForKey:@"isFinished"]; + + #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 + if ([CTASIHTTPRequest isMultitaskingSupported] && [self shouldContinueWhenAppEntersBackground]) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (backgroundTask != UIBackgroundTaskInvalid) { + [[UIApplication sharedApplication] endBackgroundTask:backgroundTask]; + backgroundTask = UIBackgroundTaskInvalid; + } + }); + } + #endif + CFRelease(self); +} + +- (void)useDataFromCache +{ + NSDictionary *headers = [[self downloadCache] cachedResponseHeadersForURL:[self url]]; + NSString *dataPath = [[self downloadCache] pathToCachedResponseDataForURL:[self url]]; + + CTASIHTTPRequest *theRequest = self; + if ([self mainRequest]) { + theRequest = [self mainRequest]; + } + + if (headers && dataPath) { + + [self setResponseStatusCode:[[headers objectForKey:@"X-ASIHTTPRequest-Response-Status-Code"] intValue]]; + [self setDidUseCachedResponse:YES]; + [theRequest setResponseHeaders:headers]; + + if ([theRequest downloadDestinationPath]) { + [theRequest setDownloadDestinationPath:dataPath]; + } else { + [theRequest setRawResponseData:[NSMutableData dataWithData:[[self downloadCache] cachedResponseDataForURL:[self url]]]]; + } + [theRequest setContentLength:(unsigned long long)[[[self responseHeaders] objectForKey:@"Content-Length"] longLongValue]]; + [theRequest setTotalBytesRead:[self contentLength]]; + + [theRequest parseStringEncodingFromHeaders]; + + [theRequest setResponseCookies:[NSHTTPCookie cookiesWithResponseHeaderFields:headers forURL:[self url]]]; + + // See if we need to redirect + if ([self willRedirect]) { + if (![self willAskDelegateToConfirmRedirect]) { + [self performRedirect]; + } + return; + } + } + + [theRequest setComplete:YES]; + [theRequest setDownloadComplete:YES]; + + // If we're pulling data from the cache without contacting the server at all, we won't have set originalURL yet + if ([self redirectCount] == 0) { + [theRequest setOriginalURL:[theRequest url]]; + } + + [theRequest updateProgressIndicators]; + [theRequest requestFinished]; + [theRequest markAsFinished]; + if ([self mainRequest]) { + [self markAsFinished]; + } +} + +- (BOOL)retryUsingNewConnection +{ + if ([self retryCount] == 0) { + + [self setWillRetryRequest:YES]; + [self cancelLoad]; + [self setWillRetryRequest:NO]; + + #if DEBUG_PERSISTENT_CONNECTIONS + ASI_DEBUG_LOG(@"[CONNECTION] Request attempted to use connection #%@, but it has been closed - will retry with a new connection", [[self connectionInfo] objectForKey:@"id"]); + #endif + [connectionsLock lock]; + [[self connectionInfo] removeObjectForKey:@"request"]; + [persistentConnectionsPool removeObject:[self connectionInfo]]; + [self setConnectionInfo:nil]; + [connectionsLock unlock]; + [self setRetryCount:[self retryCount]+1]; + [self startRequest]; + return YES; + } + #if DEBUG_PERSISTENT_CONNECTIONS + ASI_DEBUG_LOG(@"[CONNECTION] Request attempted to use connection #%@, but it has been closed - we have already retried with a new connection, so we must give up", [[self connectionInfo] objectForKey:@"id"]); + #endif + return NO; +} + +- (void)handleStreamError + +{ + NSError *underlyingError = [NSMakeCollectable(CFReadStreamCopyError((CFReadStreamRef)[self readStream])) autorelease]; + + if (![self error]) { // We may already have handled this error + + // First, check for a 'socket not connected', 'broken pipe' or 'connection lost' error + // This may occur when we've attempted to reuse a connection that should have been closed + // If we get this, we need to retry the request + // We'll only do this once - if it happens again on retry, we'll give up + // -1005 = kCFURLErrorNetworkConnectionLost - this doesn't seem to be declared on Mac OS 10.5 + if (([[underlyingError domain] isEqualToString:NSPOSIXErrorDomain] && ([underlyingError code] == ENOTCONN || [underlyingError code] == EPIPE)) + || ([[underlyingError domain] isEqualToString:(NSString *)kCFErrorDomainCFNetwork] && [underlyingError code] == -1005)) { + if ([self retryUsingNewConnection]) { + return; + } + } + + NSString *reason = @"A connection failure occurred"; + + // We'll use a custom error message for SSL errors, but you should always check underlying error if you want more details + // For some reason SecureTransport.h doesn't seem to be available on iphone, so error codes hard-coded + // Also, iPhone seems to handle errors differently from Mac OS X - a self-signed certificate returns a different error code on each platform, so we'll just provide a general error + if ([[underlyingError domain] isEqualToString:NSOSStatusErrorDomain]) { + if ([underlyingError code] <= -9800 && [underlyingError code] >= -9818) { + reason = [NSString stringWithFormat:@"%@: SSL problem (Possible causes may include a bad/expired/self-signed certificate, clock set to wrong date)",reason]; + } + } + [self cancelLoad]; + [self failWithError:[NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIConnectionFailureErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:reason, NSLocalizedDescriptionKey, underlyingError, NSUnderlyingErrorKey, nil]]]; + } else { + [self cancelLoad]; + } + [self checkRequestStatus]; +} + +#pragma mark managing the read stream + +- (void)destroyReadStream +{ + if ([self readStream]) { + [self unscheduleReadStream]; + if (![self connectionCanBeReused]) { + [[self readStream] removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]]; + [[self readStream] close]; + } + [self setReadStream:nil]; + } +} + +- (void)scheduleReadStream +{ + if ([self readStream] && ![self readStreamIsScheduled]) { + + [connectionsLock lock]; + runningRequestCount++; + if (shouldUpdateNetworkActivityIndicator) { + [[self class] showNetworkActivityIndicator]; + } + [connectionsLock unlock]; + + // Reset the timeout + [self setLastActivityTime:[NSDate date]]; + CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL}; + CFReadStreamSetClient((CFReadStreamRef)[self readStream], kNetworkEvents, ReadStreamClientCallBack, &ctxt); + [[self readStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]]; + [self setReadStreamIsScheduled:YES]; + } +} + + +- (void)unscheduleReadStream +{ + if ([self readStream] && [self readStreamIsScheduled]) { + + [connectionsLock lock]; + runningRequestCount--; + if (shouldUpdateNetworkActivityIndicator && runningRequestCount == 0) { + // This call will wait half a second before turning off the indicator + // This can prevent flicker when you have a single request finish and then immediately start another request + // We run this on the main thread because we have no guarantee this thread will have a runloop in 0.5 seconds time + // We don't bother the cancel this call if we start a new request, because we'll check if requests are running before we hide it + [[self class] performSelectorOnMainThread:@selector(hideNetworkActivityIndicatorAfterDelay) withObject:nil waitUntilDone:[NSThread isMainThread]]; + } + [connectionsLock unlock]; + + CFReadStreamSetClient((CFReadStreamRef)[self readStream], kCFStreamEventNone, NULL, NULL); + [[self readStream] removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]]; + [self setReadStreamIsScheduled:NO]; + } +} + +#pragma mark cleanup + +- (BOOL)removeTemporaryDownloadFile +{ + NSError *err = nil; + if ([self temporaryFileDownloadPath]) { + if (![[self class] removeFileAtPath:[self temporaryFileDownloadPath] error:&err]) { + [self failWithError:err]; + } + [self setTemporaryFileDownloadPath:nil]; + } + return (!err); +} + +- (BOOL)removeTemporaryUncompressedDownloadFile +{ + NSError *err = nil; + if ([self temporaryUncompressedDataDownloadPath]) { + if (![[self class] removeFileAtPath:[self temporaryUncompressedDataDownloadPath] error:&err]) { + [self failWithError:err]; + } + [self setTemporaryUncompressedDataDownloadPath:nil]; + } + return (!err); +} + +- (BOOL)removeTemporaryUploadFile +{ + NSError *err = nil; + if ([self postBodyFilePath]) { + if (![[self class] removeFileAtPath:[self postBodyFilePath] error:&err]) { + [self failWithError:err]; + } + [self setPostBodyFilePath:nil]; + } + return (!err); +} + +- (BOOL)removeTemporaryCompressedUploadFile +{ + NSError *err = nil; + if ([self compressedPostBodyFilePath]) { + if (![[self class] removeFileAtPath:[self compressedPostBodyFilePath] error:&err]) { + [self failWithError:err]; + } + [self setCompressedPostBodyFilePath:nil]; + } + return (!err); +} + ++ (BOOL)removeFileAtPath:(NSString *)path error:(NSError **)err +{ + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + if ([fileManager fileExistsAtPath:path]) { + NSError *removeError = nil; + [fileManager removeItemAtPath:path error:&removeError]; + if (removeError) { + if (err) { + *err = [NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to delete file at path '%@'", path], NSLocalizedDescriptionKey, removeError, NSUnderlyingErrorKey, nil]]; + } + return NO; + } + } + return YES; +} + +#pragma mark Proxies + +- (BOOL)configureProxies +{ + // Have details of the proxy been set on this request + if (![self isPACFileRequest] && (![self proxyHost] && ![self proxyPort])) { + + // If not, we need to figure out what they'll be + NSArray *proxies = nil; + + // Have we been given a proxy auto config file? + if ([self PACurl]) { + + // If yes, we'll need to fetch the PAC file asynchronously, so we stop this request to wait until we have the proxy details. + [self fetchPACFile]; + return NO; + + // Detect proxy settings and apply them + } else { + +#if TARGET_OS_IPHONE + NSDictionary *proxySettings = [NSMakeCollectable(CFNetworkCopySystemProxySettings()) autorelease]; +#else + NSDictionary *proxySettings = [NSMakeCollectable(SCDynamicStoreCopyProxies(NULL)) autorelease]; +#endif + + proxies = [NSMakeCollectable(CFNetworkCopyProxiesForURL((CFURLRef)[self url], (CFDictionaryRef)proxySettings)) autorelease]; + + // Now check to see if the proxy settings contained a PAC url, we need to run the script to get the real list of proxies if so + NSDictionary *settings = [proxies objectAtIndex:0]; + if ([settings objectForKey:(NSString *)kCFProxyAutoConfigurationURLKey]) { + [self setPACurl:[settings objectForKey:(NSString *)kCFProxyAutoConfigurationURLKey]]; + [self fetchPACFile]; + return NO; + } + } + + if (!proxies) { + [self setReadStream:nil]; + [self failWithError:[NSError errorWithDomain:CTNetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to obtain information on proxy servers needed for request", NSLocalizedDescriptionKey, nil]]]; + return NO; + } + // I don't really understand why the dictionary returned by CFNetworkCopyProxiesForURL uses different key names from CFNetworkCopySystemProxySettings/SCDynamicStoreCopyProxies + // and why its key names are documented while those we actually need to use don't seem to be (passing the kCF* keys doesn't seem to work) + if ([proxies count] > 0) { + NSDictionary *settings = [proxies objectAtIndex:0]; + [self setProxyHost:[settings objectForKey:(NSString *)kCFProxyHostNameKey]]; + [self setProxyPort:[[settings objectForKey:(NSString *)kCFProxyPortNumberKey] intValue]]; + [self setProxyType:[settings objectForKey:(NSString *)kCFProxyTypeKey]]; + } + } + return YES; +} + + + +// Attempts to download a PAC (Proxy Auto-Configuration) file +// PAC files at file://, http:// and https:// addresses are supported +- (void)fetchPACFile +{ + // For file:// urls, we'll use an async NSInputStream (CTASIHTTPRequest does not support file:// urls) + if ([[self PACurl] isFileURL]) { + NSInputStream *stream = [[[NSInputStream alloc] initWithFileAtPath:[[self PACurl] path]] autorelease]; + [self setPACFileReadStream:stream]; + [stream setDelegate:(id)self]; + [stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]]; + [stream open]; + // If it takes more than timeOutSeconds to read the PAC, we'll just give up and assume no proxies + // We won't bother to handle cases where the first part of the PAC is read within timeOutSeconds, but the whole thing takes longer + // Either our PAC file is in easy reach, or it's going to slow things down to the point that it's probably better requests fail + [self performSelector:@selector(timeOutPACRead) withObject:nil afterDelay:[self timeOutSeconds]]; + return; + } + + NSString *scheme = [[[self PACurl] scheme] lowercaseString]; + if (![scheme isEqualToString:@"http"] && ![scheme isEqualToString:@"https"]) { + // Don't know how to read data from this URL, we'll have to give up + // We'll simply assume no proxies, and start the request as normal + [self startRequest]; + return; + } + + // Create an CTASIHTTPRequest to fetch the PAC file + CTASIHTTPRequest *PACRequest = [CTASIHTTPRequest requestWithURL:[self PACurl]]; + + // Will prevent this request attempting to configure proxy settings for itself + [PACRequest setIsPACFileRequest:YES]; + + [PACRequest setTimeOutSeconds:[self timeOutSeconds]]; + + // If we're a synchronous request, we'll download the PAC file synchronously + if ([self isSynchronous]) { + [PACRequest startSynchronous]; + if (![PACRequest error] && [PACRequest responseString]) { + [self runPACScript:[PACRequest responseString]]; + } + [self startRequest]; + return; + } + + [self setPACFileRequest:PACRequest]; + + // Force this request to run before others in the shared queue + [PACRequest setQueuePriority:NSOperationQueuePriorityHigh]; + + // We'll treat failure to download the PAC file the same as success - if we were unable to fetch a PAC file, we proceed as if we have no proxy server and let this request fail itself if necessary + [PACRequest setDelegate:self]; + [PACRequest setDidFinishSelector:@selector(finishedDownloadingPACFile:)]; + [PACRequest setDidFailSelector:@selector(finishedDownloadingPACFile:)]; + [PACRequest startAsynchronous]; + + // Temporarily increase the number of operations in the shared queue to give our request a chance to run + [connectionsLock lock]; + [sharedQueue setMaxConcurrentOperationCount:[sharedQueue maxConcurrentOperationCount]+1]; + [connectionsLock unlock]; +} + +// Called as we read the PAC file from a file:// url +- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode +{ + if (![self PACFileReadStream]) { + return; + } + if (eventCode == NSStreamEventHasBytesAvailable) { + + if (![self PACFileData]) { + [self setPACFileData:[NSMutableData data]]; + } + // If your PAC file is larger than 16KB, you're just being cruel. + uint8_t buf[16384]; + NSInteger len = [(NSInputStream *)stream read:buf maxLength:16384]; + // Append only if something was actually read. + if (len > 0) { + [[self PACFileData] appendBytes:(const void *)buf length:(NSUInteger)len]; + } + + } else if (eventCode == NSStreamEventErrorOccurred || eventCode == NSStreamEventEndEncountered) { + + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeOutPACRead) object:nil]; + + [stream close]; + [stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]]; + [self setPACFileReadStream:nil]; + + if (eventCode == NSStreamEventEndEncountered) { + // It sounds as though we have no idea what encoding a PAC file will use + static NSStringEncoding encodingsToTry[2] = {NSUTF8StringEncoding,NSISOLatin1StringEncoding}; + NSUInteger i; + for (i=0; i<2; i++) { + NSString *pacScript = [[[NSString alloc] initWithBytes:[[self PACFileData] bytes] length:[[self PACFileData] length] encoding:encodingsToTry[i]] autorelease]; + if (pacScript) { + [self runPACScript:pacScript]; + break; + } + } + } + [self setPACFileData:nil]; + [self startRequest]; + } +} + +// Called if it takes longer than timeOutSeconds to read the whole PAC file (when reading from a file:// url) +- (void)timeOutPACRead +{ + [self stream:[self PACFileReadStream] handleEvent:NSStreamEventErrorOccurred]; +} + +// Runs the downloaded PAC script +- (void)runPACScript:(NSString *)script +{ + if (script) { + // From: http://developer.apple.com/samplecode/CFProxySupportTool/listing1.html + // Work around . This dummy call to + // CFNetworkCopyProxiesForURL initialise some state within CFNetwork + // that is required by CFNetworkCopyProxiesForAutoConfigurationScript. + CFRelease(CFNetworkCopyProxiesForURL((CFURLRef)[self url], NULL)); + + // Obtain the list of proxies by running the autoconfiguration script + CFErrorRef err = NULL; + NSArray *proxies = [NSMakeCollectable(CFNetworkCopyProxiesForAutoConfigurationScript((CFStringRef)script,(CFURLRef)[self url], &err)) autorelease]; + if (!err && [proxies count] > 0) { + NSDictionary *settings = [proxies objectAtIndex:0]; + [self setProxyHost:[settings objectForKey:(NSString *)kCFProxyHostNameKey]]; + [self setProxyPort:[[settings objectForKey:(NSString *)kCFProxyPortNumberKey] intValue]]; + [self setProxyType:[settings objectForKey:(NSString *)kCFProxyTypeKey]]; + } + } +} + +// Called if we successfully downloaded a PAC file from a webserver +- (void)finishedDownloadingPACFile:(CTASIHTTPRequest *)theRequest +{ + if (![theRequest error] && [theRequest responseString]) { + [self runPACScript:[theRequest responseString]]; + } + + // Set the shared queue's maxConcurrentOperationCount back to normal + [connectionsLock lock]; + [sharedQueue setMaxConcurrentOperationCount:[sharedQueue maxConcurrentOperationCount]-1]; + [connectionsLock unlock]; + + // We no longer need our PAC file request + [self setPACFileRequest:nil]; + + // Start the request + [self startRequest]; +} + + +#pragma mark persistent connections + +- (NSNumber *)connectionID +{ + return [[self connectionInfo] objectForKey:@"id"]; +} + ++ (void)expirePersistentConnections +{ + [connectionsLock lock]; + NSUInteger i; + for (i=0; i<[persistentConnectionsPool count]; i++) { + NSDictionary *existingConnection = [persistentConnectionsPool objectAtIndex:i]; + if (![existingConnection objectForKey:@"request"] && [[existingConnection objectForKey:@"expires"] timeIntervalSinceNow] <= 0) { +#if DEBUG_PERSISTENT_CONNECTIONS + ASI_DEBUG_LOG(@"[CONNECTION] Closing connection #%i because it has expired",[[existingConnection objectForKey:@"id"] intValue]); +#endif + NSInputStream *stream = [existingConnection objectForKey:@"stream"]; + if (stream) { + [stream close]; + } + [persistentConnectionsPool removeObject:existingConnection]; + i--; + } + } + [connectionsLock unlock]; +} + +#pragma mark NSCopying +- (id)copyWithZone:(NSZone *)zone +{ + // Don't forget - this will return a retained copy! + CTASIHTTPRequest *newRequest = [[[self class] alloc] initWithURL:[self url]]; + [newRequest setDelegate:[self delegate]]; + [newRequest setRequestMethod:[self requestMethod]]; + [newRequest setPostBody:[self postBody]]; + [newRequest setShouldStreamPostDataFromDisk:[self shouldStreamPostDataFromDisk]]; + [newRequest setPostBodyFilePath:[self postBodyFilePath]]; + [newRequest setRequestHeaders:[[[self requestHeaders] mutableCopyWithZone:zone] autorelease]]; + [newRequest setRequestCookies:[[[self requestCookies] mutableCopyWithZone:zone] autorelease]]; + [newRequest setUseCookiePersistence:[self useCookiePersistence]]; + [newRequest setUseKeychainPersistence:[self useKeychainPersistence]]; + [newRequest setUseSessionPersistence:[self useSessionPersistence]]; + [newRequest setAllowCompressedResponse:[self allowCompressedResponse]]; + [newRequest setDownloadDestinationPath:[self downloadDestinationPath]]; + [newRequest setTemporaryFileDownloadPath:[self temporaryFileDownloadPath]]; + [newRequest setUsername:[self username]]; + [newRequest setPassword:[self password]]; + [newRequest setDomain:[self domain]]; + [newRequest setProxyUsername:[self proxyUsername]]; + [newRequest setProxyPassword:[self proxyPassword]]; + [newRequest setProxyDomain:[self proxyDomain]]; + [newRequest setProxyHost:[self proxyHost]]; + [newRequest setProxyPort:[self proxyPort]]; + [newRequest setProxyType:[self proxyType]]; + [newRequest setUploadProgressDelegate:[self uploadProgressDelegate]]; + [newRequest setDownloadProgressDelegate:[self downloadProgressDelegate]]; + [newRequest setShouldPresentAuthenticationDialog:[self shouldPresentAuthenticationDialog]]; + [newRequest setShouldPresentProxyAuthenticationDialog:[self shouldPresentProxyAuthenticationDialog]]; + [newRequest setPostLength:[self postLength]]; + [newRequest setHaveBuiltPostBody:[self haveBuiltPostBody]]; + [newRequest setDidStartSelector:[self didStartSelector]]; + [newRequest setDidFinishSelector:[self didFinishSelector]]; + [newRequest setDidFailSelector:[self didFailSelector]]; + [newRequest setTimeOutSeconds:[self timeOutSeconds]]; + [newRequest setShouldResetDownloadProgress:[self shouldResetDownloadProgress]]; + [newRequest setShouldResetUploadProgress:[self shouldResetUploadProgress]]; + [newRequest setShowAccurateProgress:[self showAccurateProgress]]; + [newRequest setDefaultResponseEncoding:[self defaultResponseEncoding]]; + [newRequest setAllowResumeForFileDownloads:[self allowResumeForFileDownloads]]; + [newRequest setUserInfo:[[[self userInfo] copyWithZone:zone] autorelease]]; + [newRequest setTag:[self tag]]; + [newRequest setUseHTTPVersionOne:[self useHTTPVersionOne]]; + [newRequest setShouldRedirect:[self shouldRedirect]]; + [newRequest setValidatesSecureCertificate:[self validatesSecureCertificate]]; + [newRequest setClientCertificateIdentity:clientCertificateIdentity]; + [newRequest setClientCertificates:[[clientCertificates copy] autorelease]]; + [newRequest setPACurl:[self PACurl]]; + [newRequest setShouldPresentCredentialsBeforeChallenge:[self shouldPresentCredentialsBeforeChallenge]]; + [newRequest setNumberOfTimesToRetryOnTimeout:[self numberOfTimesToRetryOnTimeout]]; + [newRequest setShouldUseRFC2616RedirectBehaviour:[self shouldUseRFC2616RedirectBehaviour]]; + [newRequest setShouldAttemptPersistentConnection:[self shouldAttemptPersistentConnection]]; + [newRequest setPersistentConnectionTimeoutSeconds:[self persistentConnectionTimeoutSeconds]]; + [newRequest setAuthenticationScheme:[self authenticationScheme]]; + return newRequest; +} + +#pragma mark default time out + ++ (NSTimeInterval)defaultTimeOutSeconds +{ + return defaultTimeOutSeconds; +} + ++ (void)setDefaultTimeOutSeconds:(NSTimeInterval)newTimeOutSeconds +{ + defaultTimeOutSeconds = newTimeOutSeconds; +} + + +#pragma mark client certificate + +- (void)setClientCertificateIdentity:(SecIdentityRef)anIdentity { + if(clientCertificateIdentity) { + CFRelease(clientCertificateIdentity); + } + + clientCertificateIdentity = anIdentity; + + if (clientCertificateIdentity) { + CFRetain(clientCertificateIdentity); + } +} + + +#pragma mark session credentials + ++ (NSMutableArray *)sessionProxyCredentialsStore +{ + [sessionCredentialsLock lock]; + if (!sessionProxyCredentialsStore) { + sessionProxyCredentialsStore = [[NSMutableArray alloc] init]; + } + [sessionCredentialsLock unlock]; + return sessionProxyCredentialsStore; +} + ++ (NSMutableArray *)sessionCredentialsStore +{ + [sessionCredentialsLock lock]; + if (!sessionCredentialsStore) { + sessionCredentialsStore = [[NSMutableArray alloc] init]; + } + [sessionCredentialsLock unlock]; + return sessionCredentialsStore; +} + ++ (void)storeProxyAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials +{ + [sessionCredentialsLock lock]; + [self removeProxyAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]]; + [[[self class] sessionProxyCredentialsStore] addObject:credentials]; + [sessionCredentialsLock unlock]; +} + ++ (void)storeAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials +{ + [sessionCredentialsLock lock]; + [self removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]]; + [[[self class] sessionCredentialsStore] addObject:credentials]; + [sessionCredentialsLock unlock]; +} + ++ (void)removeProxyAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials +{ + [sessionCredentialsLock lock]; + NSMutableArray *sessionCredentialsList = [[self class] sessionProxyCredentialsStore]; + NSUInteger i; + for (i=0; i<[sessionCredentialsList count]; i++) { + NSDictionary *theCredentials = [sessionCredentialsList objectAtIndex:i]; + if ([theCredentials objectForKey:@"Credentials"] == credentials) { + [sessionCredentialsList removeObjectAtIndex:i]; + [sessionCredentialsLock unlock]; + return; + } + } + [sessionCredentialsLock unlock]; +} + ++ (void)removeAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials +{ + [sessionCredentialsLock lock]; + NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore]; + NSUInteger i; + for (i=0; i<[sessionCredentialsList count]; i++) { + NSDictionary *theCredentials = [sessionCredentialsList objectAtIndex:i]; + if ([theCredentials objectForKey:@"Credentials"] == credentials) { + [sessionCredentialsList removeObjectAtIndex:i]; + [sessionCredentialsLock unlock]; + return; + } + } + [sessionCredentialsLock unlock]; +} + +- (NSDictionary *)findSessionProxyAuthenticationCredentials +{ + [sessionCredentialsLock lock]; + NSMutableArray *sessionCredentialsList = [[self class] sessionProxyCredentialsStore]; + for (NSDictionary *theCredentials in sessionCredentialsList) { + if ([[theCredentials objectForKey:@"Host"] isEqualToString:[self proxyHost]] && [[theCredentials objectForKey:@"Port"] intValue] == [self proxyPort]) { + [sessionCredentialsLock unlock]; + return theCredentials; + } + } + [sessionCredentialsLock unlock]; + return nil; +} + + +- (NSDictionary *)findSessionAuthenticationCredentials +{ + [sessionCredentialsLock lock]; + NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore]; + NSURL *requestURL = [self url]; + + BOOL haveFoundExactMatch; + NSDictionary *closeMatch = nil; + + // Loop through all the cached credentials we have, looking for the best match for this request + for (NSDictionary *theCredentials in sessionCredentialsList) { + + haveFoundExactMatch = NO; + NSURL *cachedCredentialsURL = [theCredentials objectForKey:@"URL"]; + + // Find an exact match (same url) + if ([cachedCredentialsURL isEqual:[self url]]) { + haveFoundExactMatch = YES; + + // This is not an exact match for the url, and we already have a close match we can use + } else if (closeMatch) { + continue; + + // Find a close match (same host, scheme and port) + } else if ([[cachedCredentialsURL host] isEqualToString:[requestURL host]] && ([cachedCredentialsURL port] == [requestURL port] || ([requestURL port] && [[cachedCredentialsURL port] isEqualToNumber:[requestURL port]])) && [[cachedCredentialsURL scheme] isEqualToString:[requestURL scheme]]) { + } else { + continue; + } + + // Just a sanity check to ensure we never choose credentials from a different realm. Can't really do more than that, as either this request or the stored credentials may not have a realm when the other does + if ([self authenticationRealm] && ([theCredentials objectForKey:@"AuthenticationRealm"] && ![[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) { + continue; + } + + // If we have a username and password set on the request, check that they are the same as the cached ones + if ([self username] && [self password]) { + NSDictionary *usernameAndPassword = [theCredentials objectForKey:@"Credentials"]; + NSString *storedUsername = [usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername]; + NSString *storedPassword = [usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]; + if (![storedUsername isEqualToString:[self username]] || ![storedPassword isEqualToString:[self password]]) { + continue; + } + } + + // If we have an exact match for the url, use those credentials + if (haveFoundExactMatch) { + [sessionCredentialsLock unlock]; + return theCredentials; + } + + // We have no exact match, let's remember that we have a good match for this server, and we'll use it at the end if we don't find an exact match + closeMatch = theCredentials; + } + [sessionCredentialsLock unlock]; + + // Return credentials that matched on host, port and scheme, or nil if we didn't find any + return closeMatch; +} + +#pragma mark keychain storage + ++ (void)saveCredentials:(NSURLCredential *)credentials forHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm +{ + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease]; + [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credentials forProtectionSpace:protectionSpace]; +} + ++ (void)saveCredentials:(NSURLCredential *)credentials forProxy:(NSString *)host port:(int)port realm:(NSString *)realm +{ + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease]; + [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credentials forProtectionSpace:protectionSpace]; +} + ++ (NSURLCredential *)savedCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm +{ + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease]; + return [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace]; +} + ++ (NSURLCredential *)savedCredentialsForProxy:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm +{ + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease]; + return [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace]; +} + ++ (void)removeCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm +{ + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease]; + NSURLCredential *credential = [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace]; + if (credential) { + [[NSURLCredentialStorage sharedCredentialStorage] removeCredential:credential forProtectionSpace:protectionSpace]; + } +} + ++ (void)removeCredentialsForProxy:(NSString *)host port:(int)port realm:(NSString *)realm +{ + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease]; + NSURLCredential *credential = [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace]; + if (credential) { + [[NSURLCredentialStorage sharedCredentialStorage] removeCredential:credential forProtectionSpace:protectionSpace]; + } +} + ++ (NSMutableArray *)sessionCookies +{ + [sessionCookiesLock lock]; + if (!sessionCookies) { + [CTASIHTTPRequest setSessionCookies:[NSMutableArray array]]; + } + NSMutableArray *cookies = [[sessionCookies retain] autorelease]; + [sessionCookiesLock unlock]; + return cookies; +} + ++ (void)setSessionCookies:(NSMutableArray *)newSessionCookies +{ + [sessionCookiesLock lock]; + // Remove existing cookies from the persistent store + for (NSHTTPCookie *cookie in sessionCookies) { + [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie]; + } + [sessionCookies release]; + sessionCookies = [newSessionCookies retain]; + [sessionCookiesLock unlock]; +} + ++ (void)addSessionCookie:(NSHTTPCookie *)newCookie +{ + [sessionCookiesLock lock]; + NSHTTPCookie *cookie; + NSUInteger i; + NSUInteger max = [[CTASIHTTPRequest sessionCookies] count]; + for (i=0; i 0) { + if ([self readStreamIsScheduled]) { + [self unscheduleReadStream]; + #if DEBUG_THROTTLING + ASI_DEBUG_LOG(@"[THROTTLING] Sleeping request %@ until after %@",self,throttleWakeUpTime); + #endif + } + } else { + if (![self readStreamIsScheduled]) { + [self scheduleReadStream]; + #if DEBUG_THROTTLING + ASI_DEBUG_LOG(@"[THROTTLING] Waking up request %@",self); + #endif + } + } + } + [bandwidthThrottlingLock unlock]; + + // Bandwidth throttling must have been turned off since we last looked, let's re-schedule the stream + } else if (![self readStreamIsScheduled]) { + [self scheduleReadStream]; + } +} + ++ (BOOL)isBandwidthThrottled +{ +#if TARGET_OS_IPHONE + [bandwidthThrottlingLock lock]; + + BOOL throttle = isBandwidthThrottled || (!shouldThrottleBandwidthForWWANOnly && (maxBandwidthPerSecond > 0)); + [bandwidthThrottlingLock unlock]; + return throttle; +#else + [bandwidthThrottlingLock lock]; + BOOL throttle = (maxBandwidthPerSecond > 0); + [bandwidthThrottlingLock unlock]; + return throttle; +#endif +} + ++ (unsigned long)maxBandwidthPerSecond +{ + [bandwidthThrottlingLock lock]; + unsigned long amount = maxBandwidthPerSecond; + [bandwidthThrottlingLock unlock]; + return amount; +} + ++ (void)setMaxBandwidthPerSecond:(unsigned long)bytes +{ + [bandwidthThrottlingLock lock]; + maxBandwidthPerSecond = bytes; + [bandwidthThrottlingLock unlock]; +} + ++ (void)incrementBandwidthUsedInLastSecond:(unsigned long)bytes +{ + [bandwidthThrottlingLock lock]; + bandwidthUsedInLastSecond += bytes; + [bandwidthThrottlingLock unlock]; +} + ++ (void)recordBandwidthUsage +{ + if (bandwidthUsedInLastSecond == 0) { + [bandwidthUsageTracker removeAllObjects]; + } else { + NSTimeInterval interval = [bandwidthMeasurementDate timeIntervalSinceNow]; + while ((interval < 0 || [bandwidthUsageTracker count] > 5) && [bandwidthUsageTracker count] > 0) { + [bandwidthUsageTracker removeObjectAtIndex:0]; + interval++; + } + } + #if DEBUG_THROTTLING + ASI_DEBUG_LOG(@"[THROTTLING] ===Used: %u bytes of bandwidth in last measurement period===",bandwidthUsedInLastSecond); + #endif + [bandwidthUsageTracker addObject:[NSNumber numberWithUnsignedLong:bandwidthUsedInLastSecond]]; + [bandwidthMeasurementDate release]; + bandwidthMeasurementDate = [[NSDate dateWithTimeIntervalSinceNow:1] retain]; + bandwidthUsedInLastSecond = 0; + + NSUInteger measurements = [bandwidthUsageTracker count]; + unsigned long totalBytes = 0; + for (NSNumber *bytes in bandwidthUsageTracker) { + totalBytes += [bytes unsignedLongValue]; + } + if (measurements > 0) + averageBandwidthUsedPerSecond = totalBytes/measurements; +} + ++ (unsigned long)averageBandwidthUsedPerSecond +{ + [bandwidthThrottlingLock lock]; + unsigned long amount = averageBandwidthUsedPerSecond; + [bandwidthThrottlingLock unlock]; + return amount; +} + ++ (void)measureBandwidthUsage +{ + // Other requests may have to wait for this lock if we're sleeping, but this is fine, since in that case we already know they shouldn't be sending or receiving data + [bandwidthThrottlingLock lock]; + + if (!bandwidthMeasurementDate || [bandwidthMeasurementDate timeIntervalSinceNow] < -0) { + [CTASIHTTPRequest recordBandwidthUsage]; + } + + // Are we performing bandwidth throttling? + if ( + #if TARGET_OS_IPHONE + isBandwidthThrottled || (!shouldThrottleBandwidthForWWANOnly && (maxBandwidthPerSecond)) + #else + maxBandwidthPerSecond + #endif + ) { + // How much data can we still send or receive this second? + long long bytesRemaining = (long long)maxBandwidthPerSecond - (long long)bandwidthUsedInLastSecond; + + // Have we used up our allowance? + if (bytesRemaining < 0) { + + // Yes, put this request to sleep until a second is up, with extra added punishment sleeping time for being very naughty (we have used more bandwidth than we were allowed) + double extraSleepyTime = (-bytesRemaining/(maxBandwidthPerSecond*1.0)); + [throttleWakeUpTime release]; + throttleWakeUpTime = [[NSDate alloc] initWithTimeInterval:extraSleepyTime sinceDate:bandwidthMeasurementDate]; + } + } + [bandwidthThrottlingLock unlock]; +} + ++ (unsigned long)maxUploadReadLength +{ + [bandwidthThrottlingLock lock]; + + // We'll split our bandwidth allowance into 4 (which is the default for an CTASINetworkQueue's max concurrent operations count) to give all running requests a fighting chance of reading data this cycle + long long toRead = maxBandwidthPerSecond/4; + if (maxBandwidthPerSecond > 0 && (bandwidthUsedInLastSecond + toRead > maxBandwidthPerSecond)) { + toRead = (long long)maxBandwidthPerSecond-(long long)bandwidthUsedInLastSecond; + if (toRead < 0) { + toRead = 0; + } + } + + if (toRead == 0 || !bandwidthMeasurementDate || [bandwidthMeasurementDate timeIntervalSinceNow] < -0) { + [throttleWakeUpTime release]; + throttleWakeUpTime = [bandwidthMeasurementDate retain]; + } + [bandwidthThrottlingLock unlock]; + return (unsigned long)toRead; +} + + +#if TARGET_OS_IPHONE ++ (void)setShouldThrottleBandwidthForWWAN:(BOOL)throttle +{ + if (throttle) { + [CTASIHTTPRequest throttleBandwidthForWWANUsingLimit:ASIWWANBandwidthThrottleAmount]; + } else { + [CTASIHTTPRequest unsubscribeFromNetworkReachabilityNotifications]; + [CTASIHTTPRequest setMaxBandwidthPerSecond:0]; + [bandwidthThrottlingLock lock]; + isBandwidthThrottled = NO; + shouldThrottleBandwidthForWWANOnly = NO; + [bandwidthThrottlingLock unlock]; + } +} + ++ (void)throttleBandwidthForWWANUsingLimit:(unsigned long)limit +{ + [bandwidthThrottlingLock lock]; + shouldThrottleBandwidthForWWANOnly = YES; + maxBandwidthPerSecond = limit; + [CTASIHTTPRequest registerForNetworkReachabilityNotifications]; + [bandwidthThrottlingLock unlock]; + [CTASIHTTPRequest reachabilityChanged:nil]; +} + +#pragma mark reachability + ++ (void)registerForNetworkReachabilityNotifications +{ + [[CTReachability reachabilityForInternetConnection] startNotifier]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kCTReachabilityChangedNotification object:nil]; +} + + ++ (void)unsubscribeFromNetworkReachabilityNotifications +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:kCTReachabilityChangedNotification object:nil]; +} + ++ (BOOL)isNetworkReachableViaWWAN +{ + return ([[CTReachability reachabilityForInternetConnection] currentReachabilityStatus] == CTReachableViaWWAN); +} + ++ (void)reachabilityChanged:(NSNotification *)note +{ + [bandwidthThrottlingLock lock]; + isBandwidthThrottled = [CTASIHTTPRequest isNetworkReachableViaWWAN]; + [bandwidthThrottlingLock unlock]; +} +#endif + +#pragma mark queue + +// Returns the shared queue ++ (NSOperationQueue *)sharedQueue +{ + return [[sharedQueue retain] autorelease]; +} + +#pragma mark cache + ++ (void)setDefaultCache:(id )cache +{ + @synchronized (self) { + [cache retain]; + [defaultCache release]; + defaultCache = cache; + } +} + ++ (id )defaultCache +{ + @synchronized(self) { + return [[defaultCache retain] autorelease]; + } + return nil; +} + + +#pragma mark network activity + ++ (BOOL)isNetworkInUse +{ + [connectionsLock lock]; + BOOL inUse = (runningRequestCount > 0); + [connectionsLock unlock]; + return inUse; +} + ++ (void)setShouldUpdateNetworkActivityIndicator:(BOOL)shouldUpdate +{ + [connectionsLock lock]; + shouldUpdateNetworkActivityIndicator = shouldUpdate; + [connectionsLock unlock]; +} + ++ (void)showNetworkActivityIndicator +{ +#if TARGET_OS_IPHONE + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; +#endif +} + ++ (void)hideNetworkActivityIndicator +{ +#if TARGET_OS_IPHONE + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; +#endif +} + + +/* Always called on main thread */ ++ (void)hideNetworkActivityIndicatorAfterDelay +{ + [self performSelector:@selector(hideNetworkActivityIndicatorIfNeeeded) withObject:nil afterDelay:0.5]; +} + ++ (void)hideNetworkActivityIndicatorIfNeeeded +{ + [connectionsLock lock]; + if (runningRequestCount == 0) { + [self hideNetworkActivityIndicator]; + } + [connectionsLock unlock]; +} + + +#pragma mark threading behaviour + +// In the default implementation, all requests run in a single background thread +// Advanced users only: Override this method in a subclass for a different threading behaviour +// Eg: return [NSThread mainThread] to run all requests in the main thread +// Alternatively, you can create a thread on demand, or manage a pool of threads +// Threads returned by this method will need to run the runloop in default mode (eg CFRunLoopRun()) +// Requests will stop the runloop when they complete +// If you have multiple requests sharing the thread or you want to re-use the thread, you'll need to restart the runloop ++ (NSThread *)threadForRequest:(CTASIHTTPRequest *)request +{ + if (networkThread == nil) { + @synchronized(self) { + if (networkThread == nil) { + networkThread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequests) object:nil]; + [networkThread start]; + } + } + } + return networkThread; +} + ++ (void)runRequests +{ + // Should keep the runloop from exiting + CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; + CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); + CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); + + BOOL runAlways = YES; // Introduced to cheat Static Analyzer + while (runAlways) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true); + [pool drain]; + } + + // Should never be called, but anyway + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); + CFRelease(source); +} + +#pragma mark miscellany + +#if TARGET_OS_IPHONE ++ (BOOL)isMultitaskingSupported +{ + BOOL multiTaskingSupported = NO; + if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)]) { + multiTaskingSupported = [(id)[UIDevice currentDevice] isMultitaskingSupported]; + } + return multiTaskingSupported; +} +#endif + +// From: http://www.cocoadev.com/index.pl?BaseSixtyFour + ++ (NSString*)base64forData:(NSData*)theData { + + const uint8_t* input = (const uint8_t*)[theData bytes]; + NSUInteger length = [theData length]; + + static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4]; + uint8_t* output = (uint8_t*)data.mutableBytes; + + NSUInteger i,i2; + for (i=0; i < length; i += 3) { + NSInteger value = 0; + for (i2=0; i2<3; i2++) { + value <<= 8; + if (i+i2 < length) { + value |= (0xFF & input[i+i2]); + } + } + + NSInteger theIndex = (i / 3) * 4; + output[theIndex + 0] = (uint8_t)table[(value >> 18) & 0x3F]; + output[theIndex + 1] = (uint8_t)table[(value >> 12) & 0x3F]; + output[theIndex + 2] = (i + 1) < length ? (uint8_t)table[(value >> 6) & 0x3F] : '='; + output[theIndex + 3] = (i + 2) < length ? (uint8_t)table[(value >> 0) & 0x3F] : '='; + } + + return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease]; +} + ++ (NSDate *)expiryDateForRequest:(CTASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge +{ + NSDictionary *responseHeaders = [request responseHeaders]; + + // If we weren't given a custom max-age, lets look for one in the response headers + if (!maxAge) { + NSString *cacheControl = [[responseHeaders objectForKey:@"Cache-Control"] lowercaseString]; + if (cacheControl) { + NSScanner *scanner = [NSScanner scannerWithString:cacheControl]; + [scanner scanUpToString:@"max-age" intoString:NULL]; + if ([scanner scanString:@"max-age" intoString:NULL]) { + [scanner scanString:@"=" intoString:NULL]; + [scanner scanDouble:&maxAge]; + } + } + } + + // RFC 2612 says max-age must override any Expires header + if (maxAge) { + NSDate *date = [NSDate date]; + if ([date respondsToSelector:@selector(dateByAddingTimeInterval:)]) { + return [date dateByAddingTimeInterval:maxAge]; + } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + return [date addTimeInterval:maxAge]; +#pragma clang diagnostic pop + } + } else { + NSString *expires = [responseHeaders objectForKey:@"Expires"]; + if (expires) { + return [CTASIHTTPRequest dateFromRFC1123String:expires]; + } + } + return nil; +} + +// Based on hints from http://stackoverflow.com/questions/1850824/parsing-a-rfc-822-date-with-nsdateformatter ++ (NSDate *)dateFromRFC1123String:(NSString *)string +{ + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]]; + // Does the string include a week day? + NSString *day = @""; + if ([string rangeOfString:@","].location != NSNotFound) { + day = @"EEE, "; + } + // Does the string include seconds? + NSString *seconds = @""; + if ([[string componentsSeparatedByString:@":"] count] == 3) { + seconds = @":ss"; + } + [formatter setDateFormat:[NSString stringWithFormat:@"%@dd MMM yyyy HH:mm%@ z",day,seconds]]; + return [formatter dateFromString:string]; +} + ++ (void)parseMimeType:(NSString **)mimeType andResponseEncoding:(NSStringEncoding *)stringEncoding fromContentType:(NSString *)contentType +{ + if (!contentType) { + return; + } + NSScanner *charsetScanner = [NSScanner scannerWithString: contentType]; + if (![charsetScanner scanUpToString:@";" intoString:mimeType] || [charsetScanner scanLocation] == [contentType length]) { + *mimeType = [contentType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + return; + } + *mimeType = [*mimeType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + NSString *charsetSeparator = @"charset="; + NSString *IANAEncoding = nil; + + if ([charsetScanner scanUpToString: charsetSeparator intoString: NULL] && [charsetScanner scanLocation] < [contentType length]) { + [charsetScanner setScanLocation: [charsetScanner scanLocation] + [charsetSeparator length]]; + [charsetScanner scanUpToString: @";" intoString: &IANAEncoding]; + } + + if (IANAEncoding) { + CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)IANAEncoding); + if (cfEncoding != kCFStringEncodingInvalidId) { + *stringEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); + } + } +} + +#pragma mark - +#pragma mark blocks +#if NS_BLOCKS_AVAILABLE +- (void)setStartedBlock:(ASIBasicBlock)aStartedBlock +{ + [startedBlock release]; + startedBlock = [aStartedBlock copy]; +} + +- (void)setHeadersReceivedBlock:(ASIHeadersBlock)aReceivedBlock +{ + [headersReceivedBlock release]; + headersReceivedBlock = [aReceivedBlock copy]; +} + +- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock +{ + [completionBlock release]; + completionBlock = [aCompletionBlock copy]; +} + +- (void)setFailedBlock:(ASIBasicBlock)aFailedBlock +{ + [failureBlock release]; + failureBlock = [aFailedBlock copy]; +} + +- (void)setBytesReceivedBlock:(ASIProgressBlock)aBytesReceivedBlock +{ + [bytesReceivedBlock release]; + bytesReceivedBlock = [aBytesReceivedBlock copy]; +} + +- (void)setBytesSentBlock:(ASIProgressBlock)aBytesSentBlock +{ + [bytesSentBlock release]; + bytesSentBlock = [aBytesSentBlock copy]; +} + +- (void)setDownloadSizeIncrementedBlock:(ASISizeBlock)aDownloadSizeIncrementedBlock{ + [downloadSizeIncrementedBlock release]; + downloadSizeIncrementedBlock = [aDownloadSizeIncrementedBlock copy]; +} + +- (void)setUploadSizeIncrementedBlock:(ASISizeBlock)anUploadSizeIncrementedBlock +{ + [uploadSizeIncrementedBlock release]; + uploadSizeIncrementedBlock = [anUploadSizeIncrementedBlock copy]; +} + +- (void)setDataReceivedBlock:(ASIDataBlock)aReceivedBlock +{ + [dataReceivedBlock release]; + dataReceivedBlock = [aReceivedBlock copy]; +} + +- (void)setAuthenticationNeededBlock:(ASIBasicBlock)anAuthenticationBlock +{ + [authenticationNeededBlock release]; + authenticationNeededBlock = [anAuthenticationBlock copy]; +} +- (void)setProxyAuthenticationNeededBlock:(ASIBasicBlock)aProxyAuthenticationBlock +{ + [proxyAuthenticationNeededBlock release]; + proxyAuthenticationNeededBlock = [aProxyAuthenticationBlock copy]; +} +- (void)setRequestRedirectedBlock:(ASIBasicBlock)aRedirectBlock +{ + [requestRedirectedBlock release]; + requestRedirectedBlock = [aRedirectBlock copy]; +} +#endif + +#pragma mark === + +@synthesize username; +@synthesize password; +@synthesize userAgentString; +@synthesize domain; +@synthesize proxyUsername; +@synthesize proxyPassword; +@synthesize proxyDomain; +@synthesize url; +@synthesize originalURL; +@synthesize delegate; +@synthesize queue; +@synthesize uploadProgressDelegate; +@synthesize downloadProgressDelegate; +@synthesize useKeychainPersistence; +@synthesize useSessionPersistence; +@synthesize useCookiePersistence; +@synthesize downloadDestinationPath; +@synthesize temporaryFileDownloadPath; +@synthesize temporaryUncompressedDataDownloadPath; +@synthesize didStartSelector; +@synthesize didReceiveResponseHeadersSelector; +@synthesize willRedirectSelector; +@synthesize didFinishSelector; +@synthesize didFailSelector; +@synthesize didReceiveDataSelector; +@synthesize authenticationRealm; +@synthesize proxyAuthenticationRealm; +@synthesize error; +@synthesize complete; +@synthesize requestHeaders; +@synthesize responseHeaders; +@synthesize responseCookies; +@synthesize requestCookies; +@synthesize requestCredentials; +@synthesize responseStatusCode; +@synthesize rawResponseData; +@synthesize lastActivityTime; +@synthesize timeOutSeconds; +@synthesize requestMethod; +@synthesize postBody; +@synthesize compressedPostBody; +@synthesize contentLength; +@synthesize partialDownloadSize; +@synthesize postLength; +@synthesize shouldResetDownloadProgress; +@synthesize shouldResetUploadProgress; +@synthesize mainRequest; +@synthesize totalBytesRead; +@synthesize totalBytesSent; +@synthesize showAccurateProgress; +@synthesize uploadBufferSize; +@synthesize defaultResponseEncoding; +@synthesize responseEncoding; +@synthesize allowCompressedResponse; +@synthesize allowResumeForFileDownloads; +@synthesize userInfo; +@synthesize tag; +@synthesize postBodyFilePath; +@synthesize compressedPostBodyFilePath; +@synthesize postBodyWriteStream; +@synthesize postBodyReadStream; +@synthesize shouldStreamPostDataFromDisk; +@synthesize didCreateTemporaryPostDataFile; +@synthesize useHTTPVersionOne; +@synthesize lastBytesRead; +@synthesize lastBytesSent; +@synthesize cancelledLock; +@synthesize haveBuiltPostBody; +@synthesize fileDownloadOutputStream; +@synthesize inflatedFileDownloadOutputStream; +@synthesize authenticationRetryCount; +@synthesize proxyAuthenticationRetryCount; +@synthesize updatedProgress; +@synthesize shouldRedirect; +@synthesize validatesSecureCertificate; +@synthesize needsRedirect; +@synthesize redirectCount; +@synthesize shouldCompressRequestBody; +@synthesize proxyCredentials; +@synthesize proxyHost; +@synthesize proxyPort; +@synthesize proxyType; +@synthesize PACurl; +@synthesize authenticationScheme; +@synthesize proxyAuthenticationScheme; +@synthesize shouldPresentAuthenticationDialog; +@synthesize shouldPresentProxyAuthenticationDialog; +@synthesize authenticationNeeded; +@synthesize responseStatusMessage; +@synthesize shouldPresentCredentialsBeforeChallenge; +@synthesize haveBuiltRequestHeaders; +@synthesize inProgress; +@synthesize numberOfTimesToRetryOnTimeout; +@synthesize retryCount; +@synthesize willRetryRequest; +@synthesize shouldAttemptPersistentConnection; +@synthesize persistentConnectionTimeoutSeconds; +@synthesize connectionCanBeReused; +@synthesize connectionInfo; +@synthesize readStream; +@synthesize readStreamIsScheduled; +@synthesize shouldUseRFC2616RedirectBehaviour; +@synthesize downloadComplete; +@synthesize requestID; +@synthesize runLoopMode; +@synthesize statusTimer; +@synthesize downloadCache; +@synthesize cachePolicy; +@synthesize cacheStoragePolicy; +@synthesize didUseCachedResponse; +@synthesize secondsToCache; +@synthesize clientCertificates; +@synthesize redirectURL; +#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 +@synthesize shouldContinueWhenAppEntersBackground; +#endif +@synthesize dataDecompressor; +@synthesize shouldWaitToInflateCompressedResponses; + +@synthesize isPACFileRequest; +@synthesize PACFileRequest; +@synthesize PACFileReadStream; +@synthesize PACFileData; + +@synthesize isSynchronous; +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASIHTTPRequestConfig.h b/core/Frameworks/asi-http-request/Classes/CTASIHTTPRequestConfig.h new file mode 100644 index 00000000..dd4a9799 --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASIHTTPRequestConfig.h @@ -0,0 +1,46 @@ +// +// ASIHTTPRequestConfig.h +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 14/12/2009. +// Copyright 2009 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + + +// ====== +// Debug output configuration options +// ====== + +// If defined will use the specified function for debug logging +// Otherwise use NSLog +#ifndef ASI_DEBUG_LOG + #define ASI_DEBUG_LOG NSLog +#endif + +// When set to 1 ASIHTTPRequests will print information about what a request is doing +#ifndef DEBUG_REQUEST_STATUS + #define DEBUG_REQUEST_STATUS 0 +#endif + +// When set to 1, ASIFormDataRequests will print information about the request body to the console +#ifndef DEBUG_FORM_DATA_REQUEST + #define DEBUG_FORM_DATA_REQUEST 0 +#endif + +// When set to 1, ASIHTTPRequests will print information about bandwidth throttling to the console +#ifndef DEBUG_THROTTLING + #define DEBUG_THROTTLING 0 +#endif + +// When set to 1, ASIHTTPRequests will print information about persistent connections to the console +#ifndef DEBUG_PERSISTENT_CONNECTIONS + #define DEBUG_PERSISTENT_CONNECTIONS 0 +#endif + +// When set to 1, ASIHTTPRequests will print information about HTTP authentication (Basic, Digest or NTLM) to the console +#ifndef DEBUG_HTTP_AUTHENTICATION + #define DEBUG_HTTP_AUTHENTICATION 0 +#endif diff --git a/core/Frameworks/asi-http-request/Classes/CTASIHTTPRequestDelegate.h b/core/Frameworks/asi-http-request/Classes/CTASIHTTPRequestDelegate.h new file mode 100644 index 00000000..654397e2 --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASIHTTPRequestDelegate.h @@ -0,0 +1,38 @@ +// +// CTASIHTTPRequestDelegate.h +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 13/04/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +@class CTASIHTTPRequest; + +@protocol CTASIHTTPRequestDelegate + +@optional + +// These are the default delegate methods for request status +// You can use different ones by setting didStartSelector / didFinishSelector / didFailSelector +- (void)requestStarted:(CTASIHTTPRequest *)request; +- (void)request:(CTASIHTTPRequest *)request didReceiveResponseHeaders:(NSDictionary *)responseHeaders; +- (void)request:(CTASIHTTPRequest *)request willRedirectToURL:(NSURL *)newURL; +- (void)requestFinished:(CTASIHTTPRequest *)request; +- (void)requestFailed:(CTASIHTTPRequest *)request; +- (void)requestRedirected:(CTASIHTTPRequest *)request; + +// When a delegate implements this method, it is expected to process all incoming data itself +// This means that responseData / responseString / downloadDestinationPath etc are ignored +// You can have the request call a different method by setting didReceiveDataSelector +- (void)request:(CTASIHTTPRequest *)request didReceiveData:(NSData *)data; + +// If a delegate implements one of these, it will be asked to supply credentials when none are available +// The delegate can then either restart the request ([request retryUsingSuppliedCredentials]) once credentials have been set +// or cancel it ([request cancelAuthentication]) +- (void)authenticationNeededForRequest:(CTASIHTTPRequest *)request; +- (void)proxyAuthenticationNeededForRequest:(CTASIHTTPRequest *)request; + +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASIInputStream.h b/core/Frameworks/asi-http-request/Classes/CTASIInputStream.h new file mode 100644 index 00000000..5adfeb8c --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASIInputStream.h @@ -0,0 +1,29 @@ +// +// CTASIInputStream.h +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 10/08/2009. +// Copyright 2009 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +#import + +@class CTASIHTTPRequest; + +// This is a wrapper for NSInputStream that pretends to be an NSInputStream itself +// Subclassing NSInputStream seems to be tricky, and may involve overriding undocumented methods, so we'll cheat instead. +// It is used by CTASIHTTPRequest whenever we have a request body, and handles measuring and throttling the bandwidth used for uploading + +@interface CTASIInputStream : NSObject { + NSInputStream *stream; + CTASIHTTPRequest *request; +} ++ (id)inputStreamWithFileAtPath:(NSString *)path request:(CTASIHTTPRequest *)request; ++ (id)inputStreamWithData:(NSData *)data request:(CTASIHTTPRequest *)request; + +@property (retain, nonatomic) NSInputStream *stream; +@property (assign, nonatomic) CTASIHTTPRequest *request; +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASIInputStream.m b/core/Frameworks/asi-http-request/Classes/CTASIInputStream.m new file mode 100644 index 00000000..10a2e615 --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASIInputStream.m @@ -0,0 +1,141 @@ +// +// CTASIInputStream.m +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 10/08/2009. +// Copyright 2009 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +#import "CTASIInputStream.h" +#import "CTASIHTTPRequest.h" + +// Used to ensure only one request can read data at once +static NSLock *readLock = nil; + +@implementation CTASIInputStream + ++ (void)initialize +{ + if (self == [CTASIInputStream class]) { + readLock = [[NSLock alloc] init]; + } +} + ++ (id)inputStreamWithFileAtPath:(NSString *)path request:(CTASIHTTPRequest *)theRequest +{ + CTASIInputStream *theStream = [[[self alloc] init] autorelease]; + [theStream setRequest:theRequest]; + [theStream setStream:[NSInputStream inputStreamWithFileAtPath:path]]; + return theStream; +} + ++ (id)inputStreamWithData:(NSData *)data request:(CTASIHTTPRequest *)theRequest +{ + CTASIInputStream *theStream = [[[self alloc] init] autorelease]; + [theStream setRequest:theRequest]; + [theStream setStream:[NSInputStream inputStreamWithData:data]]; + return theStream; +} + +- (void)dealloc +{ + [stream release]; + [super dealloc]; +} + +// Called when CFNetwork wants to read more of our request body +// When throttling is on, we ask CTASIHTTPRequest for the maximum amount of data we can read +- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len +{ + [readLock lock]; + unsigned long toRead = len; + if ([CTASIHTTPRequest isBandwidthThrottled]) { + toRead = [CTASIHTTPRequest maxUploadReadLength]; + if (toRead > len) { + toRead = len; + } else if (toRead == 0) { + toRead = 1; + } + [request performThrottling]; + } + [readLock unlock]; + NSInteger rv = [stream read:buffer maxLength:toRead]; + if (rv > 0) + [CTASIHTTPRequest incrementBandwidthUsedInLastSecond:(NSUInteger) rv]; + return rv; +} + +/* + * Implement NSInputStream mandatory methods to make sure they are implemented + * (necessary for MacRuby for example) and avoid the overhead of method + * forwarding for these common methods. + */ +- (void)open +{ + [stream open]; +} + +- (void)close +{ + [stream close]; +} + +- (id)delegate +{ + return [stream delegate]; +} + +- (void)setDelegate:(id)delegate +{ + [stream setDelegate:delegate]; +} + +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode +{ + [stream scheduleInRunLoop:aRunLoop forMode:mode]; +} + +- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode +{ + [stream removeFromRunLoop:aRunLoop forMode:mode]; +} + +- (id)propertyForKey:(NSString *)key +{ + return [stream propertyForKey:key]; +} + +- (BOOL)setProperty:(id)property forKey:(NSString *)key +{ + return [stream setProperty:property forKey:key]; +} + +- (NSStreamStatus)streamStatus +{ + return [stream streamStatus]; +} + +- (NSError *)streamError +{ + return [stream streamError]; +} + +// If we get asked to perform a method we don't have (probably internal ones), +// we'll just forward the message to our stream + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector +{ + return [stream methodSignatureForSelector:aSelector]; +} + +- (void)forwardInvocation:(NSInvocation *)anInvocation +{ + [anInvocation invokeWithTarget:stream]; +} + +@synthesize stream; +@synthesize request; +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASINetworkQueue.h b/core/Frameworks/asi-http-request/Classes/CTASINetworkQueue.h new file mode 100644 index 00000000..55414d5f --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASINetworkQueue.h @@ -0,0 +1,111 @@ +// +// CTASINetworkQueue.h +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 07/11/2008. +// Copyright 2008-2009 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +#import +#import "CTASIHTTPRequestDelegate.h" +#import "CTASIProgressDelegate.h" + +@interface CTASINetworkQueue : NSOperationQueue { + + // Delegate will get didFail + didFinish messages (if set) + id delegate; + + // Will be called when a request starts with the request as the argument + SEL requestDidStartSelector; + + // Will be called when a request receives response headers + // Should take the form request:didRecieveResponseHeaders:, where the first argument is the request, and the second the headers dictionary + SEL requestDidReceiveResponseHeadersSelector; + + // Will be called when a request is about to redirect + // Should take the form request:willRedirectToURL:, where the first argument is the request, and the second the new url + SEL requestWillRedirectSelector; + + // Will be called when a request completes with the request as the argument + SEL requestDidFinishSelector; + + // Will be called when a request fails with the request as the argument + SEL requestDidFailSelector; + + // Will be called when the queue finishes with the queue as the argument + SEL queueDidFinishSelector; + + // Upload progress indicator, probably an NSProgressIndicator or UIProgressView + id uploadProgressDelegate; + + // Total amount uploaded so far for all requests in this queue + unsigned long long bytesUploadedSoFar; + + // Total amount to be uploaded for all requests in this queue - requests add to this figure as they work out how much data they have to transmit + unsigned long long totalBytesToUpload; + + // Download progress indicator, probably an NSProgressIndicator or UIProgressView + id downloadProgressDelegate; + + // Total amount downloaded so far for all requests in this queue + unsigned long long bytesDownloadedSoFar; + + // Total amount to be downloaded for all requests in this queue - requests add to this figure as they receive Content-Length headers + unsigned long long totalBytesToDownload; + + // When YES, the queue will cancel all requests when a request fails. Default is YES + BOOL shouldCancelAllRequestsOnFailure; + + //Number of real requests (excludes HEAD requests created to manage showAccurateProgress) + int requestsCount; + + // When NO, this request will only update the progress indicator when it completes + // When YES, this request will update the progress indicator according to how much data it has received so far + // When YES, the queue will first perform HEAD requests for all GET requests in the queue, so it can calculate the total download size before it starts + // NO means better performance, because it skips this step for GET requests, and it won't waste time updating the progress indicator until a request completes + // Set to YES if the size of a requests in the queue varies greatly for much more accurate results + // Default for requests in the queue is NO + BOOL showAccurateProgress; + + // Storage container for additional queue information. + NSDictionary *userInfo; + +} + +// Convenience constructor ++ (id)queue; + +// Call this to reset a queue - it will cancel all operations, clear delegates, and suspend operation +- (void)reset; + +// Used internally to manage HEAD requests when showAccurateProgress is YES, do not use! +- (void)addHEADOperation:(NSOperation *)operation; + +// All ASINetworkQueues are paused when created so that total size can be calculated before the queue starts +// This method will start the queue +- (void)go; + +@property (assign, nonatomic, setter=setUploadProgressDelegate:) id uploadProgressDelegate; +@property (assign, nonatomic, setter=setDownloadProgressDelegate:) id downloadProgressDelegate; + +@property (assign, atomic) SEL requestDidStartSelector; +@property (assign, atomic) SEL requestDidReceiveResponseHeadersSelector; +@property (assign, atomic) SEL requestWillRedirectSelector; +@property (assign, atomic) SEL requestDidFinishSelector; +@property (assign, atomic) SEL requestDidFailSelector; +@property (assign, atomic) SEL queueDidFinishSelector; +@property (assign, atomic) BOOL shouldCancelAllRequestsOnFailure; +@property (assign, atomic) id delegate; +@property (assign, atomic) BOOL showAccurateProgress; +@property (assign, atomic, readonly) int requestsCount; +@property (retain, atomic) NSDictionary *userInfo; + +@property (assign, atomic) unsigned long long bytesUploadedSoFar; +@property (assign, atomic) unsigned long long totalBytesToUpload; +@property (assign, atomic) unsigned long long bytesDownloadedSoFar; +@property (assign, atomic) unsigned long long totalBytesToDownload; + +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASINetworkQueue.m b/core/Frameworks/asi-http-request/Classes/CTASINetworkQueue.m new file mode 100644 index 00000000..a6c1d3d0 --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASINetworkQueue.m @@ -0,0 +1,346 @@ +// +// CTASINetworkQueue.m +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 07/11/2008. +// Copyright 2008-2009 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +#import "CTASINetworkQueue.h" +#import "CTASIHTTPRequest.h" + +// Private stuff +@interface CTASINetworkQueue () + - (void)resetProgressDelegate:(id *)progressDelegate; + @property (assign) int requestsCount; +@end + +@implementation CTASINetworkQueue + +- (id)init +{ + self = [super init]; + [self setShouldCancelAllRequestsOnFailure:YES]; + [self setMaxConcurrentOperationCount:4]; + [self setSuspended:YES]; + + return self; +} + ++ (id)queue +{ + return [[[self alloc] init] autorelease]; +} + +- (void)dealloc +{ + //We need to clear the queue on any requests that haven't got around to cleaning up yet, as otherwise they'll try to let us know if something goes wrong, and we'll be long gone by then + for (CTASIHTTPRequest *request in [self operations]) { + [request setQueue:nil]; + } + [userInfo release]; + [super dealloc]; +} + +- (void)setSuspended:(BOOL)suspend +{ + [super setSuspended:suspend]; +} + +- (void)reset +{ + [self cancelAllOperations]; + [self setDelegate:nil]; + [self setDownloadProgressDelegate:nil]; + [self setUploadProgressDelegate:nil]; + [self setRequestDidStartSelector:NULL]; + [self setRequestDidReceiveResponseHeadersSelector:NULL]; + [self setRequestDidFailSelector:NULL]; + [self setRequestDidFinishSelector:NULL]; + [self setQueueDidFinishSelector:NULL]; + [self setSuspended:YES]; +} + + +- (void)go +{ + [self setSuspended:NO]; +} + +- (void)cancelAllOperations +{ + [self setBytesUploadedSoFar:0]; + [self setTotalBytesToUpload:0]; + [self setBytesDownloadedSoFar:0]; + [self setTotalBytesToDownload:0]; + [super cancelAllOperations]; +} + +- (void)setUploadProgressDelegate:(id)newDelegate +{ + uploadProgressDelegate = newDelegate; + [self resetProgressDelegate:&uploadProgressDelegate]; + +} + +- (void)setDownloadProgressDelegate:(id)newDelegate +{ + downloadProgressDelegate = newDelegate; + [self resetProgressDelegate:&downloadProgressDelegate]; +} + +- (void)resetProgressDelegate:(id *)progressDelegate +{ +#if !TARGET_OS_IPHONE + // If the uploadProgressDelegate is an NSProgressIndicator, we set its MaxValue to 1.0 so we can treat it similarly to UIProgressViews + SEL selector = @selector(setMaxValue:); + if ([*progressDelegate respondsToSelector:selector]) { + double max = 1.0; + [CTASIHTTPRequest performSelector:selector onTarget:progressDelegate withObject:nil amount:&max callerToRetain:nil]; + } + selector = @selector(setDoubleValue:); + if ([*progressDelegate respondsToSelector:selector]) { + double value = 0.0; + [CTASIHTTPRequest performSelector:selector onTarget:progressDelegate withObject:nil amount:&value callerToRetain:nil]; + } +#else + SEL selector = @selector(setProgress:); + if ([*progressDelegate respondsToSelector:selector]) { + float value = 0.0f; + [CTASIHTTPRequest performSelector:selector onTarget:progressDelegate withObject:nil amount:&value callerToRetain:nil]; + } +#endif +} + +- (void)addHEADOperation:(NSOperation *)operation +{ + if ([operation isKindOfClass:[CTASIHTTPRequest class]]) { + + CTASIHTTPRequest *request = (CTASIHTTPRequest *)operation; + [request setRequestMethod:@"HEAD"]; + [request setQueuePriority:10]; + [request setShowAccurateProgress:YES]; + [request setQueue:self]; + + // Important - we are calling NSOperation's add method - we don't want to add this as a normal request! + [super addOperation:request]; + } +} + +// Only add ASIHTTPRequests to this queue!! +- (void)addOperation:(NSOperation *)operation +{ + if (![operation isKindOfClass:[CTASIHTTPRequest class]]) { + [NSException raise:@"AttemptToAddInvalidRequest" format:@"Attempted to add an object that was not an ASIHTTPRequest to an CTASINetworkQueue"]; + } + + [self setRequestsCount:[self requestsCount]+1]; + + CTASIHTTPRequest *request = (CTASIHTTPRequest *)operation; + + if ([self showAccurateProgress]) { + + // Force the request to build its body (this may change requestMethod) + [request buildPostBody]; + + // If this is a GET request and we want accurate progress, perform a HEAD request first to get the content-length + // We'll only do this before the queue is started + // If requests are added after the queue is started they will probably move the overall progress backwards anyway, so there's no value performing the HEAD requests first + // Instead, they'll update the total progress if and when they receive a content-length header + if ([[request requestMethod] isEqualToString:@"GET"]) { + if ([self isSuspended]) { + CTASIHTTPRequest *HEADRequest = [request HEADRequest]; + [self addHEADOperation:HEADRequest]; + [request addDependency:HEADRequest]; + if ([request shouldResetDownloadProgress]) { + [self resetProgressDelegate:&downloadProgressDelegate]; + [request setShouldResetDownloadProgress:NO]; + } + } + } + [request buildPostBody]; + [self request:nil incrementUploadSizeBy:(long long)[request postLength]]; + + + } else { + [self request:nil incrementDownloadSizeBy:1]; + [self request:nil incrementUploadSizeBy:1]; + } + // Tell the request not to increment the upload size when it starts, as we've already added its length + if ([request shouldResetUploadProgress]) { + [self resetProgressDelegate:&uploadProgressDelegate]; + [request setShouldResetUploadProgress:NO]; + } + + [request setShowAccurateProgress:[self showAccurateProgress]]; + + [request setQueue:self]; + [super addOperation:request]; + +} + +- (void)requestStarted:(CTASIHTTPRequest *)request +{ + if ([self requestDidStartSelector]) { + [[self delegate] performSelector:[self requestDidStartSelector] withObject:request]; + } +} + +- (void)request:(CTASIHTTPRequest *)request didReceiveResponseHeaders:(NSDictionary *)responseHeaders +{ + if ([self requestDidReceiveResponseHeadersSelector]) { + [[self delegate] performSelector:[self requestDidReceiveResponseHeadersSelector] withObject:request withObject:responseHeaders]; + } +} + +- (void)request:(CTASIHTTPRequest *)request willRedirectToURL:(NSURL *)newURL +{ + if ([self requestWillRedirectSelector]) { + [[self delegate] performSelector:[self requestWillRedirectSelector] withObject:request withObject:newURL]; + } +} + +- (void)requestFinished:(CTASIHTTPRequest *)request +{ + [self setRequestsCount:[self requestsCount]-1]; + if ([self requestDidFinishSelector]) { + [[self delegate] performSelector:[self requestDidFinishSelector] withObject:request]; + } + if ([self requestsCount] == 0) { + if ([self queueDidFinishSelector]) { + [[self delegate] performSelector:[self queueDidFinishSelector] withObject:self]; + } + } +} + +- (void)requestFailed:(CTASIHTTPRequest *)request +{ + [self setRequestsCount:[self requestsCount]-1]; + if ([self requestDidFailSelector]) { + [[self delegate] performSelector:[self requestDidFailSelector] withObject:request]; + } + if ([self requestsCount] == 0) { + if ([self queueDidFinishSelector]) { + [[self delegate] performSelector:[self queueDidFinishSelector] withObject:self]; + } + } + if ([self shouldCancelAllRequestsOnFailure] && [self requestsCount] > 0) { + [self cancelAllOperations]; + } + +} + + +- (void)request:(CTASIHTTPRequest *)request didReceiveBytes:(long long)bytes +{ + [self setBytesDownloadedSoFar:[self bytesDownloadedSoFar]+(unsigned long long)bytes]; + if ([self downloadProgressDelegate]) { + [CTASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:[self bytesDownloadedSoFar] ofTotal:[self totalBytesToDownload]]; + } +} + +- (void)request:(CTASIHTTPRequest *)request didSendBytes:(long long)bytes +{ + [self setBytesUploadedSoFar:[self bytesUploadedSoFar]+(unsigned long long)bytes]; + if ([self uploadProgressDelegate]) { + [CTASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:[self bytesUploadedSoFar] ofTotal:[self totalBytesToUpload]]; + } +} + +- (void)request:(CTASIHTTPRequest *)request incrementDownloadSizeBy:(long long)newLength +{ + [self setTotalBytesToDownload:[self totalBytesToDownload]+(unsigned long long)newLength]; +} + +- (void)request:(CTASIHTTPRequest *)request incrementUploadSizeBy:(long long)newLength +{ + [self setTotalBytesToUpload:[self totalBytesToUpload]+(unsigned long long)newLength]; +} + + +// Since this queue takes over as the delegate for all requests it contains, it should forward authorisation requests to its own delegate +- (void)authenticationNeededForRequest:(CTASIHTTPRequest *)request +{ + if ([[self delegate] respondsToSelector:@selector(authenticationNeededForRequest:)]) { + [[self delegate] performSelector:@selector(authenticationNeededForRequest:) withObject:request]; + } +} + +- (void)proxyAuthenticationNeededForRequest:(CTASIHTTPRequest *)request +{ + if ([[self delegate] respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) { + [[self delegate] performSelector:@selector(proxyAuthenticationNeededForRequest:) withObject:request]; + } +} + + +- (BOOL)respondsToSelector:(SEL)selector +{ + // We handle certain methods differently because whether our delegate implements them or not can affect how the request should behave + + // If the delegate implements this, the request will stop to wait for credentials + if (selector == @selector(authenticationNeededForRequest:)) { + if ([[self delegate] respondsToSelector:@selector(authenticationNeededForRequest:)]) { + return YES; + } + return NO; + + // If the delegate implements this, the request will to wait for credentials + } else if (selector == @selector(proxyAuthenticationNeededForRequest:)) { + if ([[self delegate] respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) { + return YES; + } + return NO; + + // If the delegate implements requestWillRedirectSelector, the request will stop to allow the delegate to change the url + } else if (selector == @selector(request:willRedirectToURL:)) { + if ([self requestWillRedirectSelector] && [[self delegate] respondsToSelector:[self requestWillRedirectSelector]]) { + return YES; + } + return NO; + } + return [super respondsToSelector:selector]; +} + +#pragma mark NSCopying + +- (id)copyWithZone:(NSZone *)zone +{ + CTASINetworkQueue *newQueue = [[[self class] alloc] init]; + [newQueue setDelegate:[self delegate]]; + [newQueue setRequestDidStartSelector:[self requestDidStartSelector]]; + [newQueue setRequestWillRedirectSelector:[self requestWillRedirectSelector]]; + [newQueue setRequestDidReceiveResponseHeadersSelector:[self requestDidReceiveResponseHeadersSelector]]; + [newQueue setRequestDidFinishSelector:[self requestDidFinishSelector]]; + [newQueue setRequestDidFailSelector:[self requestDidFailSelector]]; + [newQueue setQueueDidFinishSelector:[self queueDidFinishSelector]]; + [newQueue setUploadProgressDelegate:[self uploadProgressDelegate]]; + [newQueue setDownloadProgressDelegate:[self downloadProgressDelegate]]; + [newQueue setShouldCancelAllRequestsOnFailure:[self shouldCancelAllRequestsOnFailure]]; + [newQueue setShowAccurateProgress:[self showAccurateProgress]]; + [newQueue setUserInfo:[[[self userInfo] copyWithZone:zone] autorelease]]; + return newQueue; +} + + +@synthesize requestsCount; +@synthesize bytesUploadedSoFar; +@synthesize totalBytesToUpload; +@synthesize bytesDownloadedSoFar; +@synthesize totalBytesToDownload; +@synthesize shouldCancelAllRequestsOnFailure; +@synthesize uploadProgressDelegate; +@synthesize downloadProgressDelegate; +@synthesize requestDidStartSelector; +@synthesize requestDidReceiveResponseHeadersSelector; +@synthesize requestWillRedirectSelector; +@synthesize requestDidFinishSelector; +@synthesize requestDidFailSelector; +@synthesize queueDidFinishSelector; +@synthesize delegate; +@synthesize showAccurateProgress; +@synthesize userInfo; +@end diff --git a/core/Frameworks/asi-http-request/Classes/CTASIProgressDelegate.h b/core/Frameworks/asi-http-request/Classes/CTASIProgressDelegate.h new file mode 100644 index 00000000..2a6adbcc --- /dev/null +++ b/core/Frameworks/asi-http-request/Classes/CTASIProgressDelegate.h @@ -0,0 +1,41 @@ +// +// CTASIProgressDelegate.h +// Part of CTASIHTTPRequest -> http://allseeing-i.com/CTASIHTTPRequest +// +// Created by Ben Copsey on 13/04/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +@class CTASIHTTPRequest; + +@protocol CTASIProgressDelegate + +@optional + +// These methods are used to update UIProgressViews (iPhone OS) or NSProgressIndicators (Mac OS X) +// If you are using a custom progress delegate, you may find it easier to implement didReceiveBytes / didSendBytes instead +#if TARGET_OS_IPHONE +- (void)setProgress:(float)newProgress; +#else +- (void)setDoubleValue:(double)newProgress; +- (void)setMaxValue:(double)newMax; +#endif + +// Called when the request receives some data - bytes is the length of that data +- (void)request:(CTASIHTTPRequest *)request didReceiveBytes:(long long)bytes; + +// Called when the request sends some data +// The first 32KB (128KB on older platforms) of data sent is not included in this amount because of limitations with the CFNetwork API +// bytes may be less than zero if a request needs to remove upload progress (probably because the request needs to run again) +- (void)request:(CTASIHTTPRequest *)request didSendBytes:(long long)bytes; + +// Called when a request needs to change the length of the content to download +- (void)request:(CTASIHTTPRequest *)request incrementDownloadSizeBy:(long long)newLength; + +// Called when a request needs to change the length of the content to upload +// newLength may be less than zero when a request needs to remove the size of the internal buffer from progress tracking +- (void)request:(CTASIHTTPRequest *)request incrementUploadSizeBy:(long long)newLength; +@end diff --git a/core/Frameworks/asi-http-request/External/Reachability/CTReachability.h b/core/Frameworks/asi-http-request/External/Reachability/CTReachability.h new file mode 100644 index 00000000..dc3198d3 --- /dev/null +++ b/core/Frameworks/asi-http-request/External/Reachability/CTReachability.h @@ -0,0 +1,194 @@ +/* + + File: Reachability.h + Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. + + Version: 2.0.4ddg + */ + +/* + Significant additions made by Andrew W. Donoho, August 11, 2009. + This is a derived work of Apple's CTReachability v2.0 class. + + The below license is the new BSD license with the OSI recommended personalizations. + + + Extensions Copyright (C) 2009 Donoho Design Group, LLC. All Rights Reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Andrew W. Donoho nor Donoho Design Group, L.L.C. + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY DONOHO DESIGN GROUP, L.L.C. "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + */ + + +/* + + Apple's Original License on CTReachability v2.0 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. + ("Apple") in consideration of your agreement to the following terms, and your + use, installation, modification or redistribution of this Apple software + constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject + to these terms, Apple grants you a personal, non-exclusive license, under + Apple's copyrights in this original Apple software (the "Apple Software"), to + use, reproduce, modify and redistribute the Apple Software, with or without + modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain + this notice and the following text and disclaimers in all such redistributions + of the Apple Software. + + Neither the name, trademarks, service marks or logos of Apple Inc. may be used + to endorse or promote products derived from the Apple Software without specific + prior written permission from Apple. Except as expressly stated in this notice, + no other rights or licenses, express or implied, are granted by Apple herein, + including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be + incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO + WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED + WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR + DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF + CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF + APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2009 Apple Inc. All Rights Reserved. + + */ + + +/* + DDG extensions include: + Each reachability object now has a copy of the key used to store it in a + dictionary. This allows each observer to quickly determine if the event is + important to them. + + -currentReachabilityStatus also has a significantly different decision criteria than + Apple's code. + + A multiple convenience test methods have been added. + */ + +#import +#import +#import + +#define CT_USE_DDG_EXTENSIONS 1 // Use DDG's Extensions to test network criteria. +// Since NSAssert and NSCAssert are used in this code, +// I recommend you set NS_BLOCK_ASSERTIONS=1 in the release versions of your projects. + +enum { + + // DDG CTNetworkStatus Constant Names. + kCTNotReachable = 0, // Apple's code depends upon 'CTNotReachable' being the same value as 'NO'. + kCTReachableViaWWAN, // Switched order from Apple's enum. WWAN is active before WiFi. + kCTReachableViaWiFi + +}; +typedef uint32_t CTNetworkStatus; + +enum { + + // Apple CTNetworkStatus Constant Names. + CTNotReachable = kCTNotReachable, + CTReachableViaWiFi = kCTReachableViaWiFi, + CTReachableViaWWAN = kCTReachableViaWWAN + +}; + + +extern NSString *const kCTInternetConnection; +extern NSString *const kCTLocalWiFiConnection; +extern NSString *const kCTReachabilityChangedNotification; + +@interface CTReachability : NSObject { + +@private + NSString *key_; + SCNetworkReachabilityRef reachabilityRef; + +} + +@property (atomic, copy) NSString *key; // Atomic because network operations are asynchronous. + +// Designated Initializer. +- (CTReachability *) initWithReachabilityRef: (SCNetworkReachabilityRef) ref; + +// Use to check the reachability of a particular host name. ++ (CTReachability *) reachabilityWithHostName: (NSString*) hostName; + +// Use to check the reachability of a particular IP address. ++ (CTReachability *) reachabilityWithAddress: (const struct sockaddr_in*) hostAddress; + +// Use to check whether the default route is available. +// Should be used to, at minimum, establish network connectivity. ++ (CTReachability *) reachabilityForInternetConnection; + +// Use to check whether a local wifi connection is available. ++ (CTReachability *) reachabilityForLocalWiFi; + +//Start listening for reachability notifications on the current run loop. +- (BOOL) startNotifier; +- (void) stopNotifier; + +// Comparison routines to enable choosing actions in a notification. +- (BOOL) isEqual: (CTReachability *) r; + +// These are the status tests. +- (CTNetworkStatus) currentReachabilityStatus; + +// The main direct test of reachability. +- (BOOL) isReachable; + +// WWAN may be available, but not active until a connection has been established. +// WiFi may require a connection for VPN on Demand. +- (BOOL) isConnectionRequired; // Identical DDG variant. +- (BOOL) connectionRequired; // Apple's routine. + +// Dynamic, on demand connection? +- (BOOL) isConnectionOnDemand; + +// Is user intervention required? +- (BOOL) isInterventionRequired; + +// Routines for specific connection testing by your app. +- (BOOL) isReachableViaWWAN; +- (BOOL) isReachableViaWiFi; + +- (SCNetworkReachabilityFlags) reachabilityFlags; + +@end diff --git a/core/Frameworks/asi-http-request/External/Reachability/CTReachability.m b/core/Frameworks/asi-http-request/External/Reachability/CTReachability.m new file mode 100644 index 00000000..6233c357 --- /dev/null +++ b/core/Frameworks/asi-http-request/External/Reachability/CTReachability.m @@ -0,0 +1,814 @@ +/* + + File: Reachability.m + Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. + + Version: 2.0.4ddg + */ + +/* + Significant additions made by Andrew W. Donoho, August 11, 2009. + This is a derived work of Apple's CTReachability v2.0 class. + + The below license is the new BSD license with the OSI recommended personalizations. + + + Extensions Copyright (C) 2009 Donoho Design Group, LLC. All Rights Reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Andrew W. Donoho nor Donoho Design Group, L.L.C. + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY DONOHO DESIGN GROUP, L.L.C. "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + */ + + +/* + + Apple's Original License on CTReachability v2.0 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. + ("Apple") in consideration of your agreement to the following terms, and your + use, installation, modification or redistribution of this Apple software + constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject + to these terms, Apple grants you a personal, non-exclusive license, under + Apple's copyrights in this original Apple software (the "Apple Software"), to + use, reproduce, modify and redistribute the Apple Software, with or without + modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain + this notice and the following text and disclaimers in all such redistributions + of the Apple Software. + + Neither the name, trademarks, service marks or logos of Apple Inc. may be used + to endorse or promote products derived from the Apple Software without specific + prior written permission from Apple. Except as expressly stated in this notice, + no other rights or licenses, express or implied, are granted by Apple herein, + including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be + incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO + WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED + WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR + DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF + CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF + APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2009 Apple Inc. All Rights Reserved. + +*/ + +/* + Each reachability object now has a copy of the key used to store it in a dictionary. + This allows each observer to quickly determine if the event is important to them. +*/ + +#import +#import +#import +#import +#import +#import + +#import + +#import "CTReachability.h" + +NSString *const kCTInternetConnection = @"InternetConnection"; +NSString *const kCTLocalWiFiConnection = @"LocalWiFiConnection"; +NSString *const kCTReachabilityChangedNotification = @"NetworkReachabilityChangedNotification"; + +#define CLASS_DEBUG 1 // Turn on logReachabilityFlags. Must also have a project wide defined DEBUG. + +#if (defined DEBUG && defined CLASS_DEBUG) +#define logReachabilityFlags(flags) (logReachabilityFlags_(__PRETTY_FUNCTION__, __LINE__, flags)) + +static NSString *reachabilityFlags_(SCNetworkReachabilityFlags flags) { + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 30000) // Apple advises you to use the magic number instead of a symbol. + return [NSString stringWithFormat:@"CTReachability Flags: %c%c %c%c%c%c%c%c%c", + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; +#else + // Compile out the v3.0 features for v2.2.1 deployment. + return [NSString stringWithFormat:@CTReachabilityc", + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + // v3 kSCNetworkReachabilityFlagsConnectionOnTraffic == v2 kSCNetworkReachabilityFlagsConnectionAutomatic + (flags & kSCNetworkReachabilityFlagsConnectionAutomatic) ? 'C' : '-', + // (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', // No v2 equivalent. + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; +#endif + +} // reachabilityFlags_() + +static void logReachabilityFlags_(const char *name, int line, SCNetworkReachabilityFlags flags) { + + NSLog(@"%s (%d) \n\t%@", name, line, reachabilityFlags_(flags)); + +} // logReachabilityFlags_() + +#define logNetworkStatus(status) (logNetworkStatus_(__PRETTY_FUNCTION__, __LINE__, status)) + +static void logNetworkStatus_(const char *name, int line, CTNetworkStatus status) { + + NSString *statusString = nil; + + switch (status) { + case kCTNotReachable: + statusString = @"Not Reachable"; + break; + case kCTReachableViaWWAN: + statusString = @"Reachable via WWAN"; + break; + case kCTReachableViaWiFi: + statusString = @"Reachable via WiFi"; + break; + } + + NSLog(@"%s (%d) \n\tNetwork Status: %@", name, line, statusString); + +} // logNetworkStatus_() + +#else +#define logReachabilityFlags(flags) +#define logNetworkStatus(status) +#endif + +@interface CTReachability (private) + +- (CTNetworkStatus) networkStatusForFlags: (SCNetworkReachabilityFlags) flags; + +@end + +@implementation CTReachability + +@synthesize key = key_; + +// Preclude direct access to ivars. ++ (BOOL) accessInstanceVariablesDirectly { + + return NO; + +} // accessInstanceVariablesDirectly + + +- (void) dealloc { + + [self stopNotifier]; + if(reachabilityRef) { + + CFRelease(reachabilityRef); reachabilityRef = NULL; + + } + + self.key = nil; + + [super dealloc]; + +} // dealloc + + +- (CTReachability *) initWithReachabilityRef: (SCNetworkReachabilityRef) ref +{ + self = [super init]; + if (self != nil) + { + reachabilityRef = ref; + } + + return self; + +} // initWithReachabilityRef: + + +#if (defined DEBUG && defined CLASS_DEBUG) +- (NSString *) description { + + NSAssert(reachabilityRef, @"-description called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags = 0; + + SCNetworkReachabilityGetFlags(reachabilityRef, &flags); + + return [NSString stringWithFormat: @"%@\n\t%@", self.key, reachabilityFlags_(flags)]; + +} // description +#endif + + +#pragma mark - +#pragma mark Notification Management Methods + + +//Start listening for reachability notifications on the current run loop +static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) { + + #pragma unused (target, flags) + NSCAssert(info, @"info was NULL in ReachabilityCallback"); + NSCAssert([(NSObject*) info isKindOfClass: [CTReachability class]], @"info was the wrong class in ReachabilityCallback"); + + //We're on the main RunLoop, so an NSAutoreleasePool is not necessary, but is added defensively + // in case someone uses the Reachablity object in a different thread. + NSAutoreleasePool* pool = [NSAutoreleasePool new]; + + // Post a notification to notify the client that the network reachability changed. + [[NSNotificationCenter defaultCenter] postNotificationName:kCTReachabilityChangedNotification + object: (CTReachability *) info]; + + [pool release]; + +} // ReachabilityCallback() + + +- (BOOL) startNotifier { + + SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL}; + + if(SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)) { + + if(SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) { + + return YES; + + } + + } + + return NO; + +} // startNotifier + + +- (void) stopNotifier { + + if(reachabilityRef) { + + SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + + } + +} // stopNotifier + + +- (BOOL) isEqual: (CTReachability *) r { + + return [r.key isEqualToString: self.key]; + +} // isEqual: + + +#pragma mark - +#pragma mark CTReachability Allocation Methods + + ++ (CTReachability *) reachabilityWithHostName: (NSString *) hostName { + + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); + + if (ref) { + + CTReachability *r = [[[self alloc] initWithReachabilityRef: ref] autorelease]; + + r.key = hostName; + + return r; + + } + + return nil; + +} // reachabilityWithHostName + + ++ (NSString *) makeAddressKey: (in_addr_t) addr { + // addr is assumed to be in network byte order. + + static const int highShift = 24; + static const int highMidShift = 16; + static const int lowMidShift = 8; + static const in_addr_t mask = 0x000000ff; + + addr = ntohl(addr); + + return [NSString stringWithFormat: @"%d.%d.%d.%d", + (addr >> highShift) & mask, + (addr >> highMidShift) & mask, + (addr >> lowMidShift) & mask, + addr & mask]; + +} // makeAddressKey: + + ++ (CTReachability *) reachabilityWithAddress: (const struct sockaddr_in *) hostAddress { + + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress); + + if (ref) { + + CTReachability *r = [[[self alloc] initWithReachabilityRef: ref] autorelease]; + + r.key = [self makeAddressKey: hostAddress->sin_addr.s_addr]; + + return r; + + } + + return nil; + +} // reachabilityWithAddress + + ++ (CTReachability *) reachabilityForInternetConnection { + + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + CTReachability *r = [self reachabilityWithAddress: &zeroAddress]; + + r.key = kCTInternetConnection; + + return r; + +} // reachabilityForInternetConnection + + ++ (CTReachability *) reachabilityForLocalWiFi { + + struct sockaddr_in localWifiAddress; + bzero(&localWifiAddress, sizeof(localWifiAddress)); + localWifiAddress.sin_len = sizeof(localWifiAddress); + localWifiAddress.sin_family = AF_INET; + // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 + localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); + + CTReachability *r = [self reachabilityWithAddress: &localWifiAddress]; + + r.key = kCTLocalWiFiConnection; + + return r; + +} // reachabilityForLocalWiFi + + +#pragma mark - +#pragma mark Network Flag Handling Methods + + +#if CT_USE_DDG_EXTENSIONS +// +// iPhone condition codes as reported by a 3GS running iPhone OS v3.0. +// Airplane Mode turned on: CTReachability Flag Status: -- ------- +// WWAN Active: CTReachability Flag Status: WR -t----- +// WWAN Connection required: CTReachability Flag Status: WR ct----- +// WiFi turned on: CTReachability Flag Status: -R ------- Reachable. +// Local WiFi turned on: CTReachability Flag Status: -R xxxxxxd Reachable. +// WiFi turned on: CTReachability Flag Status: -R ct----- Connection down. (Non-intuitive, empirically determined answer.) +const SCNetworkReachabilityFlags kCTConnectionDown = kSCNetworkReachabilityFlagsConnectionRequired | + kSCNetworkReachabilityFlagsTransientConnection; +// WiFi turned on: CTReachability Flag Status: -R ct-i--- Reachable but it will require user intervention (e.g. enter a WiFi password). +// WiFi turned on: CTReachability Flag Status: -R -t----- Reachable via VPN. +// +// In the below method, an 'x' in the flag status means I don't care about its value. +// +// This method differs from Apple's by testing explicitly for empirically observed values. +// This gives me more confidence in it's correct behavior. Apple's code covers more cases +// than mine. My code covers the cases that occur. +// +- (CTNetworkStatus) networkStatusForFlags: (SCNetworkReachabilityFlags) flags { + + if (flags & kSCNetworkReachabilityFlagsReachable) { + + // Local WiFi -- Test derived from Apple's code: -localWiFiStatusForFlags:. + if (self.key == kCTLocalWiFiConnection) { + + // CTReachability Flag Status: xR xxxxxxd Reachable. + return (flags & kSCNetworkReachabilityFlagsIsDirect) ? kCTReachableViaWiFi : kCTNotReachable; + + } + + // Observed WWAN Values: + // WWAN Active: CTReachability Flag Status: WR -t----- + // WWAN Connection required: CTReachability Flag Status: WR ct----- + // + // Test Value: CTReachability Flag Status: WR xxxxxxx + if (flags & kSCNetworkReachabilityFlagsIsWWAN) { return kCTReachableViaWWAN; } + + // Clear moot bits. + flags &= ~(uint32_t)kSCNetworkReachabilityFlagsReachable; + flags &= ~(uint32_t)kSCNetworkReachabilityFlagsIsDirect; + flags &= ~(uint32_t)kSCNetworkReachabilityFlagsIsLocalAddress; // kCTInternetConnection is local. + + // CTReachability Flag Status: -R ct---xx Connection down. + if (flags == kCTConnectionDown) { return kCTNotReachable; } + + // CTReachability Flag Status: -R -t---xx Reachable. WiFi + VPN(is up) (Thank you Ling Wang) + if (flags & kSCNetworkReachabilityFlagsTransientConnection) { return kCTReachableViaWiFi; } + + // CTReachability Flag Status: -R -----xx Reachable. + if (flags == 0) { return kCTReachableViaWiFi; } + + // Apple's code tests for dynamic connection types here. I don't. + // If a connection is required, regardless of whether it is on demand or not, it is a WiFi connection. + // If you care whether a connection needs to be brought up, use -isConnectionRequired. + // If you care about whether user intervention is necessary, use -isInterventionRequired. + // If you care about dynamically establishing the connection, use -isConnectionIsOnDemand. + + // CTReachability Flag Status: -R cxxxxxx Reachable. + if (flags & kSCNetworkReachabilityFlagsConnectionRequired) { return kCTReachableViaWiFi; } + + // Required by the compiler. Should never get here. Default to not connected. +#if (defined DEBUG && defined CLASS_DEBUG) + NSAssert1(NO, @"Uncaught reachability test. Flags: %@", reachabilityFlags_(flags)); +#endif + return kCTNotReachable; + + } + + // CTReachability Flag Status: x- xxxxxxx + return kCTNotReachable; + +} // networkStatusForFlags: + + +- (CTNetworkStatus) currentReachabilityStatus { + + NSAssert(reachabilityRef, @"currentReachabilityStatus called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags = 0; + CTNetworkStatus status = kCTNotReachable; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + +// logReachabilityFlags(flags); + + status = [self networkStatusForFlags: flags]; + + return status; + + } + + return kCTNotReachable; + +} // currentReachabilityStatus + + +- (BOOL) isReachable { + + NSAssert(reachabilityRef, @"isReachable called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags = 0; + CTNetworkStatus status = kCTNotReachable; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + +// logReachabilityFlags(flags); + + status = [self networkStatusForFlags: flags]; + +// logNetworkStatus(status); + + return (kCTNotReachable != status); + + } + + return NO; + +} // isReachable + + +- (BOOL) isConnectionRequired { + + NSAssert(reachabilityRef, @"isConnectionRequired called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + + logReachabilityFlags(flags); + + return (flags & kSCNetworkReachabilityFlagsConnectionRequired); + + } + + return NO; + +} // isConnectionRequired + + +- (BOOL) connectionRequired { + + return [self isConnectionRequired]; + +} // connectionRequired +#endif + + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 30000) +static const SCNetworkReachabilityFlags kOnDemandConnection = kSCNetworkReachabilityFlagsConnectionOnTraffic | + kSCNetworkReachabilityFlagsConnectionOnDemand; +#else +static const SCNetworkReachabilityFlags kOnDemandConnection = kSCNetworkReachabilityFlagsConnectionAutomatic; +#endif + +- (BOOL) isConnectionOnDemand { + + NSAssert(reachabilityRef, @"isConnectionIsOnDemand called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + + logReachabilityFlags(flags); + + return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && + (flags & kOnDemandConnection)); + + } + + return NO; + +} // isConnectionOnDemand + + +- (BOOL) isInterventionRequired { + + NSAssert(reachabilityRef, @"isInterventionRequired called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + + logReachabilityFlags(flags); + + return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && + (flags & kSCNetworkReachabilityFlagsInterventionRequired)); + + } + + return NO; + +} // isInterventionRequired + + +- (BOOL) isReachableViaWWAN { + + NSAssert(reachabilityRef, @"isReachableViaWWAN called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags = 0; + CTNetworkStatus status = kCTNotReachable; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + + logReachabilityFlags(flags); + + status = [self networkStatusForFlags: flags]; + + return (kCTReachableViaWWAN == status); + + } + + return NO; + +} // isReachableViaWWAN + + +- (BOOL) isReachableViaWiFi { + + NSAssert(reachabilityRef, @"isReachableViaWiFi called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags = 0; + CTNetworkStatus status = kCTNotReachable; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + + logReachabilityFlags(flags); + + status = [self networkStatusForFlags: flags]; + + return (kCTReachableViaWiFi == status); + + } + + return NO; + +} // isReachableViaWiFi + + +- (SCNetworkReachabilityFlags) reachabilityFlags { + + NSAssert(reachabilityRef, @"reachabilityFlags called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags = 0; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + + logReachabilityFlags(flags); + + return flags; + + } + + return 0; + +} // reachabilityFlags + + +#pragma mark - +#pragma mark Apple's Network Flag Handling Methods + + +#if !CT_USE_DDG_EXTENSIONS +/* + * + * Apple's Network Status testing code. + * The only changes that have been made are to use the new logReachabilityFlags macro and + * test for local WiFi via the key instead of Apple's boolean. Also, Apple's code was for v3.0 only + * iPhone OS. v2.2.1 and earlier conditional compiling is turned on. Hence, to mirror Apple's behavior, + * set your Base SDK to v3.0 or higher. + * + */ + +- (CTNetworkStatus) localWiFiStatusForFlags: (SCNetworkReachabilityFlags) flags +{ + logReachabilityFlags(flags); + + BOOL retVal = CTNotReachable; + if((flags & kSCNetworkReachabilityFlagsReachable) && (flags & kSCNetworkReachabilityFlagsIsDirect)) + { + retVal = CTReachableViaWiFi; + } + return retVal; +} + + +- (CTNetworkStatus) networkStatusForFlags: (SCNetworkReachabilityFlags) flags +{ + logReachabilityFlags(flags); + if (!(flags & kSCNetworkReachabilityFlagsReachable)) + { + // if target host is not reachable + return CTNotReachable; + } + + BOOL retVal = CTNotReachable; + + if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired)) + { + // if target host is reachable and no connection is required + // then we'll assume (for now) that your on Wi-Fi + retVal = CTReachableViaWiFi; + } + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 30000) // Apple advises you to use the magic number instead of a symbol. + if ((flags & kSCNetworkReachabilityFlagsConnectionOnDemand) || + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic)) +#else + if (flags & kSCNetworkReachabilityFlagsConnectionAutomatic) +#endif + { + // ... and the connection is on-demand (or on-traffic) if the + // calling application is using the CFSocketStream or higher APIs + + if (!(flags & kSCNetworkReachabilityFlagsInterventionRequired)) + { + // ... and no [user] intervention is needed + retVal = CTReachableViaWiFi; + } + } + + if (flags & kSCNetworkReachabilityFlagsIsWWAN) + { + // ... but WWAN connections are OK if the calling application + // is using the CFNetwork (CFSocketStream?) APIs. + retVal = CTReachableViaWWAN; + } + return retVal; +} + + +- (CTNetworkStatus) currentReachabilityStatus +{ + NSAssert(reachabilityRef, @"currentReachabilityStatus called with NULL reachabilityRef"); + + CTNetworkStatus retVal = CTNotReachable; + SCNetworkReachabilityFlags flags; + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + if(self.key == kCTLocalWiFiConnection) + { + retVal = [self localWiFiStatusForFlags: flags]; + } + else + { + retVal = [self networkStatusForFlags: flags]; + } + } + return retVal; +} + + +- (BOOL) isReachable { + + NSAssert(reachabilityRef, @"isReachable called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags = 0; + CTNetworkStatus status = kCTNotReachable; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + + logReachabilityFlags(flags); + + if(self.key == kCTLocalWiFiConnection) { + + status = [self localWiFiStatusForFlags: flags]; + + } else { + + status = [self networkStatusForFlags: flags]; + + } + + return (kCTNotReachable != status); + + } + + return NO; + +} // isReachable + + +- (BOOL) isConnectionRequired { + + return [self connectionRequired]; + +} // isConnectionRequired + + +- (BOOL) connectionRequired { + + NSAssert(reachabilityRef, @"connectionRequired called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + + logReachabilityFlags(flags); + + return (flags & kSCNetworkReachabilityFlagsConnectionRequired); + + } + + return NO; + +} // connectionRequired +#endif + +@end diff --git a/core/Frameworks/asi-http-request/LICENSE b/core/Frameworks/asi-http-request/LICENSE new file mode 100644 index 00000000..8f87eb65 --- /dev/null +++ b/core/Frameworks/asi-http-request/LICENSE @@ -0,0 +1,28 @@ +* Copyright (c) 2007-2011, All-Seeing Interactive +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of the All-Seeing Interactive nor the +* names of its contributors may be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY All-Seeing Interactive ''AS IS'' AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL All-Seeing Interactive BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +A different license may apply to other software included in this package, +including GHUnit and Andrew Donoho's Reachability class. Please consult their +respective headers for the terms of their individual licenses. \ No newline at end of file diff --git a/core/Frameworks/objc-guid/CTGuid.h b/core/Frameworks/objc-guid/CTGuid.h new file mode 100644 index 00000000..3ec27ae2 --- /dev/null +++ b/core/Frameworks/objc-guid/CTGuid.h @@ -0,0 +1,46 @@ +// +// CTGuid.m +// Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. +// Source: https://code.google.com/p/objc-guid/ +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +#import + +typedef enum +{ + CTGuidFormatCompact = 0, + CTGuidFormatIncludeDashes = 1, + CTGuidFormatIncludeBraces = 2, + CTGuidFormatIncludeParenthesis = 4, + CTGuidFormatUpperCase = 8, + CTGuidFormatDashed = CTGuidFormatIncludeDashes, + CTGuidFormatBraces = CTGuidFormatIncludeDashes | CTGuidFormatIncludeBraces, + CTGuidFormatParenthesis = CTGuidFormatIncludeDashes | CTGuidFormatIncludeParenthesis +} +CTGuidFormat; + +@interface CTGuid : NSObject +{ +@private + UInt8 data[16]; +} + +-(id) initWithString:(NSString*)string; +-(id) initWithBytes:(UInt8[16])bytes; +-(NSString*) stringValue; +-(CFUUIDBytes) uuidBytes; +-(void) byteDataToBuffer:(UInt8*)bytes; +-(NSString*) stringValueWithFormat:(CTGuidFormat)format; +-(BOOL) isEmpty; +-(BOOL) isEqual:(id)object; +-(NSUInteger) hash; +-(NSString*) description; + ++(CTGuid *) randomGuid; ++(CTGuid *) emptyGuid; ++(CTGuid *) guidFromString:(NSString*)guidString; + +@end diff --git a/core/Frameworks/objc-guid/CTGuid.m b/core/Frameworks/objc-guid/CTGuid.m new file mode 100644 index 00000000..3e616f55 --- /dev/null +++ b/core/Frameworks/objc-guid/CTGuid.m @@ -0,0 +1,487 @@ +// +// CTGuid.m +// Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved. +// Source: https://code.google.com/p/objc-guid/ +// +// Connect SDK Note: +// CT has been prepended to all members of this framework to avoid namespace collisions +// + +#import "CTGuid.h" + +#define ConvertToHexCharLower(x) (((x) >= 0 && (x) <= 9) ? (char)((x) + '0') : (char)((x) - 10 + 'a')) +#define ConvertToHexCharUpper(x) (((x) >= 0 && (x) <= 9) ? (char)((x) + '0') : (char)((x) - 10 + 'A')) +#define IsHexChar(x) ((((x) >= '0' && (x) <= '9') || ((x) >= 'a' && (x) <= 'f') || ((x) >= 'A' && (x) <= 'F'))) + +@interface CTGuid () +-(id) initWithCFUUIDBytes:(CFUUIDBytes)bytes; +-(id) initWithBytePointer:(unsigned char*)bytes; ++(BOOL) guidBytesFromString:(NSString*)guidString bytes:(UInt8*)bytes; +@end + +@implementation CTGuid + +-(id) initWithCoder:(NSCoder*)decoder +{ + if (self = [super init]) + { + NSUInteger length; + + const uint8_t* bytes = [decoder decodeBytesForKey:@"data" returnedLength:&length]; + + if (length == 16) + { + memcpy(data, bytes, length); + } + } + + return self; +} + +-(void) encodeWithCoder:(NSCoder*)coder +{ + [coder encodeBytes:&data[0] length:16 forKey:@"data"]; +} + ++(CTGuid *) emptyGuid +{ + static CTGuid * retval; + + if (retval == nil) + { + retval = [[CTGuid alloc] init]; + } + + return retval; +} + +-(id) initWithBytes:(UInt8[16])bytes +{ + return [self initWithBytePointer:&bytes[0]]; +} + +-(id) initWithCFUUIDBytes:(CFUUIDBytes)bytes +{ + if (self = [super init]) + { + memcpy(data, &bytes, 16); + } + + return self; +} + +-(id) initWithBytePointer:(UInt8*)bytes +{ + if (self = [super init]) + { + memcpy(data, bytes, 16); + } + + return self; +} + +-(id) initWithString:(NSString*)string +{ + if (self = [super init]) + { + if (![CTGuid guidBytesFromString:string bytes:&data[0]]) + { + [NSException raise:@"Guid wrong format" format:@"The string %@ is not a value guid", string]; + } + } + + return self; +} + +-(NSString*) stringValueWithFormat:(CTGuidFormat)format +{ + int index = 0; + unichar buffer[32 + 4 + 2]; + + if (format & CTGuidFormatIncludeBraces) + { + buffer[index++] = '{'; + } + else if (format & CTGuidFormatIncludeParenthesis) + { + buffer[index++] = '('; + } + + if (format & CTGuidFormatUpperCase) + { + if (format & CTGuidFormatIncludeDashes) + { + for (int i = 0; i < 4; i++) + { + unsigned char c = data[i]; + unsigned char c1 = c & 0x0f; + unsigned char c2 = (c & 0xf0) >> 4; + + buffer[index++] = ConvertToHexCharUpper(c2); + buffer[index++] = ConvertToHexCharUpper(c1); + } + + buffer[index++] = '-'; + + for (int i = 4; i < 6; i++) + { + unsigned char c = data[i]; + unsigned char c1 = c & 0x0f; + unsigned char c2 = (c & 0xf0) >> 4; + + buffer[index++] = ConvertToHexCharUpper(c2); + buffer[index++] = ConvertToHexCharUpper(c1); + } + + buffer[index++] = '-'; + + for (int i = 6; i < 8; i++) + { + unsigned char c = data[i]; + unsigned char c1 = c & 0x0f; + unsigned char c2 = (c & 0xf0) >> 4; + + buffer[index++] = ConvertToHexCharUpper(c2); + buffer[index++] = ConvertToHexCharUpper(c1); + } + + buffer[index++] = '-'; + + for (int i = 8; i < 10; i++) + { + unsigned char c = data[i]; + unsigned char c1 = c & 0x0f; + unsigned char c2 = (c & 0xf0) >> 4; + + buffer[index++] = ConvertToHexCharUpper(c2); + buffer[index++] = ConvertToHexCharUpper(c1); + } + + buffer[index++] = '-'; + + for (int i = 10; i < 16; i++) + { + unsigned char c = data[i]; + unsigned char c1 = c & 0x0f; + unsigned char c2 = (c & 0xf0) >> 4; + + buffer[index++] = ConvertToHexCharUpper(c2); + buffer[index++] = ConvertToHexCharUpper(c1); + } + } + else + { + for (int i = 0; i < 16; i++) + { + unsigned char c = data[i]; + unsigned char c1 = c & 0x0f; + unsigned char c2 = (c & 0xf0) >> 4; + + buffer[index++] = ConvertToHexCharUpper(c2); + buffer[index++] = ConvertToHexCharUpper(c1); + } + } + } + else + { + if (format & CTGuidFormatIncludeDashes) + { + for (int i = 0; i < 4; i++) + { + unsigned char c = data[i]; + unsigned char c1 = c & 0x0f; + unsigned char c2 = (c & 0xf0) >> 4; + + buffer[index++] = ConvertToHexCharLower(c2); + buffer[index++] = ConvertToHexCharLower(c1); + } + + buffer[index++] = '-'; + + for (int i = 4; i < 6; i++) + { + unsigned char c = data[i]; + unsigned char c1 = c & 0x0f; + unsigned char c2 = (c & 0xf0) >> 4; + + buffer[index++] = ConvertToHexCharLower(c2); + buffer[index++] = ConvertToHexCharLower(c1); + } + + buffer[index++] = '-'; + + for (int i = 6; i < 8; i++) + { + unsigned char c = data[i]; + unsigned char c1 = c & 0x0f; + unsigned char c2 = (c & 0xf0) >> 4; + + buffer[index++] = ConvertToHexCharLower(c2); + buffer[index++] = ConvertToHexCharLower(c1); + } + + buffer[index++] = '-'; + + for (int i = 8; i < 10; i++) + { + unsigned char c = data[i]; + unsigned char c1 = c & 0x0f; + unsigned char c2 = (c & 0xf0) >> 4; + + buffer[index++] = ConvertToHexCharLower(c2); + buffer[index++] = ConvertToHexCharLower(c1); + } + + buffer[index++] = '-'; + + for (int i = 10; i < 16; i++) + { + unsigned char c = data[i]; + unsigned char c1 = c & 0x0f; + unsigned char c2 = (c & 0xf0) >> 4; + + buffer[index++] = ConvertToHexCharLower(c2); + buffer[index++] = ConvertToHexCharLower(c1); + } + } + else + { + for (int i = 0; i < 16; i++) + { + unsigned char c = data[i]; + unsigned char c1 = c & 0x0f; + unsigned char c2 = (c & 0xf0) >> 4; + + buffer[index++] = ConvertToHexCharLower(c2); + buffer[index++] = ConvertToHexCharLower(c1); + } + } + } + if (format & CTGuidFormatIncludeBraces) + { + buffer[index++] = '}'; + } + else if (format & CTGuidFormatIncludeParenthesis) + { + buffer[index++] = ')'; + } + + return [NSString stringWithCharacters:buffer length:index]; +} + ++(CTGuid *) randomGuid +{ + CFUUIDRef uuid = CFUUIDCreate(0); + CFUUIDBytes bytes = CFUUIDGetUUIDBytes(uuid); + CFRelease(uuid); + + CTGuid * retval = [[CTGuid alloc] initWithCFUUIDBytes:bytes]; + + return retval; +} + ++(CTGuid *) guidFromString:(NSString*)guidString +{ + UInt8 bytes[16]; + + if ([self guidBytesFromString:guidString bytes:&bytes[0]]) + { + return [[CTGuid alloc] initWithBytes:bytes]; + } + + return nil; +} + ++(BOOL) guidBytesFromString:(NSString*)guidString bytes:(UInt8*)bytes +{ + int offset = 0; + int length = (int)guidString.length; + + if (guidString == nil || length == 0) + { + return NO; + } + + char firstChar = [guidString characterAtIndex:0]; + memset(bytes, 0, 16); + + if (firstChar == '{') + { + if (length == 0x26) + { + if ([guidString characterAtIndex:0x25] != '}') + { + return NO; + } + } + else if (length == 0x22) + { + if ([guidString characterAtIndex:0x21] != '}') + { + return NO; + } + } + else + { + return NO; + } + + offset = 1; + } + else if (firstChar == '(') + { + if (length == 0x26) + { + if ([guidString characterAtIndex:0x25] != ')') + { + return NO; + } + } + else if (length == 0x22) + { + if ([guidString characterAtIndex:0x21] != ')') + { + return NO; + } + } + else + { + return NO; + } + + offset = 1; + } + else if (length != 0x24 && length != 0x20) + { + return NO; + } + + if ((offset == 1 && length == 0x26) || (offset == 0 && length == 0x24)) + { + if ((([guidString characterAtIndex:8 + offset] != '-') || ([guidString characterAtIndex:13 + offset] != '-')) + || (([guidString characterAtIndex:0x12 + offset] != '-' || ([guidString characterAtIndex:0x17 + offset] != '-')))) + { + return NO; + } + } + + int x = 0; + int dataIndex = 0; + + for (int i = 0, j = 0; i < guidString.length; i++, dataIndex++) + { + unichar c = [guidString characterAtIndex:i]; + + if (c == '{' || c == '}' || c == '-') + { + continue; + } + + int shift; + int mask; + + x = j / 2; + + if (j % 2 == 0) + { + shift = 4; + mask = 0xf0; + } + else + { + shift = 0; + mask = 0x0f; + } + + if (x >= 16) + { + return NO; + } + + if (c >= 'a' && c <= 'f') + { + bytes[x] |= (((c - 'a' + 10) << shift) & mask); + } + else if (c >= 'A' && c <= 'F') + { + bytes[x] |= (((c - 'A' + 10) << shift) & mask); + } + else if (c >= '0' && c <= '9') + { + bytes[x] |= (((c - '0') << shift) & mask); + } + else + { + return NO; + } + + j++; + } + + return x == 15; +} + +-(BOOL) isEqual:(id)other +{ + if (other == self) + { + return YES; + } + + if (other == nil || [other class] != CTGuid.class) + { + return NO; + } + + return memcmp(&data[0], ((CTGuid *)other)->data, 16) == 0; +} + +-(NSUInteger) hash +{ + register UInt32* intData = (UInt32*)&data[0]; + + return intData[0] ^ intData[1] ^ intData[2] ^ intData[3]; +} + +-(CFUUIDBytes) uuidBytes +{ + CFUUIDBytes retval; + + memcpy(&retval, data, 16); + + return retval; +} + +-(void) byteDataToBuffer:(UInt8*)bytes +{ + memcpy(bytes, data, 16); +} + +-(BOOL) isEmpty +{ + return [self isEqual:CTGuid.emptyGuid]; +} + +-(NSString*) stringValue +{ + return [self stringValueWithFormat:CTGuidFormatDashed]; +} + +-(NSString*) description +{ + return [self stringValueWithFormat:CTGuidFormatDashed]; +} + +-(NSString*) compactStringValue +{ + return [self stringValueWithFormat:CTGuidFormatCompact]; +} + +-(id) copyWithZone:(NSZone*)zone +{ + CTGuid * retval = [[CTGuid alloc] initWithBytes:data]; + + return retval; +} + +@end diff --git a/core/Frameworks/objc-guid/LICENSE b/core/Frameworks/objc-guid/LICENSE new file mode 100644 index 00000000..2f4a5347 --- /dev/null +++ b/core/Frameworks/objc-guid/LICENSE @@ -0,0 +1,12 @@ +Copyright (c) 2012, Thong Nguyen (tumtumtum@gmail.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/core/Frameworks/xswi/XMLWriter.h b/core/Frameworks/xswi/XMLWriter.h new file mode 100644 index 00000000..3d6114ab --- /dev/null +++ b/core/Frameworks/xswi/XMLWriter.h @@ -0,0 +1,149 @@ +/*************************************************************************** + * + * XMLWriter: An XML stream writer for iOS. + * This file is part of the XSWI library - http://code.google.com/p/xswi/ + * + * Copyright (C) 2010 by Thomas Rørvik Skjølberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + +#import + +// xml stream writer +@protocol XMLStreamWriter + +- (void) writeStartDocument; +- (void) writeStartDocumentWithVersion:(NSString*)version; +- (void) writeStartDocumentWithEncodingAndVersion:(NSString*)encoding version:(NSString*)version; + +- (void) writeStartElement:(NSString *)localName; + +- (void) writeEndElement; // automatic end element (mirrors previous start element at the same level) +- (void) writeEndElement:(NSString *)localName; + +- (void) writeEmptyElement:(NSString *)localName; + +- (void) writeEndDocument; // write any remaining end elements + +- (void) writeAttribute:(NSString *)localName value:(NSString *)value; + +- (void) writeCharacters:(NSString*)text; +- (void) writeComment:(NSString*)comment; +- (void) writeProcessingInstruction:(NSString*)target data:(NSString*)data; +- (void) writeCData:(NSString*)cdata; + +// return the written xml string buffer +- (NSMutableString*) toString; +// return the written xml as data, set to the encoding used in the writeStartDocumentWithEncodingAndVersion method (UTF-8 per default) +- (NSData*) toData; + +// flush the buffers, if any +- (void) flush; +// close the writer and buffers, if any +- (void) close; + +- (void) setPrettyPrinting:(NSString*)indentation withLineBreak:(NSString*)lineBreak; + +@end + +// xml stream writer with namespace support +@protocol NSXMLStreamWriter + +- (void) writeStartElementWithNamespace:(NSString *)namespaceURI localName:(NSString *)localName; +- (void) writeEndElementWithNamespace:(NSString *)namespaceURI localName:(NSString *)localName; +- (void) writeEmptyElementWithNamespace:(NSString *)namespaceURI localName:(NSString *)localName; + +- (void) writeAttributeWithNamespace:(NSString *)namespaceURI localName:(NSString *)localName value:(NSString *)value; + +// set a namespace and prefix +- (void)setPrefix:(NSString*)prefix namespaceURI:(NSString *)namespaceURI; +// write (and set) a namespace and prefix +- (void) writeNamespace:(NSString*)prefix namespaceURI:(NSString *)namespaceURI; + +// set the default namespace (empty prefix) +- (void)setDefaultNamespace:(NSString*)namespaceURI; +// write (and set) the default namespace +- (void) writeDefaultNamespace:(NSString*)namespaceURI; + +- (NSString*)getPrefix:(NSString*)namespaceURI; +- (NSString*)getNamespaceURI:(NSString*)prefix; + +@end + +@interface XMLWriter : NSObject { + + // the current output buffer + NSMutableString* writer; + + // the target encoding + NSString* encoding; + + // the number current levels + int level; + // is the element open, i.e. the end bracket has not been written yet + BOOL openElement; + // does the element contain characters, cdata, comments + BOOL emptyElement; + + // the element stack. one per element level + NSMutableArray* elementLocalNames; + NSMutableArray* elementNamespaceURIs; + + // the namespace array. zero or more namespace attributes can be defined per element level + NSMutableArray* namespaceURIs; + // the namespace count. one per element level + NSMutableArray* namespaceCounts; + // the namespaces which have been written to the stream + NSMutableArray* namespaceWritten; + + // mapping of namespace URI to prefix and visa versa. Corresponds in size to the namespaceURIs array. + NSMutableDictionary* namespaceURIPrefixMap; + NSMutableDictionary* prefixNamespaceURIMap; + + // tag indentation + NSString* indentation; + // line break + NSString* lineBreak; + + // if true, then write elements without children as instead of + BOOL automaticEmptyElements; +} + +@property (nonatomic, retain, readwrite) NSString* indentation; +@property (nonatomic, retain, readwrite) NSString* lineBreak; +@property (nonatomic, assign, readwrite) BOOL automaticEmptyElements; +@property (nonatomic, readonly) int level; + +// helpful for formatting, special needs +// write linebreak, if any +- (void) writeLinebreak; +// write indentation, if any +- (void) writeIndentation; +// write end of start element, so that the start tag is complete +- (void) writeCloseStartElement; + +// write any outstanding namespace declaration attributes in a start element +- (void) writeNamespaceAttributes; +// write escaped text to the stream +- (void) writeEscape:(NSString*)value; +// wrote unescaped text to the stream +- (void) write:(NSString*)value; + +@end diff --git a/core/Frameworks/xswi/XMLWriter.m b/core/Frameworks/xswi/XMLWriter.m new file mode 100644 index 00000000..a285ebcb --- /dev/null +++ b/core/Frameworks/xswi/XMLWriter.m @@ -0,0 +1,745 @@ +/*************************************************************************** + * + * XMLWriter: An XML stream writer for iOS. + * This file is part of the XSWI library - http://code.google.com/p/xswi/ + * + * Copyright (C) 2010 by Thomas Rørvik Skjølberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + +#import "XMLWriter.h" + +#define NSBOOL(_X_) ((_X_) ? (id)kCFBooleanTrue : (id)kCFBooleanFalse) + +@interface XMLWriter (UtilityMethods) +// methods for internal use only +// pop the namespace stack, removing any namespaces which become out-of-scope +- (void) popNamespaceStack; +// push the namespace stack, denoting the namespaces whihch are in-scope +- (void) pushNamespaceStack; + +// add namespace and local name to the top of the element stack +- (void) pushElementStack:(NSString*)namespaceURI localName:(NSString*)localName; +// remove the top member of the element stack +- (void) popElementStack; + +// write close element, optionally as empty element +- (void) writeCloseElement:(BOOL)empty; +// write namespace attribute to stream +- (void) writeNamespaceToStream:(NSString*)prefix namespaceURI:(NSString*)namespaceURI; +// write a length of text to the stream with escaping +- (void) writeEscapeCharacters:(const UniChar*)characters length:(int)length; +@end + + +static NSString *const EMPTY_STRING = @""; +static NSString *const XML_NAMESPACE_URI = @"http://www.w3.org/XML/1998/namespace"; +static NSString *const XML_NAMESPACE_URI_PREFIX = @"xml"; +static NSString *const XMLNS_NAMESPACE_URI = @"http://www.w3.org/2000/xmlns/"; +static NSString *const XMLNS_NAMESPACE_URI_PREFIX = @"xmlns"; +static NSString *const XSI_NAMESPACE_URI = @"http://www.w3.org/2001/XMLSchema/"; +static NSString *const XSI_NAMESPACE_URI_PREFIX = @"xsi"; + +@implementation XMLWriter + +@synthesize automaticEmptyElements, indentation, lineBreak, level; + +- (XMLWriter*) init { + self = [super init]; + if (self != nil) { + // intialize variables + writer = [[NSMutableString alloc] init]; + level = 0; + openElement = NO; + emptyElement = NO; + + elementLocalNames = [[NSMutableArray alloc]init]; + elementNamespaceURIs = [[NSMutableArray alloc]init]; + + namespaceURIs = [[NSMutableArray alloc]init]; + namespaceCounts = [[NSMutableArray alloc]init]; + namespaceWritten = [[NSMutableArray alloc]init]; + + namespaceURIPrefixMap = [[NSMutableDictionary alloc] init]; + prefixNamespaceURIMap = [[NSMutableDictionary alloc] init]; + + // load default custom behaviour + automaticEmptyElements = YES; + + // setup default xml namespaces. assume both are previously known. + [namespaceCounts addObject:[NSNumber numberWithInt:2]]; + [self setPrefix:XML_NAMESPACE_URI_PREFIX namespaceURI:XML_NAMESPACE_URI]; + [self setPrefix:XMLNS_NAMESPACE_URI_PREFIX namespaceURI:XMLNS_NAMESPACE_URI]; + } + return self; +} + +- (void) pushNamespaceStack { + // step namespace count - add the current namespace count + NSNumber* previousCount = [namespaceCounts lastObject]; + if([namespaceURIs count] == [previousCount intValue]) { + // the count is still the same + [namespaceCounts addObject:previousCount]; + } else { + // the count has changed, save the it + NSNumber* count = [NSNumber numberWithInt:(int)[namespaceURIs count]]; + + [namespaceCounts addObject:count]; + } +} + +- (void) writeNamespaceAttributes { + if(openElement) { + // write namespace attributes in the namespace stack + NSNumber* previousCount = [namespaceCounts lastObject]; + for(int i = [previousCount intValue]; i < [namespaceURIs count]; i++) { + + // did we already write this namespace? + id written = [namespaceWritten objectAtIndex:i]; + if(written == NSBOOL(NO)) { + // write namespace + NSString* namespaceURI = [namespaceURIs objectAtIndex:i]; + NSString* prefix = [namespaceURIPrefixMap objectForKey:namespaceURI]; + + [self writeNamespaceToStream:prefix namespaceURI:namespaceURI]; + + [namespaceWritten replaceObjectAtIndex:i withObject:NSBOOL(YES)]; + } else { + // already written namespace + } + } + } else { + @throw([NSException exceptionWithName:@"XMLWriterException" reason:@"No open start element" userInfo:NULL]); + } +} + +- (void) popNamespaceStack { + // step namespaces one level down + if([namespaceCounts lastObject] != [namespaceCounts objectAtIndex:([namespaceCounts count] - 2)]) { + // remove namespaces which now are out of scope, i.e. between the current and the previus count + NSNumber* previousCount = [namespaceCounts lastObject]; + NSNumber* currentCount = [namespaceCounts objectAtIndex:([namespaceCounts count] - 2)]; + for(int i = [previousCount intValue] - 1; i >= [currentCount intValue]; i--) { + NSString* removedNamespaceURI = [namespaceURIs objectAtIndex:i]; + NSString* removedPrefix = [namespaceURIPrefixMap objectForKey:removedNamespaceURI]; + + [prefixNamespaceURIMap removeObjectForKey:removedPrefix]; + [namespaceURIPrefixMap removeObjectForKey:removedNamespaceURI]; + + [namespaceURIs removeLastObject]; + + [namespaceWritten removeLastObject]; + } + } else { + // not necessary to remove any namespaces + } + [namespaceCounts removeLastObject]; +} + +- (void)setPrefix:(NSString*)prefix namespaceURI:(NSString *)namespaceURI { + if(!namespaceURI) { + // raise exception + @throw([NSException exceptionWithName:@"XMLWriterException" reason:@"Namespace cannot be NULL" userInfo:NULL]); + } + if(!prefix) { + // raise exception + @throw([NSException exceptionWithName:@"XMLWriterException" reason:@"Prefix cannot be NULL" userInfo:NULL]); + } + if([namespaceURIPrefixMap objectForKey:namespaceURI]) { + // raise exception + @throw([NSException exceptionWithName:@"XMLWriterException" reason:[NSString stringWithFormat:@"Name namespace %@ has already been set", namespaceURI] userInfo:NULL]); + } + if([prefixNamespaceURIMap objectForKey:prefix]) { + // raise exception + if([prefix length]) { + @throw([NSException exceptionWithName:@"XMLWriterException" reason:[NSString stringWithFormat:@"Prefix %@ has already been set", prefix] userInfo:NULL]); + } else { + @throw([NSException exceptionWithName:@"XMLWriterException" reason:@"Default namespace has already been set" userInfo:NULL]); + } + } + + // increase the namespaces and add prefix mapping + [namespaceURIs addObject:namespaceURI]; + [namespaceURIPrefixMap setObject:prefix forKey:namespaceURI]; + [prefixNamespaceURIMap setObject:namespaceURI forKey:prefix]; + + if(openElement) { // write the namespace now + [self writeNamespaceToStream:prefix namespaceURI:namespaceURI]; + + [namespaceWritten addObject:NSBOOL(YES)]; + } else { + // write the namespace as the next start element is closed + [namespaceWritten addObject:NSBOOL(NO)]; + } +} + +- (NSString*)getPrefix:(NSString*)namespaceURI { + return [namespaceURIPrefixMap objectForKey:namespaceURI]; +} + +- (void) pushElementStack:(NSString*)namespaceURI localName:(NSString*)localName { + // save for end elements + [elementLocalNames addObject:localName]; + if(namespaceURI) { + [elementNamespaceURIs addObject:namespaceURI]; + } else { + [elementNamespaceURIs addObject:EMPTY_STRING]; + } +} + +- (void) popElementStack { + // remove element traces + [elementNamespaceURIs removeLastObject]; + [elementLocalNames removeLastObject]; +} + +- (void) writeStartDocument { + [self writeStartDocumentWithEncodingAndVersion:NULL version:NULL]; +} + +- (void) writeStartDocumentWithVersion:(NSString*)version { + [self writeStartDocumentWithEncodingAndVersion:NULL version:version]; +} + +- (void) writeStartDocumentWithEncodingAndVersion:(NSString*)aEncoding version:(NSString*)version { + if([writer length] != 0) { + // raise exception - Starting document which is not empty + @throw([NSException exceptionWithName:@"XMLWriterException" reason:@"Document has already been started" userInfo:NULL]); + } else { + [self write:@""]; + + } +} + +- (void) writeEndDocument { + while (level > 0) { + [self writeEndElement]; + } +} + +- (void) writeStartElement:(NSString *)localName { + [self writeStartElementWithNamespace:NULL localName:localName]; +} + +- (void) writeCloseStartElement { + if(openElement) { + [self writeCloseElement:NO]; + } else { + // raise exception + @throw([NSException exceptionWithName:@"XMLWriterException" reason:@"No open start element" userInfo:NULL]); + } +} + +- (void) writeCloseElement:(BOOL)empty { + [self writeNamespaceAttributes]; + [self pushNamespaceStack]; + + if(empty) { + [self write:@" />"]; + } else { + [self write:@">"]; + } + + openElement = NO; +} + +- (void) writeEndElement:(NSString *)localName { + [self writeEndElementWithNamespace:NULL localName:localName]; +} + +- (void) writeEndElement { + if(openElement && automaticEmptyElements) { + // go for + [self writeCloseElement:YES]; // write empty end element + + [self popNamespaceStack]; + [self popElementStack]; + + emptyElement = YES; + openElement = NO; + + level -= 1; + } else { + NSString* namespaceURI = [elementNamespaceURIs lastObject]; + NSString* localName = [elementLocalNames lastObject]; + + if(namespaceURI == EMPTY_STRING) { + [self writeEndElementWithNamespace:NULL localName:localName]; + } else { + [self writeEndElementWithNamespace:namespaceURI localName:localName]; + } + } +} + +- (void) writeStartElementWithNamespace:(NSString *)namespaceURI localName:(NSString *)localName { + if(openElement) { + [self writeCloseElement:NO]; + } + + [self writeLinebreak]; + [self writeIndentation]; + + [self write:@"<"]; + if(namespaceURI) { + NSString* prefix = [namespaceURIPrefixMap objectForKey:namespaceURI]; + + if(!prefix) { + // raise exception + @throw([NSException exceptionWithName:@"XMLWriterException" reason:[NSString stringWithFormat:@"Unknown namespace URI %@", namespaceURI] userInfo:NULL]); + } + + if([prefix length]) { + [self write:prefix]; + [self write:@":"]; + } + } + [self write:localName]; + + [self pushElementStack:namespaceURI localName:localName]; + + openElement = YES; + emptyElement = YES; + level += 1; + +} + +- (void) writeEndElementWithNamespace:(NSString *)namespaceURI localName:(NSString *)localName { + if(level <= 0) { + // raise exception + @throw([NSException exceptionWithName:@"XMLWriterException" reason:@"Cannot write more end elements than start elements." userInfo:NULL]); + } + + level -= 1; + + if(openElement) { + // go for + [self writeCloseElement:NO]; + } else { + if(emptyElement) { + // go for linebreak + indentation + + [self writeLinebreak]; + [self writeIndentation]; + } else { + // go for characters + } + } + + // write standard end element + [self write:@""]; + + [self popNamespaceStack]; + [self popElementStack]; + + emptyElement = YES; + openElement = NO; +} + +- (void) writeEmptyElement:(NSString *)localName { + if(openElement) { + [self writeCloseElement:NO]; + } + + [self writeLinebreak]; + [self writeIndentation]; + + [self write:@"<"]; + [self write:localName]; + [self write:@" />"]; + + emptyElement = YES; + openElement = NO; +} + +- (void) writeEmptyElementWithNamespace:(NSString *)namespaceURI localName:(NSString *)localName { + if(openElement) { + [self writeCloseElement:NO]; + } + + [self writeLinebreak]; + [self writeIndentation]; + + [self write:@"<"]; + + if(namespaceURI) { + NSString* prefix = [namespaceURIPrefixMap objectForKey:namespaceURI]; + + if(!prefix) { + // raise exception + @throw([NSException exceptionWithName:@"XMLWriterException" reason:[NSString stringWithFormat:@"Unknown namespace URI %@", namespaceURI] userInfo:NULL]); + } + + if([prefix length]) { + [self write:prefix]; + [self write:@":"]; + } + } + + [self write:localName]; + [self write:@" />"]; + + emptyElement = YES; + openElement = NO; +} + +- (void) writeAttribute:(NSString *)localName value:(NSString *)value { + [self writeAttributeWithNamespace:NULL localName:localName value:value]; +} + +- (void) writeAttributeWithNamespace:(NSString *)namespaceURI localName:(NSString *)localName value:(NSString *)value { + if(openElement) { + [self write:@" "]; + + if(namespaceURI) { + NSString* prefix = [namespaceURIPrefixMap objectForKey:namespaceURI]; + if(!prefix) { + // raise exception + @throw([NSException exceptionWithName:@"XMLWriterException" reason:[NSString stringWithFormat:@"Unknown namespace URI %@", namespaceURI] userInfo:NULL]); + } + + if([prefix length]) { + [self write:prefix]; + [self write:@":"]; + } + } + [self write:localName]; + [self write:@"=\""]; + [self writeEscape:value]; + [self write:@"\""]; + } else { + // raise expection + @throw([NSException exceptionWithName:@"XMLWriterException" reason:@"No open start element" userInfo:NULL]); + } +} + +- (void)setDefaultNamespace:(NSString*)namespaceURI { + [self setPrefix:EMPTY_STRING namespaceURI:namespaceURI]; +} + +- (void) writeNamespace:(NSString*)prefix namespaceURI:(NSString *)namespaceURI { + if(openElement) { + [self setPrefix:prefix namespaceURI:namespaceURI]; + } else { + // raise exception + @throw([NSException exceptionWithName:@"XMLWriterException" reason:@"No open start element" userInfo:NULL]); + } +} + +- (void) writeDefaultNamespace:(NSString*)namespaceURI { + [self writeNamespace:EMPTY_STRING namespaceURI:namespaceURI]; +} + +- (NSString*)getNamespaceURI:(NSString*)prefix { + return [prefixNamespaceURIMap objectForKey:prefix]; +} + +-(void) writeNamespaceToStream:(NSString*)prefix namespaceURI:(NSString*)namespaceURI { + if(openElement) { // write the namespace now + [self write:@" "]; + + NSString* xmlnsPrefix = [self getPrefix:XMLNS_NAMESPACE_URI]; + if(!xmlnsPrefix) { + // raise exception + @throw([NSException exceptionWithName:@"XMLWriterException" reason:[NSString stringWithFormat:@"Cannot declare namespace without namespace %@", XMLNS_NAMESPACE_URI] userInfo:NULL]); + } + + [self write:xmlnsPrefix]; // xmlns + if([prefix length]) { + // write xmlns:prefix="namespaceURI" attribute + + [self write:@":"]; // colon + [self write:prefix]; // prefix + } else { + // write xmlns="namespaceURI" attribute + } + [self write:@"=\""]; + [self writeEscape:namespaceURI]; + [self write:@"\""]; + } else { + @throw([NSException exceptionWithName:@"XMLWriterException" reason:@"No open start element" userInfo:NULL]); + } +} + +- (void) writeCharacters:(NSString*)text { + if(openElement) { + [self writeCloseElement:NO]; + } + + [self writeEscape:text]; + + emptyElement = NO; +} + +- (void) writeComment:(NSString*)comment { + if(openElement) { + [self writeCloseElement:NO]; + } + [self write:@""]; + + emptyElement = NO; +} + +- (void) writeProcessingInstruction:(NSString*)target data:(NSString*)data { + if(openElement) { + [self writeCloseElement:NO]; + } + [self write:@""]; + + emptyElement = NO; +} + +- (void) writeCData:(NSString*)cdata { + if(openElement) { + [self writeCloseElement:NO]; + } + [self write:@""]; + + emptyElement = NO; +} + +- (void) write:(NSString*)value { + [writer appendString:value]; +} + +- (void) writeEscape:(NSString*)value { + + const UniChar *characters = CFStringGetCharactersPtr((CFStringRef)value); + + if (characters) { + // main flow + [self writeEscapeCharacters:characters length:(int)[value length]]; + } else { + // we need to read/copy the characters for some reason, from the docs of CFStringGetCharactersPtr: + // A pointer to a buffer of Unicode character or NULL if the internal storage of the CFString does not allow this to be returned efficiently. + // Whether or not this function returns a valid pointer or NULL depends on many factors, all of which depend on how the string was created and its properties. In addition, the function result might change between different releases and on different platforms. So do not count on receiving a non- NULL result from this function under any circumstances (except when the object is created with CFStringCreateMutableWithExternalCharactersNoCopy). + + // we dont need the whole data length at once + NSMutableData *data = [NSMutableData dataWithLength:256 * sizeof(UniChar)]; + + if(!data) { + // raise exception - no more memory + @throw([NSException exceptionWithName:@"XMLWriterException" reason:[NSString stringWithFormat:@"Could not allocate data buffer of %i unicode characters", 256] userInfo:NULL]); + } + + int count = 0; + do { + int length; + if(count + 256 < [value length]) { + length = 256; + } else { + length = (int)[value length] - count; + } + + [value getCharacters:[data mutableBytes] range:NSMakeRange(count, length)]; + + [self writeEscapeCharacters:[data bytes] length:length]; + + count += length; + } while(count < [value length]); + + // buffers autorelease + } +} + +- (void)writeEscapeCharacters:(const UniChar*)characters length:(int)length { + int rangeStart = 0; + int rangeLength = 0; + + for(int i = 0; i < length; i++) { + + UniChar c = characters[i]; + if (c <= 0xd7ff) { + if (c >= 0x20) { + switch (c) { + case 34: { + // write range if any + if(rangeLength) { + CFStringAppendCharacters((CFMutableStringRef)writer, characters + rangeStart, rangeLength); + } + [self write:@"""]; + + break; + } + // quot + case 38: { + // write range if any + if(rangeLength) { + CFStringAppendCharacters((CFMutableStringRef)writer, characters + rangeStart, rangeLength); + } + [self write:@"&"]; + + break; + } + // amp; + case 60: { + // write range if any + if(rangeLength) { + CFStringAppendCharacters((CFMutableStringRef)writer, characters + rangeStart, rangeLength); + } + + [self write:@"<"]; + + break; + } + // lt; + case 62: { + // write range if any + if(rangeLength) { + CFStringAppendCharacters((CFMutableStringRef)writer, characters + rangeStart, rangeLength); + } + + [self write:@">"]; + + break; + } + // gt; + default: { + // valid + rangeLength++; + + // note: we dont need to escape char 39 for ' because we use double quotes exclusively + + continue; + } + } + + // set range start to next + rangeLength = 0; + rangeStart = i + 1; + + } else { + if (c == '\n' || c == '\r' || c == '\t') { + // valid; + rangeLength++; + + continue; + } else { + // invalid, skip + } + } + } else if (c < 0xE000) { + // invalid, skip + } else if (c <= 0xFFFD) { + // valid + rangeLength++; + + continue; + } else { + // invalid, skip + } + + // write range if any + if(rangeLength) { + CFStringAppendCharacters((CFMutableStringRef)writer, characters + rangeStart, rangeLength); + } + + // set range start to next + rangeLength = 0; + rangeStart = i + 1; + } + + // write range if any + if(rangeLength) { + // main flow will probably write all characters here + CFStringAppendCharacters((CFMutableStringRef)writer, characters + rangeStart, rangeLength); + } +} + +- (void)writeLinebreak { + if(lineBreak) { + [self write:lineBreak]; + } +} + +- (void)writeIndentation { + if(indentation) { + for (int i = 0; i < level; i++ ) { + [self write:indentation]; + } + } +} + +- (void) flush { + // do nothing +} + +- (void) close { + // do nothing +} + +- (NSMutableString*) toString { + return writer; +} + +- (NSData*) toData { + if(encoding) { + return [writer dataUsingEncoding: CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)encoding)) allowLossyConversion:NO]; + } else { + return [writer dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO]; + } +} + +- (void) setPrettyPrinting:(NSString*)aIndentation withLineBreak:(NSString*)aLineBreak { + self.indentation = aIndentation; + self.lineBreak = aLineBreak; +} + + +@end diff --git a/core/Helpers/AppInfo.h b/core/Helpers/AppInfo.h new file mode 100644 index 00000000..7258d662 --- /dev/null +++ b/core/Helpers/AppInfo.h @@ -0,0 +1,58 @@ +// +// AppInfo.h +// Connect SDK +// +// Created by Jeremy White on 1/3/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + + +/*! + * Normalized reference object for information about a DeviceService's app. This object will, in most cases, be used to launch apps. + * + * In some cases, all that is needed to launch an app is the app id. For these cases, a static constructor method has been provided. + */ +@interface AppInfo : NSObject + +/*! + * ID of the app on the first screen device. Format is different depending on the platform. (ex. youtube.leanback.v4, 0000001134, netflix, etc). + */ +@property (nonatomic, strong) NSString *id; + +/*! User-friendly name of the app (ex. YouTube, Browser, Netflix, etc). */ +@property (nonatomic, strong) NSString *name; + +/*! Raw data from the first screen device about the app. In most cases, this is an NSDictionary. */ +@property (nonatomic, strong) id rawData; + +/*! + * Compares two AppInfo objects. + * + * @param appInfo AppInfo object to compare. + * + * @return YES if both AppInfo id values are equal + */ +- (BOOL)isEqual:(AppInfo *)appInfo; + +/*! + * Static constructor method. + * + * @param appId ID of the app on the first screen device + */ ++ (AppInfo *) appInfoForId:(NSString *)appId; + +@end diff --git a/core/Helpers/AppInfo.m b/core/Helpers/AppInfo.m new file mode 100644 index 00000000..dc625c34 --- /dev/null +++ b/core/Helpers/AppInfo.m @@ -0,0 +1,38 @@ +// +// AppInfo.m +// Connect SDK +// +// Created by Jeremy White on 1/3/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "AppInfo.h" + +@implementation AppInfo + ++ (AppInfo *) appInfoForId:(NSString *)appId +{ + AppInfo *appInfo = [AppInfo new]; + appInfo.id = appId; + + return appInfo; +} + +- (BOOL)isEqual:(AppInfo *)appInfo +{ + return [self.id isEqualToString:appInfo.id]; +} + +@end diff --git a/core/Helpers/AppStateChangeNotifier.h b/core/Helpers/AppStateChangeNotifier.h new file mode 100644 index 00000000..27c3824a --- /dev/null +++ b/core/Helpers/AppStateChangeNotifier.h @@ -0,0 +1,58 @@ +// +// AppStateChangeNotifier.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/15/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@protocol BlockRunner; + +NS_ASSUME_NONNULL_BEGIN +/** + * Listens to app state change events (didEnterBackground and didBecomeActive, + * in particular) and allows other components be notified about them using a + * simpler API. + */ +@interface AppStateChangeNotifier : NSObject + +/// Type of a block that is called on an app state change event. +typedef void (^AppStateChangeBlock)(); + +/// The block is called when the app has entered background. +@property (nonatomic, copy, nullable) AppStateChangeBlock didBackgroundBlock; + +/// The block is called when the app has entered foreground. +@property (nonatomic, copy, nullable) AppStateChangeBlock didForegroundBlock; + +/// The @c BlockRunner instance specifying where to run the blocks. The +/// default value is the main dispatch queue runner. Cannot be @c nil, as it +/// will reset to the default value. +@property (nonatomic, strong) id blockRunner; + + +/// Starts listening for app state change events. This method is idempotent. +/// @warning You @b MUST call @c -stopListening for this object to be removed. +- (void)startListening; + +/// Stops listening for app state change events. This method is idempotent. +/// @warning This method @b MUST be called to @c dealloc this object if you +/// called @c -startListening before. +- (void)stopListening; + +@end +NS_ASSUME_NONNULL_END diff --git a/core/Helpers/AppStateChangeNotifier.m b/core/Helpers/AppStateChangeNotifier.m new file mode 100644 index 00000000..49bb524b --- /dev/null +++ b/core/Helpers/AppStateChangeNotifier.m @@ -0,0 +1,105 @@ +// +// AppStateChangeNotifier.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/15/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "AppStateChangeNotifier.h" + +#import + +#import "DispatchQueueBlockRunner.h" + +@interface AppStateChangeNotifier () + +/// Stores an observer handle for @c UIApplicationDidEnterBackgroundNotification +/// if subscribed. +@property (strong) id backgroundObserverHandle; +/// Stores an observer handle for @c UIApplicationDidBecomeActiveNotification +/// if subscribed. +@property (strong) id foregroundObserverHandle; + +@end + +@implementation AppStateChangeNotifier + +#pragma mark - Public Methods + +- (void)startListening { + // both should be either subscribed or not + const BOOL alreadySubscribed = (self.backgroundObserverHandle || + self.foregroundObserverHandle); + if (!alreadySubscribed) { + // the queue is retained by the NSNotificationCenter, and is released + // in -stopListening + NSOperationQueue *queue = [NSOperationQueue new]; + queue.maxConcurrentOperationCount = 1; + queue.name = [NSString stringWithFormat:@"%@ notification queue", self]; + + self.backgroundObserverHandle = ({ + [[self center] addObserverForName:UIApplicationDidEnterBackgroundNotification + object:[UIApplication sharedApplication] + queue:queue + usingBlock:^(NSNotification *note) { + [self runStateChangeBlock: + self.didBackgroundBlock]; + }]; + }); + + self.foregroundObserverHandle = ({ + [[self center] addObserverForName:UIApplicationDidBecomeActiveNotification + object:[UIApplication sharedApplication] + queue:queue + usingBlock:^(NSNotification *note) { + [self runStateChangeBlock: + self.didForegroundBlock]; + }]; + }); + } +} + +- (void)stopListening { + [[self center] removeObserver:self.backgroundObserverHandle]; + self.backgroundObserverHandle = nil; + + [[self center] removeObserver:self.foregroundObserverHandle]; + self.foregroundObserverHandle = nil; +} + +- (id __nonnull)blockRunner { + if (!_blockRunner) { + _blockRunner = [DispatchQueueBlockRunner mainQueueRunner]; + } + + return _blockRunner; +} + +#pragma mark - Helpers + +/// Returns a @c NSNotificationCenter used by the class. +- (NSNotificationCenter *)center { + return [NSNotificationCenter defaultCenter]; +} + +/// Runs the given @c AppStateChangeBlock if not @c nil. +- (void)runStateChangeBlock:(nullable AppStateChangeBlock)block { + if (block) { + [self.blockRunner runBlock:block]; + } +} + +@end diff --git a/core/Helpers/BlockRunner/BlockRunner.h b/core/Helpers/BlockRunner/BlockRunner.h new file mode 100644 index 00000000..0d268def --- /dev/null +++ b/core/Helpers/BlockRunner/BlockRunner.h @@ -0,0 +1,70 @@ +// +// BlockRunner.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/4/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +/** + * Abstracts and encapsulates asynchrony, that is how and where blocks are run. + * Using this protocol, you can easily change which dispatch queue or + * @c NSOperationQueue delegate blocks are run on, instead of hard-coding + * dispatch_async(dispatch_get_main_queue(), ^{ });. For example: + * +@code +dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); +AppStateChangeNotifier *notifier = [AppStateChangeNotifier new]; +notifier.blockRunner = [[DispatchQueueBlockRunner alloc] initWithDispatchQueue:queue]; +@endcode + * + * Another great use case is turning asynchronous tests into synchronous, making + * them faster and easier: + * +@code +- (void)testStartListeningShouldSubscribeToDidBackgroundEvent { + AppStateChangeNotifier *notifier = [AppStateChangeNotifier new]; + notifier.blockRunner = [SynchronousBlockRunner new]; + [notifier startListening]; + + __block BOOL verified = NO; + notifier.didBackgroundBlock = ^{ + verified = YES; + }; + [self postNotificationName:UIApplicationDidEnterBackgroundNotification]; + + XCTAssertTrue(verified, @"didBackgroundBlock should be called"); +} +@endcode + * + * Here we use the synchronous block runner (instead of the default + * asynchronous, main queue one) to avoid writing asynchronous tests with + * @c XCTestExpectation. + */ +@protocol BlockRunner +@required + +/// A type for blocks without arguments and no return value. +typedef void(^VoidBlock)(void); + +/** + * Runs the given @c block somewhere, depending on the concrete implementation. + * @param block block to run; must not be @c nil. + */ +- (void)runBlock:(nonnull VoidBlock)block; + +@end diff --git a/core/Helpers/BlockRunner/DispatchQueueBlockRunner.h b/core/Helpers/BlockRunner/DispatchQueueBlockRunner.h new file mode 100644 index 00000000..52482d50 --- /dev/null +++ b/core/Helpers/BlockRunner/DispatchQueueBlockRunner.h @@ -0,0 +1,38 @@ +// +// DispatchQueueBlockRunner.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/4/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "BlockRunner.h" + +NS_ASSUME_NONNULL_BEGIN +/// Dispatches a @c block asynchronously on the given @c dispatch_queue_t queue. +/// @warning Please use the @c -initWithDispatchQueue: initializer, because you +/// must specify the @c queue. +@interface DispatchQueueBlockRunner : NSObject + +/// Designated initializer. Initializes the object with the given +/// dispatch queue which will run the blocks. The @c queue must not be +/// @c nil. +- (instancetype)initWithDispatchQueue:(dispatch_queue_t)queue; + +/// Convenience method that returns a block runner with the main dispatch queue. ++ (instancetype)mainQueueRunner; + +@end +NS_ASSUME_NONNULL_END diff --git a/core/Helpers/BlockRunner/DispatchQueueBlockRunner.m b/core/Helpers/BlockRunner/DispatchQueueBlockRunner.m new file mode 100644 index 00000000..b06f4a89 --- /dev/null +++ b/core/Helpers/BlockRunner/DispatchQueueBlockRunner.m @@ -0,0 +1,70 @@ +// +// DispatchQueueBlockRunner.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/4/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DispatchQueueBlockRunner.h" +#import "CommonMacros.h" + +@interface DispatchQueueBlockRunner () + +@property (nonatomic, strong, readonly) dispatch_queue_t queue; + +@end + +@implementation DispatchQueueBlockRunner + +#pragma mark - Init + +- (instancetype)initWithDispatchQueue:(dispatch_queue_t)queue { + if ((self = [super init])) { + _assert_state(nil != queue, @"nil queue is not accepted"); + _queue = queue; + } + + return self; +} + +- (instancetype)init { + _assert_state(NO, @"Please use the -initWithDispatchQueue: initializer"); + return nil; +} + +#pragma mark - Public Methods + +- (void)runBlock:(nonnull VoidBlock)block { + _assert_state(nil != block, @"nil block is not accepted"); + dispatch_async(self.queue, block); +} + ++ (instancetype)mainQueueRunner { + return [[[self class] alloc] initWithDispatchQueue:dispatch_get_main_queue()]; +} + +#pragma mark - Equality + +- (BOOL)isEqual:(id)object { + DispatchQueueBlockRunner *other = object; + return self.queue == other.queue; +} + +- (NSUInteger)hash { + return self.queue.hash; +} + +@end diff --git a/core/Helpers/BlockRunner/SynchronousBlockRunner.h b/core/Helpers/BlockRunner/SynchronousBlockRunner.h new file mode 100644 index 00000000..e9d0a3aa --- /dev/null +++ b/core/Helpers/BlockRunner/SynchronousBlockRunner.h @@ -0,0 +1,29 @@ +// +// SynchronousBlockRunner.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/4/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "BlockRunner.h" + +/** + * Runs a @c block synchronously on the current thread/queue (that is, in the + * middle of @c -runBlock: call). + */ +@interface SynchronousBlockRunner : NSObject + +@end diff --git a/core/Helpers/BlockRunner/SynchronousBlockRunner.m b/core/Helpers/BlockRunner/SynchronousBlockRunner.m new file mode 100644 index 00000000..1b663b80 --- /dev/null +++ b/core/Helpers/BlockRunner/SynchronousBlockRunner.m @@ -0,0 +1,34 @@ +// +// SynchronousBlockRunner.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 5/4/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "SynchronousBlockRunner.h" +#import "CommonMacros.h" + +@implementation SynchronousBlockRunner + +#pragma mark - BlockRunner + +- (void)runBlock:(nonnull VoidBlock)block { + _assert_state(nil != block, @"nil block is not accepted"); + + block(); +} + +@end diff --git a/core/Helpers/ChannelInfo.h b/core/Helpers/ChannelInfo.h new file mode 100644 index 00000000..1e880ecd --- /dev/null +++ b/core/Helpers/ChannelInfo.h @@ -0,0 +1,56 @@ +// +// ChannelInfo.h +// Connect SDK +// +// Created by Jeremy White on 1/2/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + + +/*! + * Normalized reference object for information about a TVs channels. This object is required to set the channel on a TV. + */ +@interface ChannelInfo : NSObject + +/*! TV's unique ID for the channel */ +@property (nonatomic, strong) NSString *id; + +/*! User-friendly name of the channel */ +@property (nonatomic, strong) NSString *name; + +/*! TV channel's number (likely to be a combination of the major & minor numbers) */ +@property (nonatomic, strong) NSString *number; + +/*! TV channel's major number */ +@property (nonatomic) int majorNumber; + +/*! TV channel's minor number */ +@property (nonatomic) int minorNumber; + +/*! Raw data from the first screen device about the channel. In most cases, this is an NSDictionary. */ +@property (nonatomic, strong) id rawData; + +/*! + * Compares two ChannelInfo objects. + * + * @param channelInfo ChannelInfo object to compare. + * + * @return YES if both ChannelInfo number & name values are equal + */ +- (BOOL)isEqual:(ChannelInfo *)channelInfo; + +@end diff --git a/core/Helpers/ChannelInfo.m b/core/Helpers/ChannelInfo.m new file mode 100644 index 00000000..20779a8b --- /dev/null +++ b/core/Helpers/ChannelInfo.m @@ -0,0 +1,31 @@ +// +// ChannelInfo.m +// Connect SDK +// +// Created by Jeremy White on 1/2/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ChannelInfo.h" + +@implementation ChannelInfo + +- (BOOL)isEqual:(ChannelInfo *)channelInfo +{ + return [self.number isEqualToString:channelInfo.number] + && [self.name isEqualToString:channelInfo.name]; +} + +@end diff --git a/core/Helpers/CommonMacros.h b/core/Helpers/CommonMacros.h new file mode 100644 index 00000000..7dd23ec1 --- /dev/null +++ b/core/Helpers/CommonMacros.h @@ -0,0 +1,35 @@ +// +// CommonMacros.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 3/25/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +/// Asserts a condition about a state, and throws an +/// @c NSInternalInconsistencyException and given message as the reason if it +/// evaluates to @c NO. Similar to @c NSAssert(). +static inline void _assert_state(const BOOL condition, NSString *msg) { + if (!condition) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:msg + userInfo:nil]; + } +}; + +/// Get a property's name as a string. Prevents mistypings when using methods +/// like `valueForKey:`. +/// http://stackoverflow.com/questions/6615826/get-property-name-as-a-string/12623102#12623102 +#define STRING_PROPERTY(prop) NSStringFromSelector(@selector(prop)) diff --git a/core/Helpers/ConnectError.h b/core/Helpers/ConnectError.h new file mode 100644 index 00000000..48120408 --- /dev/null +++ b/core/Helpers/ConnectError.h @@ -0,0 +1,56 @@ +// +// ConnectError.h +// Connect SDK +// +// Created by Andrew Longstaff on 10/4/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +/// The error domain for ConnectSDK errors. +FOUNDATION_EXTERN NSString *const ConnectErrorDomain; + +/*! + * Helpful status codes that augment the localizedDescriptions of NSErrors that crop up throughout many places of the SDK. Most NSErrors that Connect SDK provides will have a ConnectStatusCode. + */ +typedef enum { + /*! Generic error, unknown cause */ + ConnectStatusCodeError = 1000, + + /*! The TV experienced an error */ + ConnectStatusCodeTvError = 1001, + + /*! SSL certificate error */ + ConnectStatusCodeCertificateError = 1002, + + /*! Error with WebSocket connection */ + ConnectStatusCodeSocketError = 1003, + + /*! Requested action is not supported */ + ConnectStatusCodeNotSupported = 1100, + + /*! There was a problem with the provided arguments, see error description for details */ + ConnectStatusCodeArgumentError = 1101, + + /*! Device is not connected */ + ConnectStatusCodeNotConnected = 1102 +} ConnectStatusCode; + +@interface ConnectError : NSObject + ++ (NSError *) generateErrorWithCode:(ConnectStatusCode)code andDetails:(id)details; + +@end diff --git a/core/Helpers/ConnectError.m b/core/Helpers/ConnectError.m new file mode 100644 index 00000000..859e160f --- /dev/null +++ b/core/Helpers/ConnectError.m @@ -0,0 +1,62 @@ +// +// ConnectError.m +// Connect SDK +// +// Created by Andrew Longstaff on 10/4/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ConnectError.h" + +NSString *const ConnectErrorDomain = @"com.lge.connectsdk.error"; + +@implementation ConnectError + ++ (NSError *) generateErrorWithCode:(ConnectStatusCode)code andDetails:(id)details +{ + NSString *errorMessage; + + switch (code) + { + case ConnectStatusCodeTvError: + errorMessage = [NSString stringWithFormat:@"API error: %@", details]; + break; + + case ConnectStatusCodeCertificateError: + errorMessage = [NSString stringWithFormat:@"Invalid server certificate"]; + break; + + case ConnectStatusCodeSocketError: + errorMessage = [NSString stringWithFormat:@"Web Socket Error: %@", details]; + break; + + case ConnectStatusCodeNotSupported: + errorMessage = [NSString stringWithFormat:@"This feature is not supported."]; + break; + + default: + if (details) + errorMessage = [NSString stringWithFormat:@"A generic error occured: %@", details]; + else + errorMessage = [NSString stringWithFormat:@"A generic error occured"]; + } + + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMessage forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:ConnectErrorDomain code:code userInfo:userInfo]; + +} + +@end diff --git a/core/Helpers/ConnectUtil.h b/core/Helpers/ConnectUtil.h new file mode 100644 index 00000000..06fc88eb --- /dev/null +++ b/core/Helpers/ConnectUtil.h @@ -0,0 +1,35 @@ +// +// ConnectUtil.h +// Connect SDK +// +// Created by Jeremy White on 3/6/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + + +@interface ConnectUtil : NSObject + ++ (NSString *) urlEncode:(NSString *)input; ++ (NSString *) urlDecode:(NSString *)input; ++ (NSString *) escapedUnicodeForString:(NSString *)input; + ++ (NSString *) entityEncode:(NSString *)input +__deprecated_msg("use proper XML libraries instead"); ++ (NSString *) entityDecode:(NSString *)input +__deprecated_msg("use proper XML libraries instead"); + +@end diff --git a/core/Helpers/ConnectUtil.m b/core/Helpers/ConnectUtil.m new file mode 100644 index 00000000..e4f463e8 --- /dev/null +++ b/core/Helpers/ConnectUtil.m @@ -0,0 +1,125 @@ +// +// ConnectUtil.m +// Connect SDK +// +// Created by Jeremy White on 3/6/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ConnectUtil.h" + + +@implementation ConnectUtil + ++ (NSString *)urlEncode:(NSString *)targetString +{ + NSMutableString *output = [NSMutableString string]; + const unsigned char *source = (const unsigned char *) [targetString UTF8String]; + int sourceLen = (int) strlen((const char *)source); + for (int i = 0; i < sourceLen; ++i) { + const unsigned char thisChar = source[i]; + + // credit: http://www.w3schools.com/tags/ref_urlencode.asp + switch (thisChar) + { + case ' ': [output appendString:@"%20"]; break; + case '!': [output appendString:@"%21"]; break; + case '"': [output appendString:@"%22"]; break; + case '#': [output appendString:@"%23"]; break; + case '$': [output appendString:@"%24"]; break; + case '%': [output appendString:@"%25"]; break; + case '&': [output appendString:@"%26"]; break; + case '\'': [output appendString:@"%27"]; break; + case '(': [output appendString:@"%28"]; break; + case ')': [output appendString:@"%29"]; break; + case '*': [output appendString:@"%2A"]; break; + case '+': [output appendString:@"%2B"]; break; + case ',': [output appendString:@"%2C"]; break; + case '-': [output appendString:@"%2D"]; break; + case '.': [output appendString:@"%2E"]; break; + case '/': [output appendString:@"%2F"]; break; + case ':': [output appendString:@"%3A"]; break; + case ';': [output appendString:@"%3B"]; break; + case '<': [output appendString:@"%3C"]; break; + case '=': [output appendString:@"%3D"]; break; + case '>': [output appendString:@"%3E"]; break; + case '?': [output appendString:@"%3F"]; break; + case '@': [output appendString:@"%40"]; break; + + default: + if ((thisChar >= 'a' && thisChar <= 'z') || (thisChar >= 'A' && thisChar <= 'Z') || (thisChar >= '0' && thisChar <= '9')) + [output appendFormat:@"%c", thisChar]; + else + [output appendFormat:@"%%%02X", thisChar]; + } + } + + return output; +} + ++ (NSString *)urlDecode:(NSString *)targetString +{ + NSString *result = [targetString stringByReplacingOccurrencesOfString:@"+" withString:@" "]; + result = [result stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + return result; +} + +// credit: karsten @ stackoverflow.com (http://stackoverflow.com/users/272152/karsten) +// source: http://stackoverflow.com/a/2591544/2715 ++ (NSString *) escapedUnicodeForString:(NSString *)input +{ + NSMutableString *uniString = [ [ NSMutableString alloc ] init ]; + UniChar *uniBuffer = (UniChar *) malloc ( sizeof(UniChar) * [ input length ] ); + CFRange stringRange = CFRangeMake ( 0, [ input length ] ); + + CFStringGetCharacters ( (__bridge CFStringRef)input, stringRange, uniBuffer ); + + for ( int i = 0; i < [ input length ]; i++ ) { + if ( uniBuffer[i] < 0x30 || uniBuffer[i] > 0x7e ) + [ uniString appendFormat: @"\\u%04x", uniBuffer[i] ]; + else + [ uniString appendFormat: @"%c", uniBuffer[i] ]; + } + + free ( uniBuffer ); + + NSString *retString = [ NSString stringWithString: uniString ]; + + return retString; +} + +// TODO: remove this method as it's harmful and we should be using proper XML +// reader&writer instead ++ (NSString *) entityEncode:(NSString *)input +{ + NSString *output = [[[[input stringByReplacingOccurrencesOfString: @"&" withString: @"&"] + stringByReplacingOccurrencesOfString: @"\"" withString: @"""] + stringByReplacingOccurrencesOfString: @">" withString: @">"] + stringByReplacingOccurrencesOfString: @"<" withString: @"<"]; + + return output; +} + ++ (NSString *) entityDecode:(NSString *)input +{ + NSString *output = [[[[input stringByReplacingOccurrencesOfString: @"&" withString: @"&"] + stringByReplacingOccurrencesOfString: @""" withString: @"\""] + stringByReplacingOccurrencesOfString: @">" withString: @">"] + stringByReplacingOccurrencesOfString: @"<" withString: @"<"]; + + return output; +} + +@end diff --git a/core/Helpers/DeviceServiceReachability.h b/core/Helpers/DeviceServiceReachability.h new file mode 100644 index 00000000..8b79e48f --- /dev/null +++ b/core/Helpers/DeviceServiceReachability.h @@ -0,0 +1,40 @@ +// +// Created by Jeremy White on 4/14/14. +// Copyright (c) 2014 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@protocol DeviceServiceReachabilityDelegate; + +@interface DeviceServiceReachability : NSObject + +- (instancetype) initWithTargetURL:(NSURL *)targetURL; ++ (instancetype) reachabilityWithTargetURL:(NSURL *)targetURL; + +- (void) start; +- (void) stop; + +@property (nonatomic, readonly) NSURL *targetURL; +@property (nonatomic) BOOL running; +@property (nonatomic) id delegate; + +@end + +@protocol DeviceServiceReachabilityDelegate + +- (void) didLoseReachability:(DeviceServiceReachability *)reachability; + +@end diff --git a/core/Helpers/DeviceServiceReachability.m b/core/Helpers/DeviceServiceReachability.m new file mode 100644 index 00000000..fae042a2 --- /dev/null +++ b/core/Helpers/DeviceServiceReachability.m @@ -0,0 +1,100 @@ +// +// Created by Jeremy White on 4/14/14. +// Copyright (c) 2014 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DeviceServiceReachability.h" + + +@implementation DeviceServiceReachability +{ + NSTimer *_runTimer; + NSOperationQueue *_reachabilityQueue; +} + +- (instancetype) initWithTargetURL:(NSURL *)targetURL +{ + self = [super init]; + + if (self) + { + _running = NO; + _targetURL = targetURL; + _reachabilityQueue = [[NSOperationQueue alloc] init]; + } + + return self; +} + ++ (instancetype) reachabilityWithTargetURL:(NSURL *)targetURL +{ + return [[self alloc] initWithTargetURL:targetURL]; +} + +- (void) start +{ + _running = YES; + _runTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(checkReachability) userInfo:nil repeats:YES]; + [_runTimer fire]; +} + +- (void) stop +{ + if (_running) + { + [_runTimer invalidate]; + _runTimer = nil; + + _running = NO; + } +} + +- (void) checkReachability +{ + if (!_running) + return; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.targetURL]; + [request setTimeoutInterval:10]; + [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; + + [NSURLConnection sendAsynchronousRequest:request + queue:_reachabilityQueue + completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) + { + if (!_running) + return; + + if (connectionError) { + DLog(@"Connection error to %@: %@", self.targetURL, connectionError); + } + + const BOOL noDataIsAvailable = !data && connectionError && !response; + if (noDataIsAvailable) + { + [self stop]; + + if (self.delegate) + { + dispatch_async(dispatch_get_main_queue(), ^ + { + [self.delegate didLoseReachability:self]; + }); + } + } + }]; +} + +@end diff --git a/core/Helpers/ExternalInputInfo.h b/core/Helpers/ExternalInputInfo.h new file mode 100644 index 00000000..21b374b5 --- /dev/null +++ b/core/Helpers/ExternalInputInfo.h @@ -0,0 +1,53 @@ +// +// ExternalInputInfo.h +// Connect SDK +// +// Created by Jeremy White on 1/19/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + + +/*! + * Normalized reference object for information about a DeviceService's external inputs. This object is required to set a DeviceService's external input. + */ +@interface ExternalInputInfo : NSObject + +/*! ID of the external input on the first screen device. */ +@property (nonatomic, strong) NSString *id; + +/*! User-friendly name of the external input (ex. AV, HDMI1, etc). */ +@property (nonatomic, strong) NSString *name; + +/*! Whether the DeviceService is currently connected to this external input. */ +@property (nonatomic) BOOL connected; + +/*! URL to an icon representing this external input. */ +@property (nonatomic, strong) NSURL *iconURL; + +/*! Raw data from the first screen device about the external input. In most cases, this is an NSDictionary. */ +@property (nonatomic, strong) id rawData; + +/*! + * Compares two ExternalInputInfo objects. + * + * @param externalInputInfo ExternalInputInfo object to compare. + * + * @return YES if both ExternalInputInfo id & name values are equal + */ +- (BOOL)isEqual:(ExternalInputInfo *)externalInputInfo; + +@end diff --git a/core/Helpers/ExternalInputInfo.m b/core/Helpers/ExternalInputInfo.m new file mode 100644 index 00000000..e8583761 --- /dev/null +++ b/core/Helpers/ExternalInputInfo.m @@ -0,0 +1,33 @@ +// +// ExternalInputInfo.m +// Connect SDK +// +// Created by Jeremy White on 1/19/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ExternalInputInfo.h" +#import "AppInfo.h" + + +@implementation ExternalInputInfo + +- (BOOL)isEqual:(ExternalInputInfo *)externalInputInfo +{ + return [self.id isEqualToString:externalInputInfo.id] + && [self.name isEqualToString:externalInputInfo.name]; +} + +@end diff --git a/core/Helpers/ImageInfo.h b/core/Helpers/ImageInfo.h new file mode 100644 index 00000000..541a00d6 --- /dev/null +++ b/core/Helpers/ImageInfo.h @@ -0,0 +1,67 @@ +// +// ImageInfo.h +// Connect SDK +// +// Created by Jeremy White on 8/14/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + + +/*! + * Normalized reference object for information about an image to be sent to a device through the MediaPlayer capability. + */ +@interface ImageInfo : NSObject + +/*! + * Enumerated value of the type of image that each ImageInfo instance will represent. Default is unknown. + */ +enum { + /*! Unknown to the SDK, may not be used unless you extend Connect SDK to add additional functionality */ + ImageTypeUnknown, + + /*! Icon or thumbnail image; mostly used by the MediaPlayer capability to provide an icon for media playback. */ + ImageTypeThumb, + + /*! Large-sized poster image for use by MediaPlayer capability when displaying video. It is recommended that your poster image is the same size as the target video player (full HD, in most cases). */ + ImageTypeVideoPoster, + + /*! Album art image for use when playing audio through the MediaPlayer capability. */ + ImageTypeAlbumArt +}; +typedef NSUInteger ImageType; + +/*! URL source of the image */ +@property (nonatomic, strong) NSURL *url; + +/*! Type of image (see ImageType enum) */ +@property (nonatomic) ImageType type; + +/*! Width of the image (optional) */ +@property (nonatomic) NSInteger width; + +/*! Height of the image (optional) */ +@property (nonatomic) NSInteger height; + +/*! + * Creates an instance of ImageInfo with given property values. + * + * @param url URL source of the image + * @param type Type of image (see ImageType enum) + */ +- (instancetype) initWithURL:(NSURL *)url type:(ImageType)type; + +@end diff --git a/core/Helpers/ImageInfo.m b/core/Helpers/ImageInfo.m new file mode 100644 index 00000000..aa021232 --- /dev/null +++ b/core/Helpers/ImageInfo.m @@ -0,0 +1,45 @@ +// +// ImageInfo.m +// Connect SDK +// +// Created by Jeremy White on 8/14/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ImageInfo.h" + + +@implementation ImageInfo + +// return nil here so that we force users to have URL and type +- (instancetype) init +{ + return nil; +} + +- (instancetype) initWithURL:(NSURL *)url type:(ImageType)type +{ + self = [super init]; + + if (self) + { + self.url = url; + self.type = type; + } + + return self; +} + +@end diff --git a/core/Helpers/JSONObjectCoding.h b/core/Helpers/JSONObjectCoding.h new file mode 100644 index 00000000..faf9a795 --- /dev/null +++ b/core/Helpers/JSONObjectCoding.h @@ -0,0 +1,28 @@ +// +// JSONObjectCoding.h +// Connect SDK +// +// Created by Jason Lai on 03/21/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@protocol JSONObjectCoding + +- (id) initWithJSONObject:(NSDictionary*)dict; +- (NSDictionary*) toJSONObject; + +@end diff --git a/core/Helpers/MediaInfo.h b/core/Helpers/MediaInfo.h new file mode 100644 index 00000000..c756896c --- /dev/null +++ b/core/Helpers/MediaInfo.h @@ -0,0 +1,75 @@ +// +// MediaInfo.h +// Connect SDK +// +// Created by Jeremy White on 8/14/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "ImageInfo.h" + +@class SubtitleInfo; + +/*! + * Normalized reference object for information about a media file to be sent to a device through the MediaPlayer capability. "Media file", in this context, refers to an audio or video resource. + */ +@interface MediaInfo : NSObject + +/*! URL source of the media file */ +@property (nonatomic, strong) NSURL *url; + +/*! Mime-type of the media file */ +@property (nonatomic, strong) NSString *mimeType; + +/*! Title of the media file (optional) */ +@property (nonatomic, strong) NSString *title; + +/*! Short description of the media file (optional) */ +@property (nonatomic, strong) NSString *description; + +/*! Duration of the media file */ +@property (nonatomic) NSTimeInterval duration; + +/*! Collection of ImageInfo objects to send, as necessary, to the device when launching media through the MediaPlayer capability. */ +@property (nonatomic, strong) NSArray *images; + +/// Subtitle track for this media instance (optional). +@property (nonatomic, strong) SubtitleInfo *subtitleInfo; + + +/** + * Creates an instance of MediaInfo with given property values. + * + * @param url URL source of the media file + * @param mimeType Mime-type of the media file + */ +- (instancetype) initWithURL:(NSURL *)url mimeType:(NSString *)mimeType; + +/*! + * Adds an ImageInfo object to the array of images. + * + * @param image ImageInfo object to be added + */ +- (void) addImage:(ImageInfo *)image; + +/*! + * Adds an array of ImageInfo objects to the array of images. + * + * @param images Array of ImageInfo objects to be added + */ +- (void) addImages:(NSArray *)images; + +@end diff --git a/core/Helpers/MediaInfo.m b/core/Helpers/MediaInfo.m new file mode 100644 index 00000000..18d1bc16 --- /dev/null +++ b/core/Helpers/MediaInfo.m @@ -0,0 +1,58 @@ +// +// MediaInfo.m +// Connect SDK +// +// Created by Jeremy White on 8/14/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "MediaInfo.h" + + +@implementation MediaInfo +@synthesize description; + +// return nil here so that we force users to have URL and mime-type +- (instancetype) init +{ + return nil; +} + +- (instancetype) initWithURL:(NSURL *)url mimeType:(NSString *)mimeType +{ + self = [super init]; + + if (self) + { + self.url = url; + self.mimeType = mimeType; + + self.images = [NSArray new]; + } + + return self; +} + +- (void) addImage:(ImageInfo *)image +{ + self.images = [self.images arrayByAddingObject:image]; +} + +- (void) addImages:(NSArray *)images +{ + self.images = [self.images arrayByAddingObjectsFromArray:images]; +} + +@end diff --git a/core/Helpers/MediaLaunchObject.h b/core/Helpers/MediaLaunchObject.h new file mode 100644 index 00000000..3e8fb1ad --- /dev/null +++ b/core/Helpers/MediaLaunchObject.h @@ -0,0 +1,49 @@ +// +// MediaLaunchObject.h +// ConnectSDK +// +// Created by Ibrahim Adnan on 1/19/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "MediaControl.h" +#import "PlayListControl.h" +#import "LaunchSession.h" + +/*! MediaLaunchObject is a container object which holds LaunchSession object,MediaControl object/or and PlayListControl object*/ +@interface MediaLaunchObject : NSObject + +/*! MediaControl object of Media player*/ +@property (nonatomic, strong) id mediaControl; + +/*! PlayList Control Object of Media player*/ +@property (nonatomic, strong) id playListControl; + +/*! Launch Session object of Media player*/ +@property (nonatomic, strong) LaunchSession *session; + + +/*! + * Creates an instance of MediaLaunchObject with given property values. + * + * @param launchSession LaunchSession to allow closing this media player + * @param mediaControl MediaControl object used to control playback + * @param playListControl PlayListControl object used to control playlist + */ +- (instancetype) initWithLaunchSession:(LaunchSession *)session andMediaControl:(id)mediaControl; +- (instancetype) initWithLaunchSession:(LaunchSession *)session andMediaControl:(id)mediaControl andPlayListControl:(id)playListControl; + +@end diff --git a/core/Helpers/MediaLaunchObject.m b/core/Helpers/MediaLaunchObject.m new file mode 100644 index 00000000..45caddd3 --- /dev/null +++ b/core/Helpers/MediaLaunchObject.m @@ -0,0 +1,44 @@ +// +// MediaLaunchObject.m +// ConnectSDK +// +// Created by Ibrahim Adnan on 1/19/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "MediaLaunchObject.h" + +@implementation MediaLaunchObject + +- (instancetype) initWithLaunchSession:(LaunchSession *)session andMediaControl:(id)mediaControl +{ + return [self initWithLaunchSession:session andMediaControl:mediaControl andPlayListControl:nil]; +} + +- (instancetype) initWithLaunchSession:(LaunchSession *)session andMediaControl:(id)mediaControl andPlayListControl:(id)playListControl +{ + self = [super init]; + + if (self) + { + self.session = session; + self.mediaControl = mediaControl; + self.playListControl = playListControl; + } + + return self; +} + +@end diff --git a/core/Helpers/NSDictionary+KeyPredicateSearch.h b/core/Helpers/NSDictionary+KeyPredicateSearch.h new file mode 100644 index 00000000..b2fe9bc7 --- /dev/null +++ b/core/Helpers/NSDictionary+KeyPredicateSearch.h @@ -0,0 +1,37 @@ +// +// NSDictionary+KeyPredicateSearch.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 1/13/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +/** + * Extends the NSDictionary interface to be able to get objects by keys + * matching a predicate. + */ +@interface NSDictionary (KeyPredicateSearch) + +/// Returns an object for a key which name matches the given predicate. +/// @warning There must be at most one matching key in the dictionary. +- (id)objectForKeyWithPredicate:(NSPredicate *)predicate; + +/// Returns an object for a key which name ends with the given string. +/// @warning There must be at most one matching key in the dictionary. +- (id)objectForKeyEndingWithString:(NSString *)string; + +@end diff --git a/core/Helpers/NSDictionary+KeyPredicateSearch.m b/core/Helpers/NSDictionary+KeyPredicateSearch.m new file mode 100644 index 00000000..7f3499a9 --- /dev/null +++ b/core/Helpers/NSDictionary+KeyPredicateSearch.m @@ -0,0 +1,45 @@ +// +// NSDictionary+KeyPredicateSearch.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 1/13/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NSDictionary+KeyPredicateSearch.h" + +@implementation NSDictionary (KeyPredicateSearch) + +- (id)objectForKeyWithPredicate:(NSPredicate *)predicate { + if (!predicate) { + return nil; + } + + NSArray *predicateKeys = [self.allKeys filteredArrayUsingPredicate:predicate]; + if (predicateKeys.count > 1) { + NSString *reason = [NSString stringWithFormat:@"There are %ld object for predicate %@", + (unsigned long)predicateKeys.count, predicate]; + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:reason + userInfo:nil]; + } + return self[predicateKeys.firstObject]; +} + +- (id)objectForKeyEndingWithString:(NSString *)string { + return [self objectForKeyWithPredicate:[NSPredicate predicateWithFormat:@"self ENDSWITH %@", string]]; +} + +@end diff --git a/core/Helpers/NSMutableDictionary+NilSafe.h b/core/Helpers/NSMutableDictionary+NilSafe.h new file mode 100644 index 00000000..3379fd2f --- /dev/null +++ b/core/Helpers/NSMutableDictionary+NilSafe.h @@ -0,0 +1,30 @@ +// +// NSMutableDictionary+NilSafe.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-07-23. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN +@interface NSMutableDictionary (NilSafe) + +/// Sets the @c object for the @c key; if @c object is @c nil, does nothing. +- (void)setNullableObject:(nullable id)object forKey:(id)key; + +@end +NS_ASSUME_NONNULL_END diff --git a/core/Helpers/NSMutableDictionary+NilSafe.m b/core/Helpers/NSMutableDictionary+NilSafe.m new file mode 100644 index 00000000..f5daf361 --- /dev/null +++ b/core/Helpers/NSMutableDictionary+NilSafe.m @@ -0,0 +1,33 @@ +// +// NSMutableDictionary+NilSafe.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-07-23. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NSMutableDictionary+NilSafe.h" + +NS_ASSUME_NONNULL_BEGIN +@implementation NSMutableDictionary (NilSafe) + +- (void)setNullableObject:(nullable id)object forKey:(id)key { + if (object) { + self[key] = object; + } +} + +@end +NS_ASSUME_NONNULL_END diff --git a/core/Helpers/NSString+Common.h b/core/Helpers/NSString+Common.h new file mode 100644 index 00000000..23f2772c --- /dev/null +++ b/core/Helpers/NSString+Common.h @@ -0,0 +1,28 @@ +// +// NSString+Common.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 3/16/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@interface NSString (Common) + +/// Returns itself if not `nil`, or an empty string otherwise. +- (NSString *)orEmpty; + +@end diff --git a/core/Helpers/NSString+Common.m b/core/Helpers/NSString+Common.m new file mode 100644 index 00000000..c0fe01b7 --- /dev/null +++ b/core/Helpers/NSString+Common.m @@ -0,0 +1,30 @@ +// +// NSString+Common.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 3/16/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NSString+Common.h" + +@implementation NSString (Common) + +- (NSString *)orEmpty { + // TODO: replace DeviceService.ensureString() + return self ?: @""; +} + +@end diff --git a/core/Helpers/ProgramInfo.h b/core/Helpers/ProgramInfo.h new file mode 100644 index 00000000..6cecd309 --- /dev/null +++ b/core/Helpers/ProgramInfo.h @@ -0,0 +1,50 @@ +// +// ProgramInfo.h +// Connect SDK +// +// Created by Jeremy White on 1/19/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@class ChannelInfo; + + +/*! Normalized reference object for information about a TVs program. */ +@interface ProgramInfo : NSObject + +/*! ID of the program on the first screen device. Format is different depending on the platform. */ +@property (nonatomic, strong) NSString *id; + +/*! User-friendly name of the program (ex. Sesame Street, Cosmos, Game of Thrones, etc). */ +@property (nonatomic, strong) NSString *name; + +/*! Reference to the ChannelInfo object that this program is associated with */ +@property (nonatomic, strong) ChannelInfo *channelInfo; + +/*! Raw data from the first screen device about the program. In most cases, this is an NSDictionary. */ +@property (nonatomic, strong) id rawData; + +/*! + * Compares two ProgramInfo objects. + * + * @param programInfo ProgramInfo object to compare. + * + * @return YES if both ProgramInfo id & name values are equal + */ +- (BOOL)isEqual:(ProgramInfo *)programInfo; + +@end diff --git a/core/Helpers/ProgramInfo.m b/core/Helpers/ProgramInfo.m new file mode 100644 index 00000000..82dea330 --- /dev/null +++ b/core/Helpers/ProgramInfo.m @@ -0,0 +1,33 @@ +// +// ProgramInfo.m +// Connect SDK +// +// Created by Jeremy White on 1/19/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ProgramInfo.h" +#import "ChannelInfo.h" + + +@implementation ProgramInfo + +- (BOOL)isEqual:(ProgramInfo *)programInfo +{ + return [self.id isEqualToString:programInfo.id] + && [self.name isEqualToString:programInfo.name]; +} + +@end diff --git a/core/Helpers/SSDPSocketListener.h b/core/Helpers/SSDPSocketListener.h new file mode 100644 index 00000000..a00e34c3 --- /dev/null +++ b/core/Helpers/SSDPSocketListener.h @@ -0,0 +1,49 @@ +// +// SocketListener.h +// Connect SDK +// +// Created by Mykhailo Vorontsov on 3/29/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@class SSDPSocketListener; + +@protocol SocketListenerDelegate + +@optional + +- (void)socket:(SSDPSocketListener *)aSocket didReceiveData:(NSData *)aData fromAddress:(NSString *)anAddress; +- (void)socket:(SSDPSocketListener *)aSocket didEncounterError:(NSError *)anError; + +@end + +@interface SSDPSocketListener : NSObject + +@property (nonatomic, assign) id delegate; +@property (nonatomic, strong) NSString *address; +@property (nonatomic, readwrite) NSInteger port; +@property (nonatomic, readonly) NSError *error; +@property (nonatomic, readwrite) dispatch_queue_t workQueue; +@property (nonatomic, readwrite) dispatch_queue_t delegateQueue; +@property (nonatomic, readwrite, getter = isListenToAnyMessages) BOOL listenToSpecifiedAddressOnly; + +- (instancetype)initWithAddress:(NSString *)anAddress andPort:(NSInteger)aPort; +- (void)sendData:(NSData *)aData toAddress:(NSString *)anAddress andPort:(NSUInteger)aPort; +- (void)open; +- (void)close; + +@end diff --git a/core/Helpers/SSDPSocketListener.m b/core/Helpers/SSDPSocketListener.m new file mode 100644 index 00000000..e80c9919 --- /dev/null +++ b/core/Helpers/SSDPSocketListener.m @@ -0,0 +1,221 @@ +// +// SocketListener.m +// Connect SDK +// +// Created by Mykhailo Vorontsov on 3/29/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "SSDPSocketListener.h" + +#import +#import +#import +#import +#import "ConnectError.h" + +@implementation SSDPSocketListener +{ + BOOL _isListening; + dispatch_source_t _dispatchSource; + int _socket; +} + +- (instancetype)initWithAddress:(NSString *)anAddress andPort:(NSInteger)aPort +{ + self = [super init]; + if (self) + { + _address = anAddress; + _port = aPort; + } + return self; +} + +#pragma mark - + +- (dispatch_queue_t)workQueue +{ + if (nil == _workQueue) + { + @synchronized (self) { + _workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + } + } + return _workQueue; +} + +- (dispatch_queue_t)delegateQueue +{ + if (nil == _delegateQueue) + { + @synchronized (self) { + _delegateQueue = dispatch_get_main_queue(); + } + } + return _delegateQueue; +} + +#pragma mark - + +- (void)raiseError +{ + _error = [ConnectError generateErrorWithCode:ConnectStatusCodeSocketError andDetails:@"Detection socket disconnected"]; + dispatch_async(self.delegateQueue, + ^{ + if ([self.delegate respondsToSelector:@selector(socket:didEncounterError:)]) + { + [self.delegate socket:self didEncounterError:_error]; + } + }); +} + +- (void)didReceiveData:(NSData *)aData fromAddress:(NSString *)anAddress +{ + dispatch_async(self.delegateQueue, + ^{ + if ([self.delegate respondsToSelector:@selector(socket:didReceiveData:fromAddress:)]) + { + [self.delegate socket:self didReceiveData:aData fromAddress:anAddress]; + } + }); +} + +- (void)open +{ + int theSocketDescriptor = socket (PF_INET, SOCK_DGRAM, 0); + + //Create variable to pass sot socket option setter + static const int theYesVariable = 1; + + struct sockaddr_in theSocketAddress; + memset(&theSocketAddress, 0, sizeof(theSocketAddress)); + + theSocketAddress.sin_family = AF_INET; + theSocketAddress.sin_addr.s_addr = htonl(INADDR_ANY); + theSocketAddress.sin_port = htons(self.port); + + if (![_address isEqualToString:@"0.0.0.0"]) + { + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = inet_addr(self.address.UTF8String); + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + + if (setsockopt(theSocketDescriptor,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)) < 0) + { + [self raiseError]; + return; + } + } + + if (setsockopt(theSocketDescriptor, SOL_SOCKET, SO_REUSEADDR, &theYesVariable, sizeof(theYesVariable)) < 0) + { + [self raiseError]; + return; + } + if (setsockopt(theSocketDescriptor, SOL_SOCKET, SO_REUSEPORT, &theYesVariable, sizeof(theYesVariable)) < 0) + { + [self raiseError]; + return; + } + + if (bind ( theSocketDescriptor, (void *)&theSocketAddress, sizeof(theSocketAddress)) < 0) + { + [self raiseError]; + return; + } + + _dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, theSocketDescriptor, 0, self.workQueue); + _socket = theSocketDescriptor; + __weak __typeof__(self) weakSelf = self; + dispatch_source_set_event_handler(self->_dispatchSource, + ^{ + __typeof__(self) strongSelf = weakSelf; + if (strongSelf == nil) return; + if (!strongSelf->_socket || !strongSelf->_dispatchSource) + { + [strongSelf raiseError]; + return; + } + + struct sockaddr_in theIncomingAddr; + memset(&theIncomingAddr, 0, sizeof(theIncomingAddr)); + size_t theDataSize = dispatch_source_get_data(strongSelf->_dispatchSource); + size_t maxDataSize = 65535; + char theBuffer[maxDataSize]; + int theLastReceiveBytes = 0; + int theReceiveBytesSum = 0; + socklen_t theAddressSize = sizeof(theIncomingAddr); + while (theReceiveBytesSum < theDataSize) + { + theLastReceiveBytes = recvfrom(theSocketDescriptor, theBuffer, + maxDataSize, 0, (struct sockaddr*)&theIncomingAddr, &theAddressSize); + theReceiveBytesSum += theLastReceiveBytes; + } + if (theLastReceiveBytes > 0) + { + char theCAddrBuffer[SOCK_MAXADDRLEN]; + memset(theCAddrBuffer, 0, SOCK_MAXADDRLEN); + inet_ntop(theIncomingAddr.sin_family, &theIncomingAddr.sin_addr, theCAddrBuffer, SOCK_MAXADDRLEN); + + NSString *thePath = [[NSString alloc] initWithBytes:theCAddrBuffer + length:strlen(theCAddrBuffer) encoding:NSUTF8StringEncoding]; + NSData * theReceivedData = [NSData dataWithBytes:theBuffer length:theLastReceiveBytes]; + + [strongSelf didReceiveData:theReceivedData fromAddress:thePath]; + } + }); + + dispatch_source_set_cancel_handler(self->_dispatchSource, + ^{ + close(self->_socket); + }); + + dispatch_resume(self->_dispatchSource); +} + + +- (void)sendData:(NSData *)aData toAddress:(NSString *)anAddress andPort:(NSUInteger)aPort; +{ + if (0 == _socket) + [self open]; + + if (_socket <= 0) + [self raiseError]; + + struct sockaddr_in theSocketAddress; + + memset((char *) &theSocketAddress, 0, sizeof(theSocketAddress)); + theSocketAddress.sin_family = AF_INET; + theSocketAddress.sin_port = htons(aPort); + + inet_aton([anAddress UTF8String], &theSocketAddress.sin_addr); + + if (aData.length != sendto(_socket, aData.bytes, aData.length, 0, + (void *) &theSocketAddress, sizeof(theSocketAddress))) + { + [self raiseError]; + } +} + +- (void)close +{ + if (_dispatchSource) + dispatch_source_cancel(_dispatchSource); + + _dispatchSource = nil; +} + +@end diff --git a/core/Helpers/SubscriptionDeduplicator.h b/core/Helpers/SubscriptionDeduplicator.h new file mode 100644 index 00000000..2927a659 --- /dev/null +++ b/core/Helpers/SubscriptionDeduplicator.h @@ -0,0 +1,38 @@ +// +// SubscriptionDeduplicator.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-06-05. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +NS_ASSUME_NONNULL_BEGIN +/** + * Deduplicates subscription notifications with the same state. The state can be + * of any class, allowing NSNumber-wrapped values. + * @remarks It's an immutable class. + */ +@interface SubscriptionDeduplicator : NSObject + +/** + * If the new @c state is different from the previous one, runs the @c block + * synchronously. + * @return a new instance that you should save to track the new state. + */ +- (instancetype)runBlock:(dispatch_block_t)block + ifStateDidChangeTo:(id)newState; + +@end +NS_ASSUME_NONNULL_END diff --git a/core/Helpers/SubscriptionDeduplicator.m b/core/Helpers/SubscriptionDeduplicator.m new file mode 100644 index 00000000..fe951b67 --- /dev/null +++ b/core/Helpers/SubscriptionDeduplicator.m @@ -0,0 +1,60 @@ +// +// SubscriptionDeduplicator.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-06-05. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "SubscriptionDeduplicator.h" + +@interface SubscriptionDeduplicator () + +/// Previous state of a subscription associated with this object. +@property (nonatomic, strong, readonly, nullable) id savedState; + +@end + +@implementation SubscriptionDeduplicator + +@synthesize savedState = _savedState; + +- (nonnull instancetype)runBlock:(dispatch_block_t __nonnull)block + ifStateDidChangeTo:(id __nonnull)state { + SubscriptionDeduplicator *nextInstance = self; + + // previous state is set and is different from current one + // or previous state is not set + const BOOL stateDidChange = ((self.savedState && + (![state isEqual:self.savedState])) || + !self.savedState); + if (stateDidChange) { + nextInstance = [[SubscriptionDeduplicator alloc] initWithState:state]; + block(); + } + + return nextInstance; +} + +#pragma mark - Private Init + +/// Initializes a new object with the given @c state. +- (nonnull instancetype)initWithState:(nonnull id)state { + self = [super init]; + _savedState = state; + return self; +} + +@end diff --git a/core/Helpers/SubtitleInfo.h b/core/Helpers/SubtitleInfo.h new file mode 100644 index 00000000..d5130ea1 --- /dev/null +++ b/core/Helpers/SubtitleInfo.h @@ -0,0 +1,98 @@ +// +// SubtitleInfo.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-07-14. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN +@class SubtitleInfoBuilder; + +/** + * Represents a subtitle track used for media playing. + * + * @warning The URL is required, so the @c -init method will throw an exception. + * Please use the parameterized initializers. + * + * @note This class is immutable. + * + * @note Different services support specific subtitles formats: + * + * - DLNA service supports @c SRT format only. Since there is no official + * specification for them, subtitles may not work on all DLNA-compatible devices. + * + * - Netcast service supports @c SRT format only, through DLNA. + * + * - Google Cast service supports @c WebVTT format only and has additional + * requirements: https://developers.google.com/cast/docs/ios_sender#cors-requirements + * + * - FireTV service supports @c WebVTT format only. Subtitles on Fire TV are + * hidden by default and should be displayed manually by the user. + * + * - WebOS service supports @c WebVTT format only. Server providing subtitles + * should support CORS headers, similarly to Cast service's requirements. + */ +@interface SubtitleInfo : NSObject + +/// The subtitle track's URL. +@property (nonatomic, readonly) NSURL *url; + +/// The subtitle's mimeType. +@property (nonatomic, readonly, nullable) NSString *mimeType; + +/// The subtitle's source language. The contents depend on the target device. +@property (nonatomic, readonly, nullable) NSString *language; + +/// A custom label that may be displayed by a device's media player. +@property (nonatomic, readonly, nullable) NSString *label; + + +/// Creates a new instance with the given @c url. ++ (instancetype)infoWithURL:(NSURL *)url; + +/// Creates a new instance with the given @c url and properties set in the +/// @c builder object. ++ (instancetype)infoWithURL:(NSURL *)url + andBlock:(void (^)(SubtitleInfoBuilder *builder))block; + +@end + + +/** + * Used to initialize a @c SubtitleInfo object in a convenient way. The + * properties are writable at this point, and then become readonly in a final + * object. + * + * @note You should not create this object manually. It is passed as a parameter + * to +[SubtitleInfo infoWithURL:andBlock:] method. + * + * @see http://www.annema.me/the-builder-pattern-in-objective-c + */ +@interface SubtitleInfoBuilder : NSObject + +/// The subtitle's mimeType. +@property (nonatomic, nullable) NSString *mimeType; + +/// The subtitle's source language. The contents depend on the target device. +@property (nonatomic, nullable) NSString *language; + +/// A custom label that may be displayed by a device's media player. +@property (nonatomic, nullable) NSString *label; + +@end +NS_ASSUME_NONNULL_END diff --git a/core/Helpers/SubtitleInfo.m b/core/Helpers/SubtitleInfo.m new file mode 100644 index 00000000..88c72fa9 --- /dev/null +++ b/core/Helpers/SubtitleInfo.m @@ -0,0 +1,73 @@ +// +// SubtitleInfo.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-07-14. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "SubtitleInfo.h" + +#import "CommonMacros.h" + +NS_ASSUME_NONNULL_BEGIN +@implementation SubtitleInfo + +#pragma mark - Init + +- (instancetype)init { + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:@"Please use parameterized initializers to create an instance" + userInfo:nil]; +} + ++ (instancetype)infoWithURL:(NSURL *)url { + return [[self alloc] initWithURL:url]; +} + ++ (instancetype)infoWithURL:(NSURL *)url + andBlock:(void (^)(SubtitleInfoBuilder *builder))block { + SubtitleInfoBuilder *builder = [SubtitleInfoBuilder new]; + block(builder); + return [[self alloc] initWithURL:url andBuilder:builder]; +} + +#pragma mark - Private Init + +- (instancetype)initWithURL:(NSURL *)url { + return [self initWithURL:url andBuilder:nil]; +} + +- (instancetype)initWithURL:(NSURL *)url + andBuilder:(nullable SubtitleInfoBuilder *)builder /*NS_DESIGNATED_INITIALIZER*/ { + _assert_state(nil != url, @"nil URL is not permitted"); + + self = [super init]; + + _url = url; + _mimeType = builder.mimeType; + _language = builder.language; + _label = builder.label; + + return self; +} + +@end + + +@implementation SubtitleInfoBuilder + +@end +NS_ASSUME_NONNULL_END diff --git a/core/Helpers/TextInputStatusInfo.h b/core/Helpers/TextInputStatusInfo.h new file mode 100644 index 00000000..f365a886 --- /dev/null +++ b/core/Helpers/TextInputStatusInfo.h @@ -0,0 +1,37 @@ +// +// TextInputStatusInfo.h +// Connect SDK +// +// Created by Jeremy White on 1/23/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + + +/*! Normalized reference object for information about a text input event. */ +@interface TextInputStatusInfo : NSObject + +/*! Type of keyboard that should be displayed to the user. */ +@property (nonatomic) UIKeyboardType keyboardType; + +/*! Whether the keyboard is/should be visible to the user. */ +@property (nonatomic) BOOL isVisible; + +/*! Raw data from the first screen device about the text input status. In most cases, this is an NSDictionary. */ +@property (nonatomic, strong) id rawData; + +@end diff --git a/core/Helpers/TextInputStatusInfo.m b/core/Helpers/TextInputStatusInfo.m new file mode 100644 index 00000000..8eea9268 --- /dev/null +++ b/core/Helpers/TextInputStatusInfo.m @@ -0,0 +1,29 @@ +// +// TextInputStatusInfo.m +// Connect SDK +// +// Created by Jeremy White on 1/23/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "TextInputStatusInfo.h" +#import "ServiceSubscription.h" + + +@implementation TextInputStatusInfo +{ + +} +@end diff --git a/core/LICENSE b/core/LICENSE new file mode 100644 index 00000000..61f6e5e0 --- /dev/null +++ b/core/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2013-2015 LG Electronics. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/core/README.md b/core/README.md new file mode 100644 index 00000000..75525ad7 --- /dev/null +++ b/core/README.md @@ -0,0 +1,34 @@ +# Connect SDK Core (iOS) +The Connect SDK Core contains all of the core classes required for basic operation of Connect SDK. The core also includes support for some select protocols which do not have any heavy and/or external dependencies. These protocols include: +- Apple TV +- DIAL +- DLNA +- LG Netcast +- LG webOS +- LG Cast +- Roku + +## General Information +For more information about Connect SDK, visit the [main repository](https://github.com/ConnectSDK/Connect-SDK-iOS). + +## Setup +Unless you are doing very specialized work to extend the SDK, you should not need to make direct use of this repository. Instead, clone the [main repository](https://github.com/ConnectSDK/Connect-SDK-iOS), which includes this repository as a submodule. + +## External libraries +GStreamerForLGCast.framework is shared object library and links dynamically GStreamer open-source multimedia framework that is licensed under Lesser General Public License. +You can download and rebuild GStreamer.framework at [GStreamer](https://gstreamer.freedesktop.org/download/), GStreamerForLGCast.framework at [GStreamerForLGCast](https://github.com/ConnectSDK/Connect-SDK-iOS-Core/tree/master/Frameworks/LGCast/GStreamerForLGCast.zip) + +## License +Copyright (c) 2013-2015 LG Electronics. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +> http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/core/Services/AirPlayService.h b/core/Services/AirPlayService.h new file mode 100644 index 00000000..030fa890 --- /dev/null +++ b/core/Services/AirPlayService.h @@ -0,0 +1,65 @@ +// +// AirPlayService.h +// Connect SDK +// +// Created by Jeremy White on 4/18/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#define kConnectSDKAirPlayServiceId @"AirPlay" + +#import +#import "DeviceService.h" +#import "AirPlayServiceHTTP.h" +#import "AirPlayServiceMirrored.h" +#import "MediaPlayer.h" +#import "MediaControl.h" +#import "WebAppLauncher.h" + +/*! + * The values in this enum type define what capabilities should be supported by the AirPlayService. + */ +typedef enum { + /*! Enables support for web apps via Apple's [External Display](https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/WindowAndScreenGuide/UsingExternalDisplay/UsingExternalDisplay.html) APIs */ + AirPlayServiceModeWebApp = 0, + + /*! Enables support for media (image, video, audio) by way of [HTTP commands](http://nto.github.io/AirPlay.html) */ + AirPlayServiceModeMedia +} AirPlayServiceMode; + +/*! + * ###Default functionality + * Out of the box, AirPlayService will only support web app launching through AirPlay mirroring. AirPlayService also provides a Media mode, in which HTTP commands will be sent to the AirPlay device to play and control media files (image, video, audio). Due to certain limitations of the AirPlay protocol, you may only support web apps OR media capabilities through Connect SDK. You may still directly access AirPlay APIs through AVPlayer, MPMoviePlayerController, WKWebView, audio routing, etc. + * + * To set the capability mode for the AirPlayService, see the `setAirPlayServiceMode:` static method on the AirPlayService class. + */ +@interface AirPlayService : DeviceService + +// @cond INTERNAL +@property (nonatomic, readonly) AirPlayServiceHTTP *httpService; +@property (nonatomic, readonly) AirPlayServiceMirrored *mirroredService; +// @endcond + +/*! + * Returns the current AirPlayServiceMode + */ ++ (AirPlayServiceMode) serviceMode; + +/*! + * Sets the AirPlayService mode. This property should be set before DiscoveryManager is set for the first time. + */ ++ (void) setAirPlayServiceMode:(AirPlayServiceMode)serviceMode; + +@end diff --git a/core/Services/AirPlayService.m b/core/Services/AirPlayService.m new file mode 100644 index 00000000..76a8aaf9 --- /dev/null +++ b/core/Services/AirPlayService.m @@ -0,0 +1,397 @@ +// +// AirPlayService.m +// Connect SDK +// +// Created by Jeremy White on 4/18/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "AirPlayService_Private.h" +#import "ConnectError.h" + +#import "NSObject+FeatureNotSupported_Private.h" + +@interface AirPlayService () + +@end + +static AirPlayServiceMode airPlayServiceMode; + +@implementation AirPlayService + +@synthesize httpService = _httpService; +@synthesize mirroredService = _mirroredService; + ++ (void) setAirPlayServiceMode:(AirPlayServiceMode)serviceMode +{ + airPlayServiceMode = serviceMode; +} + ++ (AirPlayServiceMode) serviceMode +{ + return airPlayServiceMode; +} + ++ (NSDictionary *) discoveryParameters +{ + return @{ + @"serviceId" : kConnectSDKAirPlayServiceId, + @"zeroconf" : @{ + @"filter" : @"_airplay._tcp" + } + }; +} + +- (void) updateCapabilities +{ + NSArray *caps = [NSArray array]; + + if ([AirPlayService serviceMode] == AirPlayServiceModeMedia) + { + caps = [caps arrayByAddingObjectsFromArray:@[ + kMediaPlayerDisplayImage, + kMediaPlayerPlayVideo, + kMediaPlayerPlayAudio, + kMediaPlayerClose, + kMediaPlayerMetaDataMimeType + ]]; + + caps = [caps arrayByAddingObjectsFromArray:@[ + kMediaControlPlay, + kMediaControlPause, + kMediaControlStop, + kMediaControlRewind, + kMediaControlFastForward, + kMediaControlPlayState, + kMediaControlDuration, + kMediaControlPosition, + kMediaControlSeek + ]]; + } else if ([AirPlayService serviceMode] == AirPlayServiceModeWebApp) + { + caps = [caps arrayByAddingObjectsFromArray:@[ + kWebAppLauncherLaunch, + kWebAppLauncherMessageSend, + kWebAppLauncherMessageReceive, + kWebAppLauncherMessageSendJSON, + kWebAppLauncherMessageReceiveJSON, + kWebAppLauncherClose, + kWebAppLauncherConnect, + kWebAppLauncherJoin, + kWebAppLauncherDisconnect + ]]; + } + + [super setCapabilities:caps]; +} + +- (BOOL) isConnectable +{ + return YES; +} + +- (void) connect +{ + if ([AirPlayService serviceMode] == AirPlayServiceModeWebApp) + [self.mirroredService connect]; + + if ([AirPlayService serviceMode] == AirPlayServiceModeMedia) + [self.httpService connect]; + + // delegate will receive connected message from either mirroredService or httpService, depending on the value AirPlayService serviceMode property +} + +- (void) disconnect +{ + if ([AirPlayService serviceMode] == AirPlayServiceModeWebApp && self.mirroredService.connected) + [self.mirroredService disconnect]; + + if ([AirPlayService serviceMode] == AirPlayServiceModeMedia && self.httpService.connected) + [self.httpService disconnect]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(deviceService:disconnectedWithError:)]) + dispatch_on_main(^{ [self.delegate deviceService:self disconnectedWithError:nil]; }); +} + +- (BOOL) connected +{ + switch ([AirPlayService serviceMode]) + { + case AirPlayServiceModeWebApp: + return self.mirroredService.connected; + + case AirPlayServiceModeMedia: + return self.httpService.connected; + + default: + return NO; + } +} + +- (AirPlayServiceHTTP *) httpService +{ + if (!_httpService) + _httpService = [self createHTTPService]; + + return _httpService; +} + +- (AirPlayServiceMirrored *) mirroredService +{ + if (!_mirroredService) + _mirroredService = [[AirPlayServiceMirrored alloc] initWithAirPlayService:self]; + + return _mirroredService; +} + +#pragma mark - MediaPlayer + +- (id ) mediaPlayer +{ + return self.httpService.mediaPlayer; +} + +- (CapabilityPriorityLevel) mediaPlayerPriority +{ + return self.mediaPlayer.mediaPlayerPriority; +} + +- (void) displayImage:(NSURL *)imageURL iconURL:(NSURL *)iconURL title:(NSString *)title description:(NSString *)description mimeType:(NSString *)mimeType success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:imageURL mimeType:mimeType]; + mediaInfo.title = title; + mediaInfo.description = description; + ImageInfo *imageInfo = [[ImageInfo alloc] initWithURL:iconURL type:ImageTypeThumb]; + [mediaInfo addImage:imageInfo]; + + [self displayImageWithMediaInfo:mediaInfo success:^(MediaLaunchObject *mediaLanchObject) { + success(mediaLanchObject.session,mediaLanchObject.mediaControl); + } failure:failure]; +} + +- (void) displayImage:(MediaInfo *)mediaInfo + success:(MediaPlayerDisplaySuccessBlock)success + failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + + [self displayImage:mediaInfo.url iconURL:iconURL title:mediaInfo.title description:mediaInfo.description mimeType:mediaInfo.mimeType success:success failure:failure]; +} + +- (void) displayImageWithMediaInfo:(MediaInfo *)mediaInfo success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + [self.mediaPlayer displayImageWithMediaInfo:mediaInfo success:success failure:failure]; +} + +- (void) playMedia:(NSURL *)mediaURL iconURL:(NSURL *)iconURL title:(NSString *)title description:(NSString *)description mimeType:(NSString *)mimeType shouldLoop:(BOOL)shouldLoop success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:mediaURL mimeType:mimeType]; + mediaInfo.title = title; + mediaInfo.description = description; + ImageInfo *imageInfo = [[ImageInfo alloc] initWithURL:iconURL type:ImageTypeThumb]; + [mediaInfo addImage:imageInfo]; + [self playMediaWithMediaInfo:mediaInfo shouldLoop:shouldLoop success:^(MediaLaunchObject *mediaLanchObject) { + success(mediaLanchObject.session,mediaLanchObject.mediaControl); + } failure:failure]; +} + +- (void) playMedia:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + [self playMedia:mediaInfo.url iconURL:iconURL title:mediaInfo.title description:mediaInfo.description mimeType:mediaInfo.mimeType shouldLoop:shouldLoop success:success failure:failure]; +} + +- (void) playMediaWithMediaInfo:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + [self.mediaPlayer playMediaWithMediaInfo:mediaInfo shouldLoop:shouldLoop success:success failure:failure]; +} + +- (void) closeMedia:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.mediaPlayer closeMedia:launchSession success:success failure:failure]; +} + +#pragma mark - Media Control + +- (id ) mediaControl +{ + return self.httpService.mediaControl; +} + +- (CapabilityPriorityLevel) mediaControlPriority +{ + return self.mediaControl.mediaControlPriority; +} + +- (void) playWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.mediaControl playWithSuccess:success failure:failure]; +} + +- (void) pauseWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.mediaControl pauseWithSuccess:success failure:failure]; +} + +- (void) stopWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.mediaControl stopWithSuccess:success failure:failure]; +} + +- (void) rewindWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.mediaControl rewindWithSuccess:success failure:failure]; +} + +- (void) fastForwardWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.mediaControl fastForwardWithSuccess:success failure:failure]; +} + +- (void) getDurationWithSuccess:(MediaDurationSuccessBlock)success failure:(FailureBlock)failure +{ + [self.mediaControl getDurationWithSuccess:success failure:failure]; +} + +- (void) getPlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + [self.mediaControl getPlayStateWithSuccess:success failure:failure]; +} + +- (void) getPositionWithSuccess:(MediaPositionSuccessBlock)success failure:(FailureBlock)failure +{ + [self.mediaControl getPositionWithSuccess:success failure:failure]; +} + +- (void) seek:(NSTimeInterval)position success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.mediaControl seek:position success:success failure:failure]; +} + +- (ServiceSubscription *) subscribePlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + return [self.mediaControl subscribePlayStateWithSuccess:success failure:failure]; +} + +- (void)getMediaMetaDataWithSuccess:(SuccessBlock)success + failure:(FailureBlock)failure { + [self.mediaControl getMediaMetaDataWithSuccess:success failure:failure]; +} + +- (ServiceSubscription *)subscribeMediaInfoWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + return [self.mediaControl subscribeMediaInfoWithSuccess:success failure:failure]; +} + + +#pragma mark - Helpers + +- (void) closeLaunchSession:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (launchSession.sessionType == LaunchSessionTypeWebApp) + { + [self.webAppLauncher closeWebApp:launchSession success:success failure:failure]; + } else if (launchSession.sessionType == LaunchSessionTypeMedia) + { + [self.mediaPlayer closeMedia:launchSession success:success failure:failure]; + } else + { + if (failure) + dispatch_on_main(^{ failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Could not find DeviceService responsible for closing this LaunchSession"]); }); + } +} + +#pragma mark - WebAppLauncher + +- (id ) webAppLauncher +{ + return self.mirroredService.webAppLauncher; +} + +- (CapabilityPriorityLevel) webAppLauncherPriority +{ + return self.webAppLauncher.webAppLauncherPriority; +} + +- (void) launchWebApp:(NSString *)webAppId success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self.webAppLauncher launchWebApp:webAppId success:success failure:failure]; +} + +- (void) launchWebApp:(NSString *)webAppId params:(NSDictionary *)params success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self.webAppLauncher launchWebApp:webAppId params:params success:success failure:failure]; +} + +- (void) launchWebApp:(NSString *)webAppId params:(NSDictionary *)params relaunchIfRunning:(BOOL)relaunchIfRunning success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self.webAppLauncher launchWebApp:webAppId params:params relaunchIfRunning:relaunchIfRunning success:success failure:failure]; +} + +- (void) launchWebApp:(NSString *)webAppId relaunchIfRunning:(BOOL)relaunchIfRunning success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self.webAppLauncher launchWebApp:webAppId relaunchIfRunning:YES success:success failure:failure]; +} + +- (void) joinWebApp:(LaunchSession *)webAppLaunchSession success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self.webAppLauncher joinWebApp:webAppLaunchSession success:success failure:failure]; +} + +- (void) joinWebAppWithId:(NSString *)webAppId success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self.webAppLauncher joinWebAppWithId:webAppId success:success failure:failure]; +} + +- (void) closeWebApp:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.webAppLauncher closeWebApp:launchSession success:success failure:failure]; +} + +- (void) pinWebApp:(NSString *)webAppId success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +-(void)unPinWebApp:(NSString *)webAppId success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)isWebAppPinned:(NSString *)webAppId success:(WebAppPinStatusBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribeIsWebAppPinned:(NSString*)webAppId success:(WebAppPinStatusBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +#pragma mark - Private + +- (AirPlayServiceHTTP *)createHTTPService { + return [[AirPlayServiceHTTP alloc] initWithAirPlayService:self]; +} + +@end diff --git a/core/Services/AirPlayService_Private.h b/core/Services/AirPlayService_Private.h new file mode 100644 index 00000000..fc02646c --- /dev/null +++ b/core/Services/AirPlayService_Private.h @@ -0,0 +1,27 @@ +// +// AirPlayService_Private.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-07-29. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "AirPlayService.h" + +@interface AirPlayService () + +- (AirPlayServiceHTTP *)createHTTPService; + +@end diff --git a/core/Services/Capabilities/Capability.h b/core/Services/Capabilities/Capability.h new file mode 100644 index 00000000..d966abce --- /dev/null +++ b/core/Services/Capabilities/Capability.h @@ -0,0 +1,63 @@ +// +// Capability.h +// Connect SDK +// +// Created by Jeremy White on 12/26/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +/*! + * CapabilityPriorityLevel values are used by ConnectableDevice to find the most suitable DeviceService capability to be presented to the user. Values of VeryLow and VeryHigh are not in use internally the SDK. Connect SDK uses Low, Normal, and High internally. + * + * Default behavior: + * If you are unsatisfied with the default priority levels & behavior of Connect SDK, it is possible to subclass a particular DeviceService and provide your own value for each capability. That DeviceService subclass would need to be registered with DiscoveryManager. + */ +typedef enum { + CapabilityPriorityLevelVeryLow = 1, + CapabilityPriorityLevelLow = 25, + CapabilityPriorityLevelNormal = 50, + CapabilityPriorityLevelHigh = 75, + CapabilityPriorityLevelVeryHigh = 100 +} CapabilityPriorityLevel; + +/*! + * Generic asynchronous operation response success handler block. If there is any response data to be processed, it will be provided via the responseObject parameter. + * + * @param responseObject Contains the output data as a generic object reference. This value may be any of a number of types (NSString, NSDictionary, NSArray, etc). It is also possible that responseObject will be nil for operations that don't require data to be returned (move mouse, send key code, etc). + */ +typedef void (^ SuccessBlock)(id responseObject); + +/*! + * Generic asynchronous operation response error handler block. In all cases, you will get a valid NSError object. Connect SDK will make all attempts to give you the lowest-level error possible. In cases where an error is generated by Connect SDK, an enumerated error code (ConnectStatusCode) will be present on the NSError object. + * + * ###Low-level error example + * ####Situation + * Connect SDK receives invalid XML from a device, generating a parsing error + * + * ####Result + * Connect SDK will call the FailureBlock and pass off the NSError generated during parsing of the XML. + * + * ###High-level error example + * ####Situation + * An invalid value is passed to a device capability method + * + * ####Result + * The capability method will immediately invoke the FailureBlock and pass off an NSError object with a status code of ConnectStatusCodeArgumentError. + * + * @param error NSError object describing the nature of the problem. Error descriptions are not localized and mostly intended for developer use. It is not recommended to display most error descriptions in UI elements. + */ +typedef void (^ FailureBlock)(NSError *error); diff --git a/core/Services/Capabilities/CapabilityConstants.m b/core/Services/Capabilities/CapabilityConstants.m new file mode 100644 index 00000000..33088f15 --- /dev/null +++ b/core/Services/Capabilities/CapabilityConstants.m @@ -0,0 +1,26 @@ +// +// CapabilityConstants.m +// Connect SDK +// +// Created by Eugene Nikolskyi on 15-07-21. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Contains the definitions of capability constants declared in header files. + +#import "MediaPlayer.h" + +NSString *const kMediaPlayerSubtitleSRT = @"MediaPlayer.Subtitle.SRT"; +NSString *const kMediaPlayerSubtitleWebVTT = @"MediaPlayer.Subtitle.WebVTT"; diff --git a/core/Services/Capabilities/ExternalInputControl.h b/core/Services/Capabilities/ExternalInputControl.h new file mode 100644 index 00000000..a94efac4 --- /dev/null +++ b/core/Services/Capabilities/ExternalInputControl.h @@ -0,0 +1,58 @@ +// +// ExternalInputControl.h +// Connect SDK +// +// Created by Jeremy White on 1/19/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "Capability.h" +#import "ExternalInputInfo.h" +#import "AppInfo.h" + +#define kExternalInputControlAny @"ExternalInputControl.Any" + +#define kExternalInputControlPickerLaunch @"ExternalInputControl.Picker.Launch" +#define kExternalInputControlPickerClose @"ExternalInputControl.Picker.Close" +#define kExternalInputControlList @"ExternalInputControl.List" +#define kExternalInputControlSet @"ExternalInputControl.Set" + +#define kExternalInputControlCapabilities @[\ + kExternalInputControlPickerLaunch,\ + kExternalInputControlPickerClose,\ + kExternalInputControlList,\ + kExternalInputControlSet\ +] + +@protocol ExternalInputControl + +/*! + * Success block that is called upon successfully getting the external input list. + * + * @param externalInputList Array containing an ExternalInputInfo object for each available external input on the device + */ +typedef void (^ ExternalInputListSuccessBlock)(NSArray *externalInputList); + +- (id)externalInputControl; +- (CapabilityPriorityLevel)externalInputControlPriority; + +- (void)launchInputPickerWithSuccess:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure; +- (void)closeInputPicker:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure; + +- (void) getExternalInputListWithSuccess:(ExternalInputListSuccessBlock)success failure:(FailureBlock)failure; +- (void) setExternalInput:(ExternalInputInfo *)externalInputInfo success:(SuccessBlock)success failure:(FailureBlock)failure; + +@end diff --git a/core/Services/Capabilities/KeyControl.h b/core/Services/Capabilities/KeyControl.h new file mode 100644 index 00000000..52d10bda --- /dev/null +++ b/core/Services/Capabilities/KeyControl.h @@ -0,0 +1,61 @@ +// +// KeyControl.h +// Connect SDK +// +// Created by Jeremy White on 1/3/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "Capability.h" + +#define kKeyControlAny @"KeyControl.Any" + +#define kKeyControlUp @"KeyControl.Up" +#define kKeyControlDown @"KeyControl.Down" +#define kKeyControlLeft @"KeyControl.Left" +#define kKeyControlRight @"KeyControl.Right" +#define kKeyControlOK @"KeyControl.OK" +#define kKeyControlBack @"KeyControl.Back" +#define kKeyControlHome @"KeyControl.Home" +#define kKeyControlSendKeyCode @"KeyControl.Send.KeyCode" + +#define kKeyControlCapabilities @[\ + kKeyControlUp,\ + kKeyControlDown,\ + kKeyControlLeft,\ + kKeyControlRight,\ + kKeyControlOK,\ + kKeyControlBack,\ + kKeyControlHome,\ + kKeyControlSendKeyCode\ +] + +@protocol KeyControl + +- (id) keyControl; +- (CapabilityPriorityLevel) keyControlPriority; + +- (void) upWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) downWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) leftWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) rightWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) okWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) backWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) homeWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; + +- (void) sendKeyCode:(NSUInteger)keyCode success:(SuccessBlock)success failure:(FailureBlock)failure; + +@end diff --git a/core/Services/Capabilities/Launcher.h b/core/Services/Capabilities/Launcher.h new file mode 100644 index 00000000..409e2581 --- /dev/null +++ b/core/Services/Capabilities/Launcher.h @@ -0,0 +1,132 @@ +// +// Launcher.h +// Connect SDK +// +// Created by Jeremy White on 12/16/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "Capability.h" +#import "AppInfo.h" +#import "ServiceSubscription.h" +#import "LaunchSession.h" + +#define kLauncherAny @"Launcher.Any" + +#define kLauncherApp @"Launcher.App" +#define kLauncherAppParams @"Launcher.App.Params" +#define kLauncherAppClose @"Launcher.App.Close" +#define kLauncherAppList @"Launcher.App.List" +#define kLauncherAppStore @"Launcher.AppStore" +#define kLauncherAppStoreParams @"Launcher.AppStore.Params" +#define kLauncherBrowser @"Launcher.Browser" +#define kLauncherBrowserParams @"Launcher.Browser.Params" +#define kLauncherHulu @"Launcher.Hulu" +#define kLauncherHuluParams @"Launcher.Hulu.Params" +#define kLauncherNetflix @"Launcher.Netflix" +#define kLauncherNetflixParams @"Launcher.Netflix.Params" +#define kLauncherYouTube @"Launcher.YouTube" +#define kLauncherYouTubeParams @"Launcher.YouTube.Params" +#define kLauncherAppState @"Launcher.AppState" +#define kLauncherAppStateSubscribe @"Launcher.AppState.Subscribe" +#define kLauncherRunningApp @"Launcher.RunningApp" +#define kLauncherRunningAppSubscribe @"Launcher.RunningApp.Subscribe" + +#define kLauncherCapabilities @[\ + kLauncherApp,\ + kLauncherAppParams,\ + kLauncherAppClose,\ + kLauncherAppList,\ + kLauncherAppStore,\ + kLauncherAppStoreParams,\ + kLauncherBrowser,\ + kLauncherBrowserParams,\ + kLauncherHulu,\ + kLauncherHuluParams,\ + kLauncherNetflix,\ + kLauncherNetflixParams,\ + kLauncherYouTube,\ + kLauncherYouTubeParams,\ + kLauncherAppState,\ + kLauncherAppStateSubscribe,\ + kLauncherRunningApp,\ + kLauncherRunningAppSubscribe\ +] + +@protocol Launcher + +/*! + * Success block that is called upon requesting info about the current running app. + * + * @param appInfo Object containing info about the running app + */ +typedef void (^ AppInfoSuccessBlock)(AppInfo *appInfo); + +/*! + * Success block that is called upon successfully launching an app. + * + * @param LaunchSession Object containing important information about the app's launch session + */ +typedef void (^ AppLaunchSuccessBlock)(LaunchSession *launchSession); + +/*! + * Success block that is called upon successfully getting the app list. + * + * @param appList Array containing an AppInfo object for each available app on the device + */ +typedef void (^ AppListSuccessBlock)(NSArray *appList); + +/*! + * Success block that is called upon successfully getting an app's state. + * + * @param running Whether the app is currently running + * @param visible Whether the app is currently visible on the screen + */ +typedef void (^ AppStateSuccessBlock)(BOOL running, BOOL visible); + +- (id) launcher; +- (CapabilityPriorityLevel) launcherPriority; + +#pragma mark Launch & close +- (void)launchApp:(NSString *)appId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure; +- (void)launchAppWithInfo:(AppInfo *)appInfo success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure; +- (void)launchAppWithInfo:(AppInfo *)appInfo params:(NSDictionary *)params success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure; + +- (void)closeApp:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure; + +#pragma mark App Info +- (void) getAppListWithSuccess:(AppListSuccessBlock)success failure:(FailureBlock)failure; + +- (void) getRunningAppWithSuccess:(AppInfoSuccessBlock)success failure:(FailureBlock)failure; +- (ServiceSubscription *)subscribeRunningAppWithSuccess:(AppInfoSuccessBlock)success failure:(FailureBlock)failure; + +- (void)getAppState:(LaunchSession *)launchSession success:(AppStateSuccessBlock)success failure:(FailureBlock)failure; +- (ServiceSubscription *)subscribeAppState:(LaunchSession *)launchSession success:(AppStateSuccessBlock)success failure:(FailureBlock)failure; + +#pragma mark Helpers for deep linking +- (void)launchAppStore:(NSString *)appId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure; +- (void)launchBrowser:(NSURL *)target success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure; +- (void)launchYouTube:(NSString *)contentId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure; +- (void)launchYouTube:(NSString *)contentId startTime:(float)startTime success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure; + +// TODO: add app store deep linking + +// @cond INTERNAL +- (void)launchNetflix:(NSString *)contentId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure; +- (void)launchHulu:(NSString *)contentId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure; +// @endcond + +@end diff --git a/core/Services/Capabilities/MediaControl.h b/core/Services/Capabilities/MediaControl.h new file mode 100644 index 00000000..55f4c971 --- /dev/null +++ b/core/Services/Capabilities/MediaControl.h @@ -0,0 +1,108 @@ +// +// MediaControl.h +// Connect SDK +// +// Created by Jeremy White on 1/22/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "Capability.h" +#import "ServiceSubscription.h" + +#define kMediaControlAny @"MediaControl.Any" + +#define kMediaControlPlay @"MediaControl.Play" +#define kMediaControlPause @"MediaControl.Pause" +#define kMediaControlStop @"MediaControl.Stop" +#define kMediaControlDuration @"MediaControl.Duration" +#define kMediaControlRewind @"MediaControl.Rewind" +#define kMediaControlFastForward @"MediaControl.FastForward" +#define kMediaControlSeek @"MediaControl.Seek" +#define kMediaControlPlayState @"MediaControl.PlayState" +#define kMediaControlPlayStateSubscribe @"MediaControl.PlayState.Subscribe" +#define kMediaControlPosition @"MediaControl.Position" +#define kMediaControlMetadata @"MediaControl.MetaData" +#define kMediaControlMetadataSubscribe @"MediaControl.MetaData.Subscribe" + +#define kMediaControlCapabilities @[\ + kMediaControlPlay,\ + kMediaControlPause,\ + kMediaControlStop,\ + kMediaControlDuration,\ + kMediaControlRewind,\ + kMediaControlFastForward,\ + kMediaControlSeek,\ + kMediaControlPlayState,\ + kMediaControlPlayStateSubscribe,\ + kMediaControlPosition,\ + kMediaControlMetadata,\ + kMediaControlMetadataSubscribe\ +] + +typedef enum { + MediaControlPlayStateUnknown, + MediaControlPlayStateIdle, + MediaControlPlayStatePlaying, + MediaControlPlayStatePaused, + MediaControlPlayStateBuffering, + MediaControlPlayStateFinished +} MediaControlPlayState; + +@protocol MediaControl + +/*! + * Success block that is called upon any change in a media file's play state. + * + * @param playState Play state of the current media file + */ +typedef void (^ MediaPlayStateSuccessBlock)(MediaControlPlayState playState); + +/*! + * Success block that is called upon successfully getting the media file's current playhead position. + * + * @param position Current playhead position of the current media file, in seconds + */ +typedef void (^ MediaPositionSuccessBlock)(NSTimeInterval position); + +/*! + * Success block that is called upon successfully getting the media file's duration. + * + * @param duration Duration of the current media file, in seconds + */ +typedef void (^ MediaDurationSuccessBlock)(NSTimeInterval duration); + +- (id) mediaControl; +- (CapabilityPriorityLevel) mediaControlPriority; + +#pragma mark Play control +- (void) playWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) pauseWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) stopWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) rewindWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) fastForwardWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; + +- (void) seek:(NSTimeInterval)position success:(SuccessBlock)success failure:(FailureBlock)failure; + +#pragma mark Play info +- (void) getDurationWithSuccess:(MediaDurationSuccessBlock)success failure:(FailureBlock)failure; +- (void) getPositionWithSuccess:(MediaPositionSuccessBlock)success failure:(FailureBlock)failure; +- (void)getMediaMetaDataWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; + +- (void) getPlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure; +- (ServiceSubscription *)subscribePlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure; +- (ServiceSubscription *)subscribeMediaInfoWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; + +@end diff --git a/core/Services/Capabilities/MediaPlayer.h b/core/Services/Capabilities/MediaPlayer.h new file mode 100644 index 00000000..8307cad0 --- /dev/null +++ b/core/Services/Capabilities/MediaPlayer.h @@ -0,0 +1,117 @@ +// +// MediaPlayer.h +// Connect SDK +// +// Created by Jeremy White on 12/16/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "Capability.h" +#import "Launcher.h" +#import "MediaControl.h" +#import "MediaInfo.h" +#import "MediaLaunchObject.h" + +#define kMediaPlayerAny @"MediaPlayer.Any" + +#define kMediaPlayerDisplayImage @"MediaPlayer.Display.Image" +#define kMediaPlayerPlayVideo @"MediaPlayer.Play.Video" +#define kMediaPlayerPlayAudio @"MediaPlayer.Play.Audio" +#define kMediaPlayerPlayPlaylist @"MediaPlayer.Play.Playlist" +#define kMediaPlayerLoop @"MediaPlayer.Loop" +#define kMediaPlayerClose @"MediaPlayer.Close" + +#define kMediaPlayerMetaDataTitle @"MediaPlayer.MetaData.Title" +#define kMediaPlayerMetaDataDescription @"MediaPlayer.MetaData.Description" +#define kMediaPlayerMetaDataThumbnail @"MediaPlayer.MetaData.Thumbnail" +#define kMediaPlayerMetaDataMimeType @"MediaPlayer.MetaData.MimeType" + +extern NSString *const kMediaPlayerSubtitleSRT; +extern NSString *const kMediaPlayerSubtitleWebVTT; + + +#define kMediaPlayerCapabilities @[\ + kMediaPlayerDisplayImage,\ + kMediaPlayerPlayVideo,\ + kMediaPlayerPlayAudio,\ + kMediaPlayerClose,\ + kMediaPlayerMetaDataTitle,\ + kMediaPlayerMetaDataDescription,\ + kMediaPlayerMetaDataThumbnail,\ + kMediaPlayerMetaDataMimeType\ +] + +@protocol MediaPlayer + +/*! + * Success block that is called upon successfully playing/displaying a media file. + * + * @param launchSession LaunchSession to allow closing this media player + * @param mediaControl MediaControl object used to control playback + */ +typedef void (^MediaPlayerDisplaySuccessBlock)(LaunchSession *launchSession, id mediaControl); +typedef void (^MediaPlayerSuccessBlock)(MediaLaunchObject *mediaLaunchObject); + + +- (id) mediaPlayer; +- (CapabilityPriorityLevel) mediaPlayerPriority; + +- (void) displayImageWithMediaInfo:(MediaInfo *)mediaInfo + success:(MediaPlayerSuccessBlock)success + failure:(FailureBlock)failure; + +- (void) playMediaWithMediaInfo:(MediaInfo *)mediaInfo + shouldLoop:(BOOL)shouldLoop + success:(MediaPlayerSuccessBlock)success + failure:(FailureBlock)failure; + +- (void) closeMedia:(LaunchSession *)launchSession + success:(SuccessBlock)success + failure:(FailureBlock)failure; + +#pragma mark - Deprecated Methods + +- (void) displayImage:(NSURL *)imageURL + iconURL:(NSURL *)iconURL + title:(NSString *)title + description:(NSString *)description + mimeType:(NSString *)mimeType + success:(MediaPlayerDisplaySuccessBlock)success + failure:(FailureBlock)failure +__deprecated_msg("Please use displayImageWithMediaInfo:success:failure: instead"); + +- (void) displayImage:(MediaInfo *)mediaInfo + success:(MediaPlayerDisplaySuccessBlock)success + failure:(FailureBlock)failure +__deprecated_msg("Please use displayImageWithMediaInfo:success:failure: instead"); + +- (void) playMedia:(NSURL *)mediaURL + iconURL:(NSURL *)iconURL + title:(NSString *)title + description:(NSString *)description + mimeType:(NSString *)mimeType + shouldLoop:(BOOL)shouldLoop + success:(MediaPlayerDisplaySuccessBlock)success + failure:(FailureBlock)failure +__deprecated_msg("Please use playMediaWithMediaInfo:shouldLoop:success:failure: instead"); + +- (void) playMedia:(MediaInfo *)mediaInfo + shouldLoop:(BOOL)shouldLoop + success:(MediaPlayerDisplaySuccessBlock)success + failure:(FailureBlock)failure +__deprecated_msg("Please use playMediaWithMediaInfo:shouldLoop:success:failure: instead"); + +@end diff --git a/core/Services/Capabilities/MouseControl.h b/core/Services/Capabilities/MouseControl.h new file mode 100644 index 00000000..475c0615 --- /dev/null +++ b/core/Services/Capabilities/MouseControl.h @@ -0,0 +1,53 @@ +// +// MouseControl.h +// Connect SDK +// +// Created by Jeremy White on 1/3/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "Capability.h" + +#define kMouseControlAny @"MouseControl.Any" + +#define kMouseControlConnect @"MouseControl.Connect" +#define kMouseControlDisconnect @"MouseControl.Disconnect" +#define kMouseControlClick @"MouseControl.Click" +#define kMouseControlMove @"MouseControl.Move" +#define kMouseControlScroll @"MouseControl.Scroll" + +#define kMouseControlCapabilities @[\ + kMouseControlConnect,\ + kMouseControlDisconnect,\ + kMouseControlClick,\ + kMouseControlMove,\ + kMouseControlScroll\ +] + +@protocol MouseControl + +- (id)mouseControl; +- (CapabilityPriorityLevel)mouseControlPriority; + +- (void) connectMouseWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) disconnectMouse; + +- (void) clickWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; + +- (void) move:(CGVector)distance success:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) scroll:(CGVector)distance success:(SuccessBlock)success failure:(FailureBlock)failure; + +@end diff --git a/core/Services/Capabilities/PlayListControl.h b/core/Services/Capabilities/PlayListControl.h new file mode 100644 index 00000000..06382c13 --- /dev/null +++ b/core/Services/Capabilities/PlayListControl.h @@ -0,0 +1,55 @@ +// +// PlayListControl.h +// ConnectSDK +// +// Created by Ibrahim Adnan on 1/19/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +@protocol PlayListControl + +#define kPlayListControlNext @"PlayListControl.Next" +#define kPlayListControlPrevious @"PlayListControl.Previous" +#define kPlayListControlJumpTrack @"PlayListControl.JumpTrack" + +#define kPlayListControlCapabilities @[\ + kPlayListControlNext,\ + kPlayListControlPrevious,\ + kPlayListControlJumpTrack\ +] + +- (id) playListControl; +- (CapabilityPriorityLevel) playListControlPriority; + +#pragma mark Playlist controls +/*! + * Plays the next track in the playlist + */ +- (void) playNextWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +/*! + * Plays the previous track in the playlist + * @param device ConnectableDevice that has been disconnected. + */ +- (void) playPreviousWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +/*! + * Jumps to track in the playlist + * + * @param index NSInteger a zero based index parameter. + */ +- (void)jumpToTrackWithIndex:(NSInteger)index success:(SuccessBlock)success failure:(FailureBlock)failure; + + +@end diff --git a/core/Services/Capabilities/PowerControl.h b/core/Services/Capabilities/PowerControl.h new file mode 100644 index 00000000..4c8f0a60 --- /dev/null +++ b/core/Services/Capabilities/PowerControl.h @@ -0,0 +1,42 @@ +// +// PowerControl.h +// Connect SDK +// +// Created by Jeremy White on 1/19/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "Capability.h" + +#define kPowerControlAny @"PowerControl.Any" + +#define kPowerControlOff @"PowerControl.Off" +#define kPowerControlOn @"PowerControl.On" + +#define kPowerControlCapabilities @[\ + kPowerControlOff,\ + kPowerControlOn\ +] + +@protocol PowerControl + +- (id)powerControl; +- (CapabilityPriorityLevel)powerControlPriority; + +- (void) powerOffWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) powerOnWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; + +@end diff --git a/core/Services/Capabilities/RemoteCameraControl.h b/core/Services/Capabilities/RemoteCameraControl.h new file mode 100644 index 00000000..58c91b8c --- /dev/null +++ b/core/Services/Capabilities/RemoteCameraControl.h @@ -0,0 +1,84 @@ +// +// RemoteCameraControl.h +// Connect SDK +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef RemoteCameraControl_h +#define RemoteCameraControl_h + +#import +#import "Capability.h" + +#define kRemoteCameraControlAny @"RemoteCameraControl.Any" + +#define kRemoteCameraControlRemoteCamera @"RemoteCameraControl.RemoteCamera" + +#define kRemoteCameraControlCapabilities @[\ + kRemoteCameraControlRemoteCamera\ +] + +#define kRemoteCameraSettingsMicMute @"RemoteCamera.Settings.MicMute" +#define kRemoteCameraSettingsLensFacing @"RemoteCamera.Settings.LensFacing" + +typedef NS_ENUM(int, RemoteCameraLensFacing) { + RemoteCameraLensFacingFront = 0, + RemoteCameraLensFacingBack = 1 +}; + +typedef NS_ENUM(int, RemoteCameraProperty) { + RemoteCameraPropertyUnknown, + RemoteCameraPropertyBrightness, + RemoteCameraPropertyWhiteBalance, + RemoteCameraPropertyRotation +}; + +typedef NS_ENUM(int, RemoteCameraError) { + RemoteCameraErrorGeneric, + RemoteCameraErrorConnectionClosed, + RemoteCameraErrorDeviceShutdown, + RemoteCameraErrorRendererTerminated +}; + +@protocol RemoteCameraControlDelegate + +- (void)remoteCameraDidPair; +- (void)remoteCameraDidStart:(BOOL)result; +- (void)remoteCameraDidStop:(BOOL)result; +- (void)remoteCameraDidPlay; +- (void)remoteCameraDidChange:(RemoteCameraProperty)property; +- (void)remoteCameraErrorDidOccur:(RemoteCameraError)error; + +@end + +@protocol RemoteCameraControl + +@property (nonatomic, readonly) BOOL isSupportedVersion; +@property (nonatomic, readonly) BOOL isRunning; + +- (id)remoteCameraControl; +- (CapabilityPriorityLevel)remoteCameraControlPriority; + +- (UIView *)startRemoteCamera; +- (UIView *)startRemoteCameraWithSettings:(nullable NSDictionary *)settings; +- (void)stopRemoteCamera; +- (void)setLensFacing:(int)lensFacing; +- (void)setMicMute:(BOOL)micMute; +- (void)setRemoteCameraDelegate:(__weak id)delegate; + +@end + +#endif /* RemoteCameraControl_h */ diff --git a/core/Services/Capabilities/ScreenMirroringControl.h b/core/Services/Capabilities/ScreenMirroringControl.h new file mode 100644 index 00000000..407adc42 --- /dev/null +++ b/core/Services/Capabilities/ScreenMirroringControl.h @@ -0,0 +1,63 @@ +// +// ScreenMirroringControl.h +// Connect SDK +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef ScreenMirroringControl_h +#define ScreenMirroringControl_h + +#import +#import +#import "Capability.h" + +#define kScreenMirroringControlAny @"ScreenMirroringControl.Any" + +#define kScreenMirroringControlScreenMirroring @"ScreenMirroringControl.ScreenMirroring" + +#define kScreenMirroringControlCapabilities @[\ + kScreenMirroringControlScreenMirroring\ +] + +typedef NS_ENUM(int, ScreenMirroringError) { + ScreenMirroringErrorGeneric, + ScreenMirroringErrorConnectionClosed, + ScreenMirroringErrorDeviceShutdown, + ScreenMirroringErrorRendererTerminated +}; + +@protocol ScreenMirroringControlDelegate + +- (void)screenMirroringDidStart:(BOOL)result; +- (void)screenMirroringDidStop:(BOOL)result; +- (void)screenMirroringErrorDidOccur:(ScreenMirroringError)error; + +@end + +@protocol ScreenMirroringControl + +- (id)screenMirroringControl; +- (CapabilityPriorityLevel)screenMirroringControlPriority; + +- (void)startScreenMirroring; +- (void)startScreenMirroringWithSettings:(nullable NSDictionary *)settings; +- (void)pushSampleBuffer:(CMSampleBufferRef)sampleBuffer with:(RPSampleBufferType)sampleBufferType; +- (void)stopScreenMirroring; +- (void)setScreenMirroringDelegate:(__weak id)delegate; + +@end + +#endif /* ScreenMirroringControl_h */ diff --git a/core/Services/Capabilities/TVControl.h b/core/Services/Capabilities/TVControl.h new file mode 100644 index 00000000..419a6ebb --- /dev/null +++ b/core/Services/Capabilities/TVControl.h @@ -0,0 +1,121 @@ +// +// TVControl.h +// Connect SDK +// +// Created by Jeremy White on 12/16/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "Capability.h" +#import "ChannelInfo.h" +#import "ProgramInfo.h" +#import "ServiceSubscription.h" + +#define kTVControlAny @"TVControl.Any" + +#define kTVControlChannelGet @"TVControl.Channel.Get" +#define kTVControlChannelSet @"TVControl.Channel.Set" +#define kTVControlChannelUp @"TVControl.Channel.Up" +#define kTVControlChannelDown @"TVControl.Channel.Down" +#define kTVControlChannelList @"TVControl.Channel.List" +#define kTVControlChannelSubscribe @"TVControl.Channel.Subscribe" +#define kTVControlProgramGet @"TVControl.Program.Get" +#define kTVControlProgramList @"TVControl.Program.List" +#define kTVControlProgramSubscribe @"TVControl.Program.Subscribe" +#define kTVControlProgramListSubscribe @"TVControl.Program.List.Subscribe" +#define kTVControl3DGet @"TVControl.3D.Get" +#define kTVControl3DSet @"TVControl.3D.Set" +#define kTVControl3DSubscribe @"TVControl.3D.Subscribe" + +#define kTVControlCapabilities @[\ + kTVControlChannelGet,\ + kTVControlChannelSet,\ + kTVControlChannelUp,\ + kTVControlChannelDown,\ + kTVControlChannelList,\ + kTVControlChannelSubscribe,\ + kTVControlProgramGet,\ + kTVControlProgramList,\ + kTVControlProgramSubscribe,\ + kTVControlProgramListSubscribe,\ + kTVControl3DGet,\ + kTVControl3DSet,\ + kTVControl3DSubscribe\ +] + +@protocol TVControl + +/*! + * Success block that is called upon successfully getting the current channel's information. + * + * @param channelInfo Object containing information about the current channel + */ +typedef void (^ CurrentChannelSuccessBlock)(ChannelInfo *channelInfo); + +/*! + * Success block that is called upon successfully getting the channel list. + * + * @param channelList Array containing a ChannelInfo object for each available channel on the TV + */ +typedef void (^ ChannelListSuccessBlock)(NSArray *channelList); + +/*! + * Success block that is called upon successfully getting the current program's information. + * + * @param programInfo Object containing information about the current program + */ +typedef void (^ ProgramInfoSuccessBlock)(ProgramInfo *programInfo); + +/*! + * Success block that is called upon successfully getting the program list for the current channel. + * + * @param programList Array containing a ProgramInfo object for each available program on the TV's current channel + */ +typedef void (^ ProgramListSuccessBlock)(NSArray *programList); + +/*! + * Success block that is called upon successfully getting the TV's 3D mode + * + * @param tv3DEnabled Whether 3D mode is currently enabled on the TV + */ +typedef void (^ TV3DEnabledSuccessBlock)(BOOL tv3DEnabled); + +- (id)tvControl; +- (CapabilityPriorityLevel)tvControlPriority; + +#pragma mark Set channel +- (void) channelUpWithSuccess:(SuccessBlock)success failure:(FailureBlock) failure; +- (void) channelDownWithSuccess:(SuccessBlock)success failure:(FailureBlock) failure; +- (void) setChannel:(ChannelInfo *)channelInfo success:(SuccessBlock)success failure:(FailureBlock) failure; + +#pragma mark Channel Info +- (void) getCurrentChannelWithSuccess:(CurrentChannelSuccessBlock)success failure:(FailureBlock) failure; +- (ServiceSubscription *)subscribeCurrentChannelWithSuccess:(CurrentChannelSuccessBlock)success failure:(FailureBlock) failure; +- (void) getChannelListWithSuccess:(ChannelListSuccessBlock)success failure:(FailureBlock) failure; + +#pragma mark Program Info +- (void) getProgramInfoWithSuccess:(ProgramInfoSuccessBlock)success failure:(FailureBlock) failure; +- (ServiceSubscription *)subscribeProgramInfoWithSuccess:(ProgramInfoSuccessBlock)success failure:(FailureBlock) failure; + +- (void) getProgramListWithSuccess:(ProgramListSuccessBlock)success failure:(FailureBlock) failure; +- (ServiceSubscription *)subscribeProgramListWithSuccess:(ProgramListSuccessBlock)success failure:(FailureBlock) failure; + +#pragma mark 3D mode +- (void) get3DEnabledWithSuccess:(TV3DEnabledSuccessBlock)success failure:(FailureBlock) failure; +- (void) set3DEnabled:(BOOL)enabled success:(SuccessBlock)success failure:(FailureBlock) failure; +- (ServiceSubscription *) subscribe3DEnabledWithSuccess:(TV3DEnabledSuccessBlock)success failure:(FailureBlock) failure; + +@end diff --git a/core/Services/Capabilities/TextInputControl.h b/core/Services/Capabilities/TextInputControl.h new file mode 100644 index 00000000..de534cdb --- /dev/null +++ b/core/Services/Capabilities/TextInputControl.h @@ -0,0 +1,58 @@ +// +// TextInputControl.h +// Connect SDK +// +// Created by Jeremy White on 1/19/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "Capability.h" +#import "TextInputStatusInfo.h" +#import "ServiceSubscription.h" + +#define kTextInputControlAny @"TextInputControl.Any" + +#define kTextInputControlSendText @"TextInputControl.Send.Text" +#define kTextInputControlSendEnter @"TextInputControl.Send.Enter" +#define kTextInputControlSendDelete @"TextInputControl.Send.Delete" +#define kTextInputControlSubscribe @"TextInputControl.Subscribe" + +#define kTextInputControlCapabilities @[\ + kTextInputControlSendText,\ + kTextInputControlSendEnter,\ + kTextInputControlSendDelete,\ + kTextInputControlSubscribe\ +] + +@protocol TextInputControl + +/*! + * Response block that is fired on any change of keyboard visibility. + * + * @param textInputStatusInfo provides keyboard type & visibility information + */ +typedef void (^ TextInputStatusInfoSuccessBlock)(TextInputStatusInfo *textInputStatusInfo); + +- (id) textInputControl; +- (CapabilityPriorityLevel) textInputControlPriority; + +- (ServiceSubscription *) subscribeTextInputStatusWithSuccess:(TextInputStatusInfoSuccessBlock)success failure:(FailureBlock)failure; + +- (void) sendText:(NSString *)input success:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) sendEnterWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) sendDeleteWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; + +@end diff --git a/core/Services/Capabilities/ToastControl.h b/core/Services/Capabilities/ToastControl.h new file mode 100644 index 00000000..26b14176 --- /dev/null +++ b/core/Services/Capabilities/ToastControl.h @@ -0,0 +1,53 @@ +// +// ToastControl.h +// Connect SDK +// +// Created by Jeremy White on 1/19/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "Capability.h" +#import "AppInfo.h" + +#define kToastControlAny @"ToastControl.Any" + +#define kToastControlShowToast @"ToastControl.Show" +#define kToastControlShowClickableToastApp @"ToastControl.Show.Clickable.App" +#define kToastControlShowClickableToastAppParams @"ToastControl.Show.Clickable.App.Params" +#define kToastControlShowClickableToastURL @"ToastControl.Show.Clickable.URL" + +#define kToastControlCapabilities @[\ + kToastControlShowToast,\ + kToastControlShowClickableToastApp,\ + kToastControlShowClickableToastAppParams,\ + kToastControlShowClickableToastURL\ +] + +@protocol ToastControl + +- (id)toastControl; +- (CapabilityPriorityLevel)toastControlPriority; + +- (void) showToast:(NSString *)message success:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) showToast:(NSString *)message iconData:(NSString *)iconData iconExtension:(NSString *)iconExtension success:(SuccessBlock)success failure:(FailureBlock)failure; + +- (void) showClickableToast:(NSString *)message appInfo:(AppInfo *)appInfo params:(NSDictionary *)params success:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) showClickableToast:(NSString *)message appInfo:(AppInfo *)appInfo params:(NSDictionary *)params iconData:(NSString *)iconData iconExtension:(NSString *)iconExtension success:(SuccessBlock)success failure:(FailureBlock)failure; + +- (void) showClickableToast:(NSString *)message URL:(NSURL *)URL success:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) showClickableToast:(NSString *)message URL:(NSURL *)URL iconData:(NSString *)iconData iconExtension:(NSString *)iconExtension success:(SuccessBlock)success failure:(FailureBlock)failure; + +@end diff --git a/core/Services/Capabilities/VolumeControl.h b/core/Services/Capabilities/VolumeControl.h new file mode 100644 index 00000000..6eaad5f4 --- /dev/null +++ b/core/Services/Capabilities/VolumeControl.h @@ -0,0 +1,79 @@ +// +// VolumeControl.h +// Connect SDK +// +// Created by Jeremy White on 12/16/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "Capability.h" +#import "ServiceSubscription.h" + +#define kVolumeControlAny @"VolumeControl.Any" + +#define kVolumeControlVolumeGet @"VolumeControl.Get" +#define kVolumeControlVolumeSet @"VolumeControl.Set" +#define kVolumeControlVolumeUpDown @"VolumeControl.UpDown" +#define kVolumeControlVolumeSubscribe @"VolumeControl.Subscribe" +#define kVolumeControlMuteGet @"VolumeControl.Mute.Get" +#define kVolumeControlMuteSet @"VolumeControl.Mute.Set" +#define kVolumeControlMuteSubscribe @"VolumeControl.Mute.Subscribe" + +#define kVolumeControlCapabilities @[\ + kVolumeControlVolumeGet,\ + kVolumeControlVolumeSet,\ + kVolumeControlVolumeUpDown,\ + kVolumeControlVolumeSubscribe,\ + kVolumeControlMuteGet,\ + kVolumeControlMuteSet,\ + kVolumeControlMuteSubscribe\ +] + +@protocol VolumeControl + +/*! + * Success block that is called upon successfully getting the device's system volume. + * + * @param volume Current system volume, value is a float between 0.0 and 1.0 + */ +typedef void (^ VolumeSuccessBlock)(float volume); + +/*! + * Success block that is called upon successfully getting the device's system mute status. + * + * @param mute Current system mute status + */ +typedef void (^ MuteSuccessBlock)(BOOL mute); + +- (id)volumeControl; +- (CapabilityPriorityLevel)volumeControlPriority; + +#pragma mark Volume +- (void) volumeUpWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) volumeDownWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; + +- (void) getVolumeWithSuccess:(VolumeSuccessBlock)success failure:(FailureBlock)failure; +- (void) setVolume:(float)volume success:(SuccessBlock)success failure:(FailureBlock)failure; + +- (ServiceSubscription *)subscribeVolumeWithSuccess:(VolumeSuccessBlock)success failure:(FailureBlock)failure; + +#pragma mark Mute +- (void) getMuteWithSuccess:(MuteSuccessBlock)success failure:(FailureBlock)failure; +- (void) setMute:(BOOL)mute success:(SuccessBlock)success failure:(FailureBlock)failure; + +- (ServiceSubscription *)subscribeMuteWithSuccess:(MuteSuccessBlock)success failure:(FailureBlock)failure; + +@end diff --git a/core/Services/Capabilities/WebAppLauncher.h b/core/Services/Capabilities/WebAppLauncher.h new file mode 100644 index 00000000..ec6aaee6 --- /dev/null +++ b/core/Services/Capabilities/WebAppLauncher.h @@ -0,0 +1,92 @@ +// +// WebAppLauncher.h +// Connect SDK +// +// Created by Jeremy White on 12/16/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "Capability.h" +#import "LaunchSession.h" +#import "WebAppSession.h" +#import "MediaControl.h" + +#define kWebAppLauncherAny @"WebAppLauncher.Any" + +#define kWebAppLauncherLaunch @"WebAppLauncher.Launch" +#define kWebAppLauncherLaunchParams @"WebAppLauncher.Launch.Params" +#define kWebAppLauncherMessageSend @"WebAppLauncher.Message.Send" +#define kWebAppLauncherMessageReceive @"WebAppLauncher.Message.Receive" +#define kWebAppLauncherMessageSendJSON @"WebAppLauncher.Message.Send.JSON" +#define kWebAppLauncherMessageReceiveJSON @"WebAppLauncher.Message.Receive.JSON" +#define kWebAppLauncherConnect @"WebAppLauncher.Connect" +#define kWebAppLauncherDisconnect @"WebAppLauncher.Disconnect" +#define kWebAppLauncherJoin @"WebAppLauncher.Join" +#define kWebAppLauncherClose @"WebAppLauncher.Close" +#define kWebAppLauncherPin @"WebAppLauncher.Pin" + +#define kWebAppLauncherCapabilities @[\ + kWebAppLauncherLaunch,\ + kWebAppLauncherLaunchParams,\ + kWebAppLauncherMessageSend,\ + kWebAppLauncherMessageReceive,\ + kWebAppLauncherMessageSendJSON,\ + kWebAppLauncherMessageReceiveJSON,\ + kWebAppLauncherConnect,\ + kWebAppLauncherDisconnect,\ + kWebAppLauncherJoin,\ + kWebAppLauncherClose,\ + kWebAppLauncherPin\ +] + +@protocol WebAppLauncher + +/*! + * Success block that is called upon successfully launch of a web app. + * + * @param webAppSession Object containing important information about the web app's session. This object is required to perform many functions with the web app, including app-to-app communication, media playback, closing, etc. + */ +typedef void (^ WebAppLaunchSuccessBlock)(WebAppSession *webAppSession); + +- (id) webAppLauncher; +- (CapabilityPriorityLevel) webAppLauncherPriority; + +- (void) launchWebApp:(NSString *)webAppId success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure; +- (void) launchWebApp:(NSString *)webAppId params:(NSDictionary *)params success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure; + +/*! + * This method requires pairing on webOS + */ +- (void) launchWebApp:(NSString *)webAppId relaunchIfRunning:(BOOL)relaunchIfRunning success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure; + +/*! + * This method requires pairing on webOS + */ +- (void) launchWebApp:(NSString *)webAppId params:(NSDictionary *)params relaunchIfRunning:(BOOL)relaunchIfRunning success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure; + +- (void) joinWebApp:(LaunchSession *)webAppLaunchSession success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure; +- (void) joinWebAppWithId:(NSString *)webAppId success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure; + +- (void) closeWebApp:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure; + +- (void) pinWebApp:(NSString *)webAppId success:(SuccessBlock)success failure:(FailureBlock)failure; + +- (void) unPinWebApp:(NSString *)webAppId success:(SuccessBlock)success failure:(FailureBlock)failure; + +- (void) isWebAppPinned:(NSString *)webAppId success:(WebAppPinStatusBlock)success failure:(FailureBlock)failure; + +- (ServiceSubscription *)subscribeIsWebAppPinned:(NSString*)webAppId success:(WebAppPinStatusBlock)success failure:(FailureBlock)failure; +@end diff --git a/core/Services/Commands/ServiceAsyncCommand.h b/core/Services/Commands/ServiceAsyncCommand.h new file mode 100644 index 00000000..cc29a3f5 --- /dev/null +++ b/core/Services/Commands/ServiceAsyncCommand.h @@ -0,0 +1,27 @@ +// +// ServiceAsyncCommand.h +// Connect SDK +// +// Created by Andrew Longstaff on 9/23/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ServiceCommand.h" + +@interface ServiceAsyncCommand : ServiceCommand + ++ (instancetype) asyncWithDelegate:(id )delegate target:(NSURL *)URL payload:(id)payload; + +@end diff --git a/core/Services/Commands/ServiceAsyncCommand.m b/core/Services/Commands/ServiceAsyncCommand.m new file mode 100644 index 00000000..fa05327f --- /dev/null +++ b/core/Services/Commands/ServiceAsyncCommand.m @@ -0,0 +1,42 @@ +// +// ServiceAsychCommand.m +// Connect SDK +// +// Created by Andrew Longstaff on 9/23/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ServiceAsyncCommand.h" + +@implementation ServiceAsyncCommand + +- (instancetype) initWithDelegate:(id )delegate target:(NSURL *)target payload:(id)payload +{ + self = [super initWithDelegate:delegate target:target payload:payload]; + return self; +} + ++ (instancetype) asyncWithDelegate:(id )delegate target:(NSURL *)target payload:(id)payload +{ + return [[ServiceAsyncCommand alloc] initWithDelegate:delegate target:target payload:payload];; +} + +- (void) send +{ + if ([self.delegate respondsToSelector:@selector(sendAsync:withPayload:toURL:)]) + [self.delegate sendAsync:self withPayload:self.payload toURL:self.target]; +} + +@end diff --git a/core/Services/Commands/ServiceCommand.h b/core/Services/Commands/ServiceCommand.h new file mode 100644 index 00000000..b1d7e1dd --- /dev/null +++ b/core/Services/Commands/ServiceCommand.h @@ -0,0 +1,40 @@ +// +// ServiceCommand.h +// Connect SDK +// +// Created by Andrew Longstaff on 9/6/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "ServiceCommandDelegate.h" +#import "Capability.h" + +@interface ServiceCommand : NSObject + +@property (nonatomic, weak) id delegate; +@property (nonatomic, copy) SuccessBlock callbackComplete; +@property (nonatomic, copy) FailureBlock callbackError; +@property (nonatomic, strong) NSString *HTTPMethod; +@property (nonatomic, strong) id payload; +@property (nonatomic, strong) NSURL *target; + + +- (instancetype) initWithDelegate:(id )delegate target:(NSURL *)url payload:(id)payload; ++ (instancetype) commandWithDelegate:(id )delegate target:(NSURL *)url payload:(id)payload; + +-(void) send; + +@end diff --git a/core/Services/Commands/ServiceCommand.m b/core/Services/Commands/ServiceCommand.m new file mode 100644 index 00000000..da48f2ea --- /dev/null +++ b/core/Services/Commands/ServiceCommand.m @@ -0,0 +1,62 @@ +// +// ServiceCommand.m +// Connect SDK +// +// Created by Andrew Longstaff on 9/6/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ServiceCommand.h" + +@implementation ServiceCommand{ + int _dataId; +} + +-(instancetype)initWithDelegate:(id )delegate target:(NSURL *)target payload:(id)payload +{ + self = [super init]; + + if (self) + { + _delegate = delegate; + _target = target; + _payload = payload; + _dataId = -1; + _HTTPMethod = @"POST"; + } + + return self; +} + ++ (instancetype) commandWithDelegate:(id )delegate target:(NSURL *)target payload:(id)payload +{ + return [[ServiceCommand alloc] initWithDelegate:delegate target:target payload:payload]; +} + +- (void) send +{ + if ([_delegate respondsToSelector:@selector(sendCommand:withPayload:toURL:)]) + /*_dataId = */[_delegate sendCommand:self withPayload:self.payload toURL:self.target]; +} + +- (instancetype) clone +{ + ServiceCommand *clone = [ServiceCommand commandWithDelegate:self.delegate target:self.target payload:self.payload]; + clone.callbackComplete = self.callbackComplete; + clone.callbackError = self.callbackError; + return clone; +} + +@end diff --git a/core/Services/Commands/ServiceCommandDelegate.h b/core/Services/Commands/ServiceCommandDelegate.h new file mode 100644 index 00000000..69149b55 --- /dev/null +++ b/core/Services/Commands/ServiceCommandDelegate.h @@ -0,0 +1,40 @@ +// +// ServiceCommandDelegate.h +// Connect SDK +// +// Created by Andrew Longstaff on 9/6/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@class ServiceCommand; +@class ServiceSubscription; +@class ServiceAsyncCommand; + +@protocol ServiceCommandDelegate + +typedef enum { + ServiceSubscriptionTypeUnsubscribe = NO, + ServiceSubscriptionTypeSubscribe = YES +} ServiceSubscriptionType; + +@optional + +- (int) sendCommand:(ServiceCommand *)comm withPayload:(id)payload toURL:(NSURL*)URL; +- (int) sendSubscription:(ServiceSubscription *)subscription type:(ServiceSubscriptionType)type payload:(id)payload toURL:(NSURL *)URL withId:(int)callId; +- (int) sendAsync:(ServiceAsyncCommand *)async withPayload:(id)payload toURL:(NSURL*)URL; + +@end diff --git a/core/Services/Commands/ServiceSubscription.h b/core/Services/Commands/ServiceSubscription.h new file mode 100644 index 00000000..f170adbb --- /dev/null +++ b/core/Services/Commands/ServiceSubscription.h @@ -0,0 +1,43 @@ +// +// ServiceSubscription.h +// Connect SDK +// +// Created by Andrew Longstaff on 9/6/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ServiceCommand.h" + +/// A special value identifying the @c callId is unset. +extern const int kUnsetCallId; + +@interface ServiceSubscription : ServiceCommand + +@property (nonatomic, readonly) int callId; +@property (nonatomic, strong) NSMutableArray *successCalls; +@property (nonatomic, strong) NSMutableArray *failureCalls; + +@property (nonatomic) BOOL isSubscribed; + +- (instancetype) initWithDelegate:(id )delegate target:(NSURL *)target payload:(id)payload callId:(int)callId; ++ (instancetype) subscriptionWithDelegate:(id )delegate target:(NSURL *)url payload:(id)payload callId:(int)callId; + +-(void) addSuccess:(id)success; +-(void) addFailure:(FailureBlock)failure; + +-(void) subscribe; +-(void) unsubscribe; + +@end diff --git a/core/Services/Commands/ServiceSubscription.m b/core/Services/Commands/ServiceSubscription.m new file mode 100644 index 00000000..4c5cef2a --- /dev/null +++ b/core/Services/Commands/ServiceSubscription.m @@ -0,0 +1,129 @@ +// +// ServiceSubscription.m +// Connect SDK +// +// Created by Andrew Longstaff on 9/6/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ServiceSubscription.h" + +const int kUnsetCallId = -1; + +@implementation ServiceSubscription + +-(instancetype)initWithDelegate:(id )delegate target:(NSURL *)target payload:(id)payload callId:(int)callId +{ + self = [self initWithDelegate:delegate target:target payload:payload]; + + if (self) + { + _callId = callId; + } + + return self; +} + +-(instancetype)initWithDelegate:(id )delegate target:(NSURL *)target payload:(id)payload +{ + self = [super initWithDelegate:delegate target:payload payload:payload]; + + if (self) + { + self.target = target; + _isSubscribed = NO; + _callId = -1; + } + + return self; +} + ++(instancetype)subscriptionWithDelegate:(id )delegate target:(NSURL *)url payload:(id)payload callId:(int)callId{ + ServiceSubscription *subscription = [[ServiceSubscription alloc] initWithDelegate:delegate target:url payload:payload callId:callId]; + return subscription; +} + +-(void) addSuccess:(id)success{ + if(!_successCalls) _successCalls = [[NSMutableArray alloc] init]; + + if (success && ![_successCalls containsObject:success]) + [_successCalls addObject:success]; + + NSArray *successes = [NSArray arrayWithArray:_successCalls]; + self.callbackComplete = ^(NSDictionary*dic){ + for(int i=0; i< [successes count]; i++){ + ((SuccessBlock)[successes objectAtIndex:i])(dic); + } + }; +} +-(void)addFailure:(FailureBlock)failure +{ + if(!_failureCalls) _failureCalls = [[NSMutableArray alloc] init]; + + if (failure && ![_failureCalls containsObject:failure]) + [_failureCalls addObject:failure]; + + NSArray *fails = [NSArray arrayWithArray:_failureCalls]; + self.callbackError = ^(NSError*err){ + for(int i=0; i< [fails count]; i++){ + ((FailureBlock)[fails objectAtIndex:i])(err); + } + }; +} + +-(void) subscribe +{ + if ([self.delegate respondsToSelector:@selector(sendSubscription:type:payload:toURL:withId:)]) + { + _callId = [self.delegate sendSubscription:self type:ServiceSubscriptionTypeSubscribe payload:self.payload toURL:self.target withId:_callId]; + _isSubscribed = true; + } +} + +//Override for semantic clarity +-(void) cancel +{ + [self unsubscribe]; +} + +-(void) unsubscribe +{ + if ([self.delegate respondsToSelector:@selector(sendSubscription:type:payload:toURL:withId:)]) + { + _callId = [self.delegate sendSubscription:self type:ServiceSubscriptionTypeUnsubscribe payload:self.payload toURL:self.target withId:_callId]; + _isSubscribed = false; + } + + _successCalls = nil; + _failureCalls = nil; +} + +-(instancetype) clone +{ + ServiceSubscription *clone = [ServiceSubscription subscriptionWithDelegate:self.delegate target:[self.target copy] payload:[self.payload copy] callId:_callId]; + return clone; +} + +#pragma mark - Debugging + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: callId=%ld; target=%@; " + @"payload=%@; #successCallbacks=%ld; #failureCallbacks=%ld>", + NSStringFromClass([self class]), (unsigned long)self.callId, + self.target, self.payload, (unsigned long)self.successCalls.count, + (unsigned long)self.failureCalls.count]; +} + +@end diff --git a/core/Services/Config/NetcastTVServiceConfig.h b/core/Services/Config/NetcastTVServiceConfig.h new file mode 100644 index 00000000..6b78c1b8 --- /dev/null +++ b/core/Services/Config/NetcastTVServiceConfig.h @@ -0,0 +1,28 @@ +// +// NetcastTVServiceConfig.h +// Connect SDK +// +// Created by Jeremy White on 12/30/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "ServiceConfig.h" + +@interface NetcastTVServiceConfig : ServiceConfig + +@property (nonatomic, strong) NSString *pairingCode; + +@end diff --git a/core/Services/Config/NetcastTVServiceConfig.m b/core/Services/Config/NetcastTVServiceConfig.m new file mode 100644 index 00000000..de03825a --- /dev/null +++ b/core/Services/Config/NetcastTVServiceConfig.m @@ -0,0 +1,69 @@ +// +// NetcastTVServiceConfig.m +// Connect SDK +// +// Created by Jeremy White on 12/30/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NetcastTVServiceConfig.h" + + +@implementation NetcastTVServiceConfig + +- (instancetype) initWithJSONObject:(NSDictionary *)dict +{ + self = [super init]; + + if (self) + { + self.pairingCode = dict[@"pairingCode"]; + } + + return self; +} + +- (NSDictionary *) toJSONObject +{ + NSDictionary *superDictionary = [super toJSONObject]; + NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithDictionary:superDictionary]; + + dictionary[@"class"] = NSStringFromClass([self class]); + + if (self.pairingCode) dictionary[@"pairingCode"] = self.pairingCode; + + return [NSDictionary dictionaryWithDictionary:dictionary]; +} + +- (void) addObservers +{ + [super addObservers]; + + [self addObserver:self forKeyPath:@"pairingCode" options:0 context:nil]; +} + +- (void)removeObservers +{ + [super removeObservers]; + + [self removeObserver:self forKeyPath:@"pairingCode"]; +} + +- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + [self.delegate serviceConfigUpdate:self]; +} + +@end diff --git a/core/Services/Config/ServiceConfig.h b/core/Services/Config/ServiceConfig.h new file mode 100644 index 00000000..117acb17 --- /dev/null +++ b/core/Services/Config/ServiceConfig.h @@ -0,0 +1,41 @@ +// +// ServiceConfig.h +// Connect SDK +// +// Created by Jeremy White on 12/9/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "ServiceDescription.h" +#import "ServiceConfigDelegate.h" +#import "JSONObjectCoding.h" + +@interface ServiceConfig : NSObject + +@property (nonatomic, strong) NSString *UUID; +@property (nonatomic) BOOL connected; +@property (nonatomic) BOOL wasConnected; +@property (nonatomic) double lastDetection; + +@property (nonatomic, weak) id delegate; + ++ (instancetype) serviceConfigWithJSONObject:(NSDictionary *)dictionary; +- (instancetype) initWithServiceConfig:(ServiceConfig *)serviceConfig; +- (instancetype) initWithServiceDescription:(ServiceDescription *)serviceDescription; +- (void) addObservers; +- (void) removeObservers; + +@end diff --git a/core/Services/Config/ServiceConfig.m b/core/Services/Config/ServiceConfig.m new file mode 100644 index 00000000..28773de0 --- /dev/null +++ b/core/Services/Config/ServiceConfig.m @@ -0,0 +1,160 @@ +// +// ServiceConfig.m +// Connect SDK +// +// Created by Jeremy White on 12/9/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ServiceConfig.h" + +@implementation ServiceConfig +{ + BOOL _hasObservers; +} + ++ (instancetype) serviceConfigWithJSONObject:(NSDictionary *)dictionary +{ + NSString *configClassName = dictionary[@"class"]; + Class configClass = NSClassFromString(configClassName); + + ServiceConfig *serviceConfig = [[configClass alloc] initWithJSONObject:dictionary]; + return serviceConfig; +} + +- (instancetype) initWithServiceConfig:(ServiceConfig *)serviceConfig +{ + self = [super init]; + + if (self) + { + self.UUID = serviceConfig.UUID; + self.connected = serviceConfig.connected; + self.wasConnected = serviceConfig.wasConnected; + self.lastDetection = serviceConfig.lastDetection; + + self.delegate = serviceConfig.delegate; + serviceConfig.delegate = nil; + } + + return self; +} + +- (instancetype) initWithServiceDescription:(ServiceDescription *)serviceDescription +{ + self = [super init]; + + if (self) + { + self.UUID = serviceDescription.UUID; + self.connected = NO; + self.wasConnected = NO; + self.lastDetection = [NSDate date].timeIntervalSince1970; + } + + return self; +} + +- (void) addObservers +{ + if (_hasObservers) + return; + + [self addObserver:self forKeyPath:@"UUID" options:0 context:nil]; + [self addObserver:self forKeyPath:@"connected" options:0 context:nil]; + [self addObserver:self forKeyPath:@"wasConnected" options:0 context:nil]; + [self addObserver:self forKeyPath:@"lastDetection" options:0 context:nil]; + + _hasObservers = YES; +} + +- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + [_delegate serviceConfigUpdate:self]; +} + +- (void)setDelegate:(id )delegate +{ + if (delegate == nil && _delegate != nil) + [self removeObservers]; + else if (delegate != nil && _delegate == nil) + { + _delegate = delegate; + [self addObservers]; + } else + _delegate = delegate; +} + +- (void) removeObservers +{ + if (!_hasObservers) + return; + + @try { + [self removeObserver:self forKeyPath:@"UUID"]; + [self removeObserver:self forKeyPath:@"connected"]; + [self removeObserver:self forKeyPath:@"wasConnected"]; + [self removeObserver:self forKeyPath:@"lastDetection"]; + } + @catch (NSException *exception) { + // don't need to handle this exception, because observers aren't added + } + + _hasObservers = NO; +} + +- (void) dealloc +{ + self.delegate = nil; +} + +#pragma mark - JSONObjectCoding methods + +- (instancetype) initWithJSONObject:(NSDictionary *)dictionary +{ + self = [super init]; + + if (self) + { + self.UUID = [dictionary objectForKey:@"UUID"]; + self.connected = [[dictionary objectForKey:@"connected"] boolValue]; + self.wasConnected = [[dictionary objectForKey:@"wasConnected"] boolValue]; + self.lastDetection = [[dictionary objectForKey:@"lastDetection"] doubleValue]; + + if (self.connected) + { + self.connected = NO; + self.wasConnected = YES; + } + } + + return self; +} + +- (NSDictionary *) toJSONObject +{ + NSMutableDictionary *dictionary = [NSMutableDictionary new]; + + dictionary[@"class"] = NSStringFromClass([self class]); + + if (self.UUID) dictionary[@"UUID"] = self.UUID; + if (self.connected) dictionary[@"connected"] = @(self.connected); + if (self.wasConnected) dictionary[@"wasConnected"] = @(self.wasConnected); + if (self.lastDetection) dictionary[@"lastDetection"] = @(self.lastDetection); + + return [NSDictionary dictionaryWithDictionary:dictionary]; +} + +@end diff --git a/core/Services/Config/ServiceConfigDelegate.h b/core/Services/Config/ServiceConfigDelegate.h new file mode 100644 index 00000000..9761a56c --- /dev/null +++ b/core/Services/Config/ServiceConfigDelegate.h @@ -0,0 +1,29 @@ +// +// ServiceConfigDelegate.h +// Connect SDK +// +// Created by Andrew Longstaff on 9/12/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@class ServiceConfig; + +@protocol ServiceConfigDelegate + +- (void) serviceConfigUpdate:(ServiceConfig*)serviceConfig; + +@end diff --git a/core/Services/Config/ServiceDescription.h b/core/Services/Config/ServiceDescription.h new file mode 100644 index 00000000..23a94d6d --- /dev/null +++ b/core/Services/Config/ServiceDescription.h @@ -0,0 +1,58 @@ +// +// ServiceDescription.h +// Connect SDK +// +// Created by Andrew Longstaff on 9/6/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "JSONObjectCoding.h" + +@interface ServiceDescription : NSObject + +@property (nonatomic, strong) NSString *address; +@property (nonatomic, strong) NSString *serviceId; +@property (nonatomic) NSUInteger port; +@property (nonatomic, strong) NSString *UUID; +@property (nonatomic, strong) NSString *type; +@property (nonatomic, strong) NSString *version; +@property (nonatomic, strong) NSString *friendlyName; +@property (nonatomic, strong) NSString *manufacturer; +@property (nonatomic, strong) NSString *modelName; +@property (nonatomic, strong) NSString *modelDescription; +@property (nonatomic, strong) NSString *modelNumber; +@property (nonatomic, strong) NSURL *commandURL; +@property (nonatomic, strong) NSString *locationXML; +@property (nonatomic, strong) NSArray *serviceList; +@property (nonatomic, strong) NSDictionary *locationResponseHeaders; +@property (nonatomic) double lastDetection; +/** + * @brief A device object set by a discovery provider when a service requires it (that is, it is the + * only way to control the remote device). + * + * For example, @c CastService requires a @c GCKDevice object retrieved during discovery in + * CastDiscoveryProvider. On the other hand, @c DLNAService doesn't require any specific + * object, because it works via HTTP using other properties. + * @note The service is responsible for checking that the property is of the expected type. + */ +@property (nonatomic, strong) id device; + +- (instancetype)initWithAddress:(NSString *)address UUID:(NSString*)UUID; ++ (instancetype)descriptionWithAddress:(NSString *)address UUID:(NSString*)UUID; + +- (BOOL)isEqualToServiceDescription:(ServiceDescription *)service; + +@end diff --git a/core/Services/Config/ServiceDescription.m b/core/Services/Config/ServiceDescription.m new file mode 100644 index 00000000..5b53548b --- /dev/null +++ b/core/Services/Config/ServiceDescription.m @@ -0,0 +1,202 @@ +// +// ServiceDescription.m +// Connect SDK +// +// Created by Andrew Longstaff on 9/6/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ServiceDescription.h" +#import "CommonMacros.h" + +@implementation ServiceDescription + +- (instancetype)initWithAddress:(NSString *)address UUID:(NSString*)UUID +{ + self = [super init]; + + if (self) + { + self.address = address; + self.UUID = UUID; + self.port = 0; + self.lastDetection = [[NSDate date] timeIntervalSince1970]; + } + + return self; +} + ++ (instancetype)descriptionWithAddress:(NSString *)address UUID:(NSString*)UUID +{ + return [[ServiceDescription alloc] initWithAddress:address UUID:UUID]; +} + +#pragma mark - JSONObjectCoding methods + +- (instancetype) initWithJSONObject:(NSDictionary *)dict +{ + self = [super init]; + + if (self) + { + self.serviceId = dict[@"serviceId"]; + self.address = dict[@"address"]; + self.port = [dict[@"port"] intValue]; + self.UUID = dict[@"UUID"]; + self.type = dict[@"serviceId"]; + self.version = dict[@"version"]; + self.friendlyName = dict[@"friendlyName"]; + self.manufacturer = dict[@"manufacturer"]; + self.modelName = dict[@"modelName"]; + self.modelDescription = dict[@"modelDescription"]; + self.modelNumber = dict[@"modelNumber"]; + + NSString *commandPath = dict[@"commandURL"]; + + if (commandPath) + self.commandURL = [NSURL URLWithString:commandPath]; + } + + return self; +} + +- (NSDictionary *) toJSONObject +{ + NSMutableDictionary *dictionary = [NSMutableDictionary new]; + + if (self.serviceId) dictionary[@"serviceId"] = self.serviceId; + if (self.address) dictionary[@"address"] = self.address; + if (self.port) dictionary[@"port"] = @(self.port); + if (self.UUID) dictionary[@"UUID"] = self.UUID; + if (self.type) dictionary[@"type"] = self.type; + if (self.version) dictionary[@"version"] = self.version; + if (self.friendlyName) dictionary[@"friendlyName"] = self.friendlyName; + if (self.manufacturer) dictionary[@"manufacturer"] = self.manufacturer; + if (self.modelName) dictionary[@"modelName"] = self.modelName; + if (self.modelDescription) dictionary[@"modelDescription"] = self.modelDescription; + if (self.modelNumber) dictionary[@"modelNumber"] = self.modelNumber; + if (self.commandURL) dictionary[@"commandURL"] = self.commandURL.absoluteString; + + return [NSDictionary dictionaryWithDictionary:dictionary]; +} + +#pragma mark - NSCopying methods + +- (id) copy +{ + ServiceDescription *serviceDescription = [[ServiceDescription alloc] initWithAddress:[self.address copy] UUID:[self.UUID copy]]; + serviceDescription.serviceId = [self.serviceId copy]; + serviceDescription.port = self.port; + serviceDescription.type = [self.type copy]; + serviceDescription.version = [self.version copy]; + serviceDescription.friendlyName = [self.friendlyName copy]; + serviceDescription.manufacturer = [self.manufacturer copy]; + serviceDescription.modelName = [self.modelName copy]; + serviceDescription.modelDescription = [self.modelDescription copy]; + serviceDescription.modelNumber = [self.modelNumber copy]; + serviceDescription.commandURL = [self.commandURL copy]; + serviceDescription.locationResponseHeaders = [self.locationResponseHeaders copy]; + serviceDescription.locationXML = [self.locationXML copy]; + serviceDescription.serviceList = [self.serviceList copy]; + + return serviceDescription; +} + +- (id) copyWithZone:(NSZone *)zone +{ + return [self copy]; +} + +#pragma mark - Equality methods + +- (BOOL)isEqualToServiceDescription:(ServiceDescription *)service +{ + if (!service) { + return NO; + } + + NSArray *stringProperties = @[STRING_PROPERTY(address), + STRING_PROPERTY(serviceId), + STRING_PROPERTY(UUID), + STRING_PROPERTY(type), + STRING_PROPERTY(version), + STRING_PROPERTY(friendlyName), + STRING_PROPERTY(manufacturer), + STRING_PROPERTY(modelName), + STRING_PROPERTY(modelDescription), + STRING_PROPERTY(modelNumber), + STRING_PROPERTY(locationXML)]; + for (NSString *propName in stringProperties) { + NSString *selfProp = [self valueForKey:propName]; + NSString *otherProp = [service valueForKey:propName]; + const BOOL haveSameProperty = (!selfProp && !otherProp) || [selfProp isEqualToString:otherProp]; + if (!haveSameProperty) { + return NO; + } + }; + + const BOOL haveSamePort = (self.port == service.port); + const BOOL haveSameCommandURL = (!self.commandURL && !service.commandURL) || [self.commandURL isEqual:service.commandURL]; + const BOOL haveSameServiceList = (!self.serviceList && !service.serviceList) || [self.serviceList isEqualToArray:service.serviceList]; + const BOOL haveSameHeaders = (!self.locationResponseHeaders && !service.locationResponseHeaders) || [self.locationResponseHeaders isEqualToDictionary:service.locationResponseHeaders]; + const BOOL haveSameDevices = ((!self.device && !service.device) || + [self.device isEqual:service.device]); + + // NB: lastDetection isn't compared here + return (haveSamePort && haveSameCommandURL && haveSameServiceList && + haveSameHeaders && haveSameDevices); +} + +- (BOOL)isEqual:(id)object +{ + if (self == object) { + return YES; + } + + if (![object isKindOfClass:[ServiceDescription class]]) { + return NO; + } + + return [self isEqualToServiceDescription:object]; +} + +- (NSUInteger)hash +{ + NSArray *properties = @[STRING_PROPERTY(address), + STRING_PROPERTY(serviceId), STRING_PROPERTY(UUID), + STRING_PROPERTY(type), STRING_PROPERTY(version), + STRING_PROPERTY(friendlyName), + STRING_PROPERTY(manufacturer), + STRING_PROPERTY(modelName), + STRING_PROPERTY(modelDescription), + STRING_PROPERTY(modelNumber), + STRING_PROPERTY(commandURL), + STRING_PROPERTY(locationXML), + STRING_PROPERTY(serviceList), + STRING_PROPERTY(locationResponseHeaders), + STRING_PROPERTY(device)]; + NSUInteger hash = 0; + for (NSString *propName in properties) { + id prop = [self valueForKey:propName]; + hash ^= [prop hash]; + } + + hash ^= self.port; + // NB: lastDetection isn't used here + + return hash; +} + +@end diff --git a/core/Services/Config/WebOSTVServiceConfig.h b/core/Services/Config/WebOSTVServiceConfig.h new file mode 100644 index 00000000..13e266f3 --- /dev/null +++ b/core/Services/Config/WebOSTVServiceConfig.h @@ -0,0 +1,28 @@ +// +// WebOSTVServiceConfig.h +// Connect SDK +// +// Created by Jeremy White on 12/9/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ServiceConfig.h" + +@interface WebOSTVServiceConfig : ServiceConfig + +@property (nonatomic, strong) NSString *clientKey; +@property (nonatomic, strong) NSArray *SSLCertificates; + +@end diff --git a/core/Services/Config/WebOSTVServiceConfig.m b/core/Services/Config/WebOSTVServiceConfig.m new file mode 100644 index 00000000..bea9152f --- /dev/null +++ b/core/Services/Config/WebOSTVServiceConfig.m @@ -0,0 +1,72 @@ +// +// WebOSTVServiceConfig.m +// Connect SDK +// +// Created by Jeremy White on 12/9/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "WebOSTVServiceConfig.h" + +@implementation WebOSTVServiceConfig + +- (instancetype) initWithJSONObject:(NSDictionary *)dict +{ + self = [super init]; + + if (self) + { + self.clientKey = dict[@"clientKey"]; + self.SSLCertificates = dict[@"SSLCertificates"]; + } + + return self; +} + +- (NSDictionary *) toJSONObject +{ + NSDictionary *superDictionary = [super toJSONObject]; + NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithDictionary:superDictionary]; + + dictionary[@"class"] = NSStringFromClass([self class]); + + if (self.clientKey) dictionary[@"clientKey"] = self.clientKey; + if (self.SSLCertificates) dictionary[@"SSLCertificates"] = self.SSLCertificates; + + return [NSDictionary dictionaryWithDictionary:dictionary]; +} + +- (void) addObservers +{ + [super addObservers]; + + [self addObserver:self forKeyPath:@"clientKey" options:0 context:nil]; + [self addObserver:self forKeyPath:@"SSLCertificates" options:0 context:nil]; +} + +- (void) removeObservers +{ + [super removeObservers]; + + [self removeObserver:self forKeyPath:@"clientKey"]; + [self removeObserver:self forKeyPath:@"SSLCertificates"]; +} + +- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + [self.delegate serviceConfigUpdate:self]; +} + +@end diff --git a/core/Services/DIALService.h b/core/Services/DIALService.h new file mode 100644 index 00000000..636754ea --- /dev/null +++ b/core/Services/DIALService.h @@ -0,0 +1,31 @@ +// +// DIALService.h +// Connect SDK +// +// Created by Jeremy White on 12/13/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#define kConnectSDKDIALServiceId @"DIAL" + +#import "DeviceService.h" +#import "ServiceCommandDelegate.h" +#import "Launcher.h" + +@interface DIALService : DeviceService + ++ (void) registerApp:(NSString *)appId; + +@end diff --git a/core/Services/DIALService.m b/core/Services/DIALService.m new file mode 100644 index 00000000..dd3b418b --- /dev/null +++ b/core/Services/DIALService.m @@ -0,0 +1,576 @@ +// +// DIALService.m +// Connect SDK +// +// Created by Jeremy White on 12/13/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DIALService.h" +#import "ConnectError.h" +#import "CTXMLReader.h" +#import "DeviceServiceReachability.h" +#import "CTGuid.h" + +#import "NSObject+FeatureNotSupported_Private.h" + +static NSMutableArray *registeredApps = nil; + +@interface DIALService () + +@end + +@implementation DIALService +{ + DeviceServiceReachability *_serviceReachability; +} + ++ (void) initialize +{ + registeredApps = [NSMutableArray arrayWithArray:@[ + @"YouTube", + @"Netflix", + @"Amazon" + ]]; +} + +- (void) commonInit +{ + [self addCapabilities:@[ + kLauncherApp, + kLauncherAppParams, + kLauncherAppClose, + kLauncherAppState + ]]; +} + +- (instancetype) init +{ + self = [super init]; + + if (self) + [self commonInit]; + + return self; +} + +- (instancetype) initWithServiceConfig:(ServiceConfig *)serviceConfig +{ + self = [super initWithServiceConfig:serviceConfig]; + + if (self) + [self commonInit]; + + return self; +} + ++ (void) registerApp:(NSString *)appId +{ + if (![registeredApps containsObject:appId]) + [registeredApps addObject:appId]; +} + ++ (NSDictionary *) discoveryParameters +{ + return @{ + @"serviceId":kConnectSDKDIALServiceId, + @"ssdp":@{ + @"filter":@"urn:dial-multiscreen-org:service:dial:1" + } + }; +} + +- (void)setServiceDescription:(ServiceDescription *)serviceDescription +{ + [super setServiceDescription:serviceDescription]; + + if (self.serviceDescription.locationResponseHeaders) + { + NSString *commandPath = [self.serviceDescription.locationResponseHeaders objectForKey:@"Application-URL"]; + self.serviceDescription.commandURL = [NSURL URLWithString:commandPath]; + } + + [self probeForAppSupport]; +} + +- (void) updateCapabilities +{ + NSArray *capabilities = @[ + kLauncherApp, + kLauncherAppParams, + kLauncherAppClose, + kLauncherAppState + ]; + + [self setCapabilities:capabilities]; +} + +- (BOOL) isConnectable +{ + return YES; +} + +- (void) connect +{ +// NSString *targetPath = [NSString stringWithFormat:@"http://%@:%@/", self.serviceDescription.address, @(self.serviceDescription.port)]; +// NSURL *targetURL = [NSURL URLWithString:targetPath]; + + _serviceReachability = [DeviceServiceReachability reachabilityWithTargetURL:self.serviceDescription.commandURL]; + _serviceReachability.delegate = self; + [_serviceReachability start]; + + self.connected = YES; + + if (self.delegate && [self.delegate respondsToSelector:@selector(deviceServiceConnectionSuccess:)]) + dispatch_on_main(^{ [self.delegate deviceServiceConnectionSuccess:self]; }); +} + +- (void) disconnect +{ + self.connected = NO; + + [_serviceReachability stop]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(deviceService:disconnectedWithError:)]) + dispatch_on_main(^{ [self.delegate deviceService:self disconnectedWithError:nil]; }); +} + +- (void) didLoseReachability:(DeviceServiceReachability *)reachability +{ + if (self.connected) + [self disconnect]; + else + [_serviceReachability stop]; +} + +- (void) probeForAppSupport +{ + [registeredApps enumerateObjectsUsingBlock:^(NSString *appName, NSUInteger idx, BOOL *stop) + { + [self hasApplication:appName success:^(id responseObject) + { + NSString *capability = [NSString stringWithFormat:@"Launcher.%@", appName]; + NSString *capabilityParams = [NSString stringWithFormat:@"Launcher.%@.Params", appName]; + + [self addCapabilities:@[capability, capabilityParams]]; + } failure:nil]; + }]; +} + +#pragma mark - ServiceCommandDelegate + +- (int) sendCommand:(ServiceCommand *)command withPayload:(id)payload toURL:(NSURL *)URL +{ + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; + [request setCachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData]; + [request setTimeoutInterval:6]; + + if (payload || [command.HTTPMethod isEqualToString:@"POST"]) + { + [request setHTTPMethod:@"POST"]; + + if (payload) + { + NSData *payloadData; + + if ([payload isKindOfClass:[NSString class]]) + { + NSString *payloadString = (NSString *)payload; + payloadData = [payloadString dataUsingEncoding:NSUTF8StringEncoding]; + } else if ([payload isKindOfClass:[NSDictionary class]]) + payloadData = [NSJSONSerialization dataWithJSONObject:payload options:0 error:nil]; + + if (payloadData == nil) + { + if (command.callbackError) + command.callbackError([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Unknown error preparing message to send"]); + + return -1; + } + + [request addValue:[NSString stringWithFormat:@"%i", (unsigned int) [payloadData length]] forHTTPHeaderField:@"Content-Length"]; + [request addValue:@"text/plain;charset=\"utf-8\"" forHTTPHeaderField:@"Content-Type"]; + [request setHTTPBody:payloadData]; + + DLog(@"[OUT] : %@ \n %@", [request allHTTPHeaderFields], payload); + } else + { + [request addValue:@"0" forHTTPHeaderField:@"Content-Length"]; + } + } else + { + [request setHTTPMethod:command.HTTPMethod]; + [request addValue:@"0" forHTTPHeaderField:@"Content-Length"]; + + DLog(@"[OUT] : %@", [request allHTTPHeaderFields]); + } + + [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) + { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + + DLog(@"[IN] : %@", [httpResponse allHeaderFields]); + + if (connectionError) + { + if (command.callbackError) + dispatch_on_main(^{ command.callbackError(connectionError); }); + } else + { + BOOL statusOK = NO; + NSError *error; + NSString *locationPath; + + switch ([httpResponse statusCode]) + { + case 503: + error = [ConnectError generateErrorWithCode:ConnectStatusCodeTvError andDetails:@"Could not start application"]; + break; + + case 501: + error = [ConnectError generateErrorWithCode:ConnectStatusCodeNotSupported andDetails:@"Was unable to perform the requested action, not supported"]; + break; + + case 413: + error = [ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Message body is too long"]; + break; + + case 404: + error = [ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Could not find requested application"]; + break; + + case 201: // CREATED: application launch success + statusOK = YES; + locationPath = [httpResponse.allHeaderFields objectForKey:@"Location"]; + break; + + case 206: // PARTIAL CONTENT: not listed in DIAL spec, but don't want to exclude successful 2xx response code + case 205: // RESET CONTENT: not listed in DIAL spec, but don't want to exclude successful 2xx response code + case 204: // NO CONTENT: not listed in DIAL spec, but don't want to exclude successful 2xx response code + case 203: // NON-AUTHORITATIVE INFORMATION: not listed in DIAL spec, but don't want to exclude successful 2xx response code + case 202: // ACCEPTED: not listed in DIAL spec, but don't want to exclude successful 2xx response code + case 200: // OK: command success + statusOK = YES; + break; + + default: + error = [ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"An unknown error occurred"]; + } + + if (statusOK) + { + NSError *xmlError; + NSDictionary *responseXML = [CTXMLReader dictionaryForXMLData:data error:&xmlError]; + + DLog(@"[IN] : %@", responseXML); + + if (xmlError) + { + if (command.callbackError) + command.callbackError(xmlError); + } else + { + if (command.callbackComplete) + { + if (locationPath) + dispatch_on_main(^{ command.callbackComplete(locationPath); }); + else + dispatch_on_main(^{ command.callbackComplete(responseXML); }); + } + } + } else + { + if (command.callbackError) + command.callbackError(error); + } + } + }]; + + // TODO: need to implement callIds in here + return 0; +} + +#pragma mark - Launcher + +- (id )launcher +{ + return self; +} + +- (CapabilityPriorityLevel) launcherPriority +{ + return CapabilityPriorityLevelNormal; +} + +- (void)launchApp:(NSString *)appId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self launchApplication:appId withParams:nil success:success failure:failure]; +} + +- (void)launchApplication:(NSString *)appId withParams:(NSDictionary *)params success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + if (!appId || [appId isEqualToString:@""]) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"Must provide a valid app id"]); + + return; + } + + AppInfo *appInfo = [AppInfo appInfoForId:appId]; + appInfo.name = appId; + + [self launchAppWithInfo:appInfo params:params success:success failure:failure]; +} + +- (void)launchAppWithInfo:(AppInfo *)appInfo success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self launchAppWithInfo:appInfo params:nil success:success failure:failure]; +} + +- (void)launchAppWithInfo:(AppInfo *)appInfo params:(NSDictionary *)params success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + if (!appInfo || !appInfo.id) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"Must provide a valid AppInfo object"]); + + return; + } + + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:appInfo.id]; + + [self getAppState:launchSession success:^(BOOL running, BOOL visible) + { + if (running) + { + [self getApplicationInfo:launchSession.appId success:^(id responseObject) + { + NSString *resourceName = [[[responseObject objectForKey:@"service"] objectForKey:@"link"] objectForKey:@"text"]; + + if (!resourceName) + resourceName = [[[responseObject objectForKey:@"service"] objectForKey:@"atom:link"] objectForKey:@"rel"]; + + [self launchApplicationWithInfo:appInfo params:params resourceName:resourceName success:success failure:failure]; + } failure:failure]; + } else + { + [self launchApplicationWithInfo:appInfo params:params resourceName:nil success:success failure:failure]; + } + + } failure:failure]; +} + +- (void) launchAppStore:(NSString *)appId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)launchBrowser:(NSURL *)target success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)launchHulu:(NSString *)contentId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)launchNetflix:(NSString *)contentId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + NSDictionary *params; + + if (contentId && ![contentId isEqualToString:@""]) + params = @{ @"v" : contentId }; // TODO: verify this works + + AppInfo *appInfo = [AppInfo appInfoForId:@"Netflix"]; + appInfo.name = appInfo.id; + + [self.launcher launchAppWithInfo:appInfo params:params success:success failure:failure]; +} + +- (void)launchYouTube:(NSString *)contentId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self.launcher launchYouTube:contentId startTime:0.0 success:success failure:failure]; +} + +- (void) launchYouTube:(NSString *)contentId startTime:(float)startTime success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *params; + + if (contentId && contentId.length > 0) { + if (startTime < 0.0) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"Start time may not be negative"]); + + return; + } + + // YouTube on some platforms requires a pairing code, which may be a random string + NSString *pairingCode = [[CTGuid randomGuid] stringValue]; + + params = [NSString stringWithFormat:@"pairingCode=%@&v=%@&t=%.1f", pairingCode, contentId, startTime]; + } + + AppInfo *appInfo = [AppInfo appInfoForId:@"YouTube"]; + appInfo.name = appInfo.id; + + [self.launcher launchAppWithInfo:appInfo params:(id)params success:^(LaunchSession *launchSession) + { + if (success) + success(launchSession); + } failure:^(NSError *error) + { + if (failure) + failure(error); + }]; +} + +- (ServiceSubscription *)subscribeRunningAppWithSuccess:(AppInfoSuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +- (void)getAppState:(LaunchSession *)launchSession success:(AppStateSuccessBlock)success failure:(FailureBlock)failure +{ + [self getApplicationInfo:launchSession.appId success:^(id responseObject) + { + NSString *state = [[[responseObject objectForKey:@"service"] objectForKey:@"state"] objectForKey:@"text"]; + + BOOL running = [state isEqualToString:@"running"]; + BOOL visible = [state isEqualToString:@"running"]; + + if (success) + success(running, visible); + } failure:failure]; +} + +- (ServiceSubscription *)subscribeAppState:(LaunchSession *)launchSession success:(AppStateSuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +- (void)closeApp:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (!launchSession || !launchSession.sessionId) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"Must provide a valid launch session"]); + + return; + } + + NSString *commandPath = [NSString stringWithFormat:@"http://%@:%@", self.serviceDescription.commandURL.host, self.serviceDescription.commandURL.port]; + if ([launchSession.sessionId hasPrefix:@"http://"] || [launchSession.sessionId hasPrefix:@"https://"]) + commandPath = launchSession.sessionId;//chromecast returns full url + else + commandPath = [commandPath stringByAppendingPathComponent:launchSession.sessionId]; + NSURL *commandURL = [NSURL URLWithString:commandPath]; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self target:commandURL payload:nil]; + command.HTTPMethod = @"DELETE"; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)getAppListWithSuccess:(AppListSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)getRunningAppWithSuccess:(AppInfoSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +#pragma mark - Helper methods + +- (void) hasApplication:(NSString *)appId success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (!appId || [appId isEqualToString:@""]) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You must provide a valid app id"]); + + return; + } + + NSString *commandPath = self.serviceDescription.commandURL.absoluteString; + commandPath = [commandPath stringByAppendingPathComponent:appId]; + + NSURL *commandURL = [NSURL URLWithString:commandPath]; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self target:commandURL payload:nil]; + command.HTTPMethod = @"GET"; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void) getApplicationInfo:(NSString *)appId success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (!appId || [appId isEqualToString:@""]) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You must provide a valid app id"]); + + return; + } + + NSString *commandPath = [NSString pathWithComponents:@[ + self.serviceDescription.commandURL.absoluteString, + appId + ]]; + + NSURL *commandURL = [NSURL URLWithString:commandPath]; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self target:commandURL payload:nil]; + command.HTTPMethod = @"GET"; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)launchApplicationWithInfo:(AppInfo *)appInfo params:(NSDictionary *)params resourceName:(NSString *)resourceName success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *commandPath; + + if (resourceName && resourceName.length > 0) + commandPath = [NSString pathWithComponents:@[self.serviceDescription.commandURL.absoluteString, appInfo.id, resourceName]]; + else + commandPath = [NSString pathWithComponents:@[self.serviceDescription.commandURL.absoluteString, appInfo.id]]; + + NSURL *statusCommandURL = [NSURL URLWithString:commandPath]; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self target:statusCommandURL payload:params]; + command.HTTPMethod = @"POST"; + command.callbackComplete = ^(NSString *locationPath) + { + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:appInfo.id]; + launchSession.name = appInfo.name; + launchSession.sessionId = locationPath; + launchSession.sessionType = LaunchSessionTypeApp; + launchSession.service = self; + + if (success) + success(launchSession); + }; + command.callbackError = failure; + [command send]; +} + +@end diff --git a/core/Services/DLNAService.h b/core/Services/DLNAService.h new file mode 100644 index 00000000..3f7fe43a --- /dev/null +++ b/core/Services/DLNAService.h @@ -0,0 +1,31 @@ +// +// DLNAService.h +// Connect SDK +// +// Created by Jeremy White on 12/13/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#define kConnectSDKDLNAServiceId @"DLNA" + +#import "DeviceService.h" +#import "MediaControl.h" +#import "MediaPlayer.h" +#import "VolumeControl.h" +#import "PlayListControl.h" + +@interface DLNAService : DeviceService + +@end diff --git a/core/Services/DLNAService.m b/core/Services/DLNAService.m new file mode 100644 index 00000000..1fd84d70 --- /dev/null +++ b/core/Services/DLNAService.m @@ -0,0 +1,1399 @@ +// +// DLNAService.m +// Connect SDK +// +// Created by Jeremy White on 12/13/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DLNAService_Private.h" +#import "ConnectError.h" +#import "CTXMLReader.h" +#import "ConnectUtil.h" +#import "DeviceServiceReachability.h" +#import "DLNAHTTPServer.h" + +#import "NSDictionary+KeyPredicateSearch.h" +#import "NSObject+FeatureNotSupported_Private.h" +#import "NSString+Common.h" +#import "XMLWriter+ConvenienceMethods.h" +#import "SubtitleInfo.h" + +NSString *const kDataFieldName = @"XMLData"; +#define kActionFieldName @"SOAPAction" +#define kSubscriptionTimeoutSeconds 300 + + +static NSString *const kAVTransportNamespace = @"urn:schemas-upnp-org:service:AVTransport:1"; +static NSString *const kRenderingControlNamespace = @"urn:schemas-upnp-org:service:RenderingControl:1"; +static NSString *const kSecSubtitleNamespace = @"http://www.sec.co.kr/"; +static NSString *const kPVSubtitleNamespace = @"http://www.pv.com/pvns/"; + +static NSString *const kDIDLLiteNamespace = @"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"; +static NSString *const kUPNPNamespace = @"urn:schemas-upnp-org:metadata-1-0/upnp/"; +static NSString *const kDCNamespace = @"http://purl.org/dc/elements/1.1/"; + +static NSString *const kDefaultSubtitleMimeType = @"text/srt"; +static NSString *const kDefaultSubtitleSubtype = @"srt"; + +static const NSInteger kValueNotFound = -1; + + +@interface DLNAService() +{ +// NSOperationQueue *_commandQueue; + DLNAHTTPServer *_httpServer; + NSMutableDictionary *_httpServerSessionIds; + + DeviceServiceReachability *_serviceReachability; +} + +@end + +@implementation DLNAService + +@synthesize serviceDescription = _serviceDescription; + +- (void) updateCapabilities +{ + NSArray *capabilities = @[ + kMediaPlayerDisplayImage, + kMediaPlayerPlayVideo, + kMediaPlayerPlayAudio, + kMediaPlayerPlayPlaylist, + kMediaPlayerClose, + kMediaPlayerMetaDataTitle, + kMediaPlayerMetaDataMimeType, + kMediaControlPlay, + kMediaControlPause, + kMediaControlStop, + kMediaControlSeek, + kMediaControlPosition, + kMediaControlDuration, + kMediaControlPlayState, + kMediaControlPlayStateSubscribe, + kMediaControlMetadata, + kMediaControlMetadataSubscribe, + kMediaPlayerSubtitleSRT, + kPlayListControlNext, + kPlayListControlPrevious, + kPlayListControlJumpTrack, + ]; + + capabilities = [capabilities arrayByAddingObjectsFromArray:kVolumeControlCapabilities]; + + [self setCapabilities:capabilities]; +} + ++ (NSDictionary *) discoveryParameters +{ + return @{ + @"serviceId": kConnectSDKDLNAServiceId, + @"ssdp":@{ + @"filter":@"urn:schemas-upnp-org:device:MediaRenderer:1", + @"requiredServices":@[ + @"urn:schemas-upnp-org:service:AVTransport:1", + @"urn:schemas-upnp-org:service:RenderingControl:1" + ] + } + }; +} + +- (id) initWithJSONObject:(NSDictionary *)dict +{ + // not supported + return nil; +} + +//- (NSDictionary *) toJSONObject +//{ +// // not supported +// return nil; +//} + +#pragma mark - Getters & Setters + +/// Returns the set delegate property value or self. +- (id)serviceCommandDelegate { + return _serviceCommandDelegate ?: self; +} + +#pragma mark - Helper methods + +//- (NSOperationQueue *)commandQueue +//{ +// if (_commandQueue == nil) +// { +// _commandQueue = [[NSOperationQueue alloc] init]; +// } +// +// return _commandQueue; +//} + +- (void)setServiceDescription:(ServiceDescription *)serviceDescription +{ + _serviceDescription = serviceDescription; + + if (_serviceDescription.locationXML) + { + [self updateControlURLs]; + + if (!_httpServer) + _httpServer = [self createDLNAHTTPServer]; + } else + { + _avTransportControlURL = nil; + _renderingControlControlURL = nil; + } +} + +- (void) updateControlURLs +{ + NSArray *serviceList = self.serviceDescription.serviceList; + + [serviceList enumerateObjectsUsingBlock:^(id service, NSUInteger idx, BOOL *stop) { + NSString *serviceName = service[@"serviceId"][@"text"]; + NSString *controlPath = service[@"controlURL"][@"text"]; + NSString *eventPath = service[@"eventSubURL"][@"text"]; + NSURL *controlURL = [self serviceURLForPath:controlPath]; + NSURL *eventURL = [self serviceURLForPath:eventPath]; + + if ([serviceName rangeOfString:@":AVTransport"].location != NSNotFound) + { + _avTransportControlURL = controlURL; + _avTransportEventURL = eventURL; + } else if ([serviceName rangeOfString:@":RenderingControl"].location != NSNotFound) + { + _renderingControlControlURL = controlURL; + _renderingControlEventURL = eventURL; + } + }]; +} + +- (BOOL) isConnectable +{ + return YES; +} + +- (void) connect +{ +// NSString *targetPath = [NSString stringWithFormat:@"http://%@:%@/", self.serviceDescription.address, @(self.serviceDescription.port)]; +// NSURL *targetURL = [NSURL URLWithString:targetPath]; + + _serviceReachability = [self createDeviceServiceReachabilityWithTargetURL:_avTransportControlURL]; + _serviceReachability.delegate = self; + [_serviceReachability start]; + + self.connected = YES; + + [_httpServer start]; + [self subscribeServices]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(deviceServiceConnectionSuccess:)]) + dispatch_on_main(^{ [self.delegate deviceServiceConnectionSuccess:self]; }); +} + +- (void) disconnect +{ + self.connected = NO; + + [self unsubscribeServices]; + [_httpServer stop]; + [_serviceReachability stop]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(deviceService:disconnectedWithError:)]) + dispatch_on_main(^{ [self.delegate deviceService:self disconnectedWithError:nil]; }); +} + +- (void) didLoseReachability:(DeviceServiceReachability *)reachability +{ + if (self.connected) + [self disconnect]; + else + [_serviceReachability stop]; +} + +/// Builds a request XML for the given command name. Prepares the outer, common +/// XML and the @c writerBlock is called to add any extra information to the XML. +- (NSString *)commandXMLForCommandName:(NSString *)commandName + commandNamespace:(NSString *)namespace + andWriterBlock:(void (^)(XMLWriter *writer))writerBlock { + NSParameterAssert(commandName); + + XMLWriter *writer = [XMLWriter new]; + [writer writeStartDocumentWithEncodingAndVersion:@"UTF-8" version:@"1.0"]; + + static NSString *const kSOAPNamespace = @"http://schemas.xmlsoap.org/soap/envelope/"; + + [writer setPrefix:@"s" namespaceURI:kSOAPNamespace]; + [writer setPrefix:@"u" namespaceURI:namespace]; + + [writer writeElement:@"Envelope" withNamespace:kSOAPNamespace andContentsBlock:^(XMLWriter *writer) { + [writer writeAttribute:@"s:encodingStyle" value:@"http://schemas.xmlsoap.org/soap/encoding/"]; + [writer writeElement:@"Body" withNamespace:kSOAPNamespace andContentsBlock:^(XMLWriter *writer) { + [writer writeElement:commandName withNamespace:namespace andContentsBlock:^(XMLWriter *writer) { + [writer writeAttribute:@"xmlns:u" value:namespace]; + [writer writeElement:@"InstanceID" withContents:@"0"]; + + if (writerBlock) { + writerBlock(writer); + } + }]; + }]; + }]; + + return [writer toString]; +} + +#pragma mark - + +/// Parses the DLNA notification and returns the value for the given key in the +/// specified channel. +- (NSInteger)valueForVolumeKey:(NSString *)key + atChannel:(NSString *)channelName + inResponse:(NSDictionary *)responseObject +{ + // this object is expected to be either a map of volume properties or + // an array of maps for different channels + id channelsObject = responseObject[@"Event"][@"InstanceID"][key]; + __block int volume = kValueNotFound; + + NSArray *channels = nil; + if ([channelsObject isKindOfClass:[NSArray class]]) { + channels = channelsObject; + } else if ([channelsObject isKindOfClass:[NSDictionary class]]) { + channels = [NSArray arrayWithObject:channelsObject]; + } else { + DLog(@"Unexpected contents for volume notification (%@ object)", + NSStringFromClass([channelsObject class])); + } + + [channels enumerateObjectsUsingBlock:^(NSDictionary *channel, NSUInteger idx, BOOL *stop) { + if ([channelName isEqualToString:channel[@"channel"]]) + { + volume = [channel[@"val"] intValue]; + *stop = YES; + } + }]; + + return volume; +} + +#pragma mark - ServiceCommandDelegate + +- (int) sendCommand:(ServiceCommand *)command withPayload:(NSDictionary *)payload toURL:(NSURL *)URL +{ + NSString *actionField = [payload objectForKey:kActionFieldName]; + NSString *xml = [payload objectForKey:kDataFieldName]; + + NSData *xmlData = [xml dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; + [request setCachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData]; + [request setTimeoutInterval:30]; + [request addValue:@"text/xml;charset=\"utf-8\"" forHTTPHeaderField:@"Content-Type"]; + [request addValue:[NSString stringWithFormat:@"%i", (unsigned int) [xmlData length]] forHTTPHeaderField:@"Content-Length"]; + [request addValue:actionField forHTTPHeaderField:kActionFieldName]; + [request setHTTPMethod:@"POST"]; + [request setHTTPBody:xmlData]; + + DLog(@"[OUT] : %@ \n %@", [request allHTTPHeaderFields], xml); + + [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) + { + NSError *xmlError; + NSDictionary *dataXML = [CTXMLReader dictionaryForXMLData:data error:&xmlError]; + + DLog(@"[IN] : %@ \n %@", [((NSHTTPURLResponse *)response) allHeaderFields], dataXML); + + if (connectionError) + { + if (command.callbackError) + dispatch_on_main(^{ command.callbackError(connectionError); }); + } else if (xmlError) + { + if (command.callbackError) + dispatch_on_main(^{ command.callbackError([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Could not parse command response"]); }); + } else + { + NSDictionary *upnpFault = [self responseDataFromResponse:dataXML + forMethod:@"Fault"]; + + if (upnpFault) + { + NSString *errorDescription = [[[[upnpFault objectForKey:@"detail"] objectForKeyEndingWithString:@":UPnPError"] objectForKeyEndingWithString:@":errorDescription"] objectForKey:@"text"]; + + if (!errorDescription) + errorDescription = @"Unknown UPnP error"; + + if (command.callbackError) + dispatch_on_main(^{ command.callbackError([ConnectError generateErrorWithCode:ConnectStatusCodeTvError andDetails:errorDescription]); }); + } else + { + if (command.callbackComplete) + dispatch_on_main(^{ command.callbackComplete(dataXML); }); + } + } + }]; + + // TODO: need to implement callIds in here + return 0; +} + +- (int) sendSubscription:(ServiceSubscription *)subscription type:(ServiceSubscriptionType)type payload:(id)payload toURL:(NSURL *)URL withId:(int)callId +{ + if (type == ServiceSubscriptionTypeSubscribe) + { + [_httpServer addSubscription:subscription]; + + if (!_httpServer.isRunning) + { + [_httpServer start]; + [self subscribeServices]; + } + } else + { + [_httpServer removeSubscription:subscription]; + + if (!_httpServer.hasSubscriptions) + { + [self unsubscribeServices]; + [_httpServer stop]; + } + } + + return -1; +} + +#pragma mark - Subscriptions + +- (void) subscribeServices +{ + _httpServerSessionIds = [NSMutableDictionary new]; + + [_serviceDescription.serviceList enumerateObjectsUsingBlock:^(id service, NSUInteger idx, BOOL *stop) { + NSString *serviceId = service[@"serviceId"][@"text"]; + NSString *eventPath = service[@"eventSubURL"][@"text"]; + NSURL *eventSubURL = [self serviceURLForPath:eventPath]; + if ([eventPath hasPrefix:@"/"]) + eventPath = [eventPath substringFromIndex:1]; + + NSString *serverPath = [[_httpServer getHostPath] stringByAppendingString:eventPath]; + serverPath = [NSString stringWithFormat:@"<%@>", serverPath]; + + NSString *timeoutValue = [NSString stringWithFormat:@"Second-%d", kSubscriptionTimeoutSeconds]; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:eventSubURL]; + [request setHTTPMethod:@"SUBSCRIBE"]; + [request setValue:serverPath forHTTPHeaderField:@"CALLBACK"]; + [request setValue:@"upnp:event" forHTTPHeaderField:@"NT"]; + [request setValue:timeoutValue forHTTPHeaderField:@"TIMEOUT"]; + [request setValue:@"close" forHTTPHeaderField:@"Connection"]; + [request setValue:@"0" forHTTPHeaderField:@"Content-Length"]; + [request setValue:@"iOS UPnP/1.1 ConnectSDK" forHTTPHeaderField:@"USER-AGENT"]; + + [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *_response, NSData *data, NSError *connectionError) { + NSHTTPURLResponse *response = (NSHTTPURLResponse *)_response; + + if (connectionError || !response) + return; + + if (response.statusCode == 200) + { + NSString *sessionId = response.allHeaderFields[@"SID"]; + + if (sessionId) + _httpServerSessionIds[serviceId] = sessionId; + + [self performSelector:@selector(resubscribeSubscriptions) withObject:nil afterDelay:kSubscriptionTimeoutSeconds / 2]; + } + }]; + }]; +} + +- (void) resubscribeSubscriptions +{ + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resubscribeSubscriptions) object:nil]; + + [_serviceDescription.serviceList enumerateObjectsUsingBlock:^(id service, NSUInteger idx, BOOL *stop) { + NSString *serviceId = service[@"serviceId"][@"text"]; + NSString *eventPath = service[@"eventSubURL"][@"text"]; + NSURL *eventSubURL = [self serviceURLForPath:eventPath]; + + NSString *timeoutValue = [NSString stringWithFormat:@"Second-%d", kSubscriptionTimeoutSeconds]; + + NSString *sessionId = _httpServerSessionIds[serviceId]; + + if (!sessionId) + return; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:eventSubURL]; + [request setHTTPMethod:@"SUBSCRIBE"]; + [request setValue:timeoutValue forHTTPHeaderField:@"TIMEOUT"]; + [request setValue:sessionId forHTTPHeaderField:@"SID"]; + + [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *_response, NSData *data, NSError *connectionError) { + NSHTTPURLResponse *response = (NSHTTPURLResponse *)_response; + + if (connectionError || !response) + return; + + if (response.statusCode == 200) + { + [self performSelector:@selector(resubscribeSubscriptions) withObject:nil afterDelay:kSubscriptionTimeoutSeconds / 2]; + } + }]; + }]; +} + +- (void) unsubscribeServices +{ + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resubscribeSubscriptions) object:nil]; + + [_serviceDescription.serviceList enumerateObjectsUsingBlock:^(id service, NSUInteger idx, BOOL *stop) { + NSString *serviceId = service[@"serviceId"][@"text"]; + NSString *eventPath = service[@"eventSubURL"][@"text"]; + NSURL *eventSubURL = [self serviceURLForPath:eventPath]; + + NSString *sessionId = _httpServerSessionIds[serviceId]; + + if (!sessionId) + return; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:eventSubURL]; + [request setHTTPMethod:@"UNSUBSCRIBE"]; + [request setValue:sessionId forHTTPHeaderField:@"SID"]; + + [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *_response, NSData *data, NSError *connectionError) { + NSHTTPURLResponse *response = (NSHTTPURLResponse *)_response; + + if (connectionError || !response) + return; + + if (response.statusCode == 200) + { + [_httpServerSessionIds removeObjectForKey:serviceId]; + } + }]; + }]; +} + +- (NSURL*)serviceURLForPath:(NSString *)path{ + if(![path hasPrefix:@"/"]){ + path = [NSString stringWithFormat:@"/%@",path]; + } + NSString *serviceURL = [NSString stringWithFormat:@"http://%@:%@%@", + self.serviceDescription.commandURL.host, + self.serviceDescription.commandURL.port, + path]; + return [NSURL URLWithString:serviceURL]; +} + +#pragma mark - Media Player + +- (id )mediaControl +{ + return self; +} + +- (CapabilityPriorityLevel) mediaControlPriority +{ + return CapabilityPriorityLevelNormal; +} + +- (void)playWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *playXML = [self commandXMLForCommandName:@"Play" + commandNamespace:kAVTransportNamespace + andWriterBlock:^(XMLWriter *writer) { + [writer writeElement:@"Speed" withContents:@"1"]; + }]; + NSDictionary *playPayload = @{kActionFieldName : @"\"urn:schemas-upnp-org:service:AVTransport:1#Play\"", + kDataFieldName : playXML}; + + ServiceCommand *playCommand = [[ServiceCommand alloc] initWithDelegate:self.serviceCommandDelegate target:_avTransportControlURL payload:playPayload]; + playCommand.callbackComplete = ^(NSDictionary *responseDic){ + if (success) + success(nil); + }; + playCommand.callbackError = failure; + [playCommand send]; +} + +- (void)pauseWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *pauseXML = [self commandXMLForCommandName:@"Pause" + commandNamespace:kAVTransportNamespace + andWriterBlock:nil]; + NSDictionary *pausePayload = @{kActionFieldName : @"\"urn:schemas-upnp-org:service:AVTransport:1#Pause\"", + kDataFieldName : pauseXML}; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.serviceCommandDelegate target:_avTransportControlURL payload:pausePayload]; + command.callbackComplete = ^(NSDictionary *responseDic){ + if (success) + success(nil); + }; + command.callbackError = failure; + [command send]; +} + +- (void)stopWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *stopXML = [self commandXMLForCommandName:@"Stop" + commandNamespace:kAVTransportNamespace + andWriterBlock:nil]; + NSDictionary *stopPayload = @{kActionFieldName : @"\"urn:schemas-upnp-org:service:AVTransport:1#Stop\"", + kDataFieldName : stopXML}; + + ServiceCommand *stopCommand = [[ServiceCommand alloc] initWithDelegate:self.serviceCommandDelegate target:_avTransportControlURL payload:stopPayload]; + stopCommand.callbackComplete = ^(NSDictionary *responseDic){ + if (success) + success(nil); + }; + stopCommand.callbackError = failure; + [stopCommand send]; +} + +- (void)rewindWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)fastForwardWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)seek:(NSTimeInterval)position success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *timeString = [self stringForTime:position]; + NSString *seekXML = [self commandXMLForCommandName:@"Seek" + commandNamespace:kAVTransportNamespace + andWriterBlock:^(XMLWriter *writer) { + [writer writeElement:@"Unit" withContents:@"REL_TIME"]; + [writer writeElement:@"Target" withContents:timeString]; + }]; + NSDictionary *seekPayload = @{kActionFieldName : @"\"urn:schemas-upnp-org:service:AVTransport:1#Seek\"", + kDataFieldName : seekXML}; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.serviceCommandDelegate target:_avTransportControlURL payload:seekPayload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)getPlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *getPlayStateXML = [self commandXMLForCommandName:@"GetTransportInfo" + commandNamespace:kAVTransportNamespace + andWriterBlock:nil]; + NSDictionary *getPlayStatePayload = @{kActionFieldName : @"\"urn:schemas-upnp-org:service:AVTransport:1#GetTransportInfo\"", + kDataFieldName : getPlayStateXML}; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.serviceCommandDelegate target:_avTransportControlURL payload:getPlayStatePayload]; + command.callbackComplete = ^(NSDictionary *responseObject) + { + NSDictionary *response = [self responseDataFromResponse:responseObject + forMethod:@"GetTransportInfoResponse"]; + NSString *transportState = [[[response objectForKey:@"CurrentTransportState"] objectForKey:@"text"] uppercaseString]; + + MediaControlPlayState playState = MediaControlPlayStateUnknown; + + if ([transportState isEqualToString:@"STOPPED"]) + playState = MediaControlPlayStateFinished; + else if ([transportState isEqualToString:@"PAUSED_PLAYBACK"]) + playState = MediaControlPlayStatePaused; + else if ([transportState isEqualToString:@"PAUSED_RECORDING"]) + playState = MediaControlPlayStateUnknown; + else if ([transportState isEqualToString:@"PLAYING"]) + playState = MediaControlPlayStatePlaying; + else if ([transportState isEqualToString:@"RECORDING"]) + playState = MediaControlPlayStateUnknown; + else if ([transportState isEqualToString:@"TRANSITIONING"]) + playState = MediaControlPlayStateIdle; + else if ([transportState isEqualToString:@"NO_MEDIA_PRESENT"]) + playState = MediaControlPlayStateIdle; + + if (success) + success(playState); + }; + command.callbackError = failure; + [command send]; +} + +- (void)getDurationWithSuccess:(MediaDurationSuccessBlock)success failure:(FailureBlock)failure +{ + [self getPositionInfoWithSuccess:^(NSDictionary *responseObject) + { + NSDictionary *response = [self responseDataFromResponse:responseObject + forMethod:@"GetPositionInfoResponse"]; + NSString *durationString = [[response objectForKey:@"TrackDuration"] objectForKey:@"text"]; + NSTimeInterval duration = [self timeForString:durationString]; + if (success) + success(duration); + } failure:failure]; +} + +- (void)getPositionWithSuccess:(MediaPositionSuccessBlock)success failure:(FailureBlock)failure +{ + [self getPositionInfoWithSuccess:^(NSDictionary *responseObject) + { + NSDictionary *response = [self responseDataFromResponse:responseObject + forMethod:@"GetPositionInfoResponse"]; + NSString *currentTimeString = [[response objectForKey:@"RelTime"] objectForKey:@"text"]; + NSTimeInterval currentTime = [self timeForString:currentTimeString]; + + if (success) + success(currentTime); + } failure:failure]; +} + +- (ServiceSubscription *)subscribePlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + [self getPlayStateWithSuccess:success failure:failure]; + + SuccessBlock successBlock = ^(NSDictionary *responseObject) { + + NSDictionary *response = responseObject[@"Event"][@"InstanceID"]; + NSString *transportState = response[@"TransportState"][@"val"]; + + MediaControlPlayState playState = MediaControlPlayStateUnknown; + + if ([transportState isEqualToString:@"STOPPED"]) + playState = MediaControlPlayStateFinished; + else if ([transportState isEqualToString:@"PAUSED_PLAYBACK"]) + playState = MediaControlPlayStatePaused; + else if ([transportState isEqualToString:@"PAUSED_RECORDING"]) + playState = MediaControlPlayStateUnknown; + else if ([transportState isEqualToString:@"PLAYING"]) + playState = MediaControlPlayStatePlaying; + else if ([transportState isEqualToString:@"RECORDING"]) + playState = MediaControlPlayStateUnknown; + else if ([transportState isEqualToString:@"TRANSITIONING"]) + playState = MediaControlPlayStateIdle; + else if ([transportState isEqualToString:@"NO_MEDIA_PRESENT"]) + playState = MediaControlPlayStateIdle; + + if (success && transportState) + success(playState); + }; + + ServiceSubscription *subscription = [ServiceSubscription subscriptionWithDelegate:self target:_avTransportEventURL payload:nil callId:-1]; + [subscription addSuccess:successBlock]; + [subscription addFailure:failure]; + [subscription subscribe]; + return subscription; +} + +- (void) getPositionInfoWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *getPositionInfoXML = [self commandXMLForCommandName:@"GetPositionInfo" + commandNamespace:kAVTransportNamespace + andWriterBlock:nil]; + NSDictionary *getPositionInfoPayload = @{kActionFieldName : @"\"urn:schemas-upnp-org:service:AVTransport:1#GetPositionInfo\"", + kDataFieldName : getPositionInfoXML}; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.serviceCommandDelegate target:_avTransportControlURL payload:getPositionInfoPayload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)getMediaMetaDataWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self getPositionInfoWithSuccess:^(NSDictionary *responseObject) + { + NSDictionary *response = [self responseDataFromResponse:responseObject + forMethod:@"GetPositionInfoResponse"]; + NSString *metaDataString = [[response objectForKey:@"TrackMetaData"] objectForKey:@"text"]; + if(metaDataString){ + if (success) + success([self parseMetadataDictionaryFromXMLString:metaDataString]); + } + } failure:failure]; +} + +- (ServiceSubscription *)subscribeMediaInfoWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self getMediaMetaDataWithSuccess:success failure:failure]; + + SuccessBlock successBlock = ^(NSDictionary *responseObject) { + + NSDictionary *response = responseObject[@"Event"][@"InstanceID"]; + NSString *currentTrackMetaData = response[@"CurrentTrackMetaData"][@"val"]; + + if(currentTrackMetaData){ + if (success) + success([self parseMetadataDictionaryFromXMLString:currentTrackMetaData]); + } + }; + + ServiceSubscription *subscription = [ServiceSubscription subscriptionWithDelegate:self target:_avTransportEventURL payload:nil callId:-1]; + [subscription addSuccess:successBlock]; + [subscription addFailure:failure]; + [subscription subscribe]; + return subscription; +} + +- (NSTimeInterval) timeForString:(NSString *)timeString +{ + if (!timeString || [timeString isEqualToString:@""]) + return 0; + + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateFormat:@"HH:m:ss"]; + + NSDate *time = [formatter dateFromString:timeString]; + NSDate *midnight = [formatter dateFromString:@"00:00:00"]; + + NSTimeInterval timeInterval = [time timeIntervalSinceDate:midnight]; + + if (timeInterval < 0) + timeInterval = 0; + + return timeInterval; +} + +- (NSString *) stringForTime:(NSTimeInterval)timeInterval +{ + int time = (int) round(timeInterval); + + int second = time % 60; + int minute = (time / 60) % 60; + int hour = time / 3600; + + NSString *timeString = [NSString stringWithFormat:@"%02d:%02d:%02d", hour, minute, second]; + + return timeString; +} + +- (NSDictionary *)parseMetadataDictionaryFromXMLString:(NSString *)metadataXML { + NSError *xmlError; + NSDictionary *mediaMetadataResponse = [[[CTXMLReader dictionaryForXMLString:metadataXML error:&xmlError] objectForKey:@"DIDL-Lite"] objectForKey:@"item"]; + // FIXME: check for XML errors + + NSMutableDictionary *mediaMetaData = [NSMutableDictionary dictionary]; + + if([mediaMetadataResponse objectForKey:@"dc:title"]) + [mediaMetaData setObject:[[mediaMetadataResponse objectForKey:@"dc:title"] objectForKey:@"text"] forKey:@"title"]; + + if([mediaMetadataResponse objectForKey:@"r:albumArtist"]) + [mediaMetaData setObject:[[mediaMetadataResponse objectForKey:@"r:albumArtist"] objectForKey:@"text"] forKey:@"subtitle"]; + + if([mediaMetadataResponse objectForKey:@"dc:description"]) + [mediaMetaData setObject:[[mediaMetadataResponse objectForKey:@"dc:description"] objectForKey:@"text"] forKey:@"subtitle"]; + + if([mediaMetadataResponse objectForKey:@"upnp:albumArtURI"]){ + NSString *imageURL = [[mediaMetadataResponse objectForKey:@"upnp:albumArtURI"] objectForKey:@"text"]; + if(![self isValidUrl:imageURL]){ + imageURL = [self serviceURLForPath:imageURL].absoluteString; + } + [mediaMetaData setObject:imageURL forKey:@"iconURL"]; + } + + return mediaMetaData; +} + +//Checks if the url provided is valid or not +- (BOOL)isValidUrl:(NSString *)urlString +{ + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]; + return [NSURLConnection canHandleRequest:request]; +} + +/// Returns a dictionary for the specified method in the given response object. +- (NSDictionary *)responseDataFromResponse:(NSDictionary *)responseObject + forMethod:(NSString *)method { + NSDictionary *envelopeObject = [responseObject objectForKeyEndingWithString:@":Envelope"]; + NSDictionary *bodyObject = [envelopeObject objectForKeyEndingWithString:@":Body"]; + NSDictionary *responseData = [bodyObject objectForKeyEndingWithString: + [@":" stringByAppendingString:method]]; + + return responseData; +} + +#pragma mark - Media Player + +- (id )mediaPlayer +{ + return self; +} + +- (CapabilityPriorityLevel) mediaPlayerPriority +{ + return CapabilityPriorityLevelNormal; +} + +- (void)displayImage:(NSURL *)imageURL iconURL:(NSURL *)iconURL title:(NSString *)title description:(NSString *)description mimeType:(NSString *)mimeType success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:imageURL mimeType:mimeType]; + mediaInfo.title = title; + mediaInfo.description = description; + ImageInfo *imageInfo = [[ImageInfo alloc] initWithURL:iconURL type:ImageTypeThumb]; + [mediaInfo addImage:imageInfo]; + + [self displayImageWithMediaInfo:mediaInfo success:^(MediaLaunchObject *mediaLanchObject) { + success(mediaLanchObject.session,mediaLanchObject.mediaControl); + } failure:failure]; +} + +- (void) displayImage:(MediaInfo *)mediaInfo + success:(MediaPlayerDisplaySuccessBlock)success + failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + + [self displayImage:mediaInfo.url iconURL:iconURL title:mediaInfo.title description:mediaInfo.description mimeType:mediaInfo.mimeType success:success failure:failure]; +} + +- (void) displayImageWithMediaInfo:(MediaInfo *)mediaInfo + success:(MediaPlayerSuccessBlock)success + failure:(FailureBlock)failure +{ + NSString *mimeType = mediaInfo.mimeType ?: @""; + NSString *mediaInfoURLString = mediaInfo.url.absoluteString ?: @""; + + NSString *metadataXML = ({ + XMLWriter *writer = [XMLWriter new]; + + [writer setPrefix:@"upnp" namespaceURI:kUPNPNamespace]; + [writer setPrefix:@"dc" namespaceURI:kDCNamespace]; + + [writer writeElement:@"DIDL-Lite" withContentsBlock:^(XMLWriter *writer) { + [writer writeAttribute:@"xmlns" value:kDIDLLiteNamespace]; + [writer writeElement:@"item" withContentsBlock:^(XMLWriter *writer) { + [writer writeAttributes:@{@"id": @"1000", + @"parentID": @"0", + @"restricted": @"0"}]; + + if (mediaInfo.title) { + [writer writeElement:@"title" withNamespace:kDCNamespace andContents:mediaInfo.title]; + } + + [writer writeElement:@"res" withContentsBlock:^(XMLWriter *writer) { + NSString *value = [NSString stringWithFormat: + @"http-get:*:%@:DLNA.ORG_OP=01", + mimeType]; + [writer writeAttribute:@"protocolInfo" value:value]; + [writer writeCharacters:mediaInfoURLString]; + }]; + + [writer writeElement:@"class" withNamespace:kUPNPNamespace andContents:@"object.item.imageItem"]; + }]; + }]; + + [writer toString]; + }); + + NSString *setURLXML = [self commandXMLForCommandName:@"SetAVTransportURI" + commandNamespace:kAVTransportNamespace + andWriterBlock:^(XMLWriter *writer) { + [writer writeElement:@"CurrentURI" withContents:mediaInfoURLString]; + [writer writeElement:@"CurrentURIMetaData" withContents:[metadataXML orEmpty]]; + }]; + NSDictionary *setURLPayload = @{kActionFieldName : @"\"urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI\"", + kDataFieldName : setURLXML}; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.serviceCommandDelegate target:_avTransportControlURL payload:setURLPayload]; + command.callbackComplete = ^(NSDictionary *responseDic) + { + [self playWithSuccess:^(id responseObject) { + if (success) + { + LaunchSession *launchSession = [LaunchSession new]; + launchSession.sessionType = LaunchSessionTypeMedia; + launchSession.service = self; + MediaLaunchObject *launchObject = [[MediaLaunchObject alloc] initWithLaunchSession:launchSession andMediaControl:self.mediaControl andPlayListControl:self.playListControl]; + success(launchObject); + } + } failure:failure]; + }; + + command.callbackError = failure; + [command send]; +} + +- (void) playMedia:(NSURL *)mediaURL iconURL:(NSURL *)iconURL title:(NSString *)title description:(NSString *)description mimeType:(NSString *)mimeType shouldLoop:(BOOL)shouldLoop success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:mediaURL mimeType:mimeType]; + mediaInfo.title = title; + mediaInfo.description = description; + ImageInfo *imageInfo = [[ImageInfo alloc] initWithURL:iconURL type:ImageTypeThumb]; + [mediaInfo addImage:imageInfo]; + + [self playMediaWithMediaInfo:mediaInfo shouldLoop:shouldLoop success:^(MediaLaunchObject *mediaLanchObject) { + success(mediaLanchObject.session,mediaLanchObject.mediaControl); + } failure:failure]; +} + +- (void) playMedia:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + [self playMedia:mediaInfo.url iconURL:iconURL title:mediaInfo.title description:mediaInfo.description mimeType:mediaInfo.mimeType shouldLoop:shouldLoop success:success failure:failure]; +} + +- (void) playMediaWithMediaInfo:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + NSError *error; + NSString *metadataXML = [self playMediaMetadataXMLForMediaInfo:mediaInfo + error:&error]; + if (error) { + if (failure) { + failure(error); + } + + return; + } + + NSString *mediaInfoURLString = mediaInfo.url.absoluteString ?: @""; + NSString *setURLXML = [self commandXMLForCommandName:@"SetAVTransportURI" + commandNamespace:kAVTransportNamespace + andWriterBlock:^(XMLWriter *writer) { + [writer writeElement:@"CurrentURI" + withContents:mediaInfoURLString]; + [writer writeElement:@"CurrentURIMetaData" + withContents:[metadataXML orEmpty]]; + }]; + NSDictionary *setURLPayload = @{kActionFieldName : @"\"urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI\"", + kDataFieldName : setURLXML}; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.serviceCommandDelegate target:_avTransportControlURL payload:setURLPayload]; + command.callbackComplete = ^(NSDictionary *responseDic) + { + [self playWithSuccess:^(id responseObject) { + if (success) + { + LaunchSession *launchSession = [LaunchSession new]; + launchSession.sessionType = LaunchSessionTypeMedia; + launchSession.service = self; + MediaLaunchObject *launchObject = [[MediaLaunchObject alloc] initWithLaunchSession:launchSession andMediaControl:self.mediaControl andPlayListControl:self.playListControl]; + success(launchObject); + } + } failure:failure]; + }; + + command.callbackError = failure; + [command send]; + +} + +- (void)closeMedia:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.mediaControl stopWithSuccess:success failure:failure]; +} + +#pragma mark - Volume + +- (id ) volumeControl +{ + return self; +} + +- (CapabilityPriorityLevel) volumeControlPriority +{ + return CapabilityPriorityLevelNormal; +} + +- (void) volumeUpWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.volumeControl getVolumeWithSuccess:^(float volume) { + if (volume < 1.0) + [self.volumeControl setVolume:(float) (volume + 0.01) success:success failure:failure]; + else + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Volume is already at max"]); + } + } failure:failure]; +} + +- (void) volumeDownWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.volumeControl getVolumeWithSuccess:^(float volume) { + if (volume > 0.0) + [self.volumeControl setVolume:(float) (volume - 0.01) success:success failure:failure]; + else + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Volume is already at 0"]); + } + } failure:failure]; +} + +- (void) getVolumeWithSuccess:(VolumeSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *getVolumeXML = [self commandXMLForCommandName:@"GetVolume" + commandNamespace:kRenderingControlNamespace + andWriterBlock:^(XMLWriter *writer) { + [writer writeElement:@"Channel" withContents:@"Master"]; + }]; + NSDictionary *getVolumePayload = @{kActionFieldName : @"\"urn:schemas-upnp-org:service:RenderingControl:1#GetVolume\"", + kDataFieldName : getVolumeXML}; + + SuccessBlock successBlock = ^(NSDictionary *responseXML) { + int volume = -1; + + volume = [[self responseDataFromResponse:responseXML + forMethod:@"GetVolumeResponse"][@"CurrentVolume"][@"text"] intValue]; + + if (volume == -1) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Could not find volume information in response"]); + } else + { + if (success) + success((float) volume / 100.0f); + } + }; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.serviceCommandDelegate target:_renderingControlControlURL payload:getVolumePayload]; + command.callbackComplete = successBlock; + command.callbackError = failure; + [command send]; +} + +- (void) setVolume:(float)volume success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *targetVolume = [NSString stringWithFormat:@"%d", (int) round(volume * 100)]; + NSString *setVolumeXML = [self commandXMLForCommandName:@"SetVolume" + commandNamespace:kRenderingControlNamespace + andWriterBlock:^(XMLWriter *writer) { + [writer writeElement:@"Channel" withContents:@"Master"]; + [writer writeElement:@"DesiredVolume" withContents:targetVolume]; + }]; + NSDictionary *setVolumePayload = @{kActionFieldName : @"\"urn:schemas-upnp-org:service:RenderingControl:1#SetVolume\"", + kDataFieldName : setVolumeXML}; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.serviceCommandDelegate target:_renderingControlControlURL payload:setVolumePayload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (ServiceSubscription *) subscribeVolumeWithSuccess:(VolumeSuccessBlock)success failure:(FailureBlock)failure +{ + [self.volumeControl getVolumeWithSuccess:success failure:failure]; + + SuccessBlock successBlock = ^(NSDictionary *responseObject) { + const NSInteger masterVolume = [self valueForVolumeKey:@"Volume" + atChannel:@"Master" + inResponse:responseObject]; + + if ((masterVolume != kValueNotFound) && success) { + success((float) masterVolume / 100.0f); + } + }; + + ServiceSubscription *subscription = [ServiceSubscription subscriptionWithDelegate:self.serviceCommandDelegate target:_renderingControlEventURL payload:nil callId:-1]; + [subscription addSuccess:successBlock]; + [subscription addFailure:failure]; + [subscription subscribe]; + return subscription; +} + +- (void) getMuteWithSuccess:(MuteSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *getMuteXML = [self commandXMLForCommandName:@"GetMute" + commandNamespace:kRenderingControlNamespace + andWriterBlock:^(XMLWriter *writer) { + [writer writeElement:@"Channel" withContents:@"Master"]; + }]; + NSDictionary *getMutePayload = @{kActionFieldName : @"\"urn:schemas-upnp-org:service:RenderingControl:1#GetMute\"", + kDataFieldName : getMuteXML}; + + SuccessBlock successBlock = ^(NSDictionary *responseXML) { + int mute = -1; + + mute = [[self responseDataFromResponse:responseXML + forMethod:@"GetMuteResponse"][@"CurrentMute"][@"text"] intValue]; + + if (mute == -1) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Could not find mute information in response"]); + } else + { + if (success) + success((BOOL) mute); + } + }; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.serviceCommandDelegate target:_renderingControlControlURL payload:getMutePayload]; + command.callbackComplete = successBlock; + command.callbackError = failure; + [command send]; +} + +- (void) setMute:(BOOL)mute success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *targetMute = [NSString stringWithFormat:@"%d", mute]; + NSString *setMuteXML = [self commandXMLForCommandName:@"SetMute" + commandNamespace:kRenderingControlNamespace + andWriterBlock:^(XMLWriter *writer) { + [writer writeElement:@"Channel" withContents:@"Master"]; + [writer writeElement:@"DesiredMute" withContents:targetMute]; + }]; + NSDictionary *setMutePayload = @{kActionFieldName : @"\"urn:schemas-upnp-org:service:RenderingControl:1#SetMute\"", + kDataFieldName : setMuteXML}; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.serviceCommandDelegate target:_renderingControlControlURL payload:setMutePayload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (ServiceSubscription *) subscribeMuteWithSuccess:(MuteSuccessBlock)success failure:(FailureBlock)failure +{ + [self.volumeControl getMuteWithSuccess:success failure:failure]; + + SuccessBlock successBlock = ^(NSDictionary *responseObject) { + const NSInteger masterMute = [self valueForVolumeKey:@"Mute" + atChannel:@"Master" + inResponse:responseObject]; + + if ((masterMute != kValueNotFound) && success) { + success((BOOL) masterMute); + } + }; + + ServiceSubscription *subscription = [ServiceSubscription subscriptionWithDelegate:self.serviceCommandDelegate target:_renderingControlEventURL payload:nil callId:-1]; + [subscription addSuccess:successBlock]; + [subscription addFailure:failure]; + [subscription subscribe]; + return subscription; +} + +#pragma mark - Playlist controls + +- (id )playListControl +{ + return self; +} + +- (CapabilityPriorityLevel) playListControlPriority +{ + return CapabilityPriorityLevelNormal; +} + +- (void) playNextWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *nextXML = [self commandXMLForCommandName:@"Next" + commandNamespace:kAVTransportNamespace + andWriterBlock:nil]; + NSDictionary *nextPayload = @{kActionFieldName : @"\"urn:schemas-upnp-org:service:AVTransport:1#Next\"", + kDataFieldName : nextXML}; + + ServiceCommand *nextCommand = [[ServiceCommand alloc] initWithDelegate:self.serviceCommandDelegate target:_avTransportControlURL payload:nextPayload]; + nextCommand.callbackComplete = ^(NSDictionary *responseDic){ + if (success) + success(nil); + }; + nextCommand.callbackError = failure; + [nextCommand send]; +} + +- (void) playPreviousWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *previousXML = [self commandXMLForCommandName:@"Previous" + commandNamespace:kAVTransportNamespace + andWriterBlock:nil]; + NSDictionary *previousPayload = @{kActionFieldName : @"\"urn:schemas-upnp-org:service:AVTransport:1#Previous\"", + kDataFieldName : previousXML}; + + ServiceCommand *previousCommand = [[ServiceCommand alloc] initWithDelegate:self.serviceCommandDelegate target:_avTransportControlURL payload:previousPayload]; + previousCommand.callbackComplete = ^(NSDictionary *responseDic){ + if (success) + success(nil); + }; + previousCommand.callbackError = failure; + [previousCommand send]; +} + +- (void)jumpToTrackWithIndex:(NSInteger)index success:(SuccessBlock)success failure:(FailureBlock)failure +{ + // our index is zero-based, but in DLNA, track numbers start at 1, so + // increase by one + NSString *trackNumberInString = [NSString stringWithFormat:@"%ld", (long)(index + 1)]; + NSString *seekXML = [self commandXMLForCommandName:@"Seek" + commandNamespace:kAVTransportNamespace + andWriterBlock:^(XMLWriter *writer) { + [writer writeElement:@"Unit" withContents:@"TRACK_NR"]; + [writer writeElement:@"Target" withContents:trackNumberInString]; + }]; + NSDictionary *seekPayload = @{kActionFieldName : @"\"urn:schemas-upnp-org:service:AVTransport:1#Seek\"", + kDataFieldName : seekXML}; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.serviceCommandDelegate target:_avTransportControlURL payload:seekPayload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +#pragma mark - Private + +- (DLNAHTTPServer *)createDLNAHTTPServer { + return [DLNAHTTPServer new]; +} + +- (DeviceServiceReachability *)createDeviceServiceReachabilityWithTargetURL:(NSURL *)url { + return [DeviceServiceReachability reachabilityWithTargetURL:url]; +} + +- (BOOL)isValidMimeType:(NSString *)mimeType { + return [mimeType rangeOfString:@"/"].length > 0; +} + +/// Returns a subtype of a MIME type (the part after the slash). The MIME type +/// string must be valid. +- (NSString *)subtypeFromMimeType:(NSString *)mimeType { + NSArray *components = [mimeType componentsSeparatedByString:@"/"]; + return components[1]; +} + +- (NSString *)playMediaMetadataXMLForMediaInfo:(MediaInfo *)mediaInfo + error:(out NSError **)error { + NSURL *iconURL; + if (mediaInfo.images) { + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + + NSArray *mediaElements = [mediaInfo.mimeType componentsSeparatedByString:@"/"]; + NSString *mediaType = mediaElements[0]; + NSString *mediaFormat = mediaElements[1]; + + if (!mediaType || mediaType.length == 0 || !mediaFormat || mediaFormat.length == 0) { + if (error) { + *error = [ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError + andDetails:@"You must provide a valid mimeType (audio/*, video/*, etc)"]; + } + + return nil; + } + + mediaFormat = [mediaFormat isEqualToString:@"mp3"] ? @"mpeg" : mediaFormat; + NSString *mimeType = [NSString stringWithFormat:@"%@/%@", mediaType, mediaFormat]; + NSString *mediaInfoURLString = mediaInfo.url.absoluteString ?: @""; + + XMLWriter *writer = [XMLWriter new]; + + [writer setPrefix:@"upnp" namespaceURI:kUPNPNamespace]; + [writer setPrefix:@"dc" namespaceURI:kDCNamespace]; + [writer setPrefix:@"sec" namespaceURI:kSecSubtitleNamespace]; + + [writer writeElement:@"DIDL-Lite" withContentsBlock:^(XMLWriter *writer) { + [writer writeAttribute:@"xmlns" value:kDIDLLiteNamespace]; + [writer writeElement:@"item" withContentsBlock:^(XMLWriter *writer) { + [writer writeAttributes:@{@"id": @"0", + @"parentID": @"0", + @"restricted": @"0"}]; + + if (mediaInfo.title) { + [writer writeElement:@"title" withNamespace:kDCNamespace andContents:mediaInfo.title]; + } + if (mediaInfo.description) { + [writer writeElement:@"description" withNamespace:kDCNamespace andContents:mediaInfo.description]; + } + + NSString *subtitleURL = mediaInfo.subtitleInfo.url.absoluteString; + NSString *subtitleMimeType = mediaInfo.subtitleInfo.mimeType; + NSString *subtitleType; + if ([self isValidMimeType:subtitleMimeType]) { + subtitleType = [self subtypeFromMimeType:subtitleMimeType]; + } else { + subtitleMimeType = kDefaultSubtitleMimeType; + subtitleType = kDefaultSubtitleSubtype; + } + + [writer writeElement:@"res" withContentsBlock:^(XMLWriter *writer) { + if (mediaInfo.subtitleInfo) { + [self writePVSubtitleAttributesForURL:subtitleURL + andType:subtitleType + toWriter:writer]; + } + + NSString *value = [NSString stringWithFormat: + @"http-get:*:%@:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000", + [mimeType orEmpty]]; + [writer writeAttribute:@"protocolInfo" value:value]; + [writer writeCharacters:mediaInfoURLString]; + }]; + + NSString *iconURLString = iconURL.absoluteString ?: @""; + [writer writeElement:@"albumArtURI" withNamespace:kUPNPNamespace andContents:iconURLString]; + NSString *classItem = [NSString stringWithFormat:@"object.item.%@Item", [mediaType orEmpty]]; + [writer writeElement:@"class" withNamespace:kUPNPNamespace andContents:classItem]; + + if (mediaInfo.subtitleInfo) { + [self writeSubtitleElementsForURL:subtitleURL + mimeType:subtitleMimeType + andType:subtitleType + toWriter:writer]; + } + }]; + }]; + + return [writer toString]; +} + +- (void)writePVSubtitleAttributesForURL:(NSString *)subtitleURL + andType:(NSString *)subtitleType + toWriter:(XMLWriter *)writer { + [writer setPrefix:@"pv" namespaceURI:kPVSubtitleNamespace]; + [writer writeAttributeWithNamespace:kPVSubtitleNamespace + localName:@"subtitleFileUri" + value:subtitleURL]; + [writer writeAttributeWithNamespace:kPVSubtitleNamespace + localName:@"subtitleFileType" + value:subtitleType]; +} + +- (void)writeSubtitleElementsForURL:(NSString *)subtitleURL + mimeType:(NSString *)mimeType + andType:(NSString *)subtitleType + toWriter:(XMLWriter *)writer { + [writer writeElement:@"res" withContentsBlock:^(XMLWriter *writer) { + [writer writeAttribute:@"protocolInfo" value:@"http-get:*:smi/caption"]; + [writer writeCharacters:subtitleURL]; + }]; + [writer writeElement:@"res" withContentsBlock:^(XMLWriter *writer) { + NSString *attrValue = [NSString stringWithFormat:@"http-get:*:%@:*", + mimeType]; + [writer writeAttribute:@"protocolInfo" value:attrValue]; + [writer writeCharacters:subtitleURL]; + }]; + [@[@"CaptionInfo", @"CaptionInfoEx"] enumerateObjectsUsingBlock: + ^(NSString *captionInfoTag, NSUInteger idx, BOOL *stop) { + [writer writeElement:captionInfoTag + withNamespace:kSecSubtitleNamespace + andContentsBlock:^(XMLWriter *writer) { + [writer writeAttributeWithNamespace:kSecSubtitleNamespace + localName:@"type" + value:subtitleType]; + [writer writeCharacters:subtitleURL]; + }]; + }]; +} + +@end diff --git a/core/Services/DLNAService_Private.h b/core/Services/DLNAService_Private.h new file mode 100644 index 00000000..278049ea --- /dev/null +++ b/core/Services/DLNAService_Private.h @@ -0,0 +1,46 @@ +// +// DLNAService_Private.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 11/13/14. +// Copyright (c) 2014 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DLNAService.h" + +extern NSString *const kDataFieldName; + +@class DeviceServiceReachability; +@class DLNAHTTPServer; + +@interface DLNAService () + +@property (nonatomic, strong) id serviceCommandDelegate; + +@property (nonatomic, strong) NSURL *avTransportControlURL; +@property (nonatomic, strong) NSURL *avTransportEventURL; +@property (nonatomic, strong) NSURL *renderingControlControlURL; +@property (nonatomic, strong) NSURL *renderingControlEventURL; + +- (NSURL*)serviceURLForPath:(NSString *)path; +/// Parses and returns a metadata dictionary from the @c metaDataXML string. +- (NSDictionary *)parseMetadataDictionaryFromXMLString:(NSString *)metadataXML; + +/// Creates a new @c DLNAHTTPServer instance. +- (DLNAHTTPServer *)createDLNAHTTPServer; +/// Creates a new @c DeviceServiceReachability instance with the given target URL. +- (DeviceServiceReachability *)createDeviceServiceReachabilityWithTargetURL:(NSURL *)url; + +@end diff --git a/core/Services/DeviceService.h b/core/Services/DeviceService.h new file mode 100644 index 00000000..c6b604e5 --- /dev/null +++ b/core/Services/DeviceService.h @@ -0,0 +1,219 @@ +// +// DeviceService.h +// Connect SDK +// +// Created by Jeremy White on 12/2/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "ServiceDescription.h" +#import "ServiceConfig.h" +#import "ConnectableDeviceDelegate.h" +#import "DeviceServiceDelegate.h" +#import "Capability.h" +#import "LaunchSession.h" + +/*! + * Enumerated value for determining how a DeviceService should handle pairing when attempting to connect. + */ +typedef enum { + /*! DeviceServices will never try to pair with a device */ + DeviceServicePairingLevelOff = 0, + + /*! DeviceServices will try to pair with a device, if needed */ + DeviceServicePairingLevelOn +}DeviceServicePairingLevel; + +/*! + * Enumerated value for determining how a DeviceService should handle pairing when attempting to connect. + */ +typedef enum { + /*! DeviceServices will never try to pair with a device */ + ConnectableDevicePairingLevelOff __attribute__((deprecated)) = DeviceServicePairingLevelOff, + + /*! DeviceServices will try to pair with a device, if needed */ + ConnectableDevicePairingLevelOn __attribute__((deprecated)) = DeviceServicePairingLevelOn +} ConnectableDevicePairingLevel; + +/*! + * ###Overview + * From a high-level perspective, DeviceService completely abstracts the functionality of a particular service/protocol (webOS TV, Netcast TV, Chromecast, Roku, DIAL, etc). + * + * ###In Depth + * DeviceService is an abstract class that is meant to be extended. You shouldn't ever use DeviceService directly, unless extending it to provide support for an additional service/protocol. + * + * Immediately after discovery of a DeviceService, DiscoveryManager will set the DeviceService's delegate to the ConnectableDevice that owns the DeviceService. You should not change the delegate unless you intend to manage the lifecycle of that service. The DeviceService will proxy all of its delegate method calls through the ConnectableDevice's ConnectableDeviceDelegate. + * + * ####Connection & Pairing + * Your ConnectableDevice object will let you know if you need to connect or pair to any services. + * + * ####Capabilities + * All DeviceService objects have a group of capabilities. These capabilities can be implemented by any object, and that object will be returned when you call the DeviceService's capability methods (launcher, mediaPlayer, volumeControl, etc). + */ +@interface DeviceService : NSObject + +/*! + * Delegate object to receive DeviceService status messages. See note in the "In Depth" section about changing the DeviceServiceDelegate. + */ +@property (nonatomic, weak) iddelegate; + +/*! Object containing the discovered information about this DeviceService */ +@property (nonatomic, strong) ServiceDescription *serviceDescription; + +/*! Object containing persistence data about this DeviceService (pairing info, SSL certificates, etc) */ +@property (nonatomic, strong) ServiceConfig *serviceConfig; + +/*! Name of the DeviceService (webOS, Chromecast, etc) */ +@property (nonatomic, strong, readonly) NSString *serviceName; + +/*! + * A dictionary of keys/values that will be used by the DiscoveryProvider used to discover this DeviceService. Some keys that are used are: service name, SSDP filter, etc. + */ ++ (NSDictionary *) discoveryParameters; + +/*! + * Returns an instantiated DeviceService of the proper subclass (CastService, WebOSTVService, etc). + * + * @property _class DeviceService subclass to instantiate + * @property serviceConfig ServiceConfig with configuration data of device (UUID, etc) + */ ++ (DeviceService *)deviceServiceWithClass:(Class)_class serviceConfig:(ServiceConfig *)serviceConfig; + +/*! + * Returns an instantiated DeviceService of the proper subclass (CastService, WebOSTVService, etc). + * + * @property serviceConfig ServiceConfig with configuration data of device (UUID, etc) + */ +- (instancetype) initWithServiceConfig:(ServiceConfig *)serviceConfig; + +// @cond INTERNAL ++ (instancetype) deviceServiceWithJSONObject:(NSDictionary *)dict; +// @endcond + +#pragma mark - Capabilities + +/*! + * An array of capabilities supported by the DeviceService. This array may change based off a number of factors. + * - DiscoveryManager's pairingLevel value + * - Connect SDK framework version + * - First screen device OS version + * - First screen device configuration (apps installed, settings, etc) + * - Physical region + */ +@property (nonatomic, strong) NSArray *capabilities; + +// @cond INTERNAL +- (void) updateCapabilities; +- (void) addCapability:(NSString *)capability; +- (void) addCapabilities:(NSArray *)capabilities; +- (void) removeCapability:(NSString *)capability; +- (void) removeCapabilities:(NSArray *)capabilities; +// @endcond + +/*! + * Test to see if the capabilities array contains a given capability. See the individual Capability classes for acceptable capability values. + * + * It is possible to append a wildcard search term `.Any` to the end of the search term. This method will return true for capabilities that match the term up to the wildcard. + * + * Example: `Launcher.App.Any` + * + * @property capability Capability to test against + */ +- (BOOL) hasCapability:(NSString *)capability; + +/*! + * Test to see if the capabilities array contains a given set of capabilities. See the individual Capability classes for acceptable capability values. + * + * See hasCapability: for a description of the wildcard feature provided by this method. + * + * @property capabilities Array of capabilities to test against + */ +- (BOOL) hasCapabilities:(NSArray *)capabilities; + +/*! + * Test to see if the capabilities array contains at least one capability in a given set of capabilities. See the individual Capability classes for acceptable capability values. + * + * See hasCapability: for a description of the wildcard feature provided by this method. + * + * @property capabilities Array of capabilities to test against + */ +- (BOOL) hasAnyCapability:(NSArray *)capabilities; + +#pragma mark - Connection + +/*! Whether the DeviceService is currently connected */ +@property (nonatomic) BOOL connected; + +/*! Whether the DeviceService requires an active connection or registration process */ +@property (nonatomic, readonly) BOOL isConnectable; + +/*! + * Will attempt to connect to the DeviceService. The failure/success will be reported back to the DeviceServiceDelegate. If the connection attempt reveals that pairing is required, the DeviceServiceDelegate will also be notified in that event. + */ +- (void) connect; + +/*! + * Will attempt to disconnect from the DeviceService. The failure/success will be reported back to the DeviceServiceDelegate. + */ +- (void) disconnect; + +# pragma mark - Pairing + +/*! Whether the DeviceService requires pairing or not. */ +@property (nonatomic, readonly) BOOL requiresPairing; + +/*! Type of pairing that this DeviceService requires. May be unknown until you try to connect. */ +@property (nonatomic, readwrite) DeviceServicePairingType pairingType; + +/*! May contain useful information regarding pairing (pairing key length, etc) */ +@property (nonatomic, readonly) id pairingData; + +/*! + * Will attempt to pair with the DeviceService with the provided pairingData. The failure/success will be reported back to the DeviceServiceDelegate. + * + * @param pairingData Data to be used for pairing. The type of this parameter will vary depending on what type of pairing is required, but is likely to be a string (pin code, pairing key, etc). + */ +- (void) pairWithData:(id)pairingData; + +#pragma mark - Utility + +/*! + * Static property that determines whether a DeviceService subclass should shut down communication channels when the app enters a background state. This may be helpful for apps that need to communicate with web apps from the background. This property may not be applicable to all DeviceService subclasses. + */ ++ (BOOL) shouldDisconnectOnBackground; + +/*! + * Sets the shouldDisconnectOnBackground static property. This property should be set before starting DiscoveryManager for the first time. + * + * @property shouldDisconnectOnBackground New value for DeviceService.shouldDisconnectOnBackground + */ ++ (void) setShouldDisconnectOnBackround:(BOOL)shouldDisconnectOnBackground; + +// @cond INTERNAL +void dispatch_on_main(dispatch_block_t block); +id ensureString(id value); +// @endcond + +/*! + * Every LaunchSession object has an associated DeviceService. Internally, LaunchSession's close method proxies to it's DeviceService's closeLaunchSession method. If, for some reason, your LaunchSession loses it's DeviceService reference, you can call this closeLaunchSession method directly. + * + * @param launchSession LaunchSession to be closed + * @param success (optional) SuccessBlock to be called on success + * @param failure (optional) FailureBlock to be called on failure + */ +- (void) closeLaunchSession:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure; + +@end diff --git a/core/Services/DeviceService.m b/core/Services/DeviceService.m new file mode 100644 index 00000000..142baa32 --- /dev/null +++ b/core/Services/DeviceService.m @@ -0,0 +1,389 @@ +// +// DeviceService.m +// Connect SDK +// +// Created by Jeremy White on 12/5/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DeviceService.h" +#import "Launcher.h" +#import "MediaPlayer.h" +#import "ExternalInputControl.h" +#import "WebAppLauncher.h" +#import "ConnectError.h" + +@implementation DeviceService +{ + NSMutableArray *_capabilities; +} + +- (NSString *)serviceName +{ + return self.serviceDescription.serviceId; +} + ++ (NSDictionary *) discoveryParameters { return nil; } + ++ (instancetype) deviceServiceWithClass:(Class)_class serviceConfig:(ServiceConfig *)serviceConfig +{ + return [[_class alloc] initWithServiceConfig:serviceConfig]; +} + +- (instancetype) init +{ + self = [super init]; + + if (self) + { + _connected = NO; + _capabilities = [NSMutableArray new]; + + [self updateCapabilities]; + } + + return self; +} + +- (instancetype) initWithServiceConfig:(ServiceConfig *)serviceConfig +{ + self = [self init]; + + if (self) + { + _serviceConfig = serviceConfig; + } + + return self; +} + +static BOOL _shouldDisconnectOnBackground = YES; + ++ (BOOL) shouldDisconnectOnBackground +{ + return _shouldDisconnectOnBackground; +} + ++ (void) setShouldDisconnectOnBackround:(BOOL)shouldDisconnectOnBackground +{ + _shouldDisconnectOnBackground = shouldDisconnectOnBackground; +} + +#pragma mark - Capabilities + +- (NSArray *) capabilities { return [NSArray arrayWithArray:_capabilities]; } + +- (BOOL) hasCapability:(NSString *)capability +{ + NSRange anyRange = [capability rangeOfString:@".Any"]; + + if (anyRange.location != NSNotFound) + { + NSString *matchedCapability = [capability substringToIndex:anyRange.location]; + + __block BOOL hasCap = NO; + + [self.capabilities enumerateObjectsUsingBlock:^(NSString *item, NSUInteger idx, BOOL *stop) + { + if ([item rangeOfString:matchedCapability].location != NSNotFound) + { + hasCap = YES; + *stop = YES; + } + }]; + + return hasCap; + } + + return [self.capabilities containsObject:capability]; +} + +- (BOOL) hasCapabilities:(NSArray *)capabilities +{ + __block BOOL hasCaps = YES; + + [capabilities enumerateObjectsUsingBlock:^(NSString *capability, NSUInteger idx, BOOL *stop) + { + if (![self hasCapability:capability]) + { + hasCaps = NO; + *stop = YES; + } + }]; + + return hasCaps; +} + +- (BOOL) hasAnyCapability:(NSArray *)capabilities +{ + __block BOOL hasAnyCap = NO; + + [capabilities enumerateObjectsUsingBlock:^(NSString *capability, NSUInteger idx, BOOL *stop) + { + if ([self hasCapability:capability]) + { + hasAnyCap = YES; + *stop = YES; + } + }]; + + return hasAnyCap; +} + +- (void) updateCapabilities { } + +- (void) setCapabilities:(NSArray *)newCapabilities +{ + NSArray *oldCapabilities = _capabilities; + + _capabilities = [NSMutableArray arrayWithArray:newCapabilities]; + + NSMutableArray *lostCapabilities = [NSMutableArray new]; + + [oldCapabilities enumerateObjectsUsingBlock:^(NSString *capability, NSUInteger idx, BOOL *stop) + { + if (![newCapabilities containsObject:capability]) + [lostCapabilities addObject:capability]; + }]; + + NSMutableArray *addedCapabilities = [NSMutableArray new]; + + [newCapabilities enumerateObjectsUsingBlock:^(NSString *capability, NSUInteger idx, BOOL *stop) + { + if (![oldCapabilities containsObject:capability]) + [addedCapabilities addObject:capability]; + }]; + + if (_delegate && [_delegate respondsToSelector:@selector(deviceService:capabilitiesAdded:removed:)]) + dispatch_on_main(^{ [_delegate deviceService:self capabilitiesAdded:addedCapabilities removed:lostCapabilities]; }); +} + +- (void) addCapability:(NSString *)capability +{ + if (!capability || capability.length == 0) + return; + + if ([self hasCapability:capability]) + return; + + [_capabilities addObject:capability]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(deviceService:capabilitiesAdded:removed:)]) + [self.delegate deviceService:self capabilitiesAdded:@[capability] removed:[NSArray array]]; +} + +- (void) addCapabilities:(NSArray *)capabilities +{ + [capabilities enumerateObjectsUsingBlock:^(NSString *capability, NSUInteger idx, BOOL *stop) + { + if (!capability || capability.length == 0) + return; + + if ([self hasCapability:capability]) + return; + + [_capabilities addObject:capability]; + }]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(deviceService:capabilitiesAdded:removed:)]) + [self.delegate deviceService:self capabilitiesAdded:capabilities removed:[NSArray array]]; +} + +- (void) removeCapability:(NSString *)capability +{ + if (!capability || capability.length == 0) + return; + + if (![self hasCapability:capability]) + return; + + do + { + [_capabilities removeObject:capability]; + } while ([_capabilities containsObject:capability]); + + if (self.delegate && [self.delegate respondsToSelector:@selector(deviceService:capabilitiesAdded:removed:)]) + [self.delegate deviceService:self capabilitiesAdded:[NSArray array] removed:@[capability]]; +} + +- (void) removeCapabilities:(NSArray *)capabilities +{ + [capabilities enumerateObjectsUsingBlock:^(NSString *capability, NSUInteger idx, BOOL *stop) + { + if (!capability || capability.length == 0) + return; + + if (![self hasCapability:capability]) + return; + + do + { + [_capabilities removeObject:capability]; + } while ([_capabilities containsObject:capability]); + }]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(deviceService:capabilitiesAdded:removed:)]) + [self.delegate deviceService:self capabilitiesAdded:[NSArray array] removed:capabilities]; +} + +#pragma mark - Connection + +- (BOOL) isConnectable +{ + return NO; +} + +- (void) connect { } +- (void) disconnect { } + +# pragma mark - Pairing + +- (BOOL) requiresPairing +{ + return self.pairingType != DeviceServicePairingTypeNone; +} + +- (DeviceServicePairingType) pairingType +{ + return DeviceServicePairingTypeNone; +} + +- (void) setPairingType:(DeviceServicePairingType)pairingType { + //Subclasses should implement this method to set pairing type. +} + +- (id) pairingData +{ + return nil; +} + +- (void)pairWithData:(id)pairingData { } + +#pragma mark - Utility + +void dispatch_on_main(dispatch_block_t block) { + if (block) + dispatch_async(dispatch_get_main_queue(), block); +} + +id ensureString(id value) +{ + return value != nil ? value : @""; +} + +- (void) closeLaunchSession:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (!launchSession) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You must provice a valid LaunchSession object"]); + + return; + } + + if (!launchSession.service) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"This LaunchSession does not have an associated DeviceService"]); + + return; + } + + switch (launchSession.sessionType) + { + case LaunchSessionTypeApp: + if ([launchSession.service conformsToProtocol:@protocol(Launcher)]) + [((id ) launchSession.service) closeApp:launchSession success:success failure:failure]; + break; + + case LaunchSessionTypeMedia: + if ([launchSession.service conformsToProtocol:@protocol(MediaPlayer)]) + [((id ) launchSession.service) closeMedia:launchSession success:success failure:failure]; + break; + + case LaunchSessionTypeExternalInputPicker: + if ([launchSession.service conformsToProtocol:@protocol(ExternalInputControl)]) + [((id ) launchSession.service) closeInputPicker:launchSession success:success failure:failure]; + break; + + case LaunchSessionTypeWebApp: + if ([launchSession.service conformsToProtocol:@protocol(WebAppLauncher)]) + [((id ) launchSession.service) closeWebApp:launchSession success:success failure:failure]; + break; + + case LaunchSessionTypeUnknown: + default: + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"This DeviceService does not know how to close this LaunchSession"]); + } +} + +#pragma mark - JSONObjectCoding methods + ++ (instancetype) deviceServiceWithJSONObject:(NSDictionary *)dict +{ + NSString *className = dict[@"class"]; + + if (!className || className.length == 0 || [className isKindOfClass:[NSNull class]]) + return nil; + + Class DeviceServiceClass = NSClassFromString(className); + + if (!DeviceServiceClass) + return nil; + + return [[DeviceServiceClass alloc] initWithJSONObject:dict]; +} + +- (instancetype) initWithJSONObject:(NSDictionary *)dict +{ + NSDictionary *configDictionary = dict[@"config"]; + ServiceConfig *config; + + if (configDictionary) + config = [ServiceConfig serviceConfigWithJSONObject:configDictionary]; + + if (config) + self = [self initWithServiceConfig:config]; + else + self = [self init]; + + if (self) + { + NSDictionary *descriptionDictionary = dict[@"description"]; + + if (descriptionDictionary) + self.serviceDescription = [[ServiceDescription alloc] initWithJSONObject:descriptionDictionary]; + } + + return self; +} + +- (NSDictionary *) toJSONObject +{ + NSMutableDictionary *dictionary = [NSMutableDictionary new]; + + dictionary[@"class"] = NSStringFromClass([self class]); + + if (self.serviceConfig) + dictionary[@"config"] = [self.serviceConfig toJSONObject]; + + if (self.serviceDescription) + dictionary[@"description"] = [self.serviceDescription toJSONObject]; + + return dictionary; +} + +@end diff --git a/core/Services/DeviceServiceDelegate.h b/core/Services/DeviceServiceDelegate.h new file mode 100644 index 00000000..e345db72 --- /dev/null +++ b/core/Services/DeviceServiceDelegate.h @@ -0,0 +1,123 @@ +// +// DeviceServiceDelegate.h +// Connect SDK +// +// Created by Jeremy White on 12/23/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +/*! + * Type of pairing that is required by a particular DeviceService. This type will be passed along with the DeviceServiceDelegate deviceService:pairingRequiredOfType:withData: message. + */ +typedef enum { + /*! DeviceService does not require pairing */ + DeviceServicePairingTypeNone = 0, + + /*! DeviceService requires user interaction on the first screen (ex. pairing alert) */ + DeviceServicePairingTypeFirstScreen, + + /*! First screen is displaying a pairing pin code that can be sent through the DeviceService */ + DeviceServicePairingTypePinCode, + + /*! DeviceService can pair with multiple pairing types (ex. first screen OR pin) */ + DeviceServicePairingTypeMixed, + + /*! DeviceService requires AirPlay mirroring to be enabled to connect */ + DeviceServicePairingTypeAirPlayMirroring, + + /*! DeviceService pairing type is unknown */ + DeviceServicePairingTypeUnknown +} DeviceServicePairingType; + +@class DeviceService; + + +/*! + * DeviceServiceDelegate allows your app to respond to each step of the connection and pairing processes, if needed. By default, a DeviceService's ConnectableDevice is set as the delegate. Changing a DeviceService's delegate will break the normal operation of Connect SDK and is discouraged. ConnectableDeviceDelegate provides proxy methods for all of the methods listed here. + */ +@protocol DeviceServiceDelegate + +@optional + +/*! + * If the DeviceService requires an active connection (websocket, pairing, etc) this method will be called. + * + * @param service DeviceService that requires connection + */ +- (void) deviceServiceConnectionRequired:(DeviceService *)service; + +/*! + * After the connection has been successfully established, and after pairing (if applicable), this method will be called. + * + * @param service DeviceService that was successfully connected + */ +- (void) deviceServiceConnectionSuccess:(DeviceService*)service; + +/*! + * There are situations in which a DeviceService will update the capabilities it supports and propagate these changes to the DeviceService. Such situations include: + * - on discovery, DIALService will reach out to detect if certain apps are installed + * - on discovery, certain DeviceServices need to reach out for version & region information + * + * For more information on this particular method, see ConnectableDeviceDelegate's connectableDevice:capabilitiesAdded:removed: method. + * + * @param service DeviceService that has experienced a change in capabilities + * @param added NSArray of capabilities that are new to the DeviceService + * @param removed NSArray of capabilities that the DeviceService has lost + */ +- (void) deviceService:(DeviceService *)service capabilitiesAdded:(NSArray *)added removed:(NSArray *)removed; + +/*! + * This method will be called on any disconnection. If error is nil, then the connection was clean and likely triggered by the responsible DiscoveryProvider or by the user. + * + * @param service DeviceService that disconnected + * @param error NSError with a description of any errors causing the disconnect. If this value is nil, then the disconnect was clean/expected. + */ +- (void) deviceService:(DeviceService *)service disconnectedWithError:(NSError*)error; + +/*! + * Will be called if the DeviceService fails to establish a connection. + * + * @param service DeviceService which has failed to connect + * @param error NSError with a description of the failure + */ +- (void) deviceService:(DeviceService *)service didFailConnectWithError:(NSError*)error; + +/*! + * If the DeviceService requires pairing, valuable data will be passed to the delegate via this method. + * + * @param service DeviceService that requires pairing + * @param pairingType DeviceServicePairingType that the DeviceService requires + * @param pairingData Any object/data that might be required for the pairing process, will usually be nil + */ +- (void) deviceService:(DeviceService *)service pairingRequiredOfType:(DeviceServicePairingType)pairingType withData:(id)pairingData; + +/*! + * This method will be called upon pairing success. On pairing success, a connection to the DeviceService will be attempted. + * + * @property service DeviceService that has successfully completed pairing + */ +- (void) deviceServicePairingSuccess:(DeviceService*)service; + +/*! + * If there is any error in pairing, this method will be called. + * + * @param service DeviceService that has failed to complete pairing + * @param error NSError with a description of the failure + */ +- (void) deviceService:(DeviceService *)service pairingFailedWithError:(NSError*)error; + +@end diff --git a/core/Services/Helpers/AirPlayServiceHTTP.h b/core/Services/Helpers/AirPlayServiceHTTP.h new file mode 100644 index 00000000..7345f480 --- /dev/null +++ b/core/Services/Helpers/AirPlayServiceHTTP.h @@ -0,0 +1,37 @@ +// +// AirPlayServiceHTTP.h +// Connect SDK +// +// Created by Jeremy White on 5/28/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "MediaPlayer.h" +#import "MediaControl.h" + +@class AirPlayService; + +@interface AirPlayServiceHTTP : NSObject + +- (instancetype) initWithAirPlayService:(AirPlayService *)service; +- (void) connect; +- (void) disconnect; + +@property (nonatomic, readonly) AirPlayService *service; +@property (nonatomic, readonly) BOOL connecting; +@property (nonatomic, readonly) BOOL connected; + +@end diff --git a/core/Services/Helpers/AirPlayServiceHTTP.m b/core/Services/Helpers/AirPlayServiceHTTP.m new file mode 100644 index 00000000..c01bba16 --- /dev/null +++ b/core/Services/Helpers/AirPlayServiceHTTP.m @@ -0,0 +1,639 @@ +// +// AirPlayServiceHTTP.m +// Connect SDK +// +// Created by Jeremy White on 5/28/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "AirPlayServiceHTTP_Private.h" +#import "AirPlayServiceHTTPKeepAlive.h" +#import "DeviceService.h" +#import "AirPlayService.h" +#import "ConnectError.h" +#import "DeviceServiceReachability.h" +#import "CTGuid.h" +#import "GCDWebServer.h" + +#import "CTASIHTTPRequest.h" + +#import "NSObject+FeatureNotSupported_Private.h" + +@interface AirPlayServiceHTTP () + +@property (nonatomic, readonly) UIBackgroundTaskIdentifier backgroundTaskId; +@property (nonatomic, readonly) DeviceServiceReachability *serviceReachability; +@property (nonatomic, readonly) NSString *sessionId; +@property (nonatomic, readonly) NSString *assetId; +@property (nonatomic, readonly) GCDWebServer *subscriptionServer; +@property (nonatomic, readonly) dispatch_queue_t networkingQueue; +@property (nonatomic, readonly) dispatch_queue_t imageProcessingQueue; +@property (nonatomic, strong) AirPlayServiceHTTPKeepAlive *keepAlive; + +@end + +@implementation AirPlayServiceHTTP + +- (instancetype) initWithAirPlayService:(AirPlayService *)service +{ + self = [super init]; + + if (self) + { + _service = service; + _backgroundTaskId = UIBackgroundTaskInvalid; + _networkingQueue = dispatch_queue_create("com.connectsdk.AirPlayServiceHTTP.Networking", DISPATCH_QUEUE_SERIAL); + _imageProcessingQueue = dispatch_queue_create("com.connectsdk.AirPlayServiceHTTP.ImageProcessing", DISPATCH_QUEUE_SERIAL); + } + + return self; +} + +#pragma mark - Connection & Reachability + +- (void) connect +{ + _sessionId = [[CTGuid randomGuid] stringValue]; + + _connected = YES; + + _serviceReachability = [DeviceServiceReachability reachabilityWithTargetURL:self.service.serviceDescription.commandURL]; + _serviceReachability.delegate = self; + [_serviceReachability start]; + + if (self.service.connected && self.service.delegate && [self.service.delegate respondsToSelector:@selector(deviceServiceConnectionSuccess:)]) + dispatch_on_main(^{ [self.service.delegate deviceServiceConnectionSuccess:self.service]; }); +} + +- (void) disconnect +{ + _sessionId = nil; + + if (self.backgroundTaskId != UIBackgroundTaskInvalid) + { + dispatch_async(self.networkingQueue, ^{ + [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskId]; + _backgroundTaskId = UIBackgroundTaskInvalid; + }); + } + + if (_serviceReachability) + [_serviceReachability stop]; + + _connected = NO; +} + +- (void) didLoseReachability:(DeviceServiceReachability *)reachability +{ + if (self.connected) + { + [self.service disconnect]; + [self disconnect]; + } else + [_serviceReachability stop]; +} + +#pragma mark - Getters & Setters + +/// Returns the set delegate property value or self. +- (id)serviceCommandDelegate { + return _serviceCommandDelegate ?: self; +} + +#pragma mark - Command management + +- (int) sendCommand:(ServiceCommand *)command withPayload:(id)payload toURL:(NSURL *)URL +{ + CTASIHTTPRequest *request = [CTASIHTTPRequest requestWithURL:command.target]; + + if (payload || [command.HTTPMethod isEqualToString:@"POST"] || [command.HTTPMethod isEqualToString:@"PUT"]) + { + if (payload) + { + NSData *payloadData; + NSString *contentType; + + if ([payload isKindOfClass:[NSString class]]) + { + NSString *payloadString = (NSString *)payload; + payloadData = [payloadString dataUsingEncoding:NSUTF8StringEncoding]; + contentType = @"text/parameters"; + } else if ([payload isKindOfClass:[NSDictionary class]]) + { + NSError *parseError; + payloadData = [NSPropertyListSerialization dataWithPropertyList:payload format:NSPropertyListBinaryFormat_v1_0 options:0 error:&parseError]; + + if (parseError || !payloadData) + { + NSError *error = parseError; + + if (!error) + error = [ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Error occurred while parsing property list"]; + + if (command.callbackError) + dispatch_on_main(^{ command.callbackError(error); }); + + return -1; + } + + contentType = @"application/x-apple-binary-plist"; + } else if ([payload isKindOfClass:[NSData class]]) + { + payloadData = payload; + contentType = @"image/jpeg"; + } + + if (payloadData == nil) + { + if (command.callbackError) + command.callbackError([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Unknown error preparing message to send"]); + + return -1; + } + + [request addRequestHeader: @"Content-Length" value:[NSString stringWithFormat:@"%i", (unsigned int) [payloadData length]]]; + [request addRequestHeader: @"Content-Type" value:contentType]; + [request addRequestHeader: @"Connection" value:@"Keep-Alive"]; + [request setPostBody:[NSMutableData dataWithData:payloadData]]; + } else + { + [request addRequestHeader: @"Content-Length" value:@"0"]; + } + + DLog(@"[OUT] : %@ \n %@", request.requestHeaders, payload); + } else + { + [request addRequestHeader: @"Content-Length" value:@"0"]; + + DLog(@"[OUT] : %@", request.requestHeaders); + } + + [request setRequestMethod:command.HTTPMethod]; + + if (self.sessionId) + [request addRequestHeader:@"X-Apple-Session-ID" value:self.sessionId]; + + if (self.assetId) + [request addRequestHeader:@"X-Apple-AssetKey" value:self.assetId]; + + [request setShouldAttemptPersistentConnection:YES]; + [request setShouldContinueWhenAppEntersBackground:YES]; + + __weak CTASIHTTPRequest *weakRequest = request; + + [request setCompletionBlock:^{ + if (!weakRequest) + { + if (command.callbackError) + dispatch_on_main(^{ command.callbackError([ConnectError generateErrorWithCode:ConnectStatusCodeSocketError andDetails:@"Could not access request data"]); }); + + return; + } + + CTASIHTTPRequest *strongRequest = weakRequest; + + if (strongRequest.responseStatusCode == 200) + { + NSError *xmlError; + NSMutableDictionary *plist = [NSPropertyListSerialization propertyListWithData:strongRequest.responseData options:NSPropertyListImmutable format:NULL error:&xmlError]; + + if (xmlError) + { + if (command.callbackComplete) + dispatch_on_main(^{ command.callbackComplete(strongRequest.responseData); }); + } else + { + if (plist) + { + if (command.callbackComplete) + dispatch_on_main(^{ command.callbackComplete(plist); }); + } + } + } else + { + if (command.callbackError) + dispatch_on_main(^{ command.callbackError([ConnectError generateErrorWithCode:strongRequest.responseStatusCode andDetails:nil]); }); + } + }]; + + dispatch_async(self.networkingQueue, ^void { + // this will prevent the connection dropping on background/sleep modes + if (self.backgroundTaskId == UIBackgroundTaskInvalid) + _backgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL]; + + [request startSynchronous]; + }); + + return -1; +} + +- (int) sendSubscription:(ServiceSubscription *)subscription type:(ServiceSubscriptionType)type payload:(id)payload toURL:(NSURL *)URL withId:(int)callId +{ + return -1; +} + +#pragma mark - Media Player + +- (id ) mediaPlayer +{ + return self; +} + +- (CapabilityPriorityLevel) mediaPlayerPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void) displayImage:(NSURL *)imageURL iconURL:(NSURL *)iconURL title:(NSString *)title description:(NSString *)description mimeType:(NSString *)mimeType success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:imageURL mimeType:mimeType]; + mediaInfo.title = title; + mediaInfo.description = description; + ImageInfo *imageInfo = [[ImageInfo alloc] initWithURL:iconURL type:ImageTypeThumb]; + [mediaInfo addImage:imageInfo]; + + [self displayImageWithMediaInfo:mediaInfo success:^(MediaLaunchObject *mediaLanchObject) { + success(mediaLanchObject.session,mediaLanchObject.mediaControl); + } failure:failure]; +} + +- (void) displayImage:(MediaInfo *)mediaInfo + success:(MediaPlayerDisplaySuccessBlock)success + failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + + [self displayImage:mediaInfo.url iconURL:iconURL title:mediaInfo.title description:mediaInfo.description mimeType:mediaInfo.mimeType success:success failure:failure]; +} + +- (void) displayImageWithMediaInfo:(MediaInfo *)mediaInfo success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + _assetId = [[CTGuid randomGuid] stringValue]; + + NSString *commandPathComponent = @"photo"; + NSURL *commandURL = [self.service.serviceDescription.commandURL URLByAppendingPathComponent:commandPathComponent]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self.serviceCommandDelegate target:commandURL payload:nil]; + command.HTTPMethod = @"PUT"; + command.callbackComplete = ^(id responseObject) { + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:commandPathComponent]; + launchSession.sessionType = LaunchSessionTypeMedia; + launchSession.service = self.service; + launchSession.sessionId = self.sessionId; + + MediaLaunchObject *launchObject = [[MediaLaunchObject alloc] initWithLaunchSession:launchSession andMediaControl:self.mediaControl]; + if(success){ + dispatch_on_main(^{ success(launchObject); }); + } + }; + + command.callbackError = failure; + + dispatch_async(self.imageProcessingQueue, ^{ + NSError *downloadError; + NSURL *imageURL = mediaInfo.url; + NSData *downloadedImageData = [NSData dataWithContentsOfURL:imageURL options:0 error:&downloadError]; + + if (!downloadedImageData || downloadError) + { + if (failure) + { + if (downloadError) + dispatch_on_main(^{ failure(downloadError); }); + else + dispatch_on_main(^{ failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Could not download requested image"]); }); + } + + return; + } + + NSData *processedImageData; + + if ([imageURL.absoluteString hasSuffix:@"jpg"] || [imageURL.absoluteString hasSuffix:@"jpeg"]) + processedImageData = downloadedImageData; + else + { + UIImage *image = [UIImage imageWithData:downloadedImageData]; + + if (image) + { + processedImageData = UIImageJPEGRepresentation(image, 1.0); + + if (!processedImageData) + { + if (failure) + dispatch_on_main(^{ failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Could not convert downloaded image to JPEG format"]); }); + + return; + } + } else + { + if (failure) + dispatch_on_main(^{ failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Could not convert downloaded data to a suitable image format"]); }); + + return; + } + } + + command.payload = processedImageData; + [command send]; + }); +} + +- (void) playMedia:(NSURL *)mediaURL iconURL:(NSURL *)iconURL title:(NSString *)title description:(NSString *)description mimeType:(NSString *)mimeType shouldLoop:(BOOL)shouldLoop success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:mediaURL mimeType:mimeType]; + mediaInfo.title = title; + mediaInfo.description = description; + ImageInfo *imageInfo = [[ImageInfo alloc] initWithURL:iconURL type:ImageTypeThumb]; + [mediaInfo addImage:imageInfo]; + [self playMediaWithMediaInfo:mediaInfo shouldLoop:shouldLoop success:^(MediaLaunchObject *mediaLanchObject) { + success(mediaLanchObject.session,mediaLanchObject.mediaControl); + } failure:failure]; +} + +- (void) playMedia:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + [self playMedia:mediaInfo.url iconURL:iconURL title:mediaInfo.title description:mediaInfo.description mimeType:mediaInfo.mimeType shouldLoop:shouldLoop success:success failure:failure]; +} + +- (void) playMediaWithMediaInfo:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + _assetId = [[CTGuid randomGuid] stringValue]; + + NSMutableDictionary *plist = [NSMutableDictionary new]; + plist[@"Content-Location"] = mediaInfo.url.absoluteString; + plist[@"Start-Position"] = @(0.0); + + NSError *parseError; + NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:plist format:NSPropertyListBinaryFormat_v1_0 options:0 error:&parseError]; + + if (parseError || !plistData) + { + NSError *error = parseError; + + if (!error) + error = [ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Error occurred while parsing property list"]; + + if (failure) + failure(error); + + return; + } + + NSString *commandPathComponent = @"play"; + NSURL *commandURL = [self.service.serviceDescription.commandURL URLByAppendingPathComponent:commandPathComponent]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:commandURL payload:plist]; + command.HTTPMethod = @"POST"; + command.callbackComplete = ^(id responseObject) { + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:commandPathComponent]; + launchSession.sessionType = LaunchSessionTypeMedia; + launchSession.service = self.service; + launchSession.sessionId = self.sessionId; + + [self startKeepAliveTimer]; + + MediaLaunchObject *launchObject = [[MediaLaunchObject alloc] initWithLaunchSession:launchSession andMediaControl:self.mediaControl]; + if(success){ + dispatch_on_main(^{ success(launchObject); }); + } + }; + command.callbackError = failure; + + [command send]; +} + +- (void) closeMedia:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.mediaControl stopWithSuccess:success failure:failure]; +} + +#pragma mark - Media Control + +- (id ) mediaControl +{ + return self; +} + +- (CapabilityPriorityLevel) mediaControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void) playWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *commandPath = [NSString stringWithFormat:@"%@rate?value=1.000000", self.service.serviceDescription.commandURL.absoluteString]; + NSURL *commandURL = [NSURL URLWithString:commandPath]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:commandURL payload:nil]; + command.HTTPMethod = @"POST"; + command.callbackComplete = success; + command.callbackError = failure; + + [command send]; +} + +- (void) pauseWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *commandPath = [NSString stringWithFormat:@"%@rate?value=0.000000", self.service.serviceDescription.commandURL.absoluteString]; + NSURL *commandURL = [NSURL URLWithString:commandPath]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:commandURL payload:nil]; + command.HTTPMethod = @"POST"; + command.callbackComplete = success; + command.callbackError = failure; + + [command send]; +} + +- (void) stopWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *commandPathComponent = @"stop"; + NSURL *commandURL = [self.service.serviceDescription.commandURL URLByAppendingPathComponent:commandPathComponent]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:commandURL payload:nil]; + command.HTTPMethod = @"POST"; + command.callbackComplete = ^(id responseObject) { + _assetId = nil; + [self stopKeepAliveTimer]; + + if (success) + success(responseObject); + }; + command.callbackError = failure; + + [command send]; +} + +- (void) rewindWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *commandPath = [NSString stringWithFormat:@"%@rate?value=-2.000000", self.service.serviceDescription.commandURL.absoluteString]; + NSURL *commandURL = [NSURL URLWithString:commandPath]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:commandURL payload:nil]; + command.HTTPMethod = @"POST"; + command.callbackComplete = success; + command.callbackError = failure; + + [command send]; +} + +- (void) fastForwardWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *commandPath = [NSString stringWithFormat:@"%@rate?value=2.000000", self.service.serviceDescription.commandURL.absoluteString]; + NSURL *commandURL = [NSURL URLWithString:commandPath]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:commandURL payload:nil]; + command.HTTPMethod = @"POST"; + command.callbackComplete = success; + command.callbackError = failure; + + [command send]; +} + +- (void) getDurationWithSuccess:(MediaDurationSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *commandPathComponent = [NSString stringWithFormat:@"playback-info"]; + NSURL *commandURL = [self.service.serviceDescription.commandURL URLByAppendingPathComponent:commandPathComponent]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:commandURL payload:nil]; + command.HTTPMethod = @"GET"; + command.callbackComplete = ^(id responseObject) { + NSTimeInterval duration = [responseObject[@"duration"] floatValue]; + + if (success) + success(duration); + }; + command.callbackError = failure; + + [command send]; +} + +- (void) getPlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *commandPathComponent = [NSString stringWithFormat:@"playback-info"]; + NSURL *commandURL = [self.service.serviceDescription.commandURL URLByAppendingPathComponent:commandPathComponent]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self.serviceCommandDelegate target:commandURL payload:nil]; + command.HTTPMethod = @"GET"; + command.callbackComplete = ^(NSDictionary *responseObject) { + MediaControlPlayState playState = MediaControlPlayStateUnknown; + + NSNumber *rateValue = responseObject[@"rate"]; + if (rateValue) { + NSInteger rate = [rateValue integerValue]; + playState = ((rate == 0) ? + MediaControlPlayStatePaused : + MediaControlPlayStatePlaying); + } else { + NSNumber *readyToPlayValue = responseObject[@"readyToPlay"]; + if (readyToPlayValue && ([readyToPlayValue integerValue] == 0)) { + playState = MediaControlPlayStateFinished; + } + } + + if (success) + success(playState); + }; + command.callbackError = failure; + + [command send]; +} + +- (void) getPositionWithSuccess:(MediaPositionSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *commandPathComponent = [NSString stringWithFormat:@"playback-info"]; + NSURL *commandURL = [self.service.serviceDescription.commandURL URLByAppendingPathComponent:commandPathComponent]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:commandURL payload:nil]; + command.HTTPMethod = @"GET"; + command.callbackComplete = ^(id responseObject) { + NSTimeInterval position = [responseObject[@"position"] floatValue]; + + if (success) + success(position); + }; + command.callbackError = failure; + + [command send]; +} + +- (void) seek:(NSTimeInterval)position success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *commandPath = [NSString stringWithFormat:@"%@scrub?position=%.06f", self.service.serviceDescription.commandURL.absoluteString, position]; + NSURL *commandURL = [NSURL URLWithString:commandPath]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:commandURL payload:nil]; + command.HTTPMethod = @"POST"; + command.callbackComplete = success; + command.callbackError = failure; + + [command send]; +} + +- (ServiceSubscription *) subscribePlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribeMediaInfoWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +- (void) playNextWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void) playPreviousWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)jumpToTrackWithIndex:(NSInteger)index success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)getMediaMetaDataWithSuccess:(SuccessBlock)success + failure:(FailureBlock)failure { + [self sendNotSupportedFailure:failure]; +} + +#pragma mark - Helpers + +- (void)startKeepAliveTimer { + self.keepAlive = [[AirPlayServiceHTTPKeepAlive alloc] initWithCommandDelegate:self]; + self.keepAlive.commandURL = self.service.serviceDescription.commandURL; + [self.keepAlive startTimer]; +} + +- (void)stopKeepAliveTimer { + [self.keepAlive stopTimer]; + self.keepAlive = nil; +} + +@end diff --git a/core/Services/Helpers/AirPlayServiceHTTPKeepAlive.h b/core/Services/Helpers/AirPlayServiceHTTPKeepAlive.h new file mode 100644 index 00000000..bc64319d --- /dev/null +++ b/core/Services/Helpers/AirPlayServiceHTTPKeepAlive.h @@ -0,0 +1,54 @@ +// +// AirPlayServiceHTTPKeepAlive.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 12/17/14. +// Copyright (c) 2014 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +@import Foundation; +@import CoreGraphics; + +@protocol ServiceCommandDelegate; + +/// The class is responsible for maintaining an AirPlay connection alive by +/// sending periodic requests. +@interface AirPlayServiceHTTPKeepAlive : NSObject + +/// The interval between keep-alive requests, in seconds. 50 by default. +@property (nonatomic, assign) CGFloat interval; + +/// An object that sends AirPlay commands. +@property (nonatomic, weak) id commandDelegate; + +/// The base URL for commands. +@property (nonatomic, strong) NSURL *commandURL; + + +/// Designated initializer, setting the interval and command delegate. +- (instancetype)initWithInterval:(CGFloat)interval + andCommandDelegate:(id)commandDelegate; + +/// Initializer that sets the command delegate. +- (instancetype)initWithCommandDelegate:(id)commandDelegate; + +/// Schedules sending keep-alive requests. The first one will be sent after the +/// specified @c interval. +- (void)startTimer; + +/// Stops sending keep-alive requests. +- (void)stopTimer; + +@end diff --git a/core/Services/Helpers/AirPlayServiceHTTPKeepAlive.m b/core/Services/Helpers/AirPlayServiceHTTPKeepAlive.m new file mode 100644 index 00000000..f8346364 --- /dev/null +++ b/core/Services/Helpers/AirPlayServiceHTTPKeepAlive.m @@ -0,0 +1,104 @@ +// +// AirPlayServiceHTTPKeepAlive.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 12/17/14. +// Copyright (c) 2014 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "AirPlayServiceHTTPKeepAlive.h" + +#import "ServiceCommand.h" + +@interface AirPlayServiceHTTPKeepAlive () + +/// The keep-alive timer. +@property (nonatomic, weak) NSTimer *timer; + +@end + + +@implementation AirPlayServiceHTTPKeepAlive + +#pragma mark - Init + +- (instancetype)initWithInterval:(CGFloat)interval + andCommandDelegate:(id)commandDelegate { + if (self = [super init]) { + _interval = interval; + _commandDelegate = commandDelegate; + } + return self; +} + +- (instancetype)initWithCommandDelegate:(id)commandDelegate { + // Apple TV 3 disconnects after 60 seconds of inactivity in an HTTP + // socket, so 50 seconds' keep-alive request should be enough + return [self initWithInterval:50 + andCommandDelegate:commandDelegate]; +} + +- (instancetype)init { + return [self initWithCommandDelegate:nil]; +} + +- (void)dealloc { + [self stopTimer]; +} + +#pragma mark - Timer Management + +- (void)startTimer { + [self stopTimer]; + + DLog(@"Starting keep-alive timer"); + self.timer = [NSTimer scheduledTimerWithTimeInterval:self.interval + target:self + selector:@selector(sendKeepAlive:) + userInfo:nil + repeats:YES]; +} + +- (void)stopTimer { + if (self.timer) { + DLog(@"Stopping keep-alive timer"); + [self.timer invalidate]; + self.timer = nil; + } +} + +#pragma mark - Private Methods + +- (void)sendKeepAlive:(NSTimer *)timer { + DLog(@"Sending keep-alive request"); + NSParameterAssert(self.commandURL); + + // the "/0" resource is unlikely to change to return something, as opposed + // to the "/" resource. a smaller response is better here + NSURL *keepAliveURL = [self.commandURL URLByAppendingPathComponent:@"0"]; + ServiceCommand *keepAliveCommand = [ServiceCommand commandWithDelegate:self.commandDelegate + target:keepAliveURL + payload:nil]; + keepAliveCommand.HTTPMethod = @"GET"; + keepAliveCommand.callbackComplete = ^(id obj) { + DLog(@"%@: keep-alive success %@", NSStringFromClass(self.class), obj); + }; + keepAliveCommand.callbackError = ^(NSError *error) { + DLog(@"%@: keep-alive error %@", NSStringFromClass(self.class), error); + }; + [keepAliveCommand send]; +} + +@end diff --git a/core/Services/Helpers/AirPlayServiceHTTP_Private.h b/core/Services/Helpers/AirPlayServiceHTTP_Private.h new file mode 100644 index 00000000..cda7ead2 --- /dev/null +++ b/core/Services/Helpers/AirPlayServiceHTTP_Private.h @@ -0,0 +1,27 @@ +// +// AirPlayServiceHTTP_Private.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 4/21/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "AirPlayServiceHTTP.h" + +@interface AirPlayServiceHTTP () + +@property (nonatomic, strong) id serviceCommandDelegate; + +@end diff --git a/core/Services/Helpers/AirPlayServiceMirrored.h b/core/Services/Helpers/AirPlayServiceMirrored.h new file mode 100644 index 00000000..0fd72611 --- /dev/null +++ b/core/Services/Helpers/AirPlayServiceMirrored.h @@ -0,0 +1,47 @@ +// +// AirPlayServiceMirrored.h +// Connect SDK +// +// Created by Jeremy White on 5/28/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import +#import +#import "MediaPlayer.h" +#import "MediaControl.h" +#import "WebAppLauncher.h" + +@class AirPlayService; + +@interface AirPlayServiceMirrored : NSObject + +- (instancetype) initWithAirPlayService:(AirPlayService *)service; + +- (void) connect; +- (void) disconnect; + +- (void) disconnectFromWebApp; + +@property (nonatomic, readonly) AirPlayService *service; + +@property (nonatomic, readonly) UIWindow *secondWindow; +@property (nonatomic, readonly) WKWebView *webAppWebView; + +@property (nonatomic, readonly) BOOL connecting; +@property (nonatomic, readonly) BOOL connected; + +@end diff --git a/core/Services/Helpers/AirPlayServiceMirrored.m b/core/Services/Helpers/AirPlayServiceMirrored.m new file mode 100644 index 00000000..5469c5c8 --- /dev/null +++ b/core/Services/Helpers/AirPlayServiceMirrored.m @@ -0,0 +1,560 @@ +// +// AirPlayServiceMirrored.m +// Connect SDK +// +// Created by Jeremy White on 5/28/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "AirPlayServiceMirrored.h" +#import +#import +#import "ConnectError.h" +#import "AirPlayWebAppSession.h" +#import "ConnectUtil.h" +#import "AirPlayService.h" + +#import "NSObject+FeatureNotSupported_Private.h" + +/*credit : http://stackoverflow.com/questions/30040055/uiviewcontroller-displayed-sideways-on-airplay-screen-when-launched-from-landsca/30355853#30355853 + + Added to AirPlayServiceWindow interface to override isKeyWindow method & AirPlayServiceViewController to override shouldAutorotate method to fix issue where web app is dsiplayed sideways when launched in landscape. + */ + +@interface AirPlayServiceWindow : UIWindow +@end + +@implementation AirPlayServiceWindow + +- (BOOL)isKeyWindow { + return NO; +} + +@end + +@interface AirPlayServiceViewController : UIViewController +@end + +@implementation AirPlayServiceViewController + +- (BOOL)shouldAutorotate { + return NO; +} +@end + +@interface AirPlayServiceMirrored () + +@property (nonatomic, copy) SuccessBlock launchSuccessBlock; +@property (nonatomic, copy) FailureBlock launchFailureBlock; + +@property (nonatomic) AirPlayWebAppSession *activeWebAppSession; +@property (nonatomic) ServiceSubscription *playStateSubscription; + +@end + +@implementation AirPlayServiceMirrored +{ + NSTimer *_connectTimer; + UIAlertController *_connectingAlertView; +} + +- (instancetype) initWithAirPlayService:(AirPlayService *)service +{ + self = [super init]; + + if (self) + { + _service = service; + } + + return self; +} + +- (void) connect +{ + [self checkForExistingScreenAndInitializeIfPresent]; + + if (self.secondWindow && self.secondWindow.screen) + { + _connecting = NO; + _connected = YES; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hScreenDisconnected:) name:UIScreenDidDisconnectNotification object:nil]; + + if (self.service.connected && self.service.delegate && [self.service.delegate respondsToSelector:@selector(deviceServiceConnectionSuccess:)]) + dispatch_on_main(^{ [self.service.delegate deviceServiceConnectionSuccess:self.service]; }); + } else + { + _connected = NO; + _connecting = YES; + + [self checkScreenCount]; + + NSString *title = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_AirPlay_Mirror_Title" value:@"Mirroring Required" table:@"ConnectSDK"]; + NSString *message = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_AirPlay_Mirror_Description" value:@"Enable AirPlay mirroring to connect to this device" table:@"ConnectSDK"]; + NSString *ok = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_AirPlay_Mirror_OK" value:@"OK" table:@"ConnectSDK"]; + NSString *cancel = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_AirPlay_Mirror_Cancel" value:@"Cancel" table:@"ConnectSDK"]; + + _connectingAlertView = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction *okAction = [UIAlertAction actionWithTitle:ok + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [_connectingAlertView dismissViewControllerAnimated:NO completion:^{ + [self actionAlert:TRUE]; + }]; + + }]; + + UIAlertAction *cancelAction = [UIAlertAction + actionWithTitle: cancel + style:UIAlertActionStyleCancel + handler:^(UIAlertAction *action) { + [_connectingAlertView dismissViewControllerAnimated:NO completion:^{ + [self actionAlert:FALSE]; + }]; + }]; + + [_connectingAlertView addAction: okAction]; + [_connectingAlertView addAction: cancelAction]; + + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hScreenConnected:) name:UIScreenDidConnectNotification object:nil]; + + if (self.service && self.service.delegate && [self.service.delegate respondsToSelector:@selector(deviceService:pairingRequiredOfType:withData:)]) + dispatch_on_main(^{ [self.service.delegate deviceService:self.service pairingRequiredOfType:DeviceServicePairingTypeAirPlayMirroring withData:_connectingAlertView]; }); + } +} + +- (void) disconnect +{ + _connected = NO; + _connecting = NO; + + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIScreenDidConnectNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIScreenDidDisconnectNotification object:nil]; + + if (self.secondWindow) + { + _secondWindow.hidden = YES; + _secondWindow.screen = nil; + _secondWindow = nil; + } + + if (_connectTimer) + { + [_connectTimer invalidate]; + _connectTimer = nil; + } + + if (_connectingAlertView){ + dispatch_on_main(^{ + [_connectingAlertView dismissViewControllerAnimated:NO completion:^{ + [self actionAlert:FALSE]; + }]; + }); + } + + if (self.service && self.service.delegate && [self.service.delegate respondsToSelector:@selector(deviceService:disconnectedWithError:)]) + [self.service.delegate deviceService:self.service disconnectedWithError:nil]; +} + +-(void) actionAlert:(BOOL) isOkAction { + _connectingAlertView = nil; + if (isOkAction == FALSE && _connecting) + [self disconnect]; +} + +- (int) sendSubscription:(ServiceSubscription *)subscription type:(ServiceSubscriptionType)type payload:(id)payload toURL:(NSURL *)URL withId:(int)callId +{ + if (type == ServiceSubscriptionTypeUnsubscribe) + { + if (subscription == self.playStateSubscription) + { + [[self.playStateSubscription successCalls] removeAllObjects]; + [[self.playStateSubscription failureCalls] removeAllObjects]; + [self.playStateSubscription setIsSubscribed:NO]; + self.playStateSubscription = nil; + } + } + + return -1; +} + +#pragma mark - External display detection, setup + +- (void) checkScreenCount +{ + if (_connectTimer) + { + [_connectTimer invalidate]; + _connectTimer = nil; + } + + if (!self.connecting) + return; + + if ([UIScreen screens].count > 1) + { + _connecting = NO; + _connected = YES; + + if (_connectingAlertView) { + dispatch_on_main(^{ + [_connectingAlertView dismissViewControllerAnimated:NO completion:^{ + [self actionAlert:TRUE]; + }]; + }); + } + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hScreenDisconnected:) name:UIScreenDidDisconnectNotification object:nil]; + + if (self.service.connected && self.service.delegate && [self.service.delegate respondsToSelector:@selector(deviceServiceConnectionSuccess:)]) + dispatch_on_main(^{ [self.service.delegate deviceServiceConnectionSuccess:self.service]; }); + } else + { + _connectTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(checkScreenCount) userInfo:nil repeats:NO]; + } +} + +- (void)checkForExistingScreenAndInitializeIfPresent +{ + if ([[UIScreen screens] count] > 1) + { + UIScreen *secondScreen = [[UIScreen screens] objectAtIndex:1]; + + CGRect screenBounds = secondScreen.bounds; + + _secondWindow = [[AirPlayServiceWindow alloc] initWithFrame:screenBounds]; + _secondWindow.screen = secondScreen; + + DLog(@"Displaying content with bounds %@", NSStringFromCGRect(screenBounds)); + } +} + +- (void) hScreenConnected:(NSNotification *)notification +{ + DLog(@"%@", notification); + + if (!self.secondWindow) + [self checkForExistingScreenAndInitializeIfPresent]; + + [self checkScreenCount]; +} + +- (void) hScreenDisconnected:(NSNotification *)notification +{ + DLog(@"%@", notification); + + if (_connecting || _connected) + [self disconnect]; +} + +#pragma mark - WebAppLauncher + +- (id ) webAppLauncher +{ + return self; +} + +- (CapabilityPriorityLevel) webAppLauncherPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void) launchWebApp:(NSString *)webAppId success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self launchWebApp:webAppId params:nil relaunchIfRunning:YES success:success failure:failure]; +} + +- (void) launchWebApp:(NSString *)webAppId params:(NSDictionary *)params success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self launchWebApp:webAppId params:params relaunchIfRunning:YES success:success failure:failure]; +} + +- (void) launchWebApp:(NSString *)webAppId params:(NSDictionary *)params relaunchIfRunning:(BOOL)relaunchIfRunning success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + if (!webAppId || webAppId.length == 0) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You must provide a valid web app URL"]); + + return; + } + + [self checkForExistingScreenAndInitializeIfPresent]; + + if (!self.secondWindow || !self.secondWindow.screen) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Could not detect a second screen -- make sure you have mirroring enabled"]); + + return; + } + + if (_webAppWebView) + { + if (relaunchIfRunning) + { + [self closeWebApp:nil success:^(id responseObject) + { + [self launchWebApp:webAppId params:params relaunchIfRunning:relaunchIfRunning success:success failure:failure]; + } failure:failure]; + + return; + } else + { + NSString *webAppHost = _webAppWebView.URL.host; + + if ([webAppId rangeOfString:webAppHost].location != NSNotFound) + { + if (params && params.count > 0) + { + [self.activeWebAppSession connectWithSuccess:^(id connectResponseObject) + { + [self.activeWebAppSession sendJSON:params success:^(id sendResponseObject) + { + if (success) + success(self.activeWebAppSession); + } failure:failure]; + } failure:failure]; + } else + { + if (success) + dispatch_on_main(^{ success(self.activeWebAppSession); }); + } + + return; + } + } + } + + DLog(@"Created a web view with bounds %@", NSStringFromCGRect(self.secondWindow.bounds)); + + WKProcessPool *commonProcessPool = [[WKProcessPool alloc] init]; + WKWebViewConfiguration *webViewConfig = [[WKWebViewConfiguration alloc] init]; + webViewConfig.processPool = commonProcessPool; + webViewConfig.allowsInlineMediaPlayback = true; + webViewConfig.mediaPlaybackAllowsAirPlay = false; + webViewConfig.mediaPlaybackRequiresUserAction = false; + + _webAppWebView = [[WKWebView alloc] initWithFrame:self.secondWindow.bounds configuration:webViewConfig]; + + AirPlayServiceViewController *secondScreenViewController = [[AirPlayServiceViewController alloc] init]; + secondScreenViewController.view = _webAppWebView; + _webAppWebView.navigationDelegate = self; + self.secondWindow.rootViewController = secondScreenViewController; + self.secondWindow.hidden = NO; + + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:webAppId]; + launchSession.sessionType = LaunchSessionTypeWebApp; + launchSession.service = self.service; + + AirPlayWebAppSession *webAppSession = [[AirPlayWebAppSession alloc] initWithLaunchSession:launchSession service:self.service]; + self.activeWebAppSession = webAppSession; + + __weak AirPlayWebAppSession *weakSession = self.activeWebAppSession; + + if (params && params.count > 0) + { + self.launchSuccessBlock = ^(id launchResponseObject) + { + [weakSession connectWithSuccess:^(id connectResponseObject) + { + [weakSession sendJSON:params success:^(id sendResponseObject) + { + if (success) + success(weakSession); + } failure:failure]; + } failure:failure]; + }; + } else + { + self.launchSuccessBlock = ^(id responseObject) + { + if (success) + success(weakSession); + }; + } + + self.launchFailureBlock = failure; + + NSURL *URL = [NSURL URLWithString:webAppId]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; + + [self.webAppWebView loadRequest:request]; +} + +- (void) launchWebApp:(NSString *)webAppId relaunchIfRunning:(BOOL)relaunchIfRunning success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self launchWebApp:webAppId params:nil relaunchIfRunning:YES success:success failure:failure]; +} + +- (void) joinWebApp:(LaunchSession *)webAppLaunchSession success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + if (self.webAppWebView && self.connected) + { + NSString *webAppHost = self.webAppWebView.URL.host; + + if ([webAppLaunchSession.appId rangeOfString:webAppHost].location != NSNotFound) + { + AirPlayWebAppSession *webAppSession = [[AirPlayWebAppSession alloc] initWithLaunchSession:webAppLaunchSession service:self.service]; + self.activeWebAppSession = webAppSession; + + [webAppSession connectWithSuccess:success failure:failure]; + } else + { + if (failure) + dispatch_on_main(^{ failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Web is not currently running"]); }); + } + } else + { + if (failure) + dispatch_on_main(^{ failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Web is not currently running"]); }); + } +} + +- (void) joinWebAppWithId:(NSString *)webAppId success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:webAppId]; + launchSession.service = self.service; + launchSession.sessionType = LaunchSessionTypeWebApp; + + [self joinWebApp:launchSession success:success failure:failure]; +} + +- (void) disconnectFromWebApp +{ + if (self.activeWebAppSession) + { + if (self.activeWebAppSession.delegate && [self.activeWebAppSession.delegate respondsToSelector:@selector(webAppSessionDidDisconnect:)]) + dispatch_on_main(^{ [self.activeWebAppSession.delegate webAppSessionDidDisconnect:self.activeWebAppSession]; }); + + self.activeWebAppSession = nil; + } + + self.launchSuccessBlock = nil; + self.launchFailureBlock = nil; +} + +- (void) closeWebApp:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self disconnectFromWebApp]; + + if (_secondWindow) + { + _secondWindow.rootViewController = nil; + _secondWindow.hidden = YES; + _secondWindow.screen = nil; + _secondWindow = nil; + + _webAppWebView.navigationDelegate = nil; + _webAppWebView = nil; + } + + if (success) + success(nil); +} + +- (void) pinWebApp:(NSString *)webAppId success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +-(void)unPinWebApp:(NSString *)webAppId success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)isWebAppPinned:(NSString *)webAppId success:(WebAppPinStatusBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribeIsWebAppPinned:(NSString*)webAppId success:(WebAppPinStatusBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +#pragma mark - WKNavigationDelegate + +- (void)webView:(WKWebView *)webView didFailLoadWithError:(NSError *)error +{ + DLog(@"%@", error.localizedDescription); + + if (self.launchFailureBlock) + self.launchFailureBlock(error); + + self.launchSuccessBlock = nil; + self.launchFailureBlock = nil; +} + +- (BOOL)webView:(WKWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request +{ + if ([request.URL.absoluteString hasPrefix:@"connectsdk://"]) + { + NSString *jsonString = [[request.URL.absoluteString componentsSeparatedByString:@"connectsdk://"] lastObject]; + jsonString = [ConnectUtil urlDecode:jsonString]; + + NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; + + NSError *jsonError; + id messageObject = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&jsonError]; + + if (jsonError || !messageObject) + messageObject = jsonString; + + DLog(@"Got p2p message from web app:\n%@", messageObject); + + if (self.activeWebAppSession) + { + NSString *webAppHost = self.webAppWebView.URL.host; + + // check if current running web app matches the current web app session + if ([self.activeWebAppSession.launchSession.appId rangeOfString:webAppHost].location != NSNotFound) + { + dispatch_on_main(^{ + if (self.activeWebAppSession) + self.activeWebAppSession.messageHandler(messageObject); + }); + } else + [self.activeWebAppSession disconnectFromWebApp]; + } + + return NO; + } else + { + return YES; + } +} + +- (void)webViewDidFinishLoad:(WKWebView *)webView +{ + DLog(@"%@", webView.request.URL.absoluteString); + + if (self.launchSuccessBlock) + self.launchSuccessBlock(nil); + + self.launchSuccessBlock = nil; + self.launchFailureBlock = nil; +} + +- (void)webViewDidStartLoad:(WKWebView *)webView +{ + DLog(@"%@", webView.request.URL.absoluteString); +} + +@end diff --git a/core/Services/Helpers/DLNAHTTPServer.h b/core/Services/Helpers/DLNAHTTPServer.h new file mode 100644 index 00000000..f00937af --- /dev/null +++ b/core/Services/Helpers/DLNAHTTPServer.h @@ -0,0 +1,38 @@ +// +// DLNAHTTPServer.h +// Connect SDK +// +// Created by Jeremy White on 9/30/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "GCDWebServer.h" +#import "ServiceSubscription.h" + +@interface DLNAHTTPServer : NSObject + +- (void) start; +- (void) stop; +- (void) addSubscription:(ServiceSubscription *)subscription; +- (void) removeSubscription:(ServiceSubscription *)subscription; + +- (NSString *) getHostPath; + +@property (nonatomic, readonly) GCDWebServer *server; +@property (nonatomic, readonly) BOOL isRunning; +@property (nonatomic, readonly) BOOL hasSubscriptions; + +@end diff --git a/core/Services/Helpers/DLNAHTTPServer.m b/core/Services/Helpers/DLNAHTTPServer.m new file mode 100644 index 00000000..53bfb393 --- /dev/null +++ b/core/Services/Helpers/DLNAHTTPServer.m @@ -0,0 +1,248 @@ +// +// DLNAHTTPServer.m +// Connect SDK +// +// Created by Jeremy White on 9/30/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import +#import "DLNAHTTPServer.h" +#import "DeviceService.h" +#import "CTXMLReader.h" +#import "GCDWebServerDataRequest.h" +#import "GCDWebServerHTTPStatusCodes.h" +#import "ConnectUtil.h" + + +@implementation DLNAHTTPServer +{ + NSMutableDictionary *_allSubscriptions; +} + +- (instancetype) init +{ + if (self = [super init]) + { + _allSubscriptions = [NSMutableDictionary new]; + } + + return self; +} + +- (BOOL) isRunning +{ + if (!_server) + return NO; + else + return _server.isRunning; +} + +- (void) start +{ + [self stop]; + + [_allSubscriptions removeAllObjects]; + + _server = [[GCDWebServer alloc] init]; + _server.delegate = self; + + GCDWebServerResponse *(^webServerResponseBlock)(GCDWebServerRequest *request) = ^GCDWebServerResponse *(GCDWebServerRequest *request) { + [self processRequest:(GCDWebServerDataRequest *)request]; + // according to the UPnP specification, a subscriber must reply with HTTP 200 OK + // to successfully acknowledge the notification + return [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_OK]; + }; + + [self.server addDefaultHandlerForMethod:@"NOTIFY" + requestClass:[GCDWebServerDataRequest class] + processBlock:webServerResponseBlock]; + + [self.server startWithPort:49291 bonjourName:nil]; +} + +- (void) stop +{ + if (!_server) + return; + + self.server.delegate = nil; + + if (_server.isRunning) + [self.server stop]; + + _server = nil; +} + +/// Returns a service subscription key for the given URL. Different service URLs +/// should produce different keys by extracting the relative path, e.g.: +/// "http://example.com:8888/foo/bar?q=a#abc" => "/foo/bar?q=a#abc" +- (NSString *)serviceSubscriptionKeyForURL:(NSURL *)url { + // unfortunately, -[NSURL relativeString] works for URLs created with + // -initWithString:relativeToURL: only + NSString *resourceSpecifier = url.absoluteURL.resourceSpecifier; + // resourceSpecifier starts with two slashes, so we'll look for the third one + NSRange relativePathStartRange = [resourceSpecifier rangeOfString:@"/" + options:0 + range:NSMakeRange(2, resourceSpecifier.length - 2)]; + NSAssert(NSNotFound != relativePathStartRange.location, @"Couldn't find relative path in %@", resourceSpecifier); + return [resourceSpecifier substringFromIndex:relativePathStartRange.location]; +} + +- (void) addSubscription:(ServiceSubscription *)subscription +{ + @synchronized (_allSubscriptions) + { + NSString *serviceSubscriptionKey = [self serviceSubscriptionKeyForURL:subscription.target]; + + if (!_allSubscriptions[serviceSubscriptionKey]) + _allSubscriptions[serviceSubscriptionKey] = [NSMutableArray new]; + + NSMutableArray *serviceSubscriptions = _allSubscriptions[serviceSubscriptionKey]; + [serviceSubscriptions addObject:subscription]; + subscription.isSubscribed = YES; + } +} + +- (void) removeSubscription:(ServiceSubscription *)subscription +{ + @synchronized (_allSubscriptions) + { + NSString *serviceSubscriptionKey = [self serviceSubscriptionKeyForURL:subscription.target]; + + NSMutableArray *serviceSubscriptions = _allSubscriptions[serviceSubscriptionKey]; + + if (!_allSubscriptions[serviceSubscriptionKey]) + return; + + subscription.isSubscribed = NO; + [serviceSubscriptions removeObject:subscription]; + + if (serviceSubscriptions.count == 0) + [_allSubscriptions removeObjectForKey:serviceSubscriptionKey]; + } +} + +- (BOOL) hasSubscriptions +{ + @synchronized (_allSubscriptions) + { + return _allSubscriptions.count > 0; + } +} + +- (void) processRequest:(GCDWebServerDataRequest *)request +{ + if (!request.data || request.data.length == 0) + return; + + NSString *serviceSubscriptionKey = [[self serviceSubscriptionKeyForURL:request.URL] + stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSArray *serviceSubscriptions; + + @synchronized (_allSubscriptions) + { + serviceSubscriptions = _allSubscriptions[serviceSubscriptionKey]; + } + + if (!serviceSubscriptions || serviceSubscriptions.count == 0) + return; + + NSError *xmlParseError; + NSDictionary *requestDataXML = [CTXMLReader dictionaryForXMLData:request.data error:&xmlParseError]; + + if (xmlParseError) + { + DLog(@"XML Parse error %@", xmlParseError.description); + return; + } + + NSString *eventXMLStringEncoded = requestDataXML[@"e:propertyset"][@"e:property"][@"LastChange"][@"text"]; + + if (!eventXMLStringEncoded) + { + DLog(@"Received event with no LastChange data, ignoring..."); + return; + } + + NSError *eventXMLParseError; + NSDictionary *eventXML = [CTXMLReader dictionaryForXMLString:eventXMLStringEncoded + error:&eventXMLParseError]; + + if (eventXMLParseError) + { + DLog(@"Could not parse event into usable format, ignoring… (%@)", eventXMLParseError); + return; + } + + [self handleEvent:eventXML forSubscriptions:serviceSubscriptions]; +} + +- (void) handleEvent:(NSDictionary *)eventInfo forSubscriptions:(NSArray *)subscriptions +{ + DLog(@"eventInfo: %@", eventInfo); + + [subscriptions enumerateObjectsUsingBlock:^(ServiceSubscription *subscription, NSUInteger subIdx, BOOL *subStop) { + [subscription.successCalls enumerateObjectsUsingBlock:^(SuccessBlock success, NSUInteger successIdx, BOOL *successStop) { + dispatch_on_main(^{ + success(eventInfo); + }); + }]; + }]; +} + +#pragma mark - GCDWebServerDelegate + +- (void) webServerDidStart:(GCDWebServer *)server { } +- (void) webServerDidStop:(GCDWebServer *)server { } + +#pragma mark - Utility + +- (NSString *)getHostPath +{ + return [NSString stringWithFormat:@"http://%@:%d/", [self getIPAddress], 49291]; +} + +-(NSString *)getIPAddress +{ + NSString *address = @"error"; + struct ifaddrs *interfaces = NULL; + struct ifaddrs *temp_addr = NULL; + int success = 0; + + // retrieve the current interfaces - returns 0 on success + success = getifaddrs(&interfaces); + if (success == 0) + { + // Loop through linked list of interfaces + temp_addr = interfaces; + while(temp_addr != NULL) + { + if(temp_addr->ifa_addr->sa_family == AF_INET) + { + // Get NSString from C String + address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)]; + } + temp_addr = temp_addr->ifa_next; + } + } + + // Free memory + freeifaddrs(interfaces); + return address; +} + +@end diff --git a/core/Services/Helpers/DLNAHTTPServer_Private.h b/core/Services/Helpers/DLNAHTTPServer_Private.h new file mode 100644 index 00000000..8d3d41e7 --- /dev/null +++ b/core/Services/Helpers/DLNAHTTPServer_Private.h @@ -0,0 +1,29 @@ +// +// DLNAHTTPServer_Private.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 1/14/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DLNAHTTPServer.h" + +@class GCDWebServerDataRequest; + +@interface DLNAHTTPServer () + +- (void) processRequest:(GCDWebServerDataRequest *)request; + +@end diff --git a/core/Services/Helpers/LGCast/ConnectionManager.h b/core/Services/Helpers/LGCast/ConnectionManager.h new file mode 100644 index 00000000..e32b0942 --- /dev/null +++ b/core/Services/Helpers/LGCast/ConnectionManager.h @@ -0,0 +1,78 @@ +// +// ConnectionManager.h +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef ConnectionManager_h +#define ConnectionManager_h + +#import + +#import "ConnectableDevice.h" + +typedef NS_ENUM(int, ConnectionState) { + kConnectionStateNone, + kConnectionStateConnecting, + kConnectionStateConnected, + kConnectionStateDisconnecting +}; + +typedef NS_ENUM(int, ConnectionError) { + kConnectionErrorUnknown, + kConnectionErrorConnectionClosed, + kConnectionErrorDeviceShutdown, + kConnectionErrorRendererTerminated +}; + +typedef NS_ENUM(int, ServiceType) { + kServiceTypeScreenMirroring, + kServiceTypeRemoteCamera +}; + +@protocol ConnectionManagerDelegate + +- (void)onPairingRequested; +- (void)onPairingRejected; +- (void)onConnectionFailed:(NSString *)message; +- (void)onConnectionCompleted:(NSDictionary *)values; +- (void)onReceivePlayCommand:(NSDictionary *)values; +- (void)onReceiveStopCommand:(NSDictionary *)values; +- (void)onReceiveGetParameter:(NSDictionary *)values; +- (void)onReceiveSetParameter:(NSDictionary *)values; +- (void)onError:(ConnectionError)error message:(NSString *)message; + +@end + +@interface ConnectionManager : NSObject + +@property (nonatomic, weak) id delegate; + ++ (instancetype)sharedInstance; + +- (void)openConnection:(ServiceType)type device:(ConnectableDevice *)device; +- (void)setSourceDeviceInfo:(NSDictionary *)sourceInfo deviceInfo:(NSDictionary *)deviceInfo; +- (void)subscribe; +- (void)sendGetParameter; +- (void)sendSetParameter:(NSDictionary *)values; +- (void)sendSetParameter:(NSDictionary *)values ignoreResult:(BOOL)ignoreResult ; +- (void)sendGetParameterResponse:(NSDictionary *)values; +- (void)sendSetParameterResponse:(NSDictionary *)values; +- (void)closeConnection; + +@end + +#endif /* ConnectionManager_h */ diff --git a/core/Services/Helpers/LGCast/ConnectionManager.m b/core/Services/Helpers/LGCast/ConnectionManager.m new file mode 100644 index 00000000..f7c7c874 --- /dev/null +++ b/core/Services/Helpers/LGCast/ConnectionManager.m @@ -0,0 +1,436 @@ +// +// ConnectionManager.m +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +#import "WebOSTVService.h" +#import "ConnectableDevice.h" +#import "ServiceSubscription.h" + +#import "ConnectionManager.h" +#import "WebOSTVService+LGCast.h" + +@interface ConnectionManager () + +@property ServiceType serviceType; +@property NSString *serviceName; +@property ConnectionState currentState; +@property ConnectableDevice *tvDevice; +@property WebOSTVService *service; +@property ServiceSubscription *commandSubscription; +@property ServiceSubscription *powerStateSubscription; +@property NSTimer *keepAliveTimer; +@property double keepAlivePeriod; + +@property SuccessBlock nilSuccessBlock; +@property FailureBlock nilFailBlock; + +@end + +@implementation ConnectionManager + +NSString *const kCMKeyScreenMirrroringService = @"mirroring"; +NSString *const kCMKeyRemoteCameraService = @"camera"; + +NSString *const kCMKeyCommand = @"cmd"; +NSString *const kCMKeySubscribed = @"subscribed"; +NSString *const kCMKeyProcessing = @"processing"; +NSString *const kCMKeyClientKey = @"clientKey"; + +NSString *const kCMKeyCommandPlay = @"PLAY"; +NSString *const kCMKeyCommandStop = @"STOP"; +NSString *const kCMKeyCommandTeardown = @"TEARDOWN"; +NSString *const kCMKeyCommandSetParameter = @"SET_PARAMETER"; +NSString *const kCMKeyCommandGetParameter = @"GET_PARAMETER"; + +NSString *const kCMValueRequestPowerOff = @"Request Power Off"; + ++ (instancetype)sharedInstance { + static ConnectionManager *shared = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = [[self alloc] init]; + + shared.currentState = kConnectionStateNone; + shared.keepAlivePeriod = 50; + shared.nilSuccessBlock = ^(id responseObject) {}; + shared.nilFailBlock = ^(NSError* error) {}; + }); + + return shared; +} + +- (void)openConnection:(ServiceType)type device:(ConnectableDevice *)device { + [Log infoLGCast: @"openConnection"]; + + if (device == nil) return; + + _currentState = kConnectionStateConnecting; + + _serviceType = type; + _serviceName = type == kServiceTypeScreenMirroring ? kCMKeyScreenMirrroringService : kCMKeyRemoteCameraService; + _service = (WebOSTVService *)[device serviceWithName:kConnectSDKWebOSTVServiceId]; + + _tvDevice = device; + [_tvDevice setDelegate:self]; + [device connect]; +} + +- (void)sendGetParameter { + [Log infoLGCast:@"sendGetParameter"]; + + SuccessBlock successBlock = ^(NSDictionary *json) { + if (json == nil) { return; } + + [self callOnConnectionCompleted:json]; + }; + + FailureBlock failureBlock = ^(NSError *error) { + if (error != nil) { + [Log errorLGCast:error.localizedDescription]; + [self callOnConnectionFailed:@"sendGetParameter failure"]; + } + }; + + [_service sendGetParameterWithService:_serviceName success:successBlock failure:failureBlock]; +} + +- (void)sendSetParameter:(NSDictionary *)values { + [self sendSetParameter:values ignoreResult:NO]; +} + + +- (void)sendSetParameter:(NSDictionary *)values ignoreResult:(BOOL)ignoreResult { + [Log infoLGCast:@"sendSetParameter"]; + + FailureBlock failureBlock = ^(NSError *error) { + if (error != nil) { + [Log errorLGCast:error.localizedDescription]; + + if (!ignoreResult) { + [self callOnConnectionFailed:@"sendSetParameter failure"]; + } + } + }; + + [_service sendSetParameterWithService:_serviceName sourceInfo:values deviceInfo:nil success:_nilSuccessBlock failure:failureBlock]; +} + +- (void)sendGetParameterResponse:(NSDictionary *)values { + [Log infoLGCast:@"sendGetParameterResponse"]; + + if (_currentState != kConnectionStateConnected) { + [Log errorLGCast:[NSString stringWithFormat:@"Remote camera is not connected %d", _currentState]]; + return; + } + + [_service sendGetParameterResponseWithService:_serviceName values:values success:_nilSuccessBlock failure:_nilFailBlock]; +} + +- (void)sendSetParameterResponse:(NSDictionary *)values { + [Log infoLGCast:@"sendSetParameterResponse"]; + + if (_currentState != kConnectionStateConnected) { + [Log errorLGCast:[NSString stringWithFormat:@"Remote camera is not connected %d", _currentState]]; + return; + } + + [_service sendSetParameterResponseWithService:_serviceName values:values success:_nilSuccessBlock failure:_nilFailBlock]; +} + +- (void)closeConnection { + [Log infoLGCast:@"closeConnection"]; + + if (_currentState == kConnectionStateDisconnecting) { + return; + } + + _currentState = kConnectionStateDisconnecting; + + if (_keepAliveTimer != nil) { + [_keepAliveTimer invalidate]; + } + + [_service sendTeardownWithService:_serviceName success:_nilSuccessBlock failure:_nilFailBlock]; + + if (_powerStateSubscription != nil) { + [_powerStateSubscription unsubscribe]; + } + + if (_commandSubscription != nil) { + [_commandSubscription unsubscribe]; + } + + if (_tvDevice != nil) { + [_tvDevice disconnect]; + [_tvDevice setDelegate:nil]; + _tvDevice = nil; + } + + _currentState = kConnectionStateNone; +} + +- (void)setSourceDeviceInfo:(NSDictionary *)sourceInfo deviceInfo:(NSDictionary *)deviceInfo { + [Log infoLGCast:@"setSourceDeviceInfo"]; + + if (sourceInfo == nil || deviceInfo == nil) { + [Log errorLGCast:@"setSourceDeviceInfo failure"]; + [self callOnConnectionFailed:@"setSourceDeviceInfo failure"]; + return; + } + + SuccessBlock successBlock = ^(id responseObject) { + [self sendKeepAlive]; + }; + + FailureBlock failureBlock = ^(NSError *error) { + if (error != nil) { + [Log errorLGCast:error.localizedDescription]; + [self callOnConnectionFailed:@"sendSetParameter failure"]; + } + }; + + [_service sendSetParameterWithService:_serviceName sourceInfo:sourceInfo deviceInfo:deviceInfo success:successBlock failure:failureBlock]; +} + +- (void)sendKeepAlive { + if (_keepAliveTimer != nil) { + [_keepAliveTimer invalidate]; + } + + [_service sendKeepAliveWithService:_serviceName success:_nilSuccessBlock failure:_nilFailBlock]; + _keepAliveTimer = [NSTimer scheduledTimerWithTimeInterval:_keepAlivePeriod repeats:YES block:^(NSTimer * timer) { + [self->_service sendKeepAliveWithService:_serviceName success:_nilSuccessBlock failure:_nilFailBlock]; + }]; +} + +- (void)subscribe { + [Log infoLGCast:@"subscribe"]; + + if (!(_currentState == kConnectionStateConnected || _currentState == kConnectionStateConnecting)) { + [Log errorLGCast:[NSString stringWithFormat:@"Remote camera is not connected %d", _currentState]]; + return; + } + + [self commandSubscribe]; + [self powerStateSubscribe]; +} + +- (void)commandSubscribe { + SuccessBlock successBlock = ^(NSDictionary *response) { + if (response == nil) { return; } + + if (!(self.currentState == kConnectionStateConnected || self.currentState == kConnectionStateConnecting)) { + if (self.commandSubscription != nil) { + [self.commandSubscription unsubscribe]; + } + return; + } + + if (response[kCMKeySubscribed]) { + [self handleSubscribed:response]; + } else if (response[kCMKeyCommand]) { + [self handleCommand:response]; + } + }; + + FailureBlock failureBlock = ^(NSError *error) { + if (error != nil) { + [Log errorLGCast:error.localizedDescription]; + [self callOnError:kConnectionErrorConnectionClosed message:@"subscribe error"]; + } + }; + + _commandSubscription = [_service subscribeCommandWithSuccess:successBlock failure:failureBlock]; +} + +- (void)powerStateSubscribe { + SuccessBlock successBlock = ^(NSDictionary *response) { + if (response == nil) { return; } + + NSString *processing = response[kCMKeyProcessing]; + if (processing != nil && [processing caseInsensitiveCompare:kCMValueRequestPowerOff] == NSOrderedSame) { + [self callOnError:kConnectionErrorDeviceShutdown message:@"Device shut down"]; + } + }; + + _powerStateSubscription = [_service subscribePowerStateWithSuccess:successBlock failure:_nilFailBlock]; +} + +- (void)handleSubscribed:(NSDictionary *)response { + [Log infoLGCast:@"handleSubscribed"]; + + if (response[kCMKeySubscribed]) { + [self sendConnect]; + } else { + [self callOnConnectionFailed:@"subscribe failure"]; + } +} + +- (void)handleCommand:(NSDictionary *)response { + [Log infoLGCast:@"handleCommand"]; + + NSString *clientKey = response[kCMKeyClientKey]; + NSString *currentClientKey = _service.webOSTVServiceConfig.clientKey; + + if (clientKey != nil && [clientKey caseInsensitiveCompare:currentClientKey] != NSOrderedSame) { + [Log errorLGCast:@"Client Key not matched!"]; + return; + } + + NSString *command = response[kCMKeyCommand]; + + if (command == nil) return; + + if ([command caseInsensitiveCompare:kCMKeyCommandPlay] == NSOrderedSame) { + [self callOnReceivePlayCommand:response]; + } + + if ([command caseInsensitiveCompare:kCMKeyCommandTeardown] == NSOrderedSame) { + [self callOnError:kConnectionErrorRendererTerminated message:@"renderer terminated"]; + } + + if ([command caseInsensitiveCompare:kCMKeyCommandStop] == NSOrderedSame) { + [self callOnReceiveStopCommand:response]; + } + + if ([command caseInsensitiveCompare:kCMKeyCommandGetParameter] == NSOrderedSame) { + [self callOnReceiveGetParameter:response]; + } + + if ([command caseInsensitiveCompare:kCMKeyCommandSetParameter] == NSOrderedSame) { + [self callOnReceiveSetParameter:response]; + } +} + +- (void)sendConnect { + [Log infoLGCast:@"sendConnect"]; + + SuccessBlock successBlock = ^(id responseObject) { + [self sendGetParameter]; + }; + + FailureBlock failureBlock = ^(NSError *error) { + if (error != nil) { + [Log errorLGCast:error.localizedDescription]; + [self callOnConnectionFailed:@"sendConnect failure"]; + } + }; + + [_service sendConnectWithService:_serviceName success:successBlock failure:failureBlock]; +} + +- (void)callOnPairingRequested { + +} + +- (void)callOnPairingRejected { + [Log infoLGCast:@"callOnPairingRejected"]; + _currentState = kConnectionStateNone; + + if (_delegate && [_delegate respondsToSelector:@selector(onPairingRejected)]) { + [_delegate onPairingRejected]; + } +} + +- (void)callOnConnectionFailed:(NSString *)message { + [Log infoLGCast:@"callOnConnectionFailed"]; + + [self closeConnection]; + _currentState = kConnectionStateNone; + + if (_delegate && [_delegate respondsToSelector:@selector(onConnectionFailed:)]) { + [_delegate onConnectionFailed:message]; + } +} + +- (void)callOnConnectionCompleted:(NSDictionary *)values { + _currentState = kConnectionStateConnected; + + if (_delegate && [_delegate respondsToSelector:@selector(onConnectionCompleted:)]) { + [_delegate onConnectionCompleted:values]; + } +} + +- (void)callOnReceivePlayCommand:(NSDictionary *)values { + if (_delegate && [_delegate respondsToSelector:@selector(onReceivePlayCommand:)]) { + [_delegate onReceivePlayCommand:values]; + } +} + +- (void)callOnReceiveStopCommand:(NSDictionary *)values { + if (_delegate && [_delegate respondsToSelector:@selector(onReceiveStopCommand:)]) { + [_delegate onReceiveStopCommand:values]; + } +} + +- (void)callOnReceiveGetParameter:(NSDictionary *)values { + if (_delegate && [_delegate respondsToSelector:@selector(onReceiveGetParameter:)]) { + [_delegate onReceiveGetParameter:values]; + } +} + +- (void)callOnReceiveSetParameter:(NSDictionary *)values { + if (_delegate && [_delegate respondsToSelector:@selector(onReceiveSetParameter:)]) { + [_delegate onReceiveSetParameter:values]; + } +} + +- (void)callOnError:(ConnectionError)error message:(NSString *)message { + [Log infoLGCast:@"callOnError"]; + + [self closeConnection]; + _currentState = kConnectionStateNone; + + if (_delegate && [_delegate respondsToSelector:@selector(onError:message:)]) { + [_delegate onError:error message:message]; + } +} + +// MARK: ConnectableDeviceDelegate +- (void)connectableDeviceDisconnected:(ConnectableDevice *)device withError:(NSError *)error { + [Log infoLGCast:[NSString stringWithFormat:@"connectableDevice disconnectedWithError: %@", error.localizedDescription]]; + + switch (_currentState) { + case kConnectionStateConnecting: + [self callOnPairingRejected]; + break; + case kConnectionStateConnected: + [self callOnError:kConnectionErrorDeviceShutdown message:@"device disconnected"]; + break; + default: + [Log infoLGCast:[NSString stringWithFormat:@"Ignore event state: %d", _currentState]]; + break; + } +} + +- (void)connectableDeviceReady:(ConnectableDevice *)device { + [Log infoLGCast:@"connectableDeviceReady"]; + + [self subscribe]; +} + +- (void)connectableDevice:(ConnectableDevice *)device service:(DeviceService *)service pairingRequiredOfType:(int)pairingType withData:(id)pairingData { + [Log infoLGCast:@"connectableDevice pairingRequiredOfType"]; + + [self callOnPairingRequested]; +} + +@end diff --git a/core/Services/Helpers/LGCast/Device/MobileCapability.h b/core/Services/Helpers/LGCast/Device/MobileCapability.h new file mode 100644 index 00000000..44414c3d --- /dev/null +++ b/core/Services/Helpers/LGCast/Device/MobileCapability.h @@ -0,0 +1,38 @@ +// +// MobileCapability.h +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MobileCapability : NSObject + +@property NSString *type; +@property NSString *version; +@property NSString *platform; +@property NSString *manufacturer; +@property NSString *modelName; +@property NSString *deviceName; + +- (id)init; +- (NSDictionary*)toNSDictionary; + +@end + +NS_ASSUME_NONNULL_END diff --git a/core/Services/Helpers/LGCast/Device/MobileCapability.m b/core/Services/Helpers/LGCast/Device/MobileCapability.m new file mode 100644 index 00000000..6f4bd1aa --- /dev/null +++ b/core/Services/Helpers/LGCast/Device/MobileCapability.m @@ -0,0 +1,50 @@ +// +// MobileCapability.m +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "MobileCapability.h" + +#import + +@implementation MobileCapability + +-(id)init { + self = [super init]; + _type = @"phone"; + _version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]; + _platform = @"ios"; + _manufacturer = @"apple"; + _modelName = UIDevice.currentDevice.model; + _deviceName = UIDevice.currentDevice.name; + + return self; +} + +- (NSDictionary*)toNSDictionary { + NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys: + _type, @"type", + _version, @"version", + _platform, @"platform", + _manufacturer, @"manufacturer", + _modelName, @"modelName", + _deviceName, @"deviceName", nil]; + + return options; +} + +@end diff --git a/core/Services/Helpers/LGCast/RemoteCameraService/CameraParameter.h b/core/Services/Helpers/LGCast/RemoteCameraService/CameraParameter.h new file mode 100644 index 00000000..cfadd3e8 --- /dev/null +++ b/core/Services/Helpers/LGCast/RemoteCameraService/CameraParameter.h @@ -0,0 +1,48 @@ +// +// CameraParameter.h +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef CameraParameter_h +#define CameraParameter_h + +#import + +@interface CameraParameter : NSObject + +@property NSInteger width; +@property NSInteger height; +@property NSInteger brightness; +@property NSInteger whiteBalance; +@property BOOL autoWhiteBalance; +@property NSInteger facing; +@property BOOL audio; +@property NSInteger rotation; + +- (NSDictionary *)toNSDictionary; +- (NSDictionary *)toNSDictionaryResolutionResult:(BOOL)result width:(int)width height:(int)height; +- (NSDictionary *)toNSDictionaryBritnessResult:(BOOL)result brightness:(int)brightness; +- (NSDictionary *)toNSDictionaryWhiteBalanceResult:(BOOL)result whiteBalance:(int)whiteBalance; +- (NSDictionary *)toNSDictionaryAutoWhiteBalanceResult:(BOOL)result autoWhiteBalance:(BOOL)autoWhiteBalance; +- (NSDictionary *)toNSDictionaryFacing:(int)facing; +- (NSDictionary *)toNSDictionaryFacingResult:(BOOL)result facing:(int)facing; +- (NSDictionary *)toNSDictionaryAudio:(BOOL)audio; +- (NSDictionary *)toNSDictionaryAudioResult:(BOOL)result audio:(BOOL)audio; + +@end + +#endif /* CameraParameter_h */ diff --git a/core/Services/Helpers/LGCast/RemoteCameraService/CameraParameter.m b/core/Services/Helpers/LGCast/RemoteCameraService/CameraParameter.m new file mode 100644 index 00000000..b5295777 --- /dev/null +++ b/core/Services/Helpers/LGCast/RemoteCameraService/CameraParameter.m @@ -0,0 +1,122 @@ +// +// CameraParameter.m +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "CameraParameter.h" +#import "RemoteCameraService.h" + +@implementation CameraParameter + +- (NSDictionary *)toNSDictionary { + return @{ + kRCKeyWidth : [NSNumber numberWithLong:_width], + kRCKeyHeight : [NSNumber numberWithLong:_height], + kRCKeyBrightness : [NSNumber numberWithLong:_brightness], + kRCKeyWhiteBalance : [NSNumber numberWithLong:_whiteBalance], + kRCKeyAutoWhiteBalance : [NSNumber numberWithBool:_autoWhiteBalance], + kRCKeyFacing : [NSNumber numberWithLong:_facing], + kRCKeyAudio : [NSNumber numberWithBool:_audio], + kRCKeyRotation : [NSNumber numberWithLong:_rotation] + }; +} + +- (NSDictionary *)toNSDictionaryResolutionResult:(BOOL)result width:(int)width height:(int)height { + if (result) { + _width = width; + _height = height; + } + + return @{ + kRCKeyResult : [NSNumber numberWithBool:result], + kRCKeyWidth : [NSNumber numberWithLong:_width], + kRCKeyHeight : [NSNumber numberWithLong:_height] + }; +} + +- (NSDictionary *)toNSDictionaryBritnessResult:(BOOL)result brightness:(int)brightness { + if (result) { + _brightness = brightness; + } + + return @{ + kRCKeyResult : [NSNumber numberWithBool:result], + kRCKeyBrightness : [NSNumber numberWithLong:_brightness] + }; +} + +- (NSDictionary *)toNSDictionaryWhiteBalanceResult:(BOOL)result whiteBalance:(int)whiteBalance { + if (result) { + _whiteBalance = whiteBalance; + } + + return @{ + kRCKeyResult : [NSNumber numberWithBool:result], + kRCKeyWhiteBalance : [NSNumber numberWithLong:_whiteBalance] + }; +} + +- (NSDictionary *)toNSDictionaryAutoWhiteBalanceResult:(BOOL)result autoWhiteBalance:(BOOL)autoWhiteBalance { + if (result) { + _autoWhiteBalance = autoWhiteBalance; + } + + return @{ + kRCKeyResult : [NSNumber numberWithBool:result], + kRCKeyAutoWhiteBalance : [NSNumber numberWithBool:_autoWhiteBalance] + }; +} + +- (NSDictionary *)toNSDictionaryFacing:(int)facing { + _facing = facing; + + return @{ + kRCKeyFacing : [NSNumber numberWithLong:_facing] + }; +} + +- (NSDictionary *)toNSDictionaryFacingResult:(BOOL)result facing:(int)facing { + if (result) { + _facing = facing; + } + + return @{ + kRCKeyResult : [NSNumber numberWithBool:result], + kRCKeyFacing : [NSNumber numberWithLong:_facing] + }; +} + +- (NSDictionary *)toNSDictionaryAudio:(BOOL)audio { + _audio = audio; + + return @{ + kRCKeyAudio : [NSNumber numberWithBool:audio] + }; +} + +- (NSDictionary *)toNSDictionaryAudioResult:(BOOL)result audio:(BOOL)audio { + if (result) { + _audio = audio; + } + + return @{ + kRCKeyResult : [NSNumber numberWithBool:result], + kRCKeyAudio : [NSNumber numberWithBool:audio] + }; +} + +@end diff --git a/core/Services/Helpers/LGCast/RemoteCameraService/CameraSinkCapability.h b/core/Services/Helpers/LGCast/RemoteCameraService/CameraSinkCapability.h new file mode 100644 index 00000000..d818db91 --- /dev/null +++ b/core/Services/Helpers/LGCast/RemoteCameraService/CameraSinkCapability.h @@ -0,0 +1,40 @@ +// +// CameraSinkCapability.h +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef CameraSinkCapability_h +#define CameraSinkCapability_h + +#import + +@interface CameraSinkCapability : NSObject + +@property NSString *ipAddress; +@property double keepAliveTimeout; +@property NSString *publicKey; + +@property (readonly) NSString *deviceType; +@property (readonly) NSString *deviceVersion; +@property (readonly) NSString *devicePlatform; +@property (readonly) NSString *deviceSoC; + +- (id)initWithJSON:(NSDictionary *)jsonObject; + +@end + +#endif /* CameraSinkCapability_h */ diff --git a/core/Services/Helpers/LGCast/RemoteCameraService/CameraSinkCapability.m b/core/Services/Helpers/LGCast/RemoteCameraService/CameraSinkCapability.m new file mode 100644 index 00000000..4d0e115a --- /dev/null +++ b/core/Services/Helpers/LGCast/RemoteCameraService/CameraSinkCapability.m @@ -0,0 +1,52 @@ +// +// CameraSinkCapability.m +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "CameraSinkCapability.h" + +@implementation CameraSinkCapability + +NSString *const kRCKeySinkIpAddress = @"ipAddress"; +NSString *const kRCKeySinkKeepAliveTimeout = @"keepAliveTimeout"; +NSString *const kRCKeySinkPublicKey = @"publicKey"; + +NSString *const kRCKeyDeviceInfo = @"deviceInfo"; +NSString *const kRCKeyDeviceInfoType = @"type"; +NSString *const kRCKeyDeviceInfoVersion = @"version"; +NSString *const kRCKeyDeviceInfoPlatform = @"platform"; +NSString *const kRCKeyDeviceInfoSoC = @"SoC"; + +- (id)initWithJSON:(NSDictionary *)jsonObject { + self = [super init]; + + _ipAddress = [jsonObject valueForKey:kRCKeySinkIpAddress]; + _keepAliveTimeout = [[jsonObject valueForKey:kRCKeySinkKeepAliveTimeout] doubleValue]; + _publicKey = [jsonObject valueForKey:kRCKeySinkPublicKey]; + + NSDictionary *deviceInfoObj = [jsonObject valueForKey:kRCKeyDeviceInfo]; + if (deviceInfoObj != NULL) { + _deviceType = [deviceInfoObj valueForKey:kRCKeyDeviceInfoType]; + _deviceVersion = [deviceInfoObj valueForKey:kRCKeyDeviceInfoVersion]; + _devicePlatform = [deviceInfoObj valueForKey:kRCKeyDeviceInfoPlatform]; + _deviceSoC = [deviceInfoObj valueForKey:kRCKeyDeviceInfoSoC]; + } + + return self; +} + +@end diff --git a/core/Services/Helpers/LGCast/RemoteCameraService/CameraSourceCapability.h b/core/Services/Helpers/LGCast/RemoteCameraService/CameraSourceCapability.h new file mode 100644 index 00000000..dedb0efb --- /dev/null +++ b/core/Services/Helpers/LGCast/RemoteCameraService/CameraSourceCapability.h @@ -0,0 +1,35 @@ +// +// CameraSourceCapability.h +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef CameraSourceCapability_h +#define CameraSourceCapability_h + +#import +#import + +@interface CameraSourceCapability : NSObject + +@property NSArray *resolutions; + +- (void)setSecurityKeys:(NSArray *)keys; +- (NSDictionary *)toNSDictionary; + +@end + +#endif /* CameraSourceCapability_h */ diff --git a/core/Services/Helpers/LGCast/RemoteCameraService/CameraSourceCapability.m b/core/Services/Helpers/LGCast/RemoteCameraService/CameraSourceCapability.m new file mode 100644 index 00000000..3bddde52 --- /dev/null +++ b/core/Services/Helpers/LGCast/RemoteCameraService/CameraSourceCapability.m @@ -0,0 +1,64 @@ +// +// CameraSourceCapability.m +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "CameraSourceCapability.h" +@interface CameraSourceCapability() + @property NSArray *masterKeys; +@end + +@implementation CameraSourceCapability + +NSString *const kRCKeyCrypto = @"crypto"; +NSString *const kRCKeyCryptoMki = @"mki"; +NSString *const kRCKeyCryptokey = @"key"; + +NSString *const kRCKeyPreviewSize = @"previewSize"; +NSString *const kRCKeyPreviewSizeW = @"w"; +NSString *const kRCKeyPreviewSizeH = @"h"; + +- (void)setSecurityKeys:(NSArray *)keys { + _masterKeys = keys; +} + +- (NSDictionary *)toNSDictionary { + NSMutableArray *cryptoSpec = [[NSMutableArray alloc] init]; + + for (LGCastSecurityKey *key in _masterKeys) { + [cryptoSpec addObject:@{ + kRCKeyCryptoMki: key.mki, + kRCKeyCryptokey: key.masterKey + }]; + } + + NSMutableArray *previewSize = [[NSMutableArray alloc] init]; + + for (LGCastCameraResolutionInfo *resolution in _resolutions) { + [previewSize addObject:@{ + kRCKeyPreviewSizeW: [NSNumber numberWithLong:resolution.width], + kRCKeyPreviewSizeH: [NSNumber numberWithLong:resolution.height] + }]; + } + + return @{ + kRCKeyCrypto: cryptoSpec, + kRCKeyPreviewSize: previewSize + }; +} + +@end diff --git a/core/Services/Helpers/LGCast/RemoteCameraService/RemoteCameraService.h b/core/Services/Helpers/LGCast/RemoteCameraService/RemoteCameraService.h new file mode 100644 index 00000000..2a23959d --- /dev/null +++ b/core/Services/Helpers/LGCast/RemoteCameraService/RemoteCameraService.h @@ -0,0 +1,70 @@ +// +// RemoteCameraService.h +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef RemoteCameraService_h +#define RemoteCameraService_h + +#import +#import + +#import "ConnectableDevice.h" +#import "RemoteCameraControl.h" + +@protocol RemoteCameraServiceDelegate + +- (void)remoteCameraDidPair; +- (void)remoteCameraDidStart:(BOOL)result; +- (void)remoteCameraDidStop:(BOOL)result; +- (void)remoteCameraDidPlay; +- (void)remoteCameraDidChange:(RemoteCameraProperty)property; +- (void)remoteCameraErrorDidOccur:(RemoteCameraError)error; + +@end + +extern NSString *const kRCKeyCamera; + +extern NSString *const kRCKeyResult; + +extern NSString *const kRCKeyVideoPort; +extern NSString *const kRCKeyAudioPort; +extern NSString *const kRCKeyWidth; +extern NSString *const kRCKeyHeight; + +extern NSString *const kRCKeyFacing; +extern NSString *const kRCKeyAudio; +extern NSString *const kRCKeyBrightness; +extern NSString *const kRCKeyWhiteBalance; +extern NSString *const kRCKeyAutoWhiteBalance; +extern NSString *const kRCKeyRotation; + +@interface RemoteCameraService : NSObject + +@property (nonatomic, weak) id delegate; +@property (readonly) BOOL isRunning; + ++ (instancetype)sharedInstance; + +- (UIView *)startRemoteCamera:(ConnectableDevice *)device settings:(nullable NSDictionary *)settings; +- (void)stopRemoteCamera; +- (void)setLensFacing:(int)lensFacing; +- (void)setMicMute:(BOOL)micMute; + +@end + +#endif /* RemoteCameraService_h */ diff --git a/core/Services/Helpers/LGCast/RemoteCameraService/RemoteCameraService.m b/core/Services/Helpers/LGCast/RemoteCameraService/RemoteCameraService.m new file mode 100644 index 00000000..53cf55b4 --- /dev/null +++ b/core/Services/Helpers/LGCast/RemoteCameraService/RemoteCameraService.m @@ -0,0 +1,425 @@ +// +// RemoteCameraService.m +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#import "ConnectionManager.h" + +#import "RemoteCameraService.h" +#import "CameraParameter.h" +#import "CameraSourceCapability.h" +#import "CameraSinkCapability.h" +#import "MobileCapability.h" + +NSString *const kRCKeyCamera = @"camera"; + +NSString *const kRCKeyResult = @"result"; + +NSString *const kRCKeyVideoPort = @"videoPort"; +NSString *const kRCKeyAudioPort = @"audioPort"; +NSString *const kRCKeyWidth = @"width"; +NSString *const kRCKeyHeight = @"height"; + +NSString *const kRCKeyFacing = @"facing"; +NSString *const kRCKeyAudio = @"audio"; +NSString *const kRCKeyBrightness = @"brightness"; +NSString *const kRCKeyWhiteBalance = @"whiteBalance"; +NSString *const kRCKeyAutoWhiteBalance = @"autoWhiteBalance"; +NSString *const kRCKeyRotation = @"rotation"; + +@interface RemoteCameraService () + +@property ConnectionManager *connectionManager; +@property BOOL isRunning; +@property BOOL isPlaying; + +@property CameraParameter *cameraParameter; +@property CameraSourceCapability *sourceCapability; +@property CameraSinkCapability *sinkCapability; + +@end + +@implementation RemoteCameraService + ++ (instancetype)sharedInstance { + static RemoteCameraService *shared = nil; + static dispatch_once_t token; + dispatch_once(&token, ^{ + shared = [[RemoteCameraService alloc] initPrivate]; + }); + + return shared; +} + +- (instancetype)init { + return [[self class] sharedInstance]; +} + +- (instancetype)initPrivate { + self = [super init]; + + _isRunning = NO; + _isPlaying = NO; + _connectionManager = ConnectionManager.sharedInstance; + _connectionManager.delegate = self; + [[LGCastCameraApi shared] setDelegate:self]; + + return self; +} + +- (UIView *)startRemoteCamera:(ConnectableDevice *)device settings:(nullable NSDictionary *)settings { + [Log infoLGCast:@"startRemoteCamera"]; + + UIView *previewView; + if (self.isRunning == NO) { + self.isRunning = YES; + self.isPlaying = NO; + previewView = [[LGCastCameraApi shared] createCameraPreviewView:nil]; + + if (settings != nil) { + if (settings[kRemoteCameraSettingsMicMute] != nil) { + bool isMicMute = [settings[kRemoteCameraSettingsMicMute] boolValue]; + [[LGCastCameraApi shared] muteMicrophone:isMicMute]; + } + + if (settings[kRemoteCameraSettingsLensFacing] != nil ) { + int lensFacing = [settings[kRemoteCameraSettingsLensFacing] intValue]; + [[LGCastCameraApi shared] changeCameraPosition:lensFacing]; + } + } + + [self updateCameraParameter]; + [_connectionManager openConnection:kServiceTypeRemoteCamera device:device]; + } else { + [self sendStartEvent:NO]; + } + + return previewView; +} + +- (void)stopRemoteCamera { + [Log infoLGCast:@"stopRemoteCamera"]; + + if (self.isRunning == YES) { + self.isRunning = NO; + self.isPlaying = NO; + [[LGCastCameraApi shared] stopRemoteCamera]; + [_connectionManager closeConnection]; + [self sendStopEvent:YES]; + } else { + [self sendStopEvent:NO]; + } +} + +- (void)setLensFacing:(int)lensFacing { + [Log infoLGCast:@"setLensFacing"]; + + BOOL result = [[LGCastCameraApi shared] changeCameraPosition:lensFacing]; + if (_isPlaying && result) { + NSDictionary *values = [_cameraParameter toNSDictionaryFacing:lensFacing]; + [_connectionManager sendSetParameter:values]; + } +} + +- (void)setMicMute:(BOOL)micMute { + [Log infoLGCast:@"setMicMute"]; + + BOOL result = [[LGCastCameraApi shared] muteMicrophone:micMute]; + if (_isPlaying && result) { + NSDictionary *values = [_cameraParameter toNSDictionaryAudio:micMute]; + [_connectionManager sendSetParameter:values]; + } +} + +// MARK: PRIVATE FUNCTIONS +- (void)updateCameraParameter { + if (_cameraParameter == nil) { + _cameraParameter = [[CameraParameter alloc] init]; + } + + LGCastCameraParameterInfo *info = [[LGCastCameraApi shared] getCameraProperties]; + if (info == nil) { return; } + + _cameraParameter.audio = info.audio == LGCastCamperaPropertyStatusEnable ? YES : NO; + _cameraParameter.autoWhiteBalance = info.autoWhiteBalance == LGCastCamperaPropertyStatusEnable ? YES : NO; + _cameraParameter.brightness = info.brightness; + _cameraParameter.facing = info.facing; + _cameraParameter.whiteBalance = info.whiteBalance; + _cameraParameter.rotation = info.rotation; + _cameraParameter.width = info.width; + _cameraParameter.height = info.height; +} + +// MARK: ConnectionManagerDelegate + +- (void)onPairingRequested { + [Log infoLGCast:@"onPairingRequested"]; + + [self sendPairEvent]; +} + +- (void)onPairingRejected { + [Log infoLGCast:@"onPairingRejected"]; + + self.isRunning = NO; + [self sendStartEvent:NO]; +} + +- (void)onConnectionFailed:(NSString *)message { + [Log infoLGCast:@"onConnectionFailed"]; + + self.isRunning = NO; + self.isPlaying = NO; + [self sendStartEvent:NO]; +} + +- (void)onConnectionCompleted:(NSDictionary *)values { + [Log infoLGCast:@"onConnectionCompleted"]; + + _sinkCapability = [[CameraSinkCapability alloc] initWithJSON:values]; + + _sourceCapability = [[CameraSourceCapability alloc] init]; + NSArray *keys = [[LGCastCameraApi shared] generateCameraMasterKey:_sinkCapability.publicKey]; + [_sourceCapability setSecurityKeys:keys]; + _sourceCapability.resolutions = [[LGCastCameraApi shared] getSupportedResolutions]; + + MobileCapability *mobileCapability = [[MobileCapability alloc] init]; + + [_connectionManager setSourceDeviceInfo:[_sourceCapability toNSDictionary] + deviceInfo:[mobileCapability toNSDictionary]]; + [self sendStartEvent:YES]; +} + +- (void)onReceivePlayCommand:(NSDictionary *)values { + [Log infoLGCast:@"onReceivePlayCommand"]; + + NSDictionary *cameraObj = values[kRCKeyCamera]; + + if (cameraObj == nil) { + [Log errorLGCast:@"invalid parameter"]; + return; + } + + int videoPort = [cameraObj[kRCKeyVideoPort] intValue]; + int audioPort = [cameraObj[kRCKeyAudioPort] intValue]; + int width = [cameraObj[kRCKeyWidth] intValue]; + int height = [cameraObj[kRCKeyHeight] intValue]; + + LGCastCameraResolutionInfo *resolution = [[LGCastCameraResolutionInfo alloc] init]; + resolution.width = width; + resolution.height = height; + [[LGCastCameraApi shared] setResolution:resolution]; + + LGCastDeviceSettings *settings = [[LGCastDeviceSettings alloc] init]; + settings.host = _sinkCapability.ipAddress; + settings.videoPort = videoPort; + settings.audioPort = audioPort; + + [[LGCastCameraApi shared] startRemoteCamera:settings]; + [self sendPlayEvent]; + self.isPlaying = YES; +} + +- (void)onReceiveStopCommand:(NSDictionary *)values { + [Log infoLGCast:@"onReceiveStopCommand"]; + + [[LGCastCameraApi shared] stopRemoteCamera]; + self.isPlaying = NO; +} + +- (void)onReceiveGetParameter:(NSDictionary *)values { + [Log infoLGCast:@"onReceiveGetParameter"]; + + [self updateCameraParameter]; + [_connectionManager sendGetParameterResponse:[_cameraParameter toNSDictionary]]; +} + +- (void)onReceiveSetParameter:(NSDictionary *)values { + [Log infoLGCast:@"onReceiveSetParameter"]; + + NSDictionary *cameraObj = values[kRCKeyCamera]; + + if (cameraObj == nil) { + [Log errorLGCast:@"invalid parameter"]; + return; + } + + BOOL result = NO; + NSDictionary *response; + + if (cameraObj[kRCKeyBrightness]) { + int brightness = [cameraObj[kRCKeyBrightness] intValue]; + + result = [[LGCastCameraApi shared] setCameraPropertiesWithProperty:LGCastCameraPropertyBrightness value:brightness]; + response = [_cameraParameter toNSDictionaryBritnessResult:result brightness:brightness]; + [self sendChangeEvent:RemoteCameraPropertyBrightness]; + } + + if (cameraObj[kRCKeyWhiteBalance]) { + int whiteBalance = [cameraObj[kRCKeyWhiteBalance] intValue]; + + result = [[LGCastCameraApi shared] setCameraPropertiesWithProperty:LGCastCameraPropertyWhitebalance value:whiteBalance]; + response = [_cameraParameter toNSDictionaryWhiteBalanceResult:result whiteBalance:whiteBalance]; + + [self sendChangeEvent:RemoteCameraPropertyWhiteBalance]; + } + + if (cameraObj[kRCKeyAutoWhiteBalance]) { + BOOL autoWhiteBalance = [cameraObj[kRCKeyAutoWhiteBalance] boolValue]; + + result = [[LGCastCameraApi shared] setCameraPropertiesWithProperty:LGCastCameraPropertyAutoWhiteBalance value:autoWhiteBalance]; + response = [_cameraParameter toNSDictionaryAutoWhiteBalanceResult:result autoWhiteBalance:autoWhiteBalance]; + } + + if (values[kRCKeyFacing]) { + int facing = [values[kRCKeyFacing] intValue]; + + result = [[LGCastCameraApi shared] setCameraPropertiesWithProperty:LGCastCameraPropertyFacing value:facing]; + response = [_cameraParameter toNSDictionaryFacingResult:result facing:facing]; + } + + if (cameraObj[kRCKeyAudio]) { + BOOL audio = [cameraObj[kRCKeyAudio] boolValue]; + + result = [[LGCastCameraApi shared] setCameraPropertiesWithProperty:LGCastCameraPropertyAudio value:audio]; + response = [_cameraParameter toNSDictionaryAudioResult:result audio:audio]; + } + + if (response != nil) { + [_connectionManager sendSetParameterResponse:response]; + } +} + +- (void)onError:(ConnectionError)error message:(NSString *)message { + [Log errorLGCast:[NSString stringWithFormat:@"onError %d %@", error, message]]; + + RemoteCameraError controlError = RemoteCameraErrorGeneric; + switch (error) { + case kConnectionErrorUnknown: + [Log errorLGCast:@"kConnectionErrorUnknown"]; + controlError = RemoteCameraErrorGeneric; + break; + case kConnectionErrorConnectionClosed: + [Log errorLGCast:@"kConnectionErrorConnectionClosed"]; + + controlError = RemoteCameraErrorConnectionClosed; + break; + case kConnectionErrorDeviceShutdown: + [Log errorLGCast:@"kConnectionErrorDeviceShutdown"]; + + controlError = RemoteCameraErrorDeviceShutdown; + break; + case kConnectionErrorRendererTerminated: + [Log errorLGCast:@"kConnectionErrorRendererTerminated"]; + controlError = RemoteCameraErrorRendererTerminated; + break; + default: + break; + } + + self.isRunning = NO; + self.isPlaying = NO; + [[LGCastCameraApi shared] stopRemoteCamera]; + [_connectionManager closeConnection]; + + [self sendErrorEvent:controlError]; +} + +// MARK: LGCastCameraApiDelegate + +- (void)lgcastCameraDidChangeWithProperty:(LGCastCameraProperty)property { + [Log infoLGCast:@"lgcastCameraDidChangeWithProperty"]; + + if (!self.isPlaying) { + return; + } + + if (property == LGCastCameraPropertyRotation) { + [self updateCameraParameter]; + [_connectionManager sendSetParameter:@{ + kRCKeyRotation: [NSNumber numberWithLong:_cameraParameter.rotation] + }]; + } else { + RemoteCameraProperty type = RemoteCameraPropertyUnknown; + + switch (type) { + case LGCastCameraPropertyBrightness: + type = RemoteCameraPropertyBrightness; + break; + case LGCastCameraPropertyWhitebalance: + type = RemoteCameraPropertyWhiteBalance; + break; + default: + break; + } + + [self sendChangeEvent:type]; + } +} + +- (void)lgcastCameraDidPlay { + [Log infoLGCast:@"lgcastCameraDidPlay"]; + + [self sendPlayEvent]; +} + +- (void)lgcastCameraErrorDidOccurWithError:(LGCastCameraError)error { + [Log errorLGCast:[NSString stringWithFormat:@"lgcastCameraErrorDidOccurWithError %ld", (long)error]]; + + self.isRunning = NO; + [self sendErrorEvent:error]; +} + +- (void)sendPairEvent { + if (_delegate && [_delegate respondsToSelector:@selector(remoteCameraDidPair)]) { + [_delegate remoteCameraDidPair]; + } +} + +- (void)sendStartEvent:(BOOL)result { + if (_delegate && [_delegate respondsToSelector:@selector(remoteCameraDidStart:)]) { + [_delegate remoteCameraDidStart:result]; + } +} + +- (void)sendStopEvent:(BOOL)result { + if (_delegate && [_delegate respondsToSelector:@selector(remoteCameraDidStop:)]) { + [_delegate remoteCameraDidStop:result]; + } +} + +- (void)sendPlayEvent { + if (_delegate && [_delegate respondsToSelector:@selector(remoteCameraDidPlay)]) { + [_delegate remoteCameraDidPlay]; + } +} + +- (void)sendChangeEvent:(RemoteCameraProperty)property { + if (_delegate && [_delegate respondsToSelector:@selector(remoteCameraDidChange:)]) { + [_delegate remoteCameraDidChange:property]; + } +} + +- (void)sendErrorEvent:(RemoteCameraError)error { + if (_delegate && [_delegate respondsToSelector:@selector(remoteCameraErrorDidOccur:)]) { + [_delegate remoteCameraErrorDidOccur:error]; + } +} + +@end diff --git a/core/Services/Helpers/LGCast/ScreenMirroringService/MirroringSinkCapability.h b/core/Services/Helpers/LGCast/ScreenMirroringService/MirroringSinkCapability.h new file mode 100644 index 00000000..d866fa79 --- /dev/null +++ b/core/Services/Helpers/LGCast/ScreenMirroringService/MirroringSinkCapability.h @@ -0,0 +1,37 @@ +// +// MirroringSinkCapability.h +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MirroringSinkCapability: NSObject + +@property NSString *ipAddress; +@property double keepAliveTimeout; +@property NSString *publicKey; +@property NSInteger videoUdpPort; +@property NSInteger audioUdpPort; +@property NSString *displayOrientation; + +- (id)initWithInfo:(NSDictionary*)info; + +@end + +NS_ASSUME_NONNULL_END diff --git a/core/Services/Helpers/LGCast/ScreenMirroringService/MirroringSinkCapability.m b/core/Services/Helpers/LGCast/ScreenMirroringService/MirroringSinkCapability.m new file mode 100644 index 00000000..cb2b2c4c --- /dev/null +++ b/core/Services/Helpers/LGCast/ScreenMirroringService/MirroringSinkCapability.m @@ -0,0 +1,119 @@ +// +// MirroringSinkCapability.m +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "MirroringSinkCapability.h" + +@interface MirroringSinkCapability() + +@property NSString *deviceType; +@property NSString *deviceVersion; +@property NSString *devicePlatform; +@property NSString *deviceSoC; + +@property NSString *videoCodec; +@property NSString *audioCodec; + +@property NSInteger videoPortraitMaxWidth; +@property NSInteger videoPortraitMaxHeight; +@property NSInteger videoLandscapeMaxWidth; +@property NSInteger videoLandscapeMaxHeight; + +@property NSString *supportedOrientation; + +@end + +@implementation MirroringSinkCapability + +#define DEFAULT_SCREEN_WIDTH 1920 +#define DEFAULT_SCREEN_HEIGHT 1080 + +-(id)initWithInfo:(NSDictionary*)info { + self = [super init]; + + _ipAddress = [info objectForKey:@"ipAddress"]; + if (_ipAddress == nil && _ipAddress.length == 0 ) { + _ipAddress = @"127.0.0.1"; + } + _keepAliveTimeout = [[info objectForKey:@"keepAliveTimeout"] doubleValue]; + if (_keepAliveTimeout == 0) { + _keepAliveTimeout = 60; + } + _publicKey = [info objectForKey:@"publicKey"]; + + NSDictionary* deviceInfoObj = [info objectForKey:@"deviceInfo"] ; + if (deviceInfoObj != nil && deviceInfoObj.count > 0) { + _deviceType = [deviceInfoObj objectForKey:@"type"]; + _deviceVersion = [deviceInfoObj objectForKey:@"version"]; + _devicePlatform = [deviceInfoObj objectForKey:@"platform"]; + _deviceSoC = [deviceInfoObj objectForKey:@"SoC"]; + } + + NSDictionary* mirroringObj = [info objectForKey:@"mirroring"]; + if (mirroringObj != nil && mirroringObj.count > 0) { + NSDictionary* videoObj = [mirroringObj objectForKey:@"video"]; + if (videoObj != nil && videoObj.count > 0) { + _videoCodec = [videoObj objectForKey:@"codec"]; + _videoUdpPort = [[videoObj objectForKey:@"udpPort"] intValue]; + + // V2 properties + NSDictionary* videoPortraitSize = [videoObj objectForKey:@"portrait"]; + if (videoPortraitSize != nil && videoPortraitSize.count > 0) { + _videoPortraitMaxWidth = [[videoPortraitSize objectForKey:@"maxWidth"] intValue]; + _videoPortraitMaxHeight = [[videoPortraitSize objectForKey:@"maxHeight"] intValue]; + } else { + _videoPortraitMaxWidth = DEFAULT_SCREEN_HEIGHT; + _videoPortraitMaxHeight = DEFAULT_SCREEN_WIDTH; + } + + // V2 properties + NSDictionary* videoLandscapeSize = [videoObj objectForKey:@"landscape"]; + if (videoLandscapeSize != nil && videoLandscapeSize.count > 0) { + _videoLandscapeMaxWidth = [[videoLandscapeSize objectForKey:@"maxWidth"] intValue]; + _videoLandscapeMaxHeight = [[videoLandscapeSize objectForKey:@"maxHeight"] intValue]; + } else { // V1 properties + // TODO + _videoLandscapeMaxWidth = DEFAULT_SCREEN_WIDTH; + _videoLandscapeMaxHeight = DEFAULT_SCREEN_HEIGHT; + } + } + + NSDictionary* audioObj = [mirroringObj objectForKey:@"audio"]; + if (audioObj != nil && audioObj.count > 0) { + _audioCodec = [audioObj objectForKey:@"codec"]; + _audioUdpPort = [[audioObj objectForKey:@"udpPort"] intValue]; + } + + NSDictionary* supportedFeatures = [mirroringObj objectForKey:@"supportedFeatures"]; + if (supportedFeatures != nil && supportedFeatures.count > 0) { + _supportedOrientation = [supportedFeatures objectForKey:@"screenOrientation"]; + if (_supportedOrientation == nil) { + _supportedOrientation = @"landscape"; + } + } + + _displayOrientation = [mirroringObj objectForKey:@"displayOrientation"]; + if (_displayOrientation == nil) { + _displayOrientation = @"landscape"; + } + } + + return self; +} + +@end diff --git a/core/Services/Helpers/LGCast/ScreenMirroringService/MirroringSourceCapability.h b/core/Services/Helpers/LGCast/ScreenMirroringService/MirroringSourceCapability.h new file mode 100644 index 00000000..277885ce --- /dev/null +++ b/core/Services/Helpers/LGCast/ScreenMirroringService/MirroringSourceCapability.h @@ -0,0 +1,51 @@ +// +// MirroringSourceCapability.h +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MirroringSourceCapability: NSObject + +@property NSString *videoCodec; +@property NSInteger videoClockRate; +@property NSInteger videoFramerate; +@property NSInteger videoBitrate; +@property NSInteger videoWidth; +@property NSInteger videoHeight; +@property NSInteger videoActiveWidth; +@property NSInteger videoActiveHeight; +@property NSString *videoOrientation; + +@property NSString *audioCodec; +@property NSInteger audioClockRate; +@property NSInteger audioFrequency; +@property NSString *audioStreamMuxConfig; +@property NSInteger audioChannels; + +@property NSString *screenOrientation; + +- (void)setSecurityKeys:(NSArray *)keys; +- (NSDictionary *)toNSDictionary; +- (NSDictionary *)toNSDictionaryVideoSize; + +@end + +NS_ASSUME_NONNULL_END diff --git a/core/Services/Helpers/LGCast/ScreenMirroringService/MirroringSourceCapability.m b/core/Services/Helpers/LGCast/ScreenMirroringService/MirroringSourceCapability.m new file mode 100644 index 00000000..23223af1 --- /dev/null +++ b/core/Services/Helpers/LGCast/ScreenMirroringService/MirroringSourceCapability.m @@ -0,0 +1,84 @@ +// +// MirroringSourceCapability.m +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "MirroringSourceCapability.h" + +@interface MirroringSourceCapability() + @property NSArray *masterKeys; +@end + +@implementation MirroringSourceCapability +- (void)setSecurityKeys:(NSArray *)keys { + _masterKeys = keys; +} + +- (NSDictionary *)toNSDictionary { + NSDictionary* videoSpec = [NSDictionary dictionaryWithObjectsAndKeys: + _videoCodec, @"codec", + [NSNumber numberWithLong:_videoClockRate], @"clockRate", + [NSNumber numberWithLong:_videoFramerate], @"framerate", + [NSNumber numberWithLong:_videoBitrate], @"bitrate", + [NSNumber numberWithLong:_videoWidth], @"width", + [NSNumber numberWithLong:_videoHeight], @"height", + [NSNumber numberWithLong:_videoActiveWidth], @"activeWidth", + [NSNumber numberWithLong:_videoActiveHeight], @"activeHeight", + _videoOrientation, @"orientation", nil]; + + NSDictionary* audioSpec = [NSDictionary dictionaryWithObjectsAndKeys: + _audioCodec, @"codec", + [NSNumber numberWithLong:_audioClockRate], @"clockRate", + [NSNumber numberWithLong:_audioFrequency], @"frequency", + _audioStreamMuxConfig, @"streamMuxConfig", + [NSNumber numberWithLong:_audioChannels], @"channels", nil]; + + + NSMutableArray *cryptoSpec = [[NSMutableArray alloc] init]; + + for (LGCastSecurityKey *key in _masterKeys) { + [cryptoSpec addObject:@{ + @"mki": key.mki, + @"key": key.masterKey + }]; + } + + NSDictionary* supportedFeatures = [NSDictionary dictionaryWithObjectsAndKeys: + _screenOrientation, @"screenOrientation", nil]; + + + return @{ + @"video": videoSpec, + @"audio": audioSpec, + @"crypto": cryptoSpec, + @"uibcEnabled": @NO, + @"supportedFeatures": supportedFeatures + }; +} + +- (NSDictionary *)toNSDictionaryVideoSize { + NSDictionary* videoSpec = [NSDictionary dictionaryWithObjectsAndKeys: + _videoOrientation, @"orientation", + [NSNumber numberWithLong:_videoWidth], @"width", + [NSNumber numberWithLong:_videoHeight], @"height", + [NSNumber numberWithLong:_videoActiveWidth], @"activeWidth", + [NSNumber numberWithLong:_videoActiveHeight], @"activeHeight", nil]; + + return @{@"video": videoSpec}; +} + +@end diff --git a/core/Services/Helpers/LGCast/ScreenMirroringService/ScreenMirroringService.h b/core/Services/Helpers/LGCast/ScreenMirroringService/ScreenMirroringService.h new file mode 100644 index 00000000..f636f994 --- /dev/null +++ b/core/Services/Helpers/LGCast/ScreenMirroringService/ScreenMirroringService.h @@ -0,0 +1,55 @@ +// +// ScreenMirroringService.h +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +#import "ScreenMirroringControl.h" +#import "ConnectableDevice.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol ScreenMirroringServiceDelegate +- (void)screenMirroringDidStart:(BOOL)result; +- (void)screenMirroringDidStop:(BOOL)result; +- (void)screenMirroringErrorDidOccur:(ScreenMirroringError)error; +@end + +extern NSString *const kSMKeyMirroring; +extern NSString *const kSMKeyResult; + +extern NSString *const kSMKeyDisplayOrientation; + +extern NSString *const kSMValueOrientationPortrait; +extern NSString *const kSMValueOrientationLandscape; + +@interface ScreenMirroringService: NSObject + +@property (nonatomic, weak) id delegate; +@property (readonly) BOOL isRunning; + ++ (instancetype)sharedInstance; + +- (void)startMirroring:(ConnectableDevice *)device settings:(nullable NSDictionary *)settings; +- (void)stopMirroring; +- (void)pushSampleBuffer:(CMSampleBufferRef)sampleBuffer with:(RPSampleBufferType)sampleBufferType; + +@end + +NS_ASSUME_NONNULL_END diff --git a/core/Services/Helpers/LGCast/ScreenMirroringService/ScreenMirroringService.m b/core/Services/Helpers/LGCast/ScreenMirroringService/ScreenMirroringService.m new file mode 100644 index 00000000..abde63c0 --- /dev/null +++ b/core/Services/Helpers/LGCast/ScreenMirroringService/ScreenMirroringService.m @@ -0,0 +1,350 @@ +// +// ScreenMirroringService.m +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#import "ScreenMirroringService.h" +#import "ConnectionManager.h" +#import "MobileCapability.h" +#import "MirroringSourceCapability.h" +#import "MirroringSinkCapability.h" + +@interface ScreenMirroringService() + +@property ConnectionManager *connectionManager; +@property BOOL isRunning; + +@property MirroringSourceCapability *sourceCapability; +@property MirroringSinkCapability *sinkCapability; + +@end + +NSString *const kSMKeyMirroring = @"mirroring"; +NSString *const kSMKeyResult = @"result"; + +NSString *const kSMKeyDisplayOrientation = @"displayOrientation"; + +NSString *const kSMValueOrientationPortrait = @"portrait"; +NSString *const kSMValueOrientationLandscape = @"landscape"; + +@implementation ScreenMirroringService + +#define DEFAULT_MIRRORING_SCREEN_WIDTH 1920 +#define DEFAULT_MIRRORING_SCREEN_HEIGHT 1080 + ++ (instancetype)sharedInstance { + static ScreenMirroringService *shared = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = [[ScreenMirroringService alloc] initPrivate]; + }); + + return shared; +} + +- (instancetype)init { + return [[self class] sharedInstance]; +} + +- (instancetype)initPrivate { + self = [super init]; + + _isRunning = NO; + _connectionManager = ConnectionManager.sharedInstance; + _connectionManager.delegate = self; + [[LGCastMirroringApi shared] setDelegate:self]; + + return self; +} + +- (void)startMirroring:(ConnectableDevice *)device settings:(nullable NSDictionary *)settings { + [Log infoLGCast:@"startMirroring"]; + + if (self.isRunning == NO) { + self.isRunning = YES; + [_connectionManager openConnection:kServiceTypeScreenMirroring device:device]; + } else { + [self sendStartEvent:NO]; + } +} + +- (void)stopMirroring { + [Log infoLGCast:@"stopMirroring"]; + + if (self.isRunning == YES) { + self.isRunning = NO; + [[LGCastMirroringApi shared] stopMirroring]; + [_connectionManager closeConnection]; + [self sendStopEvent:YES]; + } else { + [self sendStopEvent:NO]; + } +} + +- (void)pushSampleBuffer:(CMSampleBufferRef)sampleBuffer with:(RPSampleBufferType)sampleBufferType { + if (self.isRunning == NO) { + return; + } + + if (sampleBufferType != RPSampleBufferTypeVideo && sampleBufferType != RPSampleBufferTypeAudioApp) { + return; + } + + [[LGCastMirroringApi shared] pushSampleBuffer:sampleBuffer with:sampleBufferType]; +} + +// MARK: ConnectionManagerDelegate + +- (void)onPairingRequested { + [Log infoLGCast:@"onPairingRequested"]; +} + +- (void)onPairingRejected { + [Log infoLGCast:@"onPairingRejected"]; + + self.isRunning = NO; + [self sendStartEvent:NO]; +} + +- (void)onConnectionFailed:(NSString *)message { + [Log errorLGCast:[NSString stringWithFormat:@"onConnectionFailed %@", message]]; + + self.isRunning = NO; + [self sendStartEvent:NO]; +} + +- (void)onConnectionCompleted:(NSDictionary *)values { + [Log infoLGCast:@"onConnectionCompleted"]; + + if (values == nil) { + [Log errorLGCast:@"invalid parameter"]; + return; + } + + _sinkCapability = [[MirroringSinkCapability alloc] initWithInfo:values]; + + LGCastMirroringMediaSettings *mediaSettings = [[LGCastMirroringMediaSettings alloc] init]; + mediaSettings.audio = [[LGCastMirroringAudioSettings alloc] init]; + mediaSettings.video = [[LGCastMirroringVideoSettings alloc] init]; + + BOOL isPortraitMode = [_sinkCapability.displayOrientation caseInsensitiveCompare:kSMValueOrientationPortrait] == NSOrderedSame ? YES : NO; + mediaSettings.video.isPortraitMode = isPortraitMode; + + if (isPortraitMode) { + mediaSettings.video.width = DEFAULT_MIRRORING_SCREEN_HEIGHT; + mediaSettings.video.height = DEFAULT_MIRRORING_SCREEN_WIDTH; + } else { + mediaSettings.video.width = DEFAULT_MIRRORING_SCREEN_WIDTH; + mediaSettings.video.height = DEFAULT_MIRRORING_SCREEN_HEIGHT; + } + + LGCastMirroringInfo *mirroringInfo = [[LGCastMirroringApi shared] setMediaSettings:mediaSettings]; + _sourceCapability = [[MirroringSourceCapability alloc] init]; + + if (mirroringInfo.videoInfo != nil) { + _sourceCapability.videoCodec = mirroringInfo.videoInfo.codec; + _sourceCapability.videoWidth = mirroringInfo.playerInfo.width; + _sourceCapability.videoHeight = mirroringInfo.playerInfo.height; + _sourceCapability.videoActiveWidth = mirroringInfo.videoInfo.activeWidth; + _sourceCapability.videoActiveHeight = mirroringInfo.videoInfo.activeHeight; + _sourceCapability.videoFramerate = mirroringInfo.videoInfo.framerate; + _sourceCapability.videoBitrate = mirroringInfo.videoInfo.bitrate; + _sourceCapability.videoClockRate = mirroringInfo.videoInfo.samplingRate; + _sourceCapability.videoOrientation = mirroringInfo.videoInfo.isPortraitMode ? kSMValueOrientationPortrait :kSMValueOrientationLandscape; + _sourceCapability.screenOrientation = mirroringInfo.videoInfo.screenOrientation; + } + + if (mirroringInfo.audioInfo != nil) { + _sourceCapability.audioCodec = mirroringInfo.audioInfo.codec; + _sourceCapability.audioFrequency = mirroringInfo.audioInfo.samplingRate; + _sourceCapability.audioClockRate = mirroringInfo.audioInfo.samplingRate; + _sourceCapability.audioChannels = mirroringInfo.audioInfo.channelCnt; + _sourceCapability.audioStreamMuxConfig = mirroringInfo.audioInfo.streamMuxConfig; + } + + NSArray *keys = [[LGCastMirroringApi shared] generateMirroringMasterKey:_sinkCapability.publicKey]; + [_sourceCapability setSecurityKeys:keys]; + + MobileCapability *mobileCapability = [[MobileCapability alloc] init]; + [_connectionManager setSourceDeviceInfo:[_sourceCapability toNSDictionary] + deviceInfo:[mobileCapability toNSDictionary]]; +} + +- (void)onReceivePlayCommand:(NSDictionary *)values { + [Log infoLGCast:@"onReceivePlayCommand"]; + + LGCastDeviceSettings *deviceSettings = [[LGCastDeviceSettings alloc] init]; + deviceSettings.host = _sinkCapability.ipAddress; + deviceSettings.audioPort = _sinkCapability.audioUdpPort; + deviceSettings.videoPort = _sinkCapability.videoUdpPort; + + [[LGCastMirroringApi shared] startMirroring:deviceSettings]; +} + +- (void)onReceiveStopCommand:(NSDictionary *)values { + [Log infoLGCast:@"onReceiveStopCommand"]; +} + +- (void)onReceiveGetParameter:(NSDictionary *)values { + [Log infoLGCast:@"onReceiveGetParameter"]; +} + +- (void)onReceiveSetParameter:(NSDictionary *)values { + [Log infoLGCast:@"onReceiveSetParameter"]; + + NSDictionary* mirroringValues = values[kSMKeyMirroring]; + if (mirroringValues == nil) { + return; + } + + if (_sinkCapability == nil) { + [Log errorLGCast:@"Unable to handle this event"]; + return; + } + + NSString *displayOrientation = mirroringValues[kSMKeyDisplayOrientation]; + _sinkCapability.displayOrientation = displayOrientation; + + BOOL isPortraitMode = [_sinkCapability.displayOrientation caseInsensitiveCompare:kSMValueOrientationPortrait] == NSOrderedSame ? YES : NO; + LGCastMirroringInfo *castSetting = [[LGCastMirroringApi shared] updateDisplayOrientationWithIsPortraitMode:isPortraitMode]; + + if (_sourceCapability != nil) { + _sourceCapability.videoWidth = castSetting.playerInfo.width; + _sourceCapability.videoHeight = castSetting.playerInfo.height; + _sourceCapability.videoActiveWidth = castSetting.videoInfo.activeWidth; + _sourceCapability.videoActiveHeight = castSetting.videoInfo.activeHeight; + _sourceCapability.videoOrientation = castSetting.videoInfo.isPortraitMode ? @"portrait" : @"landscape"; + + [self updateCapability:[_sourceCapability toNSDictionaryVideoSize]]; + } +} + +- (void)onError:(ConnectionError)error message:(NSString *)message { + [Log errorLGCast:[NSString stringWithFormat:@"onError %d %@", error, message]]; + + ScreenMirroringError controlError = ScreenMirroringErrorGeneric; + switch (error) { + case kConnectionErrorUnknown: + [Log errorLGCast:@"kConnectionErrorUnknown"]; + controlError = ScreenMirroringErrorGeneric; + break; + case kConnectionErrorConnectionClosed: + [Log errorLGCast:@"kConnectionErrorConnectionClosed"]; + controlError = ScreenMirroringErrorConnectionClosed; + break; + case kConnectionErrorDeviceShutdown: + [Log errorLGCast:@"kConnectionErrorDeviceShutdown"]; + controlError = ScreenMirroringErrorDeviceShutdown; + break; + case kConnectionErrorRendererTerminated: + [Log errorLGCast:@"kConnectionErrorRendererTerminated"]; + controlError = ScreenMirroringErrorRendererTerminated; + break; + default: + break; + } + + self.isRunning = NO; + [[LGCastMirroringApi shared] stopMirroring]; + + [self sendErrorEvent:controlError]; +} + +// MARK: LGCastMirroringApiDelegate + +- (void)lgcastMirroringDidStartWithResult:(BOOL)result { + [Log infoLGCast:@"lgcastMirroringDidStartWithResult"]; + if (result) { + [self sendStartEvent:YES]; + } +} + +- (void)lgcastMirroringDidStopWithResult:(BOOL)result { + [Log infoLGCast:@"lgcastMirroringDidStopWithResult"]; +} + +- (void)lgcastMirroringErrorDidOccurWithError:(enum LGCastMirroringError)error { + [Log errorLGCast:[NSString stringWithFormat:@"lgcastMirroringErrorDidOccurWithError %d", (int)error]]; + + ScreenMirroringError errorType = ScreenMirroringErrorGeneric; + switch (error) { + case LGCastMirroringErrorUnknown: + errorType = ScreenMirroringErrorGeneric; + break; + case LGCastMirroringErrorConnectionClosed: + errorType = ScreenMirroringErrorConnectionClosed; + break; + case LGCastMirroringErrorDeviceShutdown: + errorType = ScreenMirroringErrorDeviceShutdown; + break; + case LGCastMirroringErrorRendererTerminated: + errorType = ScreenMirroringErrorRendererTerminated; + break; + default: + errorType = ScreenMirroringErrorGeneric; + break; + } + + self.isRunning = NO; + [self sendErrorEvent:error]; +} + +- (void)lgcastMirroringUpdateEventWithEvent:(enum LGCastMirroringEvent)event info:(LGCastMirroringInfo *)info { + [Log infoLGCast:@"lgcastMirroringUpdateEventWithEvent"]; + + if (event == LGCastMirroringEventUpdateVideoVideoSize) { + if (_sourceCapability != nil && info != nil && info.videoInfo) { + _sourceCapability.videoWidth = info.playerInfo.width; + _sourceCapability.videoHeight = info.playerInfo.height; + _sourceCapability.videoActiveWidth = info.videoInfo.activeWidth; + _sourceCapability.videoActiveHeight = info.videoInfo.activeHeight; + _sourceCapability.videoOrientation = info.videoInfo.isPortraitMode == YES ? @"portrait" : @"landscape"; + + [self updateCapability:[_sourceCapability toNSDictionaryVideoSize]]; + } + } +} + +- (void)updateCapability:(NSDictionary*)capability { + if (!_isRunning) { + return; + } + + [_connectionManager sendSetParameter:capability ignoreResult:YES]; +} + +- (void)sendStartEvent:(BOOL)result { + if(_delegate != nil && [_delegate respondsToSelector:@selector(screenMirroringDidStart:)]){ + [_delegate screenMirroringDidStart:result]; + } +} + +- (void)sendStopEvent:(BOOL)result { + if(_delegate != nil && [_delegate respondsToSelector:@selector(screenMirroringDidStop:)]){ + [_delegate screenMirroringDidStop:result]; + } +} + +- (void)sendErrorEvent:(ScreenMirroringError)error { + if(_delegate != nil && [_delegate respondsToSelector:@selector(screenMirroringErrorDidOccur:)]){ + [_delegate screenMirroringErrorDidOccur:error]; + } +} + +@end diff --git a/core/Services/Helpers/LGCast/WebOSTVService+LGCast.h b/core/Services/Helpers/LGCast/WebOSTVService+LGCast.h new file mode 100644 index 00000000..1d325267 --- /dev/null +++ b/core/Services/Helpers/LGCast/WebOSTVService+LGCast.h @@ -0,0 +1,40 @@ +// +// WebOSTVService+LGCast.h +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ConnectSDK.h" +#import "WebOSTVService.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface WebOSTVService (LGCast) + +- (ServiceSubscription *)subscribeCommandWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; +- (ServiceSubscription *)subscribePowerStateWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; + +- (void)sendConnectWithService:(NSString *)service success:(SuccessBlock)success failure:(FailureBlock)failure; +- (void)sendGetParameterWithService:(NSString *)service success:(SuccessBlock)success failure:(FailureBlock)failure; +- (void)sendSetParameterWithService:(NSString *)service sourceInfo:(NSDictionary *)sourceInfo deviceInfo:(NSDictionary *)deviceInfo success:(SuccessBlock)success failure:(FailureBlock)failure; +- (void)sendGetParameterResponseWithService:(NSString *)service values:(NSDictionary *)values success:(SuccessBlock)success failure:(FailureBlock)failure; +- (void)sendSetParameterResponseWithService:(NSString *)service values:(NSDictionary *)values success:(SuccessBlock)success failure:(FailureBlock)failure; +- (void)sendKeepAliveWithService:(NSString *)service success:(SuccessBlock)success failure:(FailureBlock)failure; +- (void)sendTeardownWithService:(NSString *)service success:(SuccessBlock)success failure:(FailureBlock)failure; + +@end + +NS_ASSUME_NONNULL_END diff --git a/core/Services/Helpers/LGCast/WebOSTVService+LGCast.m b/core/Services/Helpers/LGCast/WebOSTVService+LGCast.m new file mode 100644 index 00000000..b191bf56 --- /dev/null +++ b/core/Services/Helpers/LGCast/WebOSTVService+LGCast.m @@ -0,0 +1,128 @@ +// +// WebOSTVService+LGCast.m +// LGCast +// +// Copyright (c) 2022 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "WebOSTVService+LGCast.h" +#import "ServiceAsyncCommand.h" +#import "WebOSTVServiceSocketClient.h" +#import + +@implementation WebOSTVService (LGCast) + +NSString *const kCommandKey = @"cmd"; +NSString *const kSubscribeKey = @"subscribe"; +NSString *const kServiceKey = @"service"; +NSString *const kClientKeyKey = @"clientKey"; +NSString *const kDeviceInfoKey = @"deviceInfo"; + +NSString *const kCommandConnect = @"CONNECT"; +NSString *const kCommandGetParameter = @"GET_PARAMETER"; +NSString *const kCommandSetParameter = @"SET_PARAMETER"; +NSString *const kCommandGetParameterResponse = @"GET_PARAMETER_RESPONSE"; +NSString *const kCommandSetParameterResponse = @"SET_PARAMETER_RESPONSE"; +NSString *const kCommandKeepAlive = @"KEEPALIVE"; +NSString *const kCommandTeardown = @"TEARDOWN"; + +- (ServiceSubscription *)subscribeCommandWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure { + NSURL *URL = [NSURL URLWithString:@"ssap://com.webos.service.appcasting/getCommand"]; + NSDictionary *payload = @{ kSubscribeKey : @YES }; + + ServiceSubscription *subscription = [self.socket addSubscribe:URL payload:payload success:success failure:failure]; + + return subscription; +} + +- (ServiceSubscription *)subscribePowerStateWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure { + NSURL *URL = [NSURL URLWithString:@"ssap://com.webos.service.tvpower/power/getPowerState"]; + NSDictionary *payload = @{ kSubscribeKey : @YES }; + + ServiceSubscription *subscription = [self.socket addSubscribe:URL payload:payload success:success failure:failure]; + + return subscription; +} + +- (void)sendConnectWithService:(NSString *)service success:(SuccessBlock)success failure:(FailureBlock)failure { + [self sendCommandWithService:service command:kCommandConnect parameter:nil successBlock:success failureBlock:failure]; +} + +- (void)sendGetParameterWithService:(NSString *)service success:(SuccessBlock)success failure:(FailureBlock)failure { + [self sendCommandWithService:service command:kCommandGetParameter parameter:nil successBlock:success failureBlock:failure]; +} + +- (void)sendSetParameterWithService:(NSString *)service sourceInfo:(NSDictionary *)sourceInfo deviceInfo:(NSDictionary *)deviceInfo success:(SuccessBlock)success failure:(FailureBlock)failure { + NSDictionary *param; + + if (deviceInfo == nil) { + param = @{ service: sourceInfo }; + } else { + param = @{ + service: sourceInfo, + kDeviceInfoKey: deviceInfo + }; + } + + [self sendCommandWithService:service command:kCommandSetParameter parameter:param successBlock:success failureBlock:failure]; +} + +- (void)sendGetParameterResponseWithService:(NSString *)service values:(NSDictionary *)values success:(SuccessBlock)success failure:(FailureBlock)failure { + NSDictionary *param = @{ + service: values + }; + + [self sendCommandWithService:service command:kCommandGetParameterResponse parameter:param successBlock:success failureBlock:failure]; +} + +- (void)sendSetParameterResponseWithService:(NSString *)service values:(NSDictionary *)values success:(SuccessBlock)success failure:(FailureBlock)failure { + NSDictionary *param = @{ + service: values + }; + + [self sendCommandWithService:service command:kCommandSetParameterResponse parameter:param successBlock:success failureBlock:failure]; +} + +- (void)sendKeepAliveWithService:(NSString *)service success:(SuccessBlock)success failure:(FailureBlock)failure { + [self sendCommandWithService:service command:kCommandKeepAlive parameter:nil successBlock:success failureBlock:failure]; +} + +- (void)sendTeardownWithService:(NSString *)service success:(SuccessBlock)success failure:(FailureBlock)failure { + [self sendCommandWithService:service command:kCommandTeardown parameter:nil successBlock:success failureBlock:failure]; +} + +- (void)sendCommandWithService:(NSString *)service command:(NSString *)command parameter:(NSDictionary *)parameter successBlock:(SuccessBlock)success failureBlock:(FailureBlock)failure { + if (self.webOSTVServiceConfig == nil || self.webOSTVServiceConfig.clientKey == nil) { + [Log errorLGCast:@"client key is nil"]; + return; + } + + NSURL *url = [NSURL URLWithString:@"ssap://com.webos.service.appcasting/sendCommand"]; + NSDictionary *defaultPayload = @{ + kCommandKey: command, + kClientKeyKey: self.webOSTVServiceConfig.clientKey, + kServiceKey: service + }; + + NSMutableDictionary *payload = [NSMutableDictionary dictionaryWithDictionary:defaultPayload]; + [payload addEntriesFromDictionary: parameter]; + + ServiceCommand *serviceCommand = [ServiceAsyncCommand commandWithDelegate:self.socket target:url payload:payload]; + serviceCommand.callbackComplete = success; + serviceCommand.callbackError = failure; + [serviceCommand send]; +} + +@end diff --git a/core/Services/Helpers/NSObject+FeatureNotSupported_Private.h b/core/Services/Helpers/NSObject+FeatureNotSupported_Private.h new file mode 100644 index 00000000..7f1a1231 --- /dev/null +++ b/core/Services/Helpers/NSObject+FeatureNotSupported_Private.h @@ -0,0 +1,37 @@ +// +// NSObject+FeatureNotSupported_Private.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-07-31. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "Capability.h" + +@class ServiceSubscription; + +NS_ASSUME_NONNULL_BEGIN +@interface NSObject (FeatureNotSupported) + +/** + * Calls the @c failure block with an unsupported error. Returns @c nil. + * + * @remarks It would be better to define this category on @c DeviceService + * class, but a couple of other classes use this method as well. + */ +- (nullable ServiceSubscription *)sendNotSupportedFailure:(nullable FailureBlock)failure; + +@end +NS_ASSUME_NONNULL_END diff --git a/core/Services/Helpers/NSObject+FeatureNotSupported_Private.m b/core/Services/Helpers/NSObject+FeatureNotSupported_Private.m new file mode 100644 index 00000000..65991e90 --- /dev/null +++ b/core/Services/Helpers/NSObject+FeatureNotSupported_Private.m @@ -0,0 +1,38 @@ +// +// NSObject+FeatureNotSupported_Private.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-07-31. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NSObject+FeatureNotSupported_Private.h" + +#import "ConnectError.h" + +NS_ASSUME_NONNULL_BEGIN +@implementation NSObject (FeatureNotSupported) + +- (nullable ServiceSubscription *)sendNotSupportedFailure:(nullable FailureBlock)failure { + if (failure) { + failure([ConnectError generateErrorWithCode:ConnectStatusCodeNotSupported + andDetails:nil]); + } + + return nil; +} + +@end +NS_ASSUME_NONNULL_END diff --git a/core/Services/Helpers/WebOSTVServiceMouse.h b/core/Services/Helpers/WebOSTVServiceMouse.h new file mode 100644 index 00000000..ecea9b0f --- /dev/null +++ b/core/Services/Helpers/WebOSTVServiceMouse.h @@ -0,0 +1,42 @@ +// +// WebOSTVServiceMouse.h +// Connect SDK +// +// Created by Jeremy White on 1/3/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "Capability.h" + +typedef enum { + WebOSTVMouseButtonHome = 1000, + WebOSTVMouseButtonBack = 1001, + WebOSTVMouseButtonUp = 1002, + WebOSTVMouseButtonDown = 1003, + WebOSTVMouseButtonLeft = 1004, + WebOSTVMouseButtonRight = 1005 +} WebOSTVMouseButton; + +@interface WebOSTVServiceMouse : NSObject + +- (instancetype) initWithSocket:(NSString*)socket success:(SuccessBlock)success failure:(FailureBlock)failure; +- (void) move:(CGVector)distance; +- (void) scroll:(CGVector)distance; +- (void) click; +- (void) button:(WebOSTVMouseButton)keyName; +- (void) disconnect; + +@end diff --git a/core/Services/Helpers/WebOSTVServiceMouse.m b/core/Services/Helpers/WebOSTVServiceMouse.m new file mode 100644 index 00000000..e66a3ad5 --- /dev/null +++ b/core/Services/Helpers/WebOSTVServiceMouse.m @@ -0,0 +1,202 @@ +// +// WebOSTVServiceMouse.m +// Connect SDK +// +// Created by Jeremy White on 1/3/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "WebOSTVServiceMouse.h" +#import "LGSRWebSocket.h" +#import "ConnectError.h" + +@interface WebOSTVServiceMouse () + +@end + +@implementation WebOSTVServiceMouse +{ + LGSRWebSocket *_mouseSocket; + + SuccessBlock _success; + FailureBlock _failure; + + CGVector _mouseDistance; + CGVector _scrollDistance; + + BOOL _mouseIsMoving; + BOOL _mouseIsScrolling; +} + +- (instancetype) initWithSocket:(NSString*)socket success:(SuccessBlock)success failure:(FailureBlock)failure +{ + self = [super init]; + + if (self) + { + _success = success; + _failure = failure; + + _mouseSocket = [[LGSRWebSocket alloc] initWithURL:[[NSURL alloc] initWithString:socket]]; + _mouseSocket.delegate = self; + [_mouseSocket open]; + } + + return self; +} + +- (void) move:(CGVector)distance +{ + _mouseDistance = CGVectorMake( + _mouseDistance.dx + distance.dx, + _mouseDistance.dy + distance.dy + ); + + if (!_mouseIsMoving) + { + _mouseIsMoving = YES; + + [self moveMouse]; + } +} + +- (void) moveMouse +{ + NSString *moveString = [NSString stringWithFormat:@"type:move\ndx:%f\ndy:%f\ndown:%d\n\n", _mouseDistance.dx, _mouseDistance.dy, 0]; + [self sendPackage:moveString]; + + _mouseDistance = CGVectorMake(0, 0); + _mouseIsMoving = NO; +} + +- (void) scroll:(CGVector)distance +{ + _scrollDistance = CGVectorMake( + _scrollDistance.dx + distance.dx, + _scrollDistance.dy + distance.dy + ); + + if (!_mouseIsScrolling) + { + _mouseIsScrolling = YES; + + [self scroll]; + } +} + +- (void) scroll +{ + NSString *scrollString = [NSString stringWithFormat:@"type:scroll\ndx:%f\ndy:%f\n\n", _scrollDistance.dx, _scrollDistance.dy]; + [self sendPackage:scrollString]; + + _scrollDistance = CGVectorMake(0, 0); + _mouseIsScrolling = NO; +} + +- (void) click +{ + NSString *clickString = @"type:click\n\n"; + [self sendPackage:clickString]; +} + +-(void) button:(WebOSTVMouseButton)keyName +{ + NSString *keyString; + + switch (keyName) + { + case WebOSTVMouseButtonHome: keyString = @"HOME"; break; + case WebOSTVMouseButtonBack: keyString = @"BACK"; break; + case WebOSTVMouseButtonUp: keyString = @"UP"; break; + case WebOSTVMouseButtonDown: keyString = @"DOWN"; break; + case WebOSTVMouseButtonLeft: keyString = @"LEFT"; break; + case WebOSTVMouseButtonRight: keyString = @"RIGHT"; break; + default:break; + } + + if (keyString) + { + NSString *buttonString = [NSString stringWithFormat:@"type:button\nname:%@\n\n", keyString]; + [self sendPackage:buttonString]; + } +} + +- (void) sendPackage:(NSString*)package +{ + if ([_mouseSocket readyState] == LGSR_OPEN) + [_mouseSocket send:package]; +} + +- (void) disconnect +{ + _mouseDistance = CGVectorMake(0, 0); + _mouseIsMoving = NO; + + _scrollDistance = CGVectorMake(0, 0); + _mouseIsScrolling = NO; + + [_mouseSocket close]; + _mouseSocket.delegate = nil; + _mouseSocket = nil; + + _success = nil; + _failure = nil; +} + +#pragma mark LGSRWebSocketDelegate + +- (void)webSocketDidOpen:(LGSRWebSocket *)webSocket +{ + _mouseDistance = CGVectorMake(0, 0); + _mouseIsMoving = NO; + + _scrollDistance = CGVectorMake(0, 0); + _mouseIsScrolling = NO; + + if (_success) + _success(nil); +} + +- (void)webSocket:(LGSRWebSocket *)webSocket didReceiveMessage:(id)message +{ + // don't need to handle incoming messages on the mouseControl socket +} + +- (void)webSocket:(LGSRWebSocket *)webSocket didFailWithError:(NSError *)error +{ + if (_failure) + _failure(error); +} + +- (void)webSocket:(LGSRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean +{ + if (wasClean) + { + _mouseDistance = CGVectorMake(0, 0); + _mouseIsMoving = NO; + + _scrollDistance = CGVectorMake(0, 0); + _mouseIsScrolling = NO; + + _success = nil; + _failure = nil; + } + + if (!wasClean && _failure) + _failure([ConnectError generateErrorWithCode:ConnectStatusCodeTvError andDetails:reason]); +} + +@end diff --git a/core/Services/Helpers/WebOSTVServiceSocketClient.h b/core/Services/Helpers/WebOSTVServiceSocketClient.h new file mode 100644 index 00000000..379e59df --- /dev/null +++ b/core/Services/Helpers/WebOSTVServiceSocketClient.h @@ -0,0 +1,65 @@ +// +// WebOSTVServiceSocketClient.h +// Connect SDK +// +// Created by Jeremy White on 6/19/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "ServiceCommandDelegate.h" +#import "ServiceCommand.h" +#import "LGSRWebSocket.h" + +@class WebOSTVService; +@protocol WebOSTVServiceSocketClientDelegate; + + +@interface WebOSTVServiceSocketClient : NSObject + +- (instancetype) initWithService:(WebOSTVService *)service; + +- (void) connect; +- (void) disconnect; +- (void) disconnectWithError:(NSError *)error; + +- (ServiceSubscription *) addSubscribe:(NSURL *)URL payload:(NSDictionary *)payload success:(SuccessBlock)success failure:(FailureBlock)failure; +- (ServiceSubscription *) killSubscribe:(NSURL *)URL payload:(NSDictionary *)payload; + +- (void) sendDictionaryOverSocket:(NSDictionary *)payload; +- (void) sendStringOverSocket:(NSString *)payload; + +@property (nonatomic) id delegate; +@property (nonatomic) WebOSTVService *service; +@property (nonatomic, readonly) BOOL connected; +@property (nonatomic, readonly) LGSRWebSocket *socket; +@property (nonatomic, readonly) NSDictionary *activeConnections; +@property (nonatomic, readonly) NSArray *commandQueue; + +@end + +@protocol WebOSTVServiceSocketClientDelegate + +- (void) socketDidConnect:(WebOSTVServiceSocketClient *)socket; +- (void) socket:(WebOSTVServiceSocketClient *)socket didCloseWithError:(NSError *)error; +- (void) socket:(WebOSTVServiceSocketClient *)socket didFailWithError:(NSError *)error; + +@optional +// TODO : Deprecate this method and rename this to more meaningful one probably socketWillRequirePairingWithPairingType: +- (void) socketWillRegister:(WebOSTVServiceSocketClient *)socket; +- (void) socket:(WebOSTVServiceSocketClient *)socket registrationFailed:(NSError *)error; +- (BOOL) socket:(WebOSTVServiceSocketClient *)socket didReceiveMessage:(NSDictionary *)message; + +@end diff --git a/core/Services/Helpers/WebOSTVServiceSocketClient.m b/core/Services/Helpers/WebOSTVServiceSocketClient.m new file mode 100644 index 00000000..e0f9bf6e --- /dev/null +++ b/core/Services/Helpers/WebOSTVServiceSocketClient.m @@ -0,0 +1,731 @@ +// +// WebOSTVServiceSocketClient.m +// Connect SDK +// +// Created by Jeremy White on 6/19/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "WebOSTVServiceSocketClient.h" +#import "WebOSTVService.h" +#import "ConnectError.h" + +#define kDeviceServicePairingTypeFirstScreen @"PROMPT" +#define kDeviceServicePairingTypePinCode @"PIN" +#define kDeviceServicePairingTypeMixed @"COMBINED" + +@interface WebOSTVServiceSocketClient () + +/// Stores subscriptions that need to be automagically resubscribed after +/// reconnect. The structure is that of the @c _subscribed property. +@property (nonatomic, strong) NSDictionary *savedSubscriptions; + +@end + +@implementation WebOSTVServiceSocketClient +{ + int _UID; + + NSMutableArray *_commandQueue; + NSMutableDictionary *_activeConnections; + NSMutableDictionary *_subscribed; + + BOOL _reconnectOnWake; +} + +#pragma mark - Initial setup + +- (instancetype) initWithService:(WebOSTVService *)service +{ + self = [super init]; + + if (self) + { + _service = service; + + _UID = 0; + _connected = NO; + + _commandQueue = [[NSMutableArray alloc] init]; + _activeConnections = [[NSMutableDictionary alloc] init]; + _subscribed = [[NSMutableDictionary alloc] init]; + + _UID = 0; + } + + return self; +} + +- (NSArray *) commandQueue +{ + return [NSArray arrayWithArray:_commandQueue]; +} + +- (NSDictionary *) activeConnections +{ + return [NSDictionary dictionaryWithDictionary:_activeConnections]; +} + +#pragma mark - webOS Setup + +- (NSDictionary *) manifest +{ + NSDictionary *infoDic = [[NSBundle mainBundle] infoDictionary]; + NSString *bundleName = [infoDic valueForKey:@"CFBundleDisplayName"] ? [infoDic objectForKey:@"CFBundleDisplayName"] : [infoDic objectForKey:@"CFBundleName"]; + if(bundleName == nil){ + bundleName = @""; + } + + return @{ + @"manifestVersion" : @(1), + @"appId" : [infoDic objectForKey:@"CFBundleIdentifier"], + @"vendorId" : @"", + @"localizedAppNames" : @{ + @"" :bundleName + }, + @"permissions" : self.service.permissions + }; +} + +#pragma mark - Connection & Disconnection + +- (void) connect +{ + [self openSocket]; +} + +- (void) disconnect +{ + [self disconnectWithError:nil]; +} + +- (void) openSocket +{ + if (_socket) + { + switch (_socket.readyState) + { + case LGSR_OPEN: + if (_socket.delegate != self) + _socket.delegate = self; + + [self webSocketDidOpen:_socket]; + return; + + case LGSR_CONNECTING: + if (_socket.delegate != self) + _socket.delegate = self; + return; + + case LGSR_CLOSED: + case LGSR_CLOSING: + _socket.delegate = nil; + _socket = nil; + break; + + default:break; + } + } + + NSString *address = self.service.serviceDescription.address; + unsigned long port = self.service.serviceDescription.port; + + NSString *socketPath = [NSString stringWithFormat:@"wss://%@:%lu", address, port]; + NSURL *url = [[NSURL alloc] initWithString:socketPath]; + NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:url]; + + if (self.service.webOSTVServiceConfig.SSLCertificates) + [urlRequest setLGSR_SSLPinnedCertificates:self.service.webOSTVServiceConfig.SSLCertificates]; + + _socket = [self createSocketWithURLRequest:[urlRequest copy]]; + _socket.delegate = self; + [_socket open]; +} + +- (void) disconnectWithError:(NSError *)error +{ + if (!_reconnectOnWake) + { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; + } + + _connected = NO; + + if (_socket && _socket.readyState != LGSR_CLOSED && _socket.readyState != LGSR_CLOSING) + { + if (error) + [_socket closeWithCode:LGSRStatusCodeNormal reason:error.localizedDescription]; + else + [_socket closeWithCode:LGSRStatusCodeNormal reason:@"Disconnected by client"]; + } + + _subscribed = [[NSMutableDictionary alloc] init]; + + if (self.delegate) + [self.delegate socket:self didCloseWithError:error]; +} + +#pragma mark - App state management + +- (void) hAppDidEnterBackground:(NSNotification *)notification +{ + if (_connected) + { + _reconnectOnWake = YES; + self.savedSubscriptions = [_subscribed copy]; + [self disconnect]; + } +} + +- (void) hAppDidBecomeActive:(NSNotification *)notification +{ + if (_reconnectOnWake) + { + [self connect]; + _reconnectOnWake = NO; + } +} + +- (void) dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; +} + +#pragma mark - Hello & Registration + +-(void) helloTv +{ + NSDictionary *infoDic = [[NSBundle mainBundle] infoDictionary]; + + CGRect screenBounds = [[UIScreen mainScreen] bounds]; + CGFloat screenScale = [[UIScreen mainScreen] scale]; + CGSize screenSize = CGSizeMake(screenBounds.size.width * screenScale, screenBounds.size.height * screenScale); + NSString *screenResolution = [NSString stringWithFormat:@"%dx%d", (int)screenSize.width, (int)screenSize.height]; + + NSDictionary *payload = @{ + @"sdkVersion" : CONNECT_SDK_VERSION, + @"deviceModel" : ensureString([[UIDevice currentDevice] model]), + @"OSVersion" : ensureString([infoDic objectForKey:@"DTPlatformVersion"]), + @"resolution" : screenResolution, + @"appId" : ensureString([infoDic objectForKey:@"CFBundleIdentifier"]), + @"appName" : ensureString([infoDic objectForKey:@"CFBundleDisplayName"]), + @"appRegion" : ensureString([infoDic objectForKey:@"CFBundleDevelopmentRegion"]) + }; + + ServiceCommand *hello = [[ServiceCommand alloc] init]; + hello.payload = payload; + + hello.delegate = self; + hello.callbackComplete = ^(NSDictionary* response){ + if (self.service.serviceConfig.UUID != nil) + { + if (![self.service.serviceConfig.UUID isEqualToString:[response objectForKey:@"deviceUUID"]]) + { + //Imposter UUID, kill it, kill it with fire + self.service.webOSTVServiceConfig.clientKey = nil; + self.service.webOSTVServiceConfig.SSLCertificates = nil; + self.service.serviceConfig.UUID = nil; + self.service.serviceDescription.address = nil; + self.service.serviceDescription.UUID = nil; + + NSError *UUIDError = [ConnectError generateErrorWithCode:ConnectStatusCodeCertificateError andDetails:nil]; + [self disconnectWithError:UUIDError]; + } + } else + { + self.service.serviceConfig.UUID = self.service.serviceDescription.UUID = [response objectForKey:@"deviceUUID"]; + } + + [self registerWithTv]; + }; + + hello.callbackError = ^(NSError*err) + { + NSError *connectionError = [ConnectError generateErrorWithCode:ConnectStatusCodeSocketError andDetails:nil]; + [self disconnectWithError:connectionError]; + }; + + if (_activeConnections == nil) + _activeConnections = [[NSMutableDictionary alloc] init]; + + [_activeConnections setValue:hello forKey:@"hello"]; + + int dataId = [self getNextId]; + + NSDictionary *sendData = @{ + @"id" : @(dataId), + @"type" : @"hello", + @"payload" : hello.payload + }; + + NSString *sendString = [self writeToJSON:sendData]; + + DLog(@"[OUT] : %@", sendString); + + [_socket send:sendString]; + + if ([_commandQueue containsObject:sendString]) + [_commandQueue removeObject:sendString]; +} + +-(void) registerWithTv +{ + ServiceCommand *reg = [[ServiceCommand alloc] init]; + reg.delegate = self; + reg.callbackComplete = ^(NSDictionary* response) + { + NSString *pairingString = [response valueForKey:@"pairingType"]; + if (pairingString) { + self.service.pairingType = [self pairingStringToType:pairingString]; + // TODO: Need to update the method name socketWillRegister to socketWillRequirePairingWithPairingType. + if (self.delegate && [self.delegate respondsToSelector:@selector(socketWillRegister:)] && self.service.pairingType > DeviceServicePairingTypeFirstScreen){ + [self.delegate socketWillRegister:self]; + } + } + + if ([DeviceService shouldDisconnectOnBackground]) + { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hAppDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hAppDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; + } + +// if ([self.delegate respondsToSelector:@selector(deviceServicePairingSuccess:)]) +// dispatch_on_main(^{ [self.delegate deviceServicePairingSuccess:self]; }); +// +// if ([self.delegate respondsToSelector:@selector(deviceServiceConnectionSuccess:)]) +// dispatch_on_main(^{ [self.delegate deviceServiceConnectionSuccess:self]; }); + + if([_commandQueue count] > 0) + { + [_commandQueue enumerateObjectsUsingBlock:^(NSString *sendString, NSUInteger idx, BOOL *stop) + { + DLog(@"[OUT] : %@", sendString); + + [_socket send:sendString]; + }]; + + _commandQueue = [[NSMutableArray alloc] init]; + } + + if (self.savedSubscriptions.count > 0) { + [self resubscribeSubscriptions:self.savedSubscriptions]; + self.savedSubscriptions = nil; + } + }; + // TODO: this is getting cleaned up before a potential pairing cancelled message is received + reg.callbackError = ^(NSError *error) { + if (self.delegate && [self.delegate respondsToSelector:@selector(socketWillRegister:)]) + [self.delegate socket:self registrationFailed:error]; + }; + + int dataId = [self getNextId]; + + [_activeConnections setObject:reg forKey:[self connectionKeyForMessageId:@(dataId)]]; + + NSDictionary *registerInfo = @{ + @"manifest" : self.manifest, + @"pairingType" : [self pairingTypeToString:self.service.pairingType] + }; + + NSString *sendString = [self encodeData:registerInfo andAddress:nil withId:dataId]; + + DLog(@"[OUT] : %@", sendString); + + [_socket send:sendString]; + + if ([_commandQueue containsObject:sendString]) + [_commandQueue removeObject:sendString]; +} + +-(NSString *)pairingTypeToString:(DeviceServicePairingType)pairingType{ + NSString *pairingTypeString = @""; + + if(pairingType == DeviceServicePairingTypeFirstScreen){ + pairingTypeString = kDeviceServicePairingTypeFirstScreen; + }else + if(pairingType == DeviceServicePairingTypePinCode) + { + pairingTypeString = kDeviceServicePairingTypePinCode; + } + else + if(pairingType == DeviceServicePairingTypeMixed) + { + pairingTypeString = kDeviceServicePairingTypeMixed; + } + return pairingTypeString; +} + +-(DeviceServicePairingType)pairingStringToType:(NSString *)pairingString{ + DeviceServicePairingType pairingType = DeviceServicePairingTypeNone; + + if([pairingString isEqualToString:kDeviceServicePairingTypeFirstScreen]){ + pairingType = DeviceServicePairingTypeFirstScreen; + }else + if([pairingString isEqualToString:kDeviceServicePairingTypePinCode]) + { + pairingType = DeviceServicePairingTypePinCode; + } + else + if([pairingString isEqualToString:kDeviceServicePairingTypeMixed]) + { + pairingType = DeviceServicePairingTypeMixed; + } + return pairingType; +} + +#pragma mark - LGSRWebSocketDelegate + +- (void)webSocketDidOpen:(LGSRWebSocket *)webSocket +{ + _connected = YES; + + [self helloTv]; +} + +- (void)webSocket:(LGSRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean +{ + _connected = NO; + + _activeConnections = [NSMutableDictionary new]; + + _socket.delegate = nil; + _socket = nil; + + NSError *error; + + if (!wasClean) + error = [ConnectError generateErrorWithCode:ConnectStatusCodeTvError andDetails:reason]; + + if (self.delegate) + [self.delegate socket:self didCloseWithError:error]; +} + +- (void)webSocket:(LGSRWebSocket *)webSocket didFailWithError:(NSError *)error +{ + BOOL shouldRetry = NO; + BOOL wasConnected = _connected; + _connected = NO; + + NSError *intError; + + if (error.code == 23556) + { + intError = [ConnectError generateErrorWithCode:ConnectStatusCodeCertificateError andDetails:nil]; + + self.service.webOSTVServiceConfig.SSLCertificates = nil; + self.service.webOSTVServiceConfig.clientKey = nil; + + shouldRetry = YES; + } else + intError = [ConnectError generateErrorWithCode:ConnectStatusCodeSocketError andDetails:error.localizedDescription]; + + for (NSString *key in _activeConnections) + { + ServiceCommand *comm = (ServiceCommand *)[_activeConnections objectForKey:key]; + + if (comm.callbackError) + dispatch_on_main(^{ comm.callbackError(intError); }); + } + + _activeConnections = [NSMutableDictionary dictionary]; + + if (shouldRetry) + { + [self connect]; + return; + } + + if (wasConnected) + { + if ([self.delegate respondsToSelector:@selector(socket:didCloseWithError:)]) + [self.delegate socket:self didCloseWithError:intError]; + } else + { + if ([self.delegate respondsToSelector:@selector(socket:didCloseWithError:)]) + [self.delegate socket:self didFailWithError:intError]; + } +} + +- (void)webSocket:(LGSRWebSocket *)webSocket didGetCertificates:(NSArray *)certs +{ + self.service.webOSTVServiceConfig.SSLCertificates = certs; +} + +- (void)webSocket:(LGSRWebSocket *)webSocket didReceiveMessage:(id)message +{ + NSDictionary *decodeData = [self decodeData:message]; + + DLog(@"[IN] : %@", decodeData); + + if (self.delegate && [self.delegate respondsToSelector:@selector(socket:didReceiveMessage:)]) + { + BOOL shouldProcessMessage = [self.delegate socket:self didReceiveMessage:decodeData]; + + if (!shouldProcessMessage) + return; + } + + NSNumber *comId = [decodeData objectForKey:@"id"]; + NSString *type = [decodeData objectForKey:@"type"]; + NSDictionary *payload = [decodeData objectForKey:@"payload"]; + + NSString *connectionKey = [self connectionKeyForMessageId:comId]; + ServiceCommand *connectionCommand = [_activeConnections objectForKey:connectionKey]; + + if ([type isEqualToString:@"error"]) + { + if (comId && connectionCommand.callbackError) + { + NSError *err = [ConnectError generateErrorWithCode:ConnectStatusCodeTvError andDetails:decodeData]; + dispatch_on_main(^{ connectionCommand.callbackError(err); }); + } + } else + { + if ([type isEqualToString:@"registered"]) + { + NSString *client = [payload objectForKey:@"client-key"]; + self.service.webOSTVServiceConfig.clientKey = client; + + if (self.delegate) + [self.delegate socketDidConnect:self]; + } else if ([type isEqualToString:@"hello"]) + { + //Store information here + ServiceCommand *comm = _activeConnections[@"hello"]; + + if (comm && comm.callbackComplete) + dispatch_on_main(^{ comm.callbackComplete(payload); }); + + [_activeConnections removeObjectForKey:@"hello"]; + } + + if (comId && connectionCommand.callbackComplete) { + dispatch_on_main(^{ connectionCommand.callbackComplete(payload); }); + } + } + + // don't remove subscriptions and "register" command + const BOOL isRegistrationResponse = ([payload isKindOfClass:[NSDictionary class]] && + (payload[@"pairingType"] != nil)); + const BOOL leaveConnection = ([connectionCommand isKindOfClass:[ServiceSubscription class]] || + isRegistrationResponse); + if (!leaveConnection) { + [_activeConnections removeObjectForKey:connectionKey]; + } +} + +#pragma mark - Subscription methods + +- (ServiceSubscription *) addSubscribe:(NSURL *)URL payload:(NSDictionary *)payload success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (_subscribed == nil) + _subscribed = [[NSMutableDictionary alloc] init]; + + NSString *subscriptionReferenceId = [self subscriptionReferenceForURL:URL payload:payload]; + ServiceSubscription *subscription = [_subscribed objectForKey:subscriptionReferenceId]; + + if (subscription == nil) + { + int callId = [self getNextId]; + subscription = [ServiceSubscription subscriptionWithDelegate:self target:URL payload:payload callId:callId]; + [_subscribed setObject:subscription forKey:subscriptionReferenceId]; + } + + if (success) + [subscription addSuccess:success]; + + if (failure) + [subscription addFailure:failure]; + + if (![subscription isSubscribed]) + [subscription subscribe]; + + return subscription; +} + +- (ServiceSubscription *) killSubscribe:(NSURL *)URL payload:(NSDictionary *)payload +{ + NSString *subscriptionReferenceId = [self subscriptionReferenceForURL:URL payload:payload]; + ServiceSubscription *subscription = [_subscribed objectForKey:subscriptionReferenceId]; + + if (subscription) + [_subscribed removeObjectForKey:subscriptionReferenceId]; + + return subscription; +} + +/// Stores the given @c subscriptions dictionary as @c _subscribed and +/// subscribes to all of them. +- (void)resubscribeSubscriptions:(NSDictionary *)subscriptions { + _subscribed = [subscriptions mutableCopy]; + [[_subscribed allValues] makeObjectsPerformSelector:@selector(subscribe)]; +} + +- (NSString *)subscriptionReferenceForURL:(NSURL *)URL payload:(NSDictionary *)payload +{ + NSString *subscriptionReferenceId; + + if (payload) + { + NSString *payloadKeys = [[payload allValues] componentsJoinedByString:@""]; + subscriptionReferenceId = [NSString stringWithFormat:@"%@%@", URL.absoluteString, payloadKeys]; + } else + { + subscriptionReferenceId = URL.absoluteString; + } + + return subscriptionReferenceId; +} + +#pragma mark - Helper methods + +- (NSDictionary *) decodeData:(NSString *) data +{ + return [NSJSONSerialization JSONObjectWithData:[data dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil]; +} + +- (NSString *) writeToJSON:(id) obj +{ + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:obj options:0 error:nil]; + NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + + return jsonString; +} + +- (NSString*) encodeData:(NSDictionary*)data andAddress:(NSURL*)add withId:(int)reqId{ + NSMutableDictionary *sendData = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *payloadData = [[NSMutableDictionary alloc] initWithDictionary:data]; + + if (reqId < 0) + reqId = [self getNextId]; + + [sendData setObject:[NSString stringWithFormat:@"%d", reqId] forKey:@"id"]; + [sendData setObject:payloadData forKey:@"payload"]; + + if (add != nil) + { + [sendData setObject:[add absoluteString] forKey:@"uri"]; + [sendData setObject:@"request" forKey:@"type"]; + } else + { + if (self.service.webOSTVServiceConfig.clientKey) + [payloadData setObject:self.service.webOSTVServiceConfig.clientKey forKey:@"client-key"]; + + [sendData setObject:@"register" forKey:@"type"]; + } + + return [self writeToJSON:sendData]; +} + +- (int) getNextId +{ + _UID = _UID + 1; + return _UID; +} + +/// Returns a connection key unique for the given message id. +- (NSString *)connectionKeyForMessageId:(NSNumber *)messageId { + return [NSString stringWithFormat:@"req%@", messageId]; +} + +#pragma mark - ServiceCommandDelegate + +- (int) sendCommand:(ServiceCommand *)comm withPayload:(NSDictionary *)payload toURL:(NSURL *)URL +{ + int callId = [self getNextId]; + + [_activeConnections setObject:comm forKey:[self connectionKeyForMessageId:@(callId)]]; + + NSString *sendString = [self encodeData:payload andAddress:URL withId:callId]; + + [self sendStringOverSocket:sendString]; + + return callId; +} + +- (void) sendDictionaryOverSocket:(NSDictionary *)payload +{ + NSString *sendString = [self writeToJSON:payload]; + + [self sendStringOverSocket:sendString]; +} + +- (void) sendStringOverSocket:(NSString *)payload +{ + if (_socket == nil) + [self openSocket]; + + if (_socket.readyState == LGSR_OPEN) + { + DLog(@"[OUT] : %@", payload); + + [_socket send:payload]; + + if ([_commandQueue containsObject:payload]) + [_commandQueue removeObject:payload]; + } else if (_socket.readyState == LGSR_CONNECTING || + _socket.readyState == LGSR_CLOSING) + { + [_commandQueue addObject:payload]; + } else + { + [_commandQueue addObject:payload]; + [self openSocket]; + } +} + +- (int) sendSubscription:(ServiceSubscription *)subscription type:(ServiceSubscriptionType)type payload:(id)payload toURL:(NSURL *)URL withId:(int)callId +{ + if (callId < 0) + callId = [self getNextId]; + + [_activeConnections setObject:subscription forKey:[self connectionKeyForMessageId:@(callId)]]; + + NSMutableDictionary *subscriptionPayload = [[NSMutableDictionary alloc] init]; + [subscriptionPayload setObject:@(callId) forKey:@"id"]; + [subscriptionPayload setObject:URL.absoluteString forKey:@"uri"]; + + if (type == ServiceSubscriptionTypeSubscribe) + { + [subscriptionPayload setObject:@"subscribe" forKey:@"type"]; + + if (payload) + [subscriptionPayload setObject:payload forKey:@"payload"]; + } else + { + if (!_connected) + return -1; + + [subscriptionPayload setObject:@"unsubscribe" forKey:@"type"]; + [self killSubscribe:URL payload:payload]; + } + + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:subscriptionPayload options:0 error:nil]; + NSString *sendString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + + [self sendStringOverSocket:sendString]; + + return callId; +} + +#pragma mark - Private + +- (LGSRWebSocket *)createSocketWithURLRequest:(NSURLRequest *)request { + return [[LGSRWebSocket alloc] initWithURLRequest:request]; +} + +@end diff --git a/core/Services/Helpers/WebOSTVServiceSocketClient_Private.h b/core/Services/Helpers/WebOSTVServiceSocketClient_Private.h new file mode 100644 index 00000000..9d3e2d37 --- /dev/null +++ b/core/Services/Helpers/WebOSTVServiceSocketClient_Private.h @@ -0,0 +1,32 @@ +// +// WebOSTVServiceSocketClient_Private.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2/6/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "WebOSTVServiceSocketClient.h" + +@class LGSRWebSocket; + +@interface WebOSTVServiceSocketClient () + +/// Creates a new websocket instance with the given request. +- (LGSRWebSocket *)createSocketWithURLRequest:(NSURLRequest *)request; + +- (NSDictionary *) manifest; + +@end diff --git a/core/Services/Helpers/XMLWriter+ConvenienceMethods.h b/core/Services/Helpers/XMLWriter+ConvenienceMethods.h new file mode 100644 index 00000000..5eb9c80c --- /dev/null +++ b/core/Services/Helpers/XMLWriter+ConvenienceMethods.h @@ -0,0 +1,40 @@ +// +// XMLWriter+ConvenienceMethods.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 3/16/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "XMLWriter.h" + +@interface XMLWriter (ConvenienceMethods) + +- (void)writeElement:(NSString *)elementName withContents:(NSString *)contents; + +- (void)writeElement:(NSString *)elementName + withNamespace:(NSString *)namespace +andContents:(NSString *)contents; + +- (void)writeElement:(NSString *)elementName + withContentsBlock:(void (^)(XMLWriter *writer))writerBlock; + +- (void)writeElement:(NSString *)elementName + withNamespace:(NSString *)namespace +andContentsBlock:(void (^)(XMLWriter *writer))writerBlock; + +- (void)writeAttributes:(NSDictionary *)attributes; + +@end diff --git a/core/Services/Helpers/XMLWriter+ConvenienceMethods.m b/core/Services/Helpers/XMLWriter+ConvenienceMethods.m new file mode 100644 index 00000000..c8d3f0d4 --- /dev/null +++ b/core/Services/Helpers/XMLWriter+ConvenienceMethods.m @@ -0,0 +1,66 @@ +// +// XMLWriter+ConvenienceMethods.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 3/16/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "XMLWriter+ConvenienceMethods.h" + +@implementation XMLWriter (ConvenienceMethods) + +- (void)writeElement:(NSString *)elementName withContents:(NSString *)contents { + [self writeElement:elementName withNamespace:nil andContents:contents]; +} + +- (void)writeElement:(NSString *)elementName + withNamespace:(NSString *)namespace + andContents:(NSString *)contents { + [self writeElement:elementName + withNamespace:namespace + andContentsBlock:^(XMLWriter *xmlWriter) { + [xmlWriter writeCharacters:contents]; + }]; +} + +- (void)writeElement:(NSString *)elementName + withContentsBlock:(void (^)(XMLWriter *))writerBlock { + [self writeElement:elementName + withNamespace:nil + andContentsBlock:writerBlock]; +} + +- (void)writeElement:(NSString *)elementName + withNamespace:(NSString *)namespace + andContentsBlock:(void (^)(XMLWriter *))writerBlock { + NSParameterAssert(writerBlock); + + if (namespace) { + [self writeStartElementWithNamespace:namespace localName:elementName]; + } else { + [self writeStartElement:elementName]; + } + writerBlock(self); + [self writeEndElement]; +} + +- (void)writeAttributes:(NSDictionary *)attributes { + for (NSString *name in attributes) { + [self writeAttribute:name value:attributes[name]]; + } +} + +@end diff --git a/core/Services/NetcastTVService.h b/core/Services/NetcastTVService.h new file mode 100644 index 00000000..4cfc9824 --- /dev/null +++ b/core/Services/NetcastTVService.h @@ -0,0 +1,124 @@ +// +// NetcastTVService.h +// Connect SDK +// +// Created by Jeremy White on 12/2/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#define kConnectSDKNetcastTVServiceId @"Netcast TV" + +#import +#import "DeviceService.h" +#import "Launcher.h" +#import "NetcastTVServiceConfig.h" +#import "DIALService.h" +#import "DLNAService.h" +#import "MediaControl.h" +#import "ExternalInputControl.h" +#import "VolumeControl.h" +#import "TVControl.h" +#import "KeyControl.h" +#import "MouseControl.h" +#import "TextInputControl.h" +#import "PowerControl.h" + +@interface NetcastTVService : DeviceService + +// @cond INTERNAL +/// The base class' @c serviceConfig property downcast to +/// @c NetcastTVServiceConfig if possible, or nil. +@property (nonatomic, strong) NetcastTVServiceConfig *netcastTVServiceConfig; + +// these objects are maintained to provide certain functionality without requiring pairing +@property (nonatomic, strong, readonly) DIALService *dialService; +@property (nonatomic, strong, readonly) DLNAService *dlnaService; + +// Defined at http://developer.lgappstv.com/TV_HELP/topic/lge.tvsdk.references.book/html/UDAP/UDAP/Annex%20A%20Table%20of%20virtual%20key%20codes%20on%20remote%20Controller.htm#_Annex_A_Table +enum { + NetcastTVKeyCodePower = 1, + NetcastTVKeyCodeNumber0 = 2, + NetcastTVKeyCodeNumber1 = 3, + NetcastTVKeyCodeNumber2 = 4, + NetcastTVKeyCodeNumber3 = 5, + NetcastTVKeyCodeNumber4 = 6, + NetcastTVKeyCodeNumber5 = 7, + NetcastTVKeyCodeNumber6 = 8, + NetcastTVKeyCodeNumber7 = 9, + NetcastTVKeyCodeNumber8 = 10, + NetcastTVKeyCodeNumber9 = 11, + NetcastTVKeyCodeUp = 12, + NetcastTVKeyCodeDown = 13, + NetcastTVKeyCodeLeft = 14, + NetcastTVKeyCodeRight = 15, + NetcastTVKeyCodeOK = 20, + NetcastTVKeyCodeHome = 21, + NetcastTVKeyCodeMenu = 22, + NetcastTVKeyCodeBack = 23, + NetcastTVKeyCodeVolumeUp = 24, + NetcastTVKeyCodeVolumeDown = 25, + NetcastTVKeyCodeMute = 26, // Toggle + NetcastTVKeyCodeChannelUp = 27, + NetcastTVKeyCodeChannelDown = 28, + NetcastTVKeyCodeBlue = 29, + NetcastTVKeyCodeGreen = 30, + NetcastTVKeyCodeRed = 31, + NetcastTVKeyCodeYellow = 32, + NetcastTVKeyCodePlay = 33, + NetcastTVKeyCodePause = 34, + NetcastTVKeyCodeStop = 35, + NetcastTVKeyCodeFastForward = 36, + NetcastTVKeyCodeRewind = 37, + NetcastTVKeyCodeSkipForward = 38, + NetcastTVKeyCodeSkipBackward = 39, + NetcastTVKeyCodeRecord = 40, + NetcastTVKeyCodeRecordingList = 41, + NetcastTVKeyCodeRepeat = 42, + NetcastTVKeyCodeLiveTV = 43, + NetcastTVKeyCodeEPG = 44, + NetcastTVKeyCodeCurrentProgramInfo = 45, + NetcastTVKeyCodeAspectRatio = 46, + NetcastTVKeyCodeExternalInput = 47, + NetcastTVKeyCodePIP = 48, + NetcastTVKeyCodeSubtitle = 49, // Toggle + NetcastTVKeyCodeProgramList = 50, + NetcastTVKeyCodeTeleText = 51, + NetcastTVKeyCodeMark = 52, + NetcastTVKeyCode3DVideo = 400, + NetcastTVKeyCode3DLR = 401, + NetcastTVKeyCodeDash = 402, // (-) + NetcastTVKeyCodePreviousChannel = 403, + NetcastTVKeyCodeFavoriteChannel = 404, + NetcastTVKeyCodeQuickMenu = 405, + NetcastTVKeyCodeTextOption = 406, + NetcastTVKeyCodeAudioDescription = 407, + NetcastTVKeyCodeNetcast = 408, + NetcastTVKeyCodeEnergySaving = 409, + NetcastTVKeyCodeAVMode = 410, + NetcastTVKeyCodeSIMPLINK = 411, + NetcastTVKeyCodeExit = 412, + NetcastTVKeyCodeReservationProgramsList = 413, + NetcastTVKeyCodePIPChannelUp = 414, + NetcastTVKeyCodePIPChannelDown = 415, + NetcastTVKeyCodeVideoSwitch = 416, + NetcastTVKeyCodeMyApps = 417 +}; + +typedef NSUInteger NetcastTVKeyCode; + +- (void) pairWithData:(NSString *)pairingData; +// @endcond + +@end diff --git a/core/Services/NetcastTVService.m b/core/Services/NetcastTVService.m new file mode 100644 index 00000000..afe4aca6 --- /dev/null +++ b/core/Services/NetcastTVService.m @@ -0,0 +1,2259 @@ +// +// NetcastTVService.m +// Connect SDK +// +// Created by Jeremy White on 12/2/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NetcastTVService_Private.h" +#import "ConnectError.h" +#import "CTXMLReader.h" +#import "GCDWebServer.h" +#import "GCDWebServerDataRequest.h" +#import "ConnectUtil.h" +#import "DeviceServiceReachability.h" +#import "DiscoveryManager.h" +#import "ServiceAsyncCommand.h" +#import "CommonMacros.h" + +#import "NSObject+FeatureNotSupported_Private.h" +#import "XMLWriter+ConvenienceMethods.h" + +#define kSmartShareName @"SmartShare™" + +typedef enum { + LGE_EVENT_REQUEST = 0, + LGE_COMMAND_REQUEST, + LGE_AUTH_REQUEST, + LGE_DATA_GET_REQUEST, + LGE_STREAMING_REQUEST, + LGE_QUERY_REQUEST, + LGE_PAIRING_REQUEST, + LGE_APPTOAPP_DATA_REQUEST +} LGE_REQUEST_TYPE; + +@interface NetcastTVService() +{ + NSOperationQueue *_commandQueue; + BOOL _mouseVisible; + + GCDWebServer *_subscriptionServer; + NSString *_keyboardString; + + // TODO: pull pairing timer from WebOSTVService + UIAlertController *_pairingAlert; + + NSMutableDictionary *_subscribed; + NSURL *_commandURL; + + BOOL _reconnectOnWake; + + CGVector _mouseDistance; + BOOL _mouseIsMoving; + + DeviceServiceReachability *_serviceReachability; +} + +@end + +@implementation NetcastTVService + +@synthesize dialService = _dialService; +@synthesize dlnaService = _dlnaService; + +NSString *lgeUDAPRequestURI[8] = { + @"/udap/api/event", //LGE_EVENT_REQUEST + @"/udap/api/command", //LGE_COMMAND_REQUEST + @"/udap/api/auth", //LGE_AUTH_REQUEST + @"/udap/api/data", //LGE_DATA_GET_REQUEST + @"/", //LGE_STREAMING_REQUEST + @"/udap/api/query", //LGE_QUERY_REQUEST + @"/udap/api/pairing", //LGE_PAIRING_REQUEST + @"/udap/api/apptoapp/data" //LGE_APPTOAPP_DATA_REQUEST +}; + +- (void) commonConfig +{ + _dlnaService = [[DLNAService alloc] initWithServiceConfig:self.serviceConfig]; +} + +- (instancetype) initWithJSONObject:(NSDictionary *)dict +{ + self = [super initWithJSONObject:dict]; + + if (self) + { + [self commonConfig]; + } + + return self; +} + +- (instancetype) initWithServiceConfig:(ServiceConfig *)serviceConfig +{ + self = [super init]; + + if (self) + { + [self setServiceConfig:serviceConfig]; + } + + return self; +} + +- (NetcastTVServiceConfig *)netcastTVServiceConfig { + return ([self.serviceConfig isKindOfClass:[NetcastTVServiceConfig class]] ? + (NetcastTVServiceConfig *)self.serviceConfig : + nil); +} + +- (void) setServiceConfig:(ServiceConfig *)serviceConfig +{ + const BOOL oldServiceConfigHasCode = (self.netcastTVServiceConfig.pairingCode != nil); + if ([serviceConfig isKindOfClass:[NetcastTVServiceConfig class]]) + { + const BOOL newServiceConfigHasCode = (((NetcastTVServiceConfig *)serviceConfig).pairingCode != nil); + const BOOL wouldLoseCode = oldServiceConfigHasCode && !newServiceConfigHasCode; + _assert_state(!wouldLoseCode, @"Replacing important data!"); + + [super setServiceConfig:serviceConfig]; + } else + { + _assert_state(!oldServiceConfigHasCode, @"Replacing important data!"); + + [super setServiceConfig:[[NetcastTVServiceConfig alloc] initWithServiceConfig:serviceConfig]]; + } +} + +- (void)setServiceDescription:(ServiceDescription *)serviceDescription +{ + [super setServiceDescription:serviceDescription]; + + if (!self.serviceConfig.UUID) + self.serviceConfig.UUID = serviceDescription.UUID; + + [_dlnaService setServiceDescription:serviceDescription]; +} + +- (NSURL *)commandURL +{ + if (_commandURL == nil) + { + NSString *commandPath = [NSString stringWithFormat:@"http://%@:8080", self.serviceDescription.address]; + _commandURL = [NSURL URLWithString:commandPath]; + } + + return _commandURL; +} + +- (void) updateCapabilities +{ + NSArray *capabilities = @[kMediaPlayerSubtitleSRT]; + + if ([DiscoveryManager sharedManager].pairingLevel == DeviceServicePairingLevelOn) + { + capabilities = [capabilities arrayByAddingObjectsFromArray:kTextInputControlCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:kMouseControlCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:kKeyControlCapabilities]; + capabilities = [capabilities arrayByAddingObject:kPowerControlOff]; + capabilities = [capabilities arrayByAddingObjectsFromArray:kMediaPlayerCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:@[ + kMediaControlPlay, + kMediaControlPause, + kMediaControlStop, + kMediaControlPlayState, + kMediaControlPosition, + kMediaControlDuration, + kMediaControlSeek, + + kLauncherApp, + kLauncherAppClose, + kLauncherAppStore, + kLauncherAppList, + kLauncherAppState, + kLauncherBrowser, + kLauncherHulu, + kLauncherNetflix, + kLauncherNetflixParams, + kLauncherYouTube, + kLauncherYouTubeParams, + + kTVControlChannelUp, + kTVControlChannelDown, + kTVControlChannelGet, + kTVControlChannelList, + kTVControlChannelSubscribe, + kTVControl3DGet, + kTVControl3DSet, + kTVControl3DSubscribe, + + kExternalInputControlPickerLaunch, + kExternalInputControlPickerClose, + + kVolumeControlVolumeGet, + kVolumeControlVolumeUpDown, + kVolumeControlMuteGet, + kVolumeControlMuteSet + ]]; + + if ([self.modelNumber isEqualToString:@"4.0"]) + { + capabilities = [capabilities arrayByAddingObjectsFromArray:@[ + kLauncherAppStoreParams + ]]; + } + } else + { + // TODO: need to handle some of these controls over DLNA if no pairing + capabilities = [capabilities arrayByAddingObjectsFromArray:kMediaPlayerCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:@[ + kMediaControlPlay, + kMediaControlPause, + kMediaControlStop, + kMediaPlayerMetaDataTitle, + kMediaPlayerMetaDataMimeType, + + kLauncherYouTube, + kLauncherYouTubeParams + ]]; + } + + [self setCapabilities:capabilities]; +} + ++ (NSDictionary *) discoveryParameters +{ + /* + NetcastTVService and DLNAService have the same ssdp.filter, but + requiredServices was missing here. That could create differences in device + discovery with DLNA and (with or without Netcast). e.g., when a device had + this filter, but not the required services, Netcast would pass it even if + it is a generic DLNA device. Moreover, that would depend on the order of + adding services to the DiscoveryManager. + To avoid the inconsistency, NetcastTVService has the same requiredServices + as DLNAService. + */ + + return @{ + @"serviceId": kConnectSDKNetcastTVServiceId, + @"ssdp":@{ + @"filter":@"urn:schemas-upnp-org:device:MediaRenderer:1", + // `requiredServices` from the `DLNAService`, see comment above + @"requiredServices": @[ + @"urn:schemas-upnp-org:service:AVTransport:1", + @"urn:schemas-upnp-org:service:RenderingControl:1" + ], + @"userAgentToken":@"UDAP/2.0" + } + }; +} + +- (void) dealloc +{ + [self.commandQueue cancelAllOperations]; + + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; + +} + +#pragma mark - Getters & Setters + +/// Returns the set delegate property value or self. +- (id)serviceCommandDelegate { + return _serviceCommandDelegate ?: self; +} + +#pragma mark - Connection & Pairing + +- (BOOL)isConnectable +{ + return YES; +} + +- (void)connect +{ + if (self.connected) + return; + + if (self.netcastTVServiceConfig.pairingCode) + [self pairWithData:self.netcastTVServiceConfig.pairingCode]; + else + { + if ([DiscoveryManager sharedManager].pairingLevel == DeviceServicePairingLevelOn) + [self invokePairing]; + else + [self hConnectSuccess]; + } +} + +- (void) hConnectSuccess +{ + _serviceReachability = [DeviceServiceReachability reachabilityWithTargetURL:self.commandURL]; + _serviceReachability.delegate = self; + [_serviceReachability start]; + + self.connected = YES; + + if (self.delegate && [self.delegate respondsToSelector:@selector(deviceServiceConnectionSuccess:)]) + dispatch_on_main(^{ [self.delegate deviceServiceConnectionSuccess:self]; }); +} + +- (void) didLoseReachability:(DeviceServiceReachability *)reachability +{ + if (self.connected) + [self disconnect]; + else + [_serviceReachability stop]; +} + +- (void) invokePairing +{ + NSString *targetPath = [self.commandURL.absoluteString stringByAppendingPathComponent:lgeUDAPRequestURI[LGE_PAIRING_REQUEST]]; + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + NSString *payload = @ + "" + "" + "showKey" + "" + ""; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:payload]; + command.callbackComplete = ^(NSDictionary *responseDic) + { + [self showPairingDialog]; + [self.delegate deviceService:self pairingRequiredOfType:self.pairingType withData:self.pairingData]; + }; + command.callbackError = ^(NSError *error) + { + [self.delegate deviceService:self didFailConnectWithError:error]; + }; + [command send]; +} + +- (void) showPairingDialog +{ + NSString *title = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Pair_Title" value:@"Pairing with device" table:@"ConnectSDK"]; + NSString *message = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Pair_Request_Pin" value:@"Please enter the pin code" table:@"ConnectSDK"]; + NSString *ok = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Pair_OK" value:@"OK" table:@"ConnectSDK"]; + NSString *cancel = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Pair_Cancel" value:@"Cancel" table:@"ConnectSDK"]; + + _pairingAlert = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + // Add a text field to the alert controller + [_pairingAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.keyboardType = UIKeyboardTypeNumberPad; + }]; + + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancel + style:UIAlertActionStyleCancel + handler:^(UIAlertAction *action) { + [self dismissPairingWithSuccess:nil failure:nil]; + }]; + + UIAlertAction *okAction = [UIAlertAction actionWithTitle:ok + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + UITextField *textField = _pairingAlert.textFields.firstObject; + NSString *pairingCode = textField.text; + [self pairWithData:pairingCode]; + }]; + + // Add the actions to the alert controller + [_pairingAlert addAction:cancelAction]; + [_pairingAlert addAction:okAction]; + + // Present the alert controller + UIWindow *window = [UIApplication sharedApplication].keyWindow; + UIViewController *rootViewController = window.rootViewController; + [rootViewController presentViewController: _pairingAlert animated:YES completion:nil]; +} + +- (void) dismissPairingWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *targetPath = [self.commandURL.absoluteString stringByAppendingPathComponent:lgeUDAPRequestURI[LGE_PAIRING_REQUEST]]; + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + NSString *payload = @ + "" + "" + "byebye" + "8080" + "" + ""; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:payload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)disconnect +{ + if (!self.connected) + return; + + if (!_reconnectOnWake) + { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; + } + + self.connected = NO; + [self stopSubscriptionServer]; + + [self dismissPairingWithSuccess:^(id responseObject) + { + [self.commandQueue cancelAllOperations]; + } failure:nil]; + + [_serviceReachability stop]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(deviceService:disconnectedWithError:)]) + dispatch_on_main(^{ [self.delegate deviceService:self disconnectedWithError:nil]; }); +} + +- (void) startSubscriptionServer +{ + typedef void (^ TextEditedHandler)(NSString *); + + TextEditedHandler textEditedHandler = ^(NSString *text) { + _keyboardString = text; + }; + + typedef void (^ SubscriptionHandler)(NSString *, NSDictionary *); + + SubscriptionHandler subscriptionHandler = ^(NSString *eventName, NSDictionary *responseXML) { + ServiceSubscription *subscription = [_subscribed objectForKey:eventName]; + + if (subscription) + { + [subscription.successCalls enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) + { + ((SuccessBlock) obj)(responseXML); + }]; + } + }; + + _subscriptionServer = [[GCDWebServer alloc] init]; + + [_subscriptionServer addDefaultHandlerForMethod:@"POST" + requestClass:[GCDWebServerDataRequest class] + processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { + GCDWebServerDataRequest *dataRequest = (GCDWebServerDataRequest *)request; + + NSError *xmlError; + NSDictionary *responseXML = [CTXMLReader dictionaryForXMLData:dataRequest.data error:&xmlError]; + + if (!xmlError) + { + NSString *eventName = [[[[responseXML objectForKey:@"envelope"] objectForKey:@"api"] objectForKey:@"name"] objectForKey:@"text"]; + + if ([eventName isEqualToString:@"TextEdited"]) + { + NSString *text = [[[[responseXML objectForKey:@"envelope"] objectForKey:@"api"] objectForKey:@"value"] objectForKey:@"text"]; + + if (text && text.length > 0) + dispatch_on_main(^{ textEditedHandler(text); }); + } else + { + dispatch_on_main(^{ subscriptionHandler(eventName, responseXML); }); + } + } + + return [GCDWebServerResponse responseWithStatusCode:200]; + + }]; + + [_subscriptionServer startWithPort:8080 bonjourName:nil]; +} + +- (void) stopSubscriptionServer +{ + if (_subscriptionServer) + { + if ([_subscriptionServer isRunning]) + { + [_subscriptionServer stop]; + } + _subscriptionServer = nil; + } +} + +- (void) hAppDidEnterBackground:(NSNotification *)notification +{ + if (self.connected) + { + _reconnectOnWake = YES; + [self disconnect]; + } +} + +- (void) hAppDidBecomeActive:(NSNotification *)notification +{ + if (_reconnectOnWake) + { + [self connect]; + _reconnectOnWake = NO; + } +} + +#pragma mark - Pairing + +- (BOOL) requiresPairing +{ + return [DiscoveryManager sharedManager].pairingLevel == DeviceServicePairingLevelOn; +} + +- (DeviceServicePairingType)pairingType +{ + return DeviceServicePairingTypePinCode; +} + +- (id)pairingData +{ + return @{ + @"keyLength" : @(6) + }; +} + +- (void)pairWithData:(NSString *)pairingCode +{ + NSString *targetPath = [self.commandURL.absoluteString stringByAppendingPathComponent:lgeUDAPRequestURI[LGE_PAIRING_REQUEST]]; + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + NSString *payload = [NSString stringWithFormat:@ + "" + "" + "hello" + "%@" + "%@" + "" + "", pairingCode, self.commandURL.port]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:payload]; + command.callbackComplete = ^(NSDictionary *responseDic){ + self.netcastTVServiceConfig.pairingCode = pairingCode; + + if ([DeviceService shouldDisconnectOnBackground]) + { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hAppDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hAppDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; + } + + [self startSubscriptionServer]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(deviceServicePairingSuccess:)]) + dispatch_on_main(^{ [self.delegate deviceServicePairingSuccess:self]; }); + + [self hConnectSuccess]; + }; + command.callbackError = ^(NSError *error) + { + [self.delegate deviceService:self pairingFailedWithError:error]; + }; + [command send]; +} + +#pragma mark - ServiceCommandDelegate + +- (int) sendCommand:(ServiceCommand *)command withPayload:(NSString *)payload toURL:(NSURL *)URL +{ + NSString *xml = [NSString stringWithFormat:@"%@", payload]; + NSData *xmlData = [xml dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]; + + NSString *dataLength = [NSString stringWithFormat:@"%i", (unsigned int) [xmlData length]]; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; + [request setCachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData]; + [request setTimeoutInterval:30]; + [request setValue:@"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"]; + [request setValue:@"Close" forHTTPHeaderField:@"Connection"]; + [request setValue:@"Apple iOS UDAP/2.0 Connect SDK" forHTTPHeaderField:@"User-Agent"]; + [request setHTTPMethod:command.HTTPMethod]; + + if (payload && payload.length > 0) + { + [request setValue:dataLength forHTTPHeaderField:@"Content-Length"]; + [request setHTTPBody:xmlData]; + } + + DLog(@"[OUT] : %@ \n %@", [request allHTTPHeaderFields], xml); + + [NSURLConnection sendAsynchronousRequest:request queue:self.commandQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) + { + DLog(@"[IN] : %@", [((NSHTTPURLResponse *)response) allHeaderFields]); + + if (connectionError || !data) + { + if (command.callbackError) + dispatch_on_main(^{ command.callbackError(connectionError); }); + } else + { + if ([data length] == 0) + { + id contentLengthValue = [[((NSHTTPURLResponse *) response) allHeaderFields] objectForKey:@"Content-Length"]; + + if (contentLengthValue) + { + int contentLength = [contentLengthValue intValue]; + + if (contentLength > 0) + { + if (command.callbackError) + dispatch_on_main(^{ command.callbackError([ConnectError generateErrorWithCode:ConnectStatusCodeTvError andDetails:@"Expected data from server, but did not receive any."]); }); + + return; + } + } + + if (command.callbackComplete) + dispatch_on_main(^{ command.callbackComplete(nil); }); + } else + { + NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + + DLog(@"[IN] : %@", dataString); + + if (dataString) + { + NSError *commandError = [self parseCommandResponse:response + data:dataString]; + if (commandError) + { + if (command.callbackError) + dispatch_on_main(^{ command.callbackError(commandError); }); + } else + { + NSError *xmlError; + NSDictionary *responseDic = [CTXMLReader dictionaryForXMLData:data error:&xmlError]; + + if (xmlError || !responseDic) + { + if (command.callbackComplete) + dispatch_on_main(^{ command.callbackComplete(dataString); }); + } else + { + if (command.callbackComplete) + dispatch_on_main(^{ command.callbackComplete(responseDic); }); + } + } + } else + { + if (command.callbackComplete) + dispatch_on_main(^{ command.callbackComplete(nil); }); + } + } + } + + }]; + + // TODO: implement callIds + return 0; +} + +#pragma mark - Helper methods + +- (NSError *)parseCommandResponse:(NSURLResponse *)response data:(NSString *)responseData +{ + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + NSString *errorMessage; + + switch ([httpResponse statusCode]) + { + case 400: + errorMessage = @"The command format is not valid or it has an incorrect value."; + break; + + case 401: + errorMessage = @"A command is sent when a Host and a Controller are not paired."; + break; + + case 404: + errorMessage = @"The POST path of a command is incorrect."; + break; + + case 500: + errorMessage = @"The command execution has failed."; + break; + + default: + break; + } + + NSError *error; + if (errorMessage) + { + error = [ConnectError generateErrorWithCode:ConnectStatusCodeTvError + andDetails:errorMessage]; + } + + return error; +} + +- (NSOperationQueue *)commandQueue +{ + if (_commandQueue == nil) + { + _commandQueue = [[NSOperationQueue alloc] init]; + } + + return _commandQueue; +} + ++ (ChannelInfo *)channelInfoFromXML:(NSDictionary *)info +{ + ChannelInfo *channelInfo = [[ChannelInfo alloc] init]; + channelInfo.id = [[info objectForKey:@"physicalNum"] objectForKey:@"text"]; + channelInfo.name = [[info objectForKey:@"chname"] objectForKey:@"text"]; + channelInfo.number = [NSString stringWithFormat:@"%@-%@", + [[info objectForKey:@"major"] objectForKey:@"text"], + [[info objectForKey:@"minor"] objectForKey:@"text"]]; + channelInfo.majorNumber = [[[info objectForKey:@"displayMajor"] objectForKey:@"text"] intValue]; + channelInfo.minorNumber = [[[info objectForKey:@"displayMinor"] objectForKey:@"text"] intValue]; + channelInfo.rawData = [info copy]; + + return channelInfo; +} + ++ (AppInfo *)appInfoFromXML:(NSDictionary *)info +{ + AppInfo *appInfo = [[AppInfo alloc] init]; + appInfo.name = [[info objectForKey:@"name"] objectForKey:@"text"]; + appInfo.id = [[info objectForKey:@"auid"] objectForKey:@"text"]; + appInfo.rawData = [info copy]; + + appInfo.rawData[@"cpid"][@"text"] = @""; + + return appInfo; +} + +- (NSString *) modelNumber +{ + if (self.serviceDescription.modelNumber == nil) + { + NSString *xmlString = self.dlnaService.serviceDescription.locationXML; + NSError *xmlError; + NSDictionary *xml = [CTXMLReader dictionaryForXMLString:xmlString error:&xmlError]; + + if (!xmlError) + { + NSString *model = [[[[xml objectForKey:@"root"] objectForKey:@"device"] objectForKey:@"modelNumber"] objectForKey:@"text"]; + + if (model) + self.serviceDescription.modelNumber = [[[model stringByReplacingOccurrencesOfString:@"\n" withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""] stringByReplacingOccurrencesOfString:@"\t" withString:@""]; + } + } + + return self.serviceDescription.modelNumber; +} + +#pragma mark - Launcher + +- (id )launcher +{ + return self; +} + +- (CapabilityPriorityLevel) launcherPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)getAppListWithSuccess:(AppListSuccessBlock)success failure:(FailureBlock)failure +{ + // This is a very inefficient solution for getting a full app list. This particular solution is + // required to support 2012 Netcast TVs, which require an app count for getting a list of apps. + // 2012 Netcast TVs also don't support getting an "all apps" list or list count. + + static int APP_TYPE_PREMIUM = 2; + static int APP_TYPE_MY_APPS = 3; + + __block int premiumAppsCount; + __block int myAppsCount; + + __block NSArray *premiumApps; + __block NSArray *myApps; + + [self getNumberOfAppsForType:APP_TYPE_PREMIUM success:^(int numberOfPremiumApps) + { + premiumAppsCount = numberOfPremiumApps; + + [self getNumberOfAppsForType:APP_TYPE_MY_APPS success:^(int numberOfMyApps) + { + myAppsCount = numberOfMyApps; + + [self getAppListForType:APP_TYPE_PREMIUM numberOfApps:premiumAppsCount success:^(NSArray *premiumAppsList) + { + premiumApps = premiumAppsList; + + [self getAppListForType:APP_TYPE_MY_APPS numberOfApps:myAppsCount success:^(NSArray *myAppsList) + { + myApps = myAppsList; + + NSMutableDictionary *allApps = [[NSMutableDictionary alloc] init]; + + [premiumApps enumerateObjectsUsingBlock:^(AppInfo *appInfo, NSUInteger idx, BOOL *stop) + { + if (appInfo) + [allApps setObject:appInfo forKey:appInfo.id]; + }]; + + [myApps enumerateObjectsUsingBlock:^(AppInfo *appInfo, NSUInteger idx, BOOL *stop) + { + if (appInfo) + [allApps setObject:appInfo forKey:appInfo.id]; + }]; + + if (success) + success([allApps allValues]); + } failure:failure]; + } failure:failure]; + } failure:failure]; + } failure:failure]; +} + +- (void) getNumberOfAppsForType:(int)type success:(void (^)(int numberOfApps))success failure:(FailureBlock)failure +{ + NSString *targetPath = [NSString stringWithFormat:@"%@%@%@", + self.commandURL.absoluteString, + lgeUDAPRequestURI[LGE_DATA_GET_REQUEST], + [NSString stringWithFormat:@"?target=appnum_get&type=%d", type] + ]; + + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:nil]; + command.HTTPMethod = @"GET"; + command.callbackComplete = ^(NSDictionary *responseDic) + { + NSDictionary *rawResponse = [[[responseDic objectForKey:@"envelope"] objectForKey:@"dataList"] objectForKey:@"data"]; + NSNumber *numberOfApps = [[rawResponse objectForKey:@"number"] objectForKey:@"text"]; + + int numberOfAppsInt = numberOfApps.intValue; + + if (numberOfAppsInt == 0) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeTvError andDetails:@""]); + } else + { + if (success) + success(numberOfAppsInt); + } + }; + command.callbackError = failure; + [command send]; +} + +- (void) getAppListForType:(int)type numberOfApps:(int)numberOfApps success:(AppListSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *targetPath = [NSString stringWithFormat:@"%@%@%@", + self.commandURL.absoluteString, + lgeUDAPRequestURI[LGE_DATA_GET_REQUEST], + [NSString stringWithFormat:@"?target=applist_get&type=%d&index=1&number=%d", type, numberOfApps] + ]; + + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:nil]; + command.HTTPMethod = @"GET"; + command.callbackComplete = ^(NSDictionary *responseDic) + { + NSArray *rawApps = [[[responseDic objectForKey:@"envelope"] objectForKey:@"dataList"] objectForKey:@"data"]; + NSMutableArray *appList = [[NSMutableArray alloc] init]; + + [rawApps enumerateObjectsUsingBlock:^(NSDictionary *appInfo, NSUInteger idx, BOOL *stop) + { + [appList addObject:[NetcastTVService appInfoFromXML:appInfo]]; + }]; + + if (success) + success([NSArray arrayWithArray:appList]); + }; + command.callbackError = failure; + [command send]; +} + +- (void)launchApp:(NSString *)appId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self getAppInfoForId:appId success:^(AppInfo *appInfo) + { + [self launchAppWithInfo:appInfo success:success failure:failure]; + } failure:failure]; +} + +- (void)launchAppWithInfo:(AppInfo *)appInfo success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *targetPath = [self.commandURL.absoluteString stringByAppendingPathComponent:lgeUDAPRequestURI[LGE_COMMAND_REQUEST]]; + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + NSString *payload = [NSString stringWithFormat:@ + "" + "" + "AppExecute" + "%@" + "%@" + "%@" + "" + "", + appInfo.id, appInfo.name, [[appInfo.rawData objectForKey:@"cpid"] objectForKey:@"text"]]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:payload]; + command.callbackComplete = ^(NSDictionary *responseObject) + { + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:appInfo.id]; + launchSession.name = appInfo.name; + launchSession.sessionType = LaunchSessionTypeApp; + launchSession.service = self; + launchSession.rawData = [responseObject copy]; + + if (success) + success(launchSession); + }; + command.callbackError = failure; + [command send]; +} + +- (void)getAppInfoForId:(NSString *)appId success:(AppInfoSuccessBlock)success failure:(FailureBlock)failure +{ + [self getAppListWithSuccess:^(NSArray *appList) + { + __block AppInfo *appInfo; + + [appList enumerateObjectsUsingBlock:^(AppInfo *app, NSUInteger idx, BOOL *stop) + { + if ([app.name.lowercaseString isEqualToString:appId.lowercaseString]) + { + appInfo = app; + *stop = YES; + } + }]; + + if (appInfo && success) + success(appInfo); + + if (appInfo == nil && failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Could not find app with specified id."]); + } failure:failure]; +} + +- (void)launchApplication:(NSString *)appId withParams:(NSDictionary *)params success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)launchAppWithInfo:(AppInfo *)appInfo params:(NSDictionary *)params success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void) launchAppStore:(NSString *)appId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *targetPath = [self.commandURL.absoluteString stringByAppendingPathComponent:@"roap"]; + targetPath = [targetPath stringByAppendingPathComponent:@"api"]; + targetPath = [targetPath stringByAppendingPathComponent:@"command"]; + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + NSString *payload = [NSString stringWithFormat:@ + "" + "" + "SearchCMDPlaySDPContent" + "4" + "" + "" + "" + "12" + "" + "%@" + "S" + "" + "", [ConnectUtil urlEncode:appId]]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:payload]; + command.callbackComplete = ^(id responseObject) + { + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:@""]; + launchSession.name = @"LG Smart World"; // TODO: this will not work in Korea, use "LG 스마트 월드" instead + launchSession.sessionType = LaunchSessionTypeApp; + launchSession.service = self; + + if (success) + success(launchSession); + }; + command.callbackError = failure; + [command send]; +} + +- (void)launchBrowser:(NSURL *)target success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self launchApp:@"Internet" success:success failure:failure]; +} + +- (void)launchHulu:(NSString *)contentId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self getAppInfoForId:@"Hulu Plus" success:^(AppInfo *appInfo) + { + [[appInfo.rawData objectForKey:@"cpid"] setObject:contentId forKey:@"text"]; + + if (success) + [self launchAppWithInfo:appInfo success:success failure:failure]; + } failure:failure]; +} + +- (void)launchNetflix:(NSString *)contentId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + if (![[self modelNumber] isEqualToString:@"4.0"]) + { + [self launchApp:@"Netflix" success:success failure:failure]; + return; + } + + NSString *targetPath = [self.commandURL.absoluteString stringByAppendingPathComponent:lgeUDAPRequestURI[LGE_COMMAND_REQUEST]]; + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + NSString *contentPath = [NSString stringWithFormat:@"http://api.netflix.com/catalog/titles/movies/%@&source_type=4&trackId=6054700&trackUrl=https://api.netflix.com/API_APP_ID_6261?#Search?", contentId]; + NSString *payload = [NSString stringWithFormat:@ + "" + "" + "SearchCMDPlaySDPContent" + "1" + "20" + "N" + "2023237" + "18" + "netflix" + "-Q m=%@" + "" + "" + "", [ConnectUtil urlEncode:contentPath]]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:payload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)launchYouTube:(NSString *)contentId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self.launcher launchYouTube:contentId startTime:0.0 success:success failure:failure]; +} + +- (void) launchYouTube:(NSString *)contentId startTime:(float)startTime success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + if (self.dialService) + { + [self.dialService.launcher launchYouTube:contentId startTime:startTime success:success failure:failure]; + return; + } + + if (startTime <= 0.0) + { + [self getAppInfoForId:@"YouTube" success:^(AppInfo *appInfo) + { + [[appInfo.rawData objectForKey:@"cpid"] setObject:contentId forKey:@"text"]; + + if (success) + [self launchAppWithInfo:appInfo success:success failure:failure]; + } failure:failure]; + } else + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeNotSupported andDetails:@"Cannot reach DIAL service for launching with provided start time"]); + } +} + +- (void)closeApplicationWithName:(NSString *)appId success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self getAppInfoForId:appId success:^(AppInfo *info) + { + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:info.id]; + launchSession.name = info.name; + launchSession.sessionType = LaunchSessionTypeApp; + launchSession.service = self; + + [self closeApp:launchSession success:success failure:failure]; + } failure:failure]; +} + +- (void)closeApp:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (![launchSession.name isEqualToString:kSmartShareName]) + { + if (launchSession.appId == nil || launchSession.appId.length == 0) + { + [self closeApplicationWithName:launchSession.name success:success failure:failure]; + return; + } + } + + NSString *targetPath = [self.commandURL.absoluteString stringByAppendingPathComponent:lgeUDAPRequestURI[LGE_COMMAND_REQUEST]]; + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + NSString *appId = (launchSession.appId) ? launchSession.appId : @""; + NSString *appName = (launchSession.name) ? launchSession.name : @""; + + NSString *payload = [NSString stringWithFormat:@ + "" + "" + "AppTerminate" + "%@" + "%@" + "" + "", appId, appName]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:payload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)getRunningAppWithSuccess:(AppInfoSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribeRunningAppWithSuccess:(AppInfoSuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +- (void)getAppState:(LaunchSession *)launchSession success:(AppStateSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *targetPath = [NSString stringWithFormat:@"%@%@%@", + self.commandURL.absoluteString, + lgeUDAPRequestURI[LGE_APPTOAPP_DATA_REQUEST], + [NSString stringWithFormat:@"/%@/status", launchSession.appId] + ]; + + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:nil]; + command.callbackComplete = ^(NSString *response) + { + // TODO: need to test this + if (success) + { + if ([response isEqualToString:@"NONE"]) success(NO, NO); + else if ([response isEqualToString:@"LOAD"]) success(NO, YES); + else if ([response isEqualToString:@"RUN_NF"]) success(YES, NO); + else if ([response isEqualToString:@"TERM"]) success(NO, YES); + else success(NO, NO); + } + }; + command.callbackError = failure; + [command send]; +} + +- (ServiceSubscription *)subscribeAppState:(LaunchSession *)launchSession success:(AppStateSuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +- (DLNAService *)dlnaService +{ + if (_dlnaService == nil) + { + DiscoveryManager *discoveryManager = [DiscoveryManager sharedManager]; + ConnectableDevice *device = [discoveryManager.allDevices objectForKey:self.serviceDescription.address]; + + if (device) + { + __block DLNAService *foundService; + + [device.services enumerateObjectsUsingBlock:^(DeviceService *service, NSUInteger idx, BOOL *stop) + { + if ([service isKindOfClass:[DLNAService class]]) + { + foundService = (DLNAService *)service; + *stop = YES; + } + }]; + + _dlnaService = foundService; + } + } + + return _dlnaService; +} + +- (DIALService *)dialService +{ + if (_dialService == nil) + { + DiscoveryManager *discoveryManager = [DiscoveryManager sharedManager]; + ConnectableDevice *device = [discoveryManager.allDevices objectForKey:self.serviceDescription.address]; + + if (device) + { + __block DIALService *foundService; + + [device.services enumerateObjectsUsingBlock:^(DeviceService *service, NSUInteger idx, BOOL *stop) + { + if ([service isKindOfClass:[DIALService class]]) + { + foundService = (DIALService *)service; + *stop = YES; + } + }]; + + _dialService = foundService; + } + } + + return _dialService; +} + +#pragma mark - Media Player + +- (id )mediaPlayer +{ + return self; +} + +- (CapabilityPriorityLevel)mediaPlayerPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)displayImage:(NSURL *)imageURL iconURL:(NSURL *)iconURL title:(NSString *)title description:(NSString *)description mimeType:(NSString *)mimeType success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:imageURL mimeType:mimeType]; + mediaInfo.title = title; + mediaInfo.description = description; + ImageInfo *imageInfo = [[ImageInfo alloc] initWithURL:iconURL type:ImageTypeThumb]; + [mediaInfo addImage:imageInfo]; + + [self displayImageWithMediaInfo:mediaInfo success:^(MediaLaunchObject *mediaLanchObject) { + success(mediaLanchObject.session,mediaLanchObject.mediaControl); + } failure:failure]; +} + +- (void) displayImage:(MediaInfo *)mediaInfo + success:(MediaPlayerDisplaySuccessBlock)success + failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + + [self displayImage:mediaInfo.url iconURL:iconURL title:mediaInfo.title description:mediaInfo.description mimeType:mediaInfo.mimeType success:success failure:failure]; +} + +-(void) displayImageWithMediaInfo:(MediaInfo *)mediaInfo success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + if (self.dlnaService) + { + [self.dlnaService.mediaPlayer displayImageWithMediaInfo:mediaInfo success:^(MediaLaunchObject *launchObject) + { + + launchObject.session.appId = kSmartShareName; + launchObject.session.name = kSmartShareName; + + if (success) + success(launchObject); + } failure:failure]; + return; + } + + [self sendNotSupportedFailure:failure]; +} + +- (void) playMedia:(NSURL *)videoURL iconURL:(NSURL *)iconURL title:(NSString *)title description:(NSString *)description mimeType:(NSString *)mimeType shouldLoop:(BOOL)shouldLoop success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:videoURL mimeType:mimeType]; + mediaInfo.title = title; + mediaInfo.description = description; + ImageInfo *imageInfo = [[ImageInfo alloc] initWithURL:iconURL type:ImageTypeThumb]; + [mediaInfo addImage:imageInfo]; + + [self playMediaWithMediaInfo:mediaInfo shouldLoop:shouldLoop success:^(MediaLaunchObject *mediaLanchObject) { + success(mediaLanchObject.session,self.mediaControl); + } failure:failure]; +} + +- (void) playMedia:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + [self playMedia:mediaInfo.url iconURL:iconURL title:mediaInfo.title description:mediaInfo.description mimeType:mediaInfo.mimeType shouldLoop:shouldLoop success:success failure:failure]; +} + +-(void) playMediaWithMediaInfo:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + if (self.dlnaService) + { + [self.dlnaService.mediaPlayer playMediaWithMediaInfo:mediaInfo shouldLoop:shouldLoop success:^(MediaLaunchObject *launchObject) + { + launchObject.session.appId = kSmartShareName; + launchObject.session.name = kSmartShareName; + launchObject.mediaControl = self.mediaControl; + if (success){ + success(launchObject); + } + } failure:failure]; + return; + } + + [self sendNotSupportedFailure:failure]; +} + +- (void)closeMedia:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (self.dlnaService) + { + [self.dlnaService closeMedia:launchSession success:success failure:failure]; + return; + } + + [self sendNotSupportedFailure:failure]; +} + +#pragma mark - Media Control + +- (id )mediaControl +{ + if ([DiscoveryManager sharedManager].pairingLevel == DeviceServicePairingLevelOff) + return self.dlnaService; + else + return self; +} + +- (CapabilityPriorityLevel)mediaControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)playWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:NetcastTVKeyCodePlay success:success failure:failure]; +} + +- (void)pauseWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:NetcastTVKeyCodePause success:success failure:failure]; +} + +- (void)stopWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:NetcastTVKeyCodeStop success:success failure:failure]; +} + +- (void)rewindWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)fastForwardWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)seek:(NSTimeInterval)position success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (self.dlnaService) + { + [self.dlnaService seek:position success:success failure:failure]; + return; + } + + [self sendNotSupportedFailure:failure]; +} + +- (void)getPlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + if (self.dlnaService) + { + [self.dlnaService getPlayStateWithSuccess:success failure:failure]; + return; + } + + [self sendNotSupportedFailure:failure]; +} + +- (void) getDurationWithSuccess:(MediaDurationSuccessBlock)success failure:(FailureBlock)failure +{ + if (self.dlnaService) + { + [self.dlnaService getDurationWithSuccess:success failure:failure]; + return; + } + + [self sendNotSupportedFailure:failure]; +} + +- (void)getPositionWithSuccess:(MediaPositionSuccessBlock)success failure:(FailureBlock)failure +{ + if (self.dlnaService) + { + [self.dlnaService getPositionWithSuccess:success failure:failure]; + return; + } + + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribePlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + if (self.dlnaService) { + return [self.dlnaService subscribePlayStateWithSuccess:success failure:failure]; + } + + return [self sendNotSupportedFailure:failure]; +} + +- (void)getMediaMetaDataWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (self.dlnaService) + { + [self.dlnaService getMediaMetaDataWithSuccess:success failure:failure]; + return; + } + + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribeMediaInfoWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (self.dlnaService) + { + ServiceSubscription *subscription = [self.dlnaService subscribeMediaInfoWithSuccess:success failure:failure]; + return subscription; + } + + return [self sendNotSupportedFailure:failure]; +} + +#pragma mark - TV + +- (id )tvControl +{ + return self; +} + +- (CapabilityPriorityLevel)tvControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)getCurrentChannelWithSuccess:(CurrentChannelSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *targetPath = [NSString stringWithFormat:@"%@%@%@", + self.commandURL.absoluteString, + lgeUDAPRequestURI[LGE_DATA_GET_REQUEST], + @"?target=cur_channel" + ]; + + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:nil]; + command.HTTPMethod = @"GET"; + command.callbackComplete = ^(NSDictionary *responseDic) + { + NSDictionary *channelInfo = [[[responseDic objectForKey:@"envelope"] objectForKey:@"dataList"] objectForKey:@"data"]; + + if (success) + success([NetcastTVService channelInfoFromXML:channelInfo]); + }; + command.callbackError = failure; + [command send]; +} + +- (ServiceSubscription *)subscribeCurrentChannelWithSuccess:(CurrentChannelSuccessBlock)success failure:(FailureBlock)failure +{ + [self getCurrentChannelWithSuccess:success failure:failure]; + + ServiceSubscription *subscription = [self addSubscribe:@"ChannelChanged" success:^(NSDictionary *responseObject) + { + NSDictionary *channelInfo = [[responseObject objectForKey:@"envelope"] objectForKey:@"api"]; + + if (success) + success([NetcastTVService channelInfoFromXML:channelInfo]); + } failure:failure]; + + return subscription; +} + +- (void)getChannelListWithSuccess:(ChannelListSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *targetPath = [NSString stringWithFormat:@"%@%@%@", + self.commandURL.absoluteString, + lgeUDAPRequestURI[LGE_DATA_GET_REQUEST], + @"?target=channel_list" + ]; + + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:nil]; + command.HTTPMethod = @"GET"; + command.callbackComplete = ^(NSDictionary *responseDic) + { + id rawChannelObject = [[[responseDic objectForKey:@"envelope"] objectForKey:@"dataList"] objectForKey:@"data"]; + NSArray *rawChannels; + + if ([rawChannelObject isKindOfClass:[NSArray class]]) + rawChannels = (NSArray *)rawChannelObject; + else + { + if (success) + success([NSArray array]); + } + + NSMutableDictionary *channelList = [[NSMutableDictionary alloc] init]; + + [rawChannels enumerateObjectsUsingBlock:^(NSDictionary *channelDictionary, NSUInteger idx, BOOL *stop) + { + ChannelInfo *channelInfo = [NetcastTVService channelInfoFromXML:channelDictionary]; + + if (channelInfo) + [channelList setValue:channelInfo forKey:channelInfo.number]; + }]; + + if (success) + success([channelList allValues]); + }; + command.callbackError = failure; + [command send]; +} + +- (void)channelUpWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:NetcastTVKeyCodeChannelUp success:success failure:failure]; +} + +- (void)channelDownWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:NetcastTVKeyCodeChannelDown success:success failure:failure]; +} + +- (void)setChannel:(ChannelInfo *)channelInfo success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *major = [[channelInfo.rawData objectForKey:@"major"] objectForKey:@"text"]; + NSString *minor = [[channelInfo.rawData objectForKey:@"minor"] objectForKey:@"text"]; + NSString *sourceIndex = [[channelInfo.rawData objectForKey:@"sourceIndex"] objectForKey:@"text"]; + NSString *physicalNum = channelInfo.id; + + NSString *targetPath = [self.commandURL.absoluteString stringByAppendingPathComponent:lgeUDAPRequestURI[LGE_COMMAND_REQUEST]]; + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + NSString *payload = [NSString stringWithFormat:@ + "" + "" + "HandleChannelChange" + "%@" + "%@" + "%@" + "%@" + "" + "", major, minor, sourceIndex, physicalNum]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:payload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)getProgramInfoWithSuccess:(ProgramInfoSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribeProgramInfoWithSuccess:(ProgramInfoSuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +- (void)getProgramListWithSuccess:(ProgramListSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribeProgramListWithSuccess:(ProgramListSuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +- (void)get3DEnabledWithSuccess:(TV3DEnabledSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *targetPath = [NSString stringWithFormat:@"%@%@%@", + self.commandURL.absoluteString, + lgeUDAPRequestURI[LGE_DATA_GET_REQUEST], + @"?target=is_3d" + ]; + + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self target:targetURL payload:nil]; + command.HTTPMethod = @"GET"; + command.callbackComplete = ^(NSDictionary *responseObject) + { + BOOL status = [[[[[[responseObject objectForKey:@"envelope"] objectForKey:@"dataList"] objectForKey:@"data"] objectForKey:@"is3D"] objectForKey:@"text"] isEqualToString:@"true"]; + + if (success) + success(status); + }; + command.callbackError = failure; + [command send]; +} + +- (void)set3DEnabled:(BOOL)enabled success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self get3DEnabledWithSuccess:^(BOOL tv3DEnabled) + { + if (tv3DEnabled == enabled) + { + if (success) + success(nil); + } else + { + [self sendKeyCode:NetcastTVKeyCode3DVideo success:success failure:failure]; + } + } failure:failure]; +} + +- (ServiceSubscription *)subscribe3DEnabledWithSuccess:(TV3DEnabledSuccessBlock)success failure:(FailureBlock)failure +{ + [self get3DEnabledWithSuccess:success failure:failure]; + + ServiceSubscription *subscription = [self addSubscribe:@"3DMode" success:^(NSDictionary *responseObject) + { + BOOL status = [[[[[responseObject objectForKey:@"envelope"] objectForKey:@"api"] objectForKey:@"value"] objectForKey:@"text"] isEqualToString:@"true"]; + + if (success) + success(status); + } failure:failure]; + + return subscription; +} + +#pragma mark - Volume + +- (id )volumeControl +{ + return self; +} + +- (CapabilityPriorityLevel)volumeControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)getVolumeWithSuccess:(VolumeSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *targetPath = [NSString stringWithFormat:@"%@%@%@", + self.commandURL.absoluteString, + lgeUDAPRequestURI[LGE_DATA_GET_REQUEST], + @"?target=volume_info" + ]; + + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:nil]; + command.HTTPMethod = @"GET"; + command.callbackComplete = ^(NSDictionary *responseDic) + { + NSDictionary *volumeInfo = [[[responseDic objectForKey:@"envelope"] objectForKey:@"dataList"] objectForKey:@"data"]; + int volume = [[[volumeInfo objectForKey:@"level"] objectForKey:@"text"] intValue]; + + if (success) + success(volume / 100.0f); + }; + command.callbackError = failure; + [command send]; +} + +- (void)getMuteWithSuccess:(MuteSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *targetPath = [NSString stringWithFormat:@"%@%@%@", + self.commandURL.absoluteString, + lgeUDAPRequestURI[LGE_DATA_GET_REQUEST], + @"?target=volume_info" + ]; + + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:nil]; + command.HTTPMethod = @"GET"; + command.callbackComplete = ^(NSDictionary *responseDic) + { + NSDictionary *volumeInfo = [[[responseDic objectForKey:@"envelope"] objectForKey:@"dataList"] objectForKey:@"data"]; + BOOL mute = [[[volumeInfo objectForKey:@"mute"] objectForKey:@"text"] boolValue]; + + if (success) + success(mute); + }; + command.callbackError = failure; + [command send]; +} + +- (void)setVolume:(float)volume success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (self.dlnaService) + { + [self.dlnaService setVolume:volume success:success failure:failure]; + return; + } + + [self sendNotSupportedFailure:failure]; +} + +- (void)setMute:(BOOL)mute success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self getMuteWithSuccess:^(BOOL currentMute) + { + if (currentMute == mute) + { + if (success) + success(nil); + } else + [self sendKeyCode:NetcastTVKeyCodeMute success:success failure:failure]; + } failure:failure]; +} + +- (void)volumeUpWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:NetcastTVKeyCodeVolumeUp success:success failure:failure]; +} + +- (void)volumeDownWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:NetcastTVKeyCodeVolumeDown success:success failure:failure]; +} + +- (ServiceSubscription *)subscribeVolumeWithSuccess:(VolumeSuccessBlock)success failure:(FailureBlock)failure +{ + if (self.dlnaService) { + return [self.dlnaService subscribeVolumeWithSuccess:success failure:failure]; + } + + return [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribeMuteWithSuccess:(MuteSuccessBlock)success failure:(FailureBlock)failure +{ + if (self.dlnaService) { + return [self.dlnaService subscribeMuteWithSuccess:success failure:failure]; + } + + return [self sendNotSupportedFailure:failure]; +} + +#pragma mark - Key Control + +- (id ) keyControl +{ + return self; +} + +- (CapabilityPriorityLevel) keyControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)upWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:NetcastTVKeyCodeUp success:success failure:failure]; +} + +- (void)downWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:NetcastTVKeyCodeDown success:success failure:failure]; +} + +- (void)leftWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:NetcastTVKeyCodeLeft success:success failure:failure]; +} + +- (void)rightWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:NetcastTVKeyCodeRight success:success failure:failure]; +} + +- (void)okWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:NetcastTVKeyCodeOK success:success failure:failure]; +} + +- (void)backWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:NetcastTVKeyCodeBack success:success failure:failure]; +} + +- (void)homeWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:NetcastTVKeyCodeHome success:success failure:failure]; +} + +- (void)sendKeyCode:(NetcastTVKeyCode)keyCode success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self hideMouseWithSuccess:^(id responseObject) + { + NSString *targetPath = [self.commandURL.absoluteString stringByAppendingPathComponent:lgeUDAPRequestURI[LGE_COMMAND_REQUEST]]; + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + NSString *payload = [NSString stringWithFormat:@ + "" + "" + "HandleKeyInput" + "%d" + "" + "", (unsigned int) keyCode]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:payload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; + } failure:failure]; +} + +#pragma mark - Mouse + +- (id )mouseControl +{ + return self; +} + +- (CapabilityPriorityLevel)mouseControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void) connectMouseWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + _mouseDistance = CGVectorMake(0, 0); + _mouseIsMoving = NO; + + [self showMouseWithSuccess:success failure:failure]; +} + +- (void)disconnectMouse +{ + _mouseDistance = CGVectorMake(0, 0); + _mouseIsMoving = NO; + + [self hideMouseWithSuccess:nil failure:nil]; +} + +- (void) showMouseWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (_mouseVisible) + { + if (success) + success(nil); + + return; + } + + NSString *targetPath = [self.commandURL.absoluteString stringByAppendingPathComponent:lgeUDAPRequestURI[LGE_EVENT_REQUEST]]; + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + NSString *payload = [NSString stringWithFormat:@ + "" + "" + "CursorVisible" + "true" + "auto" + "" + ""]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:payload]; + command.callbackComplete = ^(id responseObject){ + _mouseVisible = YES; + + if (success) + success(nil); + }; + command.callbackError = failure; + [command send]; +} + +- (void) hideMouseWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (!_mouseVisible) + { + if (success) + success(nil); + + return; + } + + NSString *targetPath = [self.commandURL.absoluteString stringByAppendingPathComponent:lgeUDAPRequestURI[LGE_EVENT_REQUEST]]; + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + NSString *payload = [NSString stringWithFormat:@ + "" + "" + "CursorVisible" + "false" + "auto" + "" + ""]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:payload]; + command.callbackComplete = ^(id responseObject){ + _mouseVisible = NO; + + if (success) + success(nil); + }; + command.callbackError = failure; + [command send]; +} + +- (void) move:(CGVector)distance success:(SuccessBlock)success failure:(FailureBlock)failure +{ + _mouseDistance = CGVectorMake( + _mouseDistance.dx + distance.dx, + _mouseDistance.dy + distance.dy + ); + + if (!_mouseIsMoving) + { + _mouseIsMoving = YES; + + [self moveMouseWithSuccess:success failure:failure]; + } +} + +- (void) moveMouseWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *targetPath = [self.commandURL.absoluteString stringByAppendingPathComponent:lgeUDAPRequestURI[LGE_COMMAND_REQUEST]]; + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + NSString *payload = [NSString stringWithFormat:@ + "" + "" + "HandleTouchMove" + "%i" + "%i" + "" + "", (int) round(_mouseDistance.dx), (int) round(_mouseDistance.dy)]; + + _mouseDistance = CGVectorMake(0, 0); + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:payload]; + command.callbackComplete = ^(id responseObject) + { + if (_mouseDistance.dx != 0 || _mouseDistance.dy != 0) + [self moveMouseWithSuccess:nil failure:nil]; + else + _mouseIsMoving = NO; + + if (success) + success(responseObject); + }; + command.callbackError = ^(NSError *error) + { + _mouseIsMoving = NO; + + if (failure) + failure(error); + }; + [command send]; +} + +- (void) scroll:(CGVector)distance success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *targetPath = [self.commandURL.absoluteString stringByAppendingPathComponent:lgeUDAPRequestURI[LGE_COMMAND_REQUEST]]; + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + NSString *direction = (distance.dy > 0) ? @"down" : @"up"; + + NSString *payload = [NSString stringWithFormat:@ + "" + "" + "HandleTouchWheel" + "%@" + "" + "", direction]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:payload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)clickWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *targetPath = [self.commandURL.absoluteString stringByAppendingPathComponent:lgeUDAPRequestURI[LGE_COMMAND_REQUEST]]; + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + NSString *payload = [NSString stringWithFormat:@ + "" + "" + "HandleTouchClick" + "" + ""]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:payload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +#pragma mark - Power Control + +- (id )powerControl +{ + return self; +} + +- (CapabilityPriorityLevel)powerControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)powerOffWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:NetcastTVKeyCodePower success:success failure:failure]; +} + +- (void) powerOnWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +#pragma mark - External Input Control + +- (id )externalInputControl +{ + return self; +} + +- (CapabilityPriorityLevel)externalInputControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +- (void)launchInputPickerWithSuccess:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *appId = @"Input List"; + + NSString *targetPath = [NSString stringWithFormat:@"%@%@/%@", + self.commandURL.absoluteString, + lgeUDAPRequestURI[LGE_APPTOAPP_DATA_REQUEST], + [ConnectUtil urlEncode:appId] + ]; + + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:nil]; + command.HTTPMethod = @"GET"; + command.callbackComplete = ^(NSString *response) + { + if (response) + { + int auidRawValue = [response intValue]; + NSString *auidString = [[NSString alloc] initWithFormat:@"%lX", auidRawValue]; + + + while (auidString.length < 16) + { + auidString = [NSString stringWithFormat:@"0%@", auidString]; + } + + AppInfo *appInfo = [AppInfo appInfoForId:auidString]; + appInfo.name = appId; + + [self launchAppWithInfo:appInfo success:success failure:failure]; + } else + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Could not find app with specified id."]); + } + }; + command.callbackError = failure; + [command send]; +} +#pragma GCC diagnostic pop + +- (void)closeInputPicker:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.keyControl sendKeyCode:NetcastTVKeyCodeExit success:success failure:failure]; +} + +- (void)getExternalInputListWithSuccess:(ExternalInputListSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)setExternalInput:(ExternalInputInfo *)externalInputInfo success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +#pragma mark - Text Input Control + +- (id ) textInputControl +{ + return self; +} + +- (CapabilityPriorityLevel) textInputControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void) sendText:(NSString *)input success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (_keyboardString && _keyboardString.length > 0) + _keyboardString = [_keyboardString stringByAppendingString:input]; + else + _keyboardString = input; + + [self sendText:_keyboardString state:@"Editing" success:success failure:failure]; +} + +- (void)sendEnterWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (_keyboardString && _keyboardString.length > 0) + { + [self sendText:_keyboardString state:@"EditEnd" success:nil failure:nil]; + + [self sendKeyCode:NetcastTVKeyCodeRed success:^(id responseObject) + { + _keyboardString = @""; + + if (success) + success(nil); + } failure:failure]; + } else + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You have not inputted any text to send."]); + } +} + +- (void)sendDeleteWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (_keyboardString && _keyboardString.length > 0) + { + _keyboardString = [_keyboardString substringToIndex:_keyboardString.length - 1]; + [self sendText:_keyboardString state:@"Editing" success:success failure:failure]; + } else + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"There are no characters to delete."]); + } +} + +- (void) sendText:(NSString *)text state:(NSString *)state success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSString *targetPath = [self.commandURL.absoluteString stringByAppendingPathComponent:lgeUDAPRequestURI[LGE_EVENT_REQUEST]]; + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + NSString *payload = ({ + XMLWriter *writer = [XMLWriter new]; + + [writer writeElement:@"envelope" withContentsBlock:^(XMLWriter *writer) { + [writer writeElement:@"api" withContentsBlock:^(XMLWriter *writer) { + [writer writeAttribute:@"type" value:@"event"]; + + [writer writeElement:@"name" withContents:@"TextEdited"]; + [writer writeElement:@"state" withContents:state]; + [writer writeElement:@"value" withContents:text]; + }]; + }]; + + [writer toString]; + }); + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self.serviceCommandDelegate target:targetURL payload:payload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (ServiceSubscription *) subscribeTextInputStatusWithSuccess:(TextInputStatusInfoSuccessBlock)success failure:(FailureBlock)failure +{ + __weak NetcastTVService *weakSelf = self; + + ServiceSubscription *serviceSubscription = [self addSubscribe:@"KeyboardVisible" success:^(NSDictionary *responseObject) + { + NSString *isVisibleValue = [[[[responseObject objectForKey:@"envelope"] objectForKey:@"api"] objectForKey:@"value"] objectForKey:@"text"]; + + TextInputStatusInfo *keyboardInfo = [[TextInputStatusInfo alloc] init]; + keyboardInfo.isVisible = [isVisibleValue isEqualToString:@"true"]; + keyboardInfo.rawData = [responseObject copy]; + + [weakSelf sendText:@"" state:@"EditStart" success:nil failure:nil]; + + if (success) + success(keyboardInfo); + } failure:failure]; + + return serviceSubscription; +} + +- (int)sendSubscription:(ServiceSubscription *)subscription type:(ServiceSubscriptionType)type payload:(id)payload toURL:(NSURL *)URL withId:(int)callId +{ + if (type == ServiceSubscriptionTypeUnsubscribe) + { + NSArray *keys = [_subscribed allKeysForObject:subscription]; + [_subscribed removeObjectsForKeys:keys]; + } + + return 0; +} + +- (ServiceSubscription *) addSubscribe:(NSString *)event success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (_subscribed == nil) + _subscribed = [[NSMutableDictionary alloc] init]; + + ServiceSubscription *subscription = [_subscribed objectForKey:event]; + + if (subscription == nil) + { + NSURL *eventURL = [NSURL URLWithString:lgeUDAPRequestURI[LGE_EVENT_REQUEST]]; + subscription = [ServiceSubscription subscriptionWithDelegate:self target:eventURL payload:nil callId:0]; + [_subscribed setObject:subscription forKey:event]; + } + + if (success) + [subscription addSuccess:success]; + + if (failure) + [subscription addFailure:failure]; + + if (![subscription isSubscribed]) + [subscription subscribe]; + + return subscription; +} + +- (ServiceSubscription *) killSubscribe:(NSString *)event +{ + ServiceSubscription *subscription = [_subscribed objectForKey:event]; + + if (subscription) + [_subscribed removeObjectForKey:event]; + + return subscription; +} + +@end diff --git a/core/Services/NetcastTVService_Private.h b/core/Services/NetcastTVService_Private.h new file mode 100644 index 00000000..cd3395f2 --- /dev/null +++ b/core/Services/NetcastTVService_Private.h @@ -0,0 +1,27 @@ +// +// NetcastTVService_Private.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 3/24/15. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NetcastTVService.h" + +@interface NetcastTVService () + +@property (nonatomic, strong) id serviceCommandDelegate; + +@end diff --git a/core/Services/RokuService.h b/core/Services/RokuService.h new file mode 100644 index 00000000..aafd1713 --- /dev/null +++ b/core/Services/RokuService.h @@ -0,0 +1,63 @@ +// +// RokuService.h +// ConnectSDK +// +// Created by Jeremy White on 2/14/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#define kConnectSDKRokuServiceId @"Roku" + +#import "DIALService.h" +#import "MediaControl.h" +#import "MediaPlayer.h" +#import "KeyControl.h" +#import "TextInputControl.h" + +enum +{ + RokuKeyCodeHome, + RokuKeyCodeRewind, + RokuKeyCodeFastForward, + RokuKeyCodePlay, + RokuKeyCodeSelect, + RokuKeyCodeLeft, + RokuKeyCodeRight, + RokuKeyCodeDown, + RokuKeyCodeUp, + RokuKeyCodeBack, + RokuKeyCodeInstantReplay, + RokuKeyCodeInfo, + RokuKeyCodeBackspace, + RokuKeyCodeSearch, + RokuKeyCodeEnter, + RokuKeyCodeLiteral +}; + +// @cond INTERNAL +typedef NSUInteger RokuKeyCode; + +#define kRokuKeyCodes @[ @"Home", @"Rev", @"Fwd", @"Play", @"Select", @"Left", @"Right", @"Down", @"Up", @"Back", @"InstantReplay", @"Info", @"Backspace", @"Search", @"Enter", @"Lit_" ] +// @endcond + +@interface RokuService : DeviceService + +// @cond INTERNAL +- (void)sendKeyCode:(RokuKeyCode)keyCode success:(SuccessBlock)success failure:(FailureBlock)failure; +// @endcond + ++ (void) registerApp:(NSString *)appId; + +@end diff --git a/core/Services/RokuService.m b/core/Services/RokuService.m new file mode 100644 index 00000000..8dd36e9a --- /dev/null +++ b/core/Services/RokuService.m @@ -0,0 +1,876 @@ +// +// RokuService.m +// ConnectSDK +// +// Created by Jeremy White on 2/14/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "RokuService_Private.h" +#import "ConnectError.h" +#import "CTXMLReader.h" +#import "ConnectUtil.h" +#import "DeviceServiceReachability.h" +#import "DiscoveryManager.h" + +#import "NSObject+FeatureNotSupported_Private.h" + +@interface RokuService () +{ + DIALService *_dialService; + DeviceServiceReachability *_serviceReachability; +} +@end + +static NSMutableArray *registeredApps = nil; + +@implementation RokuService + ++ (void) initialize +{ + registeredApps = [NSMutableArray arrayWithArray:@[ + @"YouTube", + @"Netflix", + @"Amazon" + ]]; +} + ++ (NSDictionary *)discoveryParameters +{ + return @{ + @"serviceId" : kConnectSDKRokuServiceId, + @"ssdp" : @{ + @"filter" : @"roku:ecp" + } + }; +} + +- (void) updateCapabilities +{ + NSArray *capabilities = @[ + kLauncherAppList, + kLauncherApp, + kLauncherAppParams, + kLauncherAppStore, + kLauncherAppStoreParams, + kLauncherAppClose, + + kMediaPlayerDisplayImage, + kMediaPlayerPlayVideo, + kMediaPlayerPlayAudio, + kMediaPlayerClose, + kMediaPlayerMetaDataTitle, + + kMediaControlPlay, + kMediaControlPause, + kMediaControlRewind, + kMediaControlFastForward, + + kTextInputControlSendText, + kTextInputControlSendEnter, + kTextInputControlSendDelete + ]; + + capabilities = [capabilities arrayByAddingObjectsFromArray:kKeyControlCapabilities]; + + [self setCapabilities:capabilities]; +} + ++ (void) registerApp:(NSString *)appId +{ + if (![registeredApps containsObject:appId]) + [registeredApps addObject:appId]; +} + +- (void) probeForApps +{ + [registeredApps enumerateObjectsUsingBlock:^(NSString *appName, NSUInteger idx, BOOL *stop) + { + [self hasApp:appName success:^(AppInfo *appInfo) + { + NSString *capability = [NSString stringWithFormat:@"Launcher.%@", appName]; + NSString *capabilityParams = [NSString stringWithFormat:@"Launcher.%@.Params", appName]; + + [self addCapabilities:@[capability, capabilityParams]]; + } failure:nil]; + }]; +} + +- (BOOL) isConnectable +{ + return YES; +} + +- (void) connect +{ + NSString *targetPath = [NSString stringWithFormat:@"http://%@:%@/", self.serviceDescription.address, @(self.serviceDescription.port)]; + NSURL *targetURL = [NSURL URLWithString:targetPath]; + + _serviceReachability = [DeviceServiceReachability reachabilityWithTargetURL:targetURL]; + _serviceReachability.delegate = self; + [_serviceReachability start]; + + self.connected = YES; + + if (self.delegate && [self.delegate respondsToSelector:@selector(deviceServiceConnectionSuccess:)]) + dispatch_on_main(^{ [self.delegate deviceServiceConnectionSuccess:self]; }); +} + +- (void) disconnect +{ + self.connected = NO; + + [_serviceReachability stop]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(deviceService:disconnectedWithError:)]) + dispatch_on_main(^{ [self.delegate deviceService:self disconnectedWithError:nil]; }); +} + +- (void) didLoseReachability:(DeviceServiceReachability *)reachability +{ + if (self.connected) + [self disconnect]; + else + [_serviceReachability stop]; +} + +- (void)setServiceDescription:(ServiceDescription *)serviceDescription +{ + [super setServiceDescription:serviceDescription]; + + self.serviceDescription.port = 8060; + NSString *commandPath = [NSString stringWithFormat:@"http://%@:%@", self.serviceDescription.address, @(self.serviceDescription.port)]; + self.serviceDescription.commandURL = [NSURL URLWithString:commandPath]; + + [self probeForApps]; +} + +- (DIALService *) dialService +{ + if (!_dialService) + { + ConnectableDevice *device = [[DiscoveryManager sharedManager].allDevices objectForKey:self.serviceDescription.address]; + __block DIALService *foundService; + + [device.services enumerateObjectsUsingBlock:^(DeviceService *service, NSUInteger idx, BOOL *stop) + { + if ([service isKindOfClass:[DIALService class]]) + { + foundService = (DIALService *) service; + *stop = YES; + } + }]; + + if (foundService) + _dialService = foundService; + } + + return _dialService; +} + +#pragma mark - Getters & Setters + +/// Returns the set delegate property value or self. +- (id)serviceCommandDelegate { + return _serviceCommandDelegate ?: self; +} + +#pragma mark - ServiceCommandDelegate + +- (int) sendCommand:(ServiceCommand *)command withPayload:(NSDictionary *)payload toURL:(NSURL *)URL +{ + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; + [request setCachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData]; + [request setTimeoutInterval:6]; + [request addValue:@"text/plain;charset=\"utf-8\"" forHTTPHeaderField:@"Content-Type"]; + + if (payload || [command.HTTPMethod isEqualToString:@"POST"]) + { + [request setHTTPMethod:@"POST"]; + + if (payload) + { + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:payload options:0 error:nil]; + [request addValue:[NSString stringWithFormat:@"%i", (unsigned int) [jsonData length]] forHTTPHeaderField:@"Content-Length"]; + [request setHTTPBody:jsonData]; + } + } else + { + [request setHTTPMethod:@"GET"]; + [request addValue:@"0" forHTTPHeaderField:@"Content-Length"]; + } + + [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) + { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + + if (connectionError) + { + if (command.callbackError) + dispatch_on_main(^{ command.callbackError(connectionError); }); + } else + { + if ([httpResponse statusCode] < 200 || [httpResponse statusCode] >= 300) + { + NSError *error = [ConnectError generateErrorWithCode:ConnectStatusCodeTvError andDetails:nil]; + + if (command.callbackError) + command.callbackError(error); + + return; + } + + NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + + if (command.callbackComplete) + dispatch_on_main(^{ command.callbackComplete(dataString); }); + } + }]; + + // TODO: need to implement callIds in here + return 0; +} + +#pragma mark - Launcher + +- (id )launcher +{ + return self; +} + +- (CapabilityPriorityLevel)launcherPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)launchApp:(NSString *)appId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + if (!appId) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You must provide an appId."]); + return; + } + + AppInfo *appInfo = [AppInfo appInfoForId:appId]; + + [self launchAppWithInfo:appInfo params:nil success:success failure:failure]; +} + +- (void)launchAppWithInfo:(AppInfo *)appInfo success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self launchAppWithInfo:appInfo params:nil success:success failure:failure]; +} + +- (void)launchAppWithInfo:(AppInfo *)appInfo params:(NSDictionary *)params success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + if (!appInfo || !appInfo.id) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You must provide a valid AppInfo object."]); + return; + } + + NSURL *targetURL = [self.serviceDescription.commandURL URLByAppendingPathComponent:@"launch"]; + targetURL = [targetURL URLByAppendingPathComponent:appInfo.id]; + + if (params) + { + __block NSString *queryParams = @""; + __block int count = 0; + + [params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { + NSString *prefix = (count == 0) ? @"?" : @"&"; + + NSString *urlSafeKey = [ConnectUtil urlEncode:key]; + NSString *urlSafeValue = [ConnectUtil urlEncode:value]; + + NSString *appendString = [NSString stringWithFormat:@"%@%@=%@", prefix, urlSafeKey, urlSafeValue]; + queryParams = [queryParams stringByAppendingString:appendString]; + + count++; + }]; + + NSString *targetPath = [NSString stringWithFormat:@"%@%@", targetURL.absoluteString, queryParams]; + targetURL = [NSURL URLWithString:targetPath]; + } + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:nil]; + command.callbackComplete = ^(id responseObject) + { + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:appInfo.id]; + launchSession.name = appInfo.name; + launchSession.sessionType = LaunchSessionTypeApp; + launchSession.service = self; + + if (success) + success(launchSession); + }; + command.callbackError = failure; + [command send]; +} + +- (void)launchYouTube:(NSString *)contentId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self launchYouTube:contentId startTime:0.0 success:success failure:failure]; +} + +- (void) launchYouTube:(NSString *)contentId startTime:(float)startTime success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + if (self.dialService) + [self.dialService.launcher launchYouTube:contentId startTime:startTime success:success failure:failure]; + else + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeNotSupported andDetails:@"Cannot reach DIAL service for launching with provided start time"]); + } +} + +- (void) launchAppStore:(NSString *)appId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + AppInfo *appInfo = [AppInfo appInfoForId:@"11"]; + appInfo.name = @"Channel Store"; + + NSDictionary *params; + + if (appId && appId.length > 0) + params = @{ @"contentId" : appId }; + + [self launchAppWithInfo:appInfo params:params success:success failure:failure]; +} + +- (void)launchBrowser:(NSURL *)target success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)launchHulu:(NSString *)contentId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)launchNetflix:(NSString *)contentId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self getAppListWithSuccess:^(NSArray *appList) + { + __block AppInfo *foundAppInfo; + + [appList enumerateObjectsUsingBlock:^(AppInfo *appInfo, NSUInteger idx, BOOL *stop) + { + if ([appInfo.name isEqualToString:@"Netflix"]) + { + foundAppInfo = appInfo; + *stop = YES; + } + }]; + + if (foundAppInfo) + { + NSMutableDictionary *params = [NSMutableDictionary new]; + params[@"mediaType"] = @"movie"; + if (contentId && contentId.length > 0) params[@"contentId"] = contentId; + + [self launchAppWithInfo:foundAppInfo params:params success:success failure:failure]; + } else + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Netflix app could not be found on TV"]); + } + } failure:failure]; +} + +- (void)closeApp:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.keyControl homeWithSuccess:success failure:failure]; +} + +- (void)getAppListWithSuccess:(AppListSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *targetURL = [self.serviceDescription.commandURL URLByAppendingPathComponent:@"query"]; + targetURL = [targetURL URLByAppendingPathComponent:@"apps"]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self.serviceCommandDelegate target:targetURL payload:nil]; + command.HTTPMethod = @"GET"; + command.callbackComplete = ^(NSString *responseObject) + { + NSError *xmlError; + NSDictionary *appListDictionary = [CTXMLReader dictionaryForXMLString:responseObject error:&xmlError]; + + if (appListDictionary) { + NSArray *apps; + id appsObject = [appListDictionary valueForKeyPath:@"apps.app"]; + if ([appsObject isKindOfClass:[NSDictionary class]]) { + apps = @[appsObject]; + } else if ([appsObject isKindOfClass:[NSArray class]]) { + apps = appsObject; + } + + NSMutableArray *appList = [NSMutableArray new]; + + [apps enumerateObjectsUsingBlock:^(NSDictionary *appInfoDictionary, NSUInteger idx, BOOL *stop) + { + AppInfo *appInfo = [self appInfoFromDictionary:appInfoDictionary]; + [appList addObject:appInfo]; + }]; + + if (success) + success([NSArray arrayWithArray:appList]); + } else { + if (failure) { + NSString *details = [NSString stringWithFormat: + @"Couldn't parse apps XML (%@)", xmlError.localizedDescription]; + failure([ConnectError generateErrorWithCode:ConnectStatusCodeTvError + andDetails:details]); + } + } + }; + command.callbackError = failure; + [command send]; +} + +- (void)getAppState:(LaunchSession *)launchSession success:(AppStateSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribeAppState:(LaunchSession *)launchSession success:(AppStateSuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +- (void)getRunningAppWithSuccess:(AppInfoSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribeRunningAppWithSuccess:(AppInfoSuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +#pragma mark - MediaPlayer + +- (id )mediaPlayer +{ + return self; +} + +- (CapabilityPriorityLevel)mediaPlayerPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)displayImage:(NSURL *)imageURL iconURL:(NSURL *)iconURL title:(NSString *)title description:(NSString *)description mimeType:(NSString *)mimeType success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:imageURL mimeType:mimeType]; + mediaInfo.title = title; + mediaInfo.description = description; + ImageInfo *imageInfo = [[ImageInfo alloc] initWithURL:iconURL type:ImageTypeThumb]; + [mediaInfo addImage:imageInfo]; + + [self displayImageWithMediaInfo:mediaInfo success:^(MediaLaunchObject *mediaLanchObject) { + success(mediaLanchObject.session,mediaLanchObject.mediaControl); + } failure:failure]; +} + +- (void) displayImage:(MediaInfo *)mediaInfo + success:(MediaPlayerDisplaySuccessBlock)success + failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + + [self displayImage:mediaInfo.url iconURL:iconURL title:mediaInfo.title description:mediaInfo.description mimeType:mediaInfo.mimeType success:success failure:failure]; +} + +- (void) displayImageWithMediaInfo:(MediaInfo *)mediaInfo success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *imageURL = mediaInfo.url; + if (!imageURL) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You need to provide a video URL"]); + + return; + } + + NSString *applicationPath = [NSString stringWithFormat:@"15985?t=p&u=%@&tr=crossfade", + [ConnectUtil urlEncode:imageURL.absoluteString] // content path + ]; + + NSString *commandPath = [NSString pathWithComponents:@[ + self.serviceDescription.commandURL.absoluteString, + @"input", + applicationPath + ]]; + + NSURL *targetURL = [NSURL URLWithString:commandPath]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self.serviceCommandDelegate target:targetURL payload:nil]; + command.HTTPMethod = @"POST"; + command.callbackComplete = ^(id responseObject) + { + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:@"15985"]; + launchSession.name = @"simplevideoplayer"; + launchSession.sessionType = LaunchSessionTypeMedia; + launchSession.service = self; + + MediaLaunchObject *launchObject = [[MediaLaunchObject alloc] initWithLaunchSession:launchSession andMediaControl:self.mediaControl]; + if(success){ + success(launchObject); + } + }; + command.callbackError = failure; + [command send]; +} + +- (void) playMedia:(NSURL *)mediaURL iconURL:(NSURL *)iconURL title:(NSString *)title description:(NSString *)description mimeType:(NSString *)mimeType shouldLoop:(BOOL)shouldLoop success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:mediaURL mimeType:mimeType]; + mediaInfo.title = title; + mediaInfo.description = description; + ImageInfo *imageInfo = [[ImageInfo alloc] initWithURL:iconURL type:ImageTypeThumb]; + [mediaInfo addImage:imageInfo]; + + [self playMediaWithMediaInfo:mediaInfo shouldLoop:shouldLoop success:^(MediaLaunchObject *mediaLanchObject) { + success(mediaLanchObject.session,mediaLanchObject.mediaControl); + } failure:failure]; +} + +- (void) playMedia:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + [self playMedia:mediaInfo.url iconURL:iconURL title:mediaInfo.title description:mediaInfo.description mimeType:mediaInfo.mimeType shouldLoop:shouldLoop success:success failure:failure]; +} + +- (void) playMediaWithMediaInfo:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + NSURL *mediaURL = mediaInfo.url; + NSString *mimeType = mediaInfo.mimeType; + NSString *title = mediaInfo.title; + NSString *description = mediaInfo.description; + if (!mediaURL) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You need to provide a media URL"]); + + return; + } + + NSString *mediaType = [[mimeType componentsSeparatedByString:@"/"] lastObject]; + BOOL isVideo = [[mimeType substringToIndex:1] isEqualToString:@"v"]; + + NSString *applicationPath; + + if (isVideo) + { + applicationPath = [NSString stringWithFormat:@"15985?t=v&u=%@&k=(null)&videoName=%@&videoFormat=%@", + [ConnectUtil urlEncode:mediaURL.absoluteString], // content path + title ? [ConnectUtil urlEncode:title] : @"(null)", // video name + ensureString(mediaType) // video format + ]; + } else + { + applicationPath = [NSString stringWithFormat:@"15985?t=a&u=%@&k=(null)&songname=%@&artistname=%@&songformat=%@&albumarturl=%@", + [ConnectUtil urlEncode:mediaURL.absoluteString], // content path + title ? [ConnectUtil urlEncode:title] : @"(null)", // song name + description ? [ConnectUtil urlEncode:description] : @"(null)", // artist name + ensureString(mediaType), // audio format + iconURL ? [ConnectUtil urlEncode:iconURL.absoluteString] : @"(null)" + ]; + } + + NSString *commandPath = [NSString pathWithComponents:@[ + self.serviceDescription.commandURL.absoluteString, + @"input", + applicationPath + ]]; + + NSURL *targetURL = [NSURL URLWithString:commandPath]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self.serviceCommandDelegate target:targetURL payload:nil]; + command.HTTPMethod = @"POST"; + command.callbackComplete = ^(id responseObject) + { + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:@"15985"]; + launchSession.name = @"simplevideoplayer"; + launchSession.sessionType = LaunchSessionTypeMedia; + launchSession.service = self; + MediaLaunchObject *launchObject = [[MediaLaunchObject alloc] initWithLaunchSession:launchSession andMediaControl:self.mediaControl]; + if(success){ + success(launchObject); + } + }; + command.callbackError = failure; + [command send]; +} + +- (void)closeMedia:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.keyControl homeWithSuccess:success failure:failure]; +} + +#pragma mark - MediaControl + +- (id )mediaControl +{ + return self; +} + +- (CapabilityPriorityLevel)mediaControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)playWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:RokuKeyCodePlay success:success failure:failure]; +} + +- (void)pauseWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + // Roku does not have pause, it only has play/pause + [self sendKeyCode:RokuKeyCodePlay success:success failure:failure]; +} + +- (void)stopWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)rewindWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:RokuKeyCodeRewind success:success failure:failure]; +} + +- (void)fastForwardWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:RokuKeyCodeFastForward success:success failure:failure]; +} + +- (void)seek:(NSTimeInterval)position success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)getPlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribePlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +- (void)getDurationWithSuccess:(MediaPositionSuccessBlock)success + failure:(FailureBlock)failure { + [self sendNotSupportedFailure:failure]; +} + +- (void)getPositionWithSuccess:(MediaPositionSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)getMediaMetaDataWithSuccess:(SuccessBlock)success + failure:(FailureBlock)failure { + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribeMediaInfoWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +#pragma mark - Key Control + +- (id ) keyControl +{ + return self; +} + +- (CapabilityPriorityLevel) keyControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)upWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:RokuKeyCodeUp success:success failure:failure]; +} + +- (void)downWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:RokuKeyCodeDown success:success failure:failure]; +} + +- (void)leftWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:RokuKeyCodeLeft success:success failure:failure]; +} + +- (void)rightWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:RokuKeyCodeRight success:success failure:failure]; +} + +- (void)homeWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:RokuKeyCodeHome success:success failure:failure]; +} + +- (void)backWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:RokuKeyCodeBack success:success failure:failure]; +} + +- (void)okWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:RokuKeyCodeSelect success:success failure:failure]; +} + +- (void)sendKeyCode:(RokuKeyCode)keyCode success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (keyCode > kRokuKeyCodes.count) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:nil]); + return; + } + + NSString *keyCodeString = kRokuKeyCodes[keyCode]; + + [self sendKeyPress:keyCodeString success:success failure:failure]; +} + +#pragma mark - Text Input Control + +- (id ) textInputControl +{ + return self; +} + +- (CapabilityPriorityLevel) textInputControlPriority +{ + return CapabilityPriorityLevelNormal; +} + +- (void) sendText:(NSString *)input success:(SuccessBlock)success failure:(FailureBlock)failure +{ + // TODO: optimize this with queueing similiar to webOS and Netcast services + NSMutableArray *stringToSend = [NSMutableArray new]; + + [input enumerateSubstringsInRange:NSMakeRange(0, input.length) options:(NSStringEnumerationByComposedCharacterSequences) usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) + { + [stringToSend addObject:substring]; + }]; + + [stringToSend enumerateObjectsUsingBlock:^(NSString *charToSend, NSUInteger idx, BOOL *stop) + { + + NSString *codeToSend = [NSString stringWithFormat:@"%@%@", kRokuKeyCodes[RokuKeyCodeLiteral], [ConnectUtil urlEncode:charToSend]]; + + [self sendKeyPress:codeToSend success:success failure:failure]; + }]; +} + +- (void)sendEnterWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:RokuKeyCodeEnter success:success failure:failure]; +} + +- (void)sendDeleteWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendKeyCode:RokuKeyCodeBackspace success:success failure:failure]; +} + +- (ServiceSubscription *) subscribeTextInputStatusWithSuccess:(TextInputStatusInfoSuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +#pragma mark - Helper methods + +- (void) sendKeyPress:(NSString *)keyCode success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *targetURL = [self.serviceDescription.commandURL URLByAppendingPathComponent:@"keypress"]; + targetURL = [NSURL URLWithString:[targetURL.absoluteString stringByAppendingPathComponent:keyCode]]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self target:targetURL payload:nil]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (AppInfo *)appInfoFromDictionary:(NSDictionary *)appDictionary +{ + NSString *id = [appDictionary objectForKey:@"id"]; + NSString *name = [appDictionary objectForKey:@"text"]; + + AppInfo *appInfo = [AppInfo appInfoForId:id]; + appInfo.name = name; + appInfo.rawData = [appDictionary copy]; + + return appInfo; +} + +- (void) hasApp:(NSString *)appName success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.launcher getAppListWithSuccess:^(NSArray *appList) + { + if (appList) + { + __block AppInfo *foundAppInfo; + + [appList enumerateObjectsUsingBlock:^(AppInfo *appInfo, NSUInteger idx, BOOL *stop) + { + if ([appInfo.name isEqualToString:appName]) + { + foundAppInfo = appInfo; + *stop = YES; + } + }]; + + if (foundAppInfo) + { + if (success) + success(foundAppInfo); + } else + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Could not find this app on the TV"]); + } + } else + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeTvError andDetails:@"Could not find any apps on the TV."]); + } + } failure:failure]; +} + +@end diff --git a/core/Services/RokuService_Private.h b/core/Services/RokuService_Private.h new file mode 100644 index 00000000..31d3dc10 --- /dev/null +++ b/core/Services/RokuService_Private.h @@ -0,0 +1,27 @@ +// +// RokuService_Private.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-07-16. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "RokuService.h" + +@interface RokuService () + +@property (nonatomic, strong) id serviceCommandDelegate; + +@end diff --git a/core/Services/Sessions/AirPlayWebAppSession.h b/core/Services/Sessions/AirPlayWebAppSession.h new file mode 100644 index 00000000..653d3091 --- /dev/null +++ b/core/Services/Sessions/AirPlayWebAppSession.h @@ -0,0 +1,32 @@ +// +// AirPlayWebAppSession.h +// Connect SDK +// +// Created by Jeremy White on 4/24/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "WebAppSession.h" +#import "AirPlayService.h" + + +@interface AirPlayWebAppSession : WebAppSession + +@property (nonatomic, readonly) AirPlayService *service; + +@property (nonatomic, readonly) WebAppMessageBlock messageHandler; + +@end diff --git a/core/Services/Sessions/AirPlayWebAppSession.m b/core/Services/Sessions/AirPlayWebAppSession.m new file mode 100644 index 00000000..d10e39d4 --- /dev/null +++ b/core/Services/Sessions/AirPlayWebAppSession.m @@ -0,0 +1,566 @@ +// +// AirPlayWebAppSession.m +// Connect SDK +// +// Created by Jeremy White on 4/24/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "AirPlayWebAppSession.h" +#import "ConnectError.h" +#import "ConnectUtil.h" +#import "MediaLaunchObject.h" + +#import "NSObject+FeatureNotSupported_Private.h" + +@interface AirPlayWebAppSession () +{ + ServiceSubscription *_playStateSubscription; + NSMutableDictionary *_activeCommands; + + int _UID; +} + +@end + +@implementation AirPlayWebAppSession +{ + ServiceSubscription *_webAppStatusSubscription; +} + +@dynamic service; + +- (instancetype) initWithLaunchSession:(LaunchSession *)launchSession service:(DeviceService *)service +{ + self = [super initWithLaunchSession:launchSession service:service]; + + if (self) + { + _UID = 0; + _activeCommands = [NSMutableDictionary new]; + + __weak id weakSelf = self; + + _messageHandler = ^(id message) + { + if ([message isKindOfClass:[NSDictionary class]]) + { + NSDictionary *messageJSON = (NSDictionary *)message; + + NSString *contentType = [messageJSON objectForKey:@"contentType"]; + NSRange contentTypeRange = [contentType rangeOfString:@"connectsdk."]; + + if (contentType && contentTypeRange.location != NSNotFound) + { + NSString *payloadKey = [contentType substringFromIndex:contentTypeRange.length]; + + if (!payloadKey || payloadKey.length == 0) + return; + + id payload = [messageJSON objectForKey:payloadKey]; + + if (!payload) + return; + + if ([payloadKey isEqualToString:@"mediaEvent"]) + [weakSelf handleMediaEvent:payload]; + else if ([payloadKey isEqualToString:@"mediaCommandResponse"]) + [weakSelf handleMediaCommandResponse:payload]; + } else + { + [weakSelf handleMessage:messageJSON]; + } + } else if ([message isKindOfClass:[NSString class]]) + { + [weakSelf handleMessage:message]; + } + }; + } + + return self; +} + +- (int) getNextId +{ + _UID = _UID + 1; + return _UID; +} + +- (int) sendSubscription:(ServiceSubscription *)subscription type:(ServiceSubscriptionType)type payload:(id)payload toURL:(NSURL *)URL withId:(int)callId +{ + if (type == ServiceSubscriptionTypeUnsubscribe) + { + if (subscription == _webAppStatusSubscription) + { + [[_webAppStatusSubscription successCalls] removeAllObjects]; + [[_webAppStatusSubscription failureCalls] removeAllObjects]; + [_webAppStatusSubscription setIsSubscribed:NO]; + _webAppStatusSubscription = nil; + } else if (subscription == _playStateSubscription) + { + [[_playStateSubscription successCalls] removeAllObjects]; + [[_playStateSubscription failureCalls] removeAllObjects]; + [_playStateSubscription setIsSubscribed:NO]; + _playStateSubscription = nil; + } + } + + return -1; +} + +#pragma mark - Message handlers + +- (void) handleMediaEvent:(NSDictionary *)payload +{ + NSString *type = [payload objectForKey:@"type"]; + + if ([type isEqualToString:@"playState"]) + { + if (!_playStateSubscription) + return; + + NSString *playStateString = [payload objectForKey:@"playState"]; + MediaControlPlayState playState = [self parsePlayState:playStateString]; + + [_playStateSubscription.successCalls enumerateObjectsUsingBlock:^(id success, NSUInteger idx, BOOL *stop) + { + MediaPlayStateSuccessBlock mediaPlayStateSuccess = (MediaPlayStateSuccessBlock) success; + + if (mediaPlayStateSuccess) + mediaPlayStateSuccess(playState); + }]; + } +} + +- (void) handleMediaCommandResponse:(NSDictionary *)payload +{ + NSString *requestId = [payload objectForKey:@"requestId"]; + + ServiceCommand *command = [_activeCommands objectForKey:requestId]; + + if (!command) + return; + + NSString *error = [payload objectForKey:@"error"]; + + if (error) + { + if (command.callbackError) + command.callbackError([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:error]); + } else + { + if (command.callbackComplete) + command.callbackComplete(payload); + } + + [_activeCommands removeObjectForKey:requestId]; +} + +- (void) handleMessage:(id)message +{ + if (self.delegate && [self.delegate respondsToSelector:@selector(webAppSession:didReceiveMessage:)]) + [self.delegate webAppSession:self didReceiveMessage:message]; +} + +- (ServiceSubscription *) subscribeWebAppStatus:(WebAppStatusBlock)success failure:(FailureBlock)failure +{ + if (!_webAppStatusSubscription) + _webAppStatusSubscription = [ServiceSubscription subscriptionWithDelegate:self target:nil payload:nil callId:-1]; + + [_webAppStatusSubscription addSuccess:success]; + [_webAppStatusSubscription addFailure:failure]; + [_webAppStatusSubscription setIsSubscribed:YES]; + + return _webAppStatusSubscription; +} + +- (void) joinWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.service.webAppLauncher joinWebApp:self.launchSession success:success failure:failure]; +} + +- (void) closeWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.service closeLaunchSession:self.launchSession success:success failure:failure]; +} + +- (void) connectWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (success) + success(self); +} + +- (void) disconnectFromWebApp +{ + [self.service.mirroredService disconnectFromWebApp]; +} + +- (void) sendText:(NSString *)message success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (!message) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You must provide a valid argument"]); + } + + NSString *commandString = [NSString stringWithFormat:@"window.connectManager.handleMessage({from: -1, message: \"%@\" })", message]; + + [self.service.mirroredService.webAppWebView evaluateJavaScript:commandString completionHandler:^(NSString *result, NSError *error) { + NSLog(@"evaluate Completed"); + }]; + if (success) + success(nil); +} + +- (void) sendJSON:(NSDictionary *)message success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (!message) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You must provide a valid argument"]); + } + + NSError *error; + NSData *messageData = [NSJSONSerialization dataWithJSONObject:message options:0 error:&error]; + + if (error || !messageData) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Could not parse message into sendable format"]); + } else + { + NSString *messageString = [[NSString alloc] initWithData:messageData encoding:NSUTF8StringEncoding]; + NSString *commandString = [NSString stringWithFormat:@"window.connectManager.handleMessage({from: -1, message: %@ })", messageString]; + [self.service.mirroredService.webAppWebView evaluateJavaScript:commandString completionHandler:^(NSString *result, NSError *error) { + NSLog(@"evaluate Completed"); + }]; + + if (success) + success(nil); + } +} + +#pragma mark - Media Player + +- (id ) mediaPlayer +{ + return self; +} + +- (CapabilityPriorityLevel) mediaPlayerPriority +{ + return CapabilityPriorityLevelHigh; +} + +-(void) displayImageWithMediaInfo:(MediaInfo *)mediaInfo success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + + int requestIdNumber = [self getNextId]; + NSString *requestId = [NSString stringWithFormat:@"req%d", requestIdNumber]; + + NSDictionary *message = @{ + @"contentType" : @"connectsdk.mediaCommand", + @"mediaCommand" : @{ + @"type" : @"displayImage", + @"mediaURL" : ensureString(mediaInfo.url.absoluteString), + @"iconURL" : ensureString(iconURL.absoluteString), + @"title" : ensureString(mediaInfo.title), + @"description" : ensureString(mediaInfo.description), + @"mimeType" : ensureString(mediaInfo.mimeType), + @"requestId" : requestId + } + }; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:nil target:nil payload:nil]; + command.callbackComplete = ^(id responseObject) + { + if (success){ + MediaLaunchObject *launchObject = [[MediaLaunchObject alloc] initWithLaunchSession:self.launchSession andMediaControl:self.mediaControl]; + success(launchObject); + } + }; + command.callbackError = failure; + [_activeCommands setObject:command forKey:requestId]; + + [self sendJSON:message success:nil failure:failure]; +} + + +-(void) playMediaWithMediaInfo:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + + int requestIdNumber = [self getNextId]; + NSString *requestId = [NSString stringWithFormat:@"req%d", requestIdNumber]; + + NSDictionary *message = @{ + @"contentType" : @"connectsdk.mediaCommand", + @"mediaCommand" : @{ + @"type" : @"playMedia", + @"mediaURL" : ensureString(mediaInfo.url.absoluteString), + @"iconURL" : ensureString(iconURL.absoluteString), + @"title" : ensureString(mediaInfo.title), + @"description" : ensureString(mediaInfo.description), + @"mimeType" : ensureString(mediaInfo.mimeType), + @"shouldLoop" : @(shouldLoop), + @"requestId" : requestId + } + }; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:nil target:nil payload:nil]; + command.callbackComplete = ^(id responseObject) + { + if (success){ + MediaLaunchObject *launchObject = [[MediaLaunchObject alloc] initWithLaunchSession:self.launchSession andMediaControl:self.mediaControl]; + success(launchObject); + } + }; + command.callbackError = failure; + [_activeCommands setObject:command forKey:requestId]; + + [self sendJSON:message success:nil failure:failure]; +} + +- (void) closeMedia:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self closeWithSuccess:success failure:failure]; +} + +#pragma mark - Media Control + +- (id ) mediaControl +{ + return self; +} + +- (CapabilityPriorityLevel) mediaControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void) playWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + int requestIdNumber = [self getNextId]; + NSString *requestId = [NSString stringWithFormat:@"req%d", requestIdNumber]; + + NSDictionary *message = @{ + @"contentType" : @"connectsdk.mediaCommand", + @"mediaCommand" : @{ + @"type" : @"play", + @"requestId" : requestId + } + }; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:nil target:nil payload:nil]; + command.callbackComplete = success; + command.callbackError = failure; + [_activeCommands setObject:command forKey:requestId]; + + [self sendJSON:message success:nil failure:failure]; +} + +- (void) pauseWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + int requestIdNumber = [self getNextId]; + NSString *requestId = [NSString stringWithFormat:@"req%d", requestIdNumber]; + + NSDictionary *message = @{ + @"contentType" : @"connectsdk.mediaCommand", + @"mediaCommand" : @{ + @"type" : @"pause", + @"requestId" : requestId + } + }; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:nil target:nil payload:nil]; + command.callbackComplete = success; + command.callbackError = failure; + [_activeCommands setObject:command forKey:requestId]; + + [self sendJSON:message success:nil failure:failure]; +} + +- (void) fastForwardWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void) rewindWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)seek:(NSTimeInterval)position success:(SuccessBlock)success failure:(FailureBlock)failure +{ + int requestIdNumber = [self getNextId]; + NSString *requestId = [NSString stringWithFormat:@"req%d", requestIdNumber]; + + NSDictionary *message = @{ + @"contentType" : @"connectsdk.mediaCommand", + @"mediaCommand" : @{ + @"type" : @"seek", + @"position" : @(position), + @"requestId" : requestId + } + }; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:nil target:nil payload:nil]; + command.callbackComplete = success; + command.callbackError = failure; + [_activeCommands setObject:command forKey:requestId]; + + [self sendJSON:message success:nil failure:failure]; +} + +- (void)getPositionWithSuccess:(MediaPositionSuccessBlock)success failure:(FailureBlock)failure +{ + int requestIdNumber = [self getNextId]; + NSString *requestId = [NSString stringWithFormat:@"req%d", requestIdNumber]; + + NSDictionary *message = @{ + @"contentType" : @"connectsdk.mediaCommand", + @"mediaCommand" : @{ + @"type" : @"getPosition", + @"requestId" : requestId + } + }; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:nil target:nil payload:nil]; + command.callbackComplete = ^(NSDictionary *responseObject) + { + NSString *positionString = [responseObject objectForKey:@"position"]; + NSTimeInterval position = 0; + + if (positionString && ![positionString isKindOfClass:[NSNull class]]) + position = [positionString intValue]; + + if (success) + success(position); + }; + command.callbackError = failure; + [_activeCommands setObject:command forKey:requestId]; + + [self sendJSON:message success:nil failure:failure]; +} + +- (void)getDurationWithSuccess:(MediaDurationSuccessBlock)success failure:(FailureBlock)failure +{ + int requestIdNumber = [self getNextId]; + NSString *requestId = [NSString stringWithFormat:@"req%d", requestIdNumber]; + + NSDictionary *message = @{ + @"contentType" : @"connectsdk.mediaCommand", + @"mediaCommand" : @{ + @"type" : @"getDuration", + @"requestId" : requestId + } + }; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:nil target:nil payload:nil]; + command.callbackComplete = ^(id responseObject) + { + NSString *durationString = [responseObject objectForKey:@"duration"]; + NSTimeInterval duration = 0; + + if (durationString && ![durationString isKindOfClass:[NSNull class]]) + duration = [durationString intValue]; + + if (success) + success(duration); + }; + command.callbackError = failure; + [_activeCommands setObject:command forKey:requestId]; + + [self sendJSON:message success:nil failure:failure]; +} + +- (void)getPlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + int requestIdNumber = [self getNextId]; + NSString *requestId = [NSString stringWithFormat:@"req%d", requestIdNumber]; + + NSDictionary *message = @{ + @"contentType" : @"connectsdk.mediaCommand", + @"mediaCommand" : @{ + @"type" : @"getPlayState", + @"requestId" : requestId + } + }; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:nil target:nil payload:nil]; + command.callbackComplete = ^(NSDictionary *responseObject) + { + NSString *playStateString = [responseObject objectForKey:@"playState"]; + MediaControlPlayState playState = [self parsePlayState:playStateString]; + + if (success) + success(playState); + }; + command.callbackError = failure; + [_activeCommands setObject:command forKey:requestId]; + + [self sendJSON:message success:nil failure:failure]; +} + +- (ServiceSubscription *)subscribePlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + if (!_playStateSubscription) + _playStateSubscription = [ServiceSubscription subscriptionWithDelegate:nil target:nil payload:nil callId:-1]; + +// if (!_connected) +// [self connectWithSuccess:nil failure:failure]; + + if (![_playStateSubscription.successCalls containsObject:success]) + [_playStateSubscription addSuccess:success]; + + if (![_playStateSubscription.failureCalls containsObject:failure]) + [_playStateSubscription addFailure:failure]; + + return _playStateSubscription; +} + +- (MediaControlPlayState) parsePlayState:(NSString *)playStateString +{ + MediaControlPlayState playState = MediaControlPlayStateUnknown; + + if ([playStateString isEqualToString:@"playing"]) + playState = MediaControlPlayStatePlaying; + else if ([playStateString isEqualToString:@"paused"]) + playState = MediaControlPlayStatePaused; + else if ([playStateString isEqualToString:@"idle"]) + playState = MediaControlPlayStateIdle; + else if ([playStateString isEqualToString:@"buffering"]) + playState = MediaControlPlayStateBuffering; + else if ([playStateString isEqualToString:@"finished"]) + playState = MediaControlPlayStateFinished; + + return playState; +} + +- (ServiceSubscription *)subscribeMediaInfoWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +@end diff --git a/core/Services/Sessions/LaunchSession.h b/core/Services/Sessions/LaunchSession.h new file mode 100644 index 00000000..9daa90c6 --- /dev/null +++ b/core/Services/Sessions/LaunchSession.h @@ -0,0 +1,106 @@ +// +// LaunchSession.h +// Connect SDK +// +// Created by Jeremy White on 1/28/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "Capability.h" +#import "JSONObjectCoding.h" + +@class DeviceService; + + +/*! + * LaunchSession type is used to help DeviceService's know how to close a LunchSession. + */ +typedef enum +{ + /*! Unknown LaunchSession type, may be unable to close this launch session */ + LaunchSessionTypeUnknown, + + /*! LaunchSession represents a launched app */ + LaunchSessionTypeApp, + + /*! LaunchSession represents an external input picker that was launched */ + LaunchSessionTypeExternalInputPicker, + + /*! LaunchSession represents a media app */ + LaunchSessionTypeMedia, + + /*! LaunchSession represents a web app */ + LaunchSessionTypeWebApp +} LaunchSessionType; + + +/*! + * Any time anything is launched onto a first screen device, there will be important session information that needs to be tracked. LaunchSession will track this data, and must be retained to perform certain actions within the session. + */ +@interface LaunchSession : NSObject + +/*! System-specific, unique ID of the app (ex. youtube.leanback.v4, 0000134, hulu) */ +@property (nonatomic, strong) NSString *appId; + +/*! User-friendly name of the app (ex. YouTube, Browser, Hulu) */ +@property (nonatomic, strong) NSString *name; + +/*! Unique ID for the session (only provided by certain protocols) */ +@property (nonatomic, strong) NSString *sessionId; + +/*! Raw data from the first screen device about the session. In most cases, this is an NSDictionary. */ +@property (nonatomic, strong) id rawData; + +/*! + * When closing a LaunchSession, the DeviceService relies on the sessionType to determine the method of closing the session. + */ +@property (nonatomic) LaunchSessionType sessionType; + +/*! DeviceService responsible for launching the session. */ +@property (nonatomic, weak) DeviceService *service; + +/*! + * Compares two LaunchSession objects. + * + * @param launchSession LaunchSession object to compare. + * + * @return YES if both LaunchSession id and sessionId values are equal + */ +- (BOOL)isEqual:(LaunchSession *)launchSession; + +/*! + * Instantiates a LaunchSession object for a given app ID. + * + * @param appId System-specific, unique ID of the app + */ ++ (LaunchSession *) launchSessionForAppId:(NSString *)appId; + +/*! + * Deserializes a @c LaunchSession object from json object. + * + * @param json Serialized @c LaunchSession object by -[LaunchSession toJSONObject]. + */ ++ (LaunchSession *) launchSessionFromJSONObject:(NSDictionary *)json; + +/*! + * Closes the session on the first screen device. Depending on the sessionType, the associated service will have different ways of handling the close functionality. + * + * @param success (optional) SuccessBlock to be called on success + * @param failure (optional) FailureBlock to be called on failure + */ +- (void) closeWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; + +@end diff --git a/core/Services/Sessions/LaunchSession.m b/core/Services/Sessions/LaunchSession.m new file mode 100644 index 00000000..bd28c43b --- /dev/null +++ b/core/Services/Sessions/LaunchSession.m @@ -0,0 +1,94 @@ +// +// LaunchSession.m +// Connect SDK +// +// Created by Jeremy White on 1/28/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "LaunchSession.h" +#import "ConnectError.h" +#import "DeviceService.h" + +@implementation LaunchSession + + ++ (LaunchSession *) launchSessionForAppId:(NSString *)appId +{ + LaunchSession *launchSession = [[LaunchSession alloc] init]; + launchSession.appId = appId; + + return launchSession; +} + ++ (LaunchSession *) launchSessionFromJSONObject:(NSDictionary *)json +{ + return [[LaunchSession alloc] initWithJSONObject:json]; +} + +- (instancetype) initWithJSONObject:(NSDictionary*)json +{ + self = [super init]; + + if (self) { + self.appId = [json objectForKey:@"appId"]; + self.sessionId = [json objectForKey:@"sessionId"]; + self.name = [json objectForKey:@"name"]; + self.sessionType = (LaunchSessionType) [[json objectForKey:@"sessionType"] integerValue]; + self.rawData = [json objectForKey:@"rawData"]; + } + + return self; +} + +- (NSDictionary *) toJSONObject +{ + NSMutableDictionary *json = [NSMutableDictionary new]; + if (self.appId) [json setObject:self.appId forKey:@"appId"]; + if (self.sessionId) [json setObject:self.sessionId forKey:@"sessionId"]; + if (self.name) [json setObject:self.name forKey:@"name"]; + if (self.sessionType) [json setObject:@(self.sessionType) forKey:@"sessionType"]; + if (self.service) [json setObject:[self.service serviceName] forKey:@"serviceName"]; + + if (self.rawData) + { + if ([self.rawData isKindOfClass:[NSDictionary class]] || + [self.rawData isKindOfClass:[NSArray class]] || + [self.rawData isKindOfClass:[NSString class]]) + [json setObject:self.rawData forKey:@"rawData"]; + } + + return json; +} + +- (BOOL)isEqual:(LaunchSession *)launchSession +{ + return [launchSession.appId isEqualToString:self.appId] && [launchSession.sessionId isEqualToString:self.sessionId]; +} + +- (void)closeWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (!self.service) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"This LaunchSession has no DeviceService reference"]); + + return; + } + + [self.service closeLaunchSession:self success:success failure:failure]; +} + +@end diff --git a/core/Services/Sessions/WebAppSession.h b/core/Services/Sessions/WebAppSession.h new file mode 100644 index 00000000..1efca35b --- /dev/null +++ b/core/Services/Sessions/WebAppSession.h @@ -0,0 +1,183 @@ +// +// WebAppSession.h +// Connect SDK +// +// Created by Jeremy White on 2/21/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "Capability.h" +#import "DeviceService.h" +#import "MediaPlayer.h" +#import "MediaControl.h" +#import "ServiceCommandDelegate.h" +#import "LaunchSession.h" +#import "WebAppSessionDelegate.h" + +/*! Status of the web app */ +typedef enum { + /*! Web app status is unknown */ + WebAppStatusUnknown, + + /*! Web app is running and in the foreground */ + WebAppStatusOpen, + + /*! Web app is running and in the background */ + WebAppStatusBackground, + + /*! Web app is in the foreground but has not started running yet */ + WebAppStatusForeground, + + /*! Web app is not running and is not in the foreground or background */ + WebAppStatusClosed +} WebAppStatus; + + +/*! + * ###Overview + * When a web app is launched on a first screen device, there are certain tasks that can be performed with that web app. WebAppSession serves as a second screen reference of the web app that was launched. It behaves similarly to LaunchSession, but is not nearly as static. + * + * ###In Depth + * On top of maintaining session information (contained in the launchSession property), WebAppSession provides access to a number of capabilities. + * - MediaPlayer + * - MediaControl + * - Bi-directional communication with web app + * + * MediaPlayer and MediaControl are provided to allow for the most common first screen use cases -- a media player (audio, video, & images). + * + * The Connect SDK JavaScript Bridge has been produced to provide normalized support for these capabilities across protocols (Chromecast, webOS, etc). + */ +@interface WebAppSession : NSObject + +// @cond INTERNAL +// This is only being used in WebOSWebAppSession, but could be useful in other places in the future +typedef void (^ WebAppMessageBlock)(id message); +// @endcond + +/*! + * Success block that is called upon successfully getting a web app's status. + * + * @param status The current running & foreground status of the web app + */ +typedef void (^ WebAppStatusBlock)(WebAppStatus status); + +/*! + * Success block that is called upon successfully getting a web app's status. + * + * @param status The current running & foreground status of the web app + */ +typedef void (^ WebAppPinStatusBlock)(BOOL status); + +/*! + * LaunchSession object containing key session information. Much of this information is required for web app messaging & closing the web app. + */ +@property (nonatomic, strong) LaunchSession *launchSession; + +/*! + * DeviceService that was responsible for launching this web app. + */ +@property (nonatomic, weak, readonly) DeviceService *service; + +/*! + * Instantiates a WebAppSession object with all the information necessary to interact with a web app. + * + * @param launchSession LaunchSession containing info about the web app session + * @param service DeviceService that was responsible for launching this web app + */ +- (instancetype) initWithLaunchSession:(LaunchSession *)launchSession service:(DeviceService *)service; + +/*! + * Subscribes to changes in the web app's status. + * + * @param success (optional) WebAppStatusBlock to be called on app status change + * @param failure (optional) FailureBlock to be called on failure + */ +- (ServiceSubscription *) subscribeWebAppStatus:(WebAppStatusBlock)success failure:(FailureBlock)failure; + +/*! + * Join an active web app without launching/relaunching. If the app is not running/joinable, the failure block will be called immediately. + * + * @param success (optional) SuccessBlock to be called on join success + * @param failure (optional) FailureBlock to be called on failure + */ +- (void) joinWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; + +/*! + * Closes the web app on the first screen device. + * + * @param success (optional) SuccessBlock to be called on success + * @param failure (optional) FailureBlock to be called on failure + */ +- (void) closeWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; + +#pragma mark - Connection handling + +/*! + * Establishes a communication channel with the web app. + * + * @param success (optional) SuccessBlock to be called on success + * @param failure (optional) FailureBlock to be called on failure + */ +- (void) connectWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure; + +/*! + * Closes any open communication channel with the web app. + */ +- (void) disconnectFromWebApp; + +/*! + * Pin the web app on the launcher. + * + * @param webAppId NSString webAppId to be pinned. + */ +- (void)pinWebApp:(NSString *)webAppId success:(SuccessBlock)success failure:(FailureBlock)failure; + +/*! + * UnPin the web app on the launcher. + * + * @param webAppId NSString webAppId to be unpinned. + */ +- (void)unPinWebApp:(NSString *)webAppId success:(SuccessBlock)success failure:(FailureBlock)failure; + +/*! + * To check if the web app is pinned or not + */ +- (void)isWebAppPinned:(NSString *)webAppId success:(WebAppPinStatusBlock)success failure:(FailureBlock)failure; + +#pragma mark - Communication + +/*! + * When messages are received from a web app, they are parsed into the appropriate object type (string vs JSON/NSDictionary) and routed to the WebAppSessionDelegate. + */ +@property (nonatomic, strong) id delegate; + +/*! + * Sends a simple string to the web app. The Connect SDK JavaScript Bridge will receive this message and hand it off as a string object. + * + * @param success (optional) SuccessBlock to be called on success + * @param failure (optional) FailureBlock to be called on failure + */ +- (void) sendText:(NSString *)message success:(SuccessBlock)success failure:(FailureBlock)failure; + +/*! + * Sends a JSON object to the web app. The Connect SDK JavaScript Bridge will receive this message and hand it off as a JavaScript object. + * + * @param success (optional) SuccessBlock to be called on success + * @param failure (optional) FailureBlock to be called on failure + */ +- (void) sendJSON:(NSDictionary *)message success:(SuccessBlock)success failure:(FailureBlock)failure; + +@end diff --git a/core/Services/Sessions/WebAppSession.m b/core/Services/Sessions/WebAppSession.m new file mode 100644 index 00000000..226be83a --- /dev/null +++ b/core/Services/Sessions/WebAppSession.m @@ -0,0 +1,316 @@ +// +// WebAppSession.m +// Connect SDK +// +// Created by Jeremy White on 2/21/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "WebAppSession.h" + +#import "NSObject+FeatureNotSupported_Private.h" + +@implementation WebAppSession + +- (instancetype) initWithJSONObject:(NSDictionary*)dict +{ + return nil; // not supported +} + +- (NSDictionary*) toJSONObject +{ + NSMutableDictionary* dict = [NSMutableDictionary dictionary]; + + if (self.launchSession) { + dict[@"launchSession"] = [self.launchSession toJSONObject]; + } + + if (self.service && self.service.serviceDescription) { + dict[@"serviceName"] = [self.service serviceName]; + } + + return dict; +} + +- (instancetype)initWithLaunchSession:(LaunchSession *)launchSession service:(DeviceService *)service +{ + self = [super init]; + + if (self) + { + _launchSession = launchSession; + _service = service; + } + + return self; +} + +#pragma mark - ServiceCommandDelegate methods + +- (int)sendCommand:(ServiceCommand *)comm withPayload:(id)payload toURL:(NSURL *)URL +{ + return -1; +} + +- (int)sendAsync:(ServiceAsyncCommand *)async withPayload:(id)payload toURL:(NSURL *)URL +{ + return -1; +} + +- (int)sendSubscription:(ServiceSubscription *)subscription type:(ServiceSubscriptionType)type payload:(id)payload toURL:(NSURL *)URL withId:(int)callId +{ + return -1; +} + +#pragma mark - Web App methods + +- (ServiceSubscription *) subscribeWebAppStatus:(WebAppStatusBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +- (void) connectWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void) joinWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)disconnectFromWebApp { } + +- (void)sendText:(NSString *)message success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)sendJSON:(NSDictionary *)message success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)pinWebApp:(NSString *)webAppId success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)unPinWebApp:(NSString *)webAppId success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)isWebAppPinned:(NSString *)webAppId success:(WebAppPinStatusBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribeIsWebAppPinned:(NSString*)webAppId success:(WebAppPinStatusBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +#pragma mark - Media Player + +- (id ) mediaPlayer +{ + return self; +} + +- (CapabilityPriorityLevel) mediaPlayerPriority +{ + return CapabilityPriorityLevelLow; +} + +- (void) displayImage:(NSURL *)imageURL iconURL:(NSURL *)iconURL title:(NSString *)title description:(NSString *)description mimeType:(NSString *)mimeType success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void) displayImage:(MediaInfo *)mediaInfo + success:(MediaPlayerDisplaySuccessBlock)success + failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void) displayImageWithMediaInfo:(MediaInfo *)mediaInfo success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void) playMedia:(NSURL *)mediaURL iconURL:(NSURL *)iconURL title:(NSString *)title description:(NSString *)description mimeType:(NSString *)mimeType shouldLoop:(BOOL)shouldLoop success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void) playMedia:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void) playMediaWithMediaInfo:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void) closeMedia:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +#pragma mark - MediaControl + +- (id )mediaControl +{ + return self; +} + +- (CapabilityPriorityLevel)mediaControlPriority +{ + return CapabilityPriorityLevelLow; +} + +- (void)playWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + id mediaControl; + + if (self.service && [self.service respondsToSelector:@selector(mediaControl)]) + mediaControl = [(id)self.service mediaControl]; + + if (mediaControl) + [mediaControl playWithSuccess:success failure:failure]; + else + [self sendNotSupportedFailure:failure]; +} + +- (void)pauseWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + id mediaControl; + + if (self.service && [self.service respondsToSelector:@selector(mediaControl)]) + mediaControl = [(id)self.service mediaControl]; + + if (mediaControl) + [mediaControl pauseWithSuccess:success failure:failure]; + else + [self sendNotSupportedFailure:failure]; +} + +- (void)stopWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + id mediaControl; + + if (self.service && [self.service respondsToSelector:@selector(mediaControl)]) + mediaControl = [(id)self.service mediaControl]; + + if (mediaControl) + [mediaControl stopWithSuccess:success failure:failure]; + else + [self sendNotSupportedFailure:failure]; +} + +- (void)rewindWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + id mediaControl; + + if (self.service && [self.service respondsToSelector:@selector(mediaControl)]) + mediaControl = [(id)self.service mediaControl]; + + if (mediaControl) + [mediaControl rewindWithSuccess:success failure:failure]; + else + [self sendNotSupportedFailure:failure]; +} + +- (void)fastForwardWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + id mediaControl; + + if (self.service && [self.service respondsToSelector:@selector(mediaControl)]) + mediaControl = [(id)self.service mediaControl]; + + if (mediaControl) + [mediaControl fastForwardWithSuccess:success failure:failure]; + else + [self sendNotSupportedFailure:failure]; +} + +- (void) seek:(NSTimeInterval)position success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)closeWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void) getPlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)getDurationWithSuccess:(MediaDurationSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribePlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +- (void) getPositionWithSuccess:(MediaPositionSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)getMediaMetaDataWithSuccess:(SuccessBlock)success + failure:(FailureBlock)failure { + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribeMediaInfoWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +#pragma mark - Playlist Control + +- (id )playListControl +{ + return nil; +} + +- (CapabilityPriorityLevel)playListControlPriority +{ + return CapabilityPriorityLevelLow; +} + +- (void)playNextWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure{ + [self sendNotSupportedFailure:failure]; +} + +- (void)playPreviousWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure{ + [self sendNotSupportedFailure:failure]; +} + +- (void)jumpToTrackWithIndex:(NSInteger)index success:(SuccessBlock)success failure:(FailureBlock)failure{ + [self sendNotSupportedFailure:failure]; +} + +@end diff --git a/core/Services/Sessions/WebAppSessionDelegate.h b/core/Services/Sessions/WebAppSessionDelegate.h new file mode 100644 index 00000000..029725e2 --- /dev/null +++ b/core/Services/Sessions/WebAppSessionDelegate.h @@ -0,0 +1,47 @@ +// +// WebAppSessionDelegate.h +// Connect SDK +// +// Created by Jeremy White on 3/26/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@class WebAppSession; + +/*! + * WebAppSessionDelegate provides callback methods for receiving messages from a running web app. + */ +@protocol WebAppSessionDelegate + +@optional + +/*! + * This method is called when a message is received from a web app. + * + * @param webAppSession WebAppSession that corresponds to the web app that sent the message + * @param message Message from the web app, either an NSString or a JSON object in the form of an NSDictionary + */ +- (void) webAppSession:(WebAppSession *)webAppSession didReceiveMessage:(id)message; + +/*! + * This method is called when a web app's communication channel (WebSocket, etc) has become disconnected. + * + * @param webAppSession WebAppSession that became disconnected + */ +- (void) webAppSessionDidDisconnect:(WebAppSession *)webAppSession; + +@end diff --git a/core/Services/Sessions/WebOSWebAppSession.h b/core/Services/Sessions/WebOSWebAppSession.h new file mode 100644 index 00000000..d630f3e6 --- /dev/null +++ b/core/Services/Sessions/WebOSWebAppSession.h @@ -0,0 +1,35 @@ +// +// WebOSWebAppSession.h +// Connect SDK +// +// Created by Jeremy White on 2/23/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "WebOSTVService.h" +#import "WebAppSession.h" +#import "WebOSTVServiceSocketClient.h" + + +@interface WebOSWebAppSession : WebAppSession + +@property (nonatomic, readonly) WebOSTVService *service; +@property (nonatomic) ServiceSubscription *appToAppSubscription; +@property (nonatomic) NSString *fullAppId; +@property (nonatomic) BOOL connected; +@property (nonatomic, readonly) WebOSTVServiceSocketClient *socket; + +@end diff --git a/core/Services/Sessions/WebOSWebAppSession.m b/core/Services/Sessions/WebOSWebAppSession.m new file mode 100644 index 00000000..ed8c26f9 --- /dev/null +++ b/core/Services/Sessions/WebOSWebAppSession.m @@ -0,0 +1,848 @@ +// +// WebOSWebAppSession.m +// Connect SDK +// +// Created by Jeremy White on 2/23/14. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "WebOSWebAppSession_Private.h" + +#import "ConnectError.h" +#import "SubtitleInfo.h" + +#import "NSMutableDictionary+NilSafe.h" +#import "NSObject+FeatureNotSupported_Private.h" + +static NSString *const kSubtitleTrackIdentifier = @"0"; + +@implementation WebOSWebAppSession +{ + ServiceSubscription *_playStateSubscription; + ServiceSubscription *_messageSubscription; + ServiceSubscription *_webAppPinnedSubscription; + NSMutableDictionary *_activeCommands; + + SuccessBlock _connectSuccess; + FailureBlock _connectFailure; + + int _UID; +} + +@dynamic service; + +- (instancetype)initWithLaunchSession:(LaunchSession *)launchSession service:(DeviceService *)service +{ + self = [super initWithLaunchSession:launchSession service:service]; + + if (self) + { + _UID = 0; + + _activeCommands = [NSMutableDictionary new]; + } + + return self; +} + +- (int) getNextId +{ + _UID = _UID + 1; + return _UID; +} + +- (NSString *) fullAppId +{ + if (!_fullAppId) + { + if (self.launchSession.sessionType != LaunchSessionTypeWebApp) + _fullAppId = self.launchSession.appId; + else + { + [self.service.appToAppIdMappings enumerateKeysAndObjectsUsingBlock:^(NSString *mappedFullAppId, NSString *mappedAppId, BOOL *stop) { + if ([mappedAppId isEqualToString:self.launchSession.appId]) + { + _fullAppId = mappedFullAppId; + *stop = YES; + } + }]; + } + } + + if (!_fullAppId) + return self.launchSession.appId; + else + return _fullAppId; +} + +#pragma mark - WebOSTVServiceSocketClientDelegate methods + +- (void) socketDidConnect:(WebOSTVServiceSocketClient *)socket +{ + if (_connectSuccess) + _connectSuccess(nil); + + _connectSuccess = nil; + _connectFailure = nil; +} + +- (void) socket:(WebOSTVServiceSocketClient *)socket didFailWithError:(NSError *)error +{ + _connected = NO; + _appToAppSubscription = nil; + + if (_connectFailure) + { + if (!error) + error = [ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Unknown error connecting to web socket"]; + + _connectFailure(error); + } + + _connectSuccess = nil; + _connectFailure = nil; + + [self disconnectFromWebApp]; +} + +- (void) socket:(WebOSTVServiceSocketClient *)socket didCloseWithError:(NSError *)error +{ + _connected = NO; + _appToAppSubscription = nil; + + if (_connectFailure) + { + if (error) + _connectFailure(error); + else + { + if (self.delegate && [self.delegate respondsToSelector:@selector(webAppSessionDidDisconnect:)]) + [self.delegate webAppSessionDidDisconnect:self]; + } + } + + _connectSuccess = nil; + _connectFailure = nil; + + [self disconnectFromWebApp]; +} + +- (BOOL) socket:(WebOSTVServiceSocketClient *)socket didReceiveMessage:(NSDictionary *)payload +{ + NSString *type = payload[@"type"]; + + if ([type isEqualToString:@"p2p"]) + { + NSString *fromAppId = payload[@"from"]; + + if (![fromAppId isEqualToString:self.fullAppId]) + return NO; + + id message = payload[@"payload"]; + + if ([message isKindOfClass:[NSDictionary class]]) + { + NSDictionary *messageJSON = (NSDictionary *)message; + + NSString *contentType = [messageJSON objectForKey:@"contentType"]; + NSRange contentTypeRange = [contentType rangeOfString:@"connectsdk."]; + + if (contentType && contentTypeRange.location != NSNotFound) + { + NSString *payloadKey = [contentType substringFromIndex:contentTypeRange.length]; + + if (!payloadKey || payloadKey.length == 0) + return NO; + + if ([payloadKey isEqualToString:@"media-error"]){ + [self handleMediaEvent:messageJSON]; + } + + id messagePayload = [messageJSON objectForKey:payloadKey]; + + if (!messagePayload) + return NO; + + if ([payloadKey isEqualToString:@"mediaEvent"]) + [self handleMediaEvent:messagePayload]; + else if ([payloadKey isEqualToString:@"mediaCommandResponse"]) + [self handleMediaCommandResponse:messagePayload]; + } else + { + [self handleMessage:messageJSON]; + } + } else if ([message isKindOfClass:[NSString class]]) + { + [self handleMessage:message]; + } + + return NO; + } + + return YES; +} + +#pragma mark - Subscription methods + +- (int)sendSubscription:(ServiceSubscription *)subscription type:(ServiceSubscriptionType)type payload:(id)payload toURL:(NSURL *)URL withId:(int)callId +{ + if (type == ServiceSubscriptionTypeUnsubscribe) + { + if (subscription == _playStateSubscription) + _playStateSubscription = nil; + else if (subscription == _messageSubscription) + _messageSubscription = nil; + else if (subscription == _webAppPinnedSubscription) + _webAppPinnedSubscription = nil; + } + + return -1; +} + +#pragma mark - Message handlers + +- (void) handleMediaEvent:(NSDictionary *)payload +{ + NSString *type = [payload objectForKey:@"type"]; + + if(type == nil){ + NSString *errorMessage = [payload objectForKey:@"error"]; + if(errorMessage){ + [_playStateSubscription.failureCalls enumerateObjectsUsingBlock:^(FailureBlock failure, NSUInteger idx, BOOL *stop) + { + failure([ConnectError generateErrorWithCode:ConnectStatusCodeTvError andDetails:errorMessage]); + }]; + } + } + else + if ([type isEqualToString:@"playState"]) + { + if (!_playStateSubscription) + return; + + NSString *playStateString = [payload objectForKey:@"playState"]; + MediaControlPlayState playState = [self parsePlayState:playStateString]; + + [_playStateSubscription.successCalls enumerateObjectsUsingBlock:^(id success, NSUInteger idx, BOOL *stop) + { + MediaPlayStateSuccessBlock mediaPlayStateSuccess = (MediaPlayStateSuccessBlock) success; + + if (mediaPlayStateSuccess) + mediaPlayStateSuccess(playState); + }]; + } +} + +- (void) handleMediaCommandResponse:(NSDictionary *)payload +{ + NSString *requestId = [payload objectForKey:@"requestId"]; + + ServiceCommand *command = [_activeCommands objectForKey:requestId]; + + if (!command) + return; + + NSString *error = [payload objectForKey:@"error"]; + + if (error) + { + if (command.callbackError) + command.callbackError([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:error]); + } else + { + if (command.callbackComplete) + command.callbackComplete(payload); + } + + [_activeCommands removeObjectForKey:requestId]; +} + +- (void) handleMessage:(id)message +{ + if (self.delegate && [self.delegate respondsToSelector:@selector(webAppSession:didReceiveMessage:)]) + [self.delegate webAppSession:self didReceiveMessage:message]; +} + +- (MediaControlPlayState) parsePlayState:(NSString *)playStateString +{ + MediaControlPlayState playState = MediaControlPlayStateUnknown; + + if ([playStateString isEqualToString:@"playing"]) + playState = MediaControlPlayStatePlaying; + else if ([playStateString isEqualToString:@"paused"]) + playState = MediaControlPlayStatePaused; + else if ([playStateString isEqualToString:@"idle"]) + playState = MediaControlPlayStateIdle; + else if ([playStateString isEqualToString:@"buffering"]) + playState = MediaControlPlayStateBuffering; + else if ([playStateString isEqualToString:@"finished"]) + playState = MediaControlPlayStateFinished; + + return playState; +} + +- (void) connectWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self connect:NO success:success failure:failure]; +} + +- (void) joinWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self connect:YES success:success failure:failure]; +} + +- (void) connect:(BOOL)joinOnly success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (self.socket && self.socket.socket.readyState == LGSR_CONNECTING) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"You have a connect request pending, please wait until it has finished"]); + + return; + } + + if (self.connected) + { + if (success) + success(nil); + + return; + } + + if (!_messageSubscription) + _messageSubscription = [ServiceSubscription subscriptionWithDelegate:nil target:nil payload:nil callId:-1]; + + __weak WebOSWebAppSession *weakSelf = self; + + _connectFailure = ^(NSError *error) { + if (weakSelf) + { + WebOSWebAppSession *strongSelf = weakSelf; + + [strongSelf disconnectFromWebApp]; + } + + if (failure) + { + if (!error) + error = [ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Unknown error connecting to web app"]; + failure(error); + } + }; + + __weak FailureBlock weakConnectFailure = _connectFailure; + + _connectSuccess = ^(id socketResponseObject) { + if (!weakSelf || !weakConnectFailure) + return; + + WebOSWebAppSession *strongSelf = weakSelf; + FailureBlock strongConnectFailure = weakConnectFailure; + + [strongSelf.service connectToWebApp:strongSelf joinOnly:joinOnly success:^(id connectResponseObject) + { + strongSelf.connected = YES; + + if (success) + success(nil); + } failure:strongConnectFailure]; + }; + + if (self.socket && self.socket.connected) + { + _connectSuccess(nil); + } else + { + _socket = [self createSocketWithService:self.service]; + _socket.delegate = self; + [_socket connect]; + } +} + +- (void)sendText:(NSString *)message success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (!message || message.length == 0) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"Cannot send an empty message"]); + + return; + } + + [self sendP2PMessage:message success:success failure:failure]; +} + +- (void)sendJSON:(NSDictionary *)message success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (!message || message.count == 0) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"Cannot send an empty message"]); + + return; + } + + [self sendP2PMessage:message success:success failure:failure]; +} + +- (void) sendP2PMessage:(id)message success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSDictionary *payload = @{ + @"type" : @"p2p", + @"to" : self.fullAppId, + @"payload" : message + }; + + if (self.connected) + { + [self.socket sendDictionaryOverSocket:payload]; + + if (success) + success(nil); + } else + { + [self connectWithSuccess:^(id responseObject) { + [self sendP2PMessage:message success:success failure:failure]; + + if (success) + success(nil); + } failure:failure]; + } +} + +- (void) disconnectFromWebApp +{ + _connected = NO; + + _connectSuccess = nil; + _connectFailure = nil; + + [_appToAppSubscription.failureCalls removeAllObjects]; + [_appToAppSubscription.successCalls removeAllObjects]; + _appToAppSubscription = nil; + + self.socket.delegate = nil; + [self.socket disconnectWithError:nil]; + _socket = nil; + + if (self.delegate && [self.delegate respondsToSelector:@selector(webAppSessionDidDisconnect:)]) + dispatch_on_main(^{ [self.delegate webAppSessionDidDisconnect:self]; }); +} + +- (void)closeWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + _activeCommands = [NSMutableDictionary new]; + + [_playStateSubscription unsubscribe]; + _playStateSubscription = nil; + + _messageSubscription = nil; + _webAppPinnedSubscription = nil; + + [self.service.webAppLauncher closeWebApp:self.launchSession success:success failure:failure]; +} + +- (void)pinWebApp:(NSString *)webAppId success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.service.webAppLauncher pinWebApp:webAppId success:success failure:failure]; +} + +- (void)unPinWebApp:(NSString *)webAppId success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.service.webAppLauncher unPinWebApp:webAppId success:success failure:failure]; +} + +- (void)isWebAppPinned:(NSString *)webAppId success:(WebAppPinStatusBlock)success failure:(FailureBlock)failure +{ + [self.service.webAppLauncher isWebAppPinned:webAppId success:success failure:failure]; +} + +- (ServiceSubscription *)subscribeIsWebAppPinned:(NSString*)webAppId success:(WebAppPinStatusBlock)success failure:(FailureBlock)failure +{ + _webAppPinnedSubscription = [self.service.webAppLauncher subscribeIsWebAppPinned:webAppId success:success failure:failure]; + return _webAppPinnedSubscription; +} + +#pragma mark - Media Player + +- (id ) mediaPlayer +{ + return self; +} + +- (CapabilityPriorityLevel) mediaPlayerPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void) displayImage:(NSURL *)imageURL iconURL:(NSURL *)iconURL title:(NSString *)title description:(NSString *)description mimeType:(NSString *)mimeType success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:imageURL mimeType:mimeType]; + mediaInfo.title = title; + mediaInfo.description = description; + ImageInfo *imageInfo = [[ImageInfo alloc] initWithURL:iconURL type:ImageTypeThumb]; + [mediaInfo addImage:imageInfo]; + + [self displayImageWithMediaInfo:mediaInfo success:^(MediaLaunchObject *mediaLanchObject) { + success(mediaLanchObject.session,mediaLanchObject.mediaControl); + } failure:failure]; +} + +- (void) displayImage:(MediaInfo *)mediaInfo + success:(MediaPlayerDisplaySuccessBlock)success + failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + + [self displayImage:mediaInfo.url iconURL:iconURL title:mediaInfo.title description:mediaInfo.description mimeType:mediaInfo.mimeType success:success failure:failure]; +} + +- (void) displayImageWithMediaInfo:(MediaInfo *)mediaInfo success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + + int requestIdNumber = [self getNextId]; + NSString *requestId = [NSString stringWithFormat:@"req%d", requestIdNumber]; + + NSDictionary *message = @{ + @"contentType" : @"connectsdk.mediaCommand", + @"mediaCommand" : @{ + @"type" : @"displayImage", + @"mediaURL" : ensureString(mediaInfo.url.absoluteString), + @"iconURL" : ensureString(iconURL.absoluteString), + @"title" : ensureString(mediaInfo.title), + @"description" : ensureString(mediaInfo.description), + @"mimeType" : ensureString(mediaInfo.mimeType), + @"requestId" : requestId + } + }; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:nil target:nil payload:nil]; + command.callbackComplete = ^(id responseObject) + { + MediaLaunchObject *launchObject = [[MediaLaunchObject alloc] initWithLaunchSession:self.launchSession andMediaControl:self.mediaControl]; + if(success){ + success(launchObject); + } + }; + command.callbackError = failure; + [_activeCommands setObject:command forKey:requestId]; + + [self sendJSON:message success:nil failure:failure]; +} + +- (void) playMedia:(NSURL *)mediaURL iconURL:(NSURL *)iconURL title:(NSString *)title description:(NSString *)description mimeType:(NSString *)mimeType shouldLoop:(BOOL)shouldLoop success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:mediaURL mimeType:mimeType]; + mediaInfo.title = title; + mediaInfo.description = description; + ImageInfo *imageInfo = [[ImageInfo alloc] initWithURL:iconURL type:ImageTypeThumb]; + [mediaInfo addImage:imageInfo]; + + [self playMediaWithMediaInfo:mediaInfo shouldLoop:shouldLoop success:^(MediaLaunchObject *mediaLanchObject) { + success(mediaLanchObject.session,mediaLanchObject.mediaControl); + } failure:failure]; +} + +- (void) playMedia:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + [self playMedia:mediaInfo.url iconURL:iconURL title:mediaInfo.title description:mediaInfo.description mimeType:mediaInfo.mimeType shouldLoop:shouldLoop success:success failure:failure]; +} + +- (void) playMediaWithMediaInfo:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + int requestIdNumber = [self getNextId]; + NSString *requestId = [NSString stringWithFormat:@"req%d", requestIdNumber]; + + NSMutableDictionary *const mediaCommand = [@{ + @"type" : @"playMedia", + @"mediaURL" : ensureString(mediaInfo.url.absoluteString), + @"iconURL" : ensureString(iconURL.absoluteString), + @"title" : ensureString(mediaInfo.title), + @"description" : ensureString(mediaInfo.description), + @"mimeType" : ensureString(mediaInfo.mimeType), + @"shouldLoop" : @(shouldLoop), + @"requestId" : requestId, + } mutableCopy]; + if (mediaInfo.subtitleInfo) { + mediaCommand[@"subtitles"] = [self subtitlesDictionaryFromSubtitleInfo:mediaInfo.subtitleInfo]; + } + + NSDictionary *message = @{@"contentType" : @"connectsdk.mediaCommand", + @"mediaCommand" : mediaCommand}; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:nil target:nil payload:nil]; + command.callbackComplete = ^(id responseObject) + { + MediaLaunchObject *launchObject = [[MediaLaunchObject alloc] initWithLaunchSession:self.launchSession andMediaControl:self.mediaControl andPlayListControl:self.playListControl]; + if(success){ + success(launchObject); + } + }; + command.callbackError = failure; + [_activeCommands setObject:command forKey:requestId]; + + [self sendJSON:message success:nil failure:failure]; +} + +- (void) closeMedia:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self closeWithSuccess:success failure:failure]; +} + +#pragma mark - Media Control + +- (id ) mediaControl +{ + return self; +} + +- (CapabilityPriorityLevel) mediaControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)seek:(NSTimeInterval)position success:(SuccessBlock)success failure:(FailureBlock)failure +{ + int requestIdNumber = [self getNextId]; + NSString *requestId = [NSString stringWithFormat:@"req%d", requestIdNumber]; + + NSDictionary *message = @{ + @"contentType" : @"connectsdk.mediaCommand", + @"mediaCommand" : @{ + @"type" : @"seek", + @"position" : @(position), + @"requestId" : requestId + } + }; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:nil target:nil payload:nil]; + command.callbackComplete = success; + command.callbackError = failure; + [_activeCommands setObject:command forKey:requestId]; + + [self sendJSON:message success:nil failure:failure]; +} + +- (void)getPositionWithSuccess:(MediaPositionSuccessBlock)success failure:(FailureBlock)failure +{ + int requestIdNumber = [self getNextId]; + NSString *requestId = [NSString stringWithFormat:@"req%d", requestIdNumber]; + + NSDictionary *message = @{ + @"contentType" : @"connectsdk.mediaCommand", + @"mediaCommand" : @{ + @"type" : @"getPosition", + @"requestId" : requestId + } + }; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:nil target:nil payload:nil]; + command.callbackComplete = ^(NSDictionary *responseObject) + { + NSString *positionString = [responseObject objectForKey:@"position"]; + NSTimeInterval position = 0; + + if (positionString && ![positionString isKindOfClass:[NSNull class]]) + position = [positionString intValue]; + + if (success) + success(position); + }; + command.callbackError = failure; + [_activeCommands setObject:command forKey:requestId]; + + [self sendJSON:message success:nil failure:failure]; +} + +- (void)getDurationWithSuccess:(MediaDurationSuccessBlock)success failure:(FailureBlock)failure +{ + int requestIdNumber = [self getNextId]; + NSString *requestId = [NSString stringWithFormat:@"req%d", requestIdNumber]; + + NSDictionary *message = @{ + @"contentType" : @"connectsdk.mediaCommand", + @"mediaCommand" : @{ + @"type" : @"getDuration", + @"requestId" : requestId + } + }; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:nil target:nil payload:nil]; + command.callbackComplete = ^(id responseObject) + { + NSString *durationString = [responseObject objectForKey:@"duration"]; + NSTimeInterval duration = 0; + + if (durationString && ![durationString isKindOfClass:[NSNull class]]) + duration = [durationString intValue]; + + if (success) + success(duration); + }; + command.callbackError = failure; + [_activeCommands setObject:command forKey:requestId]; + + [self sendJSON:message success:nil failure:failure]; +} + +- (void)getPlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + int requestIdNumber = [self getNextId]; + NSString *requestId = [NSString stringWithFormat:@"req%d", requestIdNumber]; + + NSDictionary *message = @{ + @"contentType" : @"connectsdk.mediaCommand", + @"mediaCommand" : @{ + @"type" : @"getPlayState", + @"requestId" : requestId + } + }; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:nil target:nil payload:nil]; + command.callbackComplete = ^(NSDictionary *responseObject) + { + NSString *playStateString = [responseObject objectForKey:@"playState"]; + MediaControlPlayState playState = [self parsePlayState:playStateString]; + + if (success) + success(playState); + }; + command.callbackError = failure; + [_activeCommands setObject:command forKey:requestId]; + + [self sendJSON:message success:nil failure:failure]; +} + +- (ServiceSubscription *)subscribePlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + if (!_playStateSubscription) + _playStateSubscription = [ServiceSubscription subscriptionWithDelegate:nil target:nil payload:nil callId:-1]; + + if (!self.connected) + [self connectWithSuccess:nil failure:failure]; + + if (![_playStateSubscription.successCalls containsObject:success]) + [_playStateSubscription addSuccess:success]; + + if (![_playStateSubscription.failureCalls containsObject:failure]) + [_playStateSubscription addFailure:failure]; + + return _playStateSubscription; +} + +- (ServiceSubscription *)subscribeMediaInfoWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +#pragma mark - Playlist Control + +- (id ) playListControl +{ + return self; +} + +- (CapabilityPriorityLevel)playListControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)playNextWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure { + NSMutableDictionary *mediaCommand = [NSMutableDictionary dictionaryWithDictionary:@{ + @"type" : @"playNext" + }]; + [self sendMediaCommand:mediaCommand success:success failure:failure]; +} + +- (void)playPreviousWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure{ + NSMutableDictionary *mediaCommand = [NSMutableDictionary dictionaryWithDictionary:@{ + @"type" : @"playPrevious" + }]; + [self sendMediaCommand:mediaCommand success:success failure:failure]; +} + +- (void)jumpToTrackWithIndex:(NSInteger)index success:(SuccessBlock)success failure:(FailureBlock)failure{ + NSMutableDictionary *mediaCommand = [NSMutableDictionary dictionaryWithDictionary:@{ + @"type" : @"jumpToTrack", + @"index" : [NSNumber numberWithInteger:index] + }]; + [self sendMediaCommand:mediaCommand success:success failure:failure]; +} + +- (void)sendMediaCommand:(NSMutableDictionary *)mediaCommand success:(SuccessBlock)success failure:(FailureBlock)failure{ + int requestIdNumber = [self getNextId]; + NSString *requestId = [NSString stringWithFormat:@"req%d", requestIdNumber]; + [mediaCommand setObject:requestId forKey:@"requestId"]; + NSDictionary *message = [self messageForMediaCommand:mediaCommand]; + ServiceCommand *command = [ServiceCommand commandWithDelegate:nil target:nil payload:nil]; + command.callbackComplete = ^(id responseObject) + { + if (success) + success(responseObject); + }; + command.callbackError = failure; + [_activeCommands setObject:command forKey:requestId]; + + [self sendJSON:message success:nil failure:failure]; +} + +- (NSDictionary *)messageForMediaCommand:(NSDictionary *)mediaCommand { + NSDictionary *message = @{ + @"contentType" : @"connectsdk.mediaCommand", + @"mediaCommand" : mediaCommand + }; + return message; +} + +#pragma mark - Private Methods + +- (WebOSTVServiceSocketClient *)createSocketWithService:(WebOSTVService *)service { + return [[WebOSTVServiceSocketClient alloc] initWithService:service]; +} + +- (nonnull NSDictionary *)subtitlesDictionaryFromSubtitleInfo:(nonnull SubtitleInfo *)subtitleInfo { + NSMutableDictionary *subtitleDict = [@{ + @"id": kSubtitleTrackIdentifier, + @"source": subtitleInfo.url.absoluteString, + } mutableCopy]; + [subtitleDict setNullableObject:subtitleInfo.language forKey:@"language"]; + [subtitleDict setNullableObject:subtitleInfo.label forKey:@"label"]; + + return @{ + @"tracks": @[[subtitleDict copy]], + @"enabled": kSubtitleTrackIdentifier, + }; +} + +@end diff --git a/core/Services/Sessions/WebOSWebAppSession_Private.h b/core/Services/Sessions/WebOSWebAppSession_Private.h new file mode 100644 index 00000000..d7479881 --- /dev/null +++ b/core/Services/Sessions/WebOSWebAppSession_Private.h @@ -0,0 +1,28 @@ +// +// WebOSWebAppSession_Private.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-06-19. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "WebOSWebAppSession.h" + +@interface WebOSWebAppSession () + +/// Creates a new @c WebOSTVServiceSocketClient instance with the @c service. +- (WebOSTVServiceSocketClient *)createSocketWithService:(WebOSTVService *)service; + +@end diff --git a/core/Services/WebOSTVService.h b/core/Services/WebOSTVService.h new file mode 100644 index 00000000..24dc976c --- /dev/null +++ b/core/Services/WebOSTVService.h @@ -0,0 +1,117 @@ +// +// WebOSService.h +// Connect SDK +// +// Created by Jeremy White on 12/2/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#define kConnectSDKWebOSTVServiceId @"webOS TV" + +#import +#import "DeviceService.h" +#import "Launcher.h" +#import "WebOSTVServiceConfig.h" +#import "MediaPlayer.h" +#import "VolumeControl.h" +#import "TVControl.h" +#import "KeyControl.h" +#import "WebOSTVServiceMouse.h" +#import "MouseControl.h" +#import "PowerControl.h" +#import "MediaControl.h" +#import "WebAppLauncher.h" +#import "ToastControl.h" +#import "ExternalInputControl.h" +#import "TextInputControl.h" +#import "PlayListControl.h" +#import "ScreenMirroringControl.h" +#import "RemoteCameraControl.h" + +@class WebOSWebAppSession; +@class WebOSTVServiceSocketClient; + +@interface WebOSTVService : DeviceService + +// @cond INTERNAL +typedef enum { + LAUNCH = 0, + LAUNCH_WEBAPP, + APP_TO_APP, + CONTROL_AUDIO, + CONTROL_INPUT_MEDIA_PLAYBACK +} WebOSTVServiceOpenPermission; + +#define kWebOSTVServiceOpenPermissions @[@"LAUNCH", @"LAUNCH_WEBAPP", @"APP_TO_APP", @"CONTROL_AUDIO", @"CONTROL_INPUT_MEDIA_PLAYBACK"] + +typedef enum { + CONTROL_POWER = 0, + READ_INSTALLED_APPS, + CONTROL_DISPLAY, + CONTROL_INPUT_JOYSTICK, + CONTROL_INPUT_MEDIA_RECORDING, + CONTROL_INPUT_TV, + READ_INPUT_DEVICE_LIST, + READ_NETWORK_STATE, + READ_TV_CHANNEL_LIST, + WRITE_NOTIFICATION_TOAST +} WebOSTVServiceProtectedPermission; + +#define kWebOSTVServiceProtectedPermissions @[@"CONTROL_POWER", @"READ_INSTALLED_APPS", @"CONTROL_DISPLAY", @"CONTROL_INPUT_JOYSTICK", @"CONTROL_INPUT_MEDIA_RECORDING", @"CONTROL_INPUT_TV", @"READ_INPUT_DEVICE_LIST", @"READ_NETWORK_STATE", @"READ_TV_CHANNEL_LIST", @"WRITE_NOTIFICATION_TOAST"] + +typedef enum { + CONTROL_INPUT_TEXT = 0, + CONTROL_MOUSE_AND_KEYBOARD, + READ_CURRENT_CHANNEL, + READ_RUNNING_APPS +} WebOSTVServicePersonalActivityPermission; + +#define kWebOSTVServicePersonalActivityPermissions @[@"CONTROL_INPUT_TEXT", @"CONTROL_MOUSE_AND_KEYBOARD", @"READ_CURRENT_CHANNEL", @"READ_RUNNING_APPS"] + +@property (nonatomic, strong, readonly) WebOSTVServiceSocketClient *socket; +@property (nonatomic, strong, readonly) WebOSTVServiceMouse *mouseSocket; +/// The base class' @c serviceConfig property downcast to +/// @c WebOSTVServiceConfig class if possible, or nil. +@property (nonatomic, strong, readonly) WebOSTVServiceConfig *webOSTVServiceConfig; +@property (nonatomic, strong) NSArray *permissions; +@property (nonatomic, readonly) NSDictionary *appToAppIdMappings; +@property (nonatomic, readonly) NSDictionary *webAppSessions; +// @endcond + +#pragma mark - Web app & app to app + +// @cond INTERNAL +- (void) connectToWebApp:(WebOSWebAppSession *)webAppSession joinOnly:(BOOL)joinOnly success:(SuccessBlock)success failure:(FailureBlock)failure; +// @endcond + +#pragma mark - Native app to app + +// @cond INTERNAL +- (void) connectToApp:(NSString *)appId success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure; +- (void) joinApp:(NSString *)appId success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure; +- (void) connectToApp:(WebOSWebAppSession *)webAppSession joinOnly:(BOOL)joinOnly success:(SuccessBlock)success failure:(FailureBlock)failure; +// @endcond + +#pragma mark - System Info + +// @cond INTERNAL +typedef void (^ ServiceListSuccessBlock)(NSArray *serviceList); +typedef void (^ SystemInfoSuccessBlock)(NSArray *featureList); + +- (void)getServiceListWithSuccess:(ServiceListSuccessBlock)success failure:(FailureBlock)failure; +- (void)getSystemInfoWithSuccess:(SystemInfoSuccessBlock)success failure:(FailureBlock)failure; +// @endcond + +@end diff --git a/core/Services/WebOSTVService.m b/core/Services/WebOSTVService.m new file mode 100644 index 00000000..f86887ab --- /dev/null +++ b/core/Services/WebOSTVService.m @@ -0,0 +1,2550 @@ +// +// WebOSService.m +// Connect SDK +// +// Created by Jeremy White on 12/2/13. +// Copyright (c) 2014 LG Electronics. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "WebOSTVService_Private.h" +#import "ConnectError.h" +#import "DiscoveryManager.h" +#import "ServiceAsyncCommand.h" +#import "WebOSWebAppSession.h" +#import "WebOSTVServiceSocketClient.h" +#import "CTGuid.h" +#import "CommonMacros.h" +#import "NSMutableDictionary+NilSafe.h" +#import "NSObject+FeatureNotSupported_Private.h" +#import "CTXMLReader.h" +#import "RemoteCameraService.h" +#import "ScreenMirroringService.h" + +#define kKeyboardEnter @"\x1b ENTER \x1b" +#define kKeyboardDelete @"\x1b DELETE \x1b" + +@interface WebOSTVService () +{ + NSArray *_permissions; + + NSMutableDictionary *_webAppSessions; + NSMutableDictionary *_appToAppIdMappings; + + NSTimer *_pairingTimer; + UIAlertController *_pairingAlert; + + NSMutableArray *_keyboardQueue; + BOOL _keyboardQueueProcessing; + + BOOL _mouseInit; + UIAlertController *_pinAlertView; + + __weak id _remoteCameraDelegate; + __weak id _screenMirroringDelegate; +} + +@end + +@implementation WebOSTVService + +@synthesize serviceDescription = _serviceDescription, pairingType = _pairingType; + +#pragma mark - Setup + +- (instancetype) initWithServiceConfig:(ServiceConfig *)serviceConfig +{ + self = [super init]; + + if (self) + { + [self setServiceConfig:serviceConfig]; + } + + return self; +} + +#pragma mark - Getters & Setters + +- (void) setPairingType:(DeviceServicePairingType)pairingType { + _pairingType = pairingType; +} + +- (DeviceServicePairingType)pairingType{ + DeviceServicePairingType pairingType = DeviceServicePairingTypeNone; + if ([DiscoveryManager sharedManager].pairingLevel == DeviceServicePairingLevelOn) + { + pairingType = _pairingType!=DeviceServicePairingTypeNone ? _pairingType : DeviceServicePairingTypeFirstScreen; + } + return pairingType; +} + +- (WebOSTVServiceConfig *)webOSTVServiceConfig { + return ([self.serviceConfig isKindOfClass:[WebOSTVServiceConfig class]] ? + (WebOSTVServiceConfig *)self.serviceConfig : + nil); +} + +- (id)serviceCommandDelegate { + return _serviceCommandDelegate ?: self.socket; +} + +#pragma mark - Inherited methods + +- (void) setServiceConfig:(ServiceConfig *)serviceConfig +{ + const BOOL oldServiceConfigHasKey = (self.webOSTVServiceConfig.clientKey != nil); + if ([serviceConfig isKindOfClass:[WebOSTVServiceConfig class]]) + { + const BOOL newServiceConfigHasKey = (((WebOSTVServiceConfig *)serviceConfig).clientKey != nil); + const BOOL wouldLoseKey = oldServiceConfigHasKey && !newServiceConfigHasKey; + _assert_state(!wouldLoseKey, @"Losing important data!"); + + [super setServiceConfig:serviceConfig]; + } else + { + _assert_state(!oldServiceConfigHasKey, @"Losing important data!"); + + [super setServiceConfig:[[WebOSTVServiceConfig alloc] initWithServiceConfig:serviceConfig]]; + } +} + +- (void) setServiceDescription:(ServiceDescription *)serviceDescription +{ + _serviceDescription = serviceDescription; + + if (!self.serviceConfig.UUID) + self.serviceConfig.UUID = serviceDescription.UUID; + + if (!_serviceDescription.locationResponseHeaders) + return; + + NSString *serverInfo = [_serviceDescription.locationResponseHeaders objectForKey:@"Server"]; + NSString *systemOS = [[serverInfo componentsSeparatedByString:@" "] firstObject]; + NSString *systemVersion = [[systemOS componentsSeparatedByString:@"/"] lastObject]; + + _serviceDescription.version = systemVersion; + + [self updateCapabilities]; +} + +- (DeviceService *)dlnaService +{ + NSDictionary *allDevices = [[DiscoveryManager sharedManager] allDevices]; + ConnectableDevice *device; + DeviceService *service; + + if (allDevices && allDevices.count > 0) + device = [allDevices objectForKey:self.serviceDescription.address]; + + if (device) + service = [device serviceWithName:@"DLNA"]; + + return service; +} + +- (void) updateCapabilities +{ + NSArray *capabilities = [NSArray array]; + + if ([DiscoveryManager sharedManager].pairingLevel == DeviceServicePairingLevelOn) + { + capabilities = [capabilities arrayByAddingObjectsFromArray:@[ + kKeyControlSendKeyCode, + kKeyControlUp, + kKeyControlDown, + kKeyControlLeft, + kKeyControlRight, + kKeyControlHome, + kKeyControlBack, + kKeyControlOK + ]]; + + capabilities = [capabilities arrayByAddingObjectsFromArray:kMouseControlCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:kTextInputControlCapabilities]; + capabilities = [capabilities arrayByAddingObject:kPowerControlOff]; + capabilities = [capabilities arrayByAddingObjectsFromArray:kMediaPlayerCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:kLauncherCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:kTVControlCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:kExternalInputControlCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:kVolumeControlCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:kToastControlCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:kMediaControlCapabilities]; + } else + { + capabilities = [capabilities arrayByAddingObjectsFromArray:kMediaPlayerCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:kMediaControlCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:kVolumeControlCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:@[ + kLauncherApp, + kLauncherAppParams, + kLauncherAppStore, + kLauncherAppStoreParams + kLauncherAppClose, + kLauncherBrowser, + kLauncherBrowserParams, + kLauncherHulu, + kLauncherNetflix, + kLauncherNetflixParams, + kLauncherYouTube, + kLauncherYouTubeParams, + kLauncherAppState, + kLauncherAppStateSubscribe + ]]; + } + + if (_serviceDescription && _serviceDescription.version) + { + const BOOL isVersion400 = [_serviceDescription.version rangeOfString:@"4.0.0"].location != NSNotFound; + const BOOL isVersion401 = [_serviceDescription.version rangeOfString:@"4.0.1"].location != NSNotFound; + const BOOL isLegacyVersion = isVersion400 || isVersion401; + if (!isLegacyVersion) + { + capabilities = [capabilities arrayByAddingObjectsFromArray:kWebAppLauncherCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:kMediaControlCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:kPlayListControlCapabilities]; + capabilities = [capabilities arrayByAddingObjectsFromArray:@[kMediaPlayerPlayPlaylist,kMediaPlayerLoop]]; + capabilities = [capabilities arrayByAddingObject:kMediaPlayerSubtitleWebVTT]; + } else + { + capabilities = [capabilities arrayByAddingObjectsFromArray:@[ + kWebAppLauncherLaunch, + kWebAppLauncherLaunchParams, + + kMediaControlPlay, + kMediaControlPause, + kMediaControlStop, + kMediaControlSeek, + kMediaControlPosition, + kMediaControlDuration, + kMediaControlPlayState, + + kWebAppLauncherClose + ]]; + + if (self.dlnaService) { + capabilities = [capabilities arrayByAddingObject:kMediaPlayerSubtitleSRT]; + } + } + + if (_serviceDescription.locationXML) { + NSError *locationXMLParseError; + NSDictionary *locationXMLDic = [CTXMLReader dictionaryForXMLString:_serviceDescription.locationXML error:&locationXMLParseError]; + + if (!locationXMLParseError) { + NSDictionary *deviceDic = [[locationXMLDic objectForKey:@"root"] objectForKey:@"device"]; + NSString *appCastingFeature = [[deviceDic objectForKey:@"supportAppcastingFeatures"] objectForKey:@"text"]; + + if (!appCastingFeature) { + NSString *appCasting = [[deviceDic objectForKey:@"appCasting"] objectForKey:@"text"]; + if ([@"support" isEqualToString:appCasting]) { + capabilities = [capabilities arrayByAddingObject:kScreenMirroringControlScreenMirroring]; + } + } else { + if ([appCastingFeature rangeOfString:@"mirroring"].location != NSNotFound) { + capabilities = [capabilities arrayByAddingObject:kScreenMirroringControlScreenMirroring]; + } + if ([appCastingFeature rangeOfString:@"remote-camera"].location != NSNotFound) { + capabilities = [capabilities arrayByAddingObject:kRemoteCameraControlRemoteCamera]; + } + } + } + } + } else { + capabilities = [capabilities arrayByAddingObject:kMediaPlayerSubtitleWebVTT]; + } + + [self setCapabilities:capabilities]; +} + ++ (NSDictionary *) discoveryParameters +{ + return @{ + @"serviceId": kConnectSDKWebOSTVServiceId, + @"ssdp":@{ + @"filter":@"urn:lge-com:service:webos-second-screen:1" + } + }; +} + +- (BOOL) isConnectable +{ + return YES; +} + +- (BOOL) connected +{ + if ([DiscoveryManager sharedManager].pairingLevel == DeviceServicePairingLevelOn) + return self.socket.connected && (self.webOSTVServiceConfig.clientKey != nil); + else + return self.socket.connected; +} + +- (void) connect +{ + if (!self.socket) + { + _socket = [[WebOSTVServiceSocketClient alloc] initWithService:self]; + _socket.delegate = self; + } + + if (!self.connected) + [self.socket connect]; +} + +- (void) disconnect +{ + [self disconnectWithError:nil]; +} + +- (void) disconnectWithError:(NSError *)error +{ + [self.socket disconnectWithError:error]; + + [_webAppSessions enumerateKeysAndObjectsUsingBlock:^(id key, WebOSWebAppSession *session, BOOL *stop) { + [session disconnectFromWebApp]; + }]; + + _webAppSessions = [NSMutableDictionary new]; +} + +#pragma mark - Initial connection & pairing + +- (BOOL) requiresPairing +{ + return [DiscoveryManager sharedManager].pairingLevel == DeviceServicePairingLevelOn; +} + +#pragma mark - Paring alert + +-(void) showAlert +{ + NSString *title = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Pair_Title" value:@"Pairing with device" table:@"ConnectSDK"]; + NSString *message = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Pair_Request" value:@"Please confirm the connection on your device" table:@"ConnectSDK"]; + NSString *ok = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Pair_OK" value:@"OK" table:@"ConnectSDK"]; + NSString *cancel = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Pair_Cancel" value:@"Cancel" table:@"ConnectSDK"]; + + _pairingAlert = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + if(self.pairingType == DeviceServicePairingTypePinCode || self.pairingType == DeviceServicePairingTypeMixed){ + // Add a text field to the alert controller + [_pairingAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.keyboardType = UIKeyboardTypeNumberPad; + }]; + message = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Pair_Request_Pin" value:@"Please enter the pin code" table:@"ConnectSDK"]; + } + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancel + style:UIAlertActionStyleCancel + handler:^(UIAlertAction *action) { + [self disconnect]; + }]; + + UIAlertAction *okAction = [UIAlertAction actionWithTitle:ok + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + if(self.pairingType == DeviceServicePairingTypePinCode || self.pairingType == DeviceServicePairingTypeMixed){ + UITextField *textField = _pairingAlert.textFields.firstObject; + NSString *pairingCode = textField.text; + [self sendPairingKey:pairingCode success:nil failure:nil]; + } + }]; + + // Add the actions to the alert controller + [_pairingAlert addAction:cancelAction]; + [_pairingAlert addAction:okAction]; + dispatch_on_main(^{ + UIWindow *window = [UIApplication sharedApplication].keyWindow; + UIViewController *rootViewController = window.rootViewController; + [rootViewController presentViewController: _pairingAlert animated:YES completion:nil]; + }); +} + +-(void) showAlertWithTitle:(NSString *)title andMessage:(NSString *)message +{ + NSString *alertTitle = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Pair_Title" value:title table:@"ConnectSDK"]; + NSString *alertMessage = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Pair_Request" value:message table:@"ConnectSDK"]; + NSString *ok = [[NSBundle mainBundle] localizedStringForKey:@"Connect_SDK_Pair_OK" value:@"OK" table:@"ConnectSDK"]; + if(!_pinAlertView){ + _pinAlertView = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction *okAction = [UIAlertAction actionWithTitle:ok + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + }]; + + // Add the actions to the alert controller + [_pinAlertView addAction:okAction]; + } + dispatch_on_main(^{ + UIWindow *window = [UIApplication sharedApplication].keyWindow; + UIViewController *rootViewController = window.rootViewController; + [rootViewController presentViewController: _pinAlertView animated:YES completion:nil]; + }); +} + +-(void)dismissPinAlertView{ + if (_pinAlertView) { + [_pinAlertView dismissViewControllerAnimated:NO completion:^{ + }]; + } +} + +#pragma mark - WebOSTVServiceSocketClientDelegate + +- (void) socketWillRegister:(WebOSTVServiceSocketClient *)socket +{ + _pairingTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(showAlert) userInfo:nil repeats:NO]; +} + +- (void) socket:(WebOSTVServiceSocketClient *)socket registrationFailed:(NSError *)error +{ + if (_pairingAlert) + dispatch_on_main(^{ + [_pairingAlert dismissViewControllerAnimated:NO completion:^{ + [self disconnect]; + }]; + }); + + if (self.delegate && [self.delegate respondsToSelector:@selector(deviceService:pairingFailedWithError:)]) + dispatch_on_main(^{ [self.delegate deviceService:self pairingFailedWithError:error]; }); + + [self disconnect]; +} + +- (void) socketDidConnect:(WebOSTVServiceSocketClient *)socket +{ + [_pairingTimer invalidate]; + + if (_pairingAlert) + dispatch_on_main(^{ + [_pairingAlert dismissViewControllerAnimated:YES completion:^{ + if(self.pairingType == DeviceServicePairingTypePinCode || self.pairingType == DeviceServicePairingTypeMixed){ + UITextField *textField = _pairingAlert.textFields.firstObject; + NSString *pairingCode = textField.text; + [self sendPairingKey:pairingCode success:nil failure:nil]; + } + }]; + }); + + if ([self.delegate respondsToSelector:@selector(deviceServicePairingSuccess:)]) + dispatch_on_main(^{ [self.delegate deviceServicePairingSuccess:self]; }); + + if ([self.delegate respondsToSelector:@selector(deviceServiceConnectionSuccess:)]) + dispatch_on_main(^{ [self.delegate deviceServiceConnectionSuccess:self]; }); +} + +- (void) socket:(WebOSTVServiceSocketClient *)socket didFailWithError:(NSError *)error +{ + if (_pairingAlert) + dispatch_on_main(^{ + [_pairingAlert dismissViewControllerAnimated:YES completion:^{ + [self disconnect]; + }]; + }); + + if ([self.delegate respondsToSelector:@selector(deviceService:didFailConnectWithError:)]) + dispatch_on_main(^{ [self.delegate deviceService:self didFailConnectWithError:error]; }); +} + +- (void) socket:(WebOSTVServiceSocketClient *)socket didCloseWithError:(NSError *)error +{ + if ([self.delegate respondsToSelector:@selector(deviceService:disconnectedWithError:)]) + dispatch_on_main(^{ [self.delegate deviceService:self disconnectedWithError:error]; }); +} + +#pragma mark - Helper methods + +- (NSArray *)permissions +{ + if (_permissions) + return _permissions; + + NSMutableArray *defaultPermissions = [[NSMutableArray alloc] init]; + [defaultPermissions addObjectsFromArray:kWebOSTVServiceOpenPermissions]; + + if ([DiscoveryManager sharedManager].pairingLevel == DeviceServicePairingLevelOn) + { + [defaultPermissions addObjectsFromArray:kWebOSTVServiceProtectedPermissions]; + [defaultPermissions addObjectsFromArray:kWebOSTVServicePersonalActivityPermissions]; + } + + return [NSArray arrayWithArray:defaultPermissions]; +} + +- (void)setPermissions:(NSArray *)permissions +{ + _permissions = permissions; + + if (self.webOSTVServiceConfig.clientKey) + { + self.webOSTVServiceConfig.clientKey = nil; + + if (self.connected) + { + NSError *error = [ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Permissions changed -- you will need to re-pair to the TV."]; + [self disconnectWithError:error]; + } + } +} + ++ (ChannelInfo *)channelInfoFromDictionary:(NSDictionary *)info +{ + ChannelInfo *channelInfo = [[ChannelInfo alloc] init]; + channelInfo.id = [info objectForKey:@"channelId"]; + channelInfo.name = [info objectForKey:@"channelName"]; + channelInfo.number = [info objectForKey:@"channelNumber"]; + channelInfo.majorNumber = [[info objectForKey:@"majorNumber"] intValue]; + channelInfo.minorNumber = [[info objectForKey:@"minorNumber"] intValue]; + channelInfo.rawData = [info copy]; + + return channelInfo; +} + ++ (AppInfo *)appInfoFromDictionary:(NSDictionary *)info +{ + AppInfo *appInfo = [[AppInfo alloc] init]; + appInfo.name = [info objectForKey:@"title"]; + appInfo.id = [info objectForKey:@"id"]; + appInfo.rawData = [info copy]; + + return appInfo; +} + ++ (ExternalInputInfo *)externalInputInfoFromDictionary:(NSDictionary *)info +{ + ExternalInputInfo *externalInputInfo = [[ExternalInputInfo alloc] init]; + externalInputInfo.name = [info objectForKey:@"label"]; + externalInputInfo.id = [info objectForKey:@"id"]; + externalInputInfo.connected = [[info objectForKey:@"connected"] boolValue]; + externalInputInfo.iconURL = [NSURL URLWithString:[info objectForKey:@"icon"]]; + externalInputInfo.rawData = [info copy]; + + return externalInputInfo; +} + +#pragma mark - Launcher + +- (id )launcher +{ + return self; +} + +- (CapabilityPriorityLevel) launcherPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)getAppListWithSuccess:(AppListSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://com.webos.applicationManager/listApps"]; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = ^(NSDictionary *responseDic) + { + NSArray *foundApps = [responseDic objectForKey:@"apps"]; + NSMutableArray *appList = [[NSMutableArray alloc] init]; + + [foundApps enumerateObjectsUsingBlock:^(NSDictionary *appInfo, NSUInteger idx, BOOL *stop) + { + [appList addObject:[WebOSTVService appInfoFromDictionary:appInfo]]; + }]; + + if (success) + success(appList); + }; + command.callbackError = failure; + [command send]; +} + +- (void)launchApp:(NSString *)appId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self launchApplication:appId withParams:nil success:success failure:failure]; +} + +- (void)launchApplication:(NSString *)appId withParams:(NSDictionary *)params success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://system.launcher/launch"]; + + NSMutableDictionary *payload = [NSMutableDictionary new]; + + [payload setValue:appId forKey:@"id"]; + + if (params) { + [payload setValue:params forKey:@"params"]; + + NSString *contentId = [params objectForKey:@"contentId"]; + + if (contentId) + [payload setValue:contentId forKey:@"contentId"]; + } + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.socket target:URL payload:payload]; + command.callbackComplete = ^(NSDictionary *responseObject) + { + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:appId]; + launchSession.sessionId = [responseObject objectForKey:@"sessionId"]; + launchSession.sessionType = LaunchSessionTypeApp; + launchSession.service = self; + launchSession.rawData = [responseObject copy]; + + if (success) + success(launchSession); + }; + command.callbackError = failure; + [command send]; +} + +- (void)launchAppWithInfo:(AppInfo *)appInfo success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self launchApp:appInfo.id success:success failure:failure]; +} + +- (void)launchAppWithInfo:(AppInfo *)appInfo params:(NSDictionary *)params success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self launchApplication:appInfo.id withParams:params success:success failure:failure]; +} + +- (void) launchAppStore:(NSString *)appId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + AppInfo *appInfo = [AppInfo appInfoForId:@"com.webos.app.discovery"]; + appInfo.name = @"LG Store"; + + NSDictionary *params; + + if (appId && appId.length > 0) + { + NSString *query = [NSString stringWithFormat:@"category/GAME_APPS/%@", appId]; + params = @{ @"query" : query }; + } + + [self launchAppWithInfo:appInfo params:params success:success failure:failure]; +} + +- (void)launchBrowser:(NSURL *)target success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://system.launcher/open"]; + NSDictionary *params = @{ @"target" : target.absoluteString }; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.socket target:URL payload:params]; + command.callbackComplete = ^(NSDictionary * responseObject) + { + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:[responseObject objectForKey:@"id"]]; + launchSession.sessionId = [responseObject objectForKey:@"sessionId"]; + launchSession.sessionType = LaunchSessionTypeApp; + launchSession.service = self; + launchSession.rawData = [responseObject copy]; + + if (success) + success(launchSession); + }; + command.callbackError = failure; + [command send]; +} + +- (void)launchHulu:(NSString *)contentId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + NSDictionary *params = @{ @"hulu" : contentId }; + + [self launchApplication:@"hulu" withParams:params success:success failure:failure]; +} + +- (void)launchNetflix:(NSString *)contentId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + NSString *netflixContentId = [NSString stringWithFormat:@"m=http%%3A%%2F%%2Fapi.netflix.com%%2Fcatalog%%2Ftitles%%2Fmovies%%2F%@&source_type=4", contentId]; + + NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; + [params setValue:netflixContentId forKey:@"contentId"]; + + [self launchApplication:@"netflix" withParams:params success:success failure:failure]; +} + +- (void)launchYouTube:(NSString *)contentId success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self.launcher launchYouTube:contentId startTime:0.0 success:success failure:failure]; +} + +- (void) launchYouTube:(NSString *)contentId startTime:(float)startTime success:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + NSDictionary *params; + + if (contentId && contentId.length > 0) + { + if (startTime < 0.0) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"Start time may not be negative"]); + + return; + } + + params = @{ + @"contentId" : [NSString stringWithFormat:@"%@&pairingCode=%@&t=%.1f", contentId, [[CTGuid randomGuid] stringValue], startTime] + }; + } + + [self launchApplication:@"youtube.leanback.v4" withParams:params success:success failure:failure]; +} + +- (void) connectToApp:(NSString *)appId success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:appId]; + launchSession.service = self; + launchSession.sessionType = LaunchSessionTypeApp; + + WebOSWebAppSession *webAppSession = [self webAppSessionForLaunchSession:launchSession]; + + [self connectToApp:webAppSession joinOnly:NO success:^(id responseObject) + { + if (success) + success(webAppSession); + } failure:failure]; +} + +- (void) joinApp:(NSString *)appId success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:appId]; + launchSession.service = self; + launchSession.sessionType = LaunchSessionTypeApp; + + WebOSWebAppSession *webAppSession = [self webAppSessionForLaunchSession:launchSession]; + + [self connectToApp:webAppSession joinOnly:YES success:^(id responseObject) + { + if (success) + success(webAppSession); + } failure:failure]; +} + +- (void) connectToApp:(WebOSWebAppSession *)webAppSession joinOnly:(BOOL)joinOnly success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self connectToWebApp:webAppSession joinOnly:joinOnly success:success failure:failure]; +} + +- (ServiceSubscription *)subscribeRunningAppWithSuccess:(AppInfoSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://com.webos.applicationManager/getForegroundAppInfo"]; + + ServiceSubscription *subscription = [self.socket addSubscribe:URL payload:nil success:^(NSDictionary *responseObject) + { + AppInfo *appInfo = [[AppInfo alloc] init]; + appInfo.id = [responseObject objectForKey:@"appId"]; + appInfo.rawData = [responseObject copy]; + + if (success) + success(appInfo); + } failure:failure]; + + return subscription; +} + +- (void)getRunningAppWithSuccess:(AppInfoSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://com.webos.applicationManager/getForegroundAppInfo"]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = ^(NSDictionary *responseObject) + { + AppInfo *appInfo = [[AppInfo alloc] init]; + appInfo.id = [responseObject objectForKey:@"appId"]; + appInfo.name = [responseObject objectForKey:@"appName"]; + appInfo.rawData = [responseObject copy]; + + if (success) + success(appInfo); + }; + command.callbackError = failure; + [command send]; +} + +- (void)getAppState:(LaunchSession *)launchSession success:(AppStateSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://system.launcher/getAppState"]; + + NSMutableDictionary *params = [NSMutableDictionary new]; + if (launchSession && launchSession.appId) [params setValue:launchSession.appId forKey:@"appId"]; + if (launchSession && launchSession.sessionId) [params setValue:launchSession.sessionId forKey:@"sessionId"]; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.socket target:URL payload:params]; + command.callbackComplete = ^(NSDictionary * responseObject) + { + BOOL running = [[responseObject objectForKey:@"running"] boolValue]; + BOOL visible = [[responseObject objectForKey:@"visible"] boolValue]; + + if (success) + success(running, visible); + }; + command.callbackError = failure; + [command send]; +} + +- (ServiceSubscription *)subscribeAppState:(LaunchSession *)launchSession success:(AppStateSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://system.launcher/getAppState"]; + + NSMutableDictionary *params = [NSMutableDictionary new]; + if (launchSession && launchSession.appId) [params setValue:launchSession.appId forKey:@"appId"]; + if (launchSession && launchSession.sessionId) [params setValue:launchSession.sessionId forKey:@"sessionId"]; + + ServiceSubscription *subscription = [self.socket addSubscribe:URL payload:params success:^(NSDictionary *responseObject) + { + BOOL running = [[responseObject objectForKey:@"running"] boolValue]; + BOOL visible = [[responseObject objectForKey:@"visible"] boolValue]; + + if (success) + success(running, visible); + } failure:failure]; + + return subscription; +} + +- (void)closeApp:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://system.launcher/close"]; + + NSMutableDictionary *payload = [NSMutableDictionary new]; + if (launchSession.appId) [payload setValue:launchSession.appId forKey:@"id"]; // yes, this is id not appId (groan) + if (launchSession.sessionId) [payload setValue:launchSession.sessionId forKey:@"sessionId"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:payload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +#pragma mark - External Input Control + +- (id)externalInputControl +{ + return self; +} + +- (CapabilityPriorityLevel)externalInputControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)launchInputPickerWithSuccess:(AppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self launchApp:@"com.webos.app.inputpicker" success:success failure:^(NSError *error) { + [self launchApp:@"com.webos.app.inputmgr" success:success failure:failure]; + }]; +} + +- (void)closeInputPicker:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self.launcher closeApp:launchSession success:success failure:failure]; +} + +- (void)getExternalInputListWithSuccess:(ExternalInputListSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://tv/getExternalInputList"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = ^(NSDictionary *responseObject) + { + NSArray *externalInputsData = [responseObject objectForKey:@"devices"]; + NSMutableArray *externalInputs = [[NSMutableArray alloc] init]; + + [externalInputsData enumerateObjectsUsingBlock:^(NSDictionary *externalInputData, NSUInteger idx, BOOL *stop) + { + [externalInputs addObject:[WebOSTVService externalInputInfoFromDictionary:externalInputData]]; + }]; + + if (success) + success(externalInputs); + }; + command.callbackError = failure; + [command send]; +} + +- (void)setExternalInput:(ExternalInputInfo *)externalInputInfo success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://tv/switchInput"]; + + NSMutableDictionary *payload = [NSMutableDictionary new]; + if (externalInputInfo && externalInputInfo.id) [payload setValue:externalInputInfo.id forKey:@"inputId"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:payload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +#pragma mark - Media Player + +- (id )mediaPlayer +{ + return self; +} + +- (CapabilityPriorityLevel)mediaPlayerPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)displayImage:(NSURL *)imageURL iconURL:(NSURL *)iconURL title:(NSString *)title description:(NSString *)description mimeType:(NSString *)mimeType success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:imageURL mimeType:mimeType]; + mediaInfo.title = title; + mediaInfo.description = description; + ImageInfo *imageInfo = [[ImageInfo alloc] initWithURL:iconURL type:ImageTypeThumb]; + [mediaInfo addImage:imageInfo]; + + [self displayImageWithMediaInfo:mediaInfo success:^(MediaLaunchObject *mediaLanchObject) { + success(mediaLanchObject.session,mediaLanchObject.mediaControl); + } failure:failure]; +} + +- (void) displayImage:(MediaInfo *)mediaInfo + success:(MediaPlayerDisplaySuccessBlock)success + failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + + [self displayImage:mediaInfo.url iconURL:iconURL title:mediaInfo.title description:mediaInfo.description mimeType:mediaInfo.mimeType success:success failure:failure]; +} + +- (void) displayImageWithMediaInfo:(MediaInfo *)mediaInfo success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + if ([self.serviceDescription.version isEqualToString:@"4.0.0"]) + { + if (self.dlnaService) + { + id mediaPlayer; + + if ([self.dlnaService respondsToSelector:@selector(mediaPlayer)]) + mediaPlayer = [self.dlnaService performSelector:@selector(mediaPlayer)]; + + if (mediaPlayer && [mediaPlayer respondsToSelector:@selector(playMedia:iconURL:title:description:mimeType:shouldLoop:success:failure:)]) + { + [mediaPlayer displayImageWithMediaInfo:mediaInfo success:success failure:failure]; + return; + } + } + + NSDictionary *params = @{ + @"target" : ensureString(mediaInfo.url.absoluteString), + @"iconSrc" : ensureString(iconURL.absoluteString), + @"title" : ensureString(mediaInfo.title), + @"description" : ensureString(mediaInfo.description), + @"mimeType" : ensureString(mediaInfo.mimeType) + }; + + [self displayMediaWithParams:params success:success failure:failure]; + } else + { + NSString *webAppId = @"MediaPlayer"; + + WebAppLaunchSuccessBlock connectSuccess = ^(WebAppSession *webAppSession) + { + WebOSWebAppSession *session = (WebOSWebAppSession *)webAppSession; + [session.mediaPlayer displayImageWithMediaInfo:mediaInfo success:success failure:failure]; + }; + + [self joinWebAppWithId:webAppId success:connectSuccess failure:^(NSError *error) + { + [self launchWebApp:webAppId success:connectSuccess failure:failure]; + }]; + } +} + +- (void) playMedia:(NSURL *)mediaURL iconURL:(NSURL *)iconURL title:(NSString *)title description:(NSString *)description mimeType:(NSString *)mimeType shouldLoop:(BOOL)shouldLoop success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + MediaInfo *mediaInfo = [[MediaInfo alloc] initWithURL:mediaURL mimeType:mimeType]; + mediaInfo.title = title; + mediaInfo.description = description; + ImageInfo *imageInfo = [[ImageInfo alloc] initWithURL:iconURL type:ImageTypeThumb]; + [mediaInfo addImage:imageInfo]; + + [self playMediaWithMediaInfo:mediaInfo shouldLoop:shouldLoop success:^(MediaLaunchObject *mediaLanchObject) { + success(mediaLanchObject.session,mediaLanchObject.mediaControl); + } failure:failure]; +} + +- (void) playMedia:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerDisplaySuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + [self playMedia:mediaInfo.url iconURL:iconURL title:mediaInfo.title description:mediaInfo.description mimeType:mediaInfo.mimeType shouldLoop:shouldLoop success:success failure:failure]; +} + +- (void) playMediaWithMediaInfo:(MediaInfo *)mediaInfo shouldLoop:(BOOL)shouldLoop success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *iconURL; + if(mediaInfo.images){ + ImageInfo *imageInfo = [mediaInfo.images firstObject]; + iconURL = imageInfo.url; + } + + if ([self.serviceDescription.version isEqualToString:@"4.0.0"]) + { + if (self.dlnaService) + { + id mediaPlayer; + + if ([self.dlnaService respondsToSelector:@selector(mediaPlayer)]) + mediaPlayer = [self.dlnaService performSelector:@selector(mediaPlayer)]; + + if (mediaPlayer && [mediaPlayer respondsToSelector:@selector(playMediaWithMediaInfo:shouldLoop:success:failure:)]) + { + [mediaPlayer playMediaWithMediaInfo:mediaInfo shouldLoop:shouldLoop success:success failure:failure]; + return; + } + } + + NSDictionary *params = @{ + @"target" : ensureString(mediaInfo.url.absoluteString), + @"iconSrc" : ensureString(iconURL.absoluteString), + @"title" : ensureString(mediaInfo.title), + @"description" : ensureString(mediaInfo.description), + @"mimeType" : ensureString(mediaInfo.mimeType), + @"loop" : shouldLoop ? @"true" : @"false" + }; + + [self displayMediaWithParams:params success:success failure:failure]; + } else + { + NSString *webAppId = @"MediaPlayer"; + + WebAppLaunchSuccessBlock connectSuccess = ^(WebAppSession *webAppSession) + { + WebOSWebAppSession *session = (WebOSWebAppSession *)webAppSession; + [session.mediaPlayer playMediaWithMediaInfo:mediaInfo shouldLoop:shouldLoop success:success failure:failure]; + }; + + [self joinWebAppWithId:webAppId success:connectSuccess failure:^(NSError *error) + { + [self launchWebApp:webAppId success:connectSuccess failure:failure]; + }]; + } +} + +- (void)displayMediaWithParams:(NSDictionary *)params success:(MediaPlayerSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://media.viewer/open"]; + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.serviceCommandDelegate target:URL payload:params]; + command.callbackComplete = ^(NSDictionary *responseObject) + { + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:[responseObject objectForKey:@"id"]]; + launchSession.sessionId = [responseObject objectForKey:@"sessionId"]; + launchSession.sessionType = LaunchSessionTypeMedia; + launchSession.service = self; + launchSession.rawData = [responseObject copy]; + + MediaLaunchObject *launchObject = [[MediaLaunchObject alloc] initWithLaunchSession:launchSession andMediaControl:self.mediaControl]; + if(success){ + success(launchObject); + } + }; + command.callbackError = failure; + [command send]; +} + +- (void)closeMedia:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self closeApp:launchSession success:success failure:failure]; +} + +#pragma mark - Media Control + +- (id )mediaControl +{ + return self; +} + +- (CapabilityPriorityLevel)mediaControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)playWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://media.controls/play"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)pauseWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://media.controls/pause"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)stopWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://media.controls/stop"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)rewindWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://media.controls/rewind"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)fastForwardWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://media.controls/fastForward"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)seek:(NSTimeInterval)position success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)getPlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (void)getDurationWithSuccess:(MediaDurationSuccessBlock)success + failure:(FailureBlock)failure { + [self sendNotSupportedFailure:failure]; +} + +- (void)getPositionWithSuccess:(MediaPositionSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribePlayStateWithSuccess:(MediaPlayStateSuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +- (void)getMediaMetaDataWithSuccess:(SuccessBlock)success + failure:(FailureBlock)failure { + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribeMediaInfoWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +#pragma mark - Playlist Control + +- (id )playListControl +{ + return self; +} + +- (CapabilityPriorityLevel) playListControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)playNextWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure{ + [self sendNotSupportedFailure:failure]; +} + +- (void)playPreviousWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure{ + [self sendNotSupportedFailure:failure]; +} + +- (void)jumpToTrackWithIndex:(NSInteger)index success:(SuccessBlock)success failure:(FailureBlock)failure{ + [self sendNotSupportedFailure:failure]; +} + +#pragma mark - Volume + +- (id )volumeControl +{ + return self; +} + +- (CapabilityPriorityLevel)volumeControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)getMuteWithSuccess:(MuteSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://audio/getMute"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + + command.callbackComplete = ^(NSDictionary *responseDic) + { + BOOL mute = [[responseDic objectForKey:@"mute"] boolValue]; + + if (success) + success(mute); + }; + + command.callbackError = failure; + [command send]; +} + +- (void)setMute:(BOOL)mute success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://audio/setMute"]; + NSDictionary *payload = @{ @"mute" : @(mute) }; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:payload]; + + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)getVolumeWithSuccess:(VolumeSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://audio/getVolume"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + + command.callbackComplete = (^(NSDictionary *responseDic) + { + int fromString = 0; + if ([responseDic objectForKey:@"volume"]) + { + fromString = [[responseDic objectForKey:@"volume"] intValue]; + } + else + { + fromString = [[[responseDic objectForKey:@"volumeStatus"] objectForKey:@"volume"] intValue]; + } + float volVal = fromString / 100.0; + + if (success) + success(volVal); + }); + + command.callbackError = failure; + [command send]; +} + +- (void)setVolume:(float)volume success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://audio/setVolume"]; + NSDictionary *payload = @{ @"volume" : @(roundf(volume * 100.0f)) }; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:payload]; + + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)volumeUpWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://audio/volumeUp"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)volumeDownWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://audio/volumeDown"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (ServiceSubscription *)subscribeMuteWithSuccess:(MuteSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://audio/getMute"]; + + ServiceSubscription *subscription = [self.socket addSubscribe:URL payload:nil success:^(NSDictionary *responseObject) + { + BOOL muteValue = [[responseObject valueForKey:@"mute"] boolValue]; + + if (success) + success(muteValue); + } failure:failure]; + + return subscription; +} + +- (ServiceSubscription *)subscribeVolumeWithSuccess:(VolumeSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://audio/getVolume"]; + + ServiceSubscription *subscription = [self.socket addSubscribe:URL payload:nil success:^(NSDictionary *responseObject) + { + float volumeValue = 0; + if ([responseObject valueForKey:@"volume"]) + { + volumeValue = [[responseObject valueForKey:@"volume"] floatValue] / 100.0; + } + else + { + volumeValue = [[[responseObject valueForKey:@"volumeStatus"] valueForKey:@"volume"] floatValue] / 100.0; + } + + if (success) + success(volumeValue); + } failure:failure]; + + return subscription; +} + +#pragma mark - TV + +- (id )tvControl +{ + return self; +} + +- (CapabilityPriorityLevel)tvControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)getCurrentChannelWithSuccess:(CurrentChannelSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://tv/getCurrentChannel"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = ^(NSDictionary *responseDic) + { + if (success) + success([WebOSTVService channelInfoFromDictionary:responseDic]); + }; + command.callbackError = failure; + [command send]; +} + +- (void)getChannelListWithSuccess:(ChannelListSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://tv/getChannelList"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + + command.callbackComplete = (^(NSDictionary *responseDic) + { + NSArray *channels = [responseDic objectForKey:@"channelList"]; + NSMutableArray *channelList = [[NSMutableArray alloc] init]; + + [channels enumerateObjectsUsingBlock:^(NSDictionary *channelInfo, NSUInteger idx, BOOL *stop) + { + [channelList addObject:[WebOSTVService channelInfoFromDictionary:channelInfo]]; + }]; + + if (success) + success([NSArray arrayWithArray:channelList]); + }); + + command.callbackError = failure; + [command send]; +} + +- (ServiceSubscription *)subscribeCurrentChannelWithSuccess:(CurrentChannelSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://tv/getCurrentChannel"]; + + ServiceSubscription *subscription = [self.socket addSubscribe:URL payload:nil success:^(NSDictionary *responseObject) + { + ChannelInfo *channelInfo = [WebOSTVService channelInfoFromDictionary:responseObject]; + + if (success) + success(channelInfo); + } failure:failure]; + + return subscription; +} + +- (void)channelUpWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://tv/channelUp"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)channelDownWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://tv/channelDown"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)setChannel:(ChannelInfo *)channelInfo success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (!channelInfo) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"channelInfo cannot be empty"]); + return; + } + NSURL *URL = [NSURL URLWithString:@"ssap://tv/openChannel"]; + + NSMutableDictionary *payload = [NSMutableDictionary dictionary]; + if(channelInfo.id){ + [payload setNullableObject:channelInfo.id forKey:@"channelId"]; + } + + if(channelInfo.number){ + [payload setNullableObject:channelInfo.number forKey:@"channelNumber"]; + } + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:payload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)getProgramInfoWithSuccess:(ProgramInfoSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribeProgramInfoWithSuccess:(ProgramInfoSuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +- (void)getProgramListWithSuccess:(ProgramListSuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +- (ServiceSubscription *)subscribeProgramListWithSuccess:(ProgramListSuccessBlock)success failure:(FailureBlock)failure +{ + return [self sendNotSupportedFailure:failure]; +} + +- (void)get3DEnabledWithSuccess:(TV3DEnabledSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://com.webos.service.tv.display/get3DStatus"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = ^(NSDictionary *responseObject) + { + NSDictionary *status3D = [responseObject objectForKey:@"status3D"]; + BOOL status = [[status3D objectForKey:@"status"] boolValue]; + + if (success) + success(status); + }; + command.callbackError = failure; + [command send]; +} + +- (void)set3DEnabled:(BOOL)enabled success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL; + + if (enabled) + URL = [NSURL URLWithString:@"ssap://com.webos.service.tv.display/set3DOn"]; + else + URL = [NSURL URLWithString:@"ssap://com.webos.service.tv.display/set3DOff"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (ServiceSubscription *)subscribe3DEnabledWithSuccess:(TV3DEnabledSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://com.webos.service.tv.display/get3DStatus"]; + + ServiceSubscription *subscription = [self.socket addSubscribe:URL payload:nil success:^(NSDictionary *responseObject) + { + NSDictionary *status3D = [responseObject objectForKey:@"status3D"]; + BOOL status = [[status3D objectForKey:@"status"] boolValue]; + + if (success) + success(status); + } failure:failure]; + + return subscription; +} + +#pragma mark - Key Control + +- (id ) keyControl +{ + return self; +} + +- (CapabilityPriorityLevel) keyControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void) sendMouseButton:(WebOSTVMouseButton)button success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (self.mouseSocket) + { + [self.mouseSocket button:button]; + + if (success) + success(nil); + } else + { + [self.mouseControl connectMouseWithSuccess:^(id responseObject) + { + [self.mouseSocket button:button]; + + if (success) + success(nil); + } failure:failure]; + } +} + +- (void)upWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendMouseButton:WebOSTVMouseButtonUp success:success failure:failure]; +} + +- (void)downWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendMouseButton:WebOSTVMouseButtonDown success:success failure:failure]; +} + +- (void)leftWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendMouseButton:WebOSTVMouseButtonLeft success:success failure:failure]; +} + +- (void)rightWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendMouseButton:WebOSTVMouseButtonRight success:success failure:failure]; +} + +- (void)okWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (self.mouseSocket) + { + [self.mouseSocket click]; + + if (success) + success(nil); + } else + { + [self.mouseControl connectMouseWithSuccess:^(id responseObject) + { + [self.mouseSocket click]; + + if (success) + success(nil); + } failure:failure]; + } +} + +- (void)backWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendMouseButton:WebOSTVMouseButtonBack success:success failure:failure]; +} + +- (void)homeWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendMouseButton:WebOSTVMouseButtonHome success:success failure:failure]; +} + +- (void)sendKeyCode:(NSUInteger)keyCode success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +#pragma mark - Mouse + +- (id)mouseControl +{ + return self; +} + +- (CapabilityPriorityLevel)mouseControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)connectMouseWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (_mouseSocket || _mouseInit) + return; + + _mouseInit = YES; + + NSURL *commandURL = [NSURL URLWithString:@"ssap://com.webos.service.networkinput/getPointerInputSocket"]; + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.socket target:commandURL payload:nil]; + + command.callbackComplete = (^(NSDictionary *responseDic) + { + NSString *socket = [responseDic objectForKey:@"socketPath"]; + _mouseSocket = [[WebOSTVServiceMouse alloc] initWithSocket:socket success:success failure:failure]; + }); + command.callbackError = ^(NSError *error) + { + _mouseInit = NO; + _mouseSocket = nil; + + if (failure) + failure(error); + }; + [command send]; +} + +- (void)disconnectMouse +{ + [_mouseSocket disconnect]; + _mouseSocket = nil; + + _mouseInit = NO; +} + +- (void) move:(CGVector)distance success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (self.mouseSocket) + { + [self.mouseSocket move:distance]; + + if (success) + success(nil); + } else + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"MouseControl socket is not yet initialized."]); + } +} + +- (void) scroll:(CGVector)distance success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (self.mouseSocket) + { + [self.mouseSocket scroll:distance]; + + if (success) + success(nil); + } else + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"MouseControl socket is not yet initialized."]); + } +} + +- (void)clickWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self okWithSuccess:success failure:failure]; +} + +#pragma mark - Power + +- (id)powerControl +{ + return self; +} + +- (CapabilityPriorityLevel)powerControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)powerOffWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://system/turnOff"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:nil]; + + command.callbackComplete = (^(NSDictionary *responseDic) + { + BOOL didTurnOff = [[responseDic objectForKey:@"returnValue"] boolValue]; + + if (didTurnOff && success) + success(nil); + else if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeTvError andDetails:nil]); + }); + + command.callbackError = failure; + [command send]; +} + +- (void) powerOnWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [self sendNotSupportedFailure:failure]; +} + +#pragma mark - Web App Launcher + +- (id )webAppLauncher +{ + return self; +} + +- (CapabilityPriorityLevel)webAppLauncherPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)launchWebApp:(NSString *)webAppId success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self.webAppLauncher launchWebApp:webAppId params:nil relaunchIfRunning:YES success:success failure:failure]; +} + +- (void)launchWebApp:(NSString *)webAppId relaunchIfRunning:(BOOL)relaunchIfRunning success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + [self.webAppLauncher launchWebApp:webAppId params:nil relaunchIfRunning:relaunchIfRunning success:success failure:failure]; +} + +- (void)launchWebApp:(NSString *)webAppId params:(NSDictionary *)params success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + if (!webAppId || webAppId.length == 0) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You must provide a valid web app id"]); + + return; + } + + __block WebOSWebAppSession *webAppSession = _webAppSessions[webAppId]; + + NSURL *URL = [NSURL URLWithString:@"ssap://webapp/launchWebApp"]; + + NSMutableDictionary *payload = [NSMutableDictionary new]; + if (webAppId) [payload setObject:webAppId forKey:@"webAppId"]; + if (params) [payload setObject:params forKey:@"urlParams"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:payload]; + command.callbackComplete = ^(NSDictionary *responseObject) + { + LaunchSession *launchSession; + + if (webAppSession) + launchSession = webAppSession.launchSession; + else + { + launchSession = [LaunchSession launchSessionForAppId:webAppId]; + webAppSession = [[WebOSWebAppSession alloc] initWithLaunchSession:launchSession service:self]; + _webAppSessions[webAppId] = webAppSession; + } + + launchSession.sessionType = LaunchSessionTypeWebApp; + launchSession.service = self; + launchSession.sessionId = [responseObject objectForKey:@"sessionId"]; + launchSession.rawData = [responseObject copy]; + + if (success) + success(webAppSession); + }; + command.callbackError = failure; + [command send]; +} + +- (void)launchWebApp:(NSString *)webAppId params:(NSDictionary *)params relaunchIfRunning:(BOOL)relaunchIfRunning success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + if (!webAppId || webAppId.length == 0) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You need to provide a valid webAppId."]); + + return; + } + + if (relaunchIfRunning) + [self.webAppLauncher launchWebApp:webAppId params:params success:success failure:failure]; + else + { + [self.launcher getRunningAppWithSuccess:^(AppInfo *appInfo) + { + // TODO: this will only work on native apps, currently + if ([appInfo.id hasSuffix:webAppId]) + { + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:webAppId]; + launchSession.sessionType = LaunchSessionTypeWebApp; + launchSession.service = self; + launchSession.rawData = appInfo.rawData; + + WebOSWebAppSession *webAppSession = [self webAppSessionForLaunchSession:launchSession]; + + if (success) + success(webAppSession); + } else + { + [self.webAppLauncher launchWebApp:webAppId params:params success:success failure:failure]; + } + } failure:failure]; + } +} + +- (void)closeWebApp:(LaunchSession *)launchSession success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (!launchSession || !launchSession.appId || launchSession.appId.length == 0) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"Must provide a valid launch session object"]); + + return; + } + + WebOSWebAppSession *webAppSession = _webAppSessions[launchSession.appId]; + + if (webAppSession){ + [webAppSession disconnectFromWebApp]; + } + + NSURL *URL = [NSURL URLWithString:@"ssap://webapp/closeWebApp"]; + + NSMutableDictionary *payload = [NSMutableDictionary new]; + if (launchSession.appId) [payload setValue:launchSession.appId forKey:@"webAppId"]; + if (launchSession.sessionId) [payload setValue:launchSession.sessionId forKey:@"sessionId"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:payload]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +- (void)joinWebApp:(LaunchSession *)webAppLaunchSession success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + WebOSWebAppSession *webAppSession = [self webAppSessionForLaunchSession:webAppLaunchSession]; + + [webAppSession joinWithSuccess:^(id responseObject) + { + if (success) + success(webAppSession); + } failure:failure]; +} + +- (void)joinWebAppWithId:(NSString *)webAppId success:(WebAppLaunchSuccessBlock)success failure:(FailureBlock)failure +{ + LaunchSession *launchSession = [LaunchSession launchSessionForAppId:webAppId]; + launchSession.sessionType = LaunchSessionTypeWebApp; + launchSession.service = self; + + [self joinWebApp:launchSession success:success failure:failure]; +} + +- (void) connectToWebApp:(WebOSWebAppSession *)webAppSession joinOnly:(BOOL)joinOnly success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (!_webAppSessions) + _webAppSessions = [NSMutableDictionary new]; + + if (!_appToAppIdMappings) + _appToAppIdMappings = [NSMutableDictionary new]; + + if (!webAppSession || !webAppSession.launchSession) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You must provide a valid LaunchSession object."]); + return; + } + + NSString *appId = webAppSession.launchSession.appId; + NSString *idKey; + + if (webAppSession.launchSession.sessionType == LaunchSessionTypeWebApp) + idKey = @"webAppId"; + else + idKey = @"appId"; + + if (!appId || appId.length == 0) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You must provide a valid web app session"]); + + return; + } + + NSURL *URL = [NSURL URLWithString:@"ssap://webapp/connectToApp"]; + + NSMutableDictionary *payload = [NSMutableDictionary new]; + [payload setValue:appId forKey:idKey]; + + FailureBlock connectFailure = ^(NSError *error) + { + [webAppSession disconnectFromWebApp]; + + BOOL appChannelDidClose = [error.localizedDescription rangeOfString:@"app channel closed"].location != NSNotFound; + + if (appChannelDidClose) + { + if (webAppSession && webAppSession.delegate && [webAppSession.delegate respondsToSelector:@selector(webAppSessionDidDisconnect:)]) + [webAppSession.delegate webAppSessionDidDisconnect:webAppSession]; + } else + { + if (failure) + failure(error); + } + }; + + SuccessBlock connectSuccess = ^(id responseObject) { + NSString *state = [responseObject objectForKey:@"state"]; + + if (![state isEqualToString:@"CONNECTED"]) + { + if (joinOnly && [state isEqualToString:@"WAITING_FOR_APP"]) + { + if (connectFailure) + connectFailure([ConnectError generateErrorWithCode:ConnectStatusCodeError andDetails:@"Web app is not currently running"]); + } + + return; + } + + NSString *fullAppId = responseObject[@"appId"]; + + if (fullAppId) + { + if (webAppSession.launchSession.sessionType == LaunchSessionTypeWebApp) + _appToAppIdMappings[fullAppId] = appId; + + webAppSession.fullAppId = fullAppId; + } + + if (success) + success(responseObject); + }; + + ServiceSubscription *appToAppSubscription = [ServiceSubscription subscriptionWithDelegate:webAppSession.socket target:URL payload:payload callId:-1]; + [appToAppSubscription addSuccess:connectSuccess]; + [appToAppSubscription addFailure:connectFailure]; + + webAppSession.appToAppSubscription = appToAppSubscription; + [appToAppSubscription subscribe]; +} + + +- (void) pinWebApp:(NSString *)webAppId success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (!webAppId || webAppId.length == 0) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You must provide a valid web app id"]); + + return; + } + + NSURL *URL = [NSURL URLWithString:@"ssap://webapp/pinWebApp"]; + NSMutableDictionary *payload = [NSMutableDictionary new]; + [payload setObject:webAppId forKey:@"webAppId"]; + __weak typeof(self) weakSelf = self; + __block ServiceSubscription *subscription = [self.socket addSubscribe:URL payload:payload success:^(NSDictionary *responseDict) + { + if([responseDict valueForKey:@"pairingType"]){ + [weakSelf showAlertWithTitle:@"Pin Web App" andMessage:@"Please confirm on your device"]; + + } + else + { + [weakSelf dismissPinAlertView]; + [subscription unsubscribe]; + success(responseDict); + } + + }failure:^(NSError *error){ + [weakSelf dismissPinAlertView]; + [subscription unsubscribe]; + failure(error); + }]; +} + +- (void)unPinWebApp:(NSString *)webAppId success:(SuccessBlock)success failure:(FailureBlock)failure +{ + if (!webAppId || webAppId.length == 0) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You must provide a valid web app id"]); + + return; + } + + NSURL *URL = [NSURL URLWithString:@"ssap://webapp/removePinnedWebApp"]; + NSMutableDictionary *payload = [NSMutableDictionary new]; + [payload setObject:webAppId forKey:@"webAppId"]; + + __weak typeof(self) weakSelf = self; + __block ServiceSubscription *subscription = [self.socket addSubscribe:URL payload:payload success:^(NSDictionary *responseDict) + { + if([responseDict valueForKey:@"pairingType"]){ + [weakSelf showAlertWithTitle:@"Un Pin Web App" andMessage:@"Please confirm on your device"]; + + } + else + { + [weakSelf dismissPinAlertView]; + [subscription unsubscribe]; + success(responseDict); + } + + + }failure:^(NSError *error){ + [weakSelf dismissPinAlertView]; + [subscription unsubscribe]; + failure(error); + }]; +} + +- (void)isWebAppPinned:(NSString *)webAppId success:(WebAppPinStatusBlock)success failure:(FailureBlock)failure +{ + if (!webAppId || webAppId.length == 0) + { + if (failure) + failure([ConnectError generateErrorWithCode:ConnectStatusCodeArgumentError andDetails:@"You must provide a valid web app id"]); + + return; + } + NSURL *URL = [NSURL URLWithString:@"ssap://webapp/isWebAppPinned"]; + NSMutableDictionary *payload = [NSMutableDictionary new]; + [payload setObject:webAppId forKey:@"webAppId"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:payload]; + command.callbackComplete = (^(NSDictionary *responseDic) + { + BOOL status = [[responseDic objectForKey:@"pinned"] boolValue]; + if(success){ + success(status); + } + + }); + command.callbackError = failure; + [command send]; +} + +- (ServiceSubscription *)subscribeIsWebAppPinned:(NSString*)webAppId success:(WebAppPinStatusBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://webapp/isWebAppPinned"]; + NSMutableDictionary *payload = [NSMutableDictionary new]; + [payload setObject:webAppId forKey:@"webAppId"]; + + ServiceSubscription *subscription = [self.socket addSubscribe:URL payload:payload success:^(NSDictionary *responseObject) + { + BOOL status = [[responseObject objectForKey:@"pinned"] boolValue]; + if (success){ + success(status); + } + + } failure:failure]; + return subscription; +} + +- (void)sendPairingKey:(NSString *)pairingKey success:(SuccessBlock)success failure:(FailureBlock)failure { + + NSURL *URL = [NSURL URLWithString:@"ssap://pairing/setPin"]; + NSMutableDictionary *payload = [NSMutableDictionary new]; + [payload setObject:pairingKey forKey:@"pin"]; + + ServiceCommand *command = [ServiceAsyncCommand commandWithDelegate:self.socket target:URL payload:payload]; + command.callbackComplete = (^(NSDictionary *responseDic) + { + if (success) { + success(responseDic); + } + + }); + command.callbackError = ^(NSError *error){ + if(failure){ + failure(error); + } + }; + [command send]; +} + +- (WebOSWebAppSession *) webAppSessionForLaunchSession:(LaunchSession *)launchSession +{ + if (!_webAppSessions) + _webAppSessions = [NSMutableDictionary new]; + + if (!launchSession.service) + launchSession.service = self; + + WebOSWebAppSession *webAppSession = _webAppSessions[launchSession.appId]; + + if (!webAppSession) + { + webAppSession = [self createWebAppSessionWithLaunchSession:launchSession + andService:self]; + _webAppSessions[launchSession.appId] = webAppSession; + } + + return webAppSession; +} + +- (NSDictionary *) appToAppIdMappings +{ + return [NSDictionary dictionaryWithDictionary:_appToAppIdMappings]; +} + +- (NSDictionary *) webAppSessions +{ + return [NSDictionary dictionaryWithDictionary:_webAppSessions]; +} + +#pragma mark - Text Input Control + +- (id) textInputControl +{ + return self; +} + +- (CapabilityPriorityLevel) textInputControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void) sendText:(NSString *)input success:(SuccessBlock)success failure:(FailureBlock)failure +{ + [_keyboardQueue addObject:input]; + + if (!_keyboardQueueProcessing) + [self sendKeys]; +} + +- (void)sendEnterWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [_keyboardQueue addObject:kKeyboardEnter]; + + if (!_keyboardQueueProcessing) + [self sendKeys]; +} + +- (void)sendDeleteWithSuccess:(SuccessBlock)success failure:(FailureBlock)failure +{ + [_keyboardQueue addObject:kKeyboardDelete]; + + if (!_keyboardQueueProcessing) + [self sendKeys]; +} + +- (void) sendKeys +{ + _keyboardQueueProcessing = YES; + + NSString *target; + NSString *key = [_keyboardQueue firstObject]; + NSMutableDictionary *payload = [[NSMutableDictionary alloc] init]; + + if ([key isEqualToString:kKeyboardEnter]) + { + [_keyboardQueue removeObjectAtIndex:0]; + target = @"ssap://com.webos.service.ime/sendEnterKey"; + } else if ([key isEqualToString:kKeyboardDelete]) + { + target = @"ssap://com.webos.service.ime/deleteCharacters"; + + int count = 0; + + for (NSUInteger i = 0; i < _keyboardQueue.count; i++) + { + if ([[_keyboardQueue objectAtIndex:i] isEqualToString:kKeyboardDelete]) { + count++; + } else { + break; + } + } + + NSRange deleteRange = NSMakeRange(0, count); + [_keyboardQueue removeObjectsInRange:deleteRange]; + + [payload setObject:@(count) forKey:@"count"]; + } else + { + target = @"ssap://com.webos.service.ime/insertText"; + NSMutableString *stringToSend = [[NSMutableString alloc] init]; + + int count = 0; + + for (NSUInteger i = 0; i < _keyboardQueue.count; i++) + { + NSString *text = [_keyboardQueue objectAtIndex:i]; + + if (![text isEqualToString:kKeyboardEnter] && ![text isEqualToString:kKeyboardDelete]) { + [stringToSend appendString:text]; + count++; + } else { + break; + } + } + + NSRange textRange = NSMakeRange(0, count); + [_keyboardQueue removeObjectsInRange:textRange]; + + [payload setObject:stringToSend forKey:@"text"]; + [payload setObject:@(NO) forKey:@"replace"]; + } + + NSURL *URL = [NSURL URLWithString:target]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self.socket target:URL payload:payload]; + command.callbackComplete = ^(id responseObject) + { + _keyboardQueueProcessing = NO; + + if (_keyboardQueue.count > 0) + [self sendKeys]; + }; + command.callbackError = ^(NSError *error) + { + _keyboardQueueProcessing = NO; + + if (_keyboardQueue.count > 0) + [self sendKeys]; + }; + [command send]; +} + +- (ServiceSubscription *) subscribeTextInputStatusWithSuccess:(TextInputStatusInfoSuccessBlock)success failure:(FailureBlock)failure +{ + _keyboardQueue = [[NSMutableArray alloc] init]; + _keyboardQueueProcessing = NO; + + NSURL *URL = [NSURL URLWithString:@"ssap://com.webos.service.ime/registerRemoteKeyboard"]; + + ServiceSubscription *subscription = [self.socket addSubscribe:URL payload:nil success:^(NSDictionary *responseObject) + { + BOOL isVisible = [[[responseObject objectForKey:@"currentWidget"] objectForKey:@"focus"] boolValue]; + NSString *type = [[responseObject objectForKey:@"currentWidget"] objectForKey:@"contentType"]; + + UIKeyboardType keyboardType = UIKeyboardTypeDefault; + + if ([type isEqualToString:@"url"]) + keyboardType = UIKeyboardTypeURL; + else if ([type isEqualToString:@"number"]) + keyboardType = UIKeyboardTypeNumberPad; + else if ([type isEqualToString:@"phonenumber"]) + keyboardType = UIKeyboardTypeNamePhonePad; + else if ([type isEqualToString:@"email"]) + keyboardType = UIKeyboardTypeEmailAddress; + + TextInputStatusInfo *keyboardInfo = [[TextInputStatusInfo alloc] init]; + keyboardInfo.isVisible = isVisible; + keyboardInfo.keyboardType = keyboardType; + keyboardInfo.rawData = [responseObject copy]; + + if (success) + success(keyboardInfo); + } failure:failure]; + + return subscription; +} + +#pragma mark - Toast Control + +- (id)toastControl +{ + return self; +} + +- (CapabilityPriorityLevel)toastControlPriority +{ + return CapabilityPriorityLevelHigh; +} + +- (void)showToast:(NSString *)message success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSMutableDictionary *params = [NSMutableDictionary new]; + if (message) [params setValue:message forKey:@"message"]; + + [self showToastWithParams:params success:success failure:failure]; +} + +- (void)showToast:(NSString *)message iconData:(NSString *)iconData iconExtension:(NSString *)iconExtension success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSMutableDictionary *params = [NSMutableDictionary new]; + if (message) [params setValue:message forKey:@"message"]; + if (iconData) [params setValue:iconData forKey:@"iconData"]; + if (iconExtension) [params setValue:iconExtension forKey:@"iconExtension"]; + + [self showToastWithParams:params success:success failure:failure]; +} + +- (void)showClickableToast:(NSString *)message appInfo:(AppInfo *)appInfo params:(NSDictionary *)launchParams success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSMutableDictionary *params = [NSMutableDictionary new]; + if (message) [params setValue:message forKey:@"message"]; + if (appInfo) [params setValue:appInfo.id forKey:@"target"]; + if (launchParams) [params setValue:launchParams forKey:@"params"]; + + [self showToastWithParams:params success:success failure:failure]; +} + +- (void)showClickableToast:(NSString *)message appInfo:(AppInfo *)appInfo params:(NSDictionary *)launchParams iconData:(NSString *)iconData iconExtension:(NSString *)iconExtension success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSMutableDictionary *params = [NSMutableDictionary new]; + if (message) [params setValue:message forKey:@"message"]; + if (appInfo) [params setValue:appInfo.id forKey:@"target"]; + if (launchParams) [params setValue:launchParams forKey:@"params"]; + if (iconData) [params setValue:iconData forKey:@"iconData"]; + if (iconExtension) [params setValue:iconExtension forKey:@"iconExtension"]; + + [self showToastWithParams:params success:success failure:failure]; +} + +- (void)showClickableToast:(NSString *)message URL:(NSURL *)URL success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSMutableDictionary *params = [NSMutableDictionary new]; + if (message) [params setValue:message forKey:@"message"]; + if (URL) [params setValue:URL.absoluteString forKey:@"target"]; + + [self showToastWithParams:params success:success failure:failure]; +} + +- (void)showClickableToast:(NSString *)message URL:(NSURL *)URL iconData:(NSString *)iconData iconExtension:(NSString *)iconExtension success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSMutableDictionary *params = [NSMutableDictionary new]; + if (message) [params setValue:message forKey:@"message"]; + if (URL) [params setValue:URL.absoluteString forKey:@"target"]; + if (iconData) [params setValue:iconData forKey:@"iconData"]; + if (iconExtension) [params setValue:iconExtension forKey:@"iconExtension"]; + + [self showToastWithParams:params success:success failure:failure]; +} + +- (void) showToastWithParams:(NSDictionary *)params success:(SuccessBlock)success failure:(FailureBlock)failure +{ + NSMutableDictionary *toastParams = [NSMutableDictionary dictionaryWithDictionary:params]; + + if ([toastParams objectForKey:@"iconData"] == nil) + { + NSString *imageName = [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIconFiles"] objectAtIndex:0]; + + if (imageName == nil) + imageName = [[[[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIcons"] objectForKey:@"CFBundlePrimaryIcon"] objectForKey:@"CFBundleIconFiles"] firstObject]; + + UIImage *appIcon = [UIImage imageNamed:imageName]; + NSString *dataString; + + if (appIcon) + dataString = [UIImagePNGRepresentation(appIcon) base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; + + if (dataString) + { + [toastParams setObject:dataString forKey:@"iconData"]; + [toastParams setObject:@"png" forKey:@"iconExtension"]; + } + } + + ServiceCommand *command = [[ServiceCommand alloc] initWithDelegate:self.socket target:[NSURL URLWithString:@"ssap://system.notifications/createToast"] payload:toastParams]; + command.callbackComplete = success; + command.callbackError = failure; + [command send]; +} + +#pragma mark - System info + +- (void)getServiceListWithSuccess:(ServiceListSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://api/getServiceList"]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = ^(NSDictionary *responseObject) + { + NSArray *services = [responseObject objectForKey:@"services"]; + + if (success) + success(services); + }; + command.callbackError = failure; + [command send]; +} + +- (void)getSystemInfoWithSuccess:(SystemInfoSuccessBlock)success failure:(FailureBlock)failure +{ + NSURL *URL = [NSURL URLWithString:@"ssap://system/getSystemInfo"]; + + ServiceCommand *command = [ServiceCommand commandWithDelegate:self.socket target:URL payload:nil]; + command.callbackComplete = ^(NSDictionary *responseObject) + { + NSArray *features = [responseObject objectForKey:@"features"]; + + if (success) + success(features); + }; + command.callbackError = failure; + [command send]; +} + +#pragma mark - Helpers + +- (WebOSWebAppSession *)createWebAppSessionWithLaunchSession:(LaunchSession *)launchSession + andService:(WebOSTVService *)service { + return [[WebOSWebAppSession alloc] initWithLaunchSession:launchSession service:service]; +} + +#pragma mark - ScreenMirroringControl + +- (id)screenMirroringControl { + [[ScreenMirroringService sharedInstance] setDelegate:self]; + return self; +} + +- (CapabilityPriorityLevel)screenMirroringControlPriority { + return CapabilityPriorityLevelHigh; +} + +- (void)startScreenMirroringWithSettings:(nullable NSDictionary *)settings { + NSDictionary *allDevices = [[DiscoveryManager sharedManager] allDevices]; + ConnectableDevice *device; + + if (allDevices && allDevices.count > 0) + device = [allDevices objectForKey:self.serviceDescription.address]; + + [[ScreenMirroringService sharedInstance] startMirroring:device settings:settings]; +} + +- (void)startScreenMirroring { + [self startScreenMirroringWithSettings:nil]; +} + +- (void)pushSampleBuffer:(CMSampleBufferRef)sampleBuffer with:(RPSampleBufferType)sampleBufferType { + [[ScreenMirroringService sharedInstance] pushSampleBuffer:sampleBuffer with:sampleBufferType]; +} + +- (void)stopScreenMirroring { + [[ScreenMirroringService sharedInstance] stopMirroring]; +} + +- (void)setScreenMirroringDelegate:(__weak id)delegate { + _screenMirroringDelegate = delegate; +} + +#pragma mark - ScreenMirroringServiceDelegate +- (void)screenMirroringDidStart:(BOOL)result { + if(_screenMirroringDelegate != nil && [_screenMirroringDelegate respondsToSelector:@selector(screenMirroringDidStart:)]){ + [_screenMirroringDelegate screenMirroringDidStart:result]; + } +} + +- (void)screenMirroringDidStop:(BOOL)result { + if(_screenMirroringDelegate != nil && [_screenMirroringDelegate respondsToSelector:@selector(screenMirroringDidStop:)]){ + [_screenMirroringDelegate screenMirroringDidStop:result]; + } +} + +- (void)screenMirroringErrorDidOccur:(ScreenMirroringError)error { + if(_screenMirroringDelegate != nil && [_screenMirroringDelegate respondsToSelector:@selector(screenMirroringErrorDidOccur:)]){ + [_screenMirroringDelegate screenMirroringErrorDidOccur:error]; + } +} + +#pragma mark - RemoteCameraControl + +- (id)remoteCameraControl { + [[RemoteCameraService sharedInstance] setDelegate:self]; + return self; +} + +- (CapabilityPriorityLevel)remoteCameraControlPriority { + return CapabilityPriorityLevelHigh; +} + +- (UIView *)startRemoteCameraWithSettings:(nullable NSDictionary *)settings{ + NSDictionary *allDevices = [[DiscoveryManager sharedManager] allDevices]; + ConnectableDevice *device; + + if (allDevices && allDevices.count > 0) + device = [allDevices objectForKey:self.serviceDescription.address]; + + return [[RemoteCameraService sharedInstance] startRemoteCamera:device settings:settings]; +} + +- (UIView *)startRemoteCamera { + return [self startRemoteCameraWithSettings:nil]; +} + +- (void)stopRemoteCamera { + [[RemoteCameraService sharedInstance] stopRemoteCamera]; +} + +- (void)setLensFacing:(int)lensFacing { + [[RemoteCameraService sharedInstance] setLensFacing:lensFacing]; + return; +} + +- (void)setMicMute:(BOOL)micMute { + [[RemoteCameraService sharedInstance] setMicMute:micMute]; + return; +} + +- (void)setRemoteCameraDelegate:(__weak id)delegate { + _remoteCameraDelegate = delegate; +} + +#pragma mark - RemoteCameraServiceDelegate +- (void)remoteCameraDidPair { + if(_remoteCameraDelegate != nil && [_remoteCameraDelegate respondsToSelector:@selector(remoteCameraDidPair)]){ + [_remoteCameraDelegate remoteCameraDidPair]; + } +} + +- (void)remoteCameraDidPlay { + if(_remoteCameraDelegate != nil && [_remoteCameraDelegate respondsToSelector:@selector(remoteCameraDidPlay)]){ + [_remoteCameraDelegate remoteCameraDidPlay]; + } +} + +- (void)remoteCameraDidStart:(BOOL)result { + if(_remoteCameraDelegate != nil && [_remoteCameraDelegate respondsToSelector:@selector(remoteCameraDidStart:)]){ + [_remoteCameraDelegate remoteCameraDidStart:result]; + } +} + +- (void)remoteCameraDidStop:(BOOL)result { + if(_remoteCameraDelegate != nil && [_remoteCameraDelegate respondsToSelector:@selector(remoteCameraDidStop:)]){ + [_remoteCameraDelegate remoteCameraDidStop:result]; + } +} + +- (void)remoteCameraDidChange:(RemoteCameraProperty)property{ + if(_remoteCameraDelegate != nil && [_remoteCameraDelegate respondsToSelector:@selector(remoteCameraDidChange:)]){ + [_remoteCameraDelegate remoteCameraDidChange:property]; + } +} + +- (void)remoteCameraErrorDidOccur:(RemoteCameraError)error { + if(_remoteCameraDelegate != nil && [_remoteCameraDelegate respondsToSelector:@selector(remoteCameraErrorDidOccur:)]){ + [_remoteCameraDelegate remoteCameraErrorDidOccur:error]; + } +} + +@end diff --git a/core/Services/WebOSTVService_Private.h b/core/Services/WebOSTVService_Private.h new file mode 100644 index 00000000..a646daf2 --- /dev/null +++ b/core/Services/WebOSTVService_Private.h @@ -0,0 +1,32 @@ +// +// WebOSTVService_Private.h +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-08-04. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "WebOSTVService.h" + +@interface WebOSTVService () + +@property (nonatomic, strong) id serviceCommandDelegate; + +- (DeviceService *)dlnaService; + +- (WebOSWebAppSession *)createWebAppSessionWithLaunchSession:(LaunchSession *)launchSession + andService:(WebOSTVService *)service; + +@end diff --git a/core/en.lproj/ConnectSDK.strings b/core/en.lproj/ConnectSDK.strings new file mode 100644 index 00000000..bcef19d7 --- /dev/null +++ b/core/en.lproj/ConnectSDK.strings @@ -0,0 +1,37 @@ +/* + ConnectSDK.strings + Connect SDK + + Created by Andrew Longstaff on 9/20/13. + Copyright (c) 2014 LG Electronics. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* Search picker */ +"Connect_SDK_Search_Title" = "Pick a device"; +"Connect_SDK_Search_Cancel" = "Cancel"; +"Connect_SDK_Unnamed_Device" = "Unnamed device"; + +/* Prompt strings */ +"Connect_SDK_Pair_Title" = "Pairing with device"; +"Connect_SDK_Pair_Request" = "Please confirm the connection on your device"; +"Connect_SDK_Pair_Request_Pin" = "Please enter the pin code"; +"Connect_SDK_Pair_OK" = "OK"; +"Connect_SDK_Pair_Cancel" = "Cancel"; + +/* AirPlay alert view */ +"Connect_SDK_AirPlay_Mirror_Title" = "Mirroring Required"; +"Connect_SDK_AirPlay_Mirror_Message" = "Enable AirPlay mirroring to connect to this device"; +"Connect_SDK_AirPlay_Mirror_OK" = "OK"; +"Connect_SDK_AirPlay_Mirror_Cancel" = "Cancel"; \ No newline at end of file