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
12 changes: 10 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 32
versionName "2.07"
versionCode 33
versionName "2.08"

buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\""
}
Expand All @@ -50,6 +50,8 @@ android {
shrinkResources = true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig = signingConfigs.release
// No META-INF/version-control-info.textproto (#181): BuildConfig.GIT_HASH has the commit
vcsInfo.include = false
}
}

Expand All @@ -63,6 +65,12 @@ android {
unitTests.returnDefaultValues = true
}

// No dependency metadata in the APK and bundle (#181): there are no runtime dependencies
dependenciesInfo {
includeInApk = false
includeInBundle = false
}

lint {
// Known issues are recorded in the baseline; only new ones fail the build
baseline = file("lint-baseline.xml")
Expand Down
106 changes: 54 additions & 52 deletions android/app/src/main/java/com/openipc/decoder/DebugServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,10 @@ private static void closeQuietly(Socket 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). In single view the grid stays,
* with the camera on screen framed and the stream checks of the other slots (#100).
* Served as one line: no newlines and no optional spaces, every JS statement ends with ';'
* or '}', so the lines can be joined (#181).
*/
private static final String PAGE = "<!doctype html>\n"
private static final String PAGE = "<!doctype html>"
+ "<html><head><meta charset=\"utf-8\"><meta name=\"viewport\""
+ " content=\"width=device-width, initial-scale=1\"><title>OpenIPC Decoder</title>"
+ "<style>"
Expand Down Expand Up @@ -473,91 +475,91 @@ private static void closeQuietly(Socket s) {
+ "</header>"
+ "<main id=\"main\"><div id=\"wall\"><img id=\"snap\" alt=\"\"><div id=\"cells\">"
+ "</div></div><table id=\"list\"></table></main>"
+ "<script>\n"
+ "function $(id){return document.getElementById(id)}\n"
+ "<script>"
+ "function $(id){return document.getElementById(id)}"
+ "function esc(s){return String(s==null?'':s).replace(/[&<>\"]/g,function(c){return"
+ " {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]})}\n"
+ "{'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]})}"
// The wall keeps 16:9 (the snapshot's shape) and fills the space left; on a narrow
// screen it takes the full width and the cells are listed below it
+ "function fit(){var m=$('main'),w=m.clientWidth-16,h=m.clientHeight-16,small=w<720;\n"
+ "document.body.classList.toggle('small',small);\n"
+ "function fit(){var m=$('main'),w=m.clientWidth-16,h=m.clientHeight-16,small=w<720;"
+ "document.body.classList.toggle('small',small);"
+ "if(!small&&w*9/16>h)w=h*16/9;$('wall').style.width=Math.floor(w)+'px';"
+ "$('wall').style.height=Math.floor(w*9/16)+'px'}\n"
+ "window.onresize=fit;fit();\n"
+ "$('wall').style.height=Math.floor(w*9/16)+'px'}"
+ "window.onresize=fit;fit();"
+ "function later(f,ms){setTimeout(function(){if(document.hidden)document.addEventLis"
+ "tener('visibilitychange',function v(){if(!document.hidden){document.removeEventLis"
+ "tener('visibilitychange',v);f()}});else f()},ms)}\n"
+ "tener('visibilitychange',v);f()}});else f()},ms)}"
+ "function up(s){var d=Math.floor(s/86400),h=Math.floor(s%86400/3600),"
+ "m=Math.floor(s%3600/60);return (d?d+'d ':'')+(d||h?h+'h ':'')+m+'m'}\n"
+ "m=Math.floor(s%3600/60);return(d?d+'d ':'')+(d||h?h+'h ':'')+m+'m'}"
+ "function host(u){var m=/\\/\\/(?:[^@\\/]*@)?(\\[[^\\]]*\\]|[^\\/:?]*)/.exec(u||'');"
+ "return m?m[1]:''}\n"
+ "function num(v){return Math.round(v*10)/10}\n"
+ "return m?m[1]:''}"
+ "function num(v){return Math.round(v*10)/10}"
+ "function where(c){return esc(host(c.url))+(c.transport?' \u00b7 '+"
+ "esc(c.transport):'')+(c.audio===true?' \u00b7 \u266a':'')}\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"
+ "esc(c.transport):'')+(c.audio===true?' \u00b7 \u266a':'')}"
+ "function ok(c){return c.probe&&c.probe.state==='ok'}"
+ "function info(c){if(!c.video)return'<span class=\"'+(ok(c)?'ok':'bad')+'\">'+"
+ "esc(c.text)+'</span>';"
+ "var t=esc(c.text),r=/(\\d+)\\/(\\d+) fps/.exec(c.text||'');"
+ "if(r&&+r[1]<0.9*r[2])t=t.replace(r[0],'<span class=\"warn\">'+r[0]+'</span>');"
+ "if(c.lost_fps>0)t+=' \u00b7 <span class=\"bad\">lost '+num(c.lost_fps)"
+ "+'/s</span>';\n"
+ "return t}\n"
+ "+'/s</span>';"
+ "return t}"
// 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)+(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':'')"
+ "cls=c.on_screen?'cell on':'cell';"
+ "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"
+ "'</div></div>';"
+ "return'<div class=\"'+cls+'\"><div class=\"top\">'+top+'</div><div"
+ " class=\"bot\">'+info(c)+'</div></div>'}"
+ "function row(c){return'<tr><td>'+esc(c.slot)+'</td><td><span class=\"h\">"
+ "'+where(c)+'</span><br>'+info(c)+'</td></tr>'}"
+ "function render(s){"
+ "$('ver').textContent='v'+(s.version||'?')+' \u00b7 '+(s.git||'');\n"
+ "$('ver').textContent='v'+(s.version||'?')+' \u00b7 '+(s.git||'');"
+ "$('clock').textContent=s.time||'';$('up').textContent=s.uptime_s!=null?'up '+"
+ "up(s.uptime_s):'';\n"
+ "up(s.uptime_s):'';"
// Orange: the UI thread is stuck, or there is no activity behind the server at all
+ "var ui=s.ui_thread&&s.ui_thread!=='ok',none=!s.ui_thread;\n"
+ "var ui=s.ui_thread&&s.ui_thread!=='ok',none=!s.ui_thread;"
// Red: frames arrive, the device's decoder gives none; only a restart helps (#145)
+ "var stuck=s.decoder_stuck===true;\n"
+ "var stuck=s.decoder_stuck===true;"
+ "$('err').textContent=stuck?'video decoder not responding \u2014 restart the device'"
+ ":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"
+ ":ui?'UI '+s.ui_thread:none?(s.mode||'no data'):'';"
+ "$('dot').className='dot '+(stuck?'bad':ui||none?'warn':'ok');"
+ "var list,side=1;"
+ "if(s.mode==='paused'||!(s.cells||s.single||s.slots))"
+ "list=[{text:s.mode==='paused'?'App in background':(s.mode||'')}];\n"
+ "list=[{text:s.mode==='paused'?'App in background':(s.mode||'')}];"
// 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"
+ "else if(s.slots){list=s.slots.slice();side=list.length>4?3:2}"
// 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"
+ "while(list.length<side*side)list.push({slot:'',text:''})}"
+ "else if(s.single){var c=s.single;c.slot=s.active_slot;"
+ "c.text=c.video?(c.size||'')+' '+c.codec:c.status;list=[c]}\n"
+ "c.text=c.video?(c.size||'')+' '+c.codec:c.status;list=[c]}"
+ "$('cells').style.gridTemplateColumns='repeat('+side+',1fr)';"
+ "$('cells').style.gridTemplateRows='repeat('+side+',1fr)';\n"
+ "$('cells').style.gridTemplateRows='repeat('+side+',1fr)';"
+ "$('cells').innerHTML=list.map(cell).join('');$('list').innerHTML=list.map(row).joi"
+ "n('')}\n"
+ "n('')}"
+ "function tick(){fetch('/stats.json',{cache:'no-store'}).then(function(r){return"
+ " r.json()}).then(function(s){render(s)})\n"
+ " r.json()}).then(function(s){render(s)})"
+ ".catch(function(e){$('err').textContent='offline';$('dot').className='dot bad'}"
+ ").finally(function(){later(tick,1500)})}\n"
+ "var every=5,seg=$('every');try{every=+localStorage.getItem('every')||5}catch(x){}\n"
+ ").finally(function(){later(tick,1500)})}"
+ "var every=5,seg=$('every');try{every=+localStorage.getItem('every')||5}catch(x){}"
+ "function mark(){[].forEach.call(seg.children,function(b){b.className=+b.value===ev"
+ "ery?'on':''})}\n"
+ "ery?'on':''})}"
+ "seg.onclick=function(e){if(!e.target.value)return;every=+e.target.value;mark();"
+ "try{localStorage.setItem('every',every)}catch(x){}};mark();\n"
+ "var img=$('snap');img.onload=img.onerror=function(){later(snap,every*1000)};\n"
+ "function snap(){img.src='/snapshot.jpg?labels=0&t='+Date.now()}\n"
+ "try{localStorage.setItem('every',every)}catch(x){}};mark();"
+ "var img=$('snap');img.onload=img.onerror=function(){later(snap,every*1000)};"
+ "function snap(){img.src='/snapshot.jpg?labels=0&t='+Date.now()}"
// Info on or off (#141), remembered like the interval; Off until chosen: the page
// opens on the clean picture, every 5 s
+ "var shown=0,iseg=$('info');try{shown=localStorage.getItem('info')==='1'?1:0}catch(x){}\n"
+ "var shown=0,iseg=$('info');try{shown=localStorage.getItem('info')==='1'?1:0}catch(x){}"
+ "function imark(){[].forEach.call(iseg.children,function(b){b.className=+b.value===sh"
+ "own?'on':''});document.body.classList.toggle('clean',!shown)}\n"
+ "own?'on':''});document.body.classList.toggle('clean',!shown)}"
+ "iseg.onclick=function(e){if(!e.target.value)return;shown=+e.target.value;imark();"
+ "try{localStorage.setItem('info',shown)}catch(x){}};imark();\n"
+ "later(tick,0);later(snap,0);\n"
+ "</script></body></html>\n";
+ "try{localStorage.setItem('info',shown)}catch(x){}};imark();"
+ "later(tick,0);later(snap,0);"
+ "</script></body></html>";
}
Binary file modified android/app/src/main/res/mipmap/ic_launcher.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading