Documentation
¶
Overview ¶
Package voltkit is the VoltKit RTC server SDK for Go: rooms, tokens, chat, broadcast streams, usage, billing and verified webhooks, with the standard library only.
Your backend does two things: decide who may do what, and hand the client a short-lived token. It is never in the media path; there is no stream to proxy and nothing that scales with viewers.
client, err := voltkit.NewStreamClient(voltkit.Config{
BaseURL: os.Getenv("VOLTKIT_RTC_URL"), // from your project's Credentials tab
APIKey: os.Getenv("VOLTKIT_RTC_SECRET"), // server-side only, never sent to a client
})
if err != nil {
log.Fatal(err) // a missing or malformed option, caught at startup
}
room, err := client.EnsureRoom(ctx, voltkit.EnsureRoomParams{RoomID: "show-42", HostIdentity: "user-7"})
tok, err := client.CreateToken(ctx, voltkit.CreateTokenParams{RoomID: "show-42", Identity: "user-99", Role: voltkit.RoleViewer})
Every method takes a context.Context first; cancelling it stops the call, retries included, and returns the context's error. A call that fails returns an *Error carrying the server's Code, the HTTP Status and whether it is Retryable, reachable with errors.As; its Message is always safe to log: the API key and any URL credentials are redacted before they can reach it. A mistake in your own setup (a bad Config, a body JSON cannot carry) is a plain error, so a handler cannot mistake it for an answer from VoltKit.
The API key is tenant-wide. It is sent to exactly one place, the controller named by Config.BaseURL, in the X-API-Key header: never across a redirect (a 3xx fails with the code redirected), never to the rates host, never into an error message or a printed client.
Transient failures (a network error, a 429, a 5xx other than billing-unavailable) are retried with jittered backoff on every call that is safe to repeat; CreateRoom, CreateStream and SendMessage are sent once, because a lost reply would turn a success into a spurious "already exists" or a duplicate message. The README says what to do then.
Webhooks: VerifyWebhook and ParseWebhook check X-Stream-Signature over the raw request body, with a freshness window and a list of secrets for a rotation. The broadcast methods are kept for parity with the API and are not offered on VoltKit cloud today.
Index ¶
- Constants
- Variables
- func ParseCu(amount string) (*big.Int, error)
- func Ptr[T any](v T) *T
- func VerifyWebhook(body []byte, header string, secrets []string, opts *VerifyOptions) bool
- type Allowance
- type BroadcastStream
- type BroadcastStreamWithPublish
- type BurnView
- type ChatInfo
- type ChatMessage
- type ChatToken
- type CodeInfo
- type Config
- type CreateChatTokenParams
- type CreateRoomParams
- type CreateStreamParams
- type CreateStreamTokenParams
- type CreateTokenParams
- type DailyUsage
- type EnsureRoomParams
- type Error
- type LegacyFunds
- type ListMessagesOptions
- type Origin
- type Participant
- type PlaybackPolicy
- type PlaybackURLs
- type Producer
- type ProjectBalance
- type PublishInfo
- type ReceiveKbps
- type Role
- type Room
- type RoomList
- type RoomToken
- type RoomView
- type RoomWithHostToken
- type RtcConversion
- type RtcMode
- type RtcRates
- type RtcSurcharge
- type RtcTierRate
- type RtcUnit
- type SendMessageParams
- type StreamAction
- type StreamClient
- func (c *StreamClient) CreateChatToken(ctx context.Context, params CreateChatTokenParams) (*ChatToken, error)
- func (c *StreamClient) CreateRoom(ctx context.Context, params CreateRoomParams) (*RoomWithHostToken, error)
- func (c *StreamClient) CreateStream(ctx context.Context, params CreateStreamParams) (*BroadcastStreamWithPublish, error)
- func (c *StreamClient) CreateStreamToken(ctx context.Context, params CreateStreamTokenParams) (*StreamToken, error)
- func (c *StreamClient) CreateToken(ctx context.Context, params CreateTokenParams) (*RoomToken, error)
- func (c *StreamClient) DeleteMessage(ctx context.Context, channel, messageID string) error
- func (c *StreamClient) DeleteRoom(ctx context.Context, roomID string) error
- func (c *StreamClient) DeleteStream(ctx context.Context, streamID string) error
- func (c *StreamClient) EnsureRoom(ctx context.Context, params EnsureRoomParams) (*RoomWithHostToken, error)
- func (c StreamClient) Format(f fmt.State, verb rune)
- func (c *StreamClient) GetBalance(ctx context.Context) (*ProjectBalance, error)
- func (c *StreamClient) GetChatInfo(ctx context.Context, channel string) (*ChatInfo, error)
- func (c *StreamClient) GetRates(ctx context.Context) (*RtcRates, error)
- func (c *StreamClient) GetRoom(ctx context.Context, roomID string) (*RoomView, error)
- func (c *StreamClient) GetStream(ctx context.Context, streamID string) (*BroadcastStream, error)
- func (c *StreamClient) GetUsage(ctx context.Context) (*UsageSummary, error)
- func (c *StreamClient) GetUsageSeries(ctx context.Context, days int) ([]DailyUsage, error)
- func (c StreamClient) GoString() string
- func (c *StreamClient) ListChatMutes(ctx context.Context, channel string) ([]string, error)
- func (c *StreamClient) ListMessages(ctx context.Context, channel string, opts *ListMessagesOptions) ([]ChatMessage, error)
- func (c *StreamClient) ListParticipants(ctx context.Context, roomID string) ([]Participant, error)
- func (c *StreamClient) ListRooms(ctx context.Context) (*RoomList, error)
- func (c *StreamClient) ListStreams(ctx context.Context) ([]BroadcastStream, error)
- func (c StreamClient) LogValue() slog.Value
- func (c StreamClient) MarshalJSON() ([]byte, error)
- func (c *StreamClient) MuteChat(ctx context.Context, channel, identity string) error
- func (c *StreamClient) SendMessage(ctx context.Context, params SendMessageParams) (*ChatMessage, error)
- func (c StreamClient) String() string
- func (c *StreamClient) UnmuteChat(ctx context.Context, channel, identity string) error
- func (c *StreamClient) UpdateStream(ctx context.Context, streamID string, patch UpdateStreamParams) (*BroadcastStream, error)
- type StreamEvent
- type StreamToken
- type UpdateStreamParams
- type Usage
- type UsageSummary
- type VerifyOptions
- type WalletFunds
- type WalletLowState
- type WalletScopeAmount
Constants ¶
const CuPerCredit = 6_000_000
CuPerCredit is how many credit units (cu) one credit is. Every cu amount on the wire is a decimal string; see ParseCu.
const DefaultTolerance = 300 * time.Second
DefaultTolerance is how far out of date a delivery's timestamp may be, in either direction, when VerifyOptions.Tolerance is nil: five minutes. It guards against a captured request being replayed later.
const Version = "0.1.0"
Version is the version of this module, as published. It is the one place the number lives: the User-Agent header of every request carries it (voltkit-rtc-go/<Version>, spec section 8), and the repository's contract check compares it with docs/sdk-contract.json.
Variables ¶
var ErrorCodes = func() []string { codes := make([]string, len(registry)) for i, info := range registry { codes[i] = info.Code } return codes }()
ErrorCodes is every code in the shared registry, in its order. Most of these only ever reach a client SDK on its socket; RESTErrorCodes are the ones a call from this SDK can fail with. Treat the slice as read-only.
var RESTErrorCodes = []string{
"internal",
"no-room",
"viewer-quota",
"quota-exhausted",
"daily-limit-reached",
"monthly-limit-reached",
"tenant-suspended",
"billing-unavailable",
"bad-request",
"rate-limited",
"no-message",
"unauthorized",
"bad-signature",
"operator-only",
"bad-role",
"room-exists",
"room-quota",
"no-nodes",
"capacity-exhausted",
"unavailable",
"not-found",
"broadcast-disabled",
"chat-disabled",
"no-stream",
"stream-exists",
"stream-quota",
"network",
"rates-unconfigured",
"redirected",
}
RESTErrorCodes is the registry codes whose surfaces include the tenant REST API, in the registry's order: what a call from this SDK can fail with, besides http-<status> for a reply that names no code and, from GetRates, the website API's own codes (RATE_LIMITED, NOT_FOUND, ...), which are passed through unchanged. Treat the slice as read-only.
Functions ¶
func ParseCu ¶
ParseCu reads a cu amount as the wire sends it ("186000000", a decimal string, never a number: a float would round a large balance) into a *big.Int for exact arithmetic. It refuses anything but an optional sign and decimal digits.
func Ptr ¶
func Ptr[T any](v T) *T
Ptr returns a pointer to v: for the optional fields that distinguish "left out" from zero, Config.Retries (voltkit.Ptr(0) turns retries off) and VerifyOptions.Tolerance.
func VerifyWebhook ¶
func VerifyWebhook(body []byte, header string, secrets []string, opts *VerifyOptions) bool
VerifyWebhook reports whether a delivery is genuine and fresh: body is the request body exactly as received (never re-serialized), header the X-Stream-Signature value, secrets the signing secret and, during a rotation, the previous one too. Every secret is tried against every v1 value, each compared in constant time, and one match is enough.
A mistake in the setup is loud, not a quiet false that would look like an attack: it panics when secrets is empty or holds only empty strings, or when Tolerance is negative, as the Node SDK throws a TypeError or RangeError. Use ParseWebhook to get those as errors instead.
Types ¶
type Allowance ¶
type Allowance struct {
Plan string `json:"plan"`
FreeDailyMinutes int64 `json:"freeDailyMinutes"`
FreeMonthlyMinutes int64 `json:"freeMonthlyMinutes"`
DailyCapMinutes int64 `json:"dailyCapMinutes"`
MonthlyCapMinutes int64 `json:"monthlyCapMinutes"`
UsedTodaySeconds int64 `json:"usedTodaySeconds"`
UsedMonthSeconds int64 `json:"usedMonthSeconds"`
FreeUsedTodaySeconds int64 `json:"freeUsedTodaySeconds"`
FreeUsedMonthSeconds int64 `json:"freeUsedMonthSeconds"`
FreeRemainingSeconds int64 `json:"freeRemainingSeconds"`
PaidRemainingSeconds int64 `json:"paidRemainingSeconds"`
RemainingSeconds int64 `json:"remainingSeconds"`
// Blocked is why the account cannot be used right now, if it cannot:
// quota-exhausted, daily-limit-reached, monthly-limit-reached or
// suspended.
Blocked string `json:"blocked,omitempty"`
}
Allowance is where an account stands against every limit, in seconds; -1 means no limit.
type BroadcastStream ¶
type BroadcastStream struct {
StreamID string `json:"streamId"`
TenantID string `json:"tenantId"`
// StreamKey is the name the media server knows the stream by. Random, so
// a playback URL cannot be derived from a stream id.
StreamKey string `json:"streamKey"`
// State is idle or live.
State string `json:"state"`
Playback PlaybackPolicy `json:"playback"`
// CreatedAt, StartedAt and EndedAt are unix milliseconds.
CreatedAt int64 `json:"createdAt"`
StartedAt int64 `json:"startedAt,omitempty"`
EndedAt int64 `json:"endedAt,omitempty"`
// Viewers counts the playback sessions the media server serves itself.
// An audience behind a CDN is not in this number.
Viewers int `json:"viewers"`
PeakViewers int `json:"peakViewers"`
Metadata json.RawMessage `json:"metadata,omitempty"`
PlaybackURLs PlaybackURLs `json:"playbackUrls"`
}
BroadcastStream is a stream as the controller describes it.
type BroadcastStreamWithPublish ¶
type BroadcastStreamWithPublish struct {
BroadcastStream
Publish PublishInfo `json:"publish"`
}
BroadcastStreamWithPublish is CreateStream's answer: the only reply that ever carries a publish token. Treat Publish like a password.
type BurnView ¶
type BurnView struct {
// LiveParticipants are joined participants across all the project's rooms.
LiveParticipants int `json:"liveParticipants"`
// ProjectCuPerSecond is cu per second for this project's rooms and
// broadcasts; WalletCuPerSecond for every project drawing the same wallet.
ProjectCuPerSecond string `json:"projectCuPerSecond"`
WalletCuPerSecond string `json:"walletCuPerSecond"`
}
BurnView is what is being spent right now.
type ChatInfo ¶
type ChatInfo struct {
Channel string `json:"channel"`
// Members is how many identities are connected to the channel, in the
// room or only in its chat.
Members int `json:"members"`
}
ChatInfo is GetChatInfo's answer.
type ChatMessage ¶
type ChatMessage struct {
// ID is monotonic within a channel: the oldest id you hold is a paging
// cursor for ListMessages.
ID string `json:"id"`
Channel string `json:"channel"`
From string `json:"from"`
// To is set on a direct message.
To string `json:"to,omitempty"`
Type string `json:"type"`
// Text is delivered exactly as sent. Render it as text, never as HTML.
Text string `json:"text,omitempty"`
Metadata json.RawMessage `json:"metadata,omitempty"`
ClientMsgID string `json:"clientMsgId,omitempty"`
// TS is unix milliseconds.
TS int64 `json:"ts"`
}
ChatMessage is one stored message.
type ChatToken ¶
type ChatToken struct {
Token string `json:"token"`
// WSURL is where a chat-only client connects. Ends in /ws/chat.
WSURL string `json:"wsUrl"`
Role Role `json:"role"`
Identity string `json:"identity"`
Channel string `json:"channel"`
// RoomID is the room of the same name, when the wire names it.
RoomID string `json:"roomId,omitempty"`
// ExpiresAt is unix seconds.
ExpiresAt int64 `json:"expiresAt"`
}
ChatToken is a chat-only token, from CreateChatToken.
type CodeInfo ¶
type CodeInfo struct {
// Code is the registry code, as Error.Code carries it.
Code string
// Origin is who raises it: the server, or an SDK itself.
Origin Origin
// Terminal concerns client SDKs: a join refused with this code must not
// be retried with the same token. Nothing in this SDK acts on it.
Terminal bool
// Retryable is the registry's flag: trying the same call again later can
// succeed. Error.Retryable on a failed call follows the HTTP status rule
// of section 3.6 and agrees with this for every REST code.
Retryable bool
}
CodeInfo is one entry of the shared error registry (docs/sdk-contract.json in the VoltKit RTC repository): what ErrorInfo returns.
type Config ¶
type Config struct {
// BaseURL is the controller's origin, with its exact scheme and host:
// https://api.voltkit.in for VoltKit cloud. The API key is sent here and
// nowhere else. A trailing slash is dropped.
BaseURL string
// APIKey is the project API secret (sk_ and 64 hex digits). Server-side
// only; whitespace around it is ignored. It is never quoted in an error.
APIKey string
// RatesBaseURL is the website API's origin (https://api.voltkit.in),
// where GetRates reads the public price list with no credential. Only
// GetRates needs it; "" and "/" mean unset.
RatesBaseURL string
// Timeout is the deadline for each attempt, from sending the request to
// reading the last byte of the reply. Zero means 10 seconds; it must not
// be negative. In whole milliseconds; above 2^31-1 ms it is held there.
Timeout time.Duration
// Retries is how many more attempts a call that is safe to repeat gets
// after a transient failure. nil means 2; Ptr(0) turns retries off; it
// must not be negative.
Retries *int
// HTTPClient is the transport: a client of your own for a proxy, custom
// TLS or instrumentation. nil means a fresh http.Client on the default
// transport. It is copied, never modified: the copy for the controller
// never follows a redirect, the copy for the rates host keeps your
// redirect policy, and both leave the deadline to the context.
HTTPClient *http.Client
}
Config is what NewStreamClient needs. The zero value of an optional field means its default.
func (Config) Format ¶
Format prints the same redacted view under every fmt verb. Without it, a verb that has no meaning for a struct (%d, %t, %c, %U) makes fmt print each field as it is, key included. Config is still a plain struct: a reflection-based dumper, or %p on a value rather than a pointer (which go vet flags), reads its fields. Keep a Config out of dumps.
func (Config) MarshalJSON ¶
MarshalJSON serializes the redacted view, so a Config in a structured log line gives nothing away: zap, zerolog, logrus and slog's JSON handler all go through encoding/json.
type CreateChatTokenParams ¶
type CreateChatTokenParams struct {
Channel string `json:"-"`
Identity string `json:"identity"`
Role Role `json:"role,omitempty"`
}
CreateChatTokenParams is CreateChatToken's argument. Role defaults to viewer.
type CreateRoomParams ¶
type CreateRoomParams struct {
// RoomID is your id, so webhooks read back usefully. Left out, the
// server makes one up.
RoomID string `json:"roomId,omitempty"`
HostIdentity string `json:"hostIdentity"`
// Metadata is any JSON value: opaque, room-scoped state every participant
// receives when they join.
Metadata any `json:"metadata,omitempty"`
}
CreateRoomParams is CreateRoom's argument.
type CreateStreamParams ¶
type CreateStreamParams struct {
StreamID string `json:"streamId"`
Playback PlaybackPolicy `json:"playback,omitempty"`
Metadata any `json:"metadata,omitempty"`
}
CreateStreamParams is CreateStream's argument.
type CreateStreamTokenParams ¶
type CreateStreamTokenParams struct {
StreamID string `json:"-"`
Action StreamAction `json:"action,omitempty"`
Identity string `json:"identity,omitempty"`
TTLSeconds int `json:"ttlSeconds,omitempty"`
}
CreateStreamTokenParams is CreateStreamToken's argument. Action defaults to play; TTLSeconds to the service's default and is capped at 24 hours.
type CreateTokenParams ¶
type CreateTokenParams struct {
RoomID string `json:"-"`
Identity string `json:"identity"`
Role Role `json:"role"`
}
CreateTokenParams is CreateToken's argument. Role is required: your backend decides it, and the server refuses an unknown one with bad-role.
type DailyUsage ¶
DailyUsage is one UTC day of GetUsageSeries.
type EnsureRoomParams ¶
type EnsureRoomParams struct {
RoomID string `json:"-"`
HostIdentity string `json:"hostIdentity"`
// Metadata is applied only when the room is created.
Metadata any `json:"metadata,omitempty"`
}
EnsureRoomParams is EnsureRoom's argument.
type Error ¶
type Error struct {
// Code is the server's code (no-room, quota-exhausted, ...), network when
// no reply arrived, http-<status> when a reply named no code, redirected,
// rates-unconfigured, or bad-request for an id refused before sending. A
// newer controller may send a code this release does not know; it is
// passed through unchanged.
Code string
// Status is the HTTP status: 0 when nothing reached the server or the SDK
// refused the call itself, the 3xx for redirected.
Status int
// Message reads "<METHOD> <path>: <what went wrong>", with the path as
// sent (ids encoded, query string included).
Message string
// Retryable is true when trying again later could work: network, a 429, or
// a 5xx other than billing-unavailable. On a call the SDK repeats it means
// the retries have already been spent (section 4.1).
Retryable bool
}
Error is what every failed call returns (the Node SDK's StreamError, spec section 5.1). Use errors.As to reach it:
var e *voltkit.Error
if errors.As(err, &e) && e.IsNotFound() { ... }
The controller answers errors as { "error": "<code>", "message": "<text>" } with a meaningful status. Collapsing those into a generic error loses the one thing a caller needs to decide what to do: capacity-exhausted means page someone and retry, room-quota means your own cleanup is behind, bad-request means fix the call. So the code is kept, and Message is always safe to log: the API key and any URL credentials are redacted before they can reach it.
func (*Error) Error ¶
Error returns Message, so the generic error surface shows the method, the path and the server's words.
func (*Error) IsBillingUnavailable ¶
IsBillingUnavailable reports billing-unavailable: the project is billed from its wallet and no price list is active yet, or the controller has no wallet billing. An operator fixes this, not a retry, so although it is a 503 the SDK never retries it.
func (*Error) IsBroadcastDisabled ¶
IsBroadcastDisabled reports broadcast-disabled: broadcast is not switched on for this deployment. Not something a retry fixes.
func (*Error) IsCapacityExhausted ¶
IsCapacityExhausted reports capacity-exhausted or no-nodes: the fleet is full. This is the one to alert on; it is about capacity, not correctness.
func (*Error) IsNotFound ¶
IsNotFound reports a 404: the room, stream or message is gone, or was never yours (rooms are tenant-scoped, so those are the same answer). broadcast-disabled and chat-disabled are 404s too: test IsBroadcastDisabled first when it matters.
func (*Error) IsOutOfMinutes ¶
IsOutOfMinutes reports that the account cannot use more right now: a 402, or the codes quota-exhausted, daily-limit-reached and monthly-limit-reached.
type LegacyFunds ¶
type LegacyFunds struct {
// Unlimited: a plan whose own minutes never run out; OwnSeconds and
// TotalSeconds are then -1.
Unlimited bool `json:"unlimited"`
// FreeSeconds is the free allowance left.
FreeSeconds int64 `json:"freeSeconds"`
// OwnSeconds is the project's own minutes; -1 when unlimited.
OwnSeconds int64 `json:"ownSeconds"`
SharedSeconds int64 `json:"sharedSeconds"`
// TotalSeconds is free + own + shared; -1 when unlimited.
TotalSeconds int64 `json:"totalSeconds"`
}
LegacyFunds are the minute pockets spent before the wallet, in participant-seconds.
type ListMessagesOptions ¶
ListMessagesOptions pages through history. Both are optional: Limit defaults to 50 on the server and is capped at 100; Before is the oldest id you hold.
type Origin ¶
type Origin string
Origin says who raises a registry code: the server (the controller or a media node, passed through unchanged by every SDK) or an SDK itself.
type Participant ¶
type Participant struct {
Identity string `json:"identity"`
Role Role `json:"role"`
NodeID string `json:"nodeId"`
HandRaised bool `json:"handRaised"`
HandMetadata json.RawMessage `json:"handMetadata,omitempty"`
Kicked bool `json:"kicked,omitempty"`
// Muted and VideoBlocked are moderator state: a moderator switched the
// mic or camera off and the participant may not switch it back.
Muted bool `json:"muted"`
VideoBlocked bool `json:"videoBlocked"`
// JoinedAt is unix milliseconds.
JoinedAt int64 `json:"joinedAt"`
// Metadata is their display metadata, from the token or their own
// setMetadata.
Metadata json.RawMessage `json:"metadata,omitempty"`
}
Participant is one connected identity in a room.
type PlaybackPolicy ¶
type PlaybackPolicy string
PlaybackPolicy says who may watch a broadcast stream.
const ( // PlaybackPublic: anyone with the playback URL. The default. PlaybackPublic PlaybackPolicy = "public" // PlaybackToken: each viewer needs URLs minted with CreateStreamToken. PlaybackToken PlaybackPolicy = "token" )
type PlaybackURLs ¶
type PlaybackURLs struct {
LLHLS string `json:"llhls"`
WebRTC string `json:"webrtc"`
Token string `json:"token,omitempty"`
// ExpiresAt is unix seconds, when a token is present.
ExpiresAt int64 `json:"expiresAt,omitempty"`
}
PlaybackURLs is where a stream is watched.
type Producer ¶
type Producer struct {
ProducerID string `json:"producerId"`
Identity string `json:"identity"`
// Kind is audio or video.
Kind string `json:"kind"`
NodeID string `json:"nodeId"`
Paused bool `json:"paused"`
// Source is mic or music for audio, camera or screen for video; empty
// means mic or camera.
Source string `json:"source,omitempty"`
}
Producer is one published track in a room, as GetRoom lists them.
type ProjectBalance ¶
type ProjectBalance struct {
TenantID string `json:"tenantId"`
// BillingMode is legacy (flat minutes) or wallet (credits from a wallet).
BillingMode string `json:"billingMode"`
// CanJoin is whether a newcomer would be admitted now.
CanJoin bool `json:"canJoin"`
// Blocked is, when CanJoin is false, the code a join or a token mint is
// refused with; nil otherwise.
Blocked *string `json:"blocked"`
Legacy LegacyFunds `json:"legacy"`
// Wallet is nil unless the project is billed from its wallet.
Wallet *WalletFunds `json:"wallet"`
Burn BurnView `json:"burn"`
// RemainingSeconds estimates how long the funds last at the current burn;
// -1 when nothing is being spent or nothing limits it.
RemainingSeconds int64 `json:"remainingSeconds"`
WalletLow WalletLowState `json:"walletLow"`
// PricingVersion is the price list new wallet sessions lock; nil for a
// legacy project, and while no price list is active.
PricingVersion *int `json:"pricingVersion"`
// UpdatedAt is unix milliseconds.
UpdatedAt int64 `json:"updatedAt"`
}
ProjectBalance is GetBalance's answer: the project's spendable funds right now. Read only; recharges happen on the website.
type PublishInfo ¶
type PublishInfo struct {
WHIPURL string `json:"whipUrl"`
Token string `json:"token"`
// ExpiresAt is unix seconds.
ExpiresAt int64 `json:"expiresAt"`
}
PublishInfo is how an encoder publishes: WHIPURL already carries the token.
type ReceiveKbps ¶
type ReceiveKbps struct {
Video int `json:"video"`
Audio int `json:"audio"`
Total int `json:"total"`
}
ReceiveKbps is what one viewer receives at the top layer.
type Role ¶
type Role string
Role is what a participant may do in a room. Your backend decides it; it is signed into the token and the client cannot change it.
type Room ¶
type Room struct {
RoomID string `json:"roomId"`
TenantID string `json:"tenantId"`
OriginNode string `json:"originNode"`
State string `json:"state"`
HostIdentity string `json:"hostIdentity"`
// CreatedAt is unix milliseconds.
CreatedAt int64 `json:"createdAt"`
// Nodes are the media nodes the room spans.
Nodes []string `json:"nodes"`
// Participants is how many are connected right now.
Participants int `json:"participants"`
Demo bool `json:"demo,omitempty"`
// Metadata is the opaque, room-scoped state given when the room was
// created; every participant receives it on join.
Metadata json.RawMessage `json:"metadata,omitempty"`
}
Room is a live room as the controller describes it.
type RoomList ¶
type RoomList struct {
Rooms []Room `json:"rooms"`
}
RoomList is ListRooms's answer. The wire wraps the list in an object (so a later release can add fields beside it), and so does this SDK, like every VoltKit server SDK.
type RoomToken ¶
type RoomToken struct {
Token string `json:"token"`
WSURL string `json:"wsUrl"`
Role Role `json:"role"`
Identity string `json:"identity"`
RoomID string `json:"roomId"`
// ExpiresAt is unix seconds. A token lasts ten minutes; a connected
// client is refreshed by the controller.
ExpiresAt int64 `json:"expiresAt"`
}
RoomToken is a room token for one identity, from CreateToken.
type RoomView ¶
type RoomView struct {
Room Room `json:"room"`
Participants []Participant `json:"participants"`
Producers []Producer `json:"producers"`
}
RoomView is GetRoom's answer: the authoritative live view.
type RoomWithHostToken ¶
type RoomWithHostToken struct {
Room
// HostToken is a credential for this room: hand it to the host's client,
// never log it.
HostToken string `json:"hostToken"`
WSURL string `json:"wsUrl"`
// ExpiresAt is unix seconds.
ExpiresAt int64 `json:"expiresAt"`
// Existed is true when the room already existed and was returned rather
// than created (EnsureRoom answered 200, not 201). Always false from
// CreateRoom.
Existed bool `json:"existed"`
}
RoomWithHostToken is what CreateRoom and EnsureRoom return: the room and a fresh host token.
type RtcConversion ¶
type RtcConversion struct {
// CreditsPerRupee has four decimals; the exact rate is
// Numerator / Denominator credits per rupee.
CreditsPerRupee string `json:"creditsPerRupee"`
Numerator string `json:"numerator"`
Denominator string `json:"denominator"`
InrPerCredit string `json:"inrPerCredit"`
// Version is nil while the default (1 rupee = 1 credit) applies;
// EffectiveAt is ISO 8601, nil for the default.
Version *int `json:"version"`
EffectiveAt *string `json:"effectiveAt"`
// Source is conversion-version or default.
Source string `json:"source"`
}
RtcConversion is credits per rupee for new recharges, exactly and for display.
type RtcMode ¶
type RtcMode struct {
ProfileID string `json:"profileId"`
Label string `json:"label"`
Tier string `json:"tier"`
TierLabel string `json:"tierLabel"`
Enabled bool `json:"enabled"`
// Codecs are MIME types, e.g. "video/VP8".
Codecs []string `json:"codecs"`
ReceiveKbps ReceiveKbps `json:"receiveKbps"`
Kind string `json:"kind"`
Channels int `json:"channels,omitempty"`
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
FPS int `json:"fps,omitempty"`
Layers int `json:"layers,omitempty"`
}
RtcMode is an enabled media mode: what it sends, and the tier it bills as. Kind is audio (Channels set) or video (Width, Height, FPS, Layers set).
type RtcRates ¶
type RtcRates struct {
// Version is the price list version. A wallet session keeps the version
// it started under.
Version int `json:"version"`
// Kind is standard here: a contract list is one customer's negotiated
// rates, never public.
Kind string `json:"kind"`
// EffectiveAt is when this version took effect, ISO 8601.
EffectiveAt *string `json:"effectiveAt"`
Unit RtcUnit `json:"unit"`
Tiers []RtcTierRate `json:"tiers"`
Surcharges []RtcSurcharge `json:"surcharges"`
// IdleTier is what a participant who is only connected is billed as:
// audio_std or none.
IdleTier string `json:"idleTier"`
Modes []RtcMode `json:"modes"`
Conversion RtcConversion `json:"conversion"`
// Notes are the billing rules in plain words, for a pricing page.
Notes []string `json:"notes"`
}
RtcRates is the public price list, from GetRates. Prices only, never costs.
type RtcSurcharge ¶
type RtcSurcharge struct {
Tier string `json:"tier"`
Height int `json:"height"`
FPS int `json:"fps"`
// Credits per 1,000 participant-minutes.
Credits string `json:"credits"`
}
RtcSurcharge is the 60 fps premium at one resolution, over the video base.
type RtcTierRate ¶
type RtcTierRate struct {
// ID is stable (audio_std, video_30, video_720_60, ...): usage and the
// ledger use it. Prices are what change, from version to version.
ID string `json:"id"`
// Rank: higher wins when a participant does several things at once.
Rank int `json:"rank"`
Label string `json:"label"`
Media string `json:"media"`
// Credits per 1,000 participant-minutes, e.g. "99.00".
Credits string `json:"credits"`
// CuPerParticipantSecond is the same price as an exact integer, e.g. "9900".
CuPerParticipantSecond string `json:"cuPerParticipantSecond"`
// Base is the family price a 60 fps surcharge is added to; Surcharge the
// surcharge included in Credits, nil for a tier without one.
Base string `json:"base"`
Surcharge *string `json:"surcharge"`
// Available: some enabled media mode bills at this tier.
Available bool `json:"available"`
}
RtcTierRate is a billing tier. A participant is billed at one tier at a time: the highest media they send or receive.
type RtcUnit ¶
type RtcUnit struct {
Price string `json:"price"`
Rate string `json:"rate"`
CuPerCredit string `json:"cuPerCredit"`
}
RtcUnit names the units of a price list, and cu per credit ("6000000").
type SendMessageParams ¶
type SendMessageParams struct {
Channel string `json:"-"`
Text string `json:"text,omitempty"`
// Type defaults to "text". Anything else is your own vocabulary.
Type string `json:"type,omitempty"`
// From is who it is from. Defaults to "system".
From string `json:"from,omitempty"`
// To makes it a direct message to one identity.
To string `json:"to,omitempty"`
Metadata any `json:"metadata,omitempty"`
// Ephemeral delivers without storing: for reactions and anything not
// worth replaying.
Ephemeral bool `json:"ephemeral,omitempty"`
}
SendMessageParams is SendMessage's argument. A message needs Text or Metadata.
type StreamAction ¶
type StreamAction string
StreamAction is what a stream token is for.
const ( // ActionPlay: a viewer of a token-protected stream. The default. ActionPlay StreamAction = "play" // ActionPublish: an encoder whose publish URL has expired. ActionPublish StreamAction = "publish" )
type StreamClient ¶
type StreamClient struct {
// contains filtered or unexported fields
}
StreamClient is the client for the tenant REST API: rooms, tokens, chat, broadcast, usage, balance and rates. Make one with NewStreamClient or NewStreamClientFromEnv when your server starts and share it: it is safe for concurrent use and holds no state outside a call.
func NewStreamClient ¶
func NewStreamClient(cfg Config) (*StreamClient, error)
NewStreamClient makes a client. Every option is checked here, so a missing or malformed value fails at startup rather than on a user's first call, and none of these errors quotes the key.
func NewStreamClientFromEnv ¶
func NewStreamClientFromEnv() (*StreamClient, error)
NewStreamClientFromEnv makes a client from the environment: VOLTKIT_RTC_URL (BaseURL), VOLTKIT_RTC_SECRET (APIKey) and, optionally, VOLTKIT_API_URL (RatesBaseURL). Nothing else is read; for a timeout or retries, use NewStreamClient with a Config. A missing variable is an error naming it.
func (*StreamClient) CreateChatToken ¶
func (c *StreamClient) CreateChatToken(ctx context.Context, params CreateChatTokenParams) (*ChatToken, error)
CreateChatToken admits one of your users to a channel's chat, with no media rights: for someone who is not in the room, such as an audience watching a broadcast. People in the room already have chat on the socket they hold.
func (*StreamClient) CreateRoom ¶
func (c *StreamClient) CreateRoom(ctx context.Context, params CreateRoomParams) (*RoomWithHostToken, error)
CreateRoom creates a room and fails with room-exists (409) if the id is taken. For "go live", prefer EnsureRoom: a host reconnecting after a dropped call hits the existing-room path, and this turns that into two round trips. Sent once, never retried: a lost reply would turn a success into a spurious room-exists (see the README, "Calls that are never repeated").
func (*StreamClient) CreateStream ¶
func (c *StreamClient) CreateStream(ctx context.Context, params CreateStreamParams) (*BroadcastStreamWithPublish, error)
CreateStream creates a stream. The reply's Publish.WHIPURL is complete: hand it to the encoder as it is. This is the only call that returns a publish token; reading or listing a stream never does. Sent once, never retried, for the reason CreateRoom is not.
func (*StreamClient) CreateStreamToken ¶
func (c *StreamClient) CreateStreamToken(ctx context.Context, params CreateStreamTokenParams) (*StreamToken, error)
CreateStreamToken mints a playback token for a viewer of a token-protected stream, or a fresh publish token for an encoder whose URL has expired. The reply carries the URLs the token belongs in.
func (*StreamClient) CreateToken ¶
func (c *StreamClient) CreateToken(ctx context.Context, params CreateTokenParams) (*RoomToken, error)
CreateToken authorises one of your users for a room. Your backend decides the role: it is signed into the token and the client cannot change it. Minting twice is harmless, so this is retried.
func (*StreamClient) DeleteMessage ¶
func (c *StreamClient) DeleteMessage(ctx context.Context, channel, messageID string) error
DeleteMessage removes a message. Everyone who could see it is told to drop it.
func (*StreamClient) DeleteRoom ¶
func (c *StreamClient) DeleteRoom(ctx context.Context, roomID string) error
DeleteRoom ends a room: everyone is disconnected and the room is gone. A retry that finds the room gone after a lost reply means the lost attempt ended it, and the call succeeds.
func (*StreamClient) DeleteStream ¶
func (c *StreamClient) DeleteStream(ctx context.Context, streamID string) error
DeleteStream deletes a stream. Its publish and playback URLs stop working at once.
func (*StreamClient) EnsureRoom ¶
func (c *StreamClient) EnsureRoom(ctx context.Context, params EnsureRoomParams) (*RoomWithHostToken, error)
EnsureRoom creates the room, or returns it with a fresh host token if it already exists: one round trip whether or not the host has been here before. Existed says which happened. Metadata is applied only on creation.
func (StreamClient) Format ¶
func (c StreamClient) Format(f fmt.State, verb rune)
Format prints the same redacted view under every fmt verb. fmt consults String only for %v, %s, %q, %x and %X; for a verb that has no meaning for a struct (%t, %c, %U, %f) it walks the fields instead and prints what it finds with %v, which, before this method and the closures in secrets, included the key.
func (*StreamClient) GetBalance ¶
func (c *StreamClient) GetBalance(ctx context.Context) (*ProjectBalance, error)
GetBalance is what the project can spend right now, and whether a newcomer would be admitted. Read only: recharges happen on the website, never through this API. Amounts are cu as decimal strings; use ParseCu for arithmetic.
func (*StreamClient) GetChatInfo ¶
GetChatInfo says how many identities are connected to a channel, in the room or only in its chat.
func (*StreamClient) GetRates ¶
func (c *StreamClient) GetRates(ctx context.Context) (*RtcRates, error)
GetRates is the public price list. It comes from the website API (Config.RatesBaseURL), where prices are set, rather than the controller, and carries no credential: it is public and on another host. Without RatesBaseURL it fails at once with rates-unconfigured and sends nothing.
func (*StreamClient) GetRoom ¶
GetRoom is the authoritative live view: the room, who is in it and what they publish. Poll it for a live count rather than adding up webhook joins and leaves.
func (*StreamClient) GetStream ¶
func (c *StreamClient) GetStream(ctx context.Context, streamID string) (*BroadcastStream, error)
GetStream reads a stream: its state, viewers and playback URLs.
func (*StreamClient) GetUsage ¶
func (c *StreamClient) GetUsage(ctx context.Context) (*UsageSummary, error)
GetUsage is the project's balance of minutes, and its usage for today, this month and lifetime.
func (*StreamClient) GetUsageSeries ¶
func (c *StreamClient) GetUsageSeries(ctx context.Context, days int) ([]DailyUsage, error)
GetUsageSeries is one entry per UTC day, oldest first, ending today. Days with no usage are present and zero, so a chart drawn from it has no gaps. days of 0 means the default, 30; the server caps it at 90.
func (StreamClient) GoString ¶
func (c StreamClient) GoString() string
GoString is String: %#v shows no more than %v does.
func (*StreamClient) ListChatMutes ¶
ListChatMutes lists the identities currently muted in a channel's chat.
func (*StreamClient) ListMessages ¶
func (c *StreamClient) ListMessages(ctx context.Context, channel string, opts *ListMessagesOptions) ([]ChatMessage, error)
ListMessages is the newest messages of a channel (50 by default, at most 100), oldest first, direct messages included: your backend owns the conversation and may need to moderate it. Pass the oldest id you hold as opts.Before to page back; opts may be nil.
func (*StreamClient) ListParticipants ¶
func (c *StreamClient) ListParticipants(ctx context.Context, roomID string) ([]Participant, error)
ListParticipants is a point-in-time read of who is in a room, for an admin screen or a reconciliation job. It is not a presence feed: for that, take the webhooks. An empty room is an empty, non-nil slice.
func (*StreamClient) ListRooms ¶
func (c *StreamClient) ListRooms(ctx context.Context) (*RoomList, error)
ListRooms lists every live room of the project.
func (*StreamClient) ListStreams ¶
func (c *StreamClient) ListStreams(ctx context.Context) ([]BroadcastStream, error)
ListStreams lists the project's streams.
func (StreamClient) LogValue ¶
func (c StreamClient) LogValue() slog.Value
LogValue gives log/slog the redacted view as a group.
func (StreamClient) MarshalJSON ¶
func (c StreamClient) MarshalJSON() ([]byte, error)
MarshalJSON serializes the same redacted view, so a client that ends up in a JSON log line or a debug dump gives nothing away.
func (*StreamClient) MuteChat ¶
func (c *StreamClient) MuteChat(ctx context.Context, channel, identity string) error
MuteChat silences an identity in a channel's chat. Held for the channel, not the connection, so reconnecting does not undo it. They can still read.
func (*StreamClient) SendMessage ¶
func (c *StreamClient) SendMessage(ctx context.Context, params SendMessageParams) (*ChatMessage, error)
SendMessage speaks from your backend: a system notice, a gift announcement, a warning. It skips the per-socket rate limit and the mute list, both of which exist to restrain clients. Sent once, never retried: a lost reply would become the same message twice.
func (StreamClient) String ¶
func (c StreamClient) String() string
String prints the client with URL credentials as [redacted] and no key: what %v and %+v show. Defined on the value, so a copied or dereferenced client prints the same way.
func (*StreamClient) UnmuteChat ¶
func (c *StreamClient) UnmuteChat(ctx context.Context, channel, identity string) error
UnmuteChat lifts a mute.
func (*StreamClient) UpdateStream ¶
func (c *StreamClient) UpdateStream(ctx context.Context, streamID string, patch UpdateStreamParams) (*BroadcastStream, error)
UpdateStream changes who may watch, or the stream's metadata.
type StreamEvent ¶
type StreamEvent struct {
// ID is stable across redeliveries: deduplicate on it.
ID string `json:"id"`
Type string `json:"type"`
TenantID string `json:"tenantId"`
RoomID string `json:"roomId,omitempty"`
// CreatedAt is unix milliseconds.
CreatedAt int64 `json:"createdAt"`
Data json.RawMessage `json:"data,omitempty"`
}
StreamEvent is one webhook delivery. Data's shape varies by Type; see https://voltkit.in/docs/webhooks.
func ParseWebhook ¶
func ParseWebhook(body []byte, header string, secrets []string, opts *VerifyOptions) (*StreamEvent, error)
ParseWebhook verifies a delivery and decodes it in one step, so a handler cannot forget to check. A delivery that does not verify fails with an *Error of code bad-signature and status 401: answer 401, and VoltKit retries it. A mistake in the setup (no secret, a negative Tolerance, a verified body that is not JSON) is a plain error, never an *Error.
type StreamToken ¶
type StreamToken struct {
StreamID string `json:"streamId"`
Action StreamAction `json:"action"`
Token string `json:"token"`
// ExpiresAt is unix seconds.
ExpiresAt int64 `json:"expiresAt"`
WHIPURL string `json:"whipUrl,omitempty"`
LLHLS string `json:"llhls,omitempty"`
WebRTC string `json:"webrtc,omitempty"`
}
StreamToken is CreateStreamToken's answer: WHIPURL for publish, LLHLS and WebRTC for play.
type UpdateStreamParams ¶
type UpdateStreamParams struct {
Playback PlaybackPolicy `json:"playback,omitempty"`
Metadata any `json:"metadata,omitempty"`
}
UpdateStreamParams is UpdateStream's patch: who may watch, or the metadata.
type Usage ¶
type Usage struct {
ParticipantSeconds int64 `json:"participantSeconds"`
PublisherSeconds int64 `json:"publisherSeconds"`
RoomSeconds int64 `json:"roomSeconds"`
RoomsCreated int64 `json:"roomsCreated"`
PeakParticipants int64 `json:"peakParticipants"`
ChatMessages int64 `json:"chatMessages"`
ChatConnectionSeconds int64 `json:"chatConnectionSeconds"`
BroadcastSeconds int64 `json:"broadcastSeconds"`
BroadcastViewerSeconds int64 `json:"broadcastViewerSeconds"`
StreamsStarted int64 `json:"streamsStarted"`
// BillableSeconds is everything that counts against an allowance: room
// plus broadcast seconds. FreeSeconds is the part a free allowance
// covered. Both are absent on older controllers.
BillableSeconds int64 `json:"billableSeconds,omitempty"`
FreeSeconds int64 `json:"freeSeconds,omitempty"`
}
Usage is one period's counters.
type UsageSummary ¶
type UsageSummary struct {
TenantID string `json:"tenantId"`
// Plan is metered or unlimited; Status active or suspended.
Plan string `json:"plan"`
Status string `json:"status"`
MinutesLimit int64 `json:"minutesLimit"`
UsedMinutes int64 `json:"usedMinutes"`
// MinutesRemaining is -1 on the unlimited plan.
MinutesRemaining int64 `json:"minutesRemaining"`
Today Usage `json:"today"`
Month Usage `json:"month"`
Lifetime Usage `json:"lifetime"`
// Allowance is free allowances, paid balance and caps, right now.
Allowance *Allowance `json:"allowance,omitempty"`
}
UsageSummary is GetUsage's answer.
type VerifyOptions ¶
type VerifyOptions struct {
// Tolerance is the freshness window, inclusive at the boundary. nil means
// DefaultTolerance; Ptr(0) accepts only the current second; a negative
// duration is a mistake in your setup (it would refuse every delivery).
Tolerance *time.Duration
// Now is the clock; nil means time.Now. For tests.
Now func() time.Time
}
VerifyOptions tunes VerifyWebhook and ParseWebhook. A nil *VerifyOptions is the defaults.
type WalletFunds ¶
type WalletFunds struct {
// SpendOrder is the scopes in the order they are spent:
// project:<tenantId>, product:rtc, unallocated.
SpendOrder []string `json:"spendOrder"`
Scopes map[string]WalletScopeAmount `json:"scopes"`
PaidCu string `json:"paidCu"`
PromoCu string `json:"promoCu"`
// AvailableCu is PaidCu + PromoCu: everything this project may spend from
// the wallet now.
AvailableCu string `json:"availableCu"`
// AvailableCredits is AvailableCu in credits, at most four decimals,
// rounded down ("31", "10.62"). For display only.
AvailableCredits string `json:"availableCredits"`
}
WalletFunds is the wallet credits this project may spend. Only its own scopes are listed: another project's allocation is never shown.
type WalletLowState ¶
type WalletLowState struct {
// Active: the project's rooms are being warned now.
Active bool `json:"active"`
// GraceSeconds: the warning starts when what is left covers less than
// this many seconds at the current burn.
GraceSeconds int64 `json:"graceSeconds"`
}
WalletLowState is the low-funds warning rooms get before their funds run out.
type WalletScopeAmount ¶
WalletScopeAmount is the credits in one wallet scope, by origin. Promo is spent before paid.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
tokenserver
command
Command tokenserver is a minimal token endpoint on net/http: a client asks to join a room, this server decides the role and mints a short-lived room token with the VoltKit RTC server SDK.
|
Command tokenserver is a minimal token endpoint on net/http: a client asks to join a room, this server decides the role and mints a short-lived room token with the VoltKit RTC server SDK. |
|
webhookreceiver
command
Command webhookreceiver receives VoltKit RTC webhooks on net/http: it reads the raw body, verifies the signature with ParseWebhook against one or more secrets (so a rotation drops no delivery), answers 401 when a delivery does not verify, answers 200 before doing any work, and deduplicates on the event id, because delivery is at least once.
|
Command webhookreceiver receives VoltKit RTC webhooks on net/http: it reads the raw body, verifies the signature with ParseWebhook against one or more secrets (so a rotation drops no delivery), answers 401 when a delivery does not verify, answers 200 before doing any work, and deduplicates on the event id, because delivery is at least once. |