This release adds microphone processing controls and local chat controls, expands platform packages, and synchronizes the website and guides. It also fixes native H.264 input/format handling, signaling recovery, fresh STUN discovery, and UDP ownership during overlapping connections. This is **v1.8.0**. Browser/Server v23, Native control v9 and capture v7 remain unchanged, with the existing 1440p ceiling. Additional resolutions, capability negotiation and Magicsock are separate follow-up work. ## Validation - Full local Web check: 79 files / 1,317 tests, TypeScript and production build. - Full Go checks, platform builds and Windows native capture regressions/probe. - Native H.264 Host lifecycle, Browser-to-App fanout and App-exit recovery, and Native H.264 through embedded SFU: all passed with owned process/port/profile cleanup. - Website build, documentation and repository hygiene passed. Existing UI acceptance covers narrow screens, both languages/themes, architecture downloads and all 23 film checks; final packaging and Go race checks run on the merged revision in CI. - Physical macOS/Linux capture, representative public networks and unmatched field reports retain their documented evidence limits. No claim that every blur/disconnection report is resolved. ## Release notes ### 中文 这是一次功能与兼容性更新:麦克风和聊天增加了实用的小选项,App 与 Server 支持更多设备,同时修复了原生分享和连接恢复中的若干问题。现有 1.x 协议保持不变。 #### 新增与改进 - 浏览器麦克风新增「降噪与回声消除」开关,默认开启回声消除、降噪和自动增益;分享音乐或使用虚拟音频输入时,可按需关闭。 - 可调整弹幕字号和透明度,并将当前保留的聊天记录导出为 TXT。当前会话最多保留 1,000 条文字消息,刷新页面后清空;需要保存时请先导出。 - 新增 Intel Mac、Linux ARM64 App,以及 Linux ARM64 Server 和容器镜像。官网下载区可直接选择对应架构,并显示最新版本和发布时间。 - 更新官网演示、README 截图和中英文教程,明确浏览器分享也可选择窗口或屏幕;开始分享前显示房间号占位,保持底栏控件位置稳定。 - 官网新增直达问题排查的入口,补充浏览器 WebRTC 限制、校园网和 Windows 显卡相关的排查步骤。 - App 模式选择页显示免费开源说明、作者与贡献者署名,以及官方网站和源码链接。 #### 修复 - 修正部分 H.264 原生分享的格式兼容性校验,以及 Windows 编码器输入长度和输出缓冲区对齐问题。 - 清理已失效的重连任务和 Native 接收信令,避免旧连接继续影响当前会话。 - 修复 App 重连时可能复用过期公网地址的问题;重新探测使用当前地址,并处理探测丢包和过期响应,保留其他正常连接。 - 修复 App 新旧媒体连接重叠时共用端口造成的冲突,避免准备或撤销新连接打断仍在使用的画面;关闭连接时及时取消未完成的端口映射请求。 - 无站点密码时,连接恢复不再误引导用户输入站点密码。 - 房间失效或会话终止时,开播前后的原因提示统一显示在电视状态图标中,避免额外提示条。 - 修正 Linux 原生采集的时间戳与关键帧时间基准。感谢 Pedro Antonio 在 [#449](https://github.com/TNTcraftHIM/Piik/pull/449) 的贡献。 #### 使用说明 下载对应平台的完整压缩包,退出旧 App 后解压使用。Windows 继续提供 x64;macOS 提供 Apple 芯片与 Intel 版本,原生采集需要 macOS 13 或更新版本。Linux 提供 x64 与 ARM64,原生采集还需安装[系统组件](https://github.com/TNTcraftHIM/Piik/blob/main/native/capture/linux/README.md)。macOS 和 Linux 的实机覆盖仍有限,欢迎反馈。 ### English This update adds useful microphone and chat controls, brings Piik to more devices, and fixes several native-sharing and connection-recovery issues. The existing 1.x protocols remain unchanged. #### New and improved - Browser microphones now have a “Noise & echo reduction” toggle. Echo cancellation, noise suppression and automatic gain control are on by default; turn them off when appropriate for music or virtual audio inputs. - Adjust chat-overlay text size and opacity, and export retained messages as TXT. The current session keeps up to 1,000 text messages and clears on page refresh; export first if you want to keep them. - Added Intel Mac and Linux ARM64 App packages, plus Linux ARM64 Server and container images. The website provides architecture selectors and shows the latest release and publication date. - Updated the website demonstration, README screenshots and bilingual guides, clarifying that browser capture can also select windows or screens. A room-code placeholder keeps the bottom bar stable before sharing starts. - Added direct website links to troubleshooting, with steps for browser WebRTC restrictions, campus networks and Windows graphics issues. - The App mode-selection page now shows its free and open-source status, author and contributor credit, and links to the official website and source. #### Fixes - Corrected H.264 format compatibility checks and Windows encoder input-length and output-buffer alignment handling. - Retired stale reconnect tasks and Native receive signals so they cannot continue affecting the current session. - Fixed App reconnection reusing an expired public address. Fresh discovery handles lost probes and stale responses while preserving other working connections. - Fixed UDP socket conflicts between overlapping App media connections so preparing or canceling a replacement preserves the active picture. Closing a connection now cancels unfinished gateway mapping requests. - Connection recovery on passwordless sites no longer incorrectly asks for a site passphrase. - Expired-room and session-termination reasons now use the same TV status indicator before and during sharing, without an extra notice strip. - Corrected Linux native capture timestamps and keyframe time bases. Thanks to Pedro Antonio for [#449](https://github.com/TNTcraftHIM/Piik/pull/449). #### Updating Download the complete archive for your platform, quit the old App, and extract the new package. Windows remains x64-only. macOS packages support Apple silicon and Intel; native capture requires macOS 13 or newer. Linux supports x64 and ARM64 and requires additional [system components](https://github.com/TNTcraftHIM/Piik/blob/main/native/capture/linux/README.md) for native capture. Physical-device coverage on macOS and Linux remains limited; feedback is welcome. ## Attribution Co-authored-by: Pedro Antonio <wrenchwastaken@gmail.com>
16 KiB
ADR-0010: Cross-Platform App Runtime
- Status: accepted capability-provider architecture and native endpoint media boundary
- Date: 2026-09-05
- Superseded in part: items 1, 5, 7, 12 (its supervisor sentence), 13, 14, and the local-package consequence were amended in place by ADR-0012, which replaced the TypeScript server with one shared Go core.
- ADR-0013 supersedes item 8's single-output, Pion-GCC-observation-only and Browser-mediated LiveKit publisher details for the current candidate. The shared encoded-media adapter and direct Native SFU publisher preserve this ADR's participant and capability boundaries. The independent bounded control-session contract remains current; historical acceptance below does not validate the new media implementation.
Context
The Browser is the current Piik interaction surface, but it cannot own native capture, hardware encode, or reusable native sockets. A self-contained package must add those capabilities without creating another room model, signaling protocol, route controller, or UI.
Existing Piik room, admission, signaling, and routing behavior already has one TypeScript implementation. Rewriting that behavior in the Helper would make Hosted and Local deployments diverge without improving the media path.
Room authority and local media capability are independent choices. A room may come from Local, a temporary public link, or a configured Site while each participant independently uses Browser media or available App capability. Binding these choices into exclusive App modes prevents mixed Browser/Native topologies and makes a saved Site unavailable while another room source runs.
Decision
- One shared core is the sole owner of HTTP, room authority, admission, signaling, and routing in every deployment. The local package runs that same core and the same built Browser assets with local configuration; no deployment gets a second product core. Its implementation is owned by ADR-0012.
- The packaged product is Piik App. Its Go process is the cross-platform
entry and native capability provider. It starts
/healthand one/controlendpoint on IPv4 loopback within ports39721through39730before room- source selection and retains them until the process exits. - Discovery returns a per-process
instanceToken. The WebSocket subprotocol echoes it so the Browser connects to the process it discovered. This value is public process identity, not authentication. The listener accepts the current Local Host origin and the one user-saved Site origin. Origin and Host validation plus the Browser's local-network permission own this boundary. - Loopback v9 starts with a strict
hellohandshake. Up to two participant control sessions may coexist; each retains its own share and media lifetime. This bounds concurrent Native work, not the shared server's room count. Ending one control session cannot end another; App exit waits for all owned sessions to retire. Health discovery reports only separately probed native capture booleans. Native Viewer receive/NAT remains available when capture or hardware encode is absent. An active control session may list local screen/window choices, request bounded previews, and own one share's generation-fenced SDP/ICE edges, including at most one loopback media bridge outside route-copy capacity; it carries no room password, Host token, Viewer grant, or route policy. The participant page owns this connection and opens it lazily for native use. The presentation lifecycle owns acquisition and retirement, including releasing unused Host control. Source replacement retains the connection; a new source or picker does not create another participant. The Browser forwards current signaling. - A platform package contains one Go executable carrying the same core and
embedded Browser assets used by Hosted Piik. It may also carry one
process-isolated capture binary and the pinned
cloudflaredsidecar. Those sidecars are the only supervised children and keep their bounded lifetime. Platform packaging is metadata around that entry, not another long-running wrapper or UI. - The system Browser remains the UI. Browser extensions, userscripts, Electron, Tauri, and resident services need new evidence before they can replace this smaller boundary. An App-opened Site stores a non-secret opt-in at that exact Browser origin, so later manually opened pages may discover the running App. Pages without that opt-in do not probe localhost or request local- network permission. Clearing Site data simply requires opening it from the App again.
- A self-contained Local deployment serves reachable LAN peers without a
central Piik service. Its explicit
--linkmode starts one accountless Cloudflare Quick Tunnel for the same HTTP/WebSocket surface. The App reserves the exact IPv4 listener before creating that tunnel, then applies its HTTPS origin before the server starts serving that listener. It retains the same memory RoomStore, Browser UI, Viewer grant, signaling, and route controller. The Host sends the ordinary invitation link and the Viewer needs only a Browser. Cloudflare terminates this temporary control path; WebRTC media stays P2P and uses public STUN. The link ends with the App and is not a persistent Site, SFU, or TURN fallback. - Browser and Native are local media adapters beneath the same authenticated participant, connection identity, copy capacity, and committed route graph. Browser-to-Browser, Native-to-Browser, and Native-to-Native edges use the same WebRTC signaling contract. Native media currently covers the Host adapter; Viewer receive/relay follows the same boundary rather than adding a second participant or route protocol. A Native Viewer accepts only a negotiated H.264/VP8 and Opus offer, forwards encoded RTP into bounded local sources, and uses the existing Browser bridge for playback; compatible child edges reuse those sources. Unsupported media or a failed native bridge falls back to the existing Browser peer. One isolated platform capture feeds one encoded source and bounded independent Pion transports, with process-loopback audio for windows or system-loopback audio for screens sharing the same PeerConnection when available. Each physical Native connection owns an independent UDP socket; discovery and media share that socket, including after ICE restart. Site and one-link P2P connections request a bounded, best-effort PCP, UPnP, or NAT-PMP mapping alongside gathering; pure LAN Local connections do not. Once ordinary STUN observes a public address, Native advertises the mapped port as one lower-priority, srflx-shaped candidate on that address. Absence or rejection leaves ordinary ICE/STUN unchanged. Native P2P quality uses Pion's transport-wide GCC estimate only after feedback and source-frame progress, comparing its target payload bitrate with the measured encoded payload supplied to that edge. The existing route evidence windows own persistence; no custom score, second adaptation policy, or queued pacer is introduced. The one loopback edge supplies Host preview and the existing Browser LiveKit publisher. It neither consumes route-copy capacity nor starts port mapping; an assigned SFU publication still consumes its existing route copy. Native code does not implement LiveKit or another representation policy. The App discovers packaged capture capability at startup. Its Host page offers the Browser's standard picker and each exact App-owned screen/window; the user must select one and the App never guesses a target. The same room quality settings select native capture size, frame rate, video/audio bitrate, and the platform encoder's quality-versus-speed hint. A live quality or source change prepares a replacement capture/encoder generation and swaps it behind the existing Pion source; room, route, and PeerConnections do not change. Windows uses Graphics Capture, Media Foundation, and WASAPI; macOS uses ScreenCaptureKit, VideoToolbox, and AudioToolbox; Linux uses the ScreenCast Portal, PipeWire, and an installed GStreamer hardware-H.264 element. These adapters end at the same bounded encoded-frame protocol and do not own WebRTC or product state. The shared Native encode is the normal path. For a persistently degraded Native sender edge, the existing quality operation may prepare an overlapping stock Browser sender from the stable local bridge; existing evidence alone decides commit or rollback, and a later operation may return to Native. No extra threshold, timer, score, route operation, or representation ladder is added. Only a consumed App-launch marker travels in the URL fragment. Exact target identity travels over loopback.
- The App uses the system Browser as its only UI. On every launch, the current lightweight control center offers Local, public link, and Site. The Site value is stored in App configuration and remains one click on later launches; selecting Local or public link does not disable background RPC access for the saved Site. Command-line mode selectors remain automation inputs rather than the normal interface. An embedded shell requires a reproduced product failure and one comparative decision.
- Local mode is one explicit server composition: static current assets, memory-only rooms, peer-assisted media, no SQLite or SFU, and localhost plus current LAN IPv4 origins. It uses no public discovery by default. Public-link mode adds its temporary HTTPS origin, one ordinary public STUN destination, and two bounded public survey destinations. Site mode consumes that Site's configured STUN survey. Both feed the same connection-local prediction adapter; Native additionally owns its UDP socket and best-effort port mapping.
- One App configuration owns the optional Site origin and an optional, user-chosen Local access password. A blank value leaves the Local site open; a value gates that site through the existing SiteAccess authority. The App bootstraps a selected page through a fragment consumed before authentication; the page retains only the non-secret App opt-in at its origin. Friends use the existing room invitation grant. No App-specific room authorization system is added.
- Local authority shutdown first ends every in-memory room through the current
room-closedpath, then closes signaling and HTTP. The App cancels the server context, calls that end-then-close sequence under one bounded timeout, and only then closes the public tunnel and the loopback service. It does not restart a vanished authority. - App assembly consumes the immutable application release from the same full
Git revision and an explicit supported target. The Go binary embeds that
release's built Browser assets and the package
REVISIONrecords the same revision; a revision mismatch fails rather than loading a stale private contract. App-scoped pull requests build every target and must start the assembled Local authority, pass/healthz, and stop it cleanly before the candidate is accepted. - The root package manifest is the single dependency contract for the Browser bundle and repository tooling. No host installs packages to run Piik, so the manifest has no runtime half, and no second App dependency list or post-install package surgery is used.
Consequences
Hosted and Local operation share one product contract and one route model. The Go runtime acts as an optional capability provider when an activated Site or Local page discovers it, but it is not a second room product. A mixed room does not expose endpoint implementation to routing policy. The runtime stays small until a proven native capability needs a protocol field. The local package presents one user entry and runs its room authority inside that same process; only the optional capture and tunnel sidecars remain supervised children, and that entry, not a compatibility protocol, owns their lifetime.
Windows gates prove loopback discovery, Local static startup, automatic Host
access, LAN invitation construction, a three-Viewer Browser relay tree, two
process-isolated hardware-H.264/Pion edges sharing one encoded source and
decoded by Chrome, and bounded process cleanup. The native Host path uses the
current Browser route; its explicit in-page window picker also passes source
end, same-room reselection, and restored Viewer delivery. A remote Pion gate has
received 30+ H.264 RTP packets over a selected direct
srflx-to-srflx pair; this proves packet delivery, not decoded Browser video.
A Windows Browser gate also receives process-loopback
Opus on both native edges. A separate remote gate proves that --link generates
the ordinary public invitation and carries the unchanged Viewer page and
WebSocket control path, then disappears when the App exits. An isolated
LiveKit gate also proves native capture through the loopback Browser bridge and
the existing SFU publisher, including a live 1080p-to-480p profile change and
complete cleanup.
A Browser-Host-to-Native-Viewer gate additionally proves that the Viewer claims
the v8 App control session and Chrome decodes the 1280x720 source; the encoded
downstream edge has a separate H.264/Opus RTP integration gate.
One-link Browser media and physical non-Windows capture remain separate gates.
GitHub runners compile all three platform adapters. The macOS arm64 sidecar also
creates a hardware-only VideoToolbox encoder and produces a constrained-baseline
SPS/PPS/IDR; its ScreenCaptureKit video/audio permission, source lifecycle,
static-screen recovery, and endurance still require a physical Mac. The Linux
candidate probes and packages its Portal/PipeWire/GStreamer adapter, while a
real desktop, hardware encoder, system audio, and lifecycle still require a
physical Linux gate. Source previews remain best-effort on both platforms.
The v8 loopback gate also proves that a real Chrome receiver produces Pion transport feedback and that a non-unknown native sender-quality window reaches the existing route controller. Unknown feedback and stopped-source windows stay ineligible.
The local trust boundary is intentionally per-user: the instance token identifies the running App but is not authentication against another local process.
A physical home-router gate created and removed a UPnP mapping for an ephemeral UDP listener. The integrated native Host gate still passed capture, two-edge delivery, source restart, and cleanup with mapping enabled. The mapped port is now advertised on the observed public address; a selected mapped path across a pair that fails with STUN alone remains field evidence rather than an architectural claim.
Primary Sources
- Go child-process lifecycle
- WebRTC peer connections and signaling
- Cloudflare Quick Tunnels
- Cloudflare Tunnel WebSocket support
- RFC 6455 WebSocket Origin model
- Chrome Local Network Access
- Discord local RPC
- OBS WebSocket protocol
- LocalSend protocol
- Sunshine local Web UI
- Syncthing local GUI/API
- Native App media evidence