Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
74865d0
Implement DAVE/MLS on the gateway
alula Dec 31, 2025
44c11e6
Version from 3.0
alula Dec 31, 2025
3a065ff
Add a netty leak detector to the test bot
alula Dec 31, 2025
965b166
Refactor codec registry
alula Feb 16, 2026
eec5e4e
Fix DAVEManager compilation error
alula Feb 17, 2026
0661210
Fix the UDP queue test bot
alula Feb 17, 2026
1440bbb
Refactor the poller and provider API
alula Feb 17, 2026
eb1c4c6
Remove deprecated KoeOptions constructors
alula Feb 18, 2026
e01eb1a
Isolate the WIP stuff in .experimental package and add separate const…
alula Feb 18, 2026
ec5e448
Remove deprecated VIDEO_SINK_WANTS alias
alula Feb 18, 2026
e12ed71
Rename listeners, move gateway impl to internal
alula Feb 21, 2026
f0968de
Internalize crypto API + actually make DAVE work
alula Feb 21, 2026
45fea56
add github actions to publish koe (#42)
topi314 Feb 22, 2026
3a5613e
remove duplicate publication
topi314 Feb 22, 2026
81c80bd
remove explicit sources & javadoc jar
topi314 Feb 22, 2026
a82691e
Fix passthrough mode
alula Feb 23, 2026
d45bccd
Update libdave-jvm
alula Feb 23, 2026
b75d1e9
Screw you, git
alula Feb 23, 2026
05c80a6
Implement -dirty + use 9 char revs
alula Feb 23, 2026
220bb70
add application plugin to testbot
topi314 Feb 23, 2026
3f101a4
mix migration docs DEFAULT_BUFFER_DURATION
topi314 Feb 23, 2026
281670e
Update libdave
alula Feb 24, 2026
7d011c2
Update YouTube source
alula Feb 24, 2026
fc6a0ca
Add all natives to testbot + update Gradle
alula Feb 24, 2026
15044b4
Update Netty
alula Feb 24, 2026
0eb2b04
Ignore /build/
alula Feb 24, 2026
5774727
Fix Netty 4.2 SSL verification problem
alula Feb 24, 2026
84c8b45
Update libdave-jvm to 5f254c1fd
alula Feb 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Publish
on:
push:
branches: [ '**' ]
paths-ignore:
- '**.md'
release:
types: [ published ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: 17
cache: gradle

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5

- name: Build and Publish
run: |
./gradlew build publish \
--no-daemon \
-PMAVEN_USERNAME=${{ secrets.MAVEN_USERNAME }} \
-PMAVEN_PASSWORD=${{ secrets.MAVEN_PASSWORD }} \
-PMAVEN_CENTRAL_USERNAME=${{ secrets.MAVEN_CENTRAL_USERNAME }} \
-PMAVEN_CENTRAL_PASSWORD=${{ secrets.MAVEN_CENTRAL_PASSWORD }}

- name: Upload core Artifact
uses: actions/upload-artifact@v6
with:
name: core
path: core/build/libs/core-*.jar

- name: Upload ext-udpqueue Artifact
uses: actions/upload-artifact@v6
with:
name: ext-udpqueue
path: ext-udpqueue/build/libs/ext-udpqueue-*.jar
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ buildNumber.properties

*/build/
*/out/
/build/

# CMake
cmake-build-*/
Expand Down Expand Up @@ -85,3 +86,5 @@ fabric.properties
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser

# JVM crashes
hs_err_pid*.log
148 changes: 148 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Migrating from Koe 2.x to 3.0

## VoiceServerInfo

1. The public constructor has been removed. Use `VoiceServerInfo#builder()` to create instances of this class instead.
2. As DAVE E2E encryption is now mandatory, it's required to pass `channelId` (the ID of the voice channel), because it's needed as the MLS group identifier.

## Minor changes and removals

- Old `VIDEO_SINK_WANTS` alias for `MEDIA_SINK_WANTS` has been removed.
- Compatibility constructors from `KoeOptions` have been removed. Use `KoeOptions#builder()` instead.

## Experimental package

The package `moe.kyokobot.koe.experimental` is reserved for APIs that are not yet stable. Types and members in this package may change or be removed
in any **minor** release (e.g. 3.1, 3.2). Do not depend on them if you need binary compatibility across minor versions. The rest of the public API
follows the [binary compatibility policy](README.md#binary-compatibility) described in the README.

The unfinished video support has been moved to the experimental package in intent to be finished in future releases.

## KoeEventListener

1. The old `userConnected` method has been renamed to `userStreamsChanged` to reflect the fact that it's not fired when a user joins the voice channel, but when their stream configuration changes (e.g., starts/stops video).
2. A new `usersConnected` method has been added if you actually need to know when an user has joined the voice channel.

## Refactoring of the poller / provider API

Frame poller classes moved from `moe.kyokobot.koe.codec` to `moe.kyokobot.koe.poller`.
Transport implementations live under subpackages such as `moe.kyokobot.koe.poller.netty`
and `moe.kyokobot.koe.poller.udpqueue`.

### Class Changes

- `OpusFramePoller` has been replaced by `AbstractOpusFramePoller`.
- Netty and udpqueue opus pollers are now transport adapters on top of `AbstractOpusFramePoller`.
- `FramePollerFactory` moved to `moe.kyokobot.koe.poller.FramePollerFactory`.

### Provider API and Hot-Swap Behavior

- Opus pollers resolve providers from `MediaConnection` on each poll iteration.
- Replacing providers via `setAudioSender(...)` / `setVideoSender(...)` does not require poller recreation.
- `OpusAudioFrameProvider` is no longer required to manage silence/speaking transitions for opus transport behavior.

### MediaFrameProvider → AudioFrameProvider

`MediaFrameProvider` has been replaced by `AudioFrameProvider`. The interface is now focused on audio; codec selection and timestamp handling are handled by the poller.

**Old (2.x):**
```java
public interface MediaFrameProvider {
boolean canSendFrame(Codec codec);
boolean retrieve(Codec codec, ByteBuf buf, IntReference timestamp);
}
```

**New (3.0):**
```java
public interface AudioFrameProvider {
void onCodecChanged(CodecInstance codec);
void dispose();
boolean canProvide();
boolean provideFrame(ByteBuf buf);
}
```

- `setAudioSender` / `setVideoSender` and the corresponding getters now use `AudioFrameProvider` (same type for both in 3.0).
- Implementations must provide frames when `canProvide()` is true by writing to the buffer in `provideFrame(ByteBuf)` and returning whether a frame was written. Silence and speaking state are handled by the opus poller.

### Codec-aware providers: `onCodecChanged(CodecInstance)`

Providers are notified of the current codec so they can reject unsupported formats without racy startup behavior.

**Guarantees:**

- `onCodecChanged(CodecInstance codec)` is called **before** the first `canProvide()` for a provider attached via `setAudioSender` / `setVideoSender`.
- It is called again whenever the codec is changed via `setAudioCodec` / `setVideoCodec` while that provider is attached.

**Expected behavior:**

- In `onCodecChanged`, update any internal codec-dependent state (e.g. store the current `CodecInstance`).
- If the provider cannot supply data for the current codec, return `false` from `canProvide()` until the codec changes to a supported one. The poller will not call `provideFrame` when `canProvide()` is false.

In practice the audio codec is always Opus, so implementing codec checks is not strictly required for audio-only bots. This API is for completeness and for future or experimental video/other codec support (see [Experimental package](#experimental-package)).

**Example (Opus-only provider):**
```java
private boolean isOpus;

@Override
public void onCodecChanged(CodecInstance codec) {
this.isOpus = OpusCodecInfo.isInstanceOf(codec);
}

@Override
public boolean canProvide() {
if (!isOpus) return false;
return /* ... has opus frame ... */;
}
```

### ext-udpqueue Updates

- `UdpQueueFramePollerFactory` moved to `moe.kyokobot.koe.poller.udpqueue`.
- `UdpQueueOpusFramePoller` now follows the shared opus poller hierarchy and uses `CodecInstance`.
- `UdpQueueFramePollerFactory` no longer creates its own `QueueManagerPool`.
You must create and manage `QueueManagerPool` yourself and pass it to the factory.
This is a breaking change.

**Old (2.x):**
```java
.setFramePollerFactory(new UdpQueueFramePollerFactory())
```

**New (3.x):**
```java
var queuePool = new QueueManagerPool(
Runtime.getRuntime().availableProcessors(),
QueueManagerPool.DEFAULT_BUFFER_DURATION
);

.setFramePollerFactory(new UdpQueueFramePollerFactory(queuePool));

// On shutdown:
queuePool.close();
```

## Codec API Changes

### Overview

The old `Codec` class has been replaced with two new classes:
- **`CodecInfo`**: Immutable codec capabilities (what a codec CAN do)
- **`CodecInstance`**: Session-specific codec instance with negotiated payload types (what a codec IS doing)

### Codec Class Names

All codec classes have been renamed with an `Info` suffix:

| Old (2.x) | New (3.0) |
|----------------------|--------------------------|
| `OpusCodec.INSTANCE` | `OpusCodecInfo.INSTANCE` |
| `H264Codec.INSTANCE` | `H264CodecInfo.INSTANCE` |
| `VP8Codec.INSTANCE` | `VP8CodecInfo.INSTANCE` |
| `VP9Codec.INSTANCE` | `VP9CodecInfo.INSTANCE` |

## Gateway

The gateway implementation has been moved to the `internal` package until refactoring for an usable public API is complete.
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ Tiny, minimal dependency and embeddable library implementing Discord media serve

[Get it on JitPack](https://jitpack.io/#moe.kyokobot.koe/core)

### Versioning and stability policy

Koe follows [semantic versioning](https://semver.org/). API/ABI stability is defined as follows:

- 🟢 **Public API** (all packages except `.experimental` and `.internal`) is guaranteed to be stable and is only extended when needed. Minor and patch releases preserve binary compatibility for the public API.
- 🚧 **Experimental API** (`moe.kyokobot.koe.experimental`) - APIs in this package are subject to change or removal in any **minor** release (e.g. 3.1, 3.2). Use only if you can tolerate breaking changes between minor versions.
- 🔒 **Internal API** (`moe.kyokobot.koe.internal`) - For internal use only. Not part of the public API; may change or break in any release, including **patch** releases.

Example:

```groovy
Expand All @@ -20,29 +28,30 @@ dependencies {

`VERSION` can be either a tag or a git commit hash.

#### Dependencies
### Dependencies
- Netty
- slf4j
- Java 11+ (could be backported to Java 8 with minor code changes)
- Java 11+

#### Features
### Features

- Supports voice gateway v4, v5 and v8.
- Easily extendable for stuff such as support for codecs other than Opus or video sending, if Discord ever decides to support it on bots.
- Experimental video support.
- Basic RTCP support for measuring packet loss and other stuff.

#### Non-goals / won't do
### Non-goals / won't do

- Encoding - Koe only implements voice server communication, not voice handling itself, so it only accepts Opus frames and you have set up an encoder yourself, use [lavaplayer](https://github.com/sedmelluq/lavaplayer), libav/ffmpeg or anything else.
- Voice receiving support - [it's not supported by Discord anyway](https://github.com/discordapp/discord-api-docs/issues/808#issuecomment-458863743), although someone could implement it by registering hooks.

#### Extensions
### Extensions

- [UDP-Queue](https://github.com/KyokoBot/koe/tree/master/ext-udpqueue)

#### Credits
### Credits

[LavaLink team](https://github.com/lavalink-devs) for being the main consumer of Koe and providing most feedback and improvements.
[@TheAkio](https://github.com/TheAkio) for name idea.

Koe includes modified/stripped-down parts based on following open-source projects:
Expand Down
Loading