## Problem and outcome / 问题与结果 This phase adds lightweight room interaction without making media depend on chat, and adds Windows source-audio exclusion and optional SFU-only hosting at their existing owners. It also corrects confirmed encoder/capture ownership and native color-conversion defects. Host microphone commentary remains on the shared media path; room voice and 4K are outside this phase. ## Release notes ### 中文 这是一次功能更新:Piik 加入了文字聊天与表情互动,并完善了 Windows 声音采集、画面颜色和自部署选项。 #### 新功能 - **边看边聊。** 在可移动、可缩放的聊天窗口中发送消息,或开启弹幕。停止分享后,房间中的朋友仍可继续聊天。 - **表情互动。** 点击沙发上的人物,给自己发表情,或把番茄等表情送给朋友;各端按同一事件显示动画。 - **排除应用声音。** Windows App 分享整个屏幕时,可排除一个应用及其子进程的声音,适合同时使用语音软件。所选应用退出后,来源声音停止,避免恢复为未过滤的系统声音。 - **可选的纯 SFU 模式。** 自部署管理员可让共享画面和声音统一通过媒体服务器转发。默认仍为优先 P2P;App 公网邀请保持纯 P2P。 - **Windows HDR 转 SDR。** 原生采集与缩略图自动将 HDR 来源转换为 SDR,沿用现有观看链路。 #### 修复与改进 - 修复原生颜色转换的范围与矩阵处理,避免已复现的暗部丢失、高光裁切和颜色偏差。 - 修复共享编码器切换时使用过期需求或过期帧信息的问题,保护正在观看的画面;修复换源后旧采集事件可能影响新来源的问题。 - 统一新控件、浮窗与来源选择文案,补充中英文使用说明。 #### 使用与升级 聊天与表情需要新版站点页面和 Server;声音排除需要新版页面与支持此能力的 Windows App。聊天只保留本页最近收到的消息,刷新后清空,不补发历史,且不提供端到端加密。 HDR 转换输出为 SDR,不代表端到端 HDR 或多声道支持。既有房主麦克风与立体声能力保留。 ### English This feature update brings text chat and reactions to Piik, alongside improvements to Windows audio capture, picture colors and self-hosting options. #### New features - **Chat while watching.** Send messages in a movable, resizable window, or enable on-screen chat. Friends can keep chatting after the host stops sharing. - **Reactions.** Select a person on the sofa to react above yourself or send a tomato and other reactions to a friend. Participants animate the same shared event. - **Exclude application audio.** When sharing a whole screen with Windows App, exclude one application and its child processes, useful when using a separate voice app. Source audio stops if the selected app exits, preventing a return to unfiltered system sound. - **Optional SFU-only hosting.** Site operators can require shared video and audio to pass through the media server. P2P-first remains the default; App Public invite remains P2P-only. - **Windows HDR-to-SDR conversion.** Native capture and source thumbnails automatically convert HDR sources to SDR for the existing viewing path. #### Fixes and improvements - Correct native color-conversion ranges and matrices, addressing reproduced crushed shadows, clipped highlights and color shifts. - Correct stale demand and frame evidence during shared-encoder handoffs to protect the current picture. Prevent events from a replaced capture from affecting its successor. - Align new controls, floating panels and source-picker copy, with updated Chinese and English guides. #### Using this update Chat and reactions require updated site pages and Server. Audio exclusion requires updated pages and a Windows App that supports the capability. Chat retains only recent messages received by the current page, clears on refresh and does not replay history. It is not end-to-end encrypted. HDR conversion produces SDR; it does not add end-to-end HDR or surround sound. Existing host microphone commentary and stereo support remain available. ## Verification / 验证 - Web type checks, 78 test files / 1275 tests and production build: PASS. - Go core tests, formatting/vet and target builds: PASS; Darwin cgo remains platform-limited on this Windows runner. - Native build/check/color probes and synthetic HDR-to-SDR H.264/VP8 browser reception: PASS. - Access/privacy gate, Native H.264 SFU-only media gate and local Auto/H.264 Host gate: PASS. - Three real production pages: chat/reactions, stop/resume, idle-room join, host departure/return, offline draft recovery and invitation revocation: PASS. - Synthetic parent/child process-audio exclusion and target lifetime: PASS; no raw audio retained. - Windows candidate archive extraction and packaged Local startup/shutdown: PASS. - Documentation and website build: PASS. - [Candidate CI](https://github.com/TNTcraftHIM/Piik/actions/runs/36873766646) at `f26cd208d03bc0dcbe353f8cac217f993216b0a3`: Go core and race checks, Web tests, Server/container checks and Windows/macOS/Linux App packaging all PASS. Publication was skipped as intended. The owner accepted the UI/model and bounded local walkthrough for phase closure. Physical HDR/mixed displays, mobile keyboard, real voice applications across output devices and macOS/Linux capture remain unverified. Existing field reports without matched evidence are not claimed resolved. The owner authorized v1.7.0 publication on 2026-10-02. The final documentation-only commit records that authorization; product code matches the verified candidate above. ## Impact / 影响 Authorized version: **v1.7.0**, matching the local release planner. Current wire contracts remain Browser/server 23, Native control 9 and capture 7. Optional capabilities gate new functionality; existing room/media authority remains owned by the existing services. No new ports, message persistence, room voice or automatic room creation on page visit. Updated README, guides, standards, research evidence and TODO stay with the candidate phase.
25 KiB
ADR-0005: Automatic Hybrid Media Routing
- Status: Accepted
- Date: 2026-08-20
- Last updated: 2026-08-30
The current candidate replaces this ADR's external LiveKit room/token lifecycle with admitted in-process SFU resources under ADR-0013. Exact physical subscription closure now releases its reservation. The graph, capacity and serial-operation decisions below remain; external-service details describe the earlier implementation and its production history.
Context
Direct Browser WebRTC gives Piik its desired latency and distributed cost, but one Host cannot fan out to every Viewer and some endpoint pairs cannot establish usable direct media. The product needs automatic peer distribution and bounded server fallback without exposing topology choices to users or turning every room into an SFU conference.
This decision owns the route controller and resource model. The observable product contract is routing and transport; physical evidence is in the routing research documents.
Decision
Hosted and Local always use this controller. The former rollout switch and separate Host-star authority are removed; SFU availability is still selected by configuration and the room's privacy policy, not by another routing mode.
The optional Hosted SFU_ONLY policy restricts this controller's candidate
eligibility to SFU. It reuses the publication/subscription ledger, deadlines,
first-frame commit and recovery. It does not create another graph or scheduler.
It disables Peer relay and background Peer work; admission failure does not
relax the restriction. The service rejects an explicit peer-only request instead
of treating it as permission to forward media. Default P2P-first and App Local
behavior are unchanged.
One Capacity Rule
Every non-server endpoint uses one server-authoritative steady outbound media-
copy cap C: default 2, configurable only as 1, 2, or 3.
- one ordinary peer child consumes one copy;
- the Host's single SFU publication consumes one copy;
- upstream receive consumes none;
- role, Browser, UA, device, visibility, codec, and room Viewer limit do not create another tier;
- a client may advertise only
0..Ccurrently available copies, producing the controller's effective capacity.
That advertised capacity belongs to the connected endpoint, not one sharing generation. Replacing the room graph retains it; Viewer departure or room deletion clears it.
One transition-overlap reservation may temporarily raise a producer to
min(C + 1, 3). Commit or abort releases it. A fourth endpoint copy is never
authorized. SFU ingress and subscription egress are independent server resources
and are not inferred from endpoint child counts.
One Graph And Reconcile Loop
One room controller owns:
- one committed, versioned, acyclic, source-reachable graph;
- one event-driven reconcile loop;
- at most one room-serial child operation; and
- participant sessions, effective capacity, current failure facts, and the Host's optional SFU publication.
The Host has no upstream. Each Viewer has zero or one active upstream. Healthy unaffected edges remain sticky. Join/waiting, failed-edge repair, relay-ingress repair with subtree retention, confirmed departure, and capacity reduction are inputs to the same loop rather than separate routing systems.
Disconnected childless participants are removed naturally. A disconnected relay or a relay with reduced capacity accepts no new children; only its deterministic overflow children are reassigned. A relay whose ingress fails is itself reparented while its descendants remain attached.
Deterministic Candidates
Each child operation owns one immutable purpose, one candidate list and cursor, one current candidate and reservations, one route-demand owner, one fact version, and one total deadline. Availability work preempts a background operation by aborting it and creating the required operation; the old operation is never relabelled. Candidate creation first rejects stale sessions, unreachable sources, cycles, insufficient steady/overlap capacity, tuples already consumed by that operation, and unavailable SFU resources.
Eligible Peer parents are ordered by:
- shallowest resulting depth;
- greatest remaining steady capacity;
- a stable child-parent rank;
- join order; and
- peer identity.
There is no independent depth cap. Room admission and acyclicity bound the graph; shallowest-first is a preference, not a periodic balancing mandate. IP address, geography, NAT guess, UA, and scalar quality score do not select a parent.
One structural exception is event-driven Host-root capacity convergence. After a new Host-root edge commits, if that root has zero direct children and another Host root has at least two, one deterministic direct child may prepare against the new root. A fresh healthy candidate sender proof is required before make-before-break commit. Success moves one branch toward even fanout; failure consumes that one-shot intent. No timer, continuous rebalance or general load score is introduced.
A Peer opportunity is fenced by its exact parent and endpoint sessions plus endpoint-transition strength. With NAT traversal disabled, one failed attempt consumes it. With traversal enabled, availability acquisition and direct continuation may create at most four candidate connections for that opportunity. Only an admitted candidate creation spends an attempt; duplicate failure and timeout messages cannot spend it again. A preparation rejection that cannot create a connection remains terminal. A new session or strictly stronger transition reopens the opportunity; worse transitions use only its remaining budget. Quality and root-convergence experiments retain their existing one-shot consumption rules. SFU create, reuse, and replace are resource actions for one logical SFU opportunity keyed by the child and Host sessions; the publication generation remains a physical fence, not retry authority. Controller-owned publication creation, replacement, or teardown cannot make the same failed opportunity new. Only a relevant session, an external SFU-resource wake, or a strictly better transition reopens it. Candidate removal, worsening, reordering, and unrelated facts do not retry it. An external SFU-resource wake may reopen SFU opportunities but never Peer opportunities. There is no persistent parent blacklist.
Prepare, Commit, And Rollback
Every operation is fenced by room and endpoint sessions, share generation, base and pending route revisions, and one candidate connection identity. SFU routes also carry a publication generation. Duplicate current messages are idempotent; stale asynchronous results fail closed.
The old committed route remains authoritative while the candidate prepares. Parent and child prepare the same server-issued connection identity. Standard ICE connected is progress only. For availability work, the exact candidate child decoding its first new video frame is the application commit proof.
For availability work, the media-ready call commits typed endpoint/SFU admission before graph promotion. A quality trial treats first decoded frame as readiness and requires the native edge proof below. Success broadcasts the candidate revision. Failure, timeout, pause, or stale authority destroys the candidate, releases reservations idempotently, and broadcasts a strictly newer rollback revision before another prepare. Clients never infer rollback from silence or an older revision.
Make-before-break is used only when all required reservations exist. Availability repair may use one explicitly planned bounded-gap retirement after server resources are preflighted; it keeps the same deadline, replans the untried suffix, and retains one restore tuple. Background convergence never interrupts healthy media for a bounded-gap candidate.
Availability Order
Initial acquisition gives direct Peer candidates one bounded foreground window. Hard failure may advance a different direct candidate inside that same window, but the window and total operation deadline never reset. Exact transport- connected progress may retain the current direct candidate until the deadline; it still cannot commit without decoded media.
When the direct foreground window ends, a usable SFU candidate may provide media before unresolved direct candidates. After an SFU route commits, finite remaining Peer candidates converge behind working media, one at a time and round-robin across SFU Viewers. New join or repair work preempts this background convergence. Exhausting direct candidates simply keeps the working SFU route.
When NAT acquisition exhausts one operation but retains candidate budget, the same serial scheduler starts the next operation after rollback and resource release. Each operation retains its full original deadline; neither a failed candidate nor a retry extends a running operation. Untried parents precede repeat attempts, and waiting Viewers with fewer started Peer attempts go first. The foreground SFU window is unchanged. SFU commitment carries the same remaining Peer budget into the existing round-robin direct continuations, not another retry ledger. No usable SFU route is interrupted by these attempts. This permits longer total acquisition while keeping every operation bounded and requiring no polling or independent retry timer. The prepare message reports the actual candidate's connection attempt, not inferred UI progress. Four-attempt progress is sent only to recipients opting in after HTTP capability discovery; older pages receive the same candidate without the optional progress field. This reader support does not change routing authority or its attempt budget.
The five-second no-transport-progress window advances a silent Peer only while another bounded candidate or SFU fallback remains. It is a route scheduling boundary, not evidence that Browser ICE has reached a terminal state. The final or only Peer candidate remains until the unchanged total operation deadline; exact transport progress also retains it until media proof or that deadline. After SFU commits, each one-candidate direct continuation therefore uses its full background operation while working media stays authoritative. The pending Viewer transport has no second initial-connection or transient- disconnect deadline. It reports an actual Browser terminal failure immediately; only after commit does the active Viewer edge take ownership of its existing reconnect timers.
If the Host is full and no publication exists, one bounded SFU-bootstrap intent owns the original waiting demand and a finite cursor of eligible Host-direct children. A carrier must create the publication through a normal overlap; it may not cut a healthy branch first. Carrier failure keeps that branch and advances the cursor. Success lets the original demand try the publication before its remaining direct candidates. The intent owns no second graph, concurrent child operation, or timer.
SFU Resource Ownership
There is at most one Host publication per room/share generation. Every SFU Viewer owns one subscription to it; Viewers never publish a second SFU stream. The Host page owns capture tracks, so SFU teardown or recovery must not stop capture.
The single-process server has one injected SFU admission owner. Its ingress ceiling derives from the fixed room-code space, and egress derives from that space times the per-room Viewer limit; neither has an independent tuning key. Exact publication and subscription handles remain charged while reserved, committed, or draining. Reactivating the same handle does not double-charge it.
Grant revocation, Viewer leave, and SFU-to-P2P replacement remove the exact LiveKit Viewer participant and confirm its absence. This control-plane drain is not token revocation or ledger release: an already issued self-hosted LiveKit token remains usable until expiry, so that subscription's egress stays charged until the whole publication generation is deleted and confirmed absent.
The application explicitly creates a managed LiveKit room before issuing media tokens. Replacement, abort, timeout, confirmed media-participant loss, share rollover, stop, and room deletion transfer the resources they own into the same typed drain path. Only successful room deletion plus an absence readback releases that generation, so a stale token cannot recreate off-ledger media. The deployment must dedicate the LiveKit namespace to Piik and complete startup ownership/cleanup before accepting traffic; self-hosting operations owns that procedure.
Retiring a publication generation releases every physical SFU handle. An old SFU root that still carries Peer descendants remains only as an inactive graph anchor until those descendants are reassigned; a childless old root is removed immediately. This preserves one source-reachable transition graph without keeping old media active.
Only confirmed LiveKit media-participant absence retires an abandoned Host publication. A Host signaling disconnect keeps the generation charged while the exact LiveKit Host participant remains; one bounded control-plane check releases it only after that participant disappears.
A room denied by deployment-wide SFU admission enters one bounded waiter set. Actual SFU usage decrease drains that set once, advances each surviving room's external fact, and schedules ordinary reconciliation. There is no periodic capacity poll or second resource-specific controller.
Ordinary Peers use deployment STUN only. Browser SFU PeerConnections use an empty external ICE-server list while retaining LiveKit-signaled UDP candidates. Piik configures no TURN, ICE/TCP, media TCP, or TLS-relayed media. A future strict-firewall transport must be accepted as a LiveKit-internal capability, not as another application route candidate.
Recovery, Pause, And Quality
WebRTC and LiveKit first own ICE/consent and transient reconnect on the current route. Only framework recovery exhaustion, hard failure, confirmed departure, or capacity invalidation enters route reconciliation. Decoded-frame silence alone cannot locate a route failure: a live source can stop repainting, and missing decoded frames do not distinguish source, decoder and transport faults. Manual reconnect remains on the exact current route. The retained connection and a same-parent candidate keep separate signaling authority. A replacement offer from the retained parent supersedes optional preparation for that child at adoption, including an SFU bootstrap carrier's trial. This retires its reservation and fences late candidate signaling; required availability work keeps priority and candidate budgets do not reopen.
Authoritative Host pause aborts the pending operation and reservations, preserves the committed graph, and leaves new Viewers waiting. Resume starts reconciliation from the current graph. Missing or suspended observations do not create route-failure authority.
Confirmed departure ends the participant's physical media authority before descendants finish recovery, including during Host pause. A departed relay may remain as an inactive graph anchor only. Publishing that retirement supersedes any pending candidate through the same operation owner and revision sequence; its late readiness cannot restore the retired route.
Native-edge convergence runs inside the same graph, reconcile loop, and room-serial child operation when its per-share gate is enabled. Availability first gets every Viewer usable media; quality work runs only while no join, failure, pause, identity, departure, capacity, or SFU-resource work needs that operation. With the gate disabled, quality evidence remains observation-only.
Each exact sender has one native observation: unknown, clear, or limited.
The wire retains healthy and degraded as the internal categorical names, but
they mean only one complete same-identity qualityLimitationReason delta in
none, or one in bandwidth/cpu. This observation can trigger one bounded
experiment; it does not locate the physical bottleneck or prove end-to-end
quality. other, missing, reset, hidden, or stale evidence is unknown. SFU
publication and subscription state remains owned by LiveKit plus the exact
Viewer's continuing decoded progress. Piik does not combine loss, RTT,
jitter, bitrate, FPS, resolution, or freezes into a weighted route score.
Routing degradation requires three consecutive complete degraded deltas for the same exact sender identity. Unknown, stale, reset, source change, or identity change clears that run. A newly committed availability or direct-convergence edge may establish a new run without first reporting healthy. Quality- and root-convergence commits remain disarmed until the new active identity reports a fresh healthy delta; an explicit sender reset clears the old latch and starts a new sequence. A single native limitation interval remains diagnostic. Once a Peer quality experiment starts, a later unknown current-sender window remains inconclusive inside that bounded operation; it does not cancel the candidate. Fresh healthy evidence, an explicit reset, or an edge/sender identity change still ends the experiment. When the current sender remains persistently limited, the controller considers the shallowest affected child first and uses the existing deterministic candidate filters, ordering, cursor, reservations, and total deadline. Candidate Peer parents need a usable source path and available steady capacity; clear native paths are tried before the remaining deterministic Peer candidates but are not a hard eligibility tier. Only one candidate runs at a time and the old route keeps playing. The same Viewer compares fresh, overlapping old and candidate receive windows. A P2P candidate commits only after first decoded frame, one fresh healthy delta from its exact candidate sender, and three consecutive windows with no freeze/pause and no lower pixel area or rounded FPS. Strict improvement during overlap is not required because shared source adaptation can hide recovery until the old sender closes. An otherwise complete current-route window with zero decoded frames is compared as zero delivered pixels, FPS, and bitrate. It can approve only a temporally overlapping candidate window that actually decoded video and satisfies the same freeze, pause, and P2P/SFU partial-order checks. If both routes decode nothing, the source may be stalled, so the window remains unknown and cannot justify a move. Bitrate does not rank P2P candidates because codec and content phase make it non-monotonic. Three consecutive comparable regressing windows reject the candidate and advance the existing cursor without resetting the deadline. Unknown candidate-sender evidence waits within the existing deadline; a stale relative proof rejects that candidate and advances the existing cursor, while a degraded candidate sender cannot commit. None creates a score. Old-edge recovery, authority change, or deadline ends the experiment. A failed candidate opportunity is consumed for that exact current sender sequence; an unrelated route revision cannot reopen it. A newly eligible parent, a less disruptive capacity transition, healthy recovery, or an edge/sender identity change is a new opportunity. A successful commit clears and rebaselines the affected subtree. The same rule supplies both active parent change and relay abdication. A bad relay ingress reparents that relay while retaining its subtree. A bad exact parent-to-child sender moves only that child. If several senders on one parent are degraded, one child moves and all remaining native states are observed again; relief can cancel the remaining work without a parent score or explicit capacity penalty.
If a persistently limited edge has no eligible different Peer parent, the same quality operation appends one same-parent regeneration candidate at the end of its existing cursor. It creates a new connection and sender generation while retaining the old edge until first-frame and comparative proof; the candidate uses the existing steady or one-slot overlap reservation. This is connection regeneration, not an in-place sender reset and not a new operation reason.
The Host capture/source is a separate fact. A stalled source cannot be repaired
by topology. One limited Host-origin edge may try the same measured Peer move. SFU
becomes a quality suffix only when every current Host-origin Peer edge, with a
minimum of two, independently remains persistently degraded. This bounded
condition identifies possible Host fanout pressure without making SFU an
ordinary candidate. Healthy Peer candidates still run first; if none succeeds,
the same operation may create or reuse the single Host publication. The exact
Host-to-SFU publication generation must supply three consecutive healthy sender
windows before commit. The old Peer route and candidate SFU subscription overlap
while the same Viewer gathers three complete receiver windows. Each candidate
window must decode without a freeze or pause and must
not regress delivered pixel area, rounded frame rate, or bitrate against a fresh
old-route window. This strict partial order has no weights or tradeoff score; an
unknown, incomparable, or worse candidate never commits. One Viewer moves, then
all edge state is re-observed before another Host-relief move. SFU availability
fallback remains independent. An SFU-fed Viewer remains an ordinary Peer parent,
allowing SFU -> Viewer -> Peer distribution without turning every Viewer into
an SFU subscription. The shared publication is only resource topology: its
Host-to-SFU ingress proof and each exact SFU-to-Viewer subscription proof remain
separate. A successful Viewer canary cannot authorize any other Viewer, and a
new publication generation invalidates all prior ingress authority.
After a quality- or root-convergence reparent commit, the new active identity starts from unknown and must establish a fresh healthy delta before another degradation can trigger a move. A same-parent regeneration commit instead records the exact new sender identity as a same-edge regeneration latch. A healthy sample or an identity/session change clears that latch; while it is set, only another same-edge regeneration is suppressed, while different-parent candidates remain eligible. There is no periodic wake, weighted prediction, global optimizer, persistent parent blacklist, custom congestion controller, or manual SFU layer selection. The result converges gradually to a local stable topology: no current degraded edge has a proved healthy candidate under the current graph and resource facts. It does not promise a static mathematical global optimum.
Two pre-share gates constrain this same controller. Peer-only policy removes every SFU tuple and bootstrap path before reservation, so exhaustion keeps the existing explicit failure. Topology convergence is enabled by default and may be disabled before sharing; when disabled, native evidence never creates quality work. Both gates are bound to share generation and cannot change while sharing.
Consequences
Positive:
- one capacity rule replaces role- and Browser-specific tiers;
- one graph and operation make route, identity, and resource ownership explicit;
- media stays distributed while SFU cost and stale-token behavior remain bounded;
- unrelated healthy branches survive local join, failure, and repair.
Negative:
- the serial operation can create queue-tail latency during bursts;
- Browser relay creates per-child decode/re-encode cost;
- make-before-break requires temporary reservations and some availability repair may require a bounded gap; and
- active quality work is local and event-driven, so it does not promise a globally optimal tree or react when native evidence remains unknown; and
- UDP-only media ends in explicit failure on fully blocked networks.
Related Decisions And Evidence
- ADR-0001: initial P2P baseline.
- ADR-0004: Browser relay evidence.
- ADR-0007: framework-owned representation adaptation.
- Low-server-cost routes and peer-assisted media: measurements and rejected alternatives.
- Verification status: current open physical gates.