Client capability bridge for commands, scripts, and custom data

ClientMod ext

ClientMod is a PyJavaBridge extension for communicating with a supported client mod over a custom packet channel.

The extension exposes a high-level session interface so you do not need to pass player on every call.

from bridge.extensions.client_mod import client_mod

cm = client_mod.session(player)
if await cm.is_available():
    await cm.command("audio.play", {{"sound": "block.note_block.bell", "volume": 1.0, "pitch": 1.0}})

Overview

The interface provides:

All responses are dictionaries that follow the status contract:


Basic Usage

result = await send_command(
    player,
    "audio.play",
    {{
        "sound": "block.note_block.bell",
        "volume": 1.0,
        "pitch": 1.0,
    }},
)

if result.get("status") != "ok":
    player.send_message(f"Client command failed: {{result.get('message')}}")

API

High-level Interface

from bridge.extensions.client_mod import client_mod

cm = client_mod.for_player(player)
await cm.register_script("hud", SOURCE, auto_start=True)
await cm.metrics_get()
await cm.voice_subscribe("party", source_player=None, mix=True)

Availability

await cm.is_available() -> bool

Commands

await cm.command(capability, args=None, handle=None, timeout_ms=1000)

Data

await cm.data(channel, payload=None, timeout_ms=1000)

Scripts

await cm.register_script(name, source, auto_start=True, metadata=None, timeout_ms=2000)

Permissions

await cm.set_permissions(capabilities, reason=None, remember_prompt=True, timeout_ms=3000)
await cm.get_permissions()

Spatial and Metrics Helpers

await cm.raycast(max_distance=64.0, include_fluids=False)
await cm.entities_list({{"radius": 32, "type": "zombie"}})
await cm.entities_query({{"name": "villager", "limit": 5}})
await cm.particles_spawn("minecraft:flame", count=16, spread=0.35)
await cm.metrics_get()

request() Data Keys

await client_mod.register_request_data(key, value)
await client_mod.unregister_request_data(key)

Audio Stream Helpers

await cm.stream_audio_file(path, stream_id=None, sample_rate=48000, channels=2, chunk_size=4096)
await cm.stream_audio_generator(gen, stream_id=None, sample_rate=48000, channels=2, chunk_size=4096)
await cm.audio_stream_set_volume(stream_id, volume=1.0)
await cm.audio_stream_pause(stream_id)
await cm.audio_stream_resume(stream_id)

stream_audio_generator accepts either:

Microphone and Voice Helpers

await cm.mic_set_mute(muted=True)
await cm.mic_get_state()
await cm.mic_level_subscribe(stream_id, interval_ms=250)
await cm.mic_level_unsubscribe(stream_id)
await cm.mic_vad_set(enabled=True, threshold=0.02, min_speech_ms=200)

await cm.voice_subscribe(stream_id, source_player=None, mix=False)
await cm.voice_unsubscribe(stream_id)

UI Prompt and Client Preferences

await cm.ui_prompt_confirm(title=None, message=None, remember_option=False, timeout_ms=60000)
await cm.client_pref_set(key, value)
await cm.client_pref_get(key)

Implemented Capabilities

The client supports the following capabilities:

Audio streaming

The client supports server-driven streaming of raw PCM (signed 16-bit little-endian) audio. Use the Python-facing extension APIs rather than sending raw JSON frames.

# Start a stream (returns stream_id)
res = await send_command(player, "audio.stream.start", {{"stream_id": "mystream", "sample_rate": 48000, "channels": 2}})
if res.get("status") != "ok":
    player.send_message("Failed to start audio stream: %s" % res.get("message"))

# Stop the stream
await send_command(player, "audio.stream.stop", {{"stream_id": "mystream"}})

Chunks (binary): send raw PCM bytes using send_data. When sending binary chunks the bridge prefers msgpack (binary-safe); if msgpack is unavailable you can Base64-encode the data instead. Example (raw PCM file):

with open("audio.pcm", "rb") as f:
    while True:
        chunk = f.read(4096)
        if not chunk:
            break
        # send_data accepts dict payloads; 'data' may be bytes when msgpack is enabled
        await send_data(player, "audio_stream_chunk", {{"stream_id": "mystream", "data": chunk}})

# or, if you must use JSON-only transport, send Base64:
import base64
with open("audio.pcm", "rb") as f:
    while (chunk := f.read(4096)):
        await send_data(player, "audio_stream_chunk", {{"stream_id": "mystream", "data_b64": base64.b64encode(chunk).decode("ascii")}})

Notes and limitations:

Events

Use decorators to receive pushed events from client-mod packets:

Example:

@client_mod.on_client_data
async def handle_client_data(event):
    data = event.data
    print("client data", data)

Notes