robopark 3.3.11 → 3.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -41,7 +41,18 @@
|
|
|
41
41
|
callPanel.insertAdjacentHTML('beforeend','<p class="call-note call-status" id=hardware-status>Grant browser media access to reveal device names.</p><span class=mode-badge id=mode-badge hidden>TEST ? BROWSER ONLY</span>');
|
|
42
42
|
function setHardwareStatus(message,state){text('hardware-status',message);el('hardware-status').className='call-note call-status '+(state||'')}
|
|
43
43
|
function fillHardwareSelect(id,devices,kind,label){var select=el(id),current=select.value;select.innerHTML='<option value=default>System default '+label+'</option>';devices.filter(function(device){return device.kind===kind&&device.deviceId&&device.deviceId!=='default';}).forEach(function(device,index){var option=document.createElement('option');option.value=device.deviceId;option.textContent=device.label||(label+' '+(index+1));select.appendChild(option)});if(current&&Array.from(select.options).some(function(option){return option.value===current}))select.value=current}
|
|
44
|
-
|
|
44
|
+
// Browsers expose navigator.mediaDevices only in a secure context, so on a
|
|
45
|
+
// plain http:// LAN or tailnet address it is undefined and every camera and
|
|
46
|
+
// microphone call throws. Naming that explicitly matters: the failure looks
|
|
47
|
+
// like a broken browser or a broken build, and it is neither.
|
|
48
|
+
function mediaBlockedReason(){
|
|
49
|
+
if(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&navigator.mediaDevices.enumerateDevices)return '';
|
|
50
|
+
var secure=window.isSecureContext||location.protocol==='https:'||location.hostname==='localhost'||location.hostname==='127.0.0.1';
|
|
51
|
+
return secure
|
|
52
|
+
? 'This browser does not expose microphone and camera access.'
|
|
53
|
+
: 'Microphone and camera are blocked because this page is not on a secure origin ('+location.protocol+'//'+location.host+'). Open it over https:// or from localhost.';
|
|
54
|
+
}
|
|
55
|
+
function refreshHardware(requestPermission){if(!navigator.mediaDevices||!navigator.mediaDevices.enumerateDevices){setHardwareStatus(mediaBlockedReason(),'bad');return Promise.reject(new Error('media devices unavailable'))}var permission=Promise.resolve();if(requestPermission){permission=navigator.mediaDevices.getUserMedia({audio:true,video:true}).catch(function(){return navigator.mediaDevices.getUserMedia({audio:true})}).then(function(stream){stream.getTracks().forEach(function(track){track.stop()})})}setHardwareStatus(requestPermission?'Requesting browser media access...':'Scanning browser hardware...','');return permission.then(function(){return navigator.mediaDevices.enumerateDevices()}).then(function(devices){fillHardwareSelect('call-microphone',devices,'audioinput','microphone');fillHardwareSelect('call-camera',devices,'videoinput','camera');fillHardwareSelect('call-speaker',devices,'audiooutput','speaker');var mic=devices.filter(function(device){return device.kind==='audioinput'}).length,cam=devices.filter(function(device){return device.kind==='videoinput'}).length,out=devices.filter(function(device){return device.kind==='audiooutput'}).length;setHardwareStatus('Ready: '+mic+' microphone(s), '+cam+' camera(s), '+out+' speaker output(s).','ok');return devices}).catch(function(error){setHardwareStatus(error&&error.message||String(error),'bad');throw error})}
|
|
45
56
|
function ensureCameraSdk(){if(window.LivekitClient)return Promise.resolve(window.LivekitClient);if(cameraSdkPromise)return cameraSdkPromise;cameraSdkPromise=new Promise(function(resolve,reject){var script=document.createElement('script');script.src='/vendor/livekit-client.umd.js';script.onload=function(){window.LivekitClient?resolve(window.LivekitClient):reject(new Error('LiveKit camera SDK unavailable'))};script.onerror=function(){reject(new Error('LiveKit camera SDK failed to load'))};document.head.appendChild(script)});return cameraSdkPromise}
|
|
46
57
|
function scheduleCameraPublishRetry(){if(cameraPublishRetry||ending||!localVideoStream)return;cameraPublishRetry=setTimeout(function(){cameraPublishRetry=null;publishLocalVideo().catch(function(){})},3000)}
|
|
47
58
|
function publishLocalVideo(){if(!localVideoStream||!localVideoStream.getVideoTracks().length)return Promise.resolve();if(cameraRoom)return Promise.resolve();return Promise.all([ensureCameraSdk(),fetch(API_BASE+'/api/camera-token',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({character_id:character,robot_id:robot||null})}).then(function(response){if(!response.ok)throw new Error('camera token '+response.status);return response.json()})]).then(function(parts){var LK=parts[0],info=parts[1],room=new LK.Room();cameraRoom=room;room.on(LK.RoomEvent.Disconnected,function(){if(cameraRoom===room){cameraRoom=null;scheduleCameraPublishRetry()}});return room.connect(info.url,info.token).then(function(){return room.localParticipant.publishTrack(localVideoStream.getVideoTracks()[0],{name:'camera',source:LK.Track.Source.Camera})}).then(function(){if(cameraPublishRetry){clearTimeout(cameraPublishRetry);cameraPublishRetry=null}event('camera_published','ok','Camera relay live',{})})}).catch(function(error){cameraRoom=null;event('camera_published','failed',error.message,{});scheduleCameraPublishRetry();throw error})}
|
|
@@ -331,7 +342,7 @@
|
|
|
331
342
|
var status=Number(error&&error.status||0),permanent=(status>=400&&status<500)||status===503;
|
|
332
343
|
if(!permanent&&alwaysOn&&desired&&!ending){el('join').hidden=true;el('end').hidden=false;scheduleReconnect();return}
|
|
333
344
|
fail(details.message)})}
|
|
334
|
-
function start(){if(desired||conversation||connecting)return;if(mode==='production'&&!robot){fail('Select a registered production robot first.');return}alwaysOn=true;el('always-on').classList.add('on');el('always-on').setAttribute('aria-pressed','true');el('always-on').textContent='Always on: ON';if(mode==='test')ensureMicAudioContext();desired=true;retryAttempt=0;el('join').disabled=true;text('state','requesting microphone permission');stage('permission');var audio=inputDeviceId?{deviceId:{exact:inputDeviceId}}:true;navigator.mediaDevices.getUserMedia({audio:audio}).then(function(stream){stream.getTracks().forEach(function(track){track.stop()});return startLocalVideo()}).then(function(){connectAttempt('initial')}).catch(function(error){if(desired){text('state','microphone unavailable; retrying');scheduleReconnect()}else{fail(error&&error.message||String(error))}})}
|
|
345
|
+
function start(){if(desired||conversation||connecting)return;var mediaBlocked=mediaBlockedReason();if(mediaBlocked){fail(mediaBlocked);return}if(mode==='production'&&!robot){fail('Select a registered production robot first.');return}alwaysOn=true;el('always-on').classList.add('on');el('always-on').setAttribute('aria-pressed','true');el('always-on').textContent='Always on: ON';if(mode==='test')ensureMicAudioContext();desired=true;retryAttempt=0;el('join').disabled=true;text('state','requesting microphone permission');stage('permission');var audio=inputDeviceId?{deviceId:{exact:inputDeviceId}}:true;navigator.mediaDevices.getUserMedia({audio:audio}).then(function(stream){stream.getTracks().forEach(function(track){track.stop()});return startLocalVideo()}).then(function(){connectAttempt('initial')}).catch(function(error){if(desired){text('state','microphone unavailable; retrying');scheduleReconnect()}else{fail(error&&error.message||String(error))}})}
|
|
335
346
|
el('kiosk').onclick=function(){
|
|
336
347
|
kioskOn=!kioskOn;syncKioskButton();
|
|
337
348
|
if(kioskOn){kioskStart()}
|
|
@@ -40,7 +40,18 @@
|
|
|
40
40
|
callPanel.insertAdjacentHTML('beforeend','<p class="call-note call-status" id=hardware-status>Grant browser media access to reveal device names.</p><span class=mode-badge id=mode-badge hidden>TEST ? BROWSER ONLY</span>');
|
|
41
41
|
function setHardwareStatus(message,state){text('hardware-status',message);el('hardware-status').className='call-note call-status '+(state||'')}
|
|
42
42
|
function fillHardwareSelect(id,devices,kind,label){var select=el(id),current=select.value;select.innerHTML='<option value=default>System default '+label+'</option>';devices.filter(function(device){return device.kind===kind&&device.deviceId&&device.deviceId!=='default';}).forEach(function(device,index){var option=document.createElement('option');option.value=device.deviceId;option.textContent=device.label||(label+' '+(index+1));select.appendChild(option)});if(current&&Array.from(select.options).some(function(option){return option.value===current}))select.value=current}
|
|
43
|
-
|
|
43
|
+
// Browsers expose navigator.mediaDevices only in a secure context, so on a
|
|
44
|
+
// plain http:// LAN or tailnet address it is undefined and every camera and
|
|
45
|
+
// microphone call throws. Naming that explicitly matters: the failure looks
|
|
46
|
+
// like a broken browser or a broken build, and it is neither.
|
|
47
|
+
function mediaBlockedReason(){
|
|
48
|
+
if(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&navigator.mediaDevices.enumerateDevices)return '';
|
|
49
|
+
var secure=window.isSecureContext||location.protocol==='https:'||location.hostname==='localhost'||location.hostname==='127.0.0.1';
|
|
50
|
+
return secure
|
|
51
|
+
? 'This browser does not expose microphone and camera access.'
|
|
52
|
+
: 'Microphone and camera are blocked because this page is not on a secure origin ('+location.protocol+'//'+location.host+'). Open it over https:// or from localhost.';
|
|
53
|
+
}
|
|
54
|
+
function refreshHardware(requestPermission){if(!navigator.mediaDevices||!navigator.mediaDevices.enumerateDevices){setHardwareStatus(mediaBlockedReason(),'bad');return Promise.reject(new Error('media devices unavailable'))}var permission=Promise.resolve();if(requestPermission){permission=navigator.mediaDevices.getUserMedia({audio:true,video:true}).catch(function(){return navigator.mediaDevices.getUserMedia({audio:true})}).then(function(stream){stream.getTracks().forEach(function(track){track.stop()})})}setHardwareStatus(requestPermission?'Requesting browser media access...':'Scanning browser hardware...','');return permission.then(function(){return navigator.mediaDevices.enumerateDevices()}).then(function(devices){fillHardwareSelect('call-microphone',devices,'audioinput','microphone');fillHardwareSelect('call-camera',devices,'videoinput','camera');fillHardwareSelect('call-speaker',devices,'audiooutput','speaker');var mic=devices.filter(function(device){return device.kind==='audioinput'}).length,cam=devices.filter(function(device){return device.kind==='videoinput'}).length,out=devices.filter(function(device){return device.kind==='audiooutput'}).length;setHardwareStatus('Ready: '+mic+' microphone(s), '+cam+' camera(s), '+out+' speaker output(s).','ok');return devices}).catch(function(error){setHardwareStatus(error&&error.message||String(error),'bad');throw error})}
|
|
44
55
|
function ensureCameraSdk(){if(window.LivekitClient)return Promise.resolve(window.LivekitClient);if(cameraSdkPromise)return cameraSdkPromise;cameraSdkPromise=new Promise(function(resolve,reject){var script=document.createElement('script');script.src='/vendor/livekit-client.umd.js';script.onload=function(){window.LivekitClient?resolve(window.LivekitClient):reject(new Error('LiveKit camera SDK unavailable'))};script.onerror=function(){reject(new Error('LiveKit camera SDK failed to load'))};document.head.appendChild(script)});return cameraSdkPromise}
|
|
45
56
|
function scheduleCameraPublishRetry(){if(cameraPublishRetry||ending||!localVideoStream)return;cameraPublishRetry=setTimeout(function(){cameraPublishRetry=null;publishLocalVideo().catch(function(){})},3000)}
|
|
46
57
|
function publishLocalVideo(){if(!localVideoStream||!localVideoStream.getVideoTracks().length)return Promise.resolve();if(cameraRoom)return Promise.resolve();return Promise.all([ensureCameraSdk(),fetch('/api/robopark/camera-token',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({character_id:character,robot_id:robot||null})}).then(function(response){if(!response.ok)throw new Error('camera token '+response.status);return response.json()})]).then(function(parts){var LK=parts[0],info=parts[1],room=new LK.Room();cameraRoom=room;room.on(LK.RoomEvent.Disconnected,function(){if(cameraRoom===room){cameraRoom=null;scheduleCameraPublishRetry()}});return room.connect(info.url,info.token).then(function(){return room.localParticipant.publishTrack(localVideoStream.getVideoTracks()[0],{name:'camera',source:LK.Track.Source.Camera})}).then(function(){if(cameraPublishRetry){clearTimeout(cameraPublishRetry);cameraPublishRetry=null}event('camera_published','ok','Camera relay live',{})})}).catch(function(error){cameraRoom=null;event('camera_published','failed',error.message,{});scheduleCameraPublishRetry();throw error})}
|
|
@@ -330,7 +341,7 @@
|
|
|
330
341
|
var status=Number(error&&error.status||0),permanent=(status>=400&&status<500)||status===503;
|
|
331
342
|
if(!permanent&&alwaysOn&&desired&&!ending){el('join').hidden=true;el('end').hidden=false;scheduleReconnect();return}
|
|
332
343
|
fail(details.message)})}
|
|
333
|
-
function start(){if(desired||conversation||connecting)return;if(mode==='production'&&!robot){fail('Select a registered production robot first.');return}alwaysOn=true;el('always-on').classList.add('on');el('always-on').setAttribute('aria-pressed','true');el('always-on').textContent='Always on: ON';if(mode==='test')ensureMicAudioContext();desired=true;retryAttempt=0;el('join').disabled=true;text('state','requesting microphone permission');stage('permission');var audio=inputDeviceId?{deviceId:{exact:inputDeviceId}}:true;navigator.mediaDevices.getUserMedia({audio:audio}).then(function(stream){stream.getTracks().forEach(function(track){track.stop()});return startLocalVideo()}).then(function(){connectAttempt('initial')}).catch(function(error){if(desired){text('state','microphone unavailable; retrying');scheduleReconnect()}else{fail(error&&error.message||String(error))}})}
|
|
344
|
+
function start(){if(desired||conversation||connecting)return;var mediaBlocked=mediaBlockedReason();if(mediaBlocked){fail(mediaBlocked);return}if(mode==='production'&&!robot){fail('Select a registered production robot first.');return}alwaysOn=true;el('always-on').classList.add('on');el('always-on').setAttribute('aria-pressed','true');el('always-on').textContent='Always on: ON';if(mode==='test')ensureMicAudioContext();desired=true;retryAttempt=0;el('join').disabled=true;text('state','requesting microphone permission');stage('permission');var audio=inputDeviceId?{deviceId:{exact:inputDeviceId}}:true;navigator.mediaDevices.getUserMedia({audio:audio}).then(function(stream){stream.getTracks().forEach(function(track){track.stop()});return startLocalVideo()}).then(function(){connectAttempt('initial')}).catch(function(error){if(desired){text('state','microphone unavailable; retrying');scheduleReconnect()}else{fail(error&&error.message||String(error))}})}
|
|
334
345
|
el('kiosk').onclick=function(){
|
|
335
346
|
kioskOn=!kioskOn;syncKioskButton();
|
|
336
347
|
if(kioskOn){kioskStart()}
|