Developer Documentation
4Players Releases ODIN Swift Package
Back to Blog

4Players Releases ODIN Swift Package

Written by Sven Paulsen
04 Sep 2022

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.

Info

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
))
Warning

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:

Show SwiftUI Sample

Documentation

We have extensive documentation and a reference of all available classes and APIs provided by OdinKit.

Open OdinKit Documentation