Private
Public Access
Release 0.12.0
This commit is contained in:
1 parent
f74a658097
commit
7f6eeab39e
48 files changed
+3617
-1266
No files matched your search
+340
-105
@@ -1,117 +1,352 @@
|
||||
'use strict';
|
||||
var pending=[];
|
||||
var targetDirty=false,receiver='Hyperion';
|
||||
var profileDirty=false,profile='auto',advancedProfile=false;
|
||||
var profileNames={auto:'Auto',g4:'LG G4 (measured)',average:'Average (uniform)',custom:'Custom model'};
|
||||
function showProfile(){
|
||||
document.getElementById('profile').textContent='Profile: '+profileNames[profile];
|
||||
var visible=profile==='custom' && advancedProfile;
|
||||
document.getElementById('customEditor').style.display=visible?'block':'none';
|
||||
document.getElementById('customProfile').disabled=!visible;
|
||||
document.getElementById('editProfile').disabled=profile!=='custom';
|
||||
document.getElementById('editProfile').textContent=visible?'Hide profile JSON':'Edit profile JSON';
|
||||
var pending = [];
|
||||
var actionError = '';
|
||||
|
||||
function showActionError(message) {
|
||||
actionError = message || '';
|
||||
document.getElementById('error').textContent = actionError;
|
||||
}
|
||||
|
||||
function call(method,done,data){callUri('luna://org.webosbrew.histogramhyperion.service/'+method,done,data);}
|
||||
function callUri(uri,done,data){
|
||||
if(typeof PalmServiceBridge==='undefined'){done({returnValue:false,errorText:'This app requires an LG TV with the native service installed.'});return;}
|
||||
var bridge=new PalmServiceBridge(),timer,finished=false;
|
||||
function finish(value){if(finished)return;finished=true;clearTimeout(timer);var i=pending.indexOf(bridge);if(i>=0)pending.splice(i,1);done(value);}
|
||||
bridge.onservicecallback=function(raw){try{finish(JSON.parse(raw));}catch(e){finish({returnValue:false,errorText:'Invalid service response'});}};
|
||||
pending.push(bridge);timer=setTimeout(function(){bridge.cancel();finish({returnValue:false,errorText:'The service is not responding. Check the installation and service log.'});},uri.indexOf('hbchannel')>=0?30000:10000);
|
||||
bridge.call(uri,JSON.stringify(data||{}));
|
||||
function clearActionError() {
|
||||
showActionError('');
|
||||
}
|
||||
function display(result){
|
||||
if(typeof result.uid==='number'){
|
||||
document.getElementById('grantRoot').style.display=result.uid===0?'none':'inline-block';
|
||||
document.getElementById('rootHint').style.display=result.uid===0?'none':'block';
|
||||
}
|
||||
if(result.calibrationProfile && !profileDirty){
|
||||
profile=result.calibrationProfile;showProfile();
|
||||
document.getElementById('customProfile').value=JSON.stringify({model:result.customModel||result.tvModel||'TV-MODEL',points:result.customPoints&&result.customPoints.length?result.customPoints:[]},null,2);
|
||||
}
|
||||
if(result.effectiveCalibration)document.getElementById('profileStatus').textContent='TV: '+(result.tvModel||'Unknown')+' · Active profile: '+profileNames[result.effectiveCalibration]+(result.effectiveCalibration==='average'?' · Spatial calibration needed for this model':'');
|
||||
var targetDirty = false,
|
||||
receiver = 'Hyperion';
|
||||
var profileDirty = false,
|
||||
profile = 'auto',
|
||||
advancedProfile = false;
|
||||
var profileNames = {
|
||||
auto: 'Auto',
|
||||
g4: 'LG G4 (measured)',
|
||||
average: 'Average (uniform)',
|
||||
custom: 'Custom model'
|
||||
};
|
||||
|
||||
if(result.host && !targetDirty){
|
||||
document.getElementById('host').value=result.host;document.getElementById('port').value=result.port;document.getElementById('piccapPort').value=result.piccapPort||19400;receiver=result.receiver;
|
||||
document.getElementById('receiver').textContent='Receiver: '+receiver;
|
||||
}
|
||||
document.getElementById('token').placeholder=result.tokenSet?'Saved token (leave blank to keep)':'No token saved';
|
||||
if(result.endpoint)document.getElementById('endpoint').textContent=result.receiver+' · '+result.endpoint+' · Priority 149 · 10 updates per second';
|
||||
var t=result.connectionTest;
|
||||
if(t)document.getElementById('testResult').textContent='Connection test: '+t.state+(t.state==='Passed'?' · '+t.ledCount+' LEDs · '+t.elapsedMs+' ms':'')+(t.error?' · '+t.error:'');
|
||||
document.getElementById('testConnection').disabled=!!t && ['Connecting','Sending request','Checking authentication','Reading LED layout'].indexOf(t.state)>=0;
|
||||
if(result.version)document.getElementById('diagnostics').textContent='Version '+result.version+' · '+(result.uid===0?'Root service':'Service needs root elevation')+' · '+result.captureState+' · Playback: '+result.playbackProtection+' · Capture recoveries: '+result.captureRecoveries+' (last: '+result.lastRecoveryMs+' ms)'+(result.lastOutputAgeMs>=0?' · Last histogram output: '+result.lastOutputAgeMs+' ms ago':'')+(result.retryScheduled?' · Retry in '+result.retryInMs+' ms · Attempts: '+result.retryAttempts:'')+(result.piccapError?' · '+result.piccapError:'')+(result.piccapCooldownMs?' · PicCap retry in '+Math.ceil(result.piccapCooldownMs/1000)+' s':'')+' · Layout updates: '+(result.layoutUpdates||0)+(result.layoutError?' · Layout: '+result.layoutError:'');
|
||||
var handover=document.getElementById('handover');handover.disabled=!result.piccapInstalled;
|
||||
handover.setAttribute('aria-checked',String(!!result.piccapHandover));
|
||||
handover.textContent=result.piccapInstalled?'Auto PicCap: '+(result.piccapHandover?'On':'Off'):'Auto PicCap: Not installed';
|
||||
if(typeof result.autostart==='boolean'){var toggle=document.getElementById('autostart');toggle.setAttribute('aria-checked',String(result.autostart));toggle.textContent='Autostart: '+(result.autostart?'On':'Off');}
|
||||
document.getElementById('error').textContent=result.errorText||result.error||'';
|
||||
document.getElementById('status').textContent=result.returnValue===false?'Service unavailable':(result.running?'Running':'Stopped')+' · '+(result.frames||0)+' acknowledged color updates';
|
||||
if(result.activeSource==='PicCap'){
|
||||
document.getElementById('status').textContent='PicCap · '+(result.running?'Running':'Switching capture source')+' · '+(result.piccapConnected?'Receiver connected':'Waiting for receiver');
|
||||
document.getElementById('regions').textContent='';return;
|
||||
}
|
||||
if(result.piccapHandover)document.getElementById('status').textContent+=' · HyperDRM · '+result.playbackProtection;
|
||||
if(result.piccapHandover && !result.piccapTargetCompatible)document.getElementById('status').textContent+=' · Auto PicCap unavailable for remote IPv6 target';
|
||||
if(result.colorValid && result.effectiveCalibration==='average')document.getElementById('status').textContent+=' · Average color output';
|
||||
if(result.colorValid)document.getElementById('status').textContent+=result.spatialOutput?' · '+result.ledCount+' LEDs from 16 color regions'+(result.receiver==='HyperHDR'?' · Spatial image output':''):' · Output RGB: '+result.outputColor.join(', ');
|
||||
else if(result.returnValue!==false)document.getElementById('status').textContent+=' · No current color output';
|
||||
if(result.blackBarDetection && result.running)document.getElementById('status').textContent+=result.letterboxDetected?' · Top/bottom black bars detected':' · Full-screen capture';
|
||||
if(result.spatialInterpolation)document.getElementById('status').textContent+=' · Smooth color transitions';
|
||||
if(result.holdingColors)document.getElementById('status').textContent+=' · Holding last valid colors (missing data)';
|
||||
if(result.sourceFade)document.getElementById('status').textContent+=' · Blending capture sources';
|
||||
if(result.retryScheduled)document.getElementById('status').textContent+=' · Retrying receiver or capture startup';
|
||||
if(result.retryPaused)document.getElementById('status').textContent+=' · Automatic retry paused until Start';
|
||||
var box=document.getElementById('regions');box.textContent='';
|
||||
(result.regions||[]).forEach(function(rgb,index){var e=document.createElement('div');e.className='swatch';e.style.backgroundColor='rgb('+rgb.join(',')+')';e.title='Histogram '+index;box.appendChild(e);});
|
||||
function showProfile() {
|
||||
document.getElementById('profile').textContent = 'Profile: ' + profileNames[profile];
|
||||
var visible = profile === 'custom' && advancedProfile;
|
||||
document.getElementById('customEditor').style.display = visible ? 'block' : 'none';
|
||||
document.getElementById('customProfile').disabled = !visible;
|
||||
document.getElementById('editProfile').disabled = profile !== 'custom';
|
||||
document.getElementById('editProfile').textContent = visible ? 'Hide profile JSON' : 'Edit profile JSON';
|
||||
}
|
||||
document.getElementById('start').onclick=function(){call('start',function(r){if(r.returnValue===false)display(r);else call('status',display);});};
|
||||
document.getElementById('stop').onclick=function(){call('stop',function(){call('status',display);});};
|
||||
document.getElementById('refresh').onclick=function(){call('status',display);};
|
||||
call('status',display);setInterval(function(){if(!pending.length)call('status',display);},3000);
|
||||
|
||||
function call(method, done, data) {
|
||||
if (['start', 'stop', 'autostart', 'handover', 'settings', 'testConnection', 'calibration'].indexOf(method) >= 0) clearActionError();
|
||||
callUri('luna://org.webosbrew.histogramhyperion.service/' + method, function(result) {
|
||||
if (method !== 'status' && result.returnValue === false) showActionError(result.errorText || 'The action failed.');
|
||||
done(result);
|
||||
}, data);
|
||||
}
|
||||
|
||||
function callUri(uri, done, data) {
|
||||
if (typeof PalmServiceBridge === 'undefined') {
|
||||
done({
|
||||
returnValue: false,
|
||||
errorText: 'This app requires an LG TV with the native service installed.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
var bridge = new PalmServiceBridge(),
|
||||
timer, finished = false;
|
||||
|
||||
function finish(value) {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
clearTimeout(timer);
|
||||
var i = pending.indexOf(bridge);
|
||||
if (i >= 0) pending.splice(i, 1);
|
||||
done(value);
|
||||
}
|
||||
bridge.onservicecallback = function(raw) {
|
||||
try {
|
||||
finish(JSON.parse(raw));
|
||||
} catch (e) {
|
||||
finish({
|
||||
returnValue: false,
|
||||
errorText: 'Invalid service response'
|
||||
});
|
||||
}
|
||||
};
|
||||
pending.push(bridge);
|
||||
timer = setTimeout(function() {
|
||||
bridge.cancel();
|
||||
finish({
|
||||
returnValue: false,
|
||||
errorText: 'The service is not responding. Check the installation and service log.'
|
||||
});
|
||||
}, uri.indexOf('hbchannel') >= 0 ? 30000 : 10000);
|
||||
bridge.call(uri, JSON.stringify(data || {}));
|
||||
}
|
||||
|
||||
function display(result) {
|
||||
if (typeof result.uid === 'number') {
|
||||
document.getElementById('grantRoot').style.display = result.uid === 0 ? 'none' : 'inline-block';
|
||||
document.getElementById('rootHint').style.display = result.uid === 0 ? 'none' : 'block';
|
||||
}
|
||||
if (result.calibrationProfile && !profileDirty) {
|
||||
profile = result.calibrationProfile;
|
||||
showProfile();
|
||||
document.getElementById('customProfile').value = JSON.stringify({
|
||||
model: result.customModel || result.tvModel || 'TV-MODEL',
|
||||
points: result.customPoints && result.customPoints.length ? result.customPoints : []
|
||||
}, null, 2);
|
||||
}
|
||||
if (result.effectiveCalibration) document.getElementById('profileStatus').textContent = 'TV: ' + (result.tvModel || 'Unknown') + ' · Active profile: ' + profileNames[result.effectiveCalibration] + (result.effectiveCalibration === 'average' ? ' · Spatial calibration needed for this model' : '');
|
||||
|
||||
if (result.host && !targetDirty) {
|
||||
document.getElementById('host').value = result.host;
|
||||
document.getElementById('port').value = result.port;
|
||||
document.getElementById('piccapPort').value = result.piccapPort || 19400;
|
||||
receiver = result.receiver;
|
||||
document.getElementById('receiver').textContent = 'Receiver: ' + receiver;
|
||||
}
|
||||
document.getElementById('token').placeholder = result.tokenSet ? 'Saved token (leave blank to keep)' : 'No token saved';
|
||||
if (result.endpoint) document.getElementById('endpoint').textContent = result.receiver + ' · ' + result.endpoint + ' · Priority 149 · 10 updates per second';
|
||||
var t = result.connectionTest;
|
||||
if (t) document.getElementById('testResult').textContent = 'Connection test: ' + t.state + (t.state === 'Passed' ? ' · ' + t.ledCount + ' LEDs · ' + t.elapsedMs + ' ms' : '') + (t.error ? ' · ' + t.error : '');
|
||||
document.getElementById('testConnection').disabled = !!t && ['Connecting', 'Sending request', 'Checking authentication', 'Reading LED layout'].indexOf(t.state) >= 0;
|
||||
if (result.version) document.getElementById('diagnostics').textContent = 'Version ' + result.version + ' · ' + (result.uid === 0 ? 'Root service' : 'Service needs root elevation') + ' · ' + result.captureState + ' · Playback: ' + result.playbackProtection + ' · Capture recoveries: ' + result.captureRecoveries + ' (last: ' + result.lastRecoveryMs + ' ms)' + (result.lastOutputAgeMs >= 0 ? ' · Last histogram output: ' + result.lastOutputAgeMs + ' ms ago' : '') + (result.retryScheduled ? ' · Retry in ' + result.retryInMs + ' ms · Attempts: ' + result.retryAttempts : '') + (result.piccapError ? ' · ' + result.piccapError : '') + (result.piccapCooldownMs ? ' · PicCap retry in ' + Math.ceil(result.piccapCooldownMs / 1000) + ' s' : '') + ' · Layout updates: ' + (result.layoutUpdates || 0) + (result.layoutError ? ' · Layout: ' + result.layoutError : '');
|
||||
var handover = document.getElementById('handover');
|
||||
handover.disabled = !result.piccapInstalled;
|
||||
handover.setAttribute('aria-checked', String(!!result.piccapHandover));
|
||||
handover.textContent = result.piccapInstalled ? 'Auto PicCap: ' + (result.piccapHandover ? 'On' : 'Off') : 'Auto PicCap: Not installed';
|
||||
if (typeof result.autostart === 'boolean') {
|
||||
var toggle = document.getElementById('autostart');
|
||||
toggle.setAttribute('aria-checked', String(result.autostart));
|
||||
toggle.textContent = 'Autostart: ' + (result.autostart ? 'On' : 'Off');
|
||||
}
|
||||
document.getElementById('error').textContent = actionError || result.errorText || result.error || '';
|
||||
document.getElementById('status').textContent = result.returnValue === false ? 'Service unavailable' : (result.running ? 'Running' : 'Stopped') + ' · ' + (result.frames || 0) + ' acknowledged color updates';
|
||||
if (result.activeSource === 'PicCap') {
|
||||
document.getElementById('status').textContent = 'PicCap · ' + (result.running ? 'Running' : 'Switching capture source') + ' · ' + (result.piccapConnected ? 'Receiver connected' : 'Waiting for receiver');
|
||||
document.getElementById('regions').textContent = '';
|
||||
return;
|
||||
}
|
||||
if (result.piccapHandover) document.getElementById('status').textContent += ' · HyperDRM · ' + result.playbackProtection;
|
||||
if (result.piccapHandover && !result.piccapTargetCompatible) document.getElementById('status').textContent += ' · Auto PicCap unavailable for remote IPv6 target';
|
||||
if (result.colorValid && result.effectiveCalibration === 'average') document.getElementById('status').textContent += ' · Average color output';
|
||||
if (result.colorValid) document.getElementById('status').textContent += result.spatialOutput ? ' · ' + result.ledCount + ' LEDs from 16 color regions' + (result.receiver === 'HyperHDR' ? ' · Spatial image output' : '') : ' · Output RGB: ' + result.outputColor.join(', ');
|
||||
else if (result.returnValue !== false) document.getElementById('status').textContent += ' · No current color output';
|
||||
if (result.blackBarDetection && result.running) document.getElementById('status').textContent += result.letterboxDetected ? ' · Top/bottom black bars detected' : ' · Full-screen capture';
|
||||
if (result.spatialInterpolation) document.getElementById('status').textContent += ' · Smooth color transitions';
|
||||
if (result.holdingColors) document.getElementById('status').textContent += ' · Holding last valid colors (missing data)';
|
||||
if (result.sourceFade) document.getElementById('status').textContent += ' · Blending capture sources';
|
||||
if (result.retryScheduled) document.getElementById('status').textContent += ' · Retrying receiver or capture startup';
|
||||
if (result.retryPaused) document.getElementById('status').textContent += ' · Automatic retry paused until Start';
|
||||
var box = document.getElementById('regions');
|
||||
box.textContent = '';
|
||||
(result.regions || []).forEach(function(rgb, index) {
|
||||
var e = document.createElement('div');
|
||||
e.className = 'swatch';
|
||||
e.style.backgroundColor = 'rgb(' + rgb.join(',') + ')';
|
||||
e.title = 'Histogram ' + index;
|
||||
box.appendChild(e);
|
||||
});
|
||||
}
|
||||
document.getElementById('start').onclick = function() {
|
||||
call('start', function(r) {
|
||||
if (r.returnValue === false) display(r);
|
||||
else call('status', display);
|
||||
});
|
||||
};
|
||||
document.getElementById('stop').onclick = function() {
|
||||
call('stop', function() {
|
||||
call('status', display);
|
||||
});
|
||||
};
|
||||
document.getElementById('refresh').onclick = function() {
|
||||
call('status', display);
|
||||
};
|
||||
call('status', display);
|
||||
setInterval(function() {
|
||||
if (!pending.length) call('status', display);
|
||||
}, 3000);
|
||||
document.getElementById('start').focus();
|
||||
|
||||
var repairingRoot=false,rootWaiters=[];
|
||||
function repairRoot(done){
|
||||
rootWaiters.push(done);if(repairingRoot)return;repairingRoot=true;document.getElementById('grantRoot').disabled=true;
|
||||
document.getElementById('error').textContent='Granting root access to HyperDRM...';
|
||||
function finish(error){repairingRoot=false;document.getElementById('grantRoot').disabled=false;if(error)document.getElementById('error').textContent=error;var waiters=rootWaiters;rootWaiters=[];waiters.forEach(function(callback){callback(!error);});}
|
||||
callUri('luna://org.webosbrew.hbchannel.service/elevateService',function(r){
|
||||
if(r.returnValue===false){finish('Homebrew Channel could not grant root access: '+(r.errorText||'Check Homebrew Channel installation.'));return;}
|
||||
call('restartService',function(r){
|
||||
if(r.returnValue===false){finish(r.errorText||'Could not restart the HyperDRM service.');return;}
|
||||
var attempts=0;
|
||||
function check(){call('status',function(s){if(s.uid===0){display(s);finish();}else if(++attempts<12)setTimeout(check,1000);else finish('The service is still missing root access. Elevate HyperDRM in Homebrew Channel and reopen the app.');});}
|
||||
setTimeout(check,1500);
|
||||
});
|
||||
},{id:'org.webosbrew.histogramhyperion.service'});
|
||||
var repairingRoot = false,
|
||||
rootWaiters = [];
|
||||
|
||||
function repairRoot(done) {
|
||||
clearActionError();
|
||||
rootWaiters.push(done);
|
||||
if (repairingRoot) return;
|
||||
repairingRoot = true;
|
||||
document.getElementById('grantRoot').disabled = true;
|
||||
showActionError('Granting root access to HyperDRM...');
|
||||
|
||||
function finish(error) {
|
||||
repairingRoot = false;
|
||||
document.getElementById('grantRoot').disabled = false;
|
||||
if (error) showActionError(error);
|
||||
var waiters = rootWaiters;
|
||||
rootWaiters = [];
|
||||
waiters.forEach(function(callback) {
|
||||
callback(!error);
|
||||
});
|
||||
}
|
||||
callUri('luna://org.webosbrew.hbchannel.service/elevateService', function(r) {
|
||||
if (r.returnValue === false) {
|
||||
finish('Homebrew Channel could not grant root access: ' + (r.errorText || 'Check Homebrew Channel installation.'));
|
||||
return;
|
||||
}
|
||||
call('restartService', function(r) {
|
||||
if (r.returnValue === false) {
|
||||
finish(r.errorText || 'Could not restart the HyperDRM service.');
|
||||
return;
|
||||
}
|
||||
var attempts = 0;
|
||||
|
||||
function check() {
|
||||
call('status', function(s) {
|
||||
if (s.uid === 0) {
|
||||
display(s);
|
||||
finish();
|
||||
} else if (++attempts < 12) setTimeout(check, 1000);
|
||||
else finish('The service is still missing root access. Elevate HyperDRM in Homebrew Channel and reopen the app.');
|
||||
});
|
||||
}
|
||||
setTimeout(check, 1500);
|
||||
});
|
||||
}, {
|
||||
id: 'org.webosbrew.histogramhyperion.service'
|
||||
});
|
||||
}
|
||||
document.getElementById('grantRoot').onclick=function(){repairRoot(function(ok){if(ok)call('status',display);});};
|
||||
document.getElementById('autostart').onclick=function(){
|
||||
var button=this,enabled=button.getAttribute('aria-checked')!=='true';button.disabled=true;
|
||||
function save(){call('autostart',function(r){
|
||||
if(r.rootRequired){repairRoot(function(ok){if(ok)save();else button.disabled=false;});return;}
|
||||
button.disabled=false;if(r.returnValue===false)document.getElementById('error').textContent=r.errorText;else call('status',display);
|
||||
},{enabled:enabled});}
|
||||
save();
|
||||
document.getElementById('grantRoot').onclick = function() {
|
||||
repairRoot(function(ok) {
|
||||
if (ok) call('status', display);
|
||||
});
|
||||
};
|
||||
document.getElementById('autostart').onclick = function() {
|
||||
var button = this,
|
||||
enabled = button.getAttribute('aria-checked') !== 'true';
|
||||
button.disabled = true;
|
||||
|
||||
function save() {
|
||||
call('autostart', function(r) {
|
||||
if (r.rootRequired) {
|
||||
repairRoot(function(ok) {
|
||||
if (ok) save();
|
||||
else button.disabled = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
button.disabled = false;
|
||||
if (r.returnValue === false) showActionError(r.errorText);
|
||||
else call('status', display);
|
||||
}, {
|
||||
enabled: enabled
|
||||
});
|
||||
}
|
||||
save();
|
||||
};
|
||||
|
||||
document.getElementById('handover').onclick=function(){var button=this;button.disabled=true;call('handover',function(r){button.disabled=false;if(r.returnValue===false)document.getElementById('error').textContent=r.errorText;else call('status',display);},{enabled:button.getAttribute('aria-checked')!=='true'});};
|
||||
['host','port','piccapPort','token','clearToken'].forEach(function(id){document.getElementById(id).addEventListener('input',function(){targetDirty=true;});});
|
||||
document.getElementById('receiver').onclick=function(){receiver=receiver==='Hyperion'?'HyperHDR':'Hyperion';this.textContent='Receiver: '+receiver;targetDirty=true;};
|
||||
document.getElementById('saveTarget').onclick=function(){
|
||||
var port=Number(document.getElementById('port').value),button=this;
|
||||
if(!Number.isInteger(port)||port<1||port>65535){document.getElementById('error').textContent='JSON port must be an integer from 1 to 65535.';return;}
|
||||
var flatPort=Number(document.getElementById('piccapPort').value);
|
||||
if(!Number.isInteger(flatPort)||flatPort<1||flatPort>65535){document.getElementById('error').textContent='PicCap FlatBuffer port must be an integer from 1 to 65535.';return;}
|
||||
var settings={piccapPort:flatPort,host:document.getElementById('host').value.trim(),port:port,receiver:receiver,clearToken:document.getElementById('clearToken').checked};
|
||||
var token=document.getElementById('token').value.trim();if(token)settings.token=token;
|
||||
button.disabled=true;call('settings',function(r){button.disabled=false;if(r.returnValue===false){document.getElementById('error').textContent=r.errorText;return;}targetDirty=false;document.getElementById('token').value='';document.getElementById('clearToken').checked=false;call('status',display);},settings);
|
||||
document.getElementById('handover').onclick = function() {
|
||||
var button = this;
|
||||
button.disabled = true;
|
||||
call('handover', function(r) {
|
||||
button.disabled = false;
|
||||
if (r.returnValue === false) showActionError(r.errorText);
|
||||
else call('status', display);
|
||||
}, {
|
||||
enabled: button.getAttribute('aria-checked') !== 'true'
|
||||
});
|
||||
};
|
||||
document.getElementById('testConnection').onclick=function(){if(targetDirty){document.getElementById('error').textContent='Save receiver settings before testing the connection.';return;}this.disabled=true;call('testConnection',function(r){if(r.returnValue===false)document.getElementById('error').textContent=r.errorText;call('status',display);});};
|
||||
document.getElementById('profile').onclick=function(){var keys=['auto','g4','average','custom'];profile=keys[(keys.indexOf(profile)+1)%keys.length];profileDirty=true;showProfile();};
|
||||
document.getElementById('customProfile').addEventListener('input',function(){profileDirty=true;});
|
||||
document.getElementById('saveProfile').onclick=function(){var data={profile:profile},button=this;if(profile==='custom'){try{var parsed=JSON.parse(document.getElementById('customProfile').value);data.model=parsed.model;data.points=parsed.points;}catch(e){document.getElementById('error').textContent='Custom profile must be valid JSON.';return;}}button.disabled=true;call('calibration',function(r){button.disabled=false;if(r.returnValue===false){document.getElementById('error').textContent=r.errorText;return;}profileDirty=false;call('status',display);},data);};
|
||||
document.getElementById('editProfile').onclick=function(){advancedProfile=!advancedProfile;showProfile();if(advancedProfile)document.getElementById('customProfileField').focus();};
|
||||
setupRemoteNavigation(['overviewTab','receiverTab','calibrationTab','grantRoot','start','stop','refresh','autostart','handover','calibrationShortcut','receiver','hostField','portField','piccapPortField','tokenField','clearToken','saveTarget','testConnection','calibrate','validateCalibration','profile','saveProfile','editProfile','customProfileField','cancelCalibration'].map(function(id){return document.getElementById(id);}),document,window);
|
||||
['host', 'port', 'piccapPort', 'token', 'clearToken'].forEach(function(id) {
|
||||
document.getElementById(id).addEventListener('input', function() {
|
||||
targetDirty = true;
|
||||
});
|
||||
});
|
||||
document.getElementById('receiver').onclick = function() {
|
||||
receiver = receiver === 'Hyperion' ? 'HyperHDR' : 'Hyperion';
|
||||
this.textContent = 'Receiver: ' + receiver;
|
||||
targetDirty = true;
|
||||
};
|
||||
document.getElementById('saveTarget').onclick = function() {
|
||||
var port = Number(document.getElementById('port').value),
|
||||
button = this;
|
||||
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
||||
showActionError('JSON port must be an integer from 1 to 65535.');
|
||||
return;
|
||||
}
|
||||
var flatPort = Number(document.getElementById('piccapPort').value);
|
||||
if (!Number.isInteger(flatPort) || flatPort < 1 || flatPort > 65535) {
|
||||
showActionError('PicCap FlatBuffer port must be an integer from 1 to 65535.');
|
||||
return;
|
||||
}
|
||||
var settings = {
|
||||
piccapPort: flatPort,
|
||||
host: document.getElementById('host').value.trim(),
|
||||
port: port,
|
||||
receiver: receiver,
|
||||
clearToken: document.getElementById('clearToken').checked
|
||||
};
|
||||
var token = document.getElementById('token').value.trim();
|
||||
if (token) settings.token = token;
|
||||
button.disabled = true;
|
||||
call('settings', function(r) {
|
||||
button.disabled = false;
|
||||
if (r.returnValue === false) {
|
||||
showActionError(r.errorText);
|
||||
return;
|
||||
}
|
||||
targetDirty = false;
|
||||
document.getElementById('token').value = '';
|
||||
document.getElementById('clearToken').checked = false;
|
||||
call('status', display);
|
||||
}, settings);
|
||||
};
|
||||
document.getElementById('testConnection').onclick = function() {
|
||||
if (targetDirty) {
|
||||
showActionError('Save receiver settings before testing the connection.');
|
||||
return;
|
||||
}
|
||||
this.disabled = true;
|
||||
call('testConnection', function(r) {
|
||||
if (r.returnValue === false) showActionError(r.errorText);
|
||||
call('status', display);
|
||||
});
|
||||
};
|
||||
document.getElementById('profile').onclick = function() {
|
||||
var keys = ['auto', 'g4', 'average', 'custom'];
|
||||
profile = keys[(keys.indexOf(profile) + 1) % keys.length];
|
||||
profileDirty = true;
|
||||
showProfile();
|
||||
};
|
||||
document.getElementById('customProfile').addEventListener('input', function() {
|
||||
profileDirty = true;
|
||||
});
|
||||
document.getElementById('saveProfile').onclick = function() {
|
||||
var data = {
|
||||
profile: profile
|
||||
},
|
||||
button = this;
|
||||
if (profile === 'custom') {
|
||||
try {
|
||||
var parsed = JSON.parse(document.getElementById('customProfile').value);
|
||||
data.model = parsed.model;
|
||||
data.points = parsed.points;
|
||||
} catch (e) {
|
||||
showActionError('Custom profile must be valid JSON.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
button.disabled = true;
|
||||
call('calibration', function(r) {
|
||||
button.disabled = false;
|
||||
if (r.returnValue === false) {
|
||||
showActionError(r.errorText);
|
||||
return;
|
||||
}
|
||||
profileDirty = false;
|
||||
call('status', display);
|
||||
}, data);
|
||||
};
|
||||
document.getElementById('editProfile').onclick = function() {
|
||||
advancedProfile = !advancedProfile;
|
||||
showProfile();
|
||||
if (advancedProfile) document.getElementById('customProfileField').focus();
|
||||
};
|
||||
setupRemoteNavigation(['overviewTab', 'receiverTab', 'calibrationTab', 'grantRoot', 'start', 'stop', 'refresh', 'autostart', 'handover', 'calibrationShortcut', 'receiver', 'hostField', 'portField', 'piccapPortField', 'tokenField', 'clearToken', 'saveTarget', 'testConnection', 'calibrate', 'validateCalibration', 'profile', 'saveProfile', 'editProfile', 'customProfileField', 'cancelCalibration'].map(function(id) {
|
||||
return document.getElementById(id);
|
||||
}), document, window);
|
||||
+14
-1
@@ -1 +1,14 @@
|
||||
{"id": "org.webosbrew.histogramhyperion", "version": "0.11.0", "vendor": "Local development", "type": "web", "main": "index.html", "title": "HyperDRM", "icon": "icon.png", "resolution": "1920x1080", "disableBackHistoryAPI": true, "largeIcon": "large-icon.png", "appDescription": "Ambient lighting for webOS, Hyperion and HyperHDR", "sourceUrl": "https://gitea.adnau.de/matze19999/HyperDRM"}
|
||||
{
|
||||
"id": "org.webosbrew.histogramhyperion",
|
||||
"version": "0.12.0",
|
||||
"vendor": "Local development",
|
||||
"type": "web",
|
||||
"main": "index.html",
|
||||
"title": "HyperDRM",
|
||||
"icon": "icon.png",
|
||||
"resolution": "1920x1080",
|
||||
"disableBackHistoryAPI": true,
|
||||
"largeIcon": "large-icon.png",
|
||||
"appDescription": "Ambient lighting for webOS, Hyperion and HyperHDR",
|
||||
"sourceUrl": "https://gitea.adnau.de/matze19999/HyperDRM"
|
||||
}
|
||||
+78
-32
@@ -1,35 +1,81 @@
|
||||
/* Pure measurement processing; the video uses 32 clockwise edge patches. */
|
||||
function calibrationFit(baseline,patches,model){
|
||||
if(!model || baseline.length!==16 || patches.length!==32)throw new Error('Incomplete calibration measurements.');
|
||||
var candidates=[],edges=[0,0,0,0];
|
||||
for(var region=0;region<16;region++){
|
||||
var responses=patches.map(function(p){if(!p || p.length!==16)throw new Error('Missing video measurements.');return Math.max(0,p[region]-baseline[region]);});
|
||||
var peak=Math.max.apply(Math,responses),best=responses.indexOf(peak);
|
||||
if(peak<8)continue;
|
||||
// Reject regions responding equally around the screen (global histograms).
|
||||
var strong=responses.filter(function(v){return v>=peak*.65;}).length;
|
||||
if(strong>10)continue;
|
||||
var distant=responses.some(function(v,i){var d=Math.min(Math.abs(i-best),32-Math.abs(i-best));return v>=peak*.65 && d>6;});
|
||||
if(distant)continue;
|
||||
var sum=0,weighted=0;
|
||||
for(var offset=-3;offset<=3;offset++){
|
||||
var index=(best+offset+32)%32,weight=Math.max(0,responses[index]-peak*.35);
|
||||
weighted+=offset*weight;sum+=weight;
|
||||
}
|
||||
var position=((best+.5+weighted/sum)/8+4)%4;
|
||||
candidates.push({position:position,region:region,score:peak/strong});
|
||||
}
|
||||
// Nested LG regions can measure the same edge position. Keep the most
|
||||
// localized response rather than making duplicate interpolation anchors.
|
||||
candidates.sort(function(a,b){return b.score-a.score || a.region-b.region;});
|
||||
var points=[];
|
||||
candidates.forEach(function(p){if(points.some(function(q){var d=Math.abs(p.position-q.position);return Math.min(d,4-d)<.06;}))return;points.push({position:p.position,region:p.region});edges[Math.floor(p.position)]++;});
|
||||
points.sort(function(a,b){return a.position-b.position;});
|
||||
if(points.length<4 || edges.some(function(n){return !n;}))throw new Error('No reliable spatial response on every edge. Keep the video full-screen; this TV may not expose local histogram regions. Your saved profile is unchanged.');
|
||||
return {model:model,points:points};
|
||||
function calibrationFit(baseline, patches, model) {
|
||||
if (!model || baseline.length !== 16 || patches.length !== 32) throw new Error('Incomplete calibration measurements.');
|
||||
var candidates = [],
|
||||
edges = [0, 0, 0, 0];
|
||||
for (var region = 0; region < 16; region++) {
|
||||
var responses = patches.map(function(p) {
|
||||
if (!p || p.length !== 16) throw new Error('Missing video measurements.');
|
||||
return Math.max(0, p[region] - baseline[region]);
|
||||
});
|
||||
var peak = Math.max.apply(Math, responses),
|
||||
best = responses.indexOf(peak);
|
||||
if (peak < 8) continue;
|
||||
// Reject regions responding equally around the screen (global histograms).
|
||||
var strong = responses.filter(function(v) {
|
||||
return v >= peak * .65;
|
||||
}).length;
|
||||
if (strong > 10) continue;
|
||||
var distant = responses.some(function(v, i) {
|
||||
var d = Math.min(Math.abs(i - best), 32 - Math.abs(i - best));
|
||||
return v >= peak * .65 && d > 6;
|
||||
});
|
||||
if (distant) continue;
|
||||
var sum = 0,
|
||||
weighted = 0;
|
||||
for (var offset = -3; offset <= 3; offset++) {
|
||||
var index = (best + offset + 32) % 32,
|
||||
weight = Math.max(0, responses[index] - peak * .35);
|
||||
weighted += offset * weight;
|
||||
sum += weight;
|
||||
}
|
||||
var position = ((best + .5 + weighted / sum) / 8 + 4) % 4;
|
||||
candidates.push({
|
||||
position: position,
|
||||
region: region,
|
||||
score: peak / strong
|
||||
});
|
||||
}
|
||||
// Nested LG regions can measure the same edge position. Keep the most
|
||||
// localized response rather than making duplicate interpolation anchors.
|
||||
candidates.sort(function(a, b) {
|
||||
return b.score - a.score || a.region - b.region;
|
||||
});
|
||||
var points = [];
|
||||
candidates.forEach(function(p) {
|
||||
if (points.some(function(q) {
|
||||
var d = Math.abs(p.position - q.position);
|
||||
return Math.min(d, 4 - d) < .06;
|
||||
})) return;
|
||||
points.push({
|
||||
position: p.position,
|
||||
region: p.region
|
||||
});
|
||||
edges[Math.floor(p.position)]++;
|
||||
});
|
||||
points.sort(function(a, b) {
|
||||
return a.position - b.position;
|
||||
});
|
||||
if (points.length < 4 || edges.some(function(n) {
|
||||
return !n;
|
||||
})) throw new Error('No reliable spatial response on every edge. Keep the video full-screen; this TV may not expose local histogram regions. Your saved profile is unchanged.');
|
||||
return {
|
||||
model: model,
|
||||
points: points
|
||||
};
|
||||
}
|
||||
function calibrationLuma(regions){return regions.map(function(c){return (c[0]+c[1]+c[2])/3;});}
|
||||
function calibrationAverage(samples){
|
||||
if(samples.length<3)throw new Error('Not enough fresh histogram samples. Keep the app visible and repeat calibration.');
|
||||
return samples[0].map(function(_,r){return samples.reduce(function(sum,s){return sum+s[r];},0)/samples.length;});
|
||||
|
||||
function calibrationLuma(regions) {
|
||||
return regions.map(function(c) {
|
||||
return (c[0] + c[1] + c[2]) / 3;
|
||||
});
|
||||
}
|
||||
|
||||
function calibrationAverage(samples) {
|
||||
if (samples.length < 3) throw new Error('Not enough fresh histogram samples. Keep the app visible and repeat calibration.');
|
||||
return samples[0].map(function(_, r) {
|
||||
return samples.reduce(function(sum, s) {
|
||||
return sum + s[r];
|
||||
}, 0) / samples.length;
|
||||
});
|
||||
}
|
||||
+65
-19
@@ -1,21 +1,67 @@
|
||||
function showPanel(name,focus){
|
||||
['overview','receiver','calibration'].forEach(function(key){document.getElementById(key+'Panel').style.display=key===name?'block':'none';document.getElementById(key+'Tab').setAttribute('aria-pressed',String(key===name));});
|
||||
if(focus!==false)document.getElementById({overview:'start',receiver:'receiver',calibration:'calibrate'}[name]).focus();
|
||||
function showPanel(name, focus) {
|
||||
['overview', 'receiver', 'calibration'].forEach(function(key) {
|
||||
document.getElementById(key + 'Panel').style.display = key === name ? 'block' : 'none';
|
||||
document.getElementById(key + 'Tab').setAttribute('aria-pressed', String(key === name));
|
||||
});
|
||||
if (focus !== false) document.getElementById({
|
||||
overview: 'start',
|
||||
receiver: 'receiver',
|
||||
calibration: 'calibrate'
|
||||
} [name]).focus();
|
||||
}
|
||||
['overview','receiver','calibration'].forEach(function(name){document.getElementById(name+'Tab').onclick=function(){showPanel(name);};});
|
||||
document.getElementById('calibrationShortcut').onclick=function(){showPanel('calibration');};
|
||||
function setupFormEditors(ids,doc,win){
|
||||
var pairs=ids.map(function(id){return {input:doc.getElementById(id),field:doc.getElementById(id+'Field')};});
|
||||
pairs.forEach(function(pair){
|
||||
var input=pair.input,field=pair.field;input.readOnly=true;
|
||||
field.onclick=function(){if(input.disabled || !input.readOnly)return;input.readOnly=false;input.focus();};
|
||||
input.addEventListener('blur',function(){input.readOnly=true;});
|
||||
input.addEventListener('mousedown',function(event){if(input.readOnly)event.preventDefault();});
|
||||
});
|
||||
function finishEditing(){
|
||||
pairs.forEach(function(pair){if(!pair.input.readOnly){pair.input.readOnly=true;pair.input.blur();pair.field.focus();}});
|
||||
}
|
||||
doc.addEventListener('keyboardStateChange',function(event){var detail=event.detail||{};if(detail.visibility===false || detail.isShowing===false)finishEditing();});
|
||||
doc.addEventListener('keydown',function(event){if((event.keyCode===461 || event.key==='Escape') && pairs.some(function(pair){return !pair.input.readOnly;})){event.preventDefault();finishEditing();}});
|
||||
['overview', 'receiver', 'calibration'].forEach(function(name) {
|
||||
document.getElementById(name + 'Tab').onclick = function() {
|
||||
showPanel(name);
|
||||
};
|
||||
});
|
||||
document.getElementById('calibrationShortcut').onclick = function() {
|
||||
showPanel('calibration');
|
||||
};
|
||||
|
||||
function setupFormEditors(ids, doc, win) {
|
||||
var pairs = ids.map(function(id) {
|
||||
return {
|
||||
input: doc.getElementById(id),
|
||||
field: doc.getElementById(id + 'Field')
|
||||
};
|
||||
});
|
||||
pairs.forEach(function(pair) {
|
||||
var input = pair.input,
|
||||
field = pair.field;
|
||||
input.readOnly = true;
|
||||
field.onclick = function() {
|
||||
if (input.disabled || !input.readOnly) return;
|
||||
input.readOnly = false;
|
||||
input.focus();
|
||||
};
|
||||
input.addEventListener('blur', function() {
|
||||
input.readOnly = true;
|
||||
});
|
||||
input.addEventListener('mousedown', function(event) {
|
||||
if (input.readOnly) event.preventDefault();
|
||||
});
|
||||
});
|
||||
|
||||
function finishEditing() {
|
||||
pairs.forEach(function(pair) {
|
||||
if (!pair.input.readOnly) {
|
||||
pair.input.readOnly = true;
|
||||
pair.input.blur();
|
||||
pair.field.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
doc.addEventListener('keyboardStateChange', function(event) {
|
||||
var detail = event.detail || {};
|
||||
if (detail.visibility === false || detail.isShowing === false) finishEditing();
|
||||
});
|
||||
doc.addEventListener('keydown', function(event) {
|
||||
if ((event.keyCode === 461 || event.key === 'Escape') && pairs.some(function(pair) {
|
||||
return !pair.input.readOnly;
|
||||
})) {
|
||||
event.preventDefault();
|
||||
finishEditing();
|
||||
}
|
||||
});
|
||||
}
|
||||
setupFormEditors(['host','port','piccapPort','token','customProfile'],document,window);
|
||||
setupFormEditors(['host', 'port', 'piccapPort', 'token', 'customProfile'], document, window);
|
||||
+46
-29
@@ -1,32 +1,49 @@
|
||||
/* Directional control for webOS remotes and standard keyboards. */
|
||||
function setupRemoteNavigation(buttons, doc, win) {
|
||||
var lastFocused=buttons[0], enterDown=false;
|
||||
buttons.forEach(function(button){
|
||||
button.addEventListener('focus',function(){lastFocused=button;});
|
||||
button.addEventListener('mouseenter',function(){if(!button.disabled)button.focus();});
|
||||
});
|
||||
doc.addEventListener('keydown',function(event){
|
||||
var code=event.keyCode||event.which;
|
||||
var direction=event.key==='ArrowLeft'||event.key==='ArrowUp'||code===37||code===38?-1:
|
||||
event.key==='ArrowRight'||event.key==='ArrowDown'||code===39||code===40?1:0;
|
||||
var enter=event.key==='Enter'||code===13;
|
||||
if(!direction&&!enter)return;
|
||||
var editing=doc.activeElement && !doc.activeElement.readOnly && (doc.activeElement.tagName==='INPUT' && doc.activeElement.type!=='checkbox' || doc.activeElement.tagName==='TEXTAREA');
|
||||
if(editing)return;
|
||||
event.preventDefault();
|
||||
var enabled=buttons.filter(function(button){return !button.disabled && (typeof button.getClientRects!=='function' || button.getClientRects().length) && (!win.remoteNavigationScope || win.remoteNavigationScope.indexOf(button.id)>=0);});
|
||||
if(!enabled.length)return;
|
||||
var focused=doc.activeElement,index=enabled.indexOf(focused);
|
||||
if(direction){
|
||||
if(index<0){index=enabled.indexOf(lastFocused);if(index<0)index=0;enabled[index].focus();}
|
||||
else enabled[(index+direction+enabled.length)%enabled.length].focus();
|
||||
}else{
|
||||
if(enterDown||event.repeat)return;
|
||||
enterDown=true;
|
||||
if(index<0){index=enabled.indexOf(lastFocused);enabled[index<0?0:index].focus();}
|
||||
else focused.click();
|
||||
}
|
||||
});
|
||||
doc.addEventListener('keyup',function(event){if(event.key==='Enter'||event.keyCode===13||event.which===13)enterDown=false;});
|
||||
win.addEventListener('blur',function(){enterDown=false;});
|
||||
var lastFocused = buttons[0],
|
||||
enterDown = false;
|
||||
buttons.forEach(function(button) {
|
||||
button.addEventListener('focus', function() {
|
||||
lastFocused = button;
|
||||
});
|
||||
button.addEventListener('mouseenter', function() {
|
||||
if (!button.disabled) button.focus();
|
||||
});
|
||||
});
|
||||
doc.addEventListener('keydown', function(event) {
|
||||
var code = event.keyCode || event.which;
|
||||
var direction = event.key === 'ArrowLeft' || event.key === 'ArrowUp' || code === 37 || code === 38 ? -1 :
|
||||
event.key === 'ArrowRight' || event.key === 'ArrowDown' || code === 39 || code === 40 ? 1 : 0;
|
||||
var enter = event.key === 'Enter' || code === 13;
|
||||
if (!direction && !enter) return;
|
||||
var editing = doc.activeElement && !doc.activeElement.readOnly && (doc.activeElement.tagName === 'INPUT' && doc.activeElement.type !== 'checkbox' || doc.activeElement.tagName === 'TEXTAREA');
|
||||
if (editing) return;
|
||||
event.preventDefault();
|
||||
var enabled = buttons.filter(function(button) {
|
||||
return !button.disabled && (typeof button.getClientRects !== 'function' || button.getClientRects().length) && (!win.remoteNavigationScope || win.remoteNavigationScope.indexOf(button.id) >= 0);
|
||||
});
|
||||
if (!enabled.length) return;
|
||||
var focused = doc.activeElement,
|
||||
index = enabled.indexOf(focused);
|
||||
if (direction) {
|
||||
if (index < 0) {
|
||||
index = enabled.indexOf(lastFocused);
|
||||
if (index < 0) index = 0;
|
||||
enabled[index].focus();
|
||||
} else enabled[(index + direction + enabled.length) % enabled.length].focus();
|
||||
} else {
|
||||
if (enterDown || event.repeat) return;
|
||||
enterDown = true;
|
||||
if (index < 0) {
|
||||
index = enabled.indexOf(lastFocused);
|
||||
enabled[index < 0 ? 0 : index].focus();
|
||||
} else focused.click();
|
||||
}
|
||||
});
|
||||
doc.addEventListener('keyup', function(event) {
|
||||
if (event.key === 'Enter' || event.keyCode === 13 || event.which === 13) enterDown = false;
|
||||
});
|
||||
win.addEventListener('blur', function() {
|
||||
enterDown = false;
|
||||
});
|
||||
}
|
||||
+203
-82
@@ -1,87 +1,208 @@
|
||||
'use strict';
|
||||
var wizard=null;
|
||||
var video=document.getElementById('calibrationVideo');
|
||||
function wizardFinish(error){
|
||||
if(!wizard)return;
|
||||
clearInterval(wizard.timer);wizard=null;window.remoteNavigationScope=null;video.pause();video.style.display='none';
|
||||
if(typeof video.removeAttribute==='function'){video.removeAttribute('src');video.load();}
|
||||
document.getElementById('cancelCalibration').disabled=true;
|
||||
document.getElementById('calibrate').disabled=false;document.getElementById('validateCalibration').disabled=false;
|
||||
call('calibrationSession',function(r){if(r.returnValue===false)document.getElementById('error').textContent=r.errorText;else call('status',display);},{enabled:false});
|
||||
call('calibrationResult',function(){},{message:error||document.getElementById('wizardStatus').textContent});
|
||||
if(error)document.getElementById('wizardStatus').textContent=error;
|
||||
document.getElementById('calibrate').focus();
|
||||
var WizardPhase = {
|
||||
STARTING: 'starting',
|
||||
WAITING: 'waiting',
|
||||
MEASURING: 'measuring',
|
||||
VALIDATING: 'validating',
|
||||
COMMITTING: 'committing'
|
||||
};
|
||||
var wizard = null;
|
||||
var video = document.getElementById('calibrationVideo');
|
||||
|
||||
function wizardFinish(error) {
|
||||
if (!wizard) return;
|
||||
clearInterval(wizard.timer);
|
||||
wizard = null;
|
||||
window.remoteNavigationScope = null;
|
||||
video.pause();
|
||||
video.style.display = 'none';
|
||||
if (typeof video.removeAttribute === 'function') {
|
||||
video.removeAttribute('src');
|
||||
video.load();
|
||||
}
|
||||
document.getElementById('cancelCalibration').disabled = true;
|
||||
document.getElementById('calibrate').disabled = false;
|
||||
document.getElementById('validateCalibration').disabled = false;
|
||||
call('calibrationSession', function(r) {
|
||||
if (r.returnValue === false) document.getElementById('error').textContent = r.errorText;
|
||||
else call('status', display);
|
||||
}, {
|
||||
enabled: false
|
||||
});
|
||||
call('calibrationResult', function() {}, {
|
||||
message: error || document.getElementById('wizardStatus').textContent
|
||||
});
|
||||
if (error) document.getElementById('wizardStatus').textContent = error;
|
||||
document.getElementById('calibrate').focus();
|
||||
}
|
||||
function wizardSample(){
|
||||
if(!wizard || wizard.busy)return;
|
||||
if(Date.now()-wizard.started>100000){wizardFinish('Calibration timed out. Saved profile unchanged.');return;}
|
||||
if(wizard.validation)return;
|
||||
var t=video.currentTime;
|
||||
if(t>=68){
|
||||
try{
|
||||
var measured=calibrationFit(calibrationAverage(wizard.baseline),wizard.patches.map(calibrationAverage),wizard.model);
|
||||
profile='custom';profileDirty=true;showProfile();document.getElementById('customProfile').value=JSON.stringify(measured,null,2);
|
||||
document.getElementById('wizardStatus').textContent='Measured '+measured.points.length+' edge regions. Select Save profile, then Validate profile to check all four colored edges. The previous saved profile is still active.';
|
||||
wizard.busy=true;
|
||||
call('calibrationDraft',function(r){if(wizard)wizardFinish(r.returnValue===false?r.errorText:undefined);},{profile:'custom',model:measured.model,points:measured.points});
|
||||
}catch(e){wizardFinish(e.message);}
|
||||
return;
|
||||
}
|
||||
var step=t<4?-1:Math.floor((t-4)/2),local=t<4?t:(t-4)%2;
|
||||
if(step<0 && (t<.8 || t>3.2) || step>=0 && (local<.65 || local>1.75))return;
|
||||
wizard.busy=true;
|
||||
call('status',function(s){
|
||||
if(!wizard)return;
|
||||
wizard.busy=false;
|
||||
if(s.returnValue===false || !s.calibrating){wizardFinish(s.errorText||'Calibration session ended. Saved profile unchanged.');return;}
|
||||
// Ignore duplicates, held output and replies crossing a stimulus boundary.
|
||||
var current=video.currentTime,nowStep=current<4?-1:Math.floor((current-4)/2);
|
||||
if(nowStep!==step || s.sampleSequence===wizard.sequence || s.sampleAgeMs<0 || s.sampleAgeMs>350 || !s.colorValid || !Array.isArray(s.regions) || s.regions.length!==16)return;
|
||||
wizard.sequence=s.sampleSequence;
|
||||
(step<0?wizard.baseline:wizard.patches[step]).push(calibrationLuma(s.regions));
|
||||
document.getElementById('wizardStatus').textContent=step<0?'Measuring black reference...':'Measuring edge patch '+(step+1)+' of 32...';
|
||||
});
|
||||
|
||||
function wizardSample() {
|
||||
if (!wizard || wizard.busy) return;
|
||||
var session = wizard;
|
||||
if (Date.now() - wizard.started > 100000) {
|
||||
wizardFinish('Calibration timed out. Saved profile unchanged.');
|
||||
return;
|
||||
}
|
||||
if (wizard.phase !== WizardPhase.MEASURING) return;
|
||||
var t = video.currentTime;
|
||||
if (t >= 68) {
|
||||
try {
|
||||
var measured = calibrationFit(calibrationAverage(wizard.baseline), wizard.patches.map(calibrationAverage), wizard.model);
|
||||
profile = 'custom';
|
||||
profileDirty = true;
|
||||
showProfile();
|
||||
document.getElementById('customProfile').value = JSON.stringify(measured, null, 2);
|
||||
document.getElementById('wizardStatus').textContent = 'Measured ' + measured.points.length + ' edge regions. Select Save profile, then Validate profile to check all four colored edges. The previous saved profile is still active.';
|
||||
wizard.busy = true;
|
||||
wizard.phase = WizardPhase.COMMITTING;
|
||||
call('calibrationDraft', function(r) {
|
||||
if (wizard === session) wizardFinish(r.returnValue === false ? r.errorText : undefined);
|
||||
}, {
|
||||
profile: 'custom',
|
||||
model: measured.model,
|
||||
points: measured.points
|
||||
});
|
||||
} catch (e) {
|
||||
wizardFinish(e.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var step = t < 4 ? -1 : Math.floor((t - 4) / 2),
|
||||
local = t < 4 ? t : (t - 4) % 2;
|
||||
if (step < 0 && (t < .8 || t > 3.2) || step >= 0 && (local < .65 || local > 1.75)) return;
|
||||
wizard.busy = true;
|
||||
call('status', function(s) {
|
||||
if (wizard !== session) return;
|
||||
wizard.busy = false;
|
||||
if (s.returnValue === false || !s.calibrating) {
|
||||
wizardFinish(s.errorText || 'Calibration session ended. Saved profile unchanged.');
|
||||
return;
|
||||
}
|
||||
// Ignore duplicates, held output and replies crossing a stimulus boundary.
|
||||
var current = video.currentTime,
|
||||
nowStep = current < 4 ? -1 : Math.floor((current - 4) / 2);
|
||||
if (nowStep !== step || s.sampleSequence === wizard.sequence || s.sampleAgeMs < 0 || s.sampleAgeMs > 350 || !s.colorValid || !Array.isArray(s.regions) || s.regions.length !== 16) return;
|
||||
wizard.sequence = s.sampleSequence;
|
||||
(step < 0 ? wizard.baseline : wizard.patches[step]).push(calibrationLuma(s.regions));
|
||||
document.getElementById('wizardStatus').textContent = step < 0 ? 'Measuring black reference...' : 'Measuring edge patch ' + (step + 1) + ' of 32...';
|
||||
});
|
||||
}
|
||||
function wizardStart(validation){
|
||||
if(wizard)return;
|
||||
if(typeof showPanel==='function')showPanel('calibration',false);
|
||||
if(targetDirty || profileDirty){document.getElementById('wizardStatus').textContent='Save receiver and profile edits before starting.';return;}
|
||||
document.getElementById('calibrate').disabled=true;document.getElementById('validateCalibration').disabled=true;
|
||||
document.getElementById('cancelCalibration').disabled=false;
|
||||
window.remoteNavigationScope=['cancelCalibration'];
|
||||
wizard={validation:validation,started:Date.now(),busy:false,sequence:-1,model:'',baseline:[],patches:Array.from({length:32},function(){return [];})};
|
||||
document.getElementById('wizardStatus').textContent='Starting full-screen capture. Keep the app visible; do not change zoom or aspect ratio.';
|
||||
call('calibrationSession',function(r){
|
||||
if(!wizard)return;if(r.returnValue===false){wizardFinish(r.errorText);return;}
|
||||
// Start the video plane before waiting for histogram data. Without another
|
||||
// running video, waiting first would prevent calibration from ever starting.
|
||||
video.src='calibration.mp4';if(typeof video.load==='function')video.load();
|
||||
video.style.display='block';var warmup=video.play();
|
||||
if(warmup && warmup.catch)warmup.catch(function(){wizardFinish('The TV could not play the calibration video. Saved profile unchanged.');});
|
||||
call('start',function(){waitCapture();});
|
||||
},{enabled:true});
|
||||
function waitCapture(){
|
||||
if(!wizard)return;
|
||||
call('status',function(s){
|
||||
if(!wizard)return;
|
||||
if(s.running && s.activeSource!=='PicCap' && s.tvModel && s.colorValid){
|
||||
wizard.model=s.tvModel;video.currentTime=validation?68:0;video.style.display='block';
|
||||
document.getElementById('cancelCalibration').focus();
|
||||
var playing=video.play();if(playing && playing.catch)playing.catch(function(){wizardFinish('The TV could not play the calibration video. Saved profile unchanged.');});
|
||||
wizard.timer=setInterval(wizardSample,250);
|
||||
}else if(Date.now()-wizard.started<15000)setTimeout(waitCapture,500);
|
||||
else wizardFinish('No fresh full-screen histogram capture or TV model. Saved profile unchanged.');
|
||||
});
|
||||
}
|
||||
|
||||
function wizardStart(validation) {
|
||||
if (wizard) return;
|
||||
if (typeof showPanel === 'function') showPanel('calibration', false);
|
||||
if (targetDirty || profileDirty) {
|
||||
document.getElementById('wizardStatus').textContent = 'Save receiver and profile edits before starting.';
|
||||
return;
|
||||
}
|
||||
document.getElementById('calibrate').disabled = true;
|
||||
document.getElementById('validateCalibration').disabled = true;
|
||||
document.getElementById('cancelCalibration').disabled = false;
|
||||
window.remoteNavigationScope = ['cancelCalibration'];
|
||||
wizard = {
|
||||
phase: WizardPhase.STARTING,
|
||||
validation: validation,
|
||||
started: Date.now(),
|
||||
busy: false,
|
||||
sequence: -1,
|
||||
model: '',
|
||||
baseline: [],
|
||||
patches: Array.from({
|
||||
length: 32
|
||||
}, function() {
|
||||
return [];
|
||||
})
|
||||
};
|
||||
var session = wizard;
|
||||
document.getElementById('wizardStatus').textContent = 'Starting full-screen capture. Keep the app visible; do not change zoom or aspect ratio.';
|
||||
call('calibrationSession', function(r) {
|
||||
if (wizard !== session) return;
|
||||
if (r.returnValue === false) {
|
||||
wizardFinish(r.errorText);
|
||||
return;
|
||||
}
|
||||
// Start the video plane before waiting for histogram data. Without another
|
||||
// running video, waiting first would prevent calibration from ever starting.
|
||||
video.src = 'calibration.mp4';
|
||||
if (typeof video.load === 'function') video.load();
|
||||
video.style.display = 'block';
|
||||
var warmup = video.play();
|
||||
if (warmup && warmup.catch) warmup.catch(function() {
|
||||
if (wizard !== session) return;
|
||||
wizardFinish('The TV could not play the calibration video. Saved profile unchanged.');
|
||||
});
|
||||
wizard.phase = WizardPhase.WAITING;
|
||||
call('start', function() {
|
||||
if (wizard === session) waitCapture();
|
||||
});
|
||||
}, {
|
||||
enabled: true
|
||||
});
|
||||
|
||||
function waitCapture() {
|
||||
if (wizard !== session) return;
|
||||
call('status', function(s) {
|
||||
if (wizard !== session) return;
|
||||
if (s.running && s.activeSource !== 'PicCap' && s.tvModel && s.colorValid) {
|
||||
wizard.phase = validation ? WizardPhase.VALIDATING : WizardPhase.MEASURING;
|
||||
wizard.model = s.tvModel;
|
||||
video.currentTime = validation ? 68 : 0;
|
||||
video.style.display = 'block';
|
||||
document.getElementById('cancelCalibration').focus();
|
||||
var playing = video.play();
|
||||
if (playing && playing.catch) playing.catch(function() {
|
||||
if (wizard !== session) return;
|
||||
wizardFinish('The TV could not play the calibration video. Saved profile unchanged.');
|
||||
});
|
||||
wizard.timer = setInterval(wizardSample, 250);
|
||||
} else if (Date.now() - wizard.started < 15000) setTimeout(waitCapture, 500);
|
||||
else wizardFinish('No fresh full-screen histogram capture or TV model. Saved profile unchanged.');
|
||||
});
|
||||
}
|
||||
}
|
||||
document.getElementById('calibrate').onclick=function(){wizardStart(false);};
|
||||
document.getElementById('validateCalibration').onclick=function(){wizardStart(true);};
|
||||
document.getElementById('cancelCalibration').onclick=function(){wizardFinish('Calibration cancelled. Saved profile unchanged.');};
|
||||
video.addEventListener('ended',function(){if(wizard && wizard.validation)wizardFinish('Validation finished. Top-left red, top-right green, bottom-right blue, bottom-left yellow. Check matching LED positions.');else wizardSample();});
|
||||
video.addEventListener('error',function(){wizardFinish('Calibration video playback failed. Saved profile unchanged.');});
|
||||
document.addEventListener('keydown',function(e){if(wizard && (e.keyCode===461 || e.key==='Escape')){e.preventDefault();wizardFinish('Calibration cancelled. Saved profile unchanged.');}});
|
||||
window.addEventListener('pagehide',function(){wizardFinish();});
|
||||
document.getElementById('calibrate').onclick = function() {
|
||||
wizardStart(false);
|
||||
};
|
||||
document.getElementById('validateCalibration').onclick = function() {
|
||||
wizardStart(true);
|
||||
};
|
||||
document.getElementById('cancelCalibration').onclick = function() {
|
||||
wizardFinish('Calibration cancelled. Saved profile unchanged.');
|
||||
};
|
||||
video.addEventListener('ended', function() {
|
||||
if (wizard && wizard.validation) wizardFinish('Validation finished. Top-left red, top-right green, bottom-right blue, bottom-left yellow. Check matching LED positions.');
|
||||
else wizardSample();
|
||||
});
|
||||
video.addEventListener('error', function() {
|
||||
wizardFinish('Calibration video playback failed. Saved profile unchanged.');
|
||||
});
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (wizard && (e.keyCode === 461 || e.key === 'Escape')) {
|
||||
e.preventDefault();
|
||||
wizardFinish('Calibration cancelled. Saved profile unchanged.');
|
||||
}
|
||||
});
|
||||
window.addEventListener('pagehide', function() {
|
||||
wizardFinish();
|
||||
});
|
||||
// An explicit launch action also supports starting the TV's guided test remotely.
|
||||
function wizardLaunch(raw){try{var params=typeof raw==='string'?JSON.parse(raw):raw;if(params && params.action==='calibrate')wizardStart(false);else if(params && params.action==='repairRoot'){showPanel('overview');repairRoot(function(ok){if(ok)call('status',display);});}}catch(e){document.getElementById('wizardStatus').textContent='Invalid launch parameters.';}}
|
||||
if(typeof PalmSystem!=='undefined' && PalmSystem.launchParams)setTimeout(function(){wizardLaunch(PalmSystem.launchParams);},1000);
|
||||
window.addEventListener('webOSRelaunch',function(){if(typeof PalmSystem!=='undefined')wizardLaunch(PalmSystem.launchParams);});
|
||||
function wizardLaunch(raw) {
|
||||
try {
|
||||
var params = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
||||
if (params && params.action === 'calibrate') wizardStart(false);
|
||||
else if (params && params.action === 'repairRoot') {
|
||||
showPanel('overview');
|
||||
repairRoot(function(ok) {
|
||||
if (ok) call('status', display);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
document.getElementById('wizardStatus').textContent = 'Invalid launch parameters.';
|
||||
}
|
||||
}
|
||||
if (typeof PalmSystem !== 'undefined' && PalmSystem.launchParams) setTimeout(function() {
|
||||
wizardLaunch(PalmSystem.launchParams);
|
||||
}, 1000);
|
||||
window.addEventListener('webOSRelaunch', function() {
|
||||
if (typeof PalmSystem !== 'undefined') wizardLaunch(PalmSystem.launchParams);
|
||||
});
|
||||
Reference in new issue
Block a user