├── SkylinkSample ├── SkylinkSample │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── banner.imageset │ │ │ ├── banner4.png │ │ │ └── Contents.json │ │ ├── btn_chat.imageset │ │ │ ├── btn_chat.png │ │ │ └── Contents.json │ │ ├── call_off.imageset │ │ │ ├── call_off.png │ │ │ └── Contents.json │ │ ├── call_on.imageset │ │ │ ├── call_on.png │ │ │ └── Contents.json │ │ ├── btn_audio.imageset │ │ │ ├── btn_audio.png │ │ │ └── Contents.json │ │ ├── btn_video.imageset │ │ │ ├── btn_video.png │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── SkylinkAppLogo4-29.png │ │ │ ├── SkylinkAppLogo4-40.png │ │ │ ├── SkylinkAppLogo4-76.png │ │ │ ├── SkylinkAppLogo4-29@2x.png │ │ │ ├── SkylinkAppLogo4-29@3x.png │ │ │ ├── SkylinkAppLogo4-40@2x.png │ │ │ ├── SkylinkAppLogo4-40@3x.png │ │ │ ├── SkylinkAppLogo4-60@2x.png │ │ │ ├── SkylinkAppLogo4-60@3x.png │ │ │ ├── SkylinkAppLogo4-76@2x.png │ │ │ ├── SkylinkAppLogo4-29@2x-1.png │ │ │ ├── SkylinkAppLogo4-40@2x-1.png │ │ │ ├── SkylinkAppLogo4-83_5@2x.png │ │ │ └── Contents.json │ │ ├── btn_multiVideo.imageset │ │ │ ├── btn_multiVideo.png │ │ │ └── Contents.json │ │ ├── btn_dataTransfer.imageset │ │ │ ├── btn_dataTransfer.png │ │ │ └── Contents.json │ │ ├── btn_fileTransfer.imageset │ │ │ ├── btn_fileTransfer.png │ │ │ └── Contents.json │ │ ├── sampleImage_transfer.imageset │ │ │ ├── sampleImage_transfer.png │ │ │ └── Contents.json │ │ └── sampleImage_groupTransfer.imageset │ │ │ ├── sampleImage_groupTransfer.png │ │ │ └── Contents.json │ ├── images │ │ ├── icons │ │ │ ├── Cancel.png │ │ │ ├── Unlock.png │ │ │ ├── Cancel@2x.png │ │ │ ├── Refresh.png │ │ │ ├── Unlock@2x.png │ │ │ ├── VideoCall.png │ │ │ ├── LockFilled.png │ │ │ ├── Microphone.png │ │ │ ├── Refresh@2x.png │ │ │ ├── HideKeyboard.png │ │ │ ├── LockFilled@2x.png │ │ │ ├── Microphone@2x.png │ │ │ ├── NoVideoFilled.png │ │ │ ├── SwitchCamera.png │ │ │ ├── VideoCall@2x.png │ │ │ ├── HideKeyboard@2x.png │ │ │ ├── NoVideoFilled@2x.png │ │ │ ├── SwitchCamera@2x.png │ │ │ ├── NoMicrophoneFilled.png │ │ │ └── NoMicrophoneFilled@2x.png │ │ ├── logoSkylink.png │ │ ├── logoSkylink@2x.png │ │ └── logoSkylink@3x.png │ ├── DataTransferSamples │ │ └── dataTransferImage.png │ ├── SkylinkSample-Bridging-Header.h │ ├── TransferFileSamples │ │ ├── sampleImage_transfer.png │ │ └── sampleImage_groupTransfer.png │ ├── AudioCallViewController.h │ ├── MessagesViewController.h │ ├── VideoCallViewController.h │ ├── utils │ │ ├── UIAspectFitButton.h │ │ ├── Utils.h │ │ ├── UIAspectFitButton.m │ │ └── Utils.m │ ├── DataTransferViewController.h │ ├── MultiVideoCallViewController.h │ ├── HomeViewController.h │ ├── FileTransferViewController.h │ ├── PrefixHeader.pch │ ├── AppDelegate.h │ ├── Categories │ │ ├── NSString+Ext.h │ │ ├── NSString+Ext.m │ │ ├── Categories.h │ │ ├── NSDate+Ext.h │ │ ├── UIView+Ext.h │ │ ├── UIAlertController+Ext.h │ │ ├── NSDate+Ext.m │ │ ├── UIView+Ext.m │ │ └── UIAlertController+Ext.m │ ├── SettingsViewController.h │ ├── main.m │ ├── SettingCell.h │ ├── BaseVC.h │ ├── EncryptSecretCell.h │ ├── Peer.h │ ├── Peer.m │ ├── SAMessage.h │ ├── StatsView.h │ ├── SAMessage.m │ ├── EncryptSecretCell.m │ ├── SettingCell.m │ ├── Info.plist │ ├── AppDelegate.m │ ├── Constant.h │ ├── Constant.m │ ├── StatsView.m │ ├── BaseVC.m │ ├── SettingCell.xib │ ├── EncryptSecretCell.xib │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── SettingsViewController.m │ ├── HomeViewController.m │ ├── AudioCallViewController.m │ ├── DataTransferViewController.m │ ├── VideoCallViewController.m │ ├── MultiVideoCallViewController.m │ ├── MessagesViewController.m │ ├── FileTransferViewController.m │ └── StatsView.xib ├── Podfile └── SkylinkSample.xcodeproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ └── xcschemes │ └── SkylinkSample.xcscheme ├── LICENSE.md ├── .gitignore └── README.md /SkylinkSample/SkylinkSample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/Cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/Cancel.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/Unlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/Unlock.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/logoSkylink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/logoSkylink.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/Cancel@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/Cancel@2x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/Refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/Refresh.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/Unlock@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/Unlock@2x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/VideoCall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/VideoCall.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/logoSkylink@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/logoSkylink@2x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/logoSkylink@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/logoSkylink@3x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/LockFilled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/LockFilled.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/Microphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/Microphone.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/Refresh@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/Refresh@2x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/HideKeyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/HideKeyboard.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/LockFilled@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/LockFilled@2x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/Microphone@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/Microphone@2x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/NoVideoFilled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/NoVideoFilled.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/SwitchCamera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/SwitchCamera.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/VideoCall@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/VideoCall@2x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/HideKeyboard@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/HideKeyboard@2x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/NoVideoFilled@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/NoVideoFilled@2x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/SwitchCamera@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/SwitchCamera@2x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/NoMicrophoneFilled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/NoMicrophoneFilled.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/images/icons/NoMicrophoneFilled@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/images/icons/NoMicrophoneFilled@2x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/DataTransferSamples/dataTransferImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/DataTransferSamples/dataTransferImage.png -------------------------------------------------------------------------------- /SkylinkSample/Podfile: -------------------------------------------------------------------------------- 1 | project 'SkylinkSample.xcodeproj' 2 | 3 | platform :ios, '9.0' 4 | 5 | target 'SkylinkSample' do 6 | use_frameworks! 7 | 8 | 9 | pod 'SKYLINK' 10 | 11 | end 12 | 13 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/banner.imageset/banner4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/banner.imageset/banner4.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/SkylinkSample-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | //#import "Constant.h" 6 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/btn_chat.imageset/btn_chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/btn_chat.imageset/btn_chat.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/call_off.imageset/call_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/call_off.imageset/call_off.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/call_on.imageset/call_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/call_on.imageset/call_on.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/TransferFileSamples/sampleImage_transfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/TransferFileSamples/sampleImage_transfer.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/btn_audio.imageset/btn_audio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/btn_audio.imageset/btn_audio.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/btn_video.imageset/btn_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/btn_video.imageset/btn_video.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/TransferFileSamples/sampleImage_groupTransfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/TransferFileSamples/sampleImage_groupTransfer.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-29.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-40.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-76.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/btn_multiVideo.imageset/btn_multiVideo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/btn_multiVideo.imageset/btn_multiVideo.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-29@2x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-29@3x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-40@2x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-40@3x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-60@2x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-60@3x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-76@2x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-29@2x-1.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-40@2x-1.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-83_5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/SkylinkAppLogo4-83_5@2x.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/btn_dataTransfer.imageset/btn_dataTransfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/btn_dataTransfer.imageset/btn_dataTransfer.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/btn_fileTransfer.imageset/btn_fileTransfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/btn_fileTransfer.imageset/btn_fileTransfer.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/sampleImage_transfer.imageset/sampleImage_transfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/sampleImage_transfer.imageset/sampleImage_transfer.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/sampleImage_groupTransfer.imageset/sampleImage_groupTransfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Temasys/SkylinkSDK-iOS-Sample/HEAD/SkylinkSample/SkylinkSample/Assets.xcassets/sampleImage_groupTransfer.imageset/sampleImage_groupTransfer.png -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/AudioCallViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AudioCallViewController.h 3 | // Skylink_Examples 4 | // 5 | // Created by Temasys on 07/01/2016. 6 | // Copyright © 2016 Temasys. All rights reserved. 7 | // 8 | 9 | #import "BaseVC.h" 10 | 11 | @interface AudioCallViewController : BaseVC 12 | @end 13 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/MessagesViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MessagesViewController.h 3 | // Skylink_Examples 4 | // 5 | // Created by Temasys on 04/01/2016. 6 | // Copyright © 2016 Temasys. All rights reserved. 7 | // 8 | 9 | #import "BaseVC.h" 10 | 11 | @interface MessagesViewController : BaseVC 12 | @end 13 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/VideoCallViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideCallViewController.h 3 | // Skylink_Examples 4 | // 5 | // Created by Temasys on 11/12/2015. 6 | // Copyright © 2015 Temasys. All rights reserved. 7 | // 8 | 9 | #import "BaseVC.h" 10 | 11 | @interface VideoCallViewController : BaseVC 12 | @end 13 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/utils/UIAspectFitButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIAspectFitButton.h 3 | // Skylink_Examples 4 | // 5 | // Created by Temasys on 16/12/2015. 6 | // Copyright © 2015 Temasys. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIAspectFitButton : UIButton 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/DataTransferViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DataTransferViewController.h 3 | // SkylinkSample 4 | // 5 | // Created by Temasys on 08/06/2016. 6 | // Copyright © 2016 Temasys. All rights reserved. 7 | // 8 | 9 | #import "BaseVC.h" 10 | 11 | @interface DataTransferViewController : BaseVC 12 | @end 13 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/MultiVideoCallViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MultiVideoCallViewController.h 3 | // Skylink_Examples 4 | // 5 | // Created by Temasys on 11/01/2016. 6 | // Copyright © 2016 Temasys. All rights reserved. 7 | // 8 | 9 | #import "BaseVC.h" 10 | 11 | @interface MultiVideoCallViewController : BaseVC 12 | @end 13 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/HomeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Skylink_Examples 4 | // 5 | // Created by Temasys on 11/12/2015. 6 | // Copyright © 2015 Temasys. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | 13 | @interface HomeViewController : UIViewController 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/FileTransferViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FileTransferViewController.h 3 | // Skylink_Examples 4 | // 5 | // Created by Temasys on 18/12/2015. 6 | // Copyright © 2015 Temasys. All rights reserved. 7 | // 8 | 9 | #import "BaseVC.h" 10 | #import 11 | 12 | @interface FileTransferViewController : BaseVC 13 | @end 14 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/PrefixHeader.pch: -------------------------------------------------------------------------------- 1 | // 2 | // PrefixHeader.pch 3 | // SkylinkSample 4 | // 5 | // Created by Charlie on 26/11/19. 6 | // Copyright © 2019 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #ifndef PrefixHeader_pch 10 | #define PrefixHeader_pch 11 | 12 | #import "Constant.h" 13 | #import "Categories.h" 14 | 15 | #endif /* PrefixHeader_pch */ 16 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // SkylinkSample 4 | // 5 | // Created by Temasys on 01/02/2016. 6 | // Copyright © 2016 Temasys. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Categories/NSString+Ext.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+Ext.h 3 | // SkylinkSample 4 | // 5 | // Created by Charlie on 10/3/20. 6 | // Copyright © 2020 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NSString(Ext) 14 | - (BOOL)isNotEmpty; 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/SettingsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.h 3 | // SkylinkSample 4 | // 5 | // Created by Temasys on 29/08/2016. 6 | // Copyright © 2016 Temasys. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SettingsViewController : UIViewController 12 | @property (strong, nonatomic) IBOutlet UITableView *tableView; 13 | @end 14 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/utils/Utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.h 3 | // objc_SampleApp 4 | // 5 | // Created by Charlie on 19/11/19. 6 | // Copyright © 2019 Charlie. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface Utils : NSObject 15 | UIViewController * topVC(void); 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Categories/NSString+Ext.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+Ext.m 3 | // SkylinkSample 4 | // 5 | // Created by Charlie on 10/3/20. 6 | // Copyright © 2020 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #import "NSString+Ext.h" 10 | 11 | @implementation NSString(Ext) 12 | - (BOOL)isNotEmpty{ 13 | if (self && self.length>0) { 14 | return YES; 15 | } 16 | return NO; 17 | } 18 | @end 19 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Categories/Categories.h: -------------------------------------------------------------------------------- 1 | // 2 | // Categories.h 3 | // SkylinkSample 4 | // 5 | // Created by Charlie on 25/11/19. 6 | // Copyright © 2019 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #ifndef Categories_h 10 | #define Categories_h 11 | 12 | #import "UIView+Ext.h" 13 | #import "UIAlertController+Ext.h" 14 | #import "NSDate+Ext.h" 15 | #import "NSString+Ext.h" 16 | 17 | #endif /* Categories_h */ 18 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SkylinkSample 4 | // 5 | // Created by Temasys on 01/02/2016. 6 | // Copyright © 2016 Temasys. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/banner.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "banner4.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/btn_audio.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "btn_audio.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/btn_chat.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "btn_chat.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/btn_video.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "btn_video.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/call_off.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "call_off.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/call_on.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "call_on.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/btn_multiVideo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "btn_multiVideo.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/btn_dataTransfer.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "btn_dataTransfer.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/btn_fileTransfer.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "btn_fileTransfer.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/sampleImage_transfer.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sampleImage_transfer.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/sampleImage_groupTransfer.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sampleImage_groupTransfer.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/SettingCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // SettingCell.h 3 | // SkylinkSample 4 | // 5 | // Created by Temasys on 5/9/19. 6 | // Copyright © 2019 Temasys. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //NS_ASSUME_NONNULL_BEGIN 12 | 13 | //extern NSString * const CELL_IDENTIFIER; 14 | static NSString *CELL_IDENTIFIER = @"SettingCell"; 15 | 16 | @interface SettingCell : UITableViewCell 17 | - (void)setupCellWithKey:(NSString *)key value:(NSString *)value; 18 | @end 19 | 20 | //NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Categories/NSDate+Ext.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+Ext.h 3 | // SkylinkSample 4 | // 5 | // Created by Charlie on 9/3/20. 6 | // Copyright © 2020 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NSDate(Ext) 14 | - (NSString*)skylinkDateString; 15 | + (NSDate*)skylinkDateFromString:(NSString*)string; 16 | 17 | - (long long)toTimeStamp; 18 | + (NSDate *)dateFromTimeStamp:(long long)timeStamp; 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/utils/UIAspectFitButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIAspectFitButton.m 3 | // Skylink_Examples 4 | // 5 | // Created by Temasys on 16/12/2015. 6 | // Copyright © 2015 Temasys. All rights reserved. 7 | // 8 | 9 | #import "UIAspectFitButton.h" 10 | 11 | @implementation UIAspectFitButton 12 | 13 | -(void)layoutSubviews { 14 | [super layoutSubviews]; 15 | for(UIView *buttonSubview in self.subviews) 16 | if ([buttonSubview isKindOfClass:[UIImageView class]]) [buttonSubview setContentMode:UIViewContentModeScaleAspectFit]; 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Categories/UIView+Ext.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Ext.h 3 | // SkylinkSample 4 | // 5 | // Created by Charlie on 25/11/19. 6 | // Copyright © 2019 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface UIView(Ext) 15 | - (void)removeSubviews; 16 | - (void)aspectFitRectForSize:(CGSize)insideSize container:(UIView*)container; 17 | - (void)aspectFillRectForSize:(CGSize)insideSize container:(UIView*)container; 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2017 Temasys Communications Pte Ltd 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/BaseVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // BaseVC.h 3 | // SkylinkSample 4 | // 5 | // Created by Charlie on 26/11/19. 6 | // Copyright © 2019 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface BaseVC : UIViewController{ 15 | SKYLINKConnection *_skylinkConnection; 16 | NSString *roomName; 17 | } 18 | - (void)startLocalMediaDevice:(SKYLINKMediaDevice)mediaDevice; 19 | - (void)joinRoom; 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/utils/Utils.m: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.m 3 | // objc_SampleApp 4 | // 5 | // Created by Charlie on 19/11/19. 6 | // Copyright © 2019 Charlie. All rights reserved. 7 | // 8 | 9 | #import "Utils.h" 10 | #import 11 | 12 | @implementation Utils 13 | UIViewController * topVC(){ 14 | UIWindow *keyWindow = nil; 15 | NSArray *windows = [[UIApplication sharedApplication]windows]; 16 | for (UIWindow *window in windows) { 17 | if (window.isKeyWindow) { 18 | keyWindow = window; 19 | break; 20 | } 21 | } 22 | return keyWindow.rootViewController; 23 | } 24 | @end 25 | 26 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/EncryptSecretCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // EncryptSecretCell.h 3 | // SkylinkSample 4 | // 5 | // Created by Charlie on 9/3/20. 6 | // Copyright © 2020 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | static NSString *CELL_IDENTIFIER_ENCRYPT_SECRET = @"EncryptSecretCell"; 14 | 15 | @interface EncryptSecretCell : UITableViewCell 16 | @property (weak, nonatomic) IBOutlet UITextField *secretIdField; 17 | @property (weak, nonatomic) IBOutlet UITextField *secretField; 18 | - (void)setupCell:(NSString *)secretId secret:(NSString *)secret; 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Categories/UIAlertController+Ext.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIAlertController+Ext.h 3 | // objc_SampleApp 4 | // 5 | // Created by Charlie on 18/11/19. 6 | // Copyright © 2019 Charlie. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface UIAlertController(Ext) 14 | void showAlert(NSString *title, NSString* message); 15 | void showAlertTouchDismiss(NSString *title, NSString* message); 16 | void showAlertAutoDismiss(NSString *title, NSString *message, float duration, UIViewController *vc); 17 | + (void)showAlertWithAutoDisappearTitle:(NSString *)title message:(NSString *)message duration:(CGFloat)duration onViewController:(UIViewController *)viewController; 18 | @end 19 | 20 | //NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | #Pods/ 27 | SkylinkSample.xcscmblueprint 28 | 29 | # Fabric 30 | # 31 | # User will add their own fabric.sh if desired. 32 | # Refer to README.md for details on adding this file. 33 | SampleAppObjectiveC/fabric.sh -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Peer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Peer.h 3 | // SkylinkSample 4 | // 5 | // Created by Yuxi Liu on 13/9/19. 6 | // Copyright © 2019 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface Peer : NSObject 15 | @property(nonatomic, copy) NSString *peerId; 16 | @property(nonatomic, copy) NSString *userName; 17 | @property(nonatomic, weak) UIView *videoView; 18 | @property(nonatomic, assign) CGSize videoSize; 19 | @property(nonatomic, strong) NSMutableArray *medias; 20 | - (instancetype)initWithPeerID:(NSString *)peerId; 21 | - (instancetype)initWithPeerID:(NSString *)peerId userName:(NSString *)userName videoView:(UIView *)videoView videoSize:(CGSize)videoSize; 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Peer.m: -------------------------------------------------------------------------------- 1 | // 2 | // Peer.m 3 | // SkylinkSample 4 | // 5 | // Created by Yuxi Liu on 13/9/19. 6 | // Copyright © 2019 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #import "Peer.h" 10 | 11 | @implementation Peer 12 | - (instancetype)initWithPeerID:(NSString *)peerId{ 13 | if (self = [super init]) { 14 | self.peerId = peerId; 15 | self.medias = [NSMutableArray new]; 16 | } 17 | return self; 18 | } 19 | - (instancetype)initWithPeerID:(NSString *)peerId userName:(NSString *)userName videoView:(UIView *)videoView videoSize:(CGSize)videoSize 20 | { 21 | if (self = [super init]) { 22 | self.peerId = peerId; 23 | self.userName = userName; 24 | self.videoView = videoView; 25 | self.videoSize = videoSize; 26 | self.medias = [NSMutableArray new]; 27 | } 28 | return self; 29 | } 30 | @end 31 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/SAMessage.h: -------------------------------------------------------------------------------- 1 | // 2 | // SAMessage.h 3 | // SkylinkSample 4 | // 5 | // Created by Charlie on 9/3/20. 6 | // Copyright © 2020 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | typedef enum SAMessageType{ 14 | SAMessageTypeSignaling, 15 | SAMessageTypeP2P 16 | }SAMessageType; 17 | 18 | @interface SAMessage : NSObject 19 | @property(strong, nonatomic)NSString *data; 20 | @property(assign, nonatomic)long long timeStamp; 21 | @property(strong, nonatomic)NSString *sender; 22 | @property(strong, nonatomic)NSString *target; 23 | @property(assign, nonatomic)SAMessageType type; 24 | 25 | - (instancetype)initWithData:(NSString *)data timeStamp:(long long)timeStamp sender:(nullable NSString *)sender target:(nullable NSString *)target type:(SAMessageType)type; 26 | - (NSString *)typeToString; 27 | - (BOOL)isPublic; 28 | - (NSString *)isPublicString; 29 | - (NSString *)timeStampString; 30 | @end 31 | 32 | NS_ASSUME_NONNULL_END 33 | 34 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/StatsView.h: -------------------------------------------------------------------------------- 1 | // 2 | // StatsView.h 3 | // SkylinkSample 4 | // 5 | // Created by Yuxi Liu on 5/9/19. 6 | // Copyright © 2019 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | typedef enum Status { 14 | StatusInput = 0, 15 | StatusSent, 16 | StatusReceived, 17 | StatusAll 18 | } Status; 19 | 20 | @interface Stats : NSObject 21 | @property(nonatomic, copy) NSString *inputWidth; 22 | @property(nonatomic, copy) NSString *inputHeight; 23 | @property(nonatomic, copy) NSString *inputFPS; 24 | @property(nonatomic, copy) NSString *sentWidth; 25 | @property(nonatomic, copy) NSString *sentHeight; 26 | @property(nonatomic, copy) NSString *sentFPS; 27 | @property(nonatomic, copy) NSString *receivedWidth; 28 | @property(nonatomic, copy) NSString *receivedHeight; 29 | @property(nonatomic, copy) NSString *receivedFPS; 30 | 31 | - (instancetype)initWithDict:(NSDictionary *)dict; 32 | @end 33 | 34 | @class Stats; 35 | @interface StatsView : UIView 36 | - (void)setupViewWithStats:(Stats *)stats status:(Status)status; 37 | @end 38 | 39 | NS_ASSUME_NONNULL_END 40 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/SAMessage.m: -------------------------------------------------------------------------------- 1 | // 2 | // SAMessage.m 3 | // SkylinkSample 4 | // 5 | // Created by Charlie on 9/3/20. 6 | // Copyright © 2020 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #import "SAMessage.h" 10 | 11 | @implementation SAMessage 12 | - (instancetype)initWithData:(NSString *)data timeStamp:(long long)timeStamp sender:(NSString *)sender target:(NSString *)target type:(SAMessageType)type{ 13 | if(self = [super init]){ 14 | self.data = data; 15 | self.timeStamp = timeStamp; 16 | self.sender = sender; 17 | self.target = target; 18 | self.type = type; 19 | } 20 | return self; 21 | } 22 | - (NSString *)typeToString{ 23 | if (self.type == SAMessageTypeSignaling) { 24 | return @"Signaling"; 25 | }else{ 26 | return @"P2P"; 27 | 28 | } 29 | } 30 | - (BOOL)isPublic{ 31 | return (self.target == nil); 32 | } 33 | - (NSString *)isPublicString{ 34 | return self.isPublic ? @"Public" : @"Private"; 35 | } 36 | - (NSString *)timeStampString{ 37 | NSDate *dateTS = [NSDate dateFromTimeStamp:self.timeStamp]; 38 | return [dateTS skylinkDateString]; 39 | } 40 | @end 41 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/EncryptSecretCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // EncryptSecretCell.m 3 | // SkylinkSample 4 | // 5 | // Created by Charlie on 9/3/20. 6 | // Copyright © 2020 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #import "EncryptSecretCell.h" 10 | #import "Constant.h" 11 | @implementation EncryptSecretCell 12 | 13 | - (void)awakeFromNib { 14 | [super awakeFromNib]; 15 | // Initialization code 16 | } 17 | 18 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated { 19 | [super setSelected:selected animated:animated]; 20 | 21 | // Configure the view for the selected state 22 | } 23 | - (void)setupCell:(NSString *)secretId secret:(NSString *)secret { 24 | _secretIdField.text = secretId; 25 | _secretField.text = secret; 26 | } 27 | - (void)textFieldDidChangeSelection:(UITextField *)textField{ 28 | if (textField == _secretField){ 29 | [SAConstants.shared.ENCRYPTION_SECRETS setObject:textField.text forKey:_secretIdField.text]; 30 | } 31 | } 32 | - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{ 33 | if (textField == _secretField) { 34 | NSString *_updateString = [textField.text stringByReplacingCharactersInRange:range withString:string]; 35 | SAConstants.shared.ENCRYPTION_SECRETS[_secretIdField.text] = _updateString; 36 | } 37 | return YES; 38 | } 39 | @end 40 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Categories/NSDate+Ext.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+Ext.m 3 | // SkylinkSample 4 | // 5 | // Created by Charlie on 9/3/20. 6 | // Copyright © 2020 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #import "NSDate+Ext.h" 10 | 11 | @implementation NSDate(Ext) 12 | - (long long)toTimeStamp{ 13 | return (long long) (self.timeIntervalSince1970 * 1000); 14 | } 15 | + (NSDate *)dateFromTimeStamp:(long long)timeStamp{ 16 | return [NSDate dateWithTimeIntervalSince1970:(double)timeStamp]; 17 | } 18 | 19 | - (NSString *)skylinkDateString{ 20 | return [NSDate stringFromDate:self format:@"yyyy-MM-dd'T'HH:mm:ss.0'Z'"]; 21 | } 22 | + (NSDate*)skylinkDateFromString:(NSString*)string{ 23 | return [NSDate dateFromString:string format:@"yyyy-MM-dd'T'HH:mm:ss.0'Z'"]; 24 | } 25 | 26 | + (NSString*)stringFromDate:(NSDate*)date format:(NSString*)format{ 27 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 28 | [dateFormatter setTimeZone:[NSTimeZone systemTimeZone]]; 29 | dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; // IOS-429 30 | [dateFormatter setDateFormat:format]; 31 | return [dateFormatter stringFromDate:date]; 32 | } 33 | + (NSDate*)dateFromString:(NSString*)string format:(NSString*)format{ 34 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 35 | [dateFormatter setTimeZone:[NSTimeZone systemTimeZone]]; 36 | dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; // IOS-429 37 | [dateFormatter setDateFormat:format]; 38 | return [dateFormatter dateFromString:string]; 39 | } 40 | @end 41 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/SettingCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // SettingCell.m 3 | // SkylinkSample 4 | // 5 | // Created by Temasys on 5/9/19. 6 | // Copyright © 2019 Temasys. All rights reserved. 7 | // 8 | 9 | #import "SettingCell.h" 10 | #import "Constant.h" 11 | 12 | //static NSString *CELL_IDENTIFIER = @"SettingCell"; 13 | //static SAConstants *shared = nil; 14 | 15 | @interface SettingCell() 16 | @property (weak, nonatomic) IBOutlet UILabel *keyLabel; 17 | @property (weak, nonatomic) IBOutlet UITextField *valueField; 18 | @end 19 | 20 | @implementation SettingCell 21 | 22 | - (void)awakeFromNib { 23 | [super awakeFromNib]; 24 | // Initialization code 25 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged) name:UITextFieldTextDidChangeNotification object:nil]; 26 | } 27 | 28 | - (void)setupCellWithKey:(NSString *)key value:(NSString *)value 29 | { 30 | self.keyLabel.text = key; 31 | self.valueField.text = value; 32 | } 33 | 34 | - (void)textChanged 35 | { 36 | if (!self.valueField.text || [self.valueField.text containsString:@" "]) { 37 | MyLog(@"Room name not valid"); 38 | } else { 39 | if ([self.keyLabel.text isEqualToString:@"App Key"]) APP_KEY = self.valueField.text; 40 | else if ([self.keyLabel.text isEqualToString:@"App Secret"]) APP_SECRET = self.valueField.text; 41 | else if ([self.keyLabel.text isEqualToString:@"1-1 video call"]) ROOM_ONE_TO_ONE_VIDEO = self.valueField.text; 42 | else if ([self.keyLabel.text isEqualToString:@"Multi video call"]) ROOM_MULTI_VIDEO = self.valueField.text; 43 | else if ([self.keyLabel.text isEqualToString:@"Audio call"]) ROOM_AUDIO = self.valueField.text; 44 | else if ([self.keyLabel.text isEqualToString:@"Messages"]) ROOM_MESSAGES = self.valueField.text; 45 | else if ([self.keyLabel.text isEqualToString:@"File transfer"]) ROOM_FILE_TRANSFER = self.valueField.text; 46 | else if ([self.keyLabel.text isEqualToString:@"Data transfer"]) ROOM_DATA_TRANSFER = self.valueField.text; 47 | } 48 | } 49 | @end 50 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Sample App 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(CURRENT_PROJECT_VERSION) 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | NSCameraUsageDescription 33 | Perform webRTC video calls 34 | NSMicrophoneUsageDescription 35 | Perform webRTC video & audio calls 36 | NSPhotoLibraryAddUsageDescription 37 | Save and transfer photos and videos 38 | NSPhotoLibraryUsageDescription 39 | Save images & videos received via webRTC file transfer 40 | UIBackgroundModes 41 | 42 | voip 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Categories/UIView+Ext.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Ext.m 3 | // SkylinkSample 4 | // 5 | // Created by Charlie on 25/11/19. 6 | // Copyright © 2019 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #import "UIView+Ext.h" 10 | 11 | @implementation UIView(Ext) 12 | - (void)removeSubviews{ 13 | while (self.subviews.count>0) { 14 | [self.subviews.firstObject removeFromSuperview]; 15 | } 16 | } 17 | - (void)aspectFitRectForSize:(CGSize)insideSize container:(UIView*)container{ 18 | if (!container || 19 | insideSize.width<=0 || 20 | insideSize.height<=0) { 21 | return; 22 | } 23 | 24 | CGFloat originRate = insideSize.width/insideSize.height; 25 | CGFloat containerRate = container.frame.size.width/container.frame.size.height; 26 | CGRect resultFrame = CGRectMake(0, 0, 0, 0); 27 | if (originRate > containerRate){ 28 | resultFrame.size.width = container.frame.size.width; 29 | resultFrame.size.height = container.frame.size.width/originRate; 30 | }else{ 31 | resultFrame.size.height = container.frame.size.height; 32 | resultFrame.size.width = container.frame.size.height*originRate; 33 | } 34 | resultFrame.origin.x = container.frame.size.width/2 - resultFrame.size.width/2; 35 | resultFrame.origin.y = container.frame.size.height/2 - resultFrame.size.height/2; 36 | self.frame = resultFrame; 37 | } 38 | - (void)aspectFillRectForSize:(CGSize)insideSize container:(UIView*)container{ 39 | if (!container || 40 | insideSize.width<=0 || 41 | insideSize.height<=0) { 42 | return; 43 | } 44 | self.frame = container.frame; 45 | /*var maxFloat: CGFloat = 0 46 | if container.frame.size.height > container.frame.size.width { 47 | maxFloat = container.frame.size.height 48 | } else if container.frame.size.height < container.frame.size.width { 49 | maxFloat = container.frame.size.width 50 | } else { 51 | maxFloat = 0 52 | } 53 | var aspectRatio: CGFloat = 0 54 | if insideSize.height != 0 { 55 | aspectRatio = insideSize.width / insideSize.height 56 | } else { 57 | aspectRatio = 1 58 | } 59 | var frame = CGRect(x: 0, y: 0, width: container.frame.size.width, height: container.frame.size.height) 60 | if insideSize.width < insideSize.height { 61 | frame.size.width = maxFloat 62 | frame.size.height = frame.size.width / aspectRatio 63 | } else { 64 | frame.size.height = maxFloat; 65 | frame.size.width = frame.size.height * aspectRatio 66 | } 67 | frame.origin.x = (container.frame.size.width - frame.size.width) / 2 68 | frame.origin.y = (container.frame.size.height - frame.size.height) / 2 69 | self.frame = frame*/ 70 | } 71 | @end 72 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "SkylinkAppLogo4-29@2x.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "SkylinkAppLogo4-29@3x.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "SkylinkAppLogo4-40@2x.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "SkylinkAppLogo4-40@3x.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "SkylinkAppLogo4-60@2x.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "SkylinkAppLogo4-60@3x.png", 47 | "scale" : "3x" 48 | }, 49 | { 50 | "idiom" : "ipad", 51 | "size" : "20x20", 52 | "scale" : "1x" 53 | }, 54 | { 55 | "idiom" : "ipad", 56 | "size" : "20x20", 57 | "scale" : "2x" 58 | }, 59 | { 60 | "size" : "29x29", 61 | "idiom" : "ipad", 62 | "filename" : "SkylinkAppLogo4-29.png", 63 | "scale" : "1x" 64 | }, 65 | { 66 | "size" : "29x29", 67 | "idiom" : "ipad", 68 | "filename" : "SkylinkAppLogo4-29@2x-1.png", 69 | "scale" : "2x" 70 | }, 71 | { 72 | "size" : "40x40", 73 | "idiom" : "ipad", 74 | "filename" : "SkylinkAppLogo4-40.png", 75 | "scale" : "1x" 76 | }, 77 | { 78 | "size" : "40x40", 79 | "idiom" : "ipad", 80 | "filename" : "SkylinkAppLogo4-40@2x-1.png", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "size" : "76x76", 85 | "idiom" : "ipad", 86 | "filename" : "SkylinkAppLogo4-76.png", 87 | "scale" : "1x" 88 | }, 89 | { 90 | "size" : "76x76", 91 | "idiom" : "ipad", 92 | "filename" : "SkylinkAppLogo4-76@2x.png", 93 | "scale" : "2x" 94 | }, 95 | { 96 | "size" : "83.5x83.5", 97 | "idiom" : "ipad", 98 | "filename" : "SkylinkAppLogo4-83_5@2x.png", 99 | "scale" : "2x" 100 | }, 101 | { 102 | "idiom" : "ios-marketing", 103 | "size" : "1024x1024", 104 | "scale" : "1x" 105 | } 106 | ], 107 | "info" : { 108 | "version" : 1, 109 | "author" : "xcode" 110 | } 111 | } -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // SkylinkSample/Users/romainpellen/Desktop/Bitbucket/Temasys/SkylinkSDK-iOS-Sample/SkylinkSample/SkylinkSample 4 | // 5 | // Created by Temasys on 01/02/2016. 6 | // Copyright © 2016 Temasys. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import 11 | #import "Constant.h" 12 | 13 | @interface AppDelegate () 14 | 15 | @end 16 | 17 | @implementation AppDelegate 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | signal(SIGPIPE, SIG_IGN); // Resolve SIGPIPE, as sugested by Apple doc. 21 | MyLog(@"- SKYLINK SampleApp launched -"); 22 | return YES; 23 | } 24 | 25 | - (void)applicationWillResignActive:(UIApplication *)application { 26 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 27 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 28 | } 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | - (void)applicationWillEnterForeground:(UIApplication *)application { 36 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 37 | } 38 | 39 | - (void)applicationDidBecomeActive:(UIApplication *)application { 40 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 41 | } 42 | 43 | - (void)applicationWillTerminate:(UIApplication *)application { 44 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 45 | } 46 | 47 | - (void)createFolder 48 | { 49 | NSArray *allDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 50 | NSString *documentDirectory = allDirectories[0]; 51 | appFilesFolder = [documentDirectory stringByAppendingPathComponent:@"app_files"]; 52 | if (![[NSFileManager defaultManager] fileExistsAtPath:appFilesFolder]) { 53 | NSError *error; 54 | [[NSFileManager defaultManager] createDirectoryAtURL:[NSURL URLWithString:appFilesFolder] withIntermediateDirectories:YES attributes:nil error:&error]; 55 | if (error) MyLog(@"%@", error.localizedDescription); 56 | } 57 | } 58 | @end 59 | 60 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Constant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Constant.h 3 | // SkylinkSample 4 | // 5 | // Created by Temasys on 5/9/19. 6 | // Copyright © 2019 Temasys. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | extern NSString *APP_KEY; 13 | extern NSString *APP_SECRET; 14 | extern NSString *ROOM_NAME; 15 | //extern NSDictionary *APP_KEYS; 16 | 17 | extern NSString *ROOM_ONE_TO_ONE_VIDEO; 18 | extern NSString *ROOM_MULTI_VIDEO; 19 | extern NSString *ROOM_AUDIO; 20 | extern NSString *ROOM_MESSAGES; 21 | extern NSString *ROOM_FILE_TRANSFER; 22 | extern NSString *ROOM_DATA_TRANSFER; 23 | 24 | extern NSString *appFilesFolder; 25 | 26 | //NSDictionary* APP_KEYS(void); 27 | //NSDictionary* ENCRYPTION_SECRETS(void); 28 | #define USER_NAME UIDevice.currentDevice.name 29 | 30 | 31 | #define SCREEN_HEIGHT [[UIScreen mainScreen] bounds].size.height 32 | #define SCREEN_WIDTH [[UIScreen mainScreen] bounds].size.width 33 | 34 | 35 | #define iPhone4 [[UIScreen mainScreen] bounds].size.height == 480 36 | #define iPhone5 [[UIScreen mainScreen] bounds].size.height == 568 37 | #define iPhone6 [[UIScreen mainScreen] bounds].size.height == 667 38 | #define iPhone6Plus [[UIScreen mainScreen] bounds].size.height == 736 39 | #define iPhoneX [[UIScreen mainScreen] bounds].size.height == 812 40 | #define iPhoneXR_XSMAX [[UIScreen mainScreen] bounds].size.height == 896 41 | 42 | 43 | #define iOS7 [[UIDevice currentDevice] systemVersion].floatValue < 8.0 44 | #define iOS8 [[UIDevice currentDevice] systemVersion].floatValue >= 8.0 && [[UIDevice currentDevice] systemVersion].floatValue < 9.0 45 | #define iOS9 [[UIDevice currentDevice] systemVersion].floatValue >= 9.0 && [[UIDevice currentDevice] systemVersion].floatValue < 10.0 46 | #define iOS10 [[UIDevice currentDevice] systemVersion].floatValue >= 10.0 && [[UIDevice currentDevice] systemVersion].floatValue < 11.0 47 | #define iOS11 [[UIDevice currentDevice] systemVersion].floatValue >= 11.0 && [[UIDevice currentDevice] systemVersion].floatValue < 12.0 48 | #define iOS12 [[UIDevice currentDevice] systemVersion].floatValue >= 12.0 && [[UIDevice currentDevice] systemVersion].floatValue < 13.0 49 | #define iOS13 [[UIDevice currentDevice] systemVersion].floatValue >= 13.0 && [[UIDevice currentDevice] systemVersion].floatValue < 14.0 50 | #define iOS11Above [[UIDevice currentDevice] systemVersion].floatValue >= 11.0 51 | 52 | 53 | #define UIRGBColor(r, g, b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1.0] 54 | #define UIRGBAColor(r, g, b, alpha) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:alpha] 55 | #define UIRandomColor UIRGBColor(arc4random_uniform(256), arc4random_uniform(256), arc4random_uniform(256)) 56 | #define UIGlobalBackgroundColor UIRGBColor(242, 243, 244) 57 | #define UIGlobalTextLightColor UIRGBColor(155, 155, 155) 58 | #define UIGlobalTextDarkColor UIRGBColor(74, 74, 74) 59 | 60 | #ifdef DEBUG 61 | #define MyLog(...) NSLog(__VA_ARGS__) 62 | #else 63 | #define MyLog(...) 64 | #endif 65 | 66 | @interface SAConstants : NSObject 67 | +(instancetype)shared; 68 | @property(strong, nonatomic) NSDictionary *APP_KEYS; 69 | @property(strong, nonatomic) NSMutableDictionary *ENCRYPTION_SECRETS; 70 | + (void)switchOutput; 71 | @end 72 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample.xcodeproj/xcshareddata/xcschemes/SkylinkSample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Constant.m: -------------------------------------------------------------------------------- 1 | // 2 | // Constant.m 3 | // SkylinkSample 4 | // 5 | // Created by Temasys on 5/9/19. 6 | // Copyright © 2019 Temasys. All rights reserved. 7 | // 8 | 9 | #import "Constant.h" 10 | #import 11 | 12 | NSString *APP_KEY = @"Enter AppKey"; 13 | NSString *APP_SECRET = @"Enter AppSecret"; 14 | NSString *ROOM_NAME = @"rt"; 15 | 16 | NSString *ROOM_ONE_TO_ONE_VIDEO = @"rt"; 17 | NSString *ROOM_MULTI_VIDEO = @"ROOM_MULTI_VIDEO"; 18 | NSString *ROOM_AUDIO = @"ROOM_AUDIO"; 19 | NSString *ROOM_MESSAGES = @"MESSAGES-ROOM"; 20 | NSString *ROOM_FILE_TRANSFER = @"ROOM_FILE_TRANSFER"; 21 | NSString *ROOM_DATA_TRANSFER = @"ROOM_DATA_TRANSFER"; 22 | 23 | NSString *appFilesFolder = @""; 24 | @implementation SAConstants 25 | 26 | + (instancetype)shared{ 27 | static SAConstants *shared = nil; 28 | static dispatch_once_t onceToken; 29 | dispatch_once(&onceToken, ^{ 30 | shared = [[self alloc] init]; 31 | //initial keys 32 | shared.APP_KEYS = @{@"APP_KEY1": @"APP_SECRET1", 33 | @"APP_KEY2": @"APP_SECRET2", 34 | @"APP_KEY3": @"APP_SECRET3"}; 35 | shared.ENCRYPTION_SECRETS = [NSMutableDictionary dictionaryWithDictionary: @{@"key1": @"secret1", 36 | @"key2": @"secret2", 37 | @"key3": @"secret3"}]; 38 | }); 39 | return shared; 40 | } 41 | 42 | + (void)switchOutput 43 | { 44 | AVAudioSessionPortDescription *builtInPortDescription = [AVAudioSessionPortDescription new]; 45 | AVAudioSessionPortDescription *bluetoothPortDescription = [AVAudioSessionPortDescription new]; 46 | BOOL isBluetoothPortDescriptionAssigned = NO; 47 | NSArray *availableInputs = [AVAudioSession sharedInstance].availableInputs; 48 | for (AVAudioSessionPortDescription *description in availableInputs) { 49 | if (description.portType == AVAudioSessionPortBuiltInMic) builtInPortDescription = description; 50 | if (description.portType == AVAudioSessionPortBluetoothLE || description.portType == AVAudioSessionPortBluetoothHFP || description.portType == AVAudioSessionPortBluetoothA2DP) { 51 | builtInPortDescription = description; 52 | isBluetoothPortDescriptionAssigned = YES; 53 | } 54 | } 55 | NSArray *dataSources = builtInPortDescription.dataSources; 56 | for (AVAudioSessionDataSourceDescription *description in dataSources) { 57 | if (description.orientation == AVAudioSessionOrientationFront || description.orientation == AVAudioSessionOrientationBottom || description.orientation == AVAudioSessionOrientationBack) { 58 | NSError *error; 59 | [bluetoothPortDescription setPreferredDataSource:description error:&error]; 60 | if (error) MyLog(@"bluetoothPortDescription setPreferredDataSource error ---> %@", error.localizedDescription); 61 | break; 62 | } 63 | } 64 | NSError *error; 65 | isBluetoothPortDescriptionAssigned ? [[AVAudioSession sharedInstance] setPreferredInput:bluetoothPortDescription error:&error] : [[AVAudioSession sharedInstance] setPreferredInput:(builtInPortDescription) error:&error]; 66 | MyLog(@"bluetoothPortDescription setPreferredInput error ---> %@", error.localizedDescription); 67 | } 68 | @end 69 | 70 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Categories/UIAlertController+Ext.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIAlertController+Ext.m 3 | // objc_SampleApp 4 | // 5 | // Created by Charlie on 18/11/19. 6 | // Copyright © 2019 Charlie. All rights reserved. 7 | // 8 | 9 | #import "UIAlertController+Ext.h" 10 | #import "Utils.h" 11 | 12 | //@interface UIAlertController(Ext) 13 | // 14 | //@end 15 | 16 | @implementation UIAlertController(Ext) 17 | 18 | void showAlert(NSString *title, NSString* message){ 19 | UIAlertController * _alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; 20 | UIAlertAction *_okBtn = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; 21 | [_alert addAction:_okBtn]; 22 | [topVC() presentViewController:_alert animated:YES completion:nil]; 23 | } 24 | void showAlertTouchDismiss(NSString *title, NSString* message){ 25 | UIAlertController * _alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; 26 | [topVC() presentViewController:_alert animated:YES completion:^{ 27 | [_alert.view.superview setUserInteractionEnabled:YES]; 28 | [_alert.view.superview addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:_alert action:@selector(touchToDismiss)]]; 29 | UIView *bg = [[UIView alloc] initWithFrame:_alert.view.bounds]; 30 | bg.backgroundColor = [UIColor clearColor]; 31 | [_alert.view addSubview:bg]; 32 | [_alert.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:_alert action:@selector(touchToDismiss)]]; 33 | }]; 34 | } 35 | - (void)touchToDismiss{ 36 | [self dismissViewControllerAnimated:YES completion:nil]; 37 | } 38 | void showAlertAutoDismiss(NSString *title, NSString *message, float duration, UIViewController *vc){ 39 | UIAlertController * _alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; 40 | [vc presentViewController:_alert animated:YES completion:nil]; 41 | // __weak typeof(vc) _weakVC = vc; 42 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 43 | [_alert dismissViewControllerAnimated:YES completion:nil]; 44 | 45 | }); 46 | } 47 | + (void)showAlertWithAutoDisappearTitle:(NSString *)title message:(NSString *)message duration:(CGFloat)duration onViewController:(UIViewController *)viewController 48 | { 49 | UIAlertController *alertVc = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; 50 | [viewController presentViewController:alertVc animated:YES completion:^{ 51 | }]; 52 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 53 | [alertVc dismissViewControllerAnimated:YES completion:^{ 54 | }]; 55 | }); 56 | } 57 | /* 58 | #pragma mark - Navigation 59 | 60 | // In a storyboard-based application, you will often want to do a little preparation before navigation 61 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 62 | // Get the new view controller using [segue destinationViewController]. 63 | // Pass the selected object to the new view controller. 64 | } 65 | */ 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/StatsView.m: -------------------------------------------------------------------------------- 1 | // 2 | // StatsView.m 3 | // SkylinkSample 4 | // 5 | // Created by Yuxi Liu on 5/9/19. 6 | // Copyright © 2019 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #import "StatsView.h" 10 | 11 | @implementation Stats 12 | 13 | - (instancetype)initWithDict:(NSDictionary *)dict 14 | { 15 | if (self = [super init]) { 16 | self.inputWidth = dict[@"FrameWidthInput"] ? dict[@"FrameWidthInput"] : @"480"; 17 | self.inputHeight = dict[@"FrameHeightInput"] ? dict[@"FrameHeightInput"] : @"640"; 18 | self.inputFPS = dict[@"FrameRateInput"] ? dict[@"FrameRateInput"] : @"30"; 19 | self.sentWidth = dict[@"FrameWidthSent"] ? dict[@"FrameWidthSent"] : @"0"; 20 | self.sentHeight = dict[@"FrameHeightSent"] ? dict[@"FrameHeightSent"] : @"0"; 21 | self.sentFPS = dict[@"FrameRateSent"] ? dict[@"FrameRateSent"] : @"0"; 22 | self.receivedWidth = dict[@"FrameWidthReceived"] ? dict[@"FrameWidthReceived"] : @"0"; 23 | self.receivedHeight = dict[@"FrameHeightReceived"] ? dict[@"FrameHeightReceived"] : @"0"; 24 | self.receivedFPS = dict[@"FrameRateReceived"] ? dict[@"FrameRateReceived"] : @"0"; 25 | } 26 | return self; 27 | } 28 | @end 29 | 30 | @interface StatsView() 31 | @property (weak, nonatomic) IBOutlet UILabel *inputWidthLabel; 32 | @property (weak, nonatomic) IBOutlet UILabel *inputHeightLabel; 33 | @property (weak, nonatomic) IBOutlet UILabel *inputFPSLabel; 34 | @property (weak, nonatomic) IBOutlet UILabel *sentWidthLabel; 35 | @property (weak, nonatomic) IBOutlet UILabel *sentHeightLabel; 36 | @property (weak, nonatomic) IBOutlet UILabel *sentFPSLabel; 37 | @property (weak, nonatomic) IBOutlet UILabel *receivedWidthLabel; 38 | @property (weak, nonatomic) IBOutlet UILabel *receivedHeightLabel; 39 | @property (weak, nonatomic) IBOutlet UILabel *receivedFPSLabel; 40 | @end 41 | 42 | @implementation StatsView 43 | 44 | - (void)setupViewWithStats:(Stats *)stats status:(Status)status 45 | { 46 | dispatch_async(dispatch_get_main_queue(), ^{ 47 | if (status == StatusInput) { 48 | self.inputWidthLabel.text = stats.inputWidth; 49 | self.inputHeightLabel.text = stats.inputHeight; 50 | self.inputFPSLabel.text = stats.inputFPS; 51 | } 52 | if (status == StatusSent) { 53 | self.sentWidthLabel.text = stats.sentWidth; 54 | self.sentHeightLabel.text = stats.sentHeight; 55 | self.sentFPSLabel.text = stats.sentFPS; 56 | } 57 | if (status == StatusReceived) { 58 | self.receivedWidthLabel.text = stats.receivedWidth; 59 | self.receivedHeightLabel.text = stats.receivedHeight; 60 | self.receivedFPSLabel.text = stats.receivedFPS; 61 | } 62 | if (status == StatusAll) { 63 | self.inputWidthLabel.text = stats.inputWidth; 64 | self.inputHeightLabel.text = stats.inputHeight; 65 | self.inputFPSLabel.text = stats.inputFPS; 66 | self.sentWidthLabel.text = stats.sentWidth; 67 | self.sentHeightLabel.text = stats.sentHeight; 68 | self.sentFPSLabel.text = stats.sentFPS; 69 | self.receivedWidthLabel.text = stats.receivedWidth; 70 | self.receivedHeightLabel.text = stats.receivedHeight; 71 | self.receivedFPSLabel.text = stats.receivedFPS; 72 | } 73 | }); 74 | } 75 | @end 76 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/BaseVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // BaseVC.m 3 | // SkylinkSample 4 | // 5 | // Created by Charlie on 26/11/19. 6 | // Copyright © 2019 Romain Pellen. All rights reserved. 7 | // 8 | 9 | #import "BaseVC.h" 10 | #import "AudioCallViewController.h" 11 | #import "VideoCallViewController.h" 12 | #import "MessagesViewController.h" 13 | #import "MultiVideoCallViewController.h" 14 | #import "FileTransferViewController.h" 15 | #import "DataTransferViewController.h" 16 | 17 | @interface BaseVC () 18 | 19 | @end 20 | 21 | @implementation BaseVC 22 | 23 | - (void)viewDidLoad { 24 | [super viewDidLoad]; 25 | [self initUI]; 26 | [self setupRoomName]; 27 | } 28 | 29 | - (void)initUI{ 30 | self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"Cancel.png"] style:UIBarButtonItemStylePlain target:self action:@selector(backToMainMenu)]; 31 | UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeInfoLight]; 32 | [infoButton addTarget:self action:@selector(showInfo) forControlEvents:UIControlEventTouchUpInside]; 33 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:infoButton]; 34 | } 35 | - (void)setupRoomName{ 36 | if ([ROOM_NAME isNotEmpty]) { 37 | roomName = ROOM_NAME; 38 | return; 39 | } 40 | if ([self isKindOfClass:[AudioCallViewController class]]) { 41 | roomName = ROOM_AUDIO; return; 42 | } 43 | if ([self isKindOfClass:[VideoCallViewController class]]) { 44 | roomName = ROOM_ONE_TO_ONE_VIDEO; return; 45 | } 46 | if ([self isKindOfClass:[MessagesViewController class]]) { 47 | roomName = ROOM_MESSAGES; return; 48 | } 49 | if ([self isKindOfClass:[MultiVideoCallViewController class]]) { 50 | roomName = ROOM_MULTI_VIDEO; return; 51 | } 52 | if ([self isKindOfClass:[FileTransferViewController class]]) { 53 | roomName = ROOM_FILE_TRANSFER; return; 54 | } 55 | if ([self isKindOfClass:[DataTransferViewController class]]) { 56 | roomName = ROOM_DATA_TRANSFER; return; 57 | } 58 | } 59 | - (void)showInfo { 60 | showAlertTouchDismiss([NSString stringWithFormat:@"%@", NSStringFromClass([self class])], [NSString stringWithFormat:@"\nRoom name:\n%@\n\nLocal ID:\n%@\n\nKey: •••••%@\n\nSkylink version %@", roomName, _skylinkConnection.localPeerId, [APP_KEY substringFromIndex: [APP_KEY length] - 7], [SKYLINKConnection getSkylinkVersion]]); 61 | // showAlert([NSString stringWithFormat:@"%@ infos", NSStringFromClass([self class])], [NSString stringWithFormat:@"\nRoom name:\n%@\n\nLocal ID:\n%@\n\nKey: •••••%@\n\nSkylink version %@", roomName, _skylinkConnection.localPeerId, [APP_KEY substringFromIndex: [APP_KEY length] - 7], [SKYLINKConnection getSkylinkVersion]]); 62 | } 63 | - (void)backToMainMenu{ 64 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 65 | [_skylinkConnection unlockTheRoom:nil]; 66 | [_skylinkConnection disconnect:^(NSError * _Nullable error) { 67 | if (!error) { 68 | [self.navigationController popViewControllerAnimated:YES]; 69 | } 70 | }]; 71 | } 72 | - (void)startLocalMediaDevice:(SKYLINKMediaDevice)mediaDevice{ 73 | [_skylinkConnection createLocalMediaWithMediaDevice:mediaDevice mediaMetadata:@"" callback:^(NSError * _Nullable error) { 74 | if (error) { 75 | showAlertAutoDismiss(@"Error", error.localizedDescription, 2.0, self); 76 | } 77 | }]; 78 | } 79 | - (void)joinRoom{ 80 | [_skylinkConnection connectToRoomWithAppKey:APP_KEY secret:APP_SECRET roomName:roomName userData:USER_NAME callback:^(NSError * _Nullable error) { 81 | if (error) { 82 | NSLog(@"error: %@", error.localizedDescription); 83 | } 84 | }]; 85 | } 86 | @end 87 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/SettingCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/EncryptSecretCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/SettingsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.m 3 | // SkylinkSample 4 | // 5 | // Created by Temasys on 29/08/2016. 6 | // Copyright © 2016 Temasys. All rights reserved. 7 | // 8 | 9 | #import "SettingsViewController.h" 10 | #import "HomeViewController.h" 11 | #import "SettingCell.h" 12 | #import "EncryptSecretCell.h" 13 | #import "Constant.h" 14 | 15 | @interface SettingsViewController () 16 | 17 | @property (nonatomic, strong) NSArray *appkey_secret_keys; 18 | @property (nonatomic, strong) NSArray *appkey_secret_values; 19 | @property (nonatomic, strong) NSArray *room_name_keys; 20 | @property (nonatomic, strong) NSArray *room_name_values; 21 | @end 22 | 23 | @implementation SettingsViewController{ 24 | NSArray *_allAppKey; 25 | NSArray *_msg_encryption_secrets; 26 | } 27 | 28 | - (void)viewDidLoad { 29 | [super viewDidLoad]; 30 | // Do any additional setup after loading the view. 31 | 32 | self.title = @"Settings"; 33 | // UINib *nib = [UINib nibWithNibName:@"SettingCell" bundle:nil]; 34 | [self.tableView registerNib:[UINib nibWithNibName:@"SettingCell" bundle:nil] forCellReuseIdentifier:CELL_IDENTIFIER]; 35 | [self.tableView registerNib:[UINib nibWithNibName:@"EncryptSecretCell" bundle:nil] forCellReuseIdentifier:CELL_IDENTIFIER_ENCRYPT_SECRET]; 36 | self.appkey_secret_keys = @[@"App Key", @"App Secret"]; 37 | self.appkey_secret_values = @[APP_KEY, APP_SECRET]; 38 | self.room_name_keys = @[@"1-1 video call", @"Multi video call", @"Audio call", @"Messages", @"File transfer", @"Data transfer"]; 39 | self.room_name_values = @[ROOM_ONE_TO_ONE_VIDEO, ROOM_MULTI_VIDEO, ROOM_AUDIO, ROOM_MESSAGES, ROOM_FILE_TRANSFER, ROOM_DATA_TRANSFER]; 40 | _allAppKey = [SAConstants.shared.APP_KEYS.allKeys sortedArrayUsingSelector:@selector(compare:)]; 41 | _msg_encryption_secrets = [SAConstants.shared.ENCRYPTION_SECRETS.allKeys sortedArrayUsingSelector:@selector(compare:)]; 42 | } 43 | 44 | // Room names should not bet set to empty 45 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 46 | { 47 | return 3; 48 | } 49 | 50 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 51 | { 52 | if (section == 0) return self.appkey_secret_keys.count + 1; 53 | else if (section == 1) return _msg_encryption_secrets.count; 54 | else return self.room_name_keys.count; 55 | } 56 | 57 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 58 | { 59 | 60 | if (indexPath.section == 0 && indexPath.row == 2) { 61 | UITableViewCell *_normalCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; 62 | _normalCell.textLabel.text = @"Select App Key"; 63 | _normalCell.textLabel.textColor = [UIColor whiteColor]; 64 | _normalCell.backgroundColor = [UIColor colorWithRed:0.1764705926 green:0.01176470611 blue:0.5607843399 alpha:1]; 65 | return _normalCell; 66 | } 67 | 68 | if (indexPath.section == 0 || indexPath.section == 2) { 69 | SettingCell *cell = [tableView dequeueReusableCellWithIdentifier:CELL_IDENTIFIER forIndexPath:indexPath]; 70 | if (!cell) { 71 | cell = [[SettingCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CELL_IDENTIFIER]; 72 | } 73 | (indexPath.section == 0) ? 74 | [cell setupCellWithKey:self.appkey_secret_keys[indexPath.row] value:self.appkey_secret_values[indexPath.row]] : 75 | [cell setupCellWithKey:self.room_name_keys[indexPath.row] value:self.room_name_values[indexPath.row]]; 76 | return cell; 77 | }else{ 78 | EncryptSecretCell *_encryptCell = [tableView dequeueReusableCellWithIdentifier:CELL_IDENTIFIER_ENCRYPT_SECRET]; 79 | if (!_encryptCell) { 80 | _encryptCell = [[EncryptSecretCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CELL_IDENTIFIER_ENCRYPT_SECRET]; 81 | } 82 | [_encryptCell setupCell:_msg_encryption_secrets[indexPath.row] secret:SAConstants.shared.ENCRYPTION_SECRETS[_msg_encryption_secrets[indexPath.row]]]; 83 | return _encryptCell; 84 | } 85 | } 86 | 87 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 88 | { 89 | if (section == 0) { 90 | return @"Skylink developer credentials"; 91 | }else if (section == 1){ 92 | return @"Encrypt Secrets"; 93 | } else { 94 | return @"Room names"; 95 | } 96 | } 97 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ 98 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 99 | if ((indexPath.section == 0) && (indexPath.row == 2)){ 100 | [self selectAppKey]; 101 | } 102 | } 103 | - (void)selectAppKey{ 104 | UIAlertController *_alert = [UIAlertController alertControllerWithTitle:@"Choose a Secret App" message:nil preferredStyle:UIAlertControllerStyleAlert]; 105 | UIAlertAction *_noAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault handler:nil]; 106 | for (NSString *appKey in _allAppKey) { 107 | UIAlertAction *_yesAction = [UIAlertAction actionWithTitle:appKey style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 108 | [self selectedAppKey:appKey]; 109 | }]; 110 | [_alert addAction:_yesAction]; 111 | } 112 | [_alert addAction:_noAction]; 113 | [self presentViewController:_alert animated:YES completion:nil]; 114 | } 115 | - (void)selectedAppKey:(NSString *)key{ 116 | APP_KEY = key; 117 | APP_SECRET = SAConstants.shared.APP_KEYS[key]; 118 | self.appkey_secret_values = @[APP_KEY, APP_SECRET]; 119 | [self.tableView reloadData]; 120 | } 121 | @end 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SkylinkSDK iOS SampleApp Objective C 2 | 3 | The Sample Application(SA), which uses the latest version of the Skylink SDK for iOS, demonstrates its use to provide embedded real time communication in the easiest way. 4 | Excluding 'Settings', this App has 6 distinct view controllers, each of them demonstrating how to build the following features: 5 | 6 | - One to one video call with Screen Share 7 | - Multi party video call 8 | - Multi party audio call 9 | - Chatroom and custom messages 10 | - File transfer 11 | - Data transfer 12 | 13 | ## Code introduction 14 | The code should be self explanatory. Each view controller works by itself and there is minimal UI code due to to Storyboard usage. 15 | In each view controller, the main idea is to **configure and instantiate a connection to a room with the Skylink iOS SDK**. 16 | You will then be able to communicate with another peer joining the same room. 17 | 18 | ##### Sample Code with Video and Audio 19 | 20 | // Creating configuration 21 | SKYLINKConnectionConfig *config = [SKYLINKConnectionConfig new]; 22 | [config setAudioVideoReceiveConfig:AudioVideoConfig_AUDIO_AND_VIDEO]; 23 | [config setAudioVideoSendConfig:AudioVideoConfig_AUDIO_AND_VIDEO]; 24 | 25 | // Creating SKYLINKConnection 26 | SKYLINKConnection *skylinkConnection = [[SKYLINKConnection alloc] initWithConfig:config callback:nil]; 27 | self.skylinkConnection.lifeCycleDelegate = self; 28 | self.skylinkConnection.mediaDelegate = self; 29 | self.skylinkConnection.remotePeerDelegate = self; 30 | self.skylinkConnection = skylinkConnection; 31 | 32 | // Coonnecting to room 33 | [skylinkConnection connectToRoomWithAppKey:self.skylinkApiKey secret:self.skylinkApiSecret roomName:ROOM_NAME userData:nil callback:nil]; 34 | 35 | 36 | You can then control what happens in the room by **sending messages to the `SKYLINKConnection` instance** (like triggering a file transfer request for example), and **respond to events by implementing the delegate methods** from the 6 protocols. 37 | Always set at least the [lifeCycleDelegate](https://cdn.temasys.io/skylink/skylinksdk/ios/latest/docs/html/Protocols/SKYLINKConnectionLifeCycleDelegate.html). For a list of all protocols, see [here](https://cdn.temasys.io/skylink/skylinksdk/ios/latest/docs/html/index.html) 38 | 39 | 40 | Aditionally, in each view controller example's viewDidLoad/initWithCoder method, some properties are initialized. 41 | A disconnect button is set in the navigation bar (left corner) as well as its selector implementation (called disconnect). An info button is set on the right corner, as well as its implementation (called showInfos). Those 2 navigation bar buttons selectors are the same in every View Controller example. 42 | 43 | The rest of the example view controllers gives you 6 example usages of the Temasys iOS SDK. 44 | 45 | ## How to run the sample project 46 | 47 | ### Step-by-step guide 48 | 49 | ##### Prerequisites 50 | Please use Xcode 11 51 | 52 | ##### STEP 1 53 | It is recommended to install the SkylinkSDK for iOS via [cocoapods](http://cocoapods.org). If you do not have it installed, follow the below steps: 54 | 55 | ###### Installing Cocoapods 56 | Check that you have Xcode command line tools installed (Xcode > Preferences > Locations > Command line tools([?](http://osxdaily.com/2014/02/12/install-command-line-tools-mac-os-x/)). If not, open the terminal and run `xcode-select --install`. 57 | Install cocoa pods in the terminal: `$ sudo gem install cocoapods` 58 | 59 | ##### STEP 2 60 | Clone the repo or download the project. 61 | 62 | ##### STEP 3 63 | Navigate to the Sample App and Run `pod install` 64 | 65 | ##### STEP 4 66 | Open the .xcworkspace file 67 | 68 | ##### STEP 5 69 | Follow the instructions [here](https://temasys.io/creating-an-account-generating-a-key/) to create an App and a key on the Temasys Console. 70 | 71 | ##### STEP 6 72 | Set your App Key and secret in Constant.h. You may also alter the room names here. 73 | 74 | NSString *APP_KEY = @"ENTER APP KEY HERE"; 75 | NSString *APP_SECRET = @"ENTER SECRET HERE"; 76 | 77 | NSString *ROOM_ONE_TO_ONE_VIDEO = @"ROOM_ONE_TO_ONE_VIDEO"; 78 | NSString *ROOM_MULTI_VIDEO = @"ROOM_MULTI_VIDEO"; 79 | NSString *ROOM_AUDIO = @"ROOM_AUDIO"; 80 | NSString *ROOM_MESSAGES = @"MESSAGES-ROOM"; 81 | NSString *ROOM_FILE_TRANSFER = @"ROOM_FILE_TRANSFER"; 82 | NSString *ROOM_DATA_TRANSFER = @"ROOM_DATA_TRANSFER"; 83 | 84 | 85 | ##### STEP 7 86 | Build and Run. You're good to go! 87 | 88 | ##### Please Note 89 | The XCode Simulator does not support video calls. 90 | If you have connected a phone, ensure it is unlocked and the appropriate team is selected under Signing & Capabilities. 91 | 92 | ### Resources 93 | 94 | 95 | ##### SDK documentation 96 | For more information on the usage of the SkylinkSDK for iOS, please refer to [SkylinkSDK for iOS Readme](https://github.com/Temasys/SKYLINK-iOS/blob/master/README.md) 97 | 98 | ##### Subscribe 99 | Star this repo to be notified of new release tags. You can also view release notes on our [support portal](http://support.temasys.com.sg/en/support/solutions/folders/12000009706) 100 | 101 | ##### Feedback 102 | Please do not hesitate to reach get in touch with us if you encounter any issue or if you have any feedback or suggestions on how we can improve the Skylink SDK for iOS or Sample Applications. You can raise tickets on our [support portal](http://support.temasys.io/). 103 | 104 | ##### Copyright and License 105 | Copyright 2019 Temasys Communications Pte Ltd Licensed under APACHE 2.0 106 | 107 | 108 | #### Tutorials and FAQs 109 | 110 | [Getting started with Temasys iOS SDK for iOS](http://temasys.io/getting-started-skylinksdk-ios/) 111 | [Handle the video view stretching](http://temasys.io/a-simple-solution-for-video-stretching/) 112 | [FAQs](http://support.temasys.com.sg/support/solutions/12000000562) 113 | 114 | 115 | Also checkout our Skylink SDKs for [Web](http://skylink.io/web/) and [Android](http://skylink.io/android) 116 | 117 | *This document was edited for Temasys iOS SDK version 2.0.0* -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/HomeViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // Skylink_Examples 4 | // 5 | // Created by Temasys on 11/12/2015. 6 | // Copyright © 2015 Temasys. All rights reserved. 7 | // 8 | 9 | #import "HomeViewController.h" 10 | #import "Constant.h" 11 | 12 | /// ====== SET YOUR KEY / SECRET HERE TO HAVE IT BY DEFAULT. ====== 13 | #define SKYLINK_APP_KEY APP_KEY 14 | #define SKYLINK_SECRET APP_SECRET 15 | /// =============================================================== 16 | 17 | 18 | 19 | 20 | // Just the NSUserDefaults keys. 21 | #define USERDEFAULTS_KEY_SKYLINK_APP_KEY @"SKYLINK_APP_KEY" 22 | #define USERDEFAULTS_KEY_SKYLINK_SECRET @"SKYLINK_SECRET" 23 | 24 | 25 | 26 | @interface HomeViewController () 27 | 28 | @property (weak, nonatomic) IBOutlet UITextField *roomNameTxt; 29 | @end 30 | 31 | 32 | @implementation HomeViewController 33 | 34 | - (void)viewDidLoad { 35 | [super viewDidLoad]; 36 | _roomNameTxt.text = ROOM_NAME; 37 | } 38 | 39 | - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender { 40 | BOOL shouldPerform = true;// = (self.keyTextField.text.length == 36 && self.secretTextField.text.length == 13); 41 | if (!shouldPerform) { 42 | [UIAlertController showAlertWithAutoDisappearTitle:@"Wrong Key / Secret" message:@"\nYou haven't correctly set your \nSkylink API Key (36 characters) or Secret (13 characters)\n\nIf you don't have access to the API yet, enroll at \nconsole.temasys.io" duration:3 onViewController:self]; 43 | } else { 44 | // [[NSUserDefaults standardUserDefaults] setObject:self.keyTextField.text forKey:USERDEFAULTS_KEY_SKYLINK_APP_KEY]; 45 | // [[NSUserDefaults standardUserDefaults] setObject:self.secretTextField.text forKey:USERDEFAULTS_KEY_SKYLINK_SECRET]; 46 | // [[NSUserDefaults standardUserDefaults] synchronize]; 47 | } 48 | return shouldPerform; 49 | } 50 | 51 | #pragma clang diagnostic ignored "-Wundeclared-selector" 52 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 53 | // if ([segue.destinationViewController respondsToSelector:@selector(setSkylinkApiKey:)] && [segue.destinationViewController respondsToSelector:@selector(setSkylinkApiSecret:)]) { 54 | // [segue.destinationViewController performSelector:@selector(setSkylinkApiKey:) withObject:self.keyTextField.text]; 55 | // [segue.destinationViewController performSelector:@selector(setSkylinkApiSecret:) withObject:self.secretTextField.text]; 56 | // } 57 | } 58 | 59 | #pragma mark - IBActions (info boxes) 60 | // Information alerts only 61 | - (IBAction)vcInfoClicked:(UIButton *)sender{ 62 | NSArray *alertTitles = @[@"Audio", @"One to one Media", @"Chat", @"Mutiparty", @"File Transfer", @"Data Streaming"]; 63 | NSArray *alertMessages = @[@"\nEnter the room to make an audio call with the other peers inside the same room. Tap the button on top to mute/unmute your microphone.\n\nRefer to the view controller's code and to the documentation for more infos.", 64 | @"\nOne to one video call sample\n\nThe first 2 people to enter the room will be able to have a video call. The bottom bar contains buttons to refresh the peer connection, mute/unmute the camera, mute/unmute the mic and switch the device camera if available.\n\nRefer to the view controller's code and to the documentation for more infos.\n", 65 | @"\nEnter the room to chat with the peers in the same room. The first text field allows you to edit your nickname, the yellow button indicates the number of peers in the room: tap it to display theirs ID and nickname if available, tap the icon to hide the keyboard if needed. There is also a button to select the type of messages you want to test (P2P, signeling server or binary data), and another one to choose if you want to send your messages to all the peers in the room (public) or privatly. If not public, you will be ask what peer you want to send your private message to when tapping the send button. To send a message, enter it in the second text field and tap the send button. The messages you sent appear in green.\n\nRefer to the view controller's code and to the documentation for more infos.\n", 66 | @"\nThe first 4 people to enter the room will be able to have a multi party video call (as long as the room isn't locked). The bottom bar contains buttons to change the aspect of the peer video views, lock/unlock the room, mute/unmute the camera, mute/unmute the mic and switch the device camera if available.\n\nRefer to the view controller's code and to the documentation for more infos.\n", 67 | @"\nEnter the room to send file to the peers in the same room. To send a file to all the peers, tap the main button, to send it to a particular peer, tap the peer in the list. In both cases you will be asked the king of media you want to send and to pick it if needed.\nBehaviour will be slightly different with MCU enabled.\n\nRefer to the view controller's code and to the documentation for more infos.\n", 68 | @"\nEnter the room to send data to the peers in the same room. To send a file to all the peers, tap the main button, to send it to a particular peer, tap the peer in the list. In both cases you will be asked the king of media you want to send and to pick it if needed.\nBehaviour will be slightly different with MCU enabled.\n\nRefer to the view controller's code and to the documentation for more infos.\n"]; 69 | showAlertTouchDismiss(alertTitles[sender.tag - 10], alertMessages[sender.tag - 10]); 70 | } 71 | - (IBAction)homeInfoTap:(UIButton *)sender { 72 | [UIAlertController showAlertWithAutoDisappearTitle:@"HomeViewController" message:@"\nSet you Skylink API Key and secret in the appropriate text field or modify HomeViewController's code to have it by default.\nIf you don't have your Key/Secret, enroll at console.temasys.io\n\nIn all view controllers, you can tap the info button in the upper right corner to get the current room name, your current local ID, the current API key and the current SDK version. Refer to the documentation for more infos on how to use it.\n" duration:3 onViewController:self]; 73 | } 74 | 75 | #pragma mark - TextField Delegate 76 | - (BOOL)textFieldShouldReturn:(UITextField *)textField{ 77 | [textField resignFirstResponder]; 78 | return YES; 79 | } 80 | - (void)textFieldDidChangeSelection:(UITextField *)textField{ 81 | if (textField == self.roomNameTxt) { 82 | ROOM_NAME = textField.text; 83 | } 84 | } 85 | - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{ 86 | if (textField == self.roomNameTxt) { 87 | NSString *updateString = [textField.text stringByReplacingCharactersInRange:range withString:string]; 88 | ROOM_NAME = updateString; 89 | } 90 | return YES; 91 | } 92 | - (BOOL)textFieldShouldClear:(UITextField *)textField{ 93 | ROOM_NAME = nil; 94 | return YES; 95 | } 96 | @end 97 | 98 | 99 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/AudioCallViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AudioCallViewController.m 3 | // Skylink_Examples 4 | // 5 | // Created by Temasys on 07/01/2016. 6 | // Copyright © 2016 Temasys. All rights reserved. 7 | // 8 | 9 | #import "AudioCallViewController.h" 10 | #import "Constant.h" 11 | 12 | //#define ROOM_NAME [[NSUserDefaults standardUserDefaults] objectForKey:@"ROOMNAME_AUDIOCALL"] 13 | 14 | 15 | @interface AudioCallViewController () 16 | // IBOutlets 17 | @property (weak, nonatomic) IBOutlet UITableView *tableView; 18 | @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator; 19 | 20 | // Other properties 21 | @property (strong, nonatomic) NSMutableArray *remotePeerArray; 22 | @end 23 | 24 | 25 | @implementation AudioCallViewController 26 | 27 | - (void)viewDidLoad { 28 | [super viewDidLoad]; 29 | // Do any additional setup after loading the view. 30 | 31 | self.title = @"Audio Call"; 32 | self.remotePeerArray = [NSMutableArray array]; 33 | // Creating configuration 34 | SKYLINKConnectionConfig *config = [SKYLINKConnectionConfig new]; 35 | [config setAudioVideoSendConfig:AudioVideoConfig_AUDIO_ONLY]; 36 | [config setAudioVideoReceiveConfig:AudioVideoConfig_AUDIO_ONLY]; 37 | 38 | // Creating SKYLINKConnection 39 | _skylinkConnection = [[SKYLINKConnection alloc] initWithConfig:config callback:nil]; 40 | _skylinkConnection.lifeCycleDelegate = self; 41 | _skylinkConnection.mediaDelegate = self; 42 | _skylinkConnection.remotePeerDelegate = self; 43 | 44 | // Connecting to a room 45 | // connectToRoomWithCredentials example 46 | // NSDictionary *credInfos = @{@"startTime" : [NSDate date], @"duration" : [NSNumber numberWithFloat:24.000f]}; 47 | // NSString *credential = [SKYLINKConnection calculateCredentials:ROOM_AUDIO duration:credInfos[@"duration"] startTime:credInfos[@"startTime"] secret:APP_SECRET]; 48 | // [_skylinkConnection connectToRoomWithStringURL:credential userData:USER_NAME callback:nil]; 49 | [self joinRoom]; 50 | } 51 | 52 | #pragma mark - SKYLINKConnectionLifeCycleDelegate 53 | - (void)connectionDidConnectToRoomSuccessful:(SKYLINKConnection *)connection 54 | { 55 | MyLog(@"Inside %s", __FUNCTION__); 56 | dispatch_async(dispatch_get_main_queue(), ^{ 57 | [self.activityIndicator stopAnimating]; 58 | [self startLocalAudio]; 59 | }); 60 | } 61 | - (void)connection:(SKYLINKConnection *)connection didConnectToRoomFailed:(NSString *)errorMessage 62 | { 63 | [UIAlertController showAlertWithAutoDisappearTitle:@"Connection failed" message:errorMessage duration:3 onViewController:self]; 64 | [self.navigationController popViewControllerAnimated:YES]; 65 | } 66 | 67 | - (void)connection:(SKYLINKConnection *)connection didDisconnectFromRoomWithSkylinkEvent:(NSDictionary *)skylinkEvent contextDescription:(NSString *)contextDescription 68 | { 69 | // showAlertAutoDismiss(@"Disconnected", skylinkEvent.description, 3, self); 70 | // [UIAlertController showAlertWithAutoDisappearTitle:@"Disconnected" message:skylinkEvent.description duration:3 onViewController:self]; 71 | // [self.navigationController popViewControllerAnimated:YES]; 72 | } 73 | #pragma mark - SKYLINKConnectionMediaDelegate 74 | 75 | - (void)connection:(SKYLINKConnection *)connection didToggleAudio:(BOOL)isMuted peerId:(NSString *)peerId { 76 | NSArray *enumarateArray = [self.remotePeerArray copy]; 77 | for (NSDictionary *peerDic in enumarateArray) { 78 | if ([peerDic[@"id"] isEqualToString:peerId]) { 79 | [self.remotePeerArray removeObject:peerDic]; 80 | [self.remotePeerArray addObject:@{@"id" : peerId, @"isAudioMuted" : [NSNumber numberWithBool:isMuted]}]; 81 | } 82 | } 83 | [self.tableView reloadData]; 84 | } 85 | 86 | #pragma mark - SKYLINKConnectionRemotePeerDelegate 87 | - (void)connection:(SKYLINKConnection *)connection didConnectWithRemotePeer:(NSString *)remotePeerId userInfo:(id)userInfo hasDataChannel:(BOOL)hasDataChannel 88 | { 89 | MyLog(@"Peer with id %@ joigned the room.", remotePeerId); 90 | [self.remotePeerArray addObject:@{@"id" : remotePeerId, @"isAudioMuted" : @(NO), @"nickname" : ([userInfo isKindOfClass:[NSString class]]) ? userInfo : @""}]; 91 | [self.tableView reloadData]; 92 | } 93 | 94 | - (void)connection:(SKYLINKConnection *)connection didDisconnectWithRemotePeer:(NSString *)remotePeerId userInfo:(id)userInfo hasDataChannel:(BOOL)hasDataChannel 95 | { 96 | MyLog(@"Peer with id %@ left the room with userInfo: %@", remotePeerId, userInfo); 97 | NSDictionary *dicToRemove; 98 | for (NSDictionary *peerDic in self.remotePeerArray) 99 | if ([peerDic[@"id"] isEqualToString:remotePeerId]) dicToRemove = peerDic; 100 | [self.remotePeerArray removeObject:dicToRemove]; 101 | [self.tableView reloadData]; 102 | } 103 | 104 | - (void)connection:(SKYLINKConnection *)connection didReceiveRemotePeerUserData:(id)userData remotePeerId:(NSString *)remotePeerId 105 | { 106 | 107 | } 108 | #pragma mark - Table view data source 109 | 110 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 111 | return [NSString stringWithFormat:@"%lu peer(s) connected", (unsigned long)self.remotePeerArray.count]; 112 | } 113 | 114 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 115 | return 1; 116 | } 117 | 118 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 119 | return self.remotePeerArray.count; 120 | } 121 | 122 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 123 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ACpeerCell"]; 124 | 125 | NSDictionary *peerDic = [self.remotePeerArray objectAtIndex:indexPath.row]; 126 | cell.textLabel.text = peerDic[@"nickname"] ? peerDic[@"nickname"] : [NSString stringWithFormat:@"Peer %ld", (long)indexPath.row]; 127 | cell.detailTextLabel.text = [NSString stringWithFormat:@"ID: %@ %@", peerDic[@"id"], [peerDic[@"isAudioMuted"] boolValue] ? @" - Audio muted" : @""]; 128 | 129 | cell.backgroundColor = [UIColor colorWithRed:0.35 green:0.35 blue:0.35 alpha:1.00]; // iPads does not use storyboard bg color value 130 | return cell; 131 | } 132 | 133 | #pragma mark - Table view delegate 134 | 135 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 136 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 137 | } 138 | 139 | #pragma mark - IBAction 140 | 141 | - (IBAction)switchAudioTap:(UIButton *)sender { 142 | [sender setTitle:(!_skylinkConnection.isAudioMuted ? @"Unmute microphone" : @"Mute microphone") forState:UIControlStateNormal]; 143 | [_skylinkConnection muteAudio:!_skylinkConnection.isAudioMuted]; 144 | } 145 | 146 | - (void)startLocalAudio 147 | { 148 | [_skylinkConnection createLocalMediaWithMediaDevice:SKYLINKMediaDeviceMicrophone mediaMetadata:nil callback:^(NSError * _Nullable error) { 149 | if (error) [UIAlertController showAlertWithAutoDisappearTitle:@"Error" message:error.localizedDescription duration:3 onViewController:self]; 150 | }]; 151 | } 152 | 153 | - (void)connection:(SKYLINKConnection *)connection didChangeSkylinkMedia:(SKYLINKMedia *)skylinkMedia peerId:(NSString *)peerId 154 | { 155 | if (skylinkMedia.skylinkMediaType == SKYLINKMediaTypeAudio) { 156 | 157 | } 158 | } 159 | @end 160 | 161 | 162 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/DataTransferViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DataTransferViewController.m 3 | // SkylinkSample 4 | // 5 | // Created by Temasys on 08/06/2016. 6 | // Copyright © 2016 Temasys. All rights reserved. 7 | // 8 | 9 | #import "DataTransferViewController.h" 10 | #import "Constant.h" 11 | 12 | //#define ROOM_NAME [[NSUserDefaults standardUserDefaults] objectForKey:@"ROOMNAME_DATATRANSFER"] 13 | 14 | @interface DataTransferViewController () 15 | @property (weak, nonatomic) IBOutlet UIView *localColorView; 16 | @property (weak, nonatomic) IBOutlet UISlider *redSlider; 17 | @property (weak, nonatomic) IBOutlet UISlider *greenSlider; 18 | @property (weak, nonatomic) IBOutlet UISlider *blueSlider; 19 | @property (weak, nonatomic) IBOutlet UITextView *infoTextView; 20 | @property (weak, nonatomic) IBOutlet UISwitch *isContinuousSwitch; 21 | @property (weak, nonatomic) IBOutlet UIButton *sendColorButton; 22 | @property (weak, nonatomic) IBOutlet UIImageView *imageView; 23 | 24 | @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator; 25 | 26 | @property (strong, nonatomic) NSTimer *timer; 27 | 28 | @end 29 | 30 | @implementation DataTransferViewController 31 | 32 | - (void)viewDidLoad { 33 | [super viewDidLoad]; 34 | // Do any additional setup after loading the view. 35 | 36 | self.title = @"Data Transfer"; 37 | 38 | SKYLINKConnectionConfig *config = [SKYLINKConnectionConfig new]; 39 | [config setAudioVideoSendConfig:AudioVideoConfig_NO_AUDIO_NO_VIDEO]; 40 | [config setAudioVideoReceiveConfig:AudioVideoConfig_NO_AUDIO_NO_VIDEO]; 41 | config.hasDataTransfer = YES; 42 | _skylinkConnection = [[SKYLINKConnection alloc] initWithConfig:config callback:nil]; 43 | _skylinkConnection.messagesDelegate = self; 44 | _skylinkConnection.lifeCycleDelegate = self; 45 | _skylinkConnection.remotePeerDelegate = self; 46 | [self joinRoom]; 47 | 48 | [self refreshUI]; 49 | } 50 | 51 | #pragma mark SKYLINKConnectionLifeCycleDelegate 52 | - (void)connectionDidConnectToRoomSuccessful:(SKYLINKConnection *)connection 53 | { 54 | [self showUIInfo:@"DID CONNECT • success "]; 55 | [self.activityIndicator stopAnimating]; 56 | } 57 | 58 | - (void)connection:(SKYLINKConnection *)connection didConnectToRoomFailed:(NSString *)errorMessage 59 | { 60 | [self showUIInfo:@"Failed to connect"]; 61 | } 62 | #pragma mark SKYLINKConnectionRemotePeerDelegate 63 | - (void)connection:(SKYLINKConnection *)connection didConnectWithRemotePeer:(NSString *)remotePeerId userInfo:(id)userInfo hasDataChannel:(BOOL)hasDataChannel 64 | { 65 | [self showUIInfo:[NSString stringWithFormat:@"👤 DID JOIN PEER •\npeerID = %@, properties = %@", remotePeerId, userInfo]]; 66 | } 67 | 68 | - (void)connection:(SKYLINKConnection *)connection didReceiveRemotePeerLeaveRoom:(NSString *)remotePeerId userInfo:(id)userInfo skylinkInfo:(NSDictionary *)skylinkInfo 69 | { 70 | [self showUIInfo:[NSString stringWithFormat:@"✋🏼 DID LEAVE PEER • peerID = %@, message = \n%@", remotePeerId, userInfo]]; 71 | } 72 | #pragma mark SKYLINKConnectionMessagesDelegate 73 | - (void)connection:(SKYLINKConnection *)connection didReceiveData:(NSData *)data remotePeerId:(NSString *)remotePeerId 74 | { 75 | if (data != nil) { 76 | NSString *dataType; 77 | id unarchivedData = [NSKeyedUnarchiver unarchiveObjectWithData:data]; 78 | if ([unarchivedData isKindOfClass:[UIColor class]]) { 79 | dataType = @"UIColor"; 80 | self.view.backgroundColor = unarchivedData; 81 | } else if ([unarchivedData isKindOfClass:[UIImage class]]) { 82 | dataType = @"UIImage"; 83 | self.imageView.image = unarchivedData; 84 | [UIView animateWithDuration:1 delay:3 options:0 animations:^(void) { 85 | self.imageView.alpha = 0; 86 | } completion:^(BOOL finished) { 87 | self.imageView.image = nil; 88 | self.imageView.alpha = 1; 89 | }]; 90 | } else { 91 | dataType = @"OTHER"; 92 | } 93 | [self showUIInfo:[NSString stringWithFormat:@"Received data of type '%@' and lenght: %lu", dataType, (unsigned long)data.length]]; 94 | } 95 | } 96 | 97 | #pragma mark IBActions 98 | 99 | - (IBAction)sendDataTap:(UIButton*)sender { 100 | [self sendCurrentColor]; 101 | } 102 | - (IBAction)onAnySliderChange:(UISlider *)sender { 103 | if (self.isContinuousSwitch.isOn) [self sendCurrentColor]; 104 | [self refreshUI]; 105 | } 106 | - (IBAction)isContinuousSwitchChanged:(UISwitch *)sender { 107 | if (sender.isOn) [self sendCurrentColor]; 108 | [self refreshUI]; 109 | } 110 | - (IBAction)sendImageTap:(UIButton *)sender { 111 | NSString *filePath = [[NSBundle mainBundle] pathForResource:@"dataTransferImage" ofType:@"png" inDirectory:@"DataTransferSamples"]; 112 | UIImage *sampleImage = [UIImage imageWithContentsOfFile:filePath]; 113 | UIImage *sampleImage2 = [UIImage imageWithCGImage:sampleImage.CGImage scale:sampleImage.scale orientation:sampleImage.imageOrientation]; 114 | [self showUIInfo:@"Sending sample image..."]; 115 | [_skylinkConnection sendData:[NSKeyedArchiver archivedDataWithRootObject:sampleImage2] toRemotePeerId:nil callback:^(NSError * _Nullable error) { 116 | if (error) [UIAlertController showAlertWithAutoDisappearTitle:@"Error" message:error.localizedDescription duration:3 onViewController:self]; 117 | }]; 118 | } 119 | - (IBAction)autoChangeColorSwitchChanged:(UISwitch *)sender { 120 | if (sender.isOn) { 121 | NSDate *d = [NSDate dateWithTimeIntervalSinceNow:0.0]; 122 | self.timer = [[NSTimer alloc] initWithFireDate: d interval: 0.04 target: self selector:@selector(onTick:) userInfo:nil repeats:YES]; 123 | NSRunLoop *runner = [NSRunLoop currentRunLoop]; 124 | [runner addTimer:self.timer forMode:NSDefaultRunLoopMode]; 125 | } else { 126 | [self.timer invalidate]; 127 | self.timer = nil; 128 | } 129 | } 130 | 131 | #pragma mark Utils 132 | 133 | - (void)refreshUI { 134 | self.localColorView.backgroundColor = [self slidersUIColor]; 135 | self.sendColorButton.hidden = self.isContinuousSwitch.isOn; 136 | } 137 | 138 | - (void)onTick:(NSTimer *)timer { 139 | float increment = 0.004; 140 | self.redSlider.value = (self.redSlider.value + increment > 1) ? 0 : self.redSlider.value + increment; 141 | self.greenSlider.value = (self.greenSlider.value + 1.9 * increment > 1) ? 0 : self.greenSlider.value + 1.9 * increment; 142 | self.blueSlider.value = (self.blueSlider.value + 3.1 * increment > 1) ? 0 : self.blueSlider.value + 3.1 * increment; 143 | if (self.isContinuousSwitch.isOn) [self sendCurrentColor]; 144 | [self refreshUI]; 145 | } 146 | 147 | - (void)sendCurrentColor { 148 | [self showUIInfo:@"Sending current local color..."]; 149 | NSData *colorData = [NSKeyedArchiver archivedDataWithRootObject:[self slidersUIColor]]; 150 | [_skylinkConnection sendData:colorData toRemotePeerId:nil callback:^(NSError * _Nullable error) { 151 | if (error) [UIAlertController showAlertWithAutoDisappearTitle:@"Error" message:error.localizedDescription duration:3 onViewController:self]; 152 | }]; 153 | } 154 | 155 | - (void)showUIInfo:(NSString *)infoMessage { 156 | self.infoTextView.text = [NSString stringWithFormat:@"[%.3f] %@\n%@", CFAbsoluteTimeGetCurrent(), infoMessage, self.infoTextView.text]; 157 | } 158 | 159 | - (UIColor *)slidersUIColor { 160 | return [UIColor colorWithRed:self.redSlider.value green:self.greenSlider.value blue:self.blueSlider.value alpha:1.0]; 161 | } 162 | 163 | 164 | @end 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/VideoCallViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // VideCallViewController.m 3 | // Skylink_Examples 4 | // 5 | // Created by Temasys on 11/12/2015. 6 | // Copyright © 2015 Temasys. All rights reserved. 7 | // 8 | 9 | #import "VideoCallViewController.h" 10 | #import 11 | 12 | //#define ROOM_NAME [[NSUserDefaults standardUserDefaults] objectForKey:@"ROOMNAME_ONETOONEVIDEOCALL"] 13 | 14 | @interface VideoCallViewController () 15 | 16 | // IBOutlets 17 | @property (weak, nonatomic) IBOutlet UIView *localVideoContainerView; // note: .clipsToBounds property set to YES via storyboard; 18 | @property (weak, nonatomic) IBOutlet UIView *localVideoContainerView2; 19 | @property (weak, nonatomic) IBOutlet UIView *remotePeerVideoContainerView; 20 | @property (weak, nonatomic) IBOutlet UIView *remotePeerVideoContainerView2; 21 | @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator; 22 | @property (weak, nonatomic) IBOutlet UIButton *callButton; 23 | @property (strong, nonatomic) IBOutletCollection(UIView) NSArray *videoViews; 24 | 25 | @end 26 | 27 | @implementation VideoCallViewController 28 | { 29 | BOOL _isJoinRoom; 30 | NSMutableArray *_localMedias; 31 | NSMutableArray *_remoteMedias; 32 | NSString *_remotePeerId; 33 | } 34 | #pragma mark - INIT 35 | - (void)viewDidLoad { 36 | [super viewDidLoad]; 37 | self.title = @"1-1 Video Call"; 38 | [self initData]; 39 | } 40 | - (void)initData{ 41 | _isJoinRoom = NO; 42 | _localMedias = [NSMutableArray new]; 43 | _remoteMedias = [NSMutableArray new]; 44 | APP_KEY = APP_KEY; 45 | APP_SECRET = APP_SECRET; 46 | // Creating configuration 47 | SKYLINKConnectionConfig *config = [SKYLINKConnectionConfig new]; 48 | [config setAudioVideoReceiveConfig:AudioVideoConfig_AUDIO_AND_VIDEO]; 49 | [config setAudioVideoSendConfig:AudioVideoConfig_AUDIO_AND_VIDEO]; 50 | config.isMultiTrackCreateEnable = YES; 51 | config.roomSize = SKYLINKRoomSizeSmall; 52 | config.isMirrorLocalFrontCameraView = true; 53 | 54 | // Creating SKYLINKConnection 55 | _skylinkConnection = [[SKYLINKConnection alloc] initWithConfig:config callback:nil]; 56 | _skylinkConnection.lifeCycleDelegate = self; 57 | _skylinkConnection.mediaDelegate = self; 58 | _skylinkConnection.remotePeerDelegate = self; 59 | _skylinkConnection.enableLogs = YES; 60 | } 61 | 62 | #pragma mark - SKYLINKConnectionLifeCycleDelegate 63 | - (void)connectionDidConnectToRoomSuccessful:(SKYLINKConnection *)connection{ 64 | // skylinkLog("Inside \(#function)"); 65 | self.title = roomName; 66 | [self.callButton setBackgroundImage:[UIImage imageNamed:@"call_off"] forState:UIControlStateNormal]; 67 | // __weak __typeof(self)weakSelf = self; 68 | // dispatch_async(dispatch_get_main_queue(), ^{ 69 | // __strong __typeof(weakSelf)strongSelf = weakSelf; 70 | // 71 | // }); 72 | [self.activityIndicator stopAnimating]; 73 | } 74 | - (void)connection:(SKYLINKConnection *)connection didConnectToRoomFailed:(NSString *)errorMessage{ 75 | showAlert(@"Connection failed!", errorMessage); 76 | } 77 | - (void)connection:(SKYLINKConnection *)connection didDisconnectFromRoomWithSkylinkEvent:(NSDictionary *)skylinkEvent contextDescription:(NSString *)contextDescription{ 78 | [self.callButton setBackgroundImage:[UIImage imageNamed:@"call_on"] forState:UIControlStateNormal]; 79 | [self.activityIndicator stopAnimating]; 80 | } 81 | #pragma mark - SKYLINKConnectionMediaDelegate 82 | - (void)connection:(SKYLINKConnection *)connection didCreateLocalMedia:(SKYLINKMedia *)localMedia{ 83 | if (!localMedia) { 84 | return; 85 | } 86 | [_localMedias addObject:localMedia]; 87 | [self reloadVideoView]; 88 | [self.activityIndicator stopAnimating]; 89 | } 90 | - (void)connection:(SKYLINKConnection *)connection didChangeLocalMedia:(SKYLINKMedia *)localMedia{ 91 | NSLog(@"changed local media %d", localMedia.skylinkMediaState); 92 | [self.activityIndicator stopAnimating]; 93 | } 94 | 95 | - (void)connection:(SKYLINKConnection *)connection didChangeRemoteMedia:(SKYLINKMedia *)remoteMedia remotePeerId:(NSString *)remotePeerId{ 96 | NSInteger _index = -1; 97 | for (SKYLINKMedia *media in _remoteMedias) { 98 | if (media.skylinkMediaID == remotePeerId) { 99 | _index = [_remoteMedias indexOfObject:media]; 100 | } 101 | } 102 | if (_index>=0) { 103 | [_remoteMedias replaceObjectAtIndex:_index withObject:remoteMedia]; 104 | [self reloadVideoView]; 105 | } 106 | } 107 | - (void)connection:(SKYLINKConnection *)connection didReceiveRemoteMedia:(SKYLINKMedia *)remoteMedia remotePeerId:(NSString *)remotePeerId{ 108 | if (!remoteMedia || !remotePeerId) { 109 | return; 110 | } 111 | [_remoteMedias addObject:remoteMedia]; 112 | [self reloadVideoView]; 113 | } 114 | - (void)connection:(SKYLINKConnection *)connection didChangeVideoSize:(CGSize)videoSize videoView:(UIView *)videoView peerId:(NSString *)peerId{ 115 | if (videoSize.height > 0 && videoSize.width > 0) { 116 | for (UIView* container in @[self.localVideoContainerView, self.localVideoContainerView2, self.remotePeerVideoContainerView, self.remotePeerVideoContainerView2]) { 117 | if ([videoView isDescendantOfView:container]) { 118 | [videoView aspectFitRectForSize:videoSize container:container]; 119 | } 120 | } 121 | } 122 | } 123 | - (void)connection:(SKYLINKConnection *)connection didDestroyLocalMedia:(SKYLINKMedia *)localMedia{ 124 | // NSInteger _index = -1; 125 | for (SKYLINKMedia *media in _localMedias ) { 126 | if ([media isEqual:localMedia]) { 127 | dispatch_async(dispatch_get_main_queue(), ^{ 128 | [self->_localMedias removeObject:media]; 129 | [self reloadVideoView]; 130 | return; 131 | }); 132 | } 133 | } 134 | // if (_index>=0) { 135 | // dispatch_async(dispatch_get_main_queue(), ^{ 136 | // [self->_localMedias removeObjectAtIndex:_index]; 137 | // [self reloadVideoView]; 138 | // }); 139 | // } 140 | } 141 | 142 | #pragma mark - SKYLINKConnectionRemotePeerDelegate 143 | - (void)connection:(SKYLINKConnection *)connection didConnectWithRemotePeer:(NSString *)remotePeerId userInfo:(id)userInfo hasDataChannel:(BOOL)hasDataChannel{ 144 | showAlertAutoDismiss(nil, [NSString stringWithFormat:@"%@ has joined room\nUserData:%@", remotePeerId, userInfo[@"userData"]], 2, self); 145 | _remotePeerId = remotePeerId; 146 | [self.activityIndicator stopAnimating]; 147 | } 148 | - (void)connection:(SKYLINKConnection *)connection didDisconnectWithRemotePeer:(NSString *)remotePeerId userInfo:(id)userInfo hasDataChannel:(BOOL)hasDataChannel{ 149 | [_remoteMedias removeAllObjects]; 150 | [self reloadVideoView]; 151 | [self.activityIndicator stopAnimating]; 152 | } 153 | - (void)connection:(SKYLINKConnection *)connection didReceiveRemotePeerLeaveRoom:(NSString *)remotePeerId userInfo:(id)userInfo skylinkInfo:(NSDictionary *)skylinkInfo{ 154 | 155 | } 156 | 157 | #pragma mark - Private functions 158 | - (void)addRenderedVideo:(UIView *)videoView insideContainer:(UIView *)containerView mirror:(BOOL)shouldMirror { 159 | [videoView aspectFitRectForSize:videoView.frame.size container:containerView]; 160 | [containerView removeSubviews]; 161 | [containerView insertSubview:videoView atIndex:0]; 162 | } 163 | - (void)changeLocalMediaStateWithMediaId:(NSString*)mediaId state:(SKYLINKMediaState)state{ 164 | [self.activityIndicator startAnimating]; 165 | [_skylinkConnection changeLocalMediaStateWithMediaId:mediaId mediaState:state callback:^(NSError * _Nullable error) { 166 | if (error) { 167 | [UIAlertController showAlertWithAutoDisappearTitle:@"Error" message:error.localizedDescription duration:3 onViewController:self]; 168 | } 169 | [self.activityIndicator stopAnimating]; 170 | }]; 171 | } 172 | 173 | - (void)destroyLocalMedia{ 174 | while (_localMedias.count>0) { 175 | SKYLINKMedia *media = _localMedias.firstObject; 176 | [_skylinkConnection destroyLocalMediaWithMediaId:media.skylinkMediaID callback:nil]; 177 | } 178 | } 179 | - (void)reloadVideoView{ 180 | for (UIView *container in _videoViews) { 181 | [container removeSubviews]; 182 | } 183 | for (SKYLINKMedia *localMedia in _localMedias) { 184 | if (localMedia.skylinkMediaType == SKYLINKMediaTypeVideoCamera) { 185 | [self addRenderedVideo:localMedia.skylinkVideoView insideContainer:self.localVideoContainerView mirror:true]; 186 | }else if(localMedia.skylinkMediaType == SKYLINKMediaTypeVideoScreen){ 187 | [self addRenderedVideo:localMedia.skylinkVideoView insideContainer:self.localVideoContainerView2 mirror:true]; 188 | } 189 | } 190 | for (SKYLINKMedia *remoteMedia in _remoteMedias) { 191 | if (remoteMedia.skylinkMediaType == SKYLINKMediaTypeVideoCamera) { 192 | [self addRenderedVideo:remoteMedia.skylinkVideoView insideContainer:self.remotePeerVideoContainerView mirror:false]; 193 | }else if(remoteMedia.skylinkMediaType == SKYLINKMediaTypeVideoScreen){ 194 | [self addRenderedVideo:remoteMedia.skylinkVideoView insideContainer:self.remotePeerVideoContainerView2 mirror:false]; 195 | } 196 | } 197 | } 198 | 199 | 200 | #pragma mark - IBActions 201 | - (IBAction)joinRoom{ 202 | [self.activityIndicator startAnimating]; 203 | if (_isJoinRoom) { 204 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 205 | [_skylinkConnection unlockTheRoom:nil]; 206 | [_skylinkConnection disconnect:^(NSError * _Nullable error) { 207 | if (error) { 208 | [self.remotePeerVideoContainerView removeSubviews]; 209 | [self.remotePeerVideoContainerView2 removeSubviews]; 210 | // self.localVideoContainerView.removeSubviews() 211 | [self destroyLocalMedia]; 212 | [self.callButton setBackgroundImage:[UIImage imageNamed:@"call_on"] forState:UIControlStateNormal]; 213 | [self.activityIndicator stopAnimating]; 214 | } 215 | }]; 216 | }else{ 217 | [super joinRoom]; 218 | } 219 | _isJoinRoom = !_isJoinRoom; 220 | } 221 | 222 | - (IBAction)startCamera{ 223 | [self startLocalMediaDevice:SKYLINKMediaDeviceCameraFront]; 224 | } 225 | 226 | - (IBAction)startAudio{ 227 | [self startLocalMediaDevice:SKYLINKMediaDeviceMicrophone]; 228 | } 229 | 230 | - (IBAction)startScreen{ 231 | [self startLocalMediaDevice:SKYLINKMediaDeviceScreen]; 232 | } 233 | 234 | - (IBAction)videoStateChanged:(UISegmentedControl*)sender{ 235 | if (!_isJoinRoom) { 236 | sender.selectedSegmentIndex = 0; 237 | return; 238 | } 239 | for(SKYLINKMedia *media in _localMedias){ 240 | if (media.skylinkMediaType == SKYLINKMediaTypeVideoCamera){ 241 | [self changeLocalMediaStateWithMediaId:media.skylinkMediaID state:(int)sender.selectedSegmentIndex+1]; 242 | } 243 | } 244 | } 245 | 246 | - (IBAction)audioStateChanged:(UISegmentedControl*)sender{ 247 | if (!_isJoinRoom){ 248 | sender.selectedSegmentIndex = 0; 249 | return; 250 | } 251 | for(SKYLINKMedia *media in _localMedias){ 252 | if (media.skylinkMediaType == SKYLINKMediaTypeAudio){ 253 | [self changeLocalMediaStateWithMediaId:media.skylinkMediaID state:(int)sender.selectedSegmentIndex+1]; 254 | } 255 | } 256 | } 257 | 258 | - (IBAction)screenStateChanged:(UISegmentedControl*)sender{ 259 | if (!_isJoinRoom){ 260 | sender.selectedSegmentIndex = 0; 261 | return; 262 | } 263 | for(SKYLINKMedia *media in _localMedias){ 264 | if (media.skylinkMediaType == SKYLINKMediaTypeVideoScreen){ 265 | [self changeLocalMediaStateWithMediaId:media.skylinkMediaID state:(int)sender.selectedSegmentIndex+1]; 266 | } 267 | } 268 | } 269 | - (IBAction)removeTrack:(UIButton*)sender{ 270 | for (SKYLINKMedia *media in _localMedias) { 271 | if (media.skylinkMediaType == sender.tag) { 272 | [_skylinkConnection destroyLocalMediaWithMediaId:media.skylinkMediaID callback:^(NSError * _Nullable error) { 273 | if (error) { 274 | NSLog(@"failed to remove track %@", error); 275 | } 276 | }]; 277 | } 278 | } 279 | } 280 | @end 281 | 282 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/MultiVideoCallViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MultiVideoCallViewController.m 3 | // Skylink_Examples 4 | // 5 | // Created by Temasys on 11/01/2016. 6 | // Copyright © 2016 Temasys. All rights reserved. 7 | // 8 | 9 | #import "MultiVideoCallViewController.h" 10 | #import 11 | #import "Constant.h" 12 | #import "Peer.h" 13 | //#define ROOM_NAME [[NSUserDefaults standardUserDefaults] objectForKey:@"ROOMNAME_MULTIVIDEOCALL"] 14 | #define MY_PEER_ID @"myPeerId" 15 | 16 | @interface MultiVideoCallViewController () 17 | // IBOutlets 18 | @property (weak, nonatomic) IBOutlet UIView *localVideoContainerView; 19 | @property (weak, nonatomic) IBOutlet UIView *firstPeerVideoContainerView; 20 | @property (weak, nonatomic) IBOutlet UIView *secondPeerVideoContainerView; 21 | @property (weak, nonatomic) IBOutlet UIView *thirdPeerVideoContainerView; 22 | @property (weak, nonatomic) IBOutlet UILabel *firstPeerLabel; 23 | @property (weak, nonatomic) IBOutlet UILabel *secondPeerLabel; 24 | @property (weak, nonatomic) IBOutlet UILabel *thirdPeerLabel; 25 | @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator; 26 | @property (weak, nonatomic) IBOutlet UIButton *lockButton; 27 | @property (weak, nonatomic) IBOutlet UISegmentedControl *videoAspectSegmentControl; 28 | @property (weak, nonatomic) IBOutlet UIPickerView *pickerView; 29 | @property (weak, nonatomic) IBOutlet UIView *pickerViewContainer; 30 | @property (weak, nonatomic) IBOutlet UIViewController *partipationsVC; 31 | @property (weak, nonatomic) IBOutlet UIButton *restartButton; 32 | @property (weak, nonatomic) IBOutlet UIButton *toggleCameraButton; 33 | // Other properties 34 | //@property (strong, nonatomic) NSMutableArray *peerIds; 35 | //@property (strong, nonatomic) NSMutableDictionary *peersInfos; 36 | //@property (nonatomic, copy) NSString *cameraMediaID; 37 | //@property (nonatomic, copy) NSString *audioMediaID; 38 | //@property (nonatomic, strong) NSMutableArray *peerObjects; 39 | @property (nonatomic, strong) IBOutletCollection(UIView) NSArray *videoContainers; 40 | @end 41 | 42 | @implementation MultiVideoCallViewController { 43 | BOOL isRoomLocked; 44 | BOOL isRecording; 45 | BOOL isLocalCameraRunning; 46 | NSMutableArray *_peers; 47 | } 48 | #pragma mark - Init 49 | 50 | - (void)viewDidLoad { 51 | [super viewDidLoad]; 52 | MyLog(@"SKYLINKConnection version = %@", [SKYLINKConnection getSkylinkVersion]); 53 | self.title = @"Multi Party Video Call"; 54 | [self setupData]; 55 | } 56 | 57 | - (void)setupData{ 58 | //creating SKYLINKConnectionConfig 59 | SKYLINKConnectionConfig *config = [SKYLINKConnectionConfig new]; 60 | [config setAudioVideoReceiveConfig:AudioVideoConfig_AUDIO_AND_VIDEO]; 61 | [config setAudioVideoSendConfig:AudioVideoConfig_AUDIO_AND_VIDEO]; 62 | config.isMultiTrackCreateEnable = YES; 63 | config.isMirrorLocalFrontCameraView = YES; 64 | //creating SKYLINKConnection 65 | _skylinkConnection = [[SKYLINKConnection alloc] initWithConfig:config callback:nil]; 66 | _skylinkConnection.lifeCycleDelegate = self; 67 | _skylinkConnection.mediaDelegate = self; 68 | _skylinkConnection.remotePeerDelegate = self; 69 | _skylinkConnection.enableLogs = YES; 70 | //init variables 71 | _peers = [NSMutableArray new]; 72 | 73 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 74 | [self startLocalMediaDevice:SKYLINKMediaDeviceCameraFront]; 75 | [self startLocalMediaDevice:SKYLINKMediaDeviceMicrophone]; 76 | }); 77 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 78 | [self joinRoom]; 79 | }); 80 | } 81 | 82 | #pragma mark - SKYLINKConnectionLifeCycleDelegate 83 | - (void)connectionDidConnectToRoomSuccessful:(SKYLINKConnection *)connection{ 84 | [_videoContainers.firstObject setAlpha:1.0]; 85 | [self.activityIndicator stopAnimating]; 86 | } 87 | - (void)connection:(SKYLINKConnection *)connection didConnectToRoomFailed:(NSString *)errorMessage{ 88 | showAlert(@"Connection failed!", errorMessage); 89 | } 90 | - (void)connection:(SKYLINKConnection *)connection didDisconnectFromRoomWithSkylinkEvent:(NSDictionary *)skylinkEvent contextDescription:(NSString *)contextDescription{ 91 | [self.activityIndicator stopAnimating]; 92 | } 93 | 94 | #pragma mark - SKYLINKConnectionMediaDelegate 95 | - (void)connection:(SKYLINKConnection *)connection didCreateLocalMedia:(SKYLINKMedia *)localMedia{ 96 | NSLog(@"didCreateLocalMedia"); 97 | //add media which is video only 98 | if (!localMedia) { 99 | return; 100 | } 101 | if (localMedia.skylinkMediaType != SKYLINKMediaTypeAudio) { 102 | [self addMedia:localMedia peerId:MY_PEER_ID]; 103 | [self reloadVideoView]; 104 | } 105 | } 106 | - (void)connection:(SKYLINKConnection *)connection didChangeLocalMedia:(SKYLINKMedia *)localMedia{ 107 | NSLog(@"changed local media %d", localMedia.skylinkMediaState); 108 | [self.activityIndicator stopAnimating]; 109 | } 110 | - (void)connection:(SKYLINKConnection *)connection didReceiveRemoteMedia:(SKYLINKMedia *)remoteMedia remotePeerId:(NSString *)remotePeerId{ 111 | NSLog(@"didReceiveRemoteMedia: %u", remoteMedia.skylinkMediaType); 112 | if (!remoteMedia || !remotePeerId) { 113 | return; 114 | } 115 | //add media which is video only 116 | if (remoteMedia.skylinkMediaType != SKYLINKMediaTypeAudio) { 117 | [self addMedia:remoteMedia peerId:remotePeerId]; 118 | [self reloadVideoView]; 119 | } 120 | } 121 | - (void)connection:(SKYLINKConnection *)connection didChangeRemoteMedia:(SKYLINKMedia *)remoteMedia remotePeerId:(NSString *)remotePeerId{ 122 | NSLog(@"didChangeRemoteMedia: %u", remoteMedia.skylinkMediaType); 123 | } 124 | - (void)connection:(SKYLINKConnection *)connection didChangeVideoSize:(CGSize)videoSize videoView:(UIView *)videoView peerId:(NSString *)peerId{ 125 | NSLog(@"changed video size!"); 126 | NSLog(@"peerId: %@", peerId); 127 | if (videoSize.height > 0 && videoSize.width > 0) { 128 | for (UIView* container in self.videoContainers) { 129 | if ([videoView isDescendantOfView:container]) { 130 | NSInteger _index = [self.videoContainers indexOfObject:container]; 131 | if (_index==0) { 132 | break; 133 | } 134 | _peers[_index].videoSize = videoSize; 135 | [videoView aspectFitRectForSize:videoSize container:container]; 136 | } 137 | } 138 | } 139 | } 140 | #pragma mark - SKYLINKConnectionRemotePeerDelegate 141 | - (void)connection:(SKYLINKConnection *)connection didReceiveRemotePeerInRoomWithRemotePeerId:(NSString *)remotePeerId userInfo:(id)userInfo{ 142 | NSLog(@"didReceiveRemotePeerInRoomWithRemotePeerId: %@ \nuserInfo: %@", remotePeerId, userInfo); 143 | showAlertAutoDismiss(nil, [NSString stringWithFormat:@"%@ has joined room\n\nUserData:%@", remotePeerId, userInfo[@"userData"]], 2, self); 144 | if (remotePeerId){ 145 | [self addMedia:nil peerId:remotePeerId]; 146 | [self.pickerView reloadAllComponents]; 147 | } 148 | } 149 | - (void)connection:(SKYLINKConnection *)connection didConnectWithRemotePeer:(NSString *)remotePeerId userInfo:(id)userInfo hasDataChannel:(BOOL)hasDataChannel{ 150 | // if (remotePeerId) { 151 | // showAlertAutoDismiss(nil, [NSString stringWithFormat:@"%@ has joined room\n\nUserData:%@", remotePeerId, userInfo[@"userData"]], 2, self); 152 | // [self addMedia:nil peerId:remotePeerId]; 153 | //// [_peers addObject:[[Peer alloc] initWithPeerID:remotePeerId]]; 154 | // [self reloadVideoView]; 155 | // [self.pickerView reloadAllComponents]; 156 | // } 157 | // [self.activityIndicator stopAnimating]; 158 | } 159 | - (void)connection:(SKYLINKConnection *)connection didDisconnectWithRemotePeer:(NSString *)remotePeerId userInfo:(id)userInfo hasDataChannel:(BOOL)hasDataChannel{ 160 | if (remotePeerId) { 161 | [self removePeer:remotePeerId]; 162 | [self reloadVideoView]; 163 | } 164 | [self.activityIndicator stopAnimating]; 165 | } 166 | - (void)connection:(SKYLINKConnection *)connection didReceiveRemotePeerLeaveRoom:(NSString *)remotePeerId userInfo:(id)userInfo skylinkInfo:(NSDictionary *)skylinkInfo{ 167 | 168 | } 169 | //- (void)connection:(SKYLINKConnection *)connection didChangeRemoteMedia:(SKYLINKMedia *)remoteMedia remotePeerId:(NSString *)remotePeerId{ 170 | // if (remoteMedia.skylinkMediaType == SKYLINKMediaTypeVideoCamera) { 171 | // if (remoteMedia.skylinkMediaState == SKYLINKMediaStateUnavailable) { 172 | // <#statements#> 173 | // } 174 | // } 175 | //} 176 | 177 | #pragma mark - UIPickerView Delegate 178 | - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { 179 | return 1; 180 | } 181 | 182 | - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { 183 | return _peers.count; 184 | } 185 | 186 | - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component { 187 | if (row == 0) return @"All"; 188 | return [NSString stringWithFormat:@"%@:%@", _peers[row].userName, _peers[row].peerId]; 189 | } 190 | #pragma mark - Private functions 191 | - (void)removePeer:(NSString*)peerId{ 192 | for (Peer *_peer in _peers) { 193 | if ([_peer.peerId isEqualToString:peerId]) { 194 | [_peers removeObject:_peer]; 195 | return; 196 | } 197 | } 198 | } 199 | - (void)addMedia:(SKYLINKMedia *)media peerId:(NSString *)peerId{ 200 | if (!peerId) { 201 | return; 202 | } 203 | Peer *_peer = nil; 204 | for (Peer *peer in _peers) { 205 | if ([peer.peerId isEqualToString:peerId]) { 206 | _peer = peer; 207 | break; 208 | } 209 | } 210 | if (!_peer) { 211 | _peer = [[Peer alloc] initWithPeerID:peerId]; 212 | if ([peerId isEqualToString:MY_PEER_ID]) { 213 | [_peers insertObject:_peer atIndex:0]; 214 | }else{ 215 | [_peers addObject:_peer]; 216 | } 217 | } 218 | if (media) { 219 | [_peer.medias addObject:media]; 220 | } 221 | // NSLog(@"ccc===peers count: %lu", (unsigned long)_peers.count); 222 | // for (Peer *_peer in _peers) { 223 | // NSLog(@"peer: %@", _peer.peerId); 224 | // NSLog(@"medias count: %lu", (unsigned long)_peer.medias.count); 225 | // NSLog(@"first media: %d", _peer.medias.firstObject.skylinkMediaType); 226 | // } 227 | } 228 | - (void)reloadVideoView{ 229 | //remove all videos 230 | for (UIView *view in _videoContainers) { 231 | [view removeSubviews]; 232 | } 233 | //add videos from peers array 234 | NSLog(@"reload video. Peers count: %lu", (unsigned long)_peers.count); 235 | for (Peer *peer in _peers) { 236 | if ([peer.peerId isEqualToString:MY_PEER_ID] && peer.medias.firstObject) { 237 | [self addRenderedVideo:peer.medias.firstObject.skylinkVideoView insideContainer:_videoContainers.firstObject mirror:YES]; 238 | }else{ 239 | if (peer.medias.firstObject.skylinkMediaState!=SKYLINKMediaStateUnavailable) { 240 | NSInteger _index = MIN([_peers indexOfObject:peer], 3); 241 | NSLog(@"addview index: %ld", (long)_index); 242 | [self addRenderedVideo:peer.medias.firstObject.skylinkVideoView insideContainer:_videoContainers[_index] mirror:NO]; 243 | } 244 | } 245 | } 246 | } 247 | - (void)addRenderedVideo:(UIView *)videoView insideContainer:(UIView *)containerView mirror:(BOOL)shouldMirror { 248 | [videoView aspectFitRectForSize:videoView.frame.size container:containerView]; 249 | // [containerView addSubview:videoView]; 250 | [containerView insertSubview:videoView atIndex:0]; 251 | } 252 | - (void)startRecording { 253 | if (!_skylinkConnection.isRecording) { 254 | [_skylinkConnection startRecording:^(NSError * _Nullable error) { 255 | if (error) MyLog(@"%@", error.localizedDescription); 256 | showAlertAutoDismiss(nil, @"You recording is started", 3, self); 257 | }]; 258 | } 259 | } 260 | 261 | - (void)stopRecording { 262 | if (_skylinkConnection.isRecording) { 263 | [_skylinkConnection stopRecording:^(NSError * _Nullable error) { 264 | if (error) MyLog(@"%@", error.localizedDescription); 265 | showAlertAutoDismiss(nil, @"You recording is stopped", 3, self); 266 | }]; 267 | } 268 | } 269 | 270 | #pragma mark - IBActions 271 | - (IBAction)toogleVideoTap:(UIButton *)sender { 272 | [_skylinkConnection muteVideo:!_skylinkConnection.isVideoMuted]; 273 | [sender setImage:[UIImage imageNamed:( (_skylinkConnection.isVideoMuted) ? @"NoVideoFilled.png" : @"VideoCall.png")] forState:UIControlStateNormal]; 274 | } 275 | - (IBAction)toogleSoundTap:(UIButton *)sender { 276 | [_skylinkConnection muteAudio:!_skylinkConnection.isAudioMuted]; 277 | [sender setImage:[UIImage imageNamed:( (_skylinkConnection.isAudioMuted) ? @"NoMicrophoneFilled.png" : @"Microphone.png")] forState:UIControlStateNormal]; 278 | } 279 | - (IBAction)switchCameraTap:(UIButton *)sender { 280 | [_skylinkConnection switchCamera:nil]; 281 | } 282 | - (IBAction)lockRoom:(UIButton *)sender { 283 | isRoomLocked ? [_skylinkConnection unlockTheRoom:nil] : [_skylinkConnection lockTheRoom:nil]; 284 | isRoomLocked = !isRoomLocked; 285 | [self.lockButton setImage:[UIImage imageNamed:isRoomLocked ? @"LockFilled" : @"Unlock.png"] forState:UIControlStateNormal]; 286 | } 287 | 288 | - (IBAction)videoAspectSegmentControlChanged:(UISegmentedControl *)sender { 289 | // [self updatePeersVideosFrames]; 290 | } 291 | 292 | - (IBAction)recording:(UISwitch *)sender { 293 | sender.isOn ? [self startRecording] : [self stopRecording]; 294 | } 295 | 296 | - (IBAction)restart { 297 | [self.pickerViewContainer setHidden:NO]; 298 | } 299 | 300 | - (IBAction)toolbarDone { 301 | [self.pickerViewContainer setHidden:YES]; 302 | } 303 | 304 | - (IBAction)toolbarSend { 305 | [self.pickerViewContainer setHidden:YES]; 306 | if ([self.pickerView selectedRowInComponent:0] == 0) { 307 | [_skylinkConnection refreshConnectionWithRemotePeerId:nil doIceRestart:YES callback:nil]; 308 | }else{ 309 | NSString *_selectedPeerId = _peers[[_pickerView selectedRowInComponent:0]].peerId; 310 | [_skylinkConnection refreshConnectionWithRemotePeerId:_selectedPeerId doIceRestart:YES callback:nil]; 311 | } 312 | } 313 | @end 314 | 315 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/MessagesViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MessagesViewController.m 3 | // Skylink_Examples 4 | // 5 | // Created by Temasys on 04/01/2016. 6 | // Copyright © 2016 Temasys. All rights reserved. 7 | // 8 | 9 | #import "MessagesViewController.h" 10 | #import "Constant.h" 11 | #import "SAMessage.h" 12 | 13 | //#define ROOM_NAME [[NSUserDefaults standardUserDefaults] objectForKey:@"ROOMNAME_MESSAGES"] 14 | 15 | 16 | @interface MessagesViewController () 17 | // IBOutlets 18 | @property (weak, nonatomic) IBOutlet UITextField *messageTextField; 19 | @property (weak, nonatomic) IBOutlet UITableView *tableView; 20 | @property (weak, nonatomic) IBOutlet UISwitch *isPublicSwitch; 21 | @property (weak, nonatomic) IBOutlet UISegmentedControl *messageTypeSegmentControl; 22 | @property (weak, nonatomic) IBOutlet UITextField *nicknameTextField; 23 | @property (weak, nonatomic) IBOutlet UIButton *peersButton; 24 | @property (weak, nonatomic) IBOutlet UIButton *sendButton; 25 | @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator; 26 | @property (weak, nonatomic) IBOutlet UITextField *encryptKeyTextField; 27 | @property (weak, nonatomic) IBOutlet UIView *pickerViewContainer; 28 | @property (weak, nonatomic) IBOutlet UIPickerView *pickerView; 29 | @property (weak, nonatomic) IBOutlet UISwitch *persistSwitch; 30 | 31 | // Properties 32 | @property (strong, nonatomic) NSMutableArray *messages; 33 | @property (strong, nonatomic) NSMutableDictionary *peers; 34 | 35 | @end 36 | 37 | 38 | @implementation MessagesViewController{ 39 | NSArray *_encryptSecretIds; 40 | } 41 | 42 | - (void)viewDidLoad { 43 | [super viewDidLoad]; 44 | // Do any additional setup after loading the view. 45 | 46 | self.title = @"Messages"; 47 | self.messages = [NSMutableArray new]; 48 | self.peers = [NSMutableDictionary new]; 49 | [self updatePeersButtonTitle]; 50 | [self loadStoredMessage]; 51 | // Creating configuration 52 | SKYLINKConnectionConfig *config = [SKYLINKConnectionConfig new]; 53 | [config setAudioVideoSendConfig:AudioVideoConfig_NO_AUDIO_NO_VIDEO]; 54 | [config setAudioVideoReceiveConfig:AudioVideoConfig_NO_AUDIO_NO_VIDEO]; 55 | config.hasP2PMessaging = YES; 56 | // Creating SKYLINKConnection 57 | _skylinkConnection = [[SKYLINKConnection alloc] initWithConfig:config callback:nil]; 58 | _skylinkConnection.lifeCycleDelegate = self; 59 | _skylinkConnection.messagesDelegate = self; 60 | _skylinkConnection.remotePeerDelegate = self; 61 | _skylinkConnection.encryptSecrets = SAConstants.shared.ENCRYPTION_SECRETS; 62 | // Connecting to a room 63 | [self joinRoom]; 64 | _encryptSecretIds = [@[@"No Key"] arrayByAddingObjectsFromArray:[SAConstants.shared.ENCRYPTION_SECRETS.allKeys sortedArrayUsingSelector:@selector(compare:)]]; 65 | _encryptKeyTextField.text = _encryptSecretIds.firstObject; 66 | } 67 | 68 | #pragma mark - SKYLINKConnectionLifeCycleDelegate 69 | - (void)connectionDidConnectToRoomSuccessful:(SKYLINKConnection *)connection 70 | { 71 | MyLog(@"Inside %s", __FUNCTION__); 72 | dispatch_async(dispatch_get_main_queue(), ^{ 73 | self.messageTextField.enabled = YES; 74 | self.messageTextField.hidden = NO; 75 | self.nicknameTextField.enabled = YES; 76 | self.nicknameTextField.hidden = NO; 77 | self.sendButton.enabled = YES; 78 | self.sendButton.hidden = NO; 79 | [self.messageTextField becomeFirstResponder]; 80 | [self.activityIndicator stopAnimating]; 81 | }); 82 | } 83 | 84 | - (void)connection:(SKYLINKConnection *)connection didConnectToRoomFailed:(NSString *)errorMessage 85 | { 86 | [UIAlertController showAlertWithAutoDisappearTitle:@"Connection failed" message:errorMessage duration:3 onViewController:self]; 87 | [self.navigationController popViewControllerAnimated:YES]; 88 | } 89 | 90 | - (void)connection:(SKYLINKConnection *)connection didDisconnectFromRoomWithSkylinkEvent:(NSDictionary *)skylinkEvent contextDescription:(NSString *)contextDescription 91 | { 92 | // [UIAlertController showAlertWithAutoDisappearTitle:@"Disconnected" message:contextDescription duration:3 onViewController:self]; 93 | // [self.navigationController popViewControllerAnimated:YES]; 94 | } 95 | 96 | 97 | #pragma mark - SKYLINKConnectionMessagesDelegate 98 | - (void)connection:(SKYLINKConnection *)connection didReceiveServerMessage:(id)message isPublic:(BOOL)isPublic timeStamp:(long long)timeStamp remotePeerId:(NSString *)remotePeerId{ 99 | NSLog(@"SIG message"); 100 | if ([message isKindOfClass:[NSString class]]) { 101 | SAMessage *receivedMsg = [[SAMessage alloc] initWithData:message timeStamp:timeStamp sender:[self getUserNameFrom:remotePeerId] target:(isPublic ? nil : _skylinkConnection.localPeerId) type:SAMessageTypeP2P]; 102 | [_messages addObject:receivedMsg]; 103 | [_tableView reloadData]; 104 | } 105 | } 106 | - (void)connection:(SKYLINKConnection *)connection didReceiveP2PMessage:(id)message isPublic:(BOOL)isPublic timeStamp:(long long)timeStamp remotePeerId:(NSString *)remotePeerId{ 107 | NSLog(@"P2P message"); 108 | if ([message isKindOfClass:[NSString class]]) { 109 | SAMessage *receivedMsg = [[SAMessage alloc] initWithData:message timeStamp:timeStamp sender:[self getUserNameFrom:remotePeerId] target:(isPublic ? nil : _skylinkConnection.localPeerId) type:SAMessageTypeP2P]; 110 | [_messages addObject:receivedMsg]; 111 | [_tableView reloadData]; 112 | } 113 | } 114 | 115 | - (void)connection:(SKYLINKConnection *)connection didReceiveP2PMessage:(id)message isPublic:(BOOL)isPublic remotePeerId:(NSString *)remotePeerId 116 | { 117 | [self.messages insertObject:@{@"message" : message, @"isPublic" : @(isPublic), @"peerId" : remotePeerId, @"type" : @"P2P"} atIndex:0]; 118 | [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade]; 119 | } 120 | 121 | #pragma mark - SKYLINKConnectionRemotePeerDelegate 122 | - (void)connection:(SKYLINKConnection *)connection didConnectWithRemotePeer:(NSString *)remotePeerId userInfo:(id)userInfo hasDataChannel:(BOOL)hasDataChannel 123 | { 124 | NSString *displayNickname = (userInfo && [userInfo isKindOfClass:[NSDictionary class]] && userInfo[@"nickname"]) ? userInfo[@"nickname"] : [NSString stringWithFormat:@"ID: %@", remotePeerId]; 125 | [self.peers addEntriesFromDictionary:@{remotePeerId:displayNickname}]; 126 | [self updatePeersButtonTitle]; 127 | [self.tableView reloadData]; 128 | [self.activityIndicator stopAnimating]; 129 | } 130 | 131 | - (void)connection:(SKYLINKConnection *)connection didReceiveRemotePeerLeaveRoom:(NSString *)remotePeerId userInfo:(id)userInfo skylinkInfo:(NSDictionary *)skylinkInfo 132 | { 133 | MyLog(@"Peer with ID %@ left with skylinkInfo: %@", remotePeerId, skylinkInfo); 134 | [self.peers removeObjectForKey:remotePeerId]; 135 | [self updatePeersButtonTitle]; 136 | } 137 | #pragma mark - Table view data source 138 | 139 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 140 | return 1; 141 | } 142 | 143 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 144 | return self.messages.count; 145 | } 146 | 147 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 148 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"messageCell"]; 149 | 150 | SAMessage *message = [_messages objectAtIndex: _messages.count - indexPath.row - 1]; 151 | 152 | cell.textLabel.text = [NSString stringWithFormat:@"%@~~~%@", [message timeStampString], message.data]; 153 | cell.detailTextLabel.text = [NSString stringWithFormat:@"From %@ via %@ • %@", message.sender, message.typeToString, [message isPublicString]]; 154 | return cell; 155 | } 156 | 157 | #pragma mark - Table view delegate 158 | 159 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 160 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 161 | SAMessage *message = [self.messages objectAtIndex:_messages.count - indexPath.row - 1]; 162 | NSString *messageDetails = [NSString stringWithFormat:@"Message:\n%@\n\nFrom :\n%@\n\n%@", message.data, ([message.sender isEqualToString:USER_NAME] ? @"me" : message.sender), [message isPublicString]]; 163 | showAlert(@"Message Detail", messageDetails); 164 | } 165 | 166 | #pragma mark - IBActions 167 | 168 | - (IBAction)sendTap:(UIButton *)sender { 169 | _skylinkConnection.messagePersist = _persistSwitch.isOn; 170 | NSString *message = _messageTextField.text; 171 | if (_peers.count<=0) { 172 | showAlert(@"No peer connected", @"\nYou can't define a private recipient since there is no peer connected."); 173 | return; 174 | } 175 | 176 | if (![message isNotEmpty]) { 177 | showAlert(@"Empty Message", @"\nType the message to be sent."); 178 | return; 179 | } 180 | 181 | if (_isPublicSwitch.isOn) { 182 | //send public 183 | [self sendMessage:message forPeerId:nil]; 184 | }else{ 185 | //send private 186 | UIAlertController *_alert = [UIAlertController alertControllerWithTitle:@"Choose a private recipient." message:@"\nYou're about to send a private message\nWho do you want to send it to ?" preferredStyle:UIAlertControllerStyleAlert]; 187 | UIAlertAction *_cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault handler:nil]; 188 | __weak __typeof(self)weakSelf = self; 189 | for (NSString *peerDicKey in _peers.allKeys) { 190 | UIAlertAction *_peerAction = [UIAlertAction actionWithTitle:_peers[peerDicKey] style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 191 | [weakSelf sendMessage:message forPeerId:peerDicKey]; 192 | // [weakSelf alert:self->_peers[peerDicKey] message:message]; 193 | }]; 194 | [_alert addAction:_peerAction]; 195 | } 196 | [_alert addAction:_cancelAction]; 197 | [self presentViewController:_alert animated:YES completion:nil]; 198 | } 199 | } 200 | 201 | - (IBAction)dismissKeyboardTap:(UIButton *)sender { 202 | [self hideKeyboardIfNeeded]; 203 | } 204 | 205 | - (IBAction)peersTap:(UIButton *)sender { 206 | [UIAlertController showAlertWithAutoDisappearTitle:sender.titleLabel.text message:[self.peers description] duration:3 onViewController:self]; 207 | } 208 | - (IBAction)doneEncryptSecret:(UIButton *)sender{ 209 | [self.pickerViewContainer setHidden:YES]; 210 | } 211 | 212 | 213 | #pragma mark - Utils 214 | -(void)alert:(NSString *)title message:(NSString*)message{ 215 | showAlert(title, message); 216 | [self sendMessage:message forPeerId:[title stringByReplacingOccurrencesOfString:@"ID: " withString:@""]]; 217 | } 218 | 219 | - (void)sendMessage:(NSString *)message forPeerId:(NSString *)peerId { // nil peerId means public message 220 | void (^processResponse)(NSError *, SAMessageType) = ^(NSError *error, SAMessageType type){ 221 | if (error) { 222 | showAlert([NSString stringWithFormat:@"ERROR: %ld", (long)error.code], error.localizedDescription); 223 | }else{ 224 | SAMessage *msg = [[SAMessage alloc] initWithData:message timeStamp:[[NSDate date] toTimeStamp] sender:USER_NAME target:peerId type:type]; 225 | [self->_messages addObject:msg]; 226 | self.messageTextField.text = @""; 227 | [self.tableView reloadData]; 228 | showAlert(message, peerId ? peerId : @"All"); 229 | } 230 | }; 231 | if (_messageTypeSegmentControl.selectedSegmentIndex) { 232 | [_skylinkConnection sendServerMessage:message toRemotePeerId:peerId callback:^(NSError * _Nullable error) { 233 | processResponse(error, SAMessageTypeSignaling); 234 | }]; 235 | }else{ 236 | [_skylinkConnection sendP2PMessage:message toRemotePeerId:peerId callback:^(NSError * _Nullable error) { 237 | processResponse(error, SAMessageTypeP2P); 238 | }]; 239 | } 240 | 241 | } 242 | 243 | - (void)updateNickname { 244 | if (self.nicknameTextField.text.length > 0) [_skylinkConnection sendLocalUserData:@{@"nickname" : self.nicknameTextField.text} callback:^(NSError * _Nullable error) { 245 | if (error) [UIAlertController showAlertWithAutoDisappearTitle:@"Error" message:error.localizedDescription duration:3 onViewController:self]; 246 | }]; 247 | else [UIAlertController showAlertWithAutoDisappearTitle:@"Empty nickname" message:@"\nType the nickname to set." duration:3 onViewController:self]; 248 | } 249 | 250 | - (void)updatePeersButtonTitle { 251 | NSUInteger peersCount = self.peers.count; 252 | if (peersCount == 0) [self.peersButton setTitle:@"No peer" forState:UIControlStateNormal]; 253 | else [self.peersButton setTitle:[NSString stringWithFormat:@"%lu peer%@", (unsigned long)peersCount, (peersCount > 1) ? @"s" : @""] forState:UIControlStateNormal]; 254 | } 255 | 256 | - (void)hideKeyboardIfNeeded { 257 | [self.messageTextField resignFirstResponder]; 258 | [self.nicknameTextField resignFirstResponder]; 259 | [self.encryptKeyTextField resignFirstResponder]; 260 | } 261 | - (void)loadStoredMessage{ 262 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 263 | [self->_skylinkConnection getStoredMessages:^(NSArray * _Nullable storedMessages, NSDictionary * _Nullable errors) { 264 | if (!self.view.window) { 265 | return; 266 | } 267 | if (errors) { 268 | showAlert(@"Error map", errors.description); 269 | } 270 | for (NSDictionary *item in storedMessages) { 271 | if ([item isKindOfClass:[NSDictionary class]]) { 272 | SAMessage *message = [[SAMessage alloc] initWithData:item[@"data"] timeStamp:[item[@"timeStamp"] longLongValue] sender:[self getUserNameFrom:item[@"peerId"]] target:nil type:SAMessageTypeSignaling]; 273 | [self.messages addObject:message]; 274 | } 275 | } 276 | [self.tableView reloadData]; 277 | }]; 278 | }); 279 | } 280 | - (NSString *)getUserNameFrom:(NSString *)peerId{ 281 | NSDictionary *userInfo = [_skylinkConnection getUserInfo:peerId]; 282 | if (userInfo) { 283 | return userInfo[@"userData"]; 284 | } 285 | return peerId; 286 | } 287 | 288 | #pragma mark - UITextField delegate 289 | 290 | - (BOOL)textFieldShouldReturn:(UITextField *)textField { 291 | if ([textField isEqual:self.nicknameTextField]) [self updateNickname]; 292 | [self hideKeyboardIfNeeded]; 293 | return YES; 294 | } 295 | - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField{ 296 | if (textField == self.encryptKeyTextField) { 297 | [self hideKeyboardIfNeeded]; 298 | [_pickerViewContainer setHidden:NO]; 299 | return NO; 300 | } 301 | return YES; 302 | } 303 | #pragma mark - PICKER VIEW 304 | - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{ 305 | return 1; 306 | } 307 | - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{ 308 | return _encryptSecretIds.count; 309 | } 310 | 311 | - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{ 312 | return _encryptSecretIds[row]; 313 | } 314 | 315 | - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{ 316 | NSString *selectedEncryptSecret = (row == 0) ? nil : _encryptSecretIds[row]; 317 | _skylinkConnection.selectedSecretId = selectedEncryptSecret; 318 | _encryptKeyTextField.text = _encryptSecretIds[row]; 319 | } 320 | @end 321 | 322 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/FileTransferViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FileTransferViewController.m 3 | // Skylink_Examples 4 | // 5 | // Created by Temasys on 18/12/2015. 6 | // Copyright © 2015 Temasys. All rights reserved. 7 | // 8 | 9 | #import "FileTransferViewController.h" 10 | #import 11 | #import 12 | #import 13 | #import 14 | #import "Constant.h" 15 | 16 | //#define ROOM_NAME [[NSUserDefaults standardUserDefaults] objectForKey:@"ROOMNAME_FILETRANSFER"] 17 | 18 | 19 | @interface FileTransferViewController () 20 | // IBOutlets 21 | @property (weak, nonatomic) IBOutlet UITableView *peersTableView; 22 | @property (weak, nonatomic) IBOutlet UITableView *fileTransferTableView; 23 | @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator; 24 | 25 | 26 | // Other properties 27 | @property (strong, nonatomic) NSMutableArray *remotePeerArray; // array holding the ids (strings) of the peers connected to the room 28 | @property (strong, nonatomic) NSMutableArray *transfersArray; // array of dictionnaries holding infos about started (and finished) file transfers 29 | 30 | @property (strong, nonatomic) AVAudioPlayer *musicPlayer; 31 | @property (assign, nonatomic) NSNumber *selectedRow; 32 | 33 | @property (strong, nonatomic) UIImagePickerController *pickerController; 34 | @end 35 | 36 | 37 | 38 | @implementation FileTransferViewController 39 | 40 | - (void)viewDidLoad { 41 | [super viewDidLoad]; 42 | // Do any additional setup after loading the view. 43 | 44 | self.title = @"File Transfer"; 45 | self.remotePeerArray = [[NSMutableArray alloc] init]; 46 | self.transfersArray = [[NSMutableArray alloc] init]; 47 | 48 | SKYLINKConnectionConfig *config = [SKYLINKConnectionConfig new]; 49 | [config setAudioVideoSendConfig:AudioVideoConfig_NO_AUDIO_NO_VIDEO]; 50 | [config setAudioVideoReceiveConfig:AudioVideoConfig_NO_AUDIO_NO_VIDEO]; 51 | config.hasFileTransfer = YES; 52 | [config setTimeout:30 skylinkAction:SkylinkAction_FILE_SEND_REQUEST]; 53 | // Creating SKYLINKConnection 54 | _skylinkConnection = [[SKYLINKConnection alloc] initWithConfig:config callback:nil]; 55 | _skylinkConnection.lifeCycleDelegate = self; 56 | _skylinkConnection.fileTransferDelegate = self; 57 | _skylinkConnection.remotePeerDelegate = self; 58 | // Connecting to a room 59 | [self joinRoom]; 60 | } 61 | 62 | 63 | // Table View 64 | #pragma mark - Table view data source 65 | 66 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 67 | NSString *title; 68 | if ([tableView isEqual:self.peersTableView]) { 69 | if (section == 0) title = (self.remotePeerArray.count > 0) ? @"Or select a connected peer recipient:" : @"No peer connected yet"; 70 | } else if ([tableView isEqual:self.fileTransferTableView]) title = [NSString stringWithFormat:@"File transfers (%lu)", (unsigned long)self.transfersArray.count]; 71 | return title; 72 | } 73 | 74 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 75 | return 1; 76 | } 77 | 78 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 79 | NSInteger rowCount; 80 | if ([tableView isEqual:self.peersTableView]) rowCount = self.remotePeerArray.count; 81 | else rowCount = self.transfersArray.count; 82 | return rowCount; 83 | } 84 | 85 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 86 | UITableViewCell *cell; 87 | if ([tableView isEqual:self.peersTableView]) { 88 | cell = [tableView dequeueReusableCellWithIdentifier:@"peerCell"]; 89 | cell.textLabel.text = [NSString stringWithFormat:@"Peer %ld, ID: %@", (long)indexPath.row + 1, [self.remotePeerArray objectAtIndex:indexPath.row]]; 90 | } else if ([tableView isEqual:self.fileTransferTableView]) { 91 | cell = [tableView dequeueReusableCellWithIdentifier:@"fileTransferCell"]; 92 | NSDictionary *trInfos = [self.transfersArray objectAtIndex:indexPath.row]; 93 | cell.textLabel.text = [NSString stringWithFormat:@"%@ %.0f%% • %@", [trInfos[@"isOutgoing"] boolValue] ? @"⬆️" : @"⬇️", ([trInfos[@"percentage"] floatValue] * 100), trInfos[@"state"]]; 94 | cell.detailTextLabel.text = [NSString stringWithFormat:@"File: %@ • Peer: %@", trInfos[@"filename"], trInfos[@"peerId"]]; 95 | } 96 | return cell; 97 | } 98 | 99 | #pragma mark Table view delegate 100 | 101 | - (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section { 102 | if ([tableView isEqual:self.peersTableView] && [view isKindOfClass:[UITableViewHeaderFooterView class]]) ((UITableViewHeaderFooterView *)view).textLabel.textColor = [UIColor lightGrayColor]; 103 | } 104 | 105 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 106 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 107 | if ([tableView isEqual:self.peersTableView]) { 108 | self.selectedRow = [NSNumber numberWithInteger:indexPath.row]; 109 | [self showTransferFormForRecipient:self.remotePeerArray[indexPath.row]]; 110 | } else if ([tableView isEqual:self.fileTransferTableView]) { 111 | NSDictionary *transferInfos = self.transfersArray[indexPath.row]; 112 | if ([transferInfos[@"state"] isEqualToString:@"In progress"]) { // then ask confirmation for transfer drop 113 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Cancel file transfer ?" message:[NSString stringWithFormat:@"\nCancel file transfer for filename:\n'%@'\npeer ID:\n%@", transferInfos[@"filename"], transferInfos[@"peerId"]] preferredStyle:UIAlertControllerStyleAlert]; 114 | UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Drop transfer" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 115 | // cancel transfer if not finished 116 | if ([self.transfersArray[indexPath.row][@"state"] isEqualToString:@"In progress"]) { // because transfer could be completed after alert showed up 117 | [_skylinkConnection cancelFileTransferWithRemotePeerId:transferInfos[@"peerId"] forSending:NO callback:^(NSError * _Nullable error) { 118 | }]; 119 | [self updateFileTranferInfosForFilename:transferInfos[@"filename"] peerId:transferInfos[@"peerId"] withState:@"Cancelled" progress:[transferInfos[@"progress"] floatValue] isOutgoing:[transferInfos[@"isOutgoing"] boolValue]]; 120 | } else [UIAlertController showAlertWithAutoDisappearTitle:@"Can not cancel" message:@"Transfer already completed" duration:3 onViewController:self]; 121 | }]; 122 | UIAlertAction *continueAction = [UIAlertAction actionWithTitle:@"Continue transfer" style:UIAlertActionStyleCancel handler:nil]; 123 | [alertController addAction:cancelAction]; 124 | [alertController addAction:continueAction]; 125 | [self presentViewController:alertController animated:YES completion:^{ 126 | }]; 127 | } else [UIAlertController showAlertWithAutoDisappearTitle:@"Transfer details" message:transferInfos.description duration:3 onViewController:self]; 128 | } 129 | } 130 | 131 | #pragma mark - UIImagePickerControllerDelegate 132 | 133 | - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { 134 | NSString *peerId = nil; 135 | if (self.selectedRow && [self.selectedRow intValue] < self.remotePeerArray.count) peerId = self.remotePeerArray[[self.selectedRow intValue]]; 136 | [self startFileTransfer:peerId url:info[UIImagePickerControllerReferenceURL] type:SKYLINKAssetTypePhoto]; 137 | [picker dismissViewControllerAnimated:YES completion:nil]; 138 | } 139 | 140 | #pragma mark - MPMediaPickerControllerDelegate 141 | 142 | - (void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection { 143 | [mediaPicker dismissViewControllerAnimated:YES completion:nil]; 144 | NSString *peerId = nil; 145 | if (self.selectedRow && [self.selectedRow intValue] < self.remotePeerArray.count) peerId = self.remotePeerArray[[self.selectedRow intValue]]; 146 | [self startFileTransfer:peerId url:[mediaItemCollection.representativeItem valueForProperty:MPMediaItemPropertyAssetURL] type:SKYLINKAssetTypeMusic]; 147 | } 148 | 149 | - (void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker { 150 | [mediaPicker dismissViewControllerAnimated:YES completion:nil]; 151 | } 152 | 153 | 154 | // SKYLINK Delegate methods implementations 155 | #pragma mark - SKYLINKConnectionLifeCycleDelegate 156 | - (void)connectionDidConnectToRoomSuccessful:(SKYLINKConnection *)connection 157 | { 158 | MyLog(@"Connection success :D"); 159 | [self.activityIndicator stopAnimating]; 160 | } 161 | 162 | - (void)connection:(SKYLINKConnection *)connection didConnectToRoomFailed:(NSString *)errorMessage 163 | { 164 | [UIAlertController showAlertWithAutoDisappearTitle:@"Connection failed" message:errorMessage duration:3 onViewController:self]; 165 | [self.navigationController popViewControllerAnimated:YES]; 166 | } 167 | 168 | - (void)connection:(SKYLINKConnection *)connection didDisconnectFromRoomWithSkylinkEvent:(NSDictionary *)skylinkEvent contextDescription:(NSString *)contextDescription 169 | { 170 | // [UIAlertController showAlertWithAutoDisappearTitle:@"Disconnected" message:contextDescription duration:3 onViewController:self]; 171 | // [self.navigationController popViewControllerAnimated:YES]; 172 | } 173 | #pragma mark SKYLINKConnectionRemotePeerDelegate 174 | - (void)connection:(SKYLINKConnection *)connection didConnectWithRemotePeer:(NSString *)remotePeerId userInfo:(id)userInfo hasDataChannel:(BOOL)hasDataChannel 175 | { 176 | MyLog(@"Peer with id %@ joigned the room, properties: %@", remotePeerId, userInfo); 177 | [self.remotePeerArray addObject:remotePeerId]; 178 | [self.peersTableView reloadData]; 179 | } 180 | 181 | - (void)connection:(SKYLINKConnection *)connection didDisconnectWithRemotePeer:(NSString *)remotePeerId userInfo:(id)userInfo hasDataChannel:(BOOL)hasDataChannel 182 | { 183 | MyLog(@"Peer with id %@ left the room with message: %@", remotePeerId, userInfo); 184 | [self.remotePeerArray removeObject:remotePeerId]; 185 | [self.peersTableView reloadData]; 186 | } 187 | 188 | - (void)connection:(SKYLINKConnection *)connection didReceiveRemotePeerUserData:(id)userData remotePeerId:(NSString *)remotePeerId 189 | { 190 | MyLog(@"Peer with id %@ left the room with message: %@", remotePeerId, userData); 191 | [self.remotePeerArray removeObject:remotePeerId]; 192 | [self.peersTableView reloadData]; 193 | } 194 | 195 | - (void)connection:(SKYLINKConnection *)connection didErrorForRemotePeerConnection:(NSError *)error remotePeerId:(NSString *)remotePeerId 196 | { 197 | MyLog(@"Peer with id %@ left the room with message: %@", remotePeerId, error); 198 | [self.remotePeerArray removeObject:remotePeerId]; 199 | [self.peersTableView reloadData]; 200 | } 201 | 202 | 203 | #pragma mark SKYLINKConnectionFileTransferDelegate 204 | - (void)connection:(SKYLINKConnection *)connection didReceiveFileTransferRequest:(NSString *)fileName isPublic:(BOOL)isPublic remotePeerId:(NSString *)remotePeerId 205 | { 206 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Accept file transfer ?" message:[NSString stringWithFormat:@"\nA user wants to send you a file named:\n'%@'", fileName] preferredStyle:UIAlertControllerStyleAlert]; 207 | UIAlertAction *declineAction = [UIAlertAction actionWithTitle:@"Decline" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { 208 | [_skylinkConnection rejectFileTransferFromRemotePeerId:remotePeerId callback:^(NSError * _Nullable error) { 209 | if (error) [UIAlertController showAlertWithAutoDisappearTitle:@"Error" message:error.localizedDescription duration:3 onViewController:self]; 210 | }]; 211 | }]; 212 | UIAlertAction *acceptAction = [UIAlertAction actionWithTitle:@"Accept" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 213 | [_skylinkConnection acceptFileTransferWithFileName:fileName fromRemotePeerId:remotePeerId callback:^(NSError * _Nullable error) { 214 | if (error) [UIAlertController showAlertWithAutoDisappearTitle:@"Error" message:error.localizedDescription duration:3 onViewController:self]; 215 | }]; 216 | }]; 217 | [alertController addAction:declineAction]; 218 | [alertController addAction:acceptAction]; 219 | [self presentViewController:alertController animated:YES completion:^{ 220 | }]; 221 | } 222 | 223 | - (void)connection:(SKYLINKConnection *)connection didReceiveFileTransferResponse:(BOOL)wasAccepted fileName:(NSString *)fileName remotePeerId:(NSString *)remotePeerId 224 | { 225 | if (!wasAccepted) [UIAlertController showAlertWithAutoDisappearTitle:@"File refused" message:[NSString stringWithFormat:@"The peer user has refused your '%@' file sending request", fileName] duration:3 onViewController:self]; 226 | } 227 | 228 | - (void)connection:(SKYLINKConnection *)connection didUpdateFileTransferSendingProgress:(CGFloat)percentage fileName:(NSString *)fileName remotePeerId:(NSString *)remotePeerId 229 | { 230 | [self updateFileTranferInfosForFilename:fileName peerId:((remotePeerId) ? remotePeerId : @"all") withState:@"In progress" progress:percentage isOutgoing:YES]; 231 | } 232 | 233 | - (void)connection:(SKYLINKConnection *)connection didUpdateFileTransferReceivingProgress:(CGFloat)percentage fileName:(NSString *)fileName remotePeerId:(NSString *)remotePeerId 234 | { 235 | [self updateFileTranferInfosForFilename:fileName peerId:((remotePeerId) ? remotePeerId : @"all") withState:@"In progress" progress:percentage isOutgoing:NO]; 236 | } 237 | 238 | - (void)connection:(SKYLINKConnection *)connection didDropFileTransfer:(NSString *)fileName message:(NSString *)message isExplicit:(BOOL)isExplicit remotePeerId:(NSString *)remotePeerId 239 | { 240 | [self updateFileTranferInfosForFilename:fileName peerId:((remotePeerId) ? remotePeerId : @"all") withState:((message.length) ? message : @"Dropped by sender") progress:0 isOutgoing:isExplicit]; 241 | } 242 | 243 | - (void)connection:(SKYLINKConnection *)connection didCompleteFileTransferReceiving:(NSString *)fileName fileData:(NSData *)fileData fileSavePath:(NSString *)fileSavePath remotePeerId:(NSString *)remotePeerId 244 | { 245 | [self updateFileTranferInfosForFilename:fileName peerId:((remotePeerId) ? remotePeerId : @"all") withState:@"Completed ✓" progress:1 isOutgoing:NO]; 246 | if (fileData) { 247 | NSString *fileExtension = [[fileName componentsSeparatedByString:@"."] lastObject]; 248 | fileName = [fileName stringByReplacingOccurrencesOfString:@" " withString:@"_"]; 249 | if ([self isImage:fileExtension] && [UIImage imageWithData:fileData]) { 250 | UIImageWriteToSavedPhotosAlbum([UIImage imageWithData:fileData], self, @selector(image:didFinishSavingWithError:contextInfo:), (__bridge void *)(fileName)); 251 | } else if ([fileExtension isEqualToString:@"mp3"] || [fileExtension isEqualToString:@"m4a"]) { 252 | NSError *pError; 253 | self.musicPlayer = [fileExtension isEqualToString:@"mp3"] ? [[AVAudioPlayer alloc] initWithData:fileData fileTypeHint:AVFileTypeMPEGLayer3 error:&pError] : [[AVAudioPlayer alloc] initWithData:fileData error:&pError]; 254 | if (!pError) [self.musicPlayer play]; 255 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Music transfer completed" message:[NSString stringWithFormat:@"File transfer success.\nPEER: %@\n\nPlaying the received music file:\n'%@'", remotePeerId, fileName] preferredStyle:UIAlertControllerStyleAlert]; 256 | UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Stop playing" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { 257 | [self.musicPlayer stop]; 258 | self.musicPlayer = nil; 259 | }]; 260 | [alertController addAction:cancelAction]; 261 | [self presentViewController:alertController animated:YES completion:^{ 262 | }]; 263 | } else { 264 | NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 265 | NSString *filePath = [[pathArray firstObject] stringByAppendingPathComponent:fileName]; 266 | if ([[NSFileManager defaultManager] fileExistsAtPath:filePath] && ![self removeFileAtPath:filePath]) return; 267 | NSError *wError; 268 | [fileData writeToFile:filePath options:NSDataWritingAtomic error:&wError]; 269 | if (wError) { 270 | MyLog(@"%s • Error while writing '%@'->%@", __FUNCTION__, filePath, wError.localizedDescription); 271 | } else { 272 | MyLog(@"File saved at %@", filePath); 273 | if ([self isMovie:fileExtension] && UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(filePath)) { 274 | [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ 275 | PHAssetChangeRequest *createAssetRequest = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:[NSURL URLWithString:filePath]]; 276 | NSParameterAssert(createAssetRequest); 277 | } completionHandler:^(BOOL success, NSError * _Nullable error) { 278 | if (error) MyLog(@"%s • Error while saving '%@'->%@", __FUNCTION__, fileName, error.localizedDescription); 279 | else [self removeFileAtPath:filePath]; 280 | }]; 281 | } 282 | } 283 | } 284 | } 285 | } 286 | 287 | #pragma mark - other methods 288 | 289 | - (void)updateFileTranferInfosForFilename:(NSString *)filename peerId:(NSString *)peerId withState:(NSString *)state progress:(CGFloat)percentage isOutgoing:(BOOL)isOutgoing { 290 | NSInteger indexOfTransfer = [self.transfersArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { 291 | return ([((NSDictionary *)obj)[@"filename"] isEqualToString:filename] && [((NSDictionary *)obj)[@"peerId"] isEqualToString:peerId]); 292 | }]; 293 | if (indexOfTransfer == NSNotFound) { // new transfer 294 | [self.transfersArray insertObject:@{@"filename" : (filename) ? filename : @"none", @"peerId" : (peerId) ? peerId : @"No peer ID", @"isOutgoing" : @(isOutgoing), @"percentage" : @(percentage), @"state" : (state) ? state : @"Undefined"} atIndex:0]; 295 | } else { // updated transfer 296 | NSMutableDictionary *transferInfos = [NSMutableDictionary dictionaryWithDictionary:self.transfersArray[indexOfTransfer]]; 297 | if (filename) [transferInfos setObject:filename forKey:@"filename"]; 298 | if (peerId) [transferInfos setObject:peerId forKey:@"peerId"]; 299 | if (isOutgoing) [transferInfos setObject:@(isOutgoing) forKey:@"isOutgoing"]; 300 | if (percentage) [transferInfos setObject:@(percentage) forKey:@"percentage"]; 301 | if (state) [transferInfos setObject:state forKey:@"state"]; 302 | [self.transfersArray replaceObjectAtIndex:indexOfTransfer withObject:transferInfos]; 303 | } 304 | __weak __typeof(self)weakSelf = self; 305 | dispatch_async(dispatch_get_main_queue(), ^{ 306 | __strong __typeof(weakSelf)strongSelf = weakSelf; 307 | [strongSelf.fileTransferTableView reloadData]; 308 | }); 309 | } 310 | 311 | - (void)startFileTransfer:(NSString *)userId url:(NSURL *)fileURL type:(SKYLINKAssetType)transferType { 312 | 313 | if (userId && fileURL) { 314 | @try { 315 | [_skylinkConnection sendFileTransferWithFileURL:fileURL assetType:transferType fileName:nil remotePeerId:userId callback:^(NSError * _Nullable error) { 316 | if (error) [UIAlertController showAlertWithAutoDisappearTitle:@"Error" message:error.localizedDescription duration:3 onViewController:self]; 317 | }]; 318 | } @catch (NSException *exception) { 319 | [UIAlertController showAlertWithAutoDisappearTitle:@"Error" message:[NSString stringWithFormat:@"%@", exception] duration:3 onViewController:self]; 320 | } 321 | } else if (fileURL) { 322 | [_skylinkConnection sendFileTransferWithFileURL:fileURL assetType:transferType fileName:nil remotePeerId:nil callback:^(NSError * _Nullable error) { 323 | 324 | }]; 325 | } else { 326 | [UIAlertController showAlertWithAutoDisappearTitle:@"No file URL" message:@"\nError: there is no file URL. Try another media." duration:3 onViewController:self]; 327 | } 328 | } 329 | 330 | - (void)showTransferFormForRecipient:(NSString *)peerId { 331 | NSString *message; 332 | if (peerId) message = [NSString stringWithFormat:@"\nYou are about to send a tranfer request to user with ID \n%@\nWhat do you want to send ?", peerId]; 333 | else message = @"\nYou are about to send a tranfer request all users\nWhat do you want to send ?"; 334 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Send a file." message:message preferredStyle:UIAlertControllerStyleAlert]; 335 | UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { 336 | }]; 337 | UIAlertAction *acceptPhotoAction = [UIAlertAction actionWithTitle:@"Photo / Video (pick from library)" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 338 | if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) { 339 | self.pickerController = [UIImagePickerController new]; 340 | self.pickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; 341 | self.pickerController.delegate = self; 342 | self.pickerController.mediaTypes = [[NSArray alloc] initWithObjects: (NSString *) kUTTypeImage, (NSString*) kUTTypeMovie, nil]; 343 | [self presentViewController:self.pickerController animated:YES completion:nil]; 344 | } 345 | }]; 346 | UIAlertAction *acceptMusicAction = [UIAlertAction actionWithTitle:@"Music (pick from library)" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 347 | MPMediaPickerController *pickerController = [MPMediaPickerController new]; 348 | pickerController.delegate = self; 349 | [self presentViewController:pickerController animated:YES completion:nil]; 350 | }]; 351 | UIAlertAction *acceptFileAction = [UIAlertAction actionWithTitle:@"File (prepared image)" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 352 | NSString *peerId = nil; 353 | if (self.selectedRow && [self.selectedRow intValue] < self.remotePeerArray.count) peerId = self.remotePeerArray[[self.selectedRow intValue]]; 354 | NSString *filePath = [[NSBundle mainBundle] pathForResource:((peerId) ? @"sampleImage_transfer" : @"sampleImage_groupTransfer") ofType:@"png" inDirectory:@"TransferFileSamples"]; 355 | [self startFileTransfer:peerId url:[NSURL URLWithString:filePath] type:SKYLINKAssetTypeFile]; 356 | }]; 357 | [alertController addAction:cancelAction]; 358 | [alertController addAction:acceptPhotoAction]; 359 | [alertController addAction:acceptMusicAction]; 360 | [alertController addAction:acceptFileAction]; 361 | [self presentViewController:alertController animated:YES completion:^{ 362 | }]; 363 | } 364 | 365 | #pragma mark - IBActions 366 | 367 | - (IBAction)sendToAllTap:(UIButton *)sender { 368 | self.selectedRow = nil; 369 | if (self.remotePeerArray.count > 0) [self showTransferFormForRecipient:nil]; 370 | else [UIAlertController showAlertWithAutoDisappearTitle:@"No peer connected" message:@"Wait for someone to connect before sending files." duration:3 onViewController:self]; 371 | } 372 | 373 | 374 | #pragma mark - Utils 375 | 376 | - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { 377 | if (error) { 378 | MyLog(@"%s • Error while saving '%@'->%@", __FUNCTION__, contextInfo, error.localizedDescription); 379 | MyLog(@"%s • Now trying to save image in the Documents Directory", __FUNCTION__); 380 | NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 381 | NSString *filePath = [[pathArray firstObject] stringByAppendingPathComponent:(__bridge NSString *)(contextInfo)]; 382 | if ([[NSFileManager defaultManager] fileExistsAtPath:filePath] && ![self removeFileAtPath:filePath]) return; 383 | NSError *wError; 384 | [UIImagePNGRepresentation(image) writeToFile:filePath options:NSDataWritingAtomic error:&wError]; 385 | if (wError) MyLog(@"%s • Error while writing '%@'->%@", __FUNCTION__, filePath, wError.localizedDescription); 386 | } else { 387 | MyLog(@"%s • Image saved successfully", __FUNCTION__); 388 | } 389 | } 390 | 391 | - (BOOL)removeFileAtPath:(NSString*)filePath { 392 | BOOL succeed = NO; 393 | NSError *error; 394 | [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error]; 395 | if (error) MyLog(@"%s • Error while removing '%@'->%@", __FUNCTION__, filePath, error.localizedDescription); 396 | else succeed = YES; 397 | return succeed; 398 | } 399 | 400 | - (BOOL)isImage:(NSString*)extension { 401 | return [@[@"jpg", @"jpeg", @"jpe", @"jif", @"jfif", @"jfi", @"jp2", @"j2k", @"jpf", @"jpx", @"jpm", @"tiff", @"tif", @"pict", @"pct", @"pic", @"gif", @"png", @"qtif", @"icns", @"bmp", @"bmpf", @"ico", @"cur", @"xbm"] containsObject:[extension lowercaseString]]; 402 | } 403 | - (BOOL)isMovie:(NSString*)extension { 404 | return [@[@"mpg", @"mpeg", @"m1v", @"mpv", @"3gp", @"3gpp", @"sdv", @"3g2", @"3gp2", @"m4v", @"mp4", @"mov", @"qt"] containsObject:[extension lowercaseString]]; 405 | } 406 | 407 | 408 | @end 409 | 410 | 411 | -------------------------------------------------------------------------------- /SkylinkSample/SkylinkSample/StatsView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 29 | 36 | 43 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 73 | 80 | 87 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 117 | 124 | 131 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 161 | 168 | 175 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | --------------------------------------------------------------------------------