Now that our team is on a release spree, here you have OdinKit - a Swift package providing an object-oriented wrapper for the ODIN native client library, which enables developers to integrate real-time VoIP chat technology and real-time data synchronization into multiplayer games and apps on macOS and iOS.
With OdinKit, we’re not only following Apples naming convention for APIs. We’re also shipping an XCFramework, which is a distributable binary package created by Xcode that contains variants of the ODIN client library so that it can be used on multiple platforms. Currently, we provide support for both Intel and Apple Silicon based Macs as well as iOS devices.
The package is open-source and available on GitHub.
Swift Playground
The project contains a macOS playground to demonstrate how to use OdinKit in your your apps, but the same code will also work on iOS and iPadOS.
Getting Started
After adding the package to your project, import OdinKit
into your application:
import OdinKit
In ODIN, every user connected to the same room can exchange voice and data. You can also join multiple rooms at once, for example one lobby but additional private chats or another room only used for text messaging. So, we create a room instance like this:
let room = OdinRoom()
Handling events
ODIN will notify you of important events like a new user joining the room or a user leaving the room. You can handle these events to build your business logic on top of it. In Swift, we use the delegation pattern to handle these events.
So, we create a class implementing the OdinRoomDelegate
protocol:
// Define a class handing events
class YourCustomDelegate: OdinRoomDelegate {
// Callback for internal room connectivity state changes
func onRoomConnectionStateChanged(room: OdinRoom, oldState: OdinRoomConnectionState, newState: OdinRoomConnectionState, reason: OdinRoomConnectionStateChangeReason) {
print("Connection status changed from \(oldState.rawValue) to \(newState.rawValue)")
}
// Callback for when a room was joined and the initial state is fully available
func onRoomJoined(room: OdinRoom) {
print("Room joined successfully as peer \(room.ownPeer.id)")
}
// Callback for room user data changes
func onRoomUserDataChanged(room: OdinRoom) {
print("Global room user data changed to: \(room.userData)")
}
// Callback for peers joining the room
func onPeerJoined(room: OdinRoom, peer: OdinPeer) {
print("Peer \(peer.id) joined the room with ID '\(peer.userId)'")
}
// Callback for peer user data changes
func onPeerUserDataChanged(room: OdinRoom, peer: OdinPeer) {
print("Peer \(peer.id) updated its user data to: \(peer.userData)")
}
// Callback for peers leaving the room
func onPeerLeft(room: OdinRoom, peer: OdinPeer) {
print("Peer \(peer.id) left the room")
}
// Callback for medias being added to the room
func onMediaAdded(room: OdinRoom, peer: OdinPeer, media: OdinMedia) {
print("Peer \(peer.id) added media \(media.id) to the room")
}
// Callback for media activity state changes
func onMediaActiveStateChanged(room: OdinRoom, peer: OdinPeer, media: OdinMedia) {
print("Peer \(peer.id) \(media.activityStatus ? "started" : "stopped") talking on media \(media.id)")
}
// Callback for medias being removed from the room
func onMediaRemoved(room: OdinRoom, peer: OdinPeer, media: OdinMedia) {
print("Peer \(peer.id) removed media \(media.id) from the room")
}
// Callback for incoming arbitrary data messages
func onMessageReceived(room: OdinRoom, senderId: UInt64, data: [UInt8]) {
print("Peer \(senderId) sent a message with arbitrary data: \(data)")
}
}
Next, we assign that delegate to our room instance:
// Create an instance of your delegate
let delegate = YourCustomDelegate()
// Add the delegate to the room
room.delegate = delegate
We also provide a set of published properties for typical use cases so you don’t have to handle these events yourself. This is ideal if you’re using SwiftUI, as your views will notice any changes automatically.
struct RootView: View {
@EnvironmentObject var room: OdinRoom
var body: some View {
switch room.connectionStatus.state {
case OdinRoomConnectionState_Connected:
RoomView()
default:
LoginView()
}
}
}
Joining a Room
That’s it for event handling. We are now ready to join a room. For this, we need to an authentication token. ODIN generates signed JSON Web Tokens (JWT) for secure authentication, which contain the room you want to join as well as a freely definable identifier for the user (e.g. a player ID or a user ID from your database).
// Create a new local access key
let accessKey = try OdinAccessKey("__YOUR_ACCESS_KEY__")
// Generate a token to authenticate with
let authToken = try accessKey.generateToken(roomId: "Meeting Room", userId: "Swift is great!")
The roomId
is just a string and uniquely identifies the room within your access key domain. Every user connected
to the same room will be able to communicate with each other. The userId
is a string that can be used to refer to
an existing record in your particular service.
Privacy and security is very important to us. All the data transferred over our servers is fully encrypted. Also, ODIN is completely user agnostic and we don’t harvest any data or tell our customers how things should work or look.
For your own security, we strongly recommend that you NEVER put an access key in your client-side code. We’ve created a very basic Node.js server, to showcase how to issue ODIN tokens to your client apps without exposing your access key.
We provide both PHP and NPM packages to make the process of generating tokens as easy as possible for you.
Generate an Access Key for FREE
Now that we have created our access token (which you should do on server side if possible) we can join the room and add a media stream using your default audio input device:
// Now that we have a token, join the room
try room.join(token: authToken)
// Append a local audio stream to capture our microphone
let newMediaId = try room.addMedia(audioConfig: OdinAudioStreamConfig(
sample_rate: 48000,
channel_count: 1
))
As OdinKit will request access to your microphone you need to set a special key in your Info.plist
file. Add
a key called NSMicrophoneUsageDescription
(Privacy - Microphone usage description) and set a text that describes
why your app wants to have microphone access. Otherwise your app won’t be able to capture audio samples from your
input device.
That’s it! Congratulations! You have added real-time voice communication to your app!
What’s Next
There are many things you can do with ODIN. You could add sending text chat so that users can share links and additional data:
// Encode a string so we can send it as a message
let yourMessage = OdinCustomData.encode("So Long, and Thanks for All the Fish")
// Send the message everyone else in the room
try room.sendMessage(data: yourMessage)
Sample App
Download our sample to get started quickly and learn how to use the APIs:
Documentation
We have extensive documentation and a reference of all available classes and APIs provided by OdinKit.