Voxeet SDK is a Swift library allowing users to:
- Create / join conferences
- Change sounds angle and direction for each conference user
- Broadcast messages to other participants
- Send and receive video stream
- Record and replay a conference
- Receive calls with CallKit
- Requirements
- Sample Application
- Installing the iOS SDK
- SDK Initialization
- SDK Usage
- VTAudioSound Usage
- Available delegates / callbacks
- Publish your app with the Voxeet SDK
- iOS 9.0+
- Xcode 9.0+
- Swift 4.0+ / Objective-C
A sample application is available on this GitHub repository. You can also use a ready to go UI: VoxeetConferenceKit available at this link (which embed this SDK).
You need to disable Bitcode in your Xcode target settings: 'Build Settings' -> 'Enable Bitcode' -> No
Enable background mode (go to your target settings -> 'Capabilities' -> 'Background Modes')
- Turn on 'Audio, AirPlay and Picture in Picture'
- Turn on 'Voice over IP' (Xcode 9 bug missing)
If you want to support CallKit (receiving incoming call when application is killed) with VoIP push notification, enable 'Push Notifications' (you will need to send your VoIP push certificate to Voxeet).
Privacy permissions, add two new keys in the Info.plist:
- Privacy - Microphone Usage Description
- Privacy - Camera Usage Description
CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:
$ sudo gem install cocoapods --pre
To integrate VoxeetSDK into your Xcode project using CocoaPods, specify it in your Podfile
:
use_frameworks!
target "YourTarget" do
pod 'VoxeetSDK', '~> 1.0'
end
Then, run the following command:
$ pod install
Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
You can install Carthage with Homebrew using the following command:
$ brew update
$ brew install carthage
To integrate VoxeetSDK into your Xcode project using Carthage, specify it in your Cartfile
:
github "voxeet/voxeet-ios-sdk" ~> 1.0
Run carthage update
to build the framework and drag the built VoxeetSDK.framework
into your Xcode project (needs to be dropped in 'Embedded Binaries' and 'Linked Frameworks and Libraries').
More information at https://github.com/Carthage/Carthage#if-youre-building-for-ios-tvos-or-watchos.
Checkout this repository and find the VoxeetSDK.framework
inside the VoxeetSDK folder.
Drag and drop the framework inside your project and add it to 'Embedded Binaries' and 'Linked Frameworks and Libraries' sections in your main target.
Initialize the SDK in the AppDelegate.swift of your application:
import VoxeetSDK
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Initialization of the Voxeet SDK.
VoxeetSDK.shared.initialize(consumerKey: "consumerKey", consumerSecret: "consumerSecret")
return true
}
}
VoxeetSDK.shared.initialize(consumerKey: "consumerKey", consumerSecret: "consumerSecret")
// With all parameters.
VoxeetSDK.shared.initialize(consumerKey: "consumerKey", consumerSecret: "consumerSecret", userInfo: nil, callKit: true, connectSession: false)
If you use external login like O365, LDAP, or custom login to retrieve contact details it's possible to also add your contact ID with the display name, the photo URL avatar and any kind of extra information. This allows you to ask guest users to introduce themselves and provide their display name and for your authenticated users in your enterprise or for your clients the ID that can be retrieved from O365 (name, department, etc).
VoxeetSDK.shared.initialize(consumerKey: "consumerKey", consumerSecret: "consumerSecret", userInfo: ["externalId": "1234", "externalName": "User", "externalPhotoUrl": "http://voxeet.com/voxeet-logo.jpg"])
Connecting a session is like a login. However you need to have initialized the SDK with connectSession
sets to false:
VoxeetSDK.shared.initialize(consumerKey: "consumerKey", consumerSecret: "consumerSecret", connectSession: false)
By passing the userID, it will log your user into our servers with your ID (you can additionally pass some extra information with the optional userInfo
parameter). This method can be useful if you want to implement CallKit (VoIP push notifications) because once the session is connected, you can receive notifications.
let userInfo = ["externalName": "User", "externalPhotoUrl": "http://voxeet.com/voxeet-logo.jpg"]
VoxeetSDK.shared.session.connect(userID: "userID", userInfo: userInfo, completion: { (error) in
})
If the session isn't connected with the method above, the connection will be automatically done when joinning a conference.
Disconnecting a session is like a logout, it stops the socket and it also stops sending VoIP push notification.
VoxeetSDK.shared.session.disconnect(completion: { (error) in
})
Easily test a conference with bots.
VoxeetSDK.shared.conference.demo { (error) in
}
Manually create a conference (join method implicitly creates one).
VoxeetSDK.shared.conference.create(success: { (json) in
guard let confID = json?["conferenceId"] as? String,
let confAlias = json?["conferenceAlias"] as? String else {
return
}
}, fail: { (error) in
})
You can also pass some optionals parameters when you create a conference such as conferenceAlias
(examples: ["conferenceAlias": "myCustomConferenceAlias", "conferenceType": "standard", "metadata": ...]).
Those parameters are specifics to a conference (not to be confused with userInfo for a user).
VoxeetSDK.shared.conference.create(parameters: ["conferenceAlias": "myCustomConferenceAlias", "conferenceType": "standard", "metadata": ...], success: { (json) in
}, fail: { (error) in
})
If you join a conference that doesn't exist, it will automatically creates one. Basically the join method can be used instead of the create method just above if you don't need custom preferences.
VoxeetSDK.shared.conference.join(conferenceID: conferenceID, success: { (json) in
}, fail: { (error) in
})
There are some optionals parameters too:
- video parameter: starts own video by default.
- userInfo parameter: with this dictionary, you can pass additional information linked to your user. For example if the user is only in "listener mode" you can add:
["participantType": "listener"]
. Other examples: ["externalName": "User", "externalPhotoUrl": "http://voxeet.com/voxeet-logo.jpg", ...] if the SDK isn't initilized with userInfo or if the session isn't connected yet.
VoxeetSDK.shared.conference.join(conferenceID: conferenceID, video: true, userInfo: ["participantType": "listener"], success: { (json) in
}, fail: { (error) in
})
VoxeetSDK.shared.conference.leave { (error) in
}
Getting current conference identifiers (id
is the internal Voxeet identifier for a conference despite alias
is your custom ID).
Both are nulls if there a no live conference.
let confID = VoxeetSDK.shared.conference.id
let confAlias = VoxeetSDK.shared.conference.alias
VoxeetSDK.shared.conference.broadcast(message: "message", completion: { (error) in
})
To receive a broadcast message, you can use 'messageReceived' method from VTConferenceDelegate (see below: Available delegates / callbacks
section).
VoxeetSDK.shared.conference.status(conferenceID: conferenceID, success: { (json) in
}, fail: { (error) in
})
The goal of this method is to have all updates about the conference status (participant added/removed, conference destroyed, ...) within a notification and without joinning it.
The conference needs to be created first before using this method.
Notification's name: VTConferenceStatusUpdated
init() {
NotificationCenter.default.addObserver(self, selector: #selector(conferenceStatusUpdated), name: .VTConferenceStatusUpdated, object: nil)
VoxeetSDK.shared.conference.subscribe(conferenceID: conferenceID, completion: { (error) in
})
}
@objc func conferenceStatusUpdated(_ notification: Notification) {
//
}
VoxeetSDK.shared.conference.history(conferenceID: conferenceID, success: { (json) in
}, fail: { (error) in
})
You can retrieve 0 to 40 events per pages.
Use lastMeetingId
& lastTimestamp
from the returned JSON to handle paging.
VoxeetSDK.shared.conference.histories(nbEvents: 10, lastConferenceID: nil, lastConferenceTimestamp: nil, success: { (json) in
}, fail: { (error) in
})
Invite some users in a conference. If CallKit and push notifications are enabled, it will ring on invited users' devices with the Apple incoming call user interface.
VoxeetSDK.shared.conference.invite(conferenceID: confID, ids: ["userID", ...], completion: { (error) in
})
Decline a call (conference users will receive a VTParticipantUpdated notification).
VoxeetSDK.shared.conference.decline(conferenceID: confID, completion: { (error) in
})
To record you need to be in the conference.
// Start recording.
VoxeetSDK.shared.conference.startRecording(conferenceID: conferenceID, completion: { (error) in
})
// Stop recording.
VoxeetSDK.shared.conference.stopRecording(conferenceID: conferenceID, completion: { (error) in
})
This method works like demo
or join
methods, it will automatically start the recorded conference like a normal one.
VoxeetSDK.shared.conference.replay(conferenceID: conferenceID, completion: { (error) in
})
You can pass an additional argument to start the replay after x milliseconds (offset).
// Replay a conference without the first second.
VoxeetSDK.shared.conference.replay(conferenceID: conferenceID, offset: 1000, completion: { (error) in
})
Returns a VTUser object.
let ownUser = VoxeetSDK.shared.session.user
let user = VoxeetSDK.shared.conference.user(userID: "userID")
Returns an array of VTUser object (containing current users' ID and information).
let users = VoxeetSDK.shared.conference.users
With this method you can spatialized sound for a user with the TrueVoice technology (only locally).
// Values for angle and distance are between: angle = [-1, 1], distance = [0, 1]
VoxeetSDK.shared.conference.userPosition(userID: "userID", angle: 0, distance: 0)
VoxeetSDK.shared.conference.mute(userID: "userID", isMuted: true)
let isMuted = VoxeetSDK.shared.conference.toggleMute(userID: "userID")
let voiceLevel = VoxeetSDK.shared.conference.voiceLevel(userID: "userID")
VoxeetSDK.shared.conference.switchDeviceSpeaker()
// You can also force the BuiltInSpeaker (true) / BuildInReceiver (false)
VoxeetSDK.shared.conference.switchDeviceSpeaker(forceBuiltInSpeaker: true)
You can also start the VoxeetSDK with the built in speaker enable by default or not by using this method before initializing the SDK:
VoxeetSDK.shared.defaultBuiltInSpeaker = true / false
VoxeetSDK.shared.conference.flipCamera()
A renderer can be created with a UIView that inherits from VideoRenderer
.
VoxeetSDK.shared.conference.attachMediaStream(stream, renderer: videoRenderer)
If the video renderer is removed from the UI, there is no need to call this method. This method can be useful to switch between several streams on the same video renderer.
VoxeetSDK.shared.conference.unattachMediaStream(stream, renderer: videoRenderer)
You can "mute / unmute" someone's video with its user ID (however you can't force starting or stopping a video from a specific user other than you).
let ownUserID = VoxeetSDK.shared.session.user!.id!
// Start own video.
VoxeetSDK.shared.conference.startVideo(userID: ownUserID, completion: { (error) in
})
// Stop own video.
VoxeetSDK.shared.conference.stopVideo(userID: ownUserID, completion: { (error) in
})
CallKit is disabled by default, you can enable it during the initialization:
VoxeetSDK.shared.initialize(consumerKey: "consumerKey", consumerSecret: "consumerSecret", callKit: true)
Some notifications can be interesting using along CallKit to update UI: VTCallKitStarted
, VTCallKitUpdated
and VTCallKitEnded
.
In order to handle VoIP push notifications bellow iOS 10, this AppDelegate extension is needed:
/*
* MARK: - Voxeet VoIP push notifications
*/
extension AppDelegate {
/// Useful bellow iOS 10.
func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
VoxeetSDK.shared.application(application, didReceive: notification)
}
/// Useful bellow iOS 10.
func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, completionHandler: @escaping () -> Void) {
VoxeetSDK.shared.application(application, handleActionWithIdentifier: identifier, for: notification, completionHandler: completionHandler)
}
}
class myClass: VTSessionDelegate {
init() {
// Session delegate.
VoxeetSDK.shared.session.delegate = self
}
func sessionUpdated(state: VTSessionState) {
}
}
or
VoxeetSDK.shared.session.updated = { (state) in
}
class myClass: VTConferenceDelegate {
init() {
// Conference delegate.
VoxeetSDK.shared.conference.delegate = self
}
func participantAdded(userID: String, userInfo: [String: Any], stream: MediaStream) {
}
func participantUpdated(userID: String, userInfo: [String: Any], stream: MediaStream) {
}
func participantRemoved(userID: String, userInfo: [String: Any]) {
}
func messageReceived(userID: String, userInfo: [String: Any], message: String) {
}
func screenShareStarted(userID: String, stream: MediaStream) {
}
func screenShareStopped(userID: String) {
}
}
or
VoxeetSDK.shared.conference.participantAdded = { (userID, userInfo, stream) in
}
VoxeetSDK.shared.conference.participantUpdated = { (userID, userInfo, stream) in
}
VoxeetSDK.shared.conference.participantRemoved = { (userID, userInfo) in
}
VoxeetSDK.shared.conference.messageReceived = { (userID, userInfo, message) in
}
VoxeetSDK.shared.conference.screenShareStarted = { (userID, stream) in
}
VoxeetSDK.shared.conference.screenShareStopped = { (userID) in
}
enum VTSessionState {
case connecting
case connected
case reconnecting
case disconnected
}
enum VTConferenceState {
case connecting
case connected
case disconnecting
case disconnected
}
Here is an example to handle notifications pushed by the Voxeet SDK (each time a notification is pushed, a log displays the name of this one):
[VoxeetSDK] Voxeet notification: \(NotificationName) (you can register to this notification to handle it)
init() {
NotificationCenter.default.addObserver(self, selector: #selector(myFunc), name: Notification.Name("NotificationName"), object: nil)
}
@objc func myFunc(notification: Notification) {
// Get JSON.
guard let userInfo = notification.userInfo?.values.first as? Data else {
return
}
let json = try? JSONSerialization.jsonObject(with: userInfo, options: .mutableContainers)
}
Voxeet's notifications can be handled easily like this: Notification.Name.VTConferenceStatusUpdated. This extension is not exhaustive.
extension Notification.Name {
public static let VTOwnConferenceCreatedEvent = Notification.Name("OwnConferenceCreatedEvent")
public static let VTConferenceStatusUpdated = Notification.Name("ConferenceStatusUpdated")
public static let VTConferenceDestroyedPush = Notification.Name("ConferenceDestroyedPush")
public static let VTConferenceMessageReceived = Notification.Name("ConferenceMessageReceived")
public static let VTOwnUserInvitedEvent = Notification.Name("OwnUserInvitedEvent")
public static let VTInvitationReceivedEvent = Notification.Name("InvitationReceivedEvent")
public static let VTOfferCreated = Notification.Name("OfferCreated")
public static let VTParticipantAdded = Notification.Name("ParticipantAdded")
public static let VTParticipantUpdated = Notification.Name("ParticipantUpdated")
public static let VTParticipantSwitched = Notification.Name("ParticipantSwitched")
public static let VTCallKitStarted = Notification.Name("VTCallKitStarted")
public static let VTCallKitUpdated = Notification.Name("VTCallKitUpdated")
public static let VTCallKitEnded = Notification.Name("VTCallKitEnded")
}
VTAudioSound helps you to play a 3D audio sound into your application.
The sound must be encoded in mono to be played spatialized.
var sound: VTAudioSound!
// Initializes VTAudioSound.
sound = try? VTAudioSound(forResource: "myFile", ofType: "mp3")
sound = try? VTAudioSound(fileURL: path)
try? sound.play() {
// Debug.
print("The sound has finished being played.")
}
sound.stop()
sound.loop = true
// The range of valid values are from 0.0 to 1.0.
sound.volume = 1
let volume = sound.volume
// The range of valid values are from -1.0 to 1.0.
sound.angle = 0
let angle = sound.angle
// The range of valid values are from 0.0 to 1.0.
sound.distance = 0
let distance = sound.distance
The SDK is built to compile with the simulator AND generic iOS device. Since november 2016, Apple has stopped supporting "Fat libraries" (simulator and device compiled together) like this one. So when you want to publish your app, you will need to execute a script that delete the simulator support:
APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
# This script loops through the frameworks embedded in the application and
# removes unused architectures.
find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK
do
FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)
FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"
echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"
EXTRACTED_ARCHS=()
for ARCH in $ARCHS
do
echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME"
lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"
EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")
done
echo "Merging extracted architectures: ${ARCHS}"
lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"
rm "${EXTRACTED_ARCHS[@]}"
echo "Replacing original executable with thinned version"
rm "$FRAMEWORK_EXECUTABLE_PATH"
mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"
done
You can add this script in the Build Phases
of your project.
Here is a tutorial if you want more details: http://ikennd.ac/blog/2015/02/stripping-unwanted-architectures-from-dynamic-libraries-in-xcode/
1.0.9
The Voxeet iOS SDK uses a number of open source projects to work properly:
- Starscream - Starscream is a conforming WebSocket (RFC 6455) client library in Swift for iOS and OSX.
- Alamofire - Alamofire is an HTTP networking library written in Swift.
- SwiftyJSON - SwiftyJSON makes it easy to deal with JSON data in Swift.
- CryptoSwift - Crypto related functions and helpers for Swift implemented in Swift.
© Voxeet, 2017