Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ While the app runs it serves its debug information over HTTP on port **8085**, f
- `http://<device>:8085/` — the snapshot fills the page, with each cell's camera, size, fps and losses (in red) drawn over it; the header shows the version, the device time and uptime. **Info On / Off** in the header hides the text over the cells (and the list below them on a narrow screen), leaving only the picture; the choice is remembered in the browser. A red note in the header says when the device's video decoder stopped responding (`decoder_stuck`);
- `http://<device>:8085/snapshot.jpg` (`?labels=0`: frames only) — a picture laid out like the screen, at the screen's resolution (at most 1920×1080), made from the frames the app shows right now; a cell without video is black with its status. Taken only when asked for; the page refreshes it every 3, 5 or 10 s while the tab is visible;
- `http://<device>:8085/stats.json` — the same as JSON, for scripts;
- In single view the page and the snapshot still show the whole grid (#100): the camera on screen is framed in blue, every other configured slot gets a short stream check (connect, a few seconds of packets, no decoding) about every 45 s while the page is open, and its cell says what was found, e.g. `Stream OK · H.265 · 25 fps · 3.1 Mbit/s · checked 12 s ago`, or the error. A slot of the camera on screen is never checked (some cameras take one client only), nor a `udp://` slot. `stats.json` has these cells under `slots`;
- `http://<device>:8085/log` — the last 500 lines of the app's log.

Only clients on the local network are answered (loopback, 10/8, 172.16/12, 192.168/16, link-local, IPv6 fc00::/7); others get 403. Logins and passwords are never shown.
Expand Down
1 change: 1 addition & 0 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ outgoing:
- `http://<приставка>:8085/` — снимок на всю страницу, поверх каждой ячейки камера, размер, fps и потери (красным); в шапке версия, время приставки и аптайм. Переключатель **Info On / Off** в шапке убирает текст поверх ячеек (и список под ними на узком экране), остаётся только картинка; выбор запоминается в браузере. Красная пометка в шапке сообщает, что видеодекодер устройства перестал отвечать (`decoder_stuck`);
- `http://<приставка>:8085/snapshot.jpg` (`?labels=0` — только кадры) — картинка с раскладкой экрана в разрешении экрана (не больше 1920×1080) из кадров, которые приложение показывает сейчас; ячейка без видео — чёрная с её статусом. Снимается только по запросу; страница обновляет её раз в 3, 5 или 10 с, пока вкладка видна;
- `http://<приставка>:8085/stats.json` — то же в JSON, для скриптов;
- В одиночном виде страница и снимок всё равно показывают всю сетку (#100): камера на экране обведена синей рамкой, а каждый другой настроенный слот, пока страница открыта, примерно раз в 45 с получает короткую проверку потока (подключение, несколько секунд пакетов, без декодирования); в его ячейке — результат, например `Stream OK · H.265 · 25 fps · 3.1 Mbit/s · checked 12 s ago`, или ошибка. Слот с камерой, которая на экране, не проверяется (некоторые камеры принимают только одного клиента), как и слоты `udp://`. В `stats.json` эти ячейки — в поле `slots`;
- `http://<приставка>:8085/log` — последние 500 строк лога приложения.

Отвечает только клиентам из локальной сети (loopback, 10/8, 172.16/12, 192.168/16, link-local, IPv6 fc00::/7), остальным — 403. Логины и пароли не показываются.
Expand Down
1 change: 1 addition & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ outgoing:
- `http://<设备>:8085/` — 快照铺满页面,每个画面上叠加摄像机、分辨率、帧率和丢帧(红色);页头显示版本、设备时间和运行时长。页头的 **Info On / Off** 开关可隐藏画面上的文字(以及窄屏时下方的列表),只保留画面;选择会保存在浏览器中。设备视频解码器停止响应时,页头会出现红色提示(`decoder_stuck`);
- `http://<设备>:8085/snapshot.jpg`(`?labels=0`:仅画面)— 按屏幕布局拼成的图片,分辨率与屏幕相同(最大 1920×1080),取自应用当前显示的画面;没有视频的画面为黑色并显示其状态。仅在请求时截取;页面在标签页可见时每 3、5 或 10 秒刷新一次;
- `http://<设备>:8085/stats.json` — 相同内容的 JSON,便于脚本使用;
- 单画面模式下,页面和快照仍显示完整网格(#100):屏幕上的摄像机用蓝框标出;页面打开期间,其他每个已配置的槽位约每 45 秒进行一次简短的流检测(连接、接收几秒数据包、不解码),其画面显示检测结果,例如 `Stream OK · H.265 · 25 fps · 3.1 Mbit/s · checked 12 s ago`,或错误信息。与屏幕上相同的摄像机的槽位不检测(部分摄像机只接受一个客户端),`udp://` 槽位也不检测。`stats.json` 中这些画面位于 `slots` 字段;
- `http://<设备>:8085/log` — 应用日志的最后 500 行。

仅响应本地网络中的客户端(loopback、10/8、172.16/12、192.168/16、链路本地地址、IPv6 fc00::/7),其他地址返回 403。不会显示用户名和密码。
Expand Down
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ android {
applicationId "com.openipc.decoder"
minSdk 21
targetSdk 36
versionCode 27
versionName "2.02"
versionCode 28
versionName "2.03"

buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\""
}
Expand Down
27 changes: 19 additions & 8 deletions android/app/src/main/java/com/openipc/decoder/DebugServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,8 @@ private static void closeQuietly(Socket s) {
* screen the snapshot takes the full width and the cells are listed below it (#103). It
* fetches /stats.json every 1.5 s and the snapshot (without labels) every 3, 5 or 10 s,
* both only while the tab is visible (#98). The Info switch hides the text over the cells
* and the list, leaving the header and the snapshot (#141).
* and the list, leaving the header and the snapshot (#141). In single view the grid stays,
* with the camera on screen framed and the stream checks of the other slots (#100).
*/
private static final String PAGE = "<!doctype html>\n"
+ "<html><head><meta charset=\"utf-8\"><meta name=\"viewport\""
Expand Down Expand Up @@ -445,6 +446,8 @@ private static void closeQuietly(Socket s) {
+ "align-items:center;justify-content:center;background:#000;color:#ff5a5a;"
+ "text-align:center;padding:8px;font-size:clamp(9px,4cqw,14px)}"
+ ".off b{font-size:clamp(16px,14cqh,30px);color:#666}"
// Single view (#100): the camera on screen framed, a stream found by a check green
+ ".cell.on{border:2px solid #3d8bff}.off.ok,.ok{color:#7fd07f}.off.none{color:#888}"
+ "#list{display:none;width:100%;border-collapse:collapse;font-size:13px}"
+ "#list td{padding:5px 6px;border-top:1px solid #262626;vertical-align:top}"
+ "#list td:first-child{font-weight:700;width:1.5em;color:#fff}"
Expand Down Expand Up @@ -491,19 +494,24 @@ private static void closeQuietly(Socket s) {
+ "function num(v){return Math.round(v*10)/10}\n"
+ "function where(c){return esc(host(c.url))+(c.transport?' \u00b7 '+"
+ "esc(c.transport):'')+(c.audio===true?' \u00b7 \u266a':'')}\n"
+ "function info(c){if(!c.video)return '<span class=\"bad\">'+esc(c.text)+'</span>';\n"
+ "function ok(c){return c.probe&&c.probe.state==='ok'}\n"
+ "function info(c){if(!c.video)return '<span class=\"'+(ok(c)?'ok':'bad')+'\">'+"
+ "esc(c.text)+'</span>';\n"
+ "var t=esc(c.text),r=/(\\d+)\\/(\\d+) fps/.exec(c.text||'');\n"
+ "if(r&&+r[1]<0.9*r[2])t=t.replace(r[0],'<span class=\"warn\">'+r[0]+'</span>');\n"
+ "if(c.lost_fps>0)t+=' \u00b7 <span class=\"bad\">lost '+num(c.lost_fps)"
+ "+'/s</span>';\n"
+ "return t}\n"
// One cell: slot, camera and transport on top; size, fps and losses below
+ "function cell(c){var top='<span class=\"slot\">'+esc(c.slot)+'</span><span"
+ " class=\"dim\">'+where(c)+'</span>';\n"
+ "if(!c.video)return '<div class=\"cell\"><div class=\"off\"><b>'+esc(c.slot)+'</b>"
+ "<span>'+esc(c.text)+'</span></div><div class=\"top\">'+top+'</div></div>';\n"
+ "return '<div class=\"cell\"><div class=\"top\">'+top+'</div><div class=\"bot\">"
+ "'+info(c)+'</div></div>'}\n"
+ " class=\"dim\">'+where(c)+(c.on_screen?' \u00b7 on screen':'')+'</span>',"
+ "cls=c.on_screen?'cell on':'cell';\n"
+ "if(!c.video)return '<div class=\"'+cls+'\"><div class=\"off'+(ok(c)?' ok':'')"
+ "+(c.text==='" + StreamProbes.NOT_CONFIGURED + "'?' none':'')+'\">"
+ "<b>'+esc(c.slot)+'</b><span>'+esc(c.text)+'</span></div><div class=\"top\">'+top+"
+ "'</div></div>';\n"
+ "return '<div class=\"'+cls+'\"><div class=\"top\">'+top+'</div><div"
+ " class=\"bot\">'+info(c)+'</div></div>'}\n"
+ "function row(c){return '<tr><td>'+esc(c.slot)+'</td><td><span class=\"h\">"
+ "'+where(c)+'</span><br>'+info(c)+'</td></tr>'}\n"
+ "function render(s){"
Expand All @@ -518,8 +526,11 @@ private static void closeQuietly(Socket s) {
+ ":ui?'UI '+s.ui_thread:none?(s.mode||'no data'):'';\n"
+ "$('dot').className='dot '+(stuck?'bad':ui||none?'warn':'ok');\n"
+ "var list,side=1;\n"
+ "if(s.mode==='paused'||!(s.cells||s.single))"
+ "if(s.mode==='paused'||!(s.cells||s.single||s.slots))"
+ "list=[{text:s.mode==='paused'?'App in background':(s.mode||'')}];\n"
// Single and U view (#100): the grid with the camera on screen and the checks of
// the others
+ "else if(s.slots){list=s.slots.slice();side=list.length>4?3:2}\n"
// The U view is the U cell alone; the 3x3 grid has it as its ninth cell (#87)
+ "else if(s.cells){side=s.mode==='udp'?1:s.cells.length>4?3:2;list=s.cells.slice();"
+ "while(list.length<side*side)list.push({slot:'',text:''})}\n"
Expand Down
Loading
Loading