{"id":23134912,"url":"https://github.com/4players/odin-wrapper-swift","last_synced_at":"2026-04-27T22:32:00.485Z","repository":{"id":60147676,"uuid":"508243787","full_name":"4Players/odin-wrapper-swift","owner":"4Players","description":"Swift package providing an object-oriented wrapper for 4Players ODIN","archived":false,"fork":false,"pushed_at":"2024-02-22T00:53:34.000Z","size":73,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-04T08:24:04.346Z","etag":null,"topics":["avaudioengine","avaudiosession","cross-platform","http3","ios","macos","odin","package","quic","spatial-audio","swift","swiftpackage","voip","wrapper","xcode"],"latest_commit_sha":null,"homepage":"https://www.4players.io","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/4Players.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2022-06-28T09:56:32.000Z","updated_at":"2022-09-03T22:54:53.000Z","dependencies_parsed_at":"2024-02-22T01:49:18.012Z","dependency_job_id":null,"html_url":"https://github.com/4Players/odin-wrapper-swift","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/4Players/odin-wrapper-swift","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4Players%2Fodin-wrapper-swift","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4Players%2Fodin-wrapper-swift/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4Players%2Fodin-wrapper-swift/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4Players%2Fodin-wrapper-swift/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/4Players","download_url":"https://codeload.github.com/4Players/odin-wrapper-swift/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4Players%2Fodin-wrapper-swift/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32358509,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-27T20:07:02.737Z","status":"ssl_error","status_checked_at":"2026-04-27T20:07:00.910Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["avaudioengine","avaudiosession","cross-platform","http3","ios","macos","odin","package","quic","spatial-audio","swift","swiftpackage","voip","wrapper","xcode"],"created_at":"2024-12-17T12:13:39.398Z","updated_at":"2026-04-27T22:32:00.479Z","avatar_url":"https://github.com/4Players.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"![OdinKit](https://docs.4players.io/img/odin/banner.jpg)\n\n[![Releases](https://img.shields.io/github/release/4Players/odin-wrapper-swift)](https://github.com/4Players/odin-wrapper-swift/releases)\n[![Platforms](https://img.shields.io/badge/platforms-macOS%20iOS-lightgrey)](#requirements)\n[![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/4Players/odin-wrapper-swift/blob/master/LICENSE)\n[![Documentation](https://img.shields.io/badge/docs-4Players.io-orange)](https://www.4players.io/developers/)\n[![Twitter](https://img.shields.io/badge/twitter-@4PlayersBiz-blue)](https://twitter.com/4PlayersBiz)\n\n# OdinKit\n\nOdinKit is a Swift package providing an object-oriented wrapper for the [ODIN](https://github.com/4Players/odin-sdk) native client library, which enables developers to integrate [ODIN Voice](https://docs.4players.io/voice/), our real-time VoIP chat technology into multiplayer games and apps on macOS and iOS.\n\n- [Requirements](#requirements)\n  - [XCFramework](#xcframework)\n- [Usage](#usage)\n  - [Quick Start](#quick-start)\n  - [Playground](#playground)\n  - [Class Overview](#class-overview)\n  - [Event Handling](#event-handling)\n  - [Audio Processing](#audio-processing)\n  - [User Data](#user-data)\n  - [Messages](#messages)\n- [Resources](#resources)\n- [License](#license)\n- [Troubleshooting](#troubleshooting)\n\n## Requirements\n\n- iOS 9.0+ / macOS 10.15+\n- Xcode 10.2+\n- Swift 5.0+\n\n### XCFramework\n\nTo use OdinKit, you'll need the `Odin.xcframework` bundle in the `Frameworks` directory. We'll provide builds with matching XCFrameworks for each version on the [GitHub Releases](https://github.com/4Players/odin-wrapper-swift/releases) page.\n\nAn XCFramework is a distributable binary package created by Xcode, which contains variants of a framework or library so that it can be used on multiple platforms. In case of ODIN, the XCFramework contains relevant C header files and a set of static libraries for the following platforms:\n\n| Platform | x86_64             | aarch64            |\n| -------- | ------------------ | ------------------ |\n| macOS    | :white_check_mark: | :white_check_mark: |\n| iOS      | :white_check_mark: | :white_check_mark: |\n\nTo manually add the correct XCFramework version, please refer to `Sources/OdinKit.swift` for the required version number and download the `odin-xcframework.tgz` file from the appropriate release of the [ODIN Core SDK](https://github.com/4Players/odin-sdk/releases).\n\nWe're also providing a simple Python script called [Setup.py](https://github.com/4Players/odin-wrapper-swift/blob/master/Setup.py), which will download the required XCFramework version and extract it to the correct location.\n\n```bash\npython3 ./Setup.py\n```\n\n## Usage\n\n### Quick Start\n\nThe following code snippet will create a token for authentication, join a room called _\"Meeting Room\"_ and add a media stream using your default audio input device:\n\n```swift\nimport OdinKit\n\nlet room = OdinRoom()\n\ndo {\n    let accessKey = try OdinAccessKey(\"\u003cYOUR_ACCESS_KEY\u003e\")\n    let authToken = try accessKey.generateToken(roomId: \"Meeting Room\")\n\n    try room.join(token: authToken)\n    try room.addMedia(type: OdinMediaStreamType_Audio)\n} catch {\n    print(\"Something went wrong, \\(error)\")\n}\n```\n\n### Playground\n\nThis project contains a macOS [playground](https://github.com/4Players/odin-wrapper-swift/blob/master/Playgrounds/macOS.playground/Contents.swift) to demonstrate how to use OdinKit in your your apps, but the same code will also work on iOS and iPadOS.\n\n### Class Overview\n\nOdinKit provides a set of classes to provide easy access to just everything you need including low-level access to C-API functions of the [ODIN Core SDK](https://github.com/4Players/odin-sdk/blob/master/include/odin.h).\n\n#### OdinAccessKey\n\nAn access key is the unique authentication key to be used to generate room tokens for accessing the ODIN server network. You should think of it as your individual username and password combination all wrapped up into a single non-comprehendible string of characters, and treat it with the same respect. 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 [here](https://docs.4players.io/voice/token-server/), to showcase how to issue ODIN tokens to your client apps without exposing your access key.\n\n**Note:** Using the `OdinAccessKey` default initializer will always create a new access key.\n\n```swift\n// Create a new access key\nlet accessKey = OdinAccessKey()\n\n// Print information about the access key\nprint(\"Public Key: \\(accessKey.publicKey)\")\nprint(\"Secret Key: \\(accessKey.secretKey)\")\nprint(\"Key ID:     \\(accessKey.id)\")\n```\n\n#### OdinToken\n\nODIN generates signed JSON Web Tokens (JWT) for secure authentication, which contain the room(s) you want to join as well as a freely definable identifier for the user. The later can be used to refer to an existing record in your particular service.\n\nAs ODIN is fully user agnostic, [4Players GmbH](https://www.4players.io) does not store any of this information on its servers.\n\n```swift\n// Generate a token to authenticate with\nlet authToken = try accessKey.generateToken(roomId: \"foo\", userId: \"bar\")\n```\n\n#### OdinRoom\n\nIn ODIN, users who want to communicate with each other need to join the same room. Optionally, you can specify an alternative gateway URL when initializing an `OdinRoom` instance.\n\nYou can choose between a managed cloud and a self-hosted solution. Let [4Players GmbH](https://www.4players.io/company/about_us/) deal with the setup, administration and bandwidth costs or run our server software on your own infrastructure allowing you complete control and customization of your deployment environment. Unless you're hosting your own servers, you don't need to set a gateway URL, which will make the ODIN client use the default gateway running in the European Union.\n\n```swift\n// Create a new room instance\nlet room = OdinRoom(gateway: \"https://gateway.odin.4players.io\")\n\n// Join the room\nlet ownPeerId = try room.join(token: authToken)\n\n// Print information about the room\nprint(\"ID:        \\(room.id)\")\nprint(\"User Data: \\(room.userData)\")\n```\n\n#### OdinPeer\n\nOnce a client joins a room, it will be treated as a peer. Every peer has its own user data, which is a byte array (`[UInt8]`). This data is synced automatically, which allows storing of arbitrary information for each individual peer and even globally for the room if needed.\n\nPeers can update their own user data at any time, even before joining a room to specify the initial user data value.\n\n```swift\n// Print information for all peers in the room\nfor (peerId, peer) in room.peers {\n    print(\"ID:        \\(peer.id)\")\n    print(\"User ID:   \\(peer.userId)\")\n    print(\"User Data: \\(peer.userData)\")\n    print(\"Is Self:   \\(peer == room.ownPeer)\")\n}\n```\n\n#### OdinMedia\n\nEach peer in an ODIN room can attach media streams to transmit voice data. By default, ODIN will always assume that your input device is working with a sample rate of 48 kHz. If you need to change these settings, you can either specify a custom `OdinAudioStreamConfig` or attach the `OdinMedia` instances of your room to an existing `AVAudioEngine` instance of your app.\n\n```swift\n// Append a local audio stream to capture our microphone\nlet newMediaId = try room.addMedia(audioConfig: OdinAudioStreamConfig(\n    sample_rate: 48000,\n    channel_count: 1\n))\n```\n\n### Event Handling\n\nThe ODIN API is event driven. Using the OdinKit package, you have two ways of handing events emitted in an ODIN room:\n\n#### a) Setting a Room Delegate\n\nEvery `OdinRoom` instance allows setting an optional delegate to handle events. The delegate must be an instance of a class implementing the `OdinRoomDelegate` protocol, which defines all the necessary event callbacks.\n\n```swift\n// Define a class handing events\nclass YourCustomDelegate: OdinRoomDelegate {\n    // Callback for internal room connectivity state changes\n    func onRoomConnectionStateChanged(room: OdinRoom, oldState: OdinRoomConnectionState, newState: OdinRoomConnectionState, reason: OdinRoomConnectionStateChangeReason) {\n        print(\"Connection status changed from \\(oldState.rawValue) to \\(newState.rawValue)\")\n    }\n\n    // Callback for when a room was joined and the initial state is fully available\n    func onRoomJoined(room: OdinRoom) {\n        print(\"Room joined successfully as peer \\(room.ownPeer.id)\")\n    }\n\n    // Callback for room user data changes\n    func onRoomUserDataChanged(room: OdinRoom) {\n        print(\"Global room user data changed to: \\(room.userData)\")\n    }\n\n    // Callback for peers joining the room\n    func onPeerJoined(room: OdinRoom, peer: OdinPeer) {\n        print(\"Peer \\(peer.id) joined the room with ID '\\(peer.userId)'\")\n    }\n\n    // Callback for peer user data changes\n    func onPeerUserDataChanged(room: OdinRoom, peer: OdinPeer) {\n        print(\"Peer \\(peer.id) updated its user data to: \\(peer.userData)\")\n    }\n\n    // Callback for peers leaving the room\n    func onPeerLeft(room: OdinRoom, peer: OdinPeer) {\n        print(\"Peer \\(peer.id) left the room\")\n    }\n\n    // Callback for medias being added to the room\n    func onMediaAdded(room: OdinRoom, peer: OdinPeer, media: OdinMedia) {\n        print(\"Peer \\(peer.id) added media \\(media.id) to the room\")\n    }\n\n    // Callback for media activity state changes\n    func onMediaActiveStateChanged(room: OdinRoom, peer: OdinPeer, media: OdinMedia) {\n        print(\"Peer \\(peer.id) \\(media.activityStatus ? \"started\" : \"stopped\") talking on media \\(media.id)\")\n    }\n\n    // Callback for medias being removed from the room\n    func onMediaRemoved(room: OdinRoom, peer: OdinPeer, media: OdinMedia) {\n        print(\"Peer \\(peer.id) removed media \\(media.id) from the room\")\n    }\n\n    // Callback for incoming arbitrary data messages\n    func onMessageReceived(room: OdinRoom, senderId: UInt64, data: [UInt8]) {\n        print(\"Peer \\(senderId) sent a message with arbitrary data: \\(data)\")\n    }\n}\n\n// Create an instance of your delegate\nlet delegate = YourCustomDelegate()\n\n// Add the delegate to the room\nroom.delegate = delegate\n```\n\n#### b) Using Published Properties\n\nEvery `OdinRoom` instance provides a set of observable properties using the `@Published` property wrapper. This allows you to easily monitor these variables as signals are emitted whenever their values were changed.\n\nThere are four distinct properties you can observe:\n\n- `OdinRoom.connectionStatus` \\\nThis is a tuple representing current connection status of the room including a reason identifier for the last update.\n- `OdinRoom.userData` \\\nThis is a byte array (`[UInt8]`), which can be used to attach arbitrary data to the room. This data is synced automatically.\n- `OdinRoom.peers` \\\nThis is a dictionary containing all peers in the room, indexed by their ID. Each peer has its own `userData` property, which is also observable and stores a byte array with arbitrary data assigned by the user.\n- `OdinRoom.medias` \\\nThis is a dictionary containing all local and remote media streams in the room, indexed by their stream handle. Each media has an observable property called `activityStatus`, which indicates wether or not the media stream is sending or receiving data.\n\n```swift\n// Monitor the room connection status\nroom.$connectionStatus.sink {\n    print(\"New Connection Status: \\($0.state.rawValue)\")\n}\n\n// Monitor the room user data\nroom.$userData.sink {\n    print(\"New User Data: \\($0)\")\n}\n\n// Monitor the list of peers in the room\nroom.$peers.sink {\n    print(\"New Peers: \\($0.keys)\")\n}\n\n// Monitor the list of media streams in the room\nroom.$medias.sink {\n    print(\"New Medias: \\($0.keys)\")\n}\n```\n\n### Audio Processing\n\nEach ODIN room handle has its own audio processing module (APM), which is in charge of filters like echo cancellation, noise suppression, advanced voice activity detection and more. These settings can be changed on-the-fly by passing an OdinApmConfig to the rooms updateAudioConfig.\n\n```swift\n// Create a new APM settings struct\nlet audioConfig: OdinApmConfig = .init(\n    voice_activity_detection: true,\n    voice_activity_detection_attack_probability: 0.9,\n    voice_activity_detection_release_probability: 0.8,\n    volume_gate: true,\n    volume_gate_attack_loudness: -30,\n    volume_gate_release_loudness: -40,\n    echo_canceller: true,\n    high_pass_filter: true,\n    pre_amplifier: true,\n    noise_suppression_level: OdinNoiseSuppressionLevel_Moderate,\n    transient_suppressor: true,\n    gain_controller: true\n)\n\n// Update the APM settings of the room\ntry room.updateAudioConfig(audioConfig)\n```\n\nThe ODIN APM provides the following features:\n\n#### Voice Activity Detection (VAD)\n\nWhen enabled, ODIN will analyze the audio input signal using smart voice detection algorithm to determine the presence of speech. You can define both the probability required to start and stop transmitting.\n\n#### Input Volume Gate\n\nWhen enabled, the volume gate will measure the volume of the input audio signal, thus deciding when a user is speaking loud enough to transmit voice data. You can define both the root mean square power (dBFS) for when the gate should engage and disengage.\n\n#### Acoustic Echo Cancellation (AEC)\n\nWhen enabled the echo canceller will try to subtract echoes, reverberation, and unwanted added sounds from the audio input signal. Note, that you need to process the reverse audio stream, also known as the loopback data to be used in the ODIN echo canceller.\n\n#### Noise Suppression\n\nWhen enbabled, the noise suppressor will remove distracting background noise from the input audio signal. You can control the aggressiveness of the suppression. Increasing the level will reduce the noise level at the expense of a higher speech distortion.\n\n#### High-Pass Filter (HPF)\n\nWhen enabled, the high-pass filter will remove low-frequency content from the input audio signal, thus making it sound cleaner and more focused.\n\n#### Preamplifier\n\nWhen enabled, the preamplifier will boost the signal of sensitive microphones by taking really weak audio signals and making them louder.\n\n#### Transient Suppression\n\nWhen enabled, the transient suppressor will try to detect and attenuate keyboard clicks.\n\n#### Automatic Gain Control (AGC)\n\nWhen enabled, the gain controller will bring the input audio signal to an appropriate range when it's either too loud or too quiet.\n\n### User Data\n\nEvery peer has its own user data, which is a byte array (`[UInt8]`). This data is synced automatically, which allows storing of arbitrary information for each individual peer and even globally for the room if needed. Peers can update their own user data at any time, even before joining a room to specify the initial user data value. For convenience, we're providing a set of helper functions in `OdinCustomData` to handle user data conversion:\n\n#### a) Using a String\n\nUse `encode` and `decode` to convert from `String` to `[UInt8]` and vice versa.\n\n```swift\n// Define a string we want to set as our peer user data\nlet yourString = \"Hello World!\"\n\n// Convert the string to a byte array\nlet stringData = OdinCustomData.encode(yourString)\n\n// Set the user data\ntry room.updatePeerUserData(userData: stringData)\n```\n\n#### b) Using a Custom Type\n\nUse `encode` and `decode` to convert from types implementing the `Codable` protocol to `[UInt8]` and vice versa.\n\n```swift\n// Define a codable type\nstruct YourCustomData: Codable {\n    var name: String\n}\n\n// Initialize the new type\nlet yourCodable = YourCustomData(name: \"John Doe\")\n\n// Convert the type to a byte array\nlet codableData = OdinCustomData.encode(yourCodable)\n\n// Set the user data\ntry room.updatePeerUserData(userData: codableData)\n```\n\n### Messages\n\nODIN allows you to send arbitrary to every other peer in the room or even individual targets. Just like user data, a message is a byte array (`[UInt8]`), which means that you can use the same convenience functions in `OdinCustomData` to make your life easier.\n\nTo send a message to a list of individual peers, simply specify a lif of peer IDs for the `targetIds` argument. We can even send messages to ourselves by explicitly adding our own peer ID to the list.\n\n**Note:** Messages are always sent to all targets in the room, even when they moved out of proximity using setPosition.\n\n```swift\n// Encode a string so we can send it as a message\nlet yourMessage = OdinCustomData.encode(\"So Long, and Thanks for All the Fish\")\n\n// Send the message everyone else in the room\ntry room.sendMessage(data: yourMessage)\n```\n\n## Resources\n\n- [Documentation](https://docs.4players.io/voice/)\n- [Examples](https://docs.4players.io/voice/swift/samples/)\n- [Frequently Asked Questions](https://docs.4players.io/voice/faq/)\n- [Pricing](https://odin.4players.io/pricing/)\n\n## License\n\nOdinKit is released under the MIT license. See [LICENSE](https://github.com/4Players/odin-wrapper-swift/blob/master/LICENSE) for details.\n\n## Troubleshooting\n\nContact us through the listed methods below to receive answers to your questions and learn more about ODIN.\n\n### Discord\n\nJoin our official Discord server to chat with us directly and become a part of the 4Players ODIN community.\n\n[![Join us on Discord](https://docs.4players.io/img/join_discord.png)](https://4np.de/discord)\n\n### Twitter\n\nHave a quick question? Tweet us at [@ODIN4Players](https://twitter.com/ODIN4Players) and we’ll help you resolve any issues.\n\n### Email\n\nDon’t use Discord or Twitter? Send us an [email](mailto:odin@4players.io) and we’ll get back to you as soon as possible.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F4players%2Fodin-wrapper-swift","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F4players%2Fodin-wrapper-swift","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F4players%2Fodin-wrapper-swift/lists"}