aotrautils 0.0.1796 → 0.0.1797

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.
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- /*utils COMMONS library associated with aotra version : «1_29072022-2359 (24/06/2025-01:50:32)»*/
3
+ /*utils COMMONS library associated with aotra version : «1_29072022-2359 (11/11/2025-17:05:51)»*/
4
4
  /*-----------------------------------------------------------------------------*/
5
5
 
6
6
 
@@ -1618,7 +1618,7 @@ aotest.profile=function(rootObject,methodName,visited=[]){
1618
1618
 
1619
1619
 
1620
1620
  //================================================================
1621
- //================= Tjread control utility methods =================
1621
+ //================= Thread control utility methods =================
1622
1622
  //================================================================
1623
1623
 
1624
1624
 
@@ -3868,7 +3868,7 @@ Math.makeMultipleOf=function(bruteValue, multiple,/*OPTIONAL*/roundingMode){
3868
3868
 
3869
3869
 
3870
3870
  getTimer=function(callback, delay){
3871
- return new Timer(callback, delay);
3871
+ return new SimpleTimer(callback, delay);
3872
3872
  // let self={
3873
3873
  // timerId:null,
3874
3874
  // start:null,
@@ -3893,14 +3893,14 @@ getTimer=function(callback, delay){
3893
3893
 
3894
3894
 
3895
3895
  //Usage :
3896
- //var timer=new Timer(function(){
3896
+ //var timer=new SimpleTimer(function(){
3897
3897
  //alert("Done!");
3898
3898
  //}, 1000);
3899
3899
  //
3900
3900
  //timer.pause();
3901
3901
  ////Do some stuff...
3902
3902
  //timer.resume();
3903
- var Timer=function(callback, delay){
3903
+ var SimpleTimer=function(callback, delay){
3904
3904
  var timerId, start, remaining=delay;
3905
3905
 
3906
3906
  this.pause=function(){
@@ -5002,6 +5002,38 @@ window.splitURL=(urlOrigin)=>{
5002
5002
  return {protocol:protocol, host:host, port:port, isSecure:isSecure};
5003
5003
  };
5004
5004
 
5005
+ window.getPathExtension=(path)=>{
5006
+ const splits = path.split(".");
5007
+ if(splits.length <= 1)
5008
+ return "";
5009
+ return splits.pop();
5010
+ }
5011
+
5012
+ window.getBasePath=(pathParam, fileSeparator="/")=>{
5013
+ // Clean :
5014
+ const path=pathParam.replace(/\/{2,}/gim,"/");
5015
+ const splits = path.split(fileSeparator);
5016
+ if(splits.length <= 1)
5017
+ return path;
5018
+ // We remove the last part, corresponding to the file name:
5019
+ return splits.slice(0,-1).join(fileSeparator)+fileSeparator;
5020
+ }
5021
+
5022
+ window.getPathFileName=(path, fileSeparator="/", removeExtension=false)=>{
5023
+ const splits = path.split(fileSeparator);
5024
+ if(splits.length <= 0)
5025
+ return "";
5026
+ let fileNameWithExtension=splits.pop();
5027
+ if(empty(fileNameWithExtension))
5028
+ return "";
5029
+ if(!removeExtension)
5030
+ return fileNameWithExtension;
5031
+ const fileNameSplits = fileNameWithExtension.split(".");
5032
+ if(fileNameSplits.length <= 0)
5033
+ return "";
5034
+ return fileNameSplits[0];
5035
+ }
5036
+
5005
5037
 
5006
5038
 
5007
5039
  // MUST REMAIN AT THE END OF THIS LIBRARY FILE !
@@ -5012,7 +5044,7 @@ AOTRAUTILS_LIB_IS_LOADED=true;
5012
5044
 
5013
5045
 
5014
5046
 
5015
- /*utils CLIENT library associated with aotra version : «1_29072022-2359 (24/06/2025-01:50:32)»*/
5047
+ /*utils CLIENT library associated with aotra version : «1_29072022-2359 (11/11/2025-17:05:51)»*/
5016
5048
  /*-----------------------------------------------------------------------------*/
5017
5049
  /* ## Utility global methods in a browser (htmljs) client environment.
5018
5050
  *
@@ -7949,55 +7981,84 @@ function filterPoints(allPoints ,ctx/*DBG*/){
7949
7981
  /*OPTIONAL*/tagConfig={},
7950
7982
  /*OPTIONAL*/audioBufferSize=null,/*OPTIONAL*/numberOfAudioChannels=null,
7951
7983
  /*OPTIONAL*/doOnStart=null,
7952
- /*OPTIONAL*/doFinallyWhenMultipleDevices=null
7984
+ /*OPTIONAL*//*DEPRECATED : USE PROMISE then() INSTEAD*/doFinallyWhenMultipleDevices=null
7953
7985
  ){
7954
7986
 
7987
+
7955
7988
  if(!isUserMediaAvailable()){
7956
7989
  // TRACE
7957
7990
  log("ERROR : User medias is not supported in your browser (or are you running in a non-https context ?). Aborting");
7958
- return null;
7991
+ return new Promise();
7959
7992
  }
7960
-
7961
- const cameras=[];
7962
- const microphones=[];
7963
- navigator.mediaDevices.enumerateDevices().then(function(devices){
7964
7993
 
7965
- devices.forEach(function(d){
7966
- if(d.kind==="videoinput") cameras.push(d);
7967
- else if(d.kind==="audioinput") microphones.push(d);
7968
- });
7994
+ return new Promise((accept,reject)=>{
7995
+
7996
+ // Force request permissions :
7997
+ const constraints={video:(videoConfigParam!=null), audio:(audioConfigParam!=null)}; // Default constraints (corresponding to no media)
7998
+ navigator.mediaDevices.getUserMedia(constraints).then(()=>{
7999
+
7969
8000
 
7970
- // CAUTION : On today this method only handles media handlers with a single track :
7971
-
7972
- let constraints={video:(videoConfigParam!=null), audio:(audioConfigParam!=null)}; // Default constraints
7973
- if(!empty(cameras) && videoConfigParam){
7974
- let webcamIndex=videoConfigParam.webcamIndex;
7975
- if(webcamIndex!=null){
7976
- const deviceIndex=Math.min(cameras.length-1, webcamIndex);
7977
- constraints={
7978
- video:{
7979
- deviceId:{ exact: cameras[deviceIndex].deviceId }
8001
+ const cameras=[];
8002
+ const microphones=[];
8003
+
8004
+ navigator.mediaDevices.enumerateDevices().then(function(devices){
8005
+
8006
+ // DBG
8007
+ lognow("DEBUG : Devices :",devices);
8008
+
8009
+ devices.forEach(function(d){
8010
+ if(d.kind==="videoinput") cameras.push(d);
8011
+ else if(d.kind==="audioinput") microphones.push(d);
8012
+ });
8013
+
8014
+ // CAUTION : On today this method only handles media handlers with a single track :
8015
+
8016
+ if(videoConfigParam){
8017
+ if(!empty(cameras)){
8018
+ let webcamIndex=videoConfigParam.webcamIndex;
8019
+ if(webcamIndex!=null && isNumber(webcamIndex)){
8020
+ const deviceIndex=Math.min(cameras.length-1, webcamIndex);
8021
+ constraints.video={ deviceId:{ exact: cameras[deviceIndex].deviceId } };
7980
8022
  }
7981
- };
7982
- }
7983
- }else if(!empty(microphones) && audioConfigParam){
7984
- let microphoneIndex=audioConfigParam.microphoneIndex;
7985
- if(microphoneIndex!=null){
7986
- const deviceIndex=Math.min(microphones.length-1, microphoneIndex);
7987
- constraints={
7988
- audio:{
7989
- deviceId: { exact: microphones[deviceIndex].deviceId }
8023
+ }else{
8024
+ // TRACE
8025
+ lognow("ERROR : Video requested but no camera found. Aborting.");
8026
+ constraints.video=false;
8027
+ }
8028
+ }
8029
+ if(audioConfigParam){
8030
+ if(!empty(microphones)){
8031
+ let microphoneIndex=audioConfigParam.microphoneIndex;
8032
+ if(microphoneIndex!=null && isNumber(microphoneIndex)){
8033
+ const deviceIndex=Math.min(microphones.length-1, microphoneIndex);
8034
+ constraints.audio={ deviceId: { exact: microphones[deviceIndex].deviceId } };
7990
8035
  }
7991
- };
7992
- }
7993
- }
7994
-
8036
+ }else{
8037
+ // TRACE
8038
+ lognow("ERROR : Audio requested but no microphone found. Aborting.");
8039
+ constraints.audio=false;
8040
+ }
8041
+ }
8042
+
8043
+
8044
+ let mediaHandler=getMediaHandlerIMPL(constraints, size, tagConfig, audioBufferSize, numberOfAudioChannels, doOnStart, {cameras:cameras, microphones:microphones});
8045
+ if(doFinallyWhenMultipleDevices) doFinallyWhenMultipleDevices(mediaHandler);
8046
+
8047
+ accept(mediaHandler);
8048
+
8049
+ }).catch(err=>{
8050
+ // TRACE
8051
+ lognow("ERROR : An error occured when trying to enumerate devices:",err);
8052
+ reject(err);
8053
+ });
7995
8054
 
7996
- let mediaHandler=getMediaHandlerIMPL(constraints, size, tagConfig, audioBufferSize, numberOfAudioChannels, doOnStart, {cameras:cameras, microphones:microphones});
7997
- if(doFinallyWhenMultipleDevices) doFinallyWhenMultipleDevices(mediaHandler);
7998
-
7999
- });
8055
+ }).catch(err=>{
8056
+ // TRACE
8057
+ lognow("ERROR : An error occured when trying to get webcam handler:",err);
8058
+ reject(err);
8059
+ });
8000
8060
 
8061
+ });
8001
8062
 
8002
8063
  };
8003
8064
 
@@ -8037,18 +8098,22 @@ function filterPoints(allPoints ,ctx/*DBG*/){
8037
8098
  // 0- Basic validation :
8038
8099
  if(!mediaConstraints){
8039
8100
  // TRACE
8040
- log("ERROR : Are you kidding me ? no media constraint has been set for this mediaHandler !");
8101
+ log("ERROR : No media constraint has been set for this mediaHandler !");
8041
8102
  return null;
8042
8103
  }
8104
+
8105
+
8106
+
8043
8107
  const isVideo=!!(mediaConstraints.video);
8044
8108
  const isAudio=!!(mediaConstraints.audio);
8045
8109
  if(!isVideo && !isAudio){
8046
8110
  // TRACE
8047
- log("ERROR : Are you kidding me ? nor audio nor video has been selected for this mediaHandler !");
8111
+ log("ERROR : No audio nor video has been selected for this mediaHandler !");
8048
8112
  return null;
8049
8113
  }
8050
8114
 
8051
8115
 
8116
+
8052
8117
  if(!isUserMediaAvailable()){
8053
8118
  // TRACE
8054
8119
  log("ERROR : User medias is not supported in your browser (or are you running in a non-https context ?). Aborting");
@@ -8066,14 +8131,19 @@ function filterPoints(allPoints ,ctx/*DBG*/){
8066
8131
  devicesInfo:devicesInfo,
8067
8132
  // UNUSED : mediaConstraints:mediaConstraints,
8068
8133
  // technical attributes :
8069
- video:null, // This is where you will read the video image data !
8134
+ videoReadingElement:null, // This is where you will read the video image data !
8135
+
8136
+ // // DOES NOT WORK WELL :
8137
+ // imageReader:null,
8138
+ // currentVideoFrame:null,
8139
+
8070
8140
  canvasTag:null,
8071
8141
  canvasCtx:null,
8072
8142
  audioData:[],// Where to store sound data
8073
8143
  audioCtx:null,
8074
8144
 
8075
- videoDeviceId:(mediaConstraints.video?mediaConstraints.video.deviceId.exact:null),
8076
- audioDeviceId:(mediaConstraints.audio?mediaConstraints.audio.deviceId.exact:null),
8145
+ videoDeviceId:(mediaConstraints.video && mediaConstraints.video.deviceId?mediaConstraints.video.deviceId.exact:null),
8146
+ audioDeviceId:(mediaConstraints.audio && mediaConstraints.audio.deviceId?mediaConstraints.audio.deviceId.exact:null),
8077
8147
 
8078
8148
  imageInterceptors:[],
8079
8149
  appendImageInterceptor:(method)=>mediaHandler.imageInterceptors.push(method),
@@ -8102,36 +8172,40 @@ function filterPoints(allPoints ,ctx/*DBG*/){
8102
8172
  //canvasTag.style.display="none";
8103
8173
 
8104
8174
 
8105
- // This temporary, off-screen video is always needed to get the image data for further use :
8106
- let videoTag;
8175
+ let videoTag=null;
8107
8176
  if(!tagConfig || !tagConfig.videoToUse){
8108
- videoTag=document.createElement("video");
8109
- videoTag.id="tmpVideoElement";
8110
- videoTag.autoplay=true;
8111
- videoTag.width=canvasTag.width;
8112
- videoTag.height=canvasTag.height;
8113
- videoTag.style.width=canvasTag.width+"px";
8114
- videoTag.style.height=canvasTag.height+"px";
8115
-
8116
- // // UNUSEFUL (SINCE VIDEO ELEMENT IS NEVER APPENDED TO PAGE !) :
8117
- // videoTag.style.position="absolute";
8118
- // videoTag.style["pointer-events"]="none";
8119
- // videoTag.style.display="none";
8120
- // videoTag.style.display="none";
8121
- // document.body.appendChild(videoTag);
8177
+
8178
+ // // DOES NOT WORK WELL :
8179
+ // if(!tagConfig.useNoVideoReadingElement){
8180
+
8181
+ // This temporary, off-screen video is always needed to get the image data for further use :
8182
+ videoTag=document.createElement("video");
8183
+ videoTag.id="tmpVideoElement";
8184
+ videoTag.autoplay=true;
8185
+ videoTag.width=canvasTag.width;
8186
+ videoTag.height=canvasTag.height;
8187
+ videoTag.style.width=canvasTag.width+"px";
8188
+ videoTag.style.height=canvasTag.height+"px";
8189
+
8190
+ // // UNUSEFUL (SINCE VIDEO ELEMENT IS NEVER APPENDED TO PAGE !) :
8191
+ // videoTag.style.position="absolute";
8192
+ // videoTag.style["pointer-events"]="none";
8193
+ // videoTag.style.display="none";
8194
+ // videoTag.style.display="none";
8195
+ // document.body.appendChild(videoTag);
8196
+ // }
8122
8197
 
8123
8198
  }else{
8124
8199
  videoTag=tagConfig.videoToUse;
8125
8200
  }
8126
8201
 
8127
-
8128
8202
 
8129
- mediaHandler.video=videoTag;
8203
+ mediaHandler.videoReadingElement=videoTag;
8130
8204
 
8131
8205
 
8132
8206
  // UNUSED
8133
8207
  // mediaHandler.isReady=function(){
8134
- //// return mediaHandler.video !== null;
8208
+ //// return mediaHandler.videoReadingElement !== null;
8135
8209
  // return !!(mediaHandler.stream);
8136
8210
  // };
8137
8211
 
@@ -8141,8 +8215,7 @@ function filterPoints(allPoints ,ctx/*DBG*/){
8141
8215
  if(!constraints){
8142
8216
  //constraints={video: (isVideo?{zoom:true,width:{ideal:nonull(width,DEFAULT_WIDTH)},height:{ideal:nonull(height,DEFAULT_HEIGHT)}}:false), audio: isAudio};
8143
8217
  // DBG
8144
- constraints={video: (isVideo?
8145
- {width:nonull(width,DEFAULT_WIDTH),height:nonull(height,DEFAULT_HEIGHT)}
8218
+ constraints={video: (isVideo?{width:{ideal:nonull(width,DEFAULT_WIDTH)},height:{ideal:nonull(height,DEFAULT_HEIGHT)} }
8146
8219
  //// ALTERNATIVELY, AND MORE FINE_TUNED :
8147
8220
  // {zoom:true,mandatory: {
8148
8221
  // minWidth: 1280,
@@ -8152,6 +8225,24 @@ function filterPoints(allPoints ,ctx/*DBG*/){
8152
8225
  // }
8153
8226
  // }
8154
8227
  :false), audio: isAudio};
8228
+ }else{
8229
+
8230
+ if(isVideo){
8231
+ if(!constraints.video){
8232
+ constraints.video={};
8233
+ }
8234
+ if(!constraints.video.width){
8235
+ constraints.video.width={ideal:nonull(width,DEFAULT_WIDTH)};
8236
+ }
8237
+ if(!constraints.video.height){
8238
+ constraints.video.height={ideal:nonull(height,DEFAULT_HEIGHT)};
8239
+ }
8240
+ }
8241
+ if(isAudio && !constraints.audio){
8242
+ constraints.audio=true;
8243
+ }
8244
+
8245
+
8155
8246
  }
8156
8247
 
8157
8248
  // TRACE
@@ -8168,9 +8259,9 @@ function filterPoints(allPoints ,ctx/*DBG*/){
8168
8259
 
8169
8260
 
8170
8261
  // DBG
8171
- lognow("(DEBUG) streamSettings:",streamSettings);
8172
- lognow("(DEBUG) stream:",stream);
8173
- lognow("(DEBUG) stream.getVideoTracks():",stream.getVideoTracks());
8262
+ lognow("DEBUG : streamSettings:",streamSettings);
8263
+ lognow("DEBUG : stream:",stream);
8264
+ lognow("DEBUG : stream.getVideoTracks():",stream.getVideoTracks());
8174
8265
 
8175
8266
 
8176
8267
  if(streamSettings){
@@ -8186,27 +8277,35 @@ function filterPoints(allPoints ,ctx/*DBG*/){
8186
8277
 
8187
8278
  // TRACE
8188
8279
  lognow("INFO : MEDIA HANDLER : Starting video...");
8189
-
8190
- // We always have a video tag (to grab the video data)
8191
- if(!isArray(mediaHandler.video)){
8280
+
8281
+ // // DOES NOT WORK WELL :
8282
+ // if(tagConfig && tagConfig.useNoVideoReadingElement){
8283
+ //
8284
+ // const track = stream.getVideoTracks()[0];
8285
+ // const processor = new MediaStreamTrackProcessor({track});
8286
+ // const readableStream = processor.readable;
8287
+ // mediaHandler.imageReader = readableStream.getReader();
8288
+ //
8289
+ //
8290
+ // }else
8291
+ // In this case we have a single video tag for this mediaHandler (to grab the video data)
8292
+ if(!isArray(mediaHandler.videoReadingElement)){
8192
8293
 
8193
- mediaHandler.video.srcObject=stream;
8294
+ mediaHandler.videoReadingElement.srcObject=stream;
8194
8295
  // DOES NOT WORK : mediaHandler.video.src=stream;
8195
8296
 
8196
- // NO : DOES NOT WORK (CREATES A BLANK IMAGE !) mediaHandler.video.muted=true;
8297
+ // NO : DOES NOT WORK (CREATES A BLANK IMAGE !) mediaHandler.videoReadingElement.muted=true;
8197
8298
  // STUPID WORKAROUND : Because apparently, if volume==0 or video is muted, then the image data is all black !!!
8198
- mediaHandler.video.volume=0.0000000000001;
8199
- // mediaHandler.video.autoplay=true;
8299
+ mediaHandler.videoReadingElement.volume=0.0000000000001;
8300
+ // mediaHandler.videoReadingElement.autoplay=true;
8200
8301
 
8201
- if(tagConfig && tagConfig.videoToUse && tagConfig.videoScale){
8202
- if(isNumber(tagConfig.videoScale)){
8203
- mediaHandler.video.style.scale=tagConfig.videoScale;
8204
- }
8302
+ if(tagConfig && tagConfig.videoToUse && tagConfig.videoScale && isNumber(tagConfig.videoScale)){
8303
+ mediaHandler.videoReadingElement.style.scale=tagConfig.videoScale;
8205
8304
  }
8206
8305
 
8207
8306
  }else{ // Audio part :
8208
8307
 
8209
- foreach(mediaHandler.video,(v)=>{
8308
+ foreach(mediaHandler.videoReadingElement,(v)=>{
8210
8309
 
8211
8310
  v.srcObject=stream;
8212
8311
 
@@ -8220,7 +8319,7 @@ function filterPoints(allPoints ,ctx/*DBG*/){
8220
8319
 
8221
8320
 
8222
8321
  mediaHandler.canvasTag=canvasTag;
8223
- mediaHandler.canvasCtx=mediaHandler.canvasTag.getContext("2d");
8322
+ mediaHandler.canvasCtx=mediaHandler.canvasTag.getContext("2d",{ willReadFrequently: true }/*CAUTION : DISABLES GPU OPTIMIZATION !*/);
8224
8323
 
8225
8324
  let x=0;
8226
8325
  let y=0;
@@ -8228,37 +8327,60 @@ function filterPoints(allPoints ,ctx/*DBG*/){
8228
8327
  let width=mediaHandler.canvasTag.width;
8229
8328
  let height=mediaHandler.canvasTag.height;
8230
8329
 
8231
- mediaHandler.getVideoData=function(){
8232
-
8233
- // To read the image data : (code from «PlaneWebcamProvider.js»)
8234
- // (in a canvas drawing function with canvasContext ctx :)
8235
- let ctx=mediaHandler.canvasCtx;
8236
-
8330
+
8331
+ // // DOES NOT WORK WELL :
8332
+ // if(tagConfig && tagConfig.useNoVideoReadingElement){
8333
+ //
8334
+ // mediaHandler.getVideoData=()=>{
8335
+ // if(mediaHandler.currentVideoFrame==null)
8336
+ // mediaHandler.imageReader.read().then((videoFrame)=>{
8337
+ // mediaHandler.currentVideoFrame=videoFrame.value;
8338
+ // });
8339
+ // currentVideoFrame=mediaHandler.currentVideoFrame;
8340
+ // mediaHandler.currentVideoFrame=null;
8341
+ // return currentVideoFrame;
8342
+ // };
8343
+ //
8344
+ // }else{
8345
+
8346
+ mediaHandler.getVideoData=function(){
8347
+
8348
+ // To read the image data : (code from «PlaneWebcamProvider.js»)
8349
+ // (in a canvas drawing function with canvasContext ctx :)
8350
+
8351
+ const ctx=mediaHandler.canvasCtx;
8237
8352
  ctx.save();
8238
- let imageObj= (isArray(mediaHandler.video)?mediaHandler.video[0]:mediaHandler.video);
8353
+ const imageObj= (isArray(mediaHandler.videoReadingElement)?mediaHandler.videoReadingElement[0]:mediaHandler.videoReadingElement);
8239
8354
  ctx.drawImage(imageObj,x,y,width,height);
8240
- let imageData=ctx.getImageData(x, y, width, height);
8355
+ const imageData=ctx.getImageData(x, y, width, height);
8356
+ imageData.messWithAlpha=true;
8241
8357
  foreach(mediaHandler.imageInterceptors,(method)=>{method(imageData.data);});
8358
+
8242
8359
  ctx.restore();
8243
-
8244
- imageData.messWithAlpha=true;
8360
+
8245
8361
 
8246
8362
  return imageData;// returns an object containing data in this form : {data:<image data array>, width:<image width>, height:<image height>, messWithAlpha:true}
8247
- };
8363
+ };
8364
+ // }
8365
+
8366
+
8367
+
8368
+
8369
+
8248
8370
 
8249
8371
  // If we have feed actual video visible tags, then we play them :
8250
8372
  if(tagConfig && tagConfig.videoToUse && !tagConfig.invisibleVideoTag){
8251
8373
 
8252
8374
 
8253
- if(!isArray(mediaHandler.video)){
8254
- mediaHandler.video.onloadedmetadata=function(event){
8375
+ if(!isArray(mediaHandler.videoReadingElement)){
8376
+ mediaHandler.videoReadingElement.onloadedmetadata=function(event){
8255
8377
  let video=event.target;
8256
8378
  video.play();
8257
8379
  if(tagConfig.videoMuted) video.muted=true;
8258
8380
  else delete video.muted;
8259
8381
  };
8260
8382
  }else{
8261
- foreach(mediaHandler.video,(v)=>{
8383
+ foreach(mediaHandler.videoReadingElement,(v)=>{
8262
8384
  v.onloadedmetadata=function(event){
8263
8385
  let video=event.target;
8264
8386
  video.play();
@@ -8393,21 +8515,31 @@ drawVideoImage=function(canvas,videoImage,
8393
8515
  /*OPTIONAL*/newWidthParam,/*OPTIONAL*/newHeightParam,
8394
8516
  /*OPTIONAL*/isMessWithAlpha){
8395
8517
 
8396
- var videoRawImageData=videoImage.data;
8518
+ const ctx=canvas.getContext("2d");
8519
+
8520
+ var x=nonull(xParam,0);
8521
+ var y=nonull(yParam,0);
8522
+
8523
+ // // DOES NOT WORK WELL :
8524
+ // if(Object.getPrototypeOf(videoImage).constructor.name==="VideoFrame"){
8525
+ // ctx.drawImage(videoImage, x, y, newWidthParam, newHeightParam);
8526
+ // return;
8527
+ // }
8528
+
8397
8529
  var oldWidth=videoImage.width;
8398
8530
  var oldHeight=videoImage.height;
8399
8531
 
8400
- var x=nonull(xParam,0);
8401
- var y=nonull(yParam,0);
8402
8532
  const zooms=nonull(zoomsParam,{zx:1,zy:1});
8403
8533
 
8404
8534
  var newWidth=newWidthParam?newWidthParam:canvas.width;
8405
8535
  var newHeight=newHeightParam?newHeightParam:canvas.height;
8406
8536
 
8407
- var ctx=canvas.getContext("2d");
8408
8537
 
8409
8538
  let imageData=null;
8410
8539
  let tmpImage=null;
8540
+
8541
+
8542
+ const videoRawImageData=videoImage.data;
8411
8543
  if(videoImage.format==="base64"){
8412
8544
 
8413
8545
  tmpImage=new Image();
@@ -8422,6 +8554,9 @@ drawVideoImage=function(canvas,videoImage,
8422
8554
  normalizeData(videoRawImageData, imageData.data, isMessWithAlpha);
8423
8555
 
8424
8556
  }
8557
+
8558
+
8559
+
8425
8560
 
8426
8561
  let clipX=0; // UNUSED
8427
8562
  let clipY=0; // UNUSED
@@ -9676,7 +9811,7 @@ function arrayFromString(strParam,compressResult=true,precisionFraction=null,isS
9676
9811
  // appendAsFirstChildOf(videoTag, backgroundContainerDiv);
9677
9812
  //}
9678
9813
  //
9679
- //webcamHandler.video=videoTag;
9814
+ //webcamHandler.videoReadingElement=videoTag;
9680
9815
  //
9681
9816
  //webcamHandler.localMediaStream=null;
9682
9817
  //
@@ -9685,8 +9820,8 @@ function arrayFromString(strParam,compressResult=true,precisionFraction=null,isS
9685
9820
  //
9686
9821
  // // Not showing vendor prefixes or code that works cross-browser.
9687
9822
  // var initStream=function(stream){
9688
- // DEPRECATED : self.video.src=window.URL.createObjectURL(stream);
9689
- // self.video.srcObject=stream;
9823
+ // DEPRECATED : self.videoReadingElement.src=window.URL.createObjectURL(stream);
9824
+ // self.videoReadingElement.srcObject=stream;
9690
9825
  // self.localMediaStream=stream;
9691
9826
  // };
9692
9827
  //
@@ -9757,7 +9892,7 @@ function arrayFromString(strParam,compressResult=true,precisionFraction=null,isS
9757
9892
  //}
9758
9893
  //
9759
9894
  //webcamHandler.isReady=function(){
9760
- // return webcamHandler.video !== null;
9895
+ // return webcamHandler.videoReadingElement !== null;
9761
9896
  //};
9762
9897
  //
9763
9898
  //return webcamHandler;
@@ -10838,6 +10973,49 @@ function normalizeDisplayQuotesForJSONParsing(str){
10838
10973
  }
10839
10974
 
10840
10975
 
10976
+
10977
+ // Workaround to avoid «User interaction» errors for XR initialization :
10978
+
10979
+ function addUserInteractionButton(selfObject, functionToExecuteAfterParam, functionArguments, label="START"){
10980
+
10981
+ const userInteractionButtonId="userInteractionButton";
10982
+ let xrButton=document.getElementById(userInteractionButtonId);
10983
+ if(!xrButton){
10984
+ xrButton=document.createElement("button");
10985
+ xrButton.id=userInteractionButtonId;
10986
+ xrButton.innerHTML=label;
10987
+ xrButton.style="position:fixed; font-size:7em; width:100%; height:100%; z-index:9999; cursor:pointer;";
10988
+ document.body.appendChild(xrButton);
10989
+ }
10990
+
10991
+ xrButton.selfObject=selfObject;
10992
+ xrButton.listenerArgs=functionArguments;
10993
+ xrButton.functionToExecuteAfter=functionToExecuteAfterParam;
10994
+ xrButton.addEventListener("click",(event)=>{
10995
+
10996
+ const targetXRButton=event.target;
10997
+ const self=targetXRButton.selfObject;
10998
+ const listenerArgs=targetXRButton.listenerArgs;
10999
+ const functionToExecuteAfter=targetXRButton.functionToExecuteAfter;
11000
+
11001
+ targetXRButton.parentNode.removeChild(targetXRButton);
11002
+
11003
+ functionToExecuteAfter.apply(self,listenerArgs);
11004
+
11005
+ });
11006
+
11007
+ }
11008
+
11009
+
11010
+
11011
+ ////
11012
+
11013
+
11014
+
11015
+
11016
+
11017
+
11018
+
10841
11019
  //-------------------------------------------------------------------------------------------
10842
11020
 
10843
11021
 
@@ -10881,7 +11059,7 @@ function promptWindow(label,type=null,defaultValue=null,
10881
11059
  //Each field rendering :
10882
11060
  /* private */const getRenderedInputElement=function(inputType, defaultValueParam=null,/* OPTIONAL */labelParam=null){
10883
11061
 
10884
- const DISABLED_OPACITY=0.6;
11062
+ const DISABLED_RADIO_OPACITY=0.6;
10885
11063
 
10886
11064
  let inputElement=null;
10887
11065
  if(contains(inputType, "password")){
@@ -10951,7 +11129,7 @@ function promptWindow(label,type=null,defaultValue=null,
10951
11129
 
10952
11130
  if(choiceLabelStr.inactive){
10953
11131
  radio.disabled=true;
10954
- radio.style.opacity=DISABLED_OPACITY;
11132
+ radio.style.opacity=DISABLED_RADIO_OPACITY;
10955
11133
  }
10956
11134
  }
10957
11135
 
@@ -11332,155 +11510,7 @@ getInputCoords=function(event){
11332
11510
 
11333
11511
 
11334
11512
 
11335
-
11336
- // Three.js utils :
11337
-
11338
- /*private*/function isWebglAvailable(){
11339
- try {
11340
- let canvas=document.createElement("canvas");
11341
- return !!(window.WebGLRenderingContext && (canvas.getContext("webgl") || canvas.getContext("experimental-webgl")));
11342
- } catch (error){
11343
- return false;
11344
- }
11345
- }
11346
-
11347
-
11348
- function getThreeRenderer(conf, pixelRatio=window.devicePixelRatio){
11349
-
11350
- if(!THREE){
11351
- lognow("ERROR : Three.js library missing ! Cannot create ThreeJS renderer.");
11352
- return null;
11353
- }
11354
-
11355
- let renderer;
11356
- if(conf.canvas){
11357
- if(isWebglAvailable()){
11358
-
11359
- if(conf.background==="transparent"){
11360
- conf.alpha=true;
11361
- }
11362
-
11363
- renderer=new THREE.WebGLRenderer(conf);
11364
- renderer.setPixelRatio(pixelRatio);
11365
-
11366
- if(conf.background==="transparent"){
11367
- renderer.setClearColor(0x000000, 0);
11368
- }
11369
-
11370
- }
11371
- }else if(conf.type === "css"){
11372
- renderer=new THREE.CSS3DRenderer();
11373
- }
11374
-
11375
- if(!renderer){
11376
- // TRACE
11377
- lognow("ERROR : No renderer found found. Aborting.");
11378
- return null;
11379
- }
11380
-
11381
- // REMOVED FROM THREEJS :
11382
- // let renderer=new THREE.CanvasRenderer({
11383
- // canvas : conf.canvas
11384
- // });
11385
-
11386
-
11387
- if(conf.canvas){
11388
- renderer.setSize(conf.canvas.width, conf.canvas.height);
11389
- }else if(conf.w && conf.h){
11390
- renderer.setSize(conf.w, conf.h);
11391
- }
11392
-
11393
- return renderer;
11394
- }
11395
-
11396
-
11397
- function getVRHandler(renderer,modeVR="immersive-vr",optionsVR=["local-floor", "bounded-floor", "hand-tracking", "layers"]){
11398
- // "immersive-ar", "immersive-vr", "inline"
11399
-
11400
- // See : https://developer.mozilla.org/en-US/docs/Web/API/XRSystem/requestSession
11401
-
11402
- if(typeof(navigator)===undefined || !renderer){
11403
- // TRACE
11404
- lognow("ERROR : No 3D renderer provided, cannot init the VR session.");
11405
- return null;
11406
- }
11407
-
11408
-
11409
- if("xr" in navigator){
11410
-
11411
- const result={
11412
-
11413
- currentSession:null,
11414
-
11415
- /*private*/onSessionStarted:async function(session){
11416
- session.addEventListener("end", result.onSessionEnded);
11417
-
11418
- await renderer.xr.setSession(session);
11419
-
11420
- result.currentSession=session;
11421
- },
11422
-
11423
- /*private*/onSessionEnded:(event)=>{
11424
- result.currentSession.removeEventListener("end", result.onSessionEnded);
11425
- result.currentSession=null;
11426
- },
11427
-
11428
- start:()=>{
11429
-
11430
- if(result.currentSession) return;
11431
-
11432
- // WebXR's requestReferenceSpace only works if the corresponding feature
11433
- // was requested at session creation time. For simplicity, just ask for
11434
- // the interesting ones as optional features, but be aware that the
11435
- // requestReferenceSpace call will fail if it turns out to be unavailable.
11436
- // ('local' is always available for immersive sessions and doesn't need to
11437
- // be requested separately.)
11438
-
11439
- const sessionInitConf={optionalFeatures:optionsVR};
11440
- navigator.xr.requestSession(modeVR, sessionInitConf)
11441
- .then(result.onSessionStarted)
11442
- .catch(error=>lognow("ERROR : Error initializing XR context : ",error));
11443
-
11444
- return result;
11445
- },
11446
-
11447
- end:()=>{
11448
- if(!result.currentSession) return;
11449
- result.currentSession.end();
11450
- return result;
11451
- },
11452
-
11453
- };
11454
-
11455
- navigator.xr.isSessionSupported(modeVR).then((supported)=>{
11456
- if(!supported){
11457
- // TRACE
11458
- lognow("ERROR : VR is not supported by your browser.");
11459
- }else{
11460
- result.isReady=true;
11461
- }
11462
- });
11463
-
11464
- if(renderer.xr) renderer.xr.enabled=true;
11465
-
11466
- return result;
11467
- }
11468
-
11469
- if(window.isSecureContext === false) {
11470
- // TRACE
11471
- lognow("ERROR : WEBXR needs a HTTPS environemnt.");
11472
- return null;
11473
- }
11474
-
11475
- // TRACE
11476
- lognow("ERROR : WEBXR not available.");
11477
-
11478
- return null;
11479
- };
11480
-
11481
-
11482
-
11483
- // WEBGL Management :
11513
+ // HTML Management :
11484
11514
 
11485
11515
  function convertHTMLToImage(elementHTML,callBack,mode="png",quality=null,height,width){
11486
11516
 
@@ -12570,6 +12600,10 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
12570
12600
  outputsGPIO:{},
12571
12601
  inputsGPIO:{},
12572
12602
 
12603
+ setCaptureConfig:(captureConfig)=>{
12604
+ oritaClient.captureConfig=captureConfig;
12605
+ return oritaClient;
12606
+ },
12573
12607
 
12574
12608
  };
12575
12609
 
@@ -12863,7 +12897,7 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
12863
12897
  console.log("mh:", mh); console.log("medias:", medias);
12864
12898
  doOnStartMediaHandlerMicroClient(mh, medias, videoSide);
12865
12899
  },
12866
- (mh) => { mediaHandlerLocal = mh; });
12900
+ (mh) => { mediaHandlerLocal = mh; }).catch(err=>{lognow("ERROR : Could not get webcam handler for BROWSER MICROCLIENT.");});
12867
12901
 
12868
12902
  } else { // Case node micro client :
12869
12903
 
@@ -12884,13 +12918,13 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
12884
12918
  console.log("audioConfigLocal", audioConfigLocal);
12885
12919
 
12886
12920
 
12887
- mediaHandlerLocal = getNodeMediaHandler(
12921
+ mediaHandlerLocal = getMediaHandler(
12888
12922
  videoConfigLocal,
12889
12923
  audioConfigLocal,
12890
12924
  // CHANNELS_CONFIG,
12891
12925
  { width: captureWidth, height: captureHeight },
12892
12926
  audioBufferSize, 1/*(means mono audio)*/,
12893
- (mh) => { doOnStartMediaHandlerMicroClient(mh, medias, videoSide); });
12927
+ (mh) => { doOnStartMediaHandlerMicroClient(mh, medias, videoSide); }).catch(err=>{lognow("ERROR : Could not get webcam handler for MICROCLIENT.");});
12894
12928
 
12895
12929
 
12896
12930
  }
@@ -13613,7 +13647,7 @@ getAORTACClient=function(clientId=getUUID(), serverNodeOrigin="ws://127.0.0.1:40
13613
13647
 
13614
13648
 
13615
13649
 
13616
- /*utils GEOMETRY library associated with aotra version : «1_29072022-2359 (24/06/2025-01:50:32)»*/
13650
+ /*utils GEOMETRY library associated with aotra version : «1_29072022-2359 (11/11/2025-17:05:51)»*/
13617
13651
  /*-----------------------------------------------------------------------------*/
13618
13652
 
13619
13653
 
@@ -13623,7 +13657,7 @@ getAORTACClient=function(clientId=getUUID(), serverNodeOrigin="ws://127.0.0.1:40
13623
13657
  * Several authors of snippets published freely on the Internet contributed to this library.
13624
13658
  * Feel free to use/modify-enhance/publish them under the terms of its license.
13625
13659
  *
13626
- * # Library name : «aotrautils»
13660
+ * # Library name : «aotrautils geometry»
13627
13661
  * # Library license : HGPL(Help Burma) (see aotra README information for details : https://alqemia.com/aotra.js )
13628
13662
  * # Author name : Jérémie Ratomposon (massively helped by his native country free education system)
13629
13663
  * # Author email : info@alqemia.com
@@ -14852,7 +14886,10 @@ function rayVsUnitSphereClosestPoint(p, r) {
14852
14886
  // MUST REMAIN AT THE END OF THIS LIBRARY FILE !
14853
14887
 
14854
14888
  AOTRAUTILS_GEOMETRY_LIB_IS_LOADED=true;
14855
- /*utils AI library associated with aotra version : «1_29072022-2359 (24/06/2025-01:50:32)»*/
14889
+ /*utils 3D library associated with aotra version : «1_29072022-2359 (11/11/2025-17:05:51)»*/
14890
+ /*-----------------------------------------------------------------------------*/
14891
+
14892
+ /*utils AI library associated with aotra version : «1_29072022-2359 (11/11/2025-17:05:51)»*/
14856
14893
  /*-----------------------------------------------------------------------------*/
14857
14894
 
14858
14895
 
@@ -14998,7 +15035,7 @@ getOpenAIAPIClient=(modelName, apiURL, agentRole, defaultPrompt)=>{
14998
15035
 
14999
15036
 
15000
15037
 
15001
- /*utils CONSOLE library associated with aotra version : «1_29072022-2359 (24/06/2025-01:50:32)»*/
15038
+ /*utils CONSOLE library associated with aotra version : «1_29072022-2359 (11/11/2025-17:05:51)»*/
15002
15039
  /*-----------------------------------------------------------------------------*/
15003
15040
 
15004
15041
 
@@ -15422,7 +15459,12 @@ WebsocketImplementation={
15422
15459
  return null;
15423
15460
  }
15424
15461
 
15425
- clientSocket=new WebSocket(serverURL+":"+port,/*WORKAROUND:*/{ rejectUnauthorized:false, secure: isSecure });
15462
+ clientSocket=new WebSocket(serverURL+":"+port,/*WORKAROUND:*/{
15463
+ // CAUTION : SECURITY BREACH :
15464
+ // BUT ALSO NECESSARY TO ALLOW SELF-SIGNED CERTIFICATES USAGE WITH THE YESBOT SYSTEM !
15465
+ rejectUnauthorized:false, // (THIS IS A KNOWN SECURITY BREACH)
15466
+ secure: isSecure
15467
+ });
15426
15468
  }else{
15427
15469
  // NOW : socket.io :
15428
15470
  //client on server-side:
@@ -15460,6 +15502,11 @@ WebsocketImplementation={
15460
15502
  // BROWSER CLIENT MODE ONLY :
15461
15503
  let clientSocket;
15462
15504
  if(!WebsocketImplementation.useSocketIOImplementation){
15505
+ // CAUTION : PARAMETER rejectUnauthorized:false WILL DO NOTHING,
15506
+ // BECAUSE THIS IS COMPLETLY HANDLED BY THE BROWSER SECURITY POLICY !
15507
+ // SO TO CLEAR THE SSL ERROR :
15508
+ // - FIRST GO TO THE HTTPS:// SERVER ADDRESS WITH BROWSER
15509
+ // - THEN ADD THE SECURITY EXCEPTION IN THE BROWSER !
15463
15510
  clientSocket=new WebSocket(serverURL+":"+port,["ws","wss"]);
15464
15511
  }else if(typeof(io)!=="undefined"){
15465
15512
  // OLD SYNTAX :clientSocket=io.connect(serverURL + ":" + port,{timeout: timeout, secure: isSecure});
@@ -15478,7 +15525,7 @@ WebsocketImplementation={
15478
15525
 
15479
15526
  launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /*OPTIONAL*/sslOptions=null, httpHandlerParam=null, addCORSHeader=ADD_CORS_HEADER){
15480
15527
 
15481
- const EXCLUDED_FILENAMES_PARTS=[".keyHash."];
15528
+ const EXCLUDED_FILENAMES_PARTS=[".keyHash.",".pem"];
15482
15529
 
15483
15530
 
15484
15531
 
@@ -15745,8 +15792,8 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
15745
15792
  }else{
15746
15793
  try{
15747
15794
  sslOptions={
15748
- cert: fs.readFileSync(certPath),
15749
- key: fs.readFileSync(keyPath),
15795
+ cert: fs.readFileSync(certPath, {encoding: "utf8"}),
15796
+ key: fs.readFileSync(keyPath, {encoding: "utf8"}),
15750
15797
  };
15751
15798
  }catch(exception){
15752
15799
  // TRACE
@@ -17162,7 +17209,14 @@ class NodeServerInstance{
17162
17209
  }
17163
17210
 
17164
17211
  close(doOnCloseServer){
17165
- if(!this.serverSocket) return;
17212
+ if(!this.serverSocket){
17213
+ //TRACE
17214
+ lognow("WARN : Server socket is null ! Cannot close an inexistant serer socket. Attempting to close attached HTTP server anyway.");
17215
+ lognow("WARN : THIS MAY CAUSE A MEMORY LEAK !!!");
17216
+ if(!this.listenableServer) return;
17217
+ this.listenableServer.close(doOnCloseServer);
17218
+ return;
17219
+ }
17166
17220
  this.serverSocket.close(()=>{
17167
17221
  if(!this.listenableServer) return;
17168
17222
  this.listenableServer.close(doOnCloseServer);
aotrautils/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aotrautils",
3
- "version": "0.0.1796",
3
+ "version": "0.0.1797",
4
4
  "main": "aotrautils.build.js",
5
5
  "description": "A library for vanilla javascript utils (client-side) used in aotra javascript CMS",
6
6
  "author": "Jeremie Ratomposon <info@alqemia.com> (https://alqemia.com)",