From a78891ec749858bfa87d147c1b887445552c9eab Mon Sep 17 00:00:00 2001 From: Compot Developer Date: Fri, 16 Oct 2026 13:59:52 +0000 Subject: [PATCH] docs: align Markdown tables for reading as plain text --- README.md | 44 +++++++++---------- README.ru.md | 42 +++++++++--------- README.zh-CN.md | 42 +++++++++--------- docs/architecture.md | 100 +++++++++++++++++++++---------------------- docs/devices.md | 84 ++++++++++++++++++------------------ docs/security.md | 20 ++++----- docs/testing.md | 32 +++++++------- docs/usage.md | 38 ++++++++-------- 8 files changed, 201 insertions(+), 201 deletions(-) diff --git a/README.md b/README.md index 45111d09..56abfa69 100644 --- a/README.md +++ b/README.md @@ -40,10 +40,10 @@ A small Android app for watching RTSP streams from IP cameras, with hardware H.2 Ready-to-install builds are in the [**latest** release](https://github.com/OpenIPC/decoder/releases/tag/latest): -| File | What it is | -|---|---| -| `decoder-v2.apk` | **Current version**, recommended | -| `decoder.apk` | Legacy version from the `basic` branch | +| File | What it is | +|------------------|----------------------------------------| +| `decoder-v2.apk` | **Current version**, recommended | +| `decoder.apk` | Legacy version from the `basic` branch | Every build is replaced in `latest` by the next one. Development is continuous, so there are no releases per version; every `v2` and `basic` build is also posted with its version and commit to the OpenIPC Telegram channel ([our channels](https://openipc.org/our-channels)). @@ -74,14 +74,14 @@ A beta channel is available on [Google Play](https://play.google.com/apps/intern [ Exit | Reset | Debug | UDP ] ``` -| Input | Action | -|---|---| -| Tap / **OK** | Open or close the menu | -| **1**–**8** | Camera slot; the number's colour shows the connection quality | -| **Grid** / **Slides** | Multi-view or one camera / every frame or key frames only | -| **Debug** / **UDP** | Text in the multi-view cells / the camera that pushes RTP to port 5600 | -| Pinch, double tap | Zoom and pan, reset the zoom (single view) | -| Long press | Screenshot (single view) / play this cell's sound (multi-view) | +| Input | Action | +|-----------------------|------------------------------------------------------------------------| +| Tap / **OK** | Open or close the menu | +| **1**–**8** | Camera slot; the number's colour shows the connection quality | +| **Grid** / **Slides** | Multi-view or one camera / every frame or key frames only | +| **Debug** / **UDP** | Text in the multi-view cells / the camera that pushes RTP to port 5600 | +| Pinch, double tap | Zoom and pan, reset the zoom (single view) | +| Long press | Screenshot (single view) / play this cell's sound (multi-view) | Every item, the remote, sharing a URL from another app and **Reset**: [Usage](docs/usage.md). @@ -101,16 +101,16 @@ Every item, the remote, sharing a URL from another app and **Reset**: [Usage](do ## Documentation -| Document | Contents | -|---|---| -| [Usage](docs/usage.md) | menu, gestures and keys, remote control, multi-view, Slides, the U cell, WebUI, sharing a URL from another app, screenshots, Reset | -| [Transports](docs/transports.md) | URL formats and options: RTSP over TCP/UDP/Auto/multicast, `udp://` push (Majestic `outgoing`), HTTP MJPEG; codecs, authentication, what the servers support | -| [Debug](docs/debug.md) | debug page on port 8085, `/stats.json` field reference, `/log`, snapshots, what to send with a bug report | -| [Architecture](docs/architecture.md) | data path, classes, threads, the decode planner, SurfaceView rendering, repository layout | -| [Testing and releases](docs/testing.md) | branches, CI, unit tests, versions and releases, branch builds, `mapping.txt`, building locally | -| [Security](docs/security.md) | credential store, debug server (LAN only), log redaction, no encryption on the network | -| [Devices](docs/devices.md) | verified and not working devices, hardware decoder notes, device and camera reports | -| [Drones and Wi‑Fi toys](docs/toys.md) | how toys stream, FLOW-UFO (in testing), what to try with an unknown toy, what to send | +| Document | Contents | +|-----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [Usage](docs/usage.md) | menu, gestures and keys, remote control, multi-view, Slides, the U cell, WebUI, sharing a URL from another app, screenshots, Reset | +| [Transports](docs/transports.md) | URL formats and options: RTSP over TCP/UDP/Auto/multicast, `udp://` push (Majestic `outgoing`), HTTP MJPEG; codecs, authentication, what the servers support | +| [Debug](docs/debug.md) | debug page on port 8085, `/stats.json` field reference, `/log`, snapshots, what to send with a bug report | +| [Architecture](docs/architecture.md) | data path, classes, threads, the decode planner, SurfaceView rendering, repository layout | +| [Testing and releases](docs/testing.md) | branches, CI, unit tests, versions and releases, branch builds, `mapping.txt`, building locally | +| [Security](docs/security.md) | credential store, debug server (LAN only), log redaction, no encryption on the network | +| [Devices](docs/devices.md) | verified and not working devices, hardware decoder notes, device and camera reports | +| [Drones and Wi‑Fi toys](docs/toys.md) | how toys stream, FLOW-UFO (in testing), what to try with an unknown toy, what to send | The change history is the [merged pull requests](https://github.com/OpenIPC/decoder/pulls?q=is%3Apr+is%3Amerged+base%3Av2) and the [commits of v2](https://github.com/OpenIPC/decoder/commits/v2). diff --git a/README.ru.md b/README.ru.md index 54f41a82..6b47b53f 100644 --- a/README.ru.md +++ b/README.ru.md @@ -40,10 +40,10 @@ Готовые сборки лежат в [релизе **latest**](https://github.com/OpenIPC/decoder/releases/tag/latest): -| Файл | Что это | -|---|---| +| Файл | Что это | +|------------------|-----------------------------------| | `decoder-v2.apk` | **Текущая версия**, рекомендуется | -| `decoder.apk` | Старая версия из ветки `basic` | +| `decoder.apk` | Старая версия из ветки `basic` | Каждая новая сборка заменяет предыдущую в `latest`. Разработка идёт непрерывно, поэтому отдельных релизов по версиям нет; каждая сборка веток `v2` и `basic` с номером версии и коммитом публикуется в Telegram-канале OpenIPC ([наши каналы](https://openipc.org/our-channels)). @@ -74,14 +74,14 @@ [ Exit | Reset | Debug | UDP ] ``` -| Действие | Результат | -|---|---| -| Касание / **OK** | Открыть или закрыть меню | -| **1**–**8** | Слот камеры; цвет номера показывает качество связи | -| **Grid** / **Slides** | Мультивид или одна камера / все кадры или только ключевые | -| **Debug** / **UDP** | Текст в ячейках мультивида / камера, которая шлёт RTP на порт 5600 | -| Два пальца, двойное касание | Масштаб и перемещение, сброс масштаба (одиночный режим) | -| Долгое нажатие | Скриншот (одиночный режим) / звук этой ячейки (мультивид) | +| Действие | Результат | +|-----------------------------|--------------------------------------------------------------------| +| Касание / **OK** | Открыть или закрыть меню | +| **1**–**8** | Слот камеры; цвет номера показывает качество связи | +| **Grid** / **Slides** | Мультивид или одна камера / все кадры или только ключевые | +| **Debug** / **UDP** | Текст в ячейках мультивида / камера, которая шлёт RTP на порт 5600 | +| Два пальца, двойное касание | Масштаб и перемещение, сброс масштаба (одиночный режим) | +| Долгое нажатие | Скриншот (одиночный режим) / звук этой ячейки (мультивид) | Все пункты меню, пульт, передача адреса из другого приложения и **Reset** — в [Usage](docs/usage.md) (на английском). @@ -103,16 +103,16 @@ Документация написана на английском языке; ссылки ведут на английские документы. -| Документ | Содержание | -|---|---| -| [Usage](docs/usage.md) | меню, жесты и клавиши, пульт, мультивид, Slides, ячейка U, WebUI, передача адреса из другого приложения, скриншоты, Reset | -| [Transports](docs/transports.md) | форматы адресов и параметры: RTSP поверх TCP/UDP/Auto/multicast, приём `udp://` (Majestic `outgoing`), HTTP MJPEG; кодеки, авторизация, что умеют серверы | -| [Debug](docs/debug.md) | отладочная страница на порту 8085, описание полей `/stats.json`, `/log`, снимки, что приложить к сообщению об ошибке | -| [Architecture](docs/architecture.md) | путь данных, классы, потоки, планировщик декодирования, вывод через SurfaceView, структура репозитория | -| [Testing and releases](docs/testing.md) | ветки, CI, юнит-тесты, версии и релизы, сборки веток, `mapping.txt`, локальная сборка | -| [Security](docs/security.md) | хранилище учётных данных, отладочный сервер (только локальная сеть), скрытие паролей в логах, отсутствие шифрования в сети | -| [Devices](docs/devices.md) | проверенные и неработающие устройства, особенности аппаратных декодеров, отчёты об устройствах и камерах | -| [Drones and Wi‑Fi toys](docs/toys.md) | как передают видео игрушки, FLOW-UFO (в тестировании), что попробовать с неизвестной игрушкой, что прислать | +| Документ | Содержание | +|-----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| +| [Usage](docs/usage.md) | меню, жесты и клавиши, пульт, мультивид, Slides, ячейка U, WebUI, передача адреса из другого приложения, скриншоты, Reset | +| [Transports](docs/transports.md) | форматы адресов и параметры: RTSP поверх TCP/UDP/Auto/multicast, приём `udp://` (Majestic `outgoing`), HTTP MJPEG; кодеки, авторизация, что умеют серверы | +| [Debug](docs/debug.md) | отладочная страница на порту 8085, описание полей `/stats.json`, `/log`, снимки, что приложить к сообщению об ошибке | +| [Architecture](docs/architecture.md) | путь данных, классы, потоки, планировщик декодирования, вывод через SurfaceView, структура репозитория | +| [Testing and releases](docs/testing.md) | ветки, CI, юнит-тесты, версии и релизы, сборки веток, `mapping.txt`, локальная сборка | +| [Security](docs/security.md) | хранилище учётных данных, отладочный сервер (только локальная сеть), скрытие паролей в логах, отсутствие шифрования в сети | +| [Devices](docs/devices.md) | проверенные и неработающие устройства, особенности аппаратных декодеров, отчёты об устройствах и камерах | +| [Drones and Wi‑Fi toys](docs/toys.md) | как передают видео игрушки, FLOW-UFO (в тестировании), что попробовать с неизвестной игрушкой, что прислать | История изменений — это [принятые pull request'ы](https://github.com/OpenIPC/decoder/pulls?q=is%3Apr+is%3Amerged+base%3Av2) и [коммиты ветки v2](https://github.com/OpenIPC/decoder/commits/v2). diff --git a/README.zh-CN.md b/README.zh-CN.md index 1fea1b09..2b1c9b43 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -40,10 +40,10 @@ 可直接安装的版本位于 [**latest** 发布页](https://github.com/OpenIPC/decoder/releases/tag/latest): -| 文件 | 说明 | -|---|---| +| 文件 | 说明 | +|------------------|------------------------| | `decoder-v2.apk` | **当前版本**,推荐使用 | -| `decoder.apk` | `basic` 分支的旧版本 | +| `decoder.apk` | `basic` 分支的旧版本 | 每次新构建都会替换 `latest` 中的上一个构建。开发是持续进行的,因此不再按版本单独发布;`v2` 和 `basic` 分支的每个构建连同版本号和提交都会发布到 OpenIPC 的 Telegram 频道([我们的频道](https://openipc.org/our-channels))。 @@ -74,14 +74,14 @@ [ Exit | Reset | Debug | UDP ] ``` -| 输入 | 功能 | -|---|---| -| 点击 / **OK** | 打开或关闭菜单 | -| **1**–**8** | 摄像机槽位;编号的颜色表示连接质量 | -| **Grid** / **Slides** | 多画面或单路画面 / 解码全部帧或仅关键帧 | -| **Debug** / **UDP** | 多画面中每个画面的文字 / 向端口 5600 推送 RTP 的摄像机 | -| 双指捏合、双击 | 缩放和平移、恢复原始大小(单路画面) | -| 长按 | 截图(单路画面)/ 播放该画面的声音(多画面) | +| 输入 | 功能 | +|-----------------------|--------------------------------------------------------| +| 点击 / **OK** | 打开或关闭菜单 | +| **1**–**8** | 摄像机槽位;编号的颜色表示连接质量 | +| **Grid** / **Slides** | 多画面或单路画面 / 解码全部帧或仅关键帧 | +| **Debug** / **UDP** | 多画面中每个画面的文字 / 向端口 5600 推送 RTP 的摄像机 | +| 双指捏合、双击 | 缩放和平移、恢复原始大小(单路画面) | +| 长按 | 截图(单路画面)/ 播放该画面的声音(多画面) | 菜单各项、遥控器、从其他应用分享地址以及 **Reset** 的说明见 [Usage](docs/usage.md)(英文)。 @@ -103,16 +103,16 @@ 文档以英文撰写,以下链接均指向英文文档。 -| 文档 | 内容 | -|---|---| -| [Usage](docs/usage.md) | 菜单、手势与按键、遥控器、多画面、Slides、U 画面、WebUI、从其他应用分享地址、截图、Reset | -| [Transports](docs/transports.md) | 地址格式与参数:RTSP over TCP/UDP/Auto/组播、`udp://` 推流接收(Majestic `outgoing`)、HTTP MJPEG;编码、认证、各服务器的支持情况 | -| [Debug](docs/debug.md) | 端口 8085 上的调试页面、`/stats.json` 字段说明、`/log`、快照、提交错误报告时需附带的内容 | -| [Architecture](docs/architecture.md) | 数据流程、类、线程、解码调度器、SurfaceView 渲染、仓库结构 | -| [Testing and releases](docs/testing.md) | 分支、CI、单元测试、版本与发布、分支构建、`mapping.txt`、本地构建 | -| [Security](docs/security.md) | 凭据存储、调试服务器(仅限局域网)、日志脱敏、网络传输未加密 | -| [Devices](docs/devices.md) | 已验证和无法使用的设备、硬件解码器说明、设备与摄像机报告 | -| [Drones and Wi‑Fi toys](docs/toys.md) | 玩具如何传输视频、FLOW-UFO(测试中)、遇到未知玩具时可以尝试什么、需要提交哪些信息 | +| 文档 | 内容 | +|-----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------| +| [Usage](docs/usage.md) | 菜单、手势与按键、遥控器、多画面、Slides、U 画面、WebUI、从其他应用分享地址、截图、Reset | +| [Transports](docs/transports.md) | 地址格式与参数:RTSP over TCP/UDP/Auto/组播、`udp://` 推流接收(Majestic `outgoing`)、HTTP MJPEG;编码、认证、各服务器的支持情况 | +| [Debug](docs/debug.md) | 端口 8085 上的调试页面、`/stats.json` 字段说明、`/log`、快照、提交错误报告时需附带的内容 | +| [Architecture](docs/architecture.md) | 数据流程、类、线程、解码调度器、SurfaceView 渲染、仓库结构 | +| [Testing and releases](docs/testing.md) | 分支、CI、单元测试、版本与发布、分支构建、`mapping.txt`、本地构建 | +| [Security](docs/security.md) | 凭据存储、调试服务器(仅限局域网)、日志脱敏、网络传输未加密 | +| [Devices](docs/devices.md) | 已验证和无法使用的设备、硬件解码器说明、设备与摄像机报告 | +| [Drones and Wi‑Fi toys](docs/toys.md) | 玩具如何传输视频、FLOW-UFO(测试中)、遇到未知玩具时可以尝试什么、需要提交哪些信息 | 更新历史即[已合并的 pull request](https://github.com/OpenIPC/decoder/pulls?q=is%3Apr+is%3Amerged+base%3Av2) 和 [v2 分支的提交](https://github.com/OpenIPC/decoder/commits/v2)。 diff --git a/docs/architecture.md b/docs/architecture.md index 9300d32b..72746236 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -41,48 +41,48 @@ The single view runs one `RtspClient` (one `RtspSession`, assemblers, audio, con ## Classes -| Class | Role | -|---|---| -| `Decoder` | Main activity: UI, lifecycle, settings, single-view decoding, multi-view grid, debug state | -| `RtspClient` | Single-view stream: RTSP session, NAL assembly, audio, connection quality (jitter) | -| `RtspSession` | One session over TCP, UDP or multicast (and the `udp://`, `http://` fronts): handshake, RTP receive, keepalive, watchdog, reconnect with back-off, Auto fallback | -| `RtspAuth`, `Sdp` | Basic and Digest authentication; parsing of the session description | -| `Transport` | Slot transport mode; the RTSP `Transport` header (request and reply) | -| `UdpReceiver`, `RtpReorder` | UDP and multicast receive with a sender filter; packet reordering | -| `RtpListener` | `udp://` slots: plain RTP pushed by the camera | -| `HttpMjpeg` | `http://` slots: MJPEG over HTTP (`multipart/x-mixed-replace`, chunked or not) | -| `NalAssembler` | H.264/H.265 NAL units from RTP; drops units damaged by packet loss, or passes them on cut short | -| `JpegAssembler` | Whole JPEG frames from RTP/JPEG (RFC 2435) | -| `JpegCodec`, `JpegRenderer` | MJPEG on screen: the device's JPEG decoder where there is one, otherwise BitmapFactory | -| `MediaCodecManager`, `DecoderRetry` | Video decoder lifecycle, instance budget per MIME type, software fallback, recovery after errors | -| `CodecInfo` | The device's video decoders and their declared limits (for `/stats.json`) | -| `DecodePlanner` | Multi-view: which cells decode every frame and which only key frames | -| `AudioManager`, `AudioConfig` | Audio: G.711/L16 conversion, AAC/Opus decoding, AudioTrack playback | -| `QuadCell` | One multi-view cell: its own session, decoder, queue and optional audio | -| `MenuManager` | Menu grid, URL dialog, reset dialog, WebUI browser | -| `CredentialStore`, `KeystoreCipher` | Encrypted storage of camera URLs (Android Keystore) | -| `FramePool`, `Frame` | Buffer pool that reduces garbage-collector load | -| `DebugServer`, `LogBuffer`, `NetInfo`, `Snapshots` | Debug server on port 8085, redacted log ring, device addresses and the LAN rule, screen snapshots | -| `StreamProbe`, `StreamProbes` | Short stream checks of the slots not on screen (single view, debug page in use) | -| `App`, `Crash`, `Restart` | Uncaught-exception handler and crash screen; restart after Reset | +| Class | Role | +|----------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `Decoder` | Main activity: UI, lifecycle, settings, single-view decoding, multi-view grid, debug state | +| `RtspClient` | Single-view stream: RTSP session, NAL assembly, audio, connection quality (jitter) | +| `RtspSession` | One session over TCP, UDP or multicast (and the `udp://`, `http://` fronts): handshake, RTP receive, keepalive, watchdog, reconnect with back-off, Auto fallback | +| `RtspAuth`, `Sdp` | Basic and Digest authentication; parsing of the session description | +| `Transport` | Slot transport mode; the RTSP `Transport` header (request and reply) | +| `UdpReceiver`, `RtpReorder` | UDP and multicast receive with a sender filter; packet reordering | +| `RtpListener` | `udp://` slots: plain RTP pushed by the camera | +| `HttpMjpeg` | `http://` slots: MJPEG over HTTP (`multipart/x-mixed-replace`, chunked or not) | +| `NalAssembler` | H.264/H.265 NAL units from RTP; drops units damaged by packet loss, or passes them on cut short | +| `JpegAssembler` | Whole JPEG frames from RTP/JPEG (RFC 2435) | +| `JpegCodec`, `JpegRenderer` | MJPEG on screen: the device's JPEG decoder where there is one, otherwise BitmapFactory | +| `MediaCodecManager`, `DecoderRetry` | Video decoder lifecycle, instance budget per MIME type, software fallback, recovery after errors | +| `CodecInfo` | The device's video decoders and their declared limits (for `/stats.json`) | +| `DecodePlanner` | Multi-view: which cells decode every frame and which only key frames | +| `AudioManager`, `AudioConfig` | Audio: G.711/L16 conversion, AAC/Opus decoding, AudioTrack playback | +| `QuadCell` | One multi-view cell: its own session, decoder, queue and optional audio | +| `MenuManager` | Menu grid, URL dialog, reset dialog, WebUI browser | +| `CredentialStore`, `KeystoreCipher` | Encrypted storage of camera URLs (Android Keystore) | +| `FramePool`, `Frame` | Buffer pool that reduces garbage-collector load | +| `DebugServer`, `LogBuffer`, `NetInfo`, `Snapshots` | Debug server on port 8085, redacted log ring, device addresses and the LAN rule, screen snapshots | +| `StreamProbe`, `StreamProbes` | Short stream checks of the slots not on screen (single view, debug page in use) | +| `App`, `Crash`, `Restart` | Uncaught-exception handler and crash screen; restart after Reset | ## Threads Network, video decoding and audio run on separate threads, so a slow decoder never blocks the socket. -| Thread | Work | -|---|---| -| UI (main) | layout, menu, planner step and stats once a second, debug state on request | -| `-net` | one per session: connect, RTSP, receive RTP or HTTP, assemble, queue | -| `-wd` | one per session: watchdog (no video for 8 s → reconnect) and RTSP keepalive | -| single-view pool (2) | video decode from the queue; audio playback | -| `Quad-N-dec` | one per multi-view cell: decode from the cell's queue | -| `quad-audio` | audio of the selected multi-view cell | -| `rtsp-aac-decode` | AAC/Opus output of the audio decoder | -| `decoder-release` | short-lived: stops and releases a decoder off the UI thread | -| `debug-http-*` | accept loop, two workers at background priority, request deadlines | -| `probe-sched`, `probe` | stream checks: scheduler and two workers at background priority | -| `codec-info` | lists the device's decoders once at start | +| Thread | Work | +|------------------------|-----------------------------------------------------------------------------| +| UI (main) | layout, menu, planner step and stats once a second, debug state on request | +| `-net` | one per session: connect, RTSP, receive RTP or HTTP, assemble, queue | +| `-wd` | one per session: watchdog (no video for 8 s → reconnect) and RTSP keepalive | +| single-view pool (2) | video decode from the queue; audio playback | +| `Quad-N-dec` | one per multi-view cell: decode from the cell's queue | +| `quad-audio` | audio of the selected multi-view cell | +| `rtsp-aac-decode` | AAC/Opus output of the audio decoder | +| `decoder-release` | short-lived: stops and releases a decoder off the UI thread | +| `debug-http-*` | accept loop, two workers at background priority, request deadlines | +| `probe-sched`, `probe` | stream checks: scheduler and two workers at background priority | +| `codec-info` | lists the device's decoders once at start | The queues between network and decoder are bounded (30 frames per cell); when a queue is full, the frame is dropped and counted as a decoder loss (`lost_fps`), not a network loss. @@ -96,19 +96,19 @@ The queues between network and decoder are bounded (30 frames per cell); when a **Priority:** the U cell while it receives a stream, the cell whose sound plays, then slot order. -| Step | Rule | -|---|---| -| warm-up | the first 10 s after the grid starts: losses decide nothing, promotions that fit go on | -| settle | after a change, 3 s before the next one | -| loss signal | decoder losses (queue full, no input buffer): at full rate ≥ 2 frames/s and ≥ 10 % of the received rate; on key frames any loss. 3 lossy samples within 10 s trigger a demotion | -| demote | the lowest-priority full-rate cell goes to key frames. The capacity drops to 90 % of the load, never below a proven load; that load is remembered as failed, and the next probe waits twice as long (up to 10 min) | -| transient | a loss below a load that already played quietly is forgiven once: the cell is demoted, but the capacity and the failed load are kept. Forgiven again only after a load at least as high plays quietly again | -| pause | when even key frames are too much, the lowest-priority cell is paused (never the last one playing); it is resumed on key frames after a quiet period that doubles with every pause | -| promote | the highest-priority key-frame cell whose load is known, if the result fits within the capacity and stays below 90 % of a failed load. Beyond the capacity only after a quiet period (a probe, from 30 s). A lower cell may go first only if it surely fits | -| proven | a load that plays 30 s without losses is proven; above the capacity it raises it. A failure at about that load is forgotten | -| retry | a failed load is tried again one hour after the last demotion | -| unknown | a playing cell whose size and rate are not known yet holds the lower cells for up to 10 s | -| stall | a second in which nothing decodes at all (and nothing is lost) is skipped and does not count as quiet | +| Step | Rule | +|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| warm-up | the first 10 s after the grid starts: losses decide nothing, promotions that fit go on | +| settle | after a change, 3 s before the next one | +| loss signal | decoder losses (queue full, no input buffer): at full rate ≥ 2 frames/s and ≥ 10 % of the received rate; on key frames any loss. 3 lossy samples within 10 s trigger a demotion | +| demote | the lowest-priority full-rate cell goes to key frames. The capacity drops to 90 % of the load, never below a proven load; that load is remembered as failed, and the next probe waits twice as long (up to 10 min) | +| transient | a loss below a load that already played quietly is forgiven once: the cell is demoted, but the capacity and the failed load are kept. Forgiven again only after a load at least as high plays quietly again | +| pause | when even key frames are too much, the lowest-priority cell is paused (never the last one playing); it is resumed on key frames after a quiet period that doubles with every pause | +| promote | the highest-priority key-frame cell whose load is known, if the result fits within the capacity and stays below 90 % of a failed load. Beyond the capacity only after a quiet period (a probe, from 30 s). A lower cell may go first only if it surely fits | +| proven | a load that plays 30 s without losses is proven; above the capacity it raises it. A failure at about that load is forgotten | +| retry | a failed load is tried again one hour after the last demotion | +| unknown | a playing cell whose size and rate are not known yet holds the lower cells for up to 10 s | +| stall | a second in which nothing decodes at all (and nothing is lost) is skipped and does not count as quiet | The learnt capacity and the failed load are stored per device (`decoder_capacity_v2`, `decoder_failed_load_v2`); stored values at or below the floor are not trusted. `/stats.json` shows them under `decoder` ([debug.md](debug.md#top-level)). diff --git a/docs/devices.md b/docs/devices.md index 6f36b6e6..74049b49 100644 --- a/docs/devices.md +++ b/docs/devices.md @@ -13,40 +13,40 @@ Tested the app on your device? Please [**submit a Device report**](https://githu Most devices below were reported with the legacy 1.0x version (`basic` branch); the *App* column shows the version that was tested. New reports should include the app version. -| Type | Device | Android | Kernel | App | Notes | -|-------------|----------------------------------------|-----------------------------------|----------------|---------|--------------------------------------------------------------------------------------------| -| Phone | Asus ZC553KL | 8.1 | 3.18.71 | 1.0x | | -| Phone | Blackview BV4900Pro | 12 | 4.19.191 | 1.0x | | -| Phone | Oppo 5X | ColorOS 15.0 | 5.15.149 | 1.0x | | -| Phone | Oppo A17 | ColorOS 12.1 | 4.19.191 | 1.0x | | -| Phone | Samsung Galaxy A51 | 13 | 4.14.113 | 1.0x | | -| Phone | Samsung Galaxy M21 (SM-M215F/DSN) | 12 | 4.14.113 | 1.0x | | -| Phone | Samsung Galaxy S25 | 15 | 6.6.30 | 1.0x | | -| Phone | Samsung A55 5G | 14 | 6.1.93 | 1.0x | | -| Phone | Samsung Galaxy J7 | 10 | 3.18.150 | 1.0x | | -| Phone | Samsung S8 | 9 | 4.4.153 | 1.0x | | -| Phone | Samsung S23 Ultra | 15 | 5.15.153 | 1.0x | | +| Type | Device | Android | Kernel | App | Notes | +|-------------|----------------------------------------|-----------------------------------|----------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Phone | Asus ZC553KL | 8.1 | 3.18.71 | 1.0x | | +| Phone | Blackview BV4900Pro | 12 | 4.19.191 | 1.0x | | +| Phone | Oppo 5X | ColorOS 15.0 | 5.15.149 | 1.0x | | +| Phone | Oppo A17 | ColorOS 12.1 | 4.19.191 | 1.0x | | +| Phone | Samsung Galaxy A51 | 13 | 4.14.113 | 1.0x | | +| Phone | Samsung Galaxy M21 (SM-M215F/DSN) | 12 | 4.14.113 | 1.0x | | +| Phone | Samsung Galaxy S25 | 15 | 6.6.30 | 1.0x | | +| Phone | Samsung A55 5G | 14 | 6.1.93 | 1.0x | | +| Phone | Samsung Galaxy J7 | 10 | 3.18.150 | 1.0x | | +| Phone | Samsung S8 | 9 | 4.4.153 | 1.0x | | +| Phone | Samsung S23 Ultra | 15 | 5.15.153 | 1.0x | | | Phone | TECNO KM4 | 15 | 5.15.194 | v2 2.04 | Lab touch device: WebUI with the on-screen keyboard checked ([#159](https://github.com/OpenIPC/decoder/issues/159), [#160](https://github.com/OpenIPC/decoder/issues/160)); Close under the status bar ([#169](https://github.com/OpenIPC/decoder/issues/169)) | -| Phone | Xiaomi Redmi 7A | 10 | 4.9.261 | 1.0x | | -| Phone | Xiaomi Redmi Note 7 (M1901F7G) | 10 | 4.4.192 | 1.0x | | -| Phone | Xiaomi Redmi Note 13 4G | 13 | 5.15.94 | 1.0x | | -| Tablet | Lenovo TB-8504X | 8.1.0 | 3.18.71 | v2 2.00 | H.265 and multi-view work; WebUI input fixed in 2.02 ([#159](https://github.com/OpenIPC/decoder/issues/159)) | -| Tablet | Lenovo TB-X304L | 8.1.0 | 3.18.71 | 1.0x | Picture cropped in 1.0x, fixed in v2 ([#17](https://github.com/OpenIPC/decoder/issues/17)) | -| Tablet | Lenovo TB-X606F | 10 | — | 1.0x | Picture cropped in 1.0x, fixed in v2 ([#17](https://github.com/OpenIPC/decoder/issues/17)) | -| Tablet | Lenovo Tab P11 | 11 | 4.19.157-perf+ | 1.0x | Picture cropped in 1.0x, fixed in v2 ([#17](https://github.com/OpenIPC/decoder/issues/17)) | -| Tablet | Samsung Galaxy Tab A | 7.1.1 | 3.10.49 | 1.0x | | -| Tablet | Samsung Galaxy Tab A | 11 | 4.9.227 | 1.0x | | -| Tablet | Samsung Tab A7 (SM-T505) | 12 | 4.19.157-perf- | 1.0x | | -| Set-top box | A95X F3 AIR | 9 (SlimBOXtv AOSP 9.14, no GAPPS) | 4.9.113 | 1.0x | | -| Set-top box | IE X3 Air | 9 (SlimBOXtv AOSP 9.14, no GAPPS) | 4.9.113 | 1.0x | | -| Set-top box | AG-216 | 10 | 4.9.170 | v2 2.00 | v2 test device; decoder budget measured at ≈ 1080p60 in total (two 1080p30 at full rate) | -| Set-top box | SberBox SBDV-00001 | custom | 4.9.228 | 1.0x | | -| Set-top box | X96Q (Allwinner H313) | 10 | 4.9.170 | 1.0x | | -| Projector | SIBIRLINK HY300 / HY320 | 11 | 4.19.323 | v2 2.02 | 1080p H.264 / H.265 work; 2304×1296 and 2560×1440 do not start ([#165](https://github.com/OpenIPC/decoder/issues/165), [#166](https://github.com/OpenIPC/decoder/issues/166)) | -| TV | Haier Candy Android TV 2K | 11 | 4.9.243+ | 1.0x | No icon in the device's app list | -| TV | Yandex TV Novex NVX-55U169TSY | 7.1.1 | 4.4.3 | 1.0x | | -| Car | Media Center JCAC10003 (ac8227/ac8229) | 12 | 3.18.79+ | 1.0x | | -| Car | Mirror Z55 | 8.1.0 | 4.4.83 | 1.0x | | +| Phone | Xiaomi Redmi 7A | 10 | 4.9.261 | 1.0x | | +| Phone | Xiaomi Redmi Note 7 (M1901F7G) | 10 | 4.4.192 | 1.0x | | +| Phone | Xiaomi Redmi Note 13 4G | 13 | 5.15.94 | 1.0x | | +| Tablet | Lenovo TB-8504X | 8.1.0 | 3.18.71 | v2 2.00 | H.265 and multi-view work; WebUI input fixed in 2.02 ([#159](https://github.com/OpenIPC/decoder/issues/159)) | +| Tablet | Lenovo TB-X304L | 8.1.0 | 3.18.71 | 1.0x | Picture cropped in 1.0x, fixed in v2 ([#17](https://github.com/OpenIPC/decoder/issues/17)) | +| Tablet | Lenovo TB-X606F | 10 | — | 1.0x | Picture cropped in 1.0x, fixed in v2 ([#17](https://github.com/OpenIPC/decoder/issues/17)) | +| Tablet | Lenovo Tab P11 | 11 | 4.19.157-perf+ | 1.0x | Picture cropped in 1.0x, fixed in v2 ([#17](https://github.com/OpenIPC/decoder/issues/17)) | +| Tablet | Samsung Galaxy Tab A | 7.1.1 | 3.10.49 | 1.0x | | +| Tablet | Samsung Galaxy Tab A | 11 | 4.9.227 | 1.0x | | +| Tablet | Samsung Tab A7 (SM-T505) | 12 | 4.19.157-perf- | 1.0x | | +| Set-top box | A95X F3 AIR | 9 (SlimBOXtv AOSP 9.14, no GAPPS) | 4.9.113 | 1.0x | | +| Set-top box | IE X3 Air | 9 (SlimBOXtv AOSP 9.14, no GAPPS) | 4.9.113 | 1.0x | | +| Set-top box | AG-216 | 10 | 4.9.170 | v2 2.00 | v2 test device; decoder budget measured at ≈ 1080p60 in total (two 1080p30 at full rate) | +| Set-top box | SberBox SBDV-00001 | custom | 4.9.228 | 1.0x | | +| Set-top box | X96Q (Allwinner H313) | 10 | 4.9.170 | 1.0x | | +| Projector | SIBIRLINK HY300 / HY320 | 11 | 4.19.323 | v2 2.02 | 1080p H.264 / H.265 work; 2304×1296 and 2560×1440 do not start ([#165](https://github.com/OpenIPC/decoder/issues/165), [#166](https://github.com/OpenIPC/decoder/issues/166)) | +| TV | Haier Candy Android TV 2K | 11 | 4.9.243+ | 1.0x | No icon in the device's app list | +| TV | Yandex TV Novex NVX-55U169TSY | 7.1.1 | 4.4.3 | 1.0x | | +| Car | Media Center JCAC10003 (ac8227/ac8229) | 12 | 3.18.79+ | 1.0x | | +| Car | Mirror Z55 | 8.1.0 | 4.4.83 | 1.0x | | ## Not working @@ -58,12 +58,12 @@ Most devices below were reported with the legacy 1.0x version (`basic` branch); What the app has measured or met on real devices. The decoders of a device are listed in `/stats.json` (`decoders`: `name`, `type`, `hardware`, `adaptive`, `max_instances`, `fps_1080p`; see [debug.md](debug.md#decoders)). -| Topic | Note | -|---|---| -| Decoder budget | The AG-216 (Allwinner H313/H616 family, Android 10) decodes about 1080p60 in total: two 1080p30 streams at full rate. Multi-view learns the budget of each device by measurement, see [architecture.md](architecture.md#decode-planner) | -| Instance limit | The AG-216 allows 4 hardware H.265 decoder instances. Key-frame cells beyond that use a software decoder (`· software`); beyond that too, `Decoder limit: N × H.265` | -| Stuck decoder | The AG-216's video decoder can stop responding until the device is restarted: frames arrive, none is decoded. The app says so on screen and in `/stats.json` (`decoder_stuck`). Cause unknown ([#145](https://github.com/OpenIPC/decoder/issues/145)) | -| MJPEG | Hardware JPEG decoding is used where the device offers `video/jpeg` (also `video/mjpeg`, `video/x-motion-jpeg`). The AG-216 declares an Allwinner MJPEG decoder that Android does not list and that cannot be created; MJPEG is then decoded with BitmapFactory ([#132](https://github.com/OpenIPC/decoder/issues/132)) | -| Window buffer stalls | On Mali TV boxes the app's window buffer stalled for seconds with every grid/single switch while the single view used a `TextureView`; since 2.09 the single view is a `SurfaceView` ([#76](https://github.com/OpenIPC/decoder/issues/76)) | -| Large resolutions | The SIBIRLINK HY300 / HY320 projector plays 1080p H.264 / H.265, but 2304×1296 and 2560×1440 do not start ([#165](https://github.com/OpenIPC/decoder/issues/165), [#166](https://github.com/OpenIPC/decoder/issues/166)) | -| Old Android | Android 5.0 is the minimum. Encrypted URL storage needs 6.0, multicast 8.0, screenshots and snapshots 7.0 | +| Topic | Note | +|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Decoder budget | The AG-216 (Allwinner H313/H616 family, Android 10) decodes about 1080p60 in total: two 1080p30 streams at full rate. Multi-view learns the budget of each device by measurement, see [architecture.md](architecture.md#decode-planner) | +| Instance limit | The AG-216 allows 4 hardware H.265 decoder instances. Key-frame cells beyond that use a software decoder (`· software`); beyond that too, `Decoder limit: N × H.265` | +| Stuck decoder | The AG-216's video decoder can stop responding until the device is restarted: frames arrive, none is decoded. The app says so on screen and in `/stats.json` (`decoder_stuck`). Cause unknown ([#145](https://github.com/OpenIPC/decoder/issues/145)) | +| MJPEG | Hardware JPEG decoding is used where the device offers `video/jpeg` (also `video/mjpeg`, `video/x-motion-jpeg`). The AG-216 declares an Allwinner MJPEG decoder that Android does not list and that cannot be created; MJPEG is then decoded with BitmapFactory ([#132](https://github.com/OpenIPC/decoder/issues/132)) | +| Window buffer stalls | On Mali TV boxes the app's window buffer stalled for seconds with every grid/single switch while the single view used a `TextureView`; since 2.09 the single view is a `SurfaceView` ([#76](https://github.com/OpenIPC/decoder/issues/76)) | +| Large resolutions | The SIBIRLINK HY300 / HY320 projector plays 1080p H.264 / H.265, but 2304×1296 and 2560×1440 do not start ([#165](https://github.com/OpenIPC/decoder/issues/165), [#166](https://github.com/OpenIPC/decoder/issues/166)) | +| Old Android | Android 5.0 is the minimum. Encrypted URL storage needs 6.0, multicast 8.0, screenshots and snapshots 7.0 | diff --git a/docs/security.md b/docs/security.md index d3b5d82c..fb5d7235 100644 --- a/docs/security.md +++ b/docs/security.md @@ -46,12 +46,12 @@ Anyone on the same LAN can see the camera pictures and hosts through it. On a ne Applied to every line before it is kept for `/log` and to every string in `/stats.json`: -| Pattern | Becomes | -|---|---| -| `rtsp://user:pass@`, `rtsps://`, `http://`, `https://` userinfo | `rtsp://***@` | -| `pass=`, `password=`, `pwd=` parameters | `=***` | -| `user=`, `username=`, `login=`, `usr=` parameters (debug server and `/log`) | `=***` | -| `Authorization: ` | `Authorization: ***` | +| Pattern | Becomes | +|-----------------------------------------------------------------------------|-------------------------------| +| `rtsp://user:pass@`, `rtsps://`, `http://`, `https://` userinfo | `rtsp://***@` | +| `pass=`, `password=`, `pwd=` parameters | `=***` | +| `user=`, `username=`, `login=`, `usr=` parameters (debug server and `/log`) | `=***` | +| `Authorization: ` | `Authorization: ***` | The app's own log calls redact URLs the same way, without the login rule ([#55](https://github.com/OpenIPC/decoder/issues/55)), and a shared link is logged by its host only: `adb logcat` shows no passwords, but may show a login that the camera expects in the URL path. Check reports before posting anyway. @@ -61,10 +61,10 @@ The built-in browser opens the camera's web interface with JavaScript enabled. H ## App permissions -| Permission | Why | -|---|---| -| `INTERNET` | streams, WebUI, the debug server | -| `CHANGE_WIFI_MULTICAST_STATE` | the multicast lock for multicast RTP on Wi‑Fi | +| Permission | Why | +|------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------| +| `INTERNET` | streams, WebUI, the debug server | +| `CHANGE_WIFI_MULTICAST_STATE` | the multicast lock for multicast RTP on Wi‑Fi | | `WRITE_EXTERNAL_STORAGE` (Android ≤ 9), `READ_EXTERNAL_STORAGE` (≤ 12) | screenshots to `Pictures/OpenIPC` and reports to `Download/OpenIPC` on older Android versions | No location, camera, microphone or account permissions. No analytics and no Google services. diff --git a/docs/testing.md b/docs/testing.md index a7588da7..79c73c5e 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -11,9 +11,9 @@ ## Branches -| Branch | Status | -|---|---| -| `v2` | **Main development branch** (default). Modular code in `android/`. Built as `decoder-v2.apk` | +| Branch | Status | +|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `v2` | **Main development branch** (default). Modular code in `android/`. Built as `decoder-v2.apk` | | `basic` | Legacy single-file version (1.0x), kept for reference and older setups. Built as `decoder.apk`. Its version history is in the [`basic` README](https://github.com/OpenIPC/decoder/tree/basic#version-history) | Base pull requests on `v2`. @@ -22,18 +22,18 @@ Base pull requests on `v2`. `.github/workflows/build.yml` runs on every push and pull request, and by hand (`workflow_dispatch`, optionally for a given commit). A newer push to the same branch cancels the older run, so `latest` always gets the build of the newest commit. -| Job | Runs on | What it does | -|---|---|---| -| `test` | every push and pull request, forks included (no secrets) | `./gradlew testDebugUnitTest`, then `./gradlew lintDebug` against `app/lint-baseline.xml` (only new findings fail). Reports are uploaded when it fails | -| `build` | pushes and manual runs, not pull requests | signed `assembleRelease` (R8 minify and resource shrinking), the R8 `mapping.txt` kept as a workflow artifact | +| Job | Runs on | What it does | +|---------|----------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------| +| `test` | every push and pull request, forks included (no secrets) | `./gradlew testDebugUnitTest`, then `./gradlew lintDebug` against `app/lint-baseline.xml` (only new findings fail). Reports are uploaded when it fails | +| `build` | pushes and manual runs, not pull requests | signed `assembleRelease` (R8 minify and resource shrinking), the R8 `mapping.txt` kept as a workflow artifact | What a `build` run publishes: -| Branch | Output | -|---|---| -| `v2` | `decoder-v2.apk` and `decoder-v2-mapping.txt` in the [`latest`](https://github.com/OpenIPC/decoder/releases/tag/latest) release (replaced by every build); a message with the APK, version and commit to the OpenIPC Telegram channel | -| `basic` | `decoder.apk` and its mapping in `latest`; Telegram | -| any other | workflow artifacts `decoder-` (APK) and `…-mapping` on the run page | +| Branch | Output | +|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `v2` | `decoder-v2.apk` and `decoder-v2-mapping.txt` in the [`latest`](https://github.com/OpenIPC/decoder/releases/tag/latest) release (replaced by every build); a message with the APK, version and commit to the OpenIPC Telegram channel | +| `basic` | `decoder.apk` and its mapping in `latest`; Telegram | +| any other | workflow artifacts `decoder-` (APK) and `…-mapping` on the run page | Every run, `v2` and `basic` included, also keeps its mapping as the artifact `…-mapping`. Artifacts expire after 90 days. @@ -108,11 +108,11 @@ For every class, field and method it gives `original -> obfuscated`, with the li A mapping fits **only the build it came from**. Use the one whose build matches the hash under **Exit** or `git` in `/stats.json`: -| Build | Mapping | -|---|---| -| the current `v2` build | `decoder-v2-mapping.txt` in [`latest`](https://github.com/OpenIPC/decoder/releases/tag/latest) | +| Build | Mapping | +|------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| the current `v2` build | `decoder-v2-mapping.txt` in [`latest`](https://github.com/OpenIPC/decoder/releases/tag/latest) | | an older `v2` build, or any branch build | the artifact `decoder-v2-mapping` (or `decoder--mapping`) of the CI run that built it, kept 90 days. A push run is found by its commit (below). A manual run (*Run workflow* with `commit_hash`) is listed under the branch it was started on, not under the commit it built: `gh run list -R OpenIPC/decoder -w Build -e workflow_dispatch -b `, then pick the run by its time or ID | -| builds before 2.08 | none: they were made before the mapping was kept (#181) | +| builds before 2.08 | none: they were made before the mapping was kept (#181) | ```sh RUN=$(gh run list -R OpenIPC/decoder -c -e push -w Build -L 1 --json databaseId -q '.[0].databaseId') diff --git a/docs/usage.md b/docs/usage.md index f09faeff..e1094a37 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -35,30 +35,30 @@ The menu is one grid: the eight slots on top, two rows of four below. Every word The menu on a phone

-| Item | Action | -|---|---| -| **1**–**8** | Switch to a camera slot (single view). The number of the active slot shows the connection quality: green under 100 ms of jitter, yellow 100–300 ms, red above. A grey number is an empty slot | -| **Settings** | URL and transport of the active slot. Single view only, grey elsewhere | -| **WebUI** | The camera's web interface, see [WebUI](#webui). Single view only, grey elsewhere | -| **Grid** | The view: [multi-view](#multi-view) (2×2 or 3×3) or the single camera. Does not change the playback mode | -| **Slides** | The playback mode: every frame, or key frames only, see [Slides](#slides). Does not change the view | -| **Exit** | Close the app. The small text under it is the version and build hash (`v2.10, `) | -| **Reset** | Clear all settings after a confirmation, see [Reset](#reset) | -| **Debug** | Text in the corner of every multi-view cell (resolution, codec, decoded/received fps, key frames, status), or none. **Long press** (remote: hold **OK**, or **Menu** on Debug): [save a report](debug.md#save-report) to `Download/OpenIPC` and share it | -| **UDP** | [The U cell](#the-u-cell) full-screen | +| Item | Action | +|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **1**–**8** | Switch to a camera slot (single view). The number of the active slot shows the connection quality: green under 100 ms of jitter, yellow 100–300 ms, red above. A grey number is an empty slot | +| **Settings** | URL and transport of the active slot. Single view only, grey elsewhere | +| **WebUI** | The camera's web interface, see [WebUI](#webui). Single view only, grey elsewhere | +| **Grid** | The view: [multi-view](#multi-view) (2×2 or 3×3) or the single camera. Does not change the playback mode | +| **Slides** | The playback mode: every frame, or key frames only, see [Slides](#slides). Does not change the view | +| **Exit** | Close the app. The small text under it is the version and build hash (`v2.10, `) | +| **Reset** | Clear all settings after a confirmation, see [Reset](#reset) | +| **Debug** | Text in the corner of every multi-view cell (resolution, codec, decoded/received fps, key frames, status), or none. **Long press** (remote: hold **OK**, or **Menu** on Debug): [save a report](debug.md#save-report) to `Download/OpenIPC` and share it | +| **UDP** | [The U cell](#the-u-cell) full-screen | Items that are on (Grid, Slides, Debug, UDP and the active slot) have an orange fill; the item the remote is on has a light blue frame. On a narrow screen or with a large font the labels are shortened with an ellipsis rather than clipped. ## Gestures and keys -| Input | Action | -|---|---| -| Tap / **OK** / **Enter** (D-pad centre) | Open or close the menu | -| **Back** / **Esc** | Close the menu or a dialog; without one, Back keeps its system action | -| Pinch, then drag | Zoom (1× to 5×) and pan, single view | -| Double tap | Reset the zoom | -| Long press, single view | Save a screenshot, see [Screenshots](#screenshots) | -| Long press, multi-view | Play this cell's sound; again to mute | +| Input | Action | +|-----------------------------------------|-----------------------------------------------------------------------| +| Tap / **OK** / **Enter** (D-pad centre) | Open or close the menu | +| **Back** / **Esc** | Close the menu or a dialog; without one, Back keeps its system action | +| Pinch, then drag | Zoom (1× to 5×) and pan, single view | +| Double tap | Reset the zoom | +| Long press, single view | Save a screenshot, see [Screenshots](#screenshots) | +| Long press, multi-view | Play this cell's sound; again to mute | ## Remote control