Tablebox ドキュメントサイト English

出す・読む

MoQ では、参加者がパスにブロードキャストを出し、他の人が出したブロードキャストを読みます。SDK はそのための公式の MoQ のオブジェクト(TypeScript では @moq/net、Rust では moq-net)をそのまま渡すので、MoQ のドキュメントにあることはここでも成り立ちます。

パスと権利

パスは / で区切った最大 32 の段で、セッションのルートからの相対です。どのパスを使うかはアプリが決めます。参加者自身の ID の下の {id}/… がふつうの形で、それを保証するのはトークンの publish のパターンです。セッションは publish のパターンに一致する所に出し、subscribe のパターンに一致する所を読めます。その外を読むと、誰も出していないパスを読むときと同じく待ちます。

.aggregation のように . で始まる段は、Tablebox のプログラムとシステムのデータのものです。アプリが求めない限り一覧には出ず、名前を指定すればほかのパスと同じく読め、権利もふつうどおり効きます。

出す

publish(path) は、path に告知された公式の Broadcast.Producer を返します。ブロードキャストは名前の付いたトラックを持ち、トラックはグループを、グループはフレームを順に運びます。createTrack(name) でトラックを作り、writeString、writeJson、writeFrame はフレームを一つのグループとして書き、appendGroup() は複数のフレームのグループを始めます。ブロードキャストを終えるときは、トラックを閉じてからブロードキャストを閉じます。再接続の後、SDK はすべてのブロードキャストを出し直します。

変わっていく値には JSON のトラックを使います。json.Snapshot.Producer(公式の @moq/json)はスナップショットに続けて RFC 7396 の merge patch の差分を書き、後から来た読み手は最新の値を最初に受け取ります。json.Stream は代わりにレコードの記録を残します。

TypeScript(Node) · publish.ts
// Publishes {user}/status with a JSON track, status.json, that changes every second.
import { connect, json } from 'tablebox.io';
import { relay } from './relay.ts';
import { sign } from './token.ts';

const user = process.argv[2] ?? 'alice';
const connection = await connect({ ...relay, getToken: () => sign(user, { publish: [`${user}/**`] }) });
const broadcast = connection.publish(`${user}/status`);
const status = new json.Snapshot.Producer<{ online: boolean; count: number }>({ track: broadcast.createTrack('status.json') });
let count = 0;
setInterval(() => status.update({ online: true, count: count++ }), 1_000);

読む・一覧する

read(path) は、何かが path を出すか受け持つまで待ち、その Broadcast.Consumer を返します。track(name).subscribe() はトラックのグループを渡し、recvGroup() の後に readString()、readJson()、readFrame() で読みます。購読の maxAge(既定は 0)はどこまでさかのぼるかで、0 なら最新のグループから始めます。ブロードキャストは、出した人が終えるか去ると終わります。そのトラックは、出した人が閉じればきれいに終わり、接続が切れればエラーで終わります。そうしたら読み直します。

list(prefix) は、prefix かその下に出ているパスを、今あるものと、出てきたり消えたりするのに合わせて、セッションの読む権利の中で渡します。各更新は { kind, prefix } で、kind は announced、updated、retracted のどれかです。list(prefix, { hidden: true }) は、prefix より下に . の段があるパスも渡します。ある範囲に誰が出しているかは、そこを一覧すれば分かります。

TypeScript(Node) · read.ts
// Lists every {user}/status as it is published and prints its status.json as it changes.
import { connect, json } from 'tablebox.io';
import { relay } from './relay.ts';
import { sign } from './token.ts';

const connection = await connect({ ...relay, getToken: () => sign('reader', { subscribe: ['**'] }) });
for await (const { kind, prefix } of connection.list('')) {
  if (kind === 'retracted' || !prefix.endsWith('/status')) continue;
  void (async () => {
    const broadcast = await connection.read(prefix);
    const values = new json.Snapshot.Consumer({ track: broadcast.track('status.json').subscribe() });
    for await (const value of values) console.log(`${prefix}: ${JSON.stringify(value)}`);
  })();
}

Rust でも同じ呼び出しが moq-net のオブジェクトを渡し、tablebox::json は moq-json です。

Rust · read.rs
//! Reads alice/status and prints its status.json as it changes, with TABLEBOX_TOKEN.
use tablebox::{ConnectOptions, connect, json::snapshot::Consumer};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let env = |name| std::env::var(name).unwrap_or_default();
    let mut options = ConnectOptions::token(env("TABLEBOX_RELAY_URL"), env("TABLEBOX_TOKEN"));
    options.server_certificate_hashes = std::env::var("TABLEBOX_RELAY_CERT_SHA256").into_iter().collect();
    let connection = connect(options)?;
    let broadcast = connection.read("alice/status").await?;
    let track = broadcast.track("status.json")?.subscribe(None).await?;
    let mut values = Consumer::<serde_json::Value>::new(track, Default::default());
    while let Some(value) = values.next().await? {
        println!("alice/status: {value}");
    }
    Ok(())
}

カメラ、マイク、画面

ブラウザーでは、公式の publish と watch の部品(@moq/publish と @moq/watch の publish と watch)が connection.origin の上で動きます。publish.Broadcast に publish.Source.Camera、Microphone、Screen を付けると hang のブロードキャスト(カタログと、レンディションごとのトラック)を出し、watch.Player はそれを canvas に再生します。使い始めるのとおりです。publish.Video.Encoder に config: { codec: 'avc1' }、publish.Audio.Encoder に codec: 'aac' を渡すと H.264 と AAC を選び、Egress はそれをそのまま配信サービスへ送ります。