@3dsource/angular-unreal-module 0.0.132 → 0.0.134

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.
@@ -807,9 +807,12 @@ function OnOpenHandler() {
807
807
  */
808
808
  const keepMaxUntilReset = (reset$) => (source$) => merge(source$, reset$.pipe(map(() => null))).pipe(scan((max, value) => (value === null ? 0 : Math.max(max, value)), 0));
809
809
 
810
+ // Reused across every inbound data-channel message — instantiating a
811
+ // TextDecoder per call adds needless GC churn on a hot path.
812
+ const utf16Decoder = new TextDecoder('utf-16');
810
813
  function decodeData(anyData) {
811
814
  try {
812
- return new TextDecoder('utf-16').decode(anyData);
815
+ return utf16Decoder.decode(anyData);
813
816
  }
814
817
  catch {
815
818
  return '';
@@ -874,7 +877,6 @@ class DataFlowMonitor {
874
877
  },
875
878
  isDropDetected: true,
876
879
  dropPercentage,
877
- dataHistory: [...this.dataHistory],
878
880
  activeMedian: secondHalfMedian,
879
881
  quality: isRedFlag ? 'red' : 'orange',
880
882
  message: `Significant flow drop detected: ${dropPercentage.toFixed(2)}% (from ${firstHalfMedian} to ${secondHalfMedian})`,
@@ -889,7 +891,6 @@ class DataFlowMonitor {
889
891
  },
890
892
  isDropDetected: false,
891
893
  dropPercentage,
892
- dataHistory: [...this.dataHistory],
893
894
  activeMedian: secondHalfMedian,
894
895
  quality: 'lime',
895
896
  message: 'Stable flow',
@@ -1071,6 +1072,28 @@ function qpToQuality(VideoEncoderQP) {
1071
1072
  return quality;
1072
1073
  }
1073
1074
 
1075
+ /**
1076
+ * Base64-encodes a byte array.
1077
+ *
1078
+ * Builds the intermediate binary string in fixed-size chunks instead of
1079
+ * concatenating one character at a time. The naive `bytes.reduce((s, b) => s +
1080
+ * String.fromCharCode(b), '')` is O(n²) (each `+` re-copies the growing string)
1081
+ * and stalls the main thread for a full-resolution frame; chunking keeps it
1082
+ * O(n). The chunk size stays under the argument-count limit of
1083
+ * `String.fromCharCode.apply`/spread.
1084
+ *
1085
+ * @param bytes - The raw bytes to encode.
1086
+ * @returns The Base64 representation of `bytes`.
1087
+ */
1088
+ function uint8ToBase64(bytes) {
1089
+ const CHUNK_SIZE = 0x8000;
1090
+ let binary = '';
1091
+ for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {
1092
+ binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK_SIZE));
1093
+ }
1094
+ return btoa(binary);
1095
+ }
1096
+
1074
1097
  function fromResizeObserver(elem) {
1075
1098
  return new Observable((subscriber) => {
1076
1099
  const observer = new window.ResizeObserver((entries) => subscriber.next(entries));
@@ -2233,7 +2256,11 @@ class SignallingService extends SubService {
2233
2256
  dataString = JSON.stringify(data);
2234
2257
  }
2235
2258
  if (this.ws && this.ws.readyState === WS_OPEN_STATE) {
2236
- Logger.colored(...COLOR_CODES.TO_CIRRUS, JSON.parse(dataString));
2259
+ // Only parse-for-display when the logger would actually print — avoids a
2260
+ // JSON.parse on every outbound message in production (logger is a no-op).
2261
+ if (Logger.isDevMode) {
2262
+ Logger.colored(...COLOR_CODES.TO_CIRRUS, JSON.parse(dataString));
2263
+ }
2237
2264
  try {
2238
2265
  this.ws.send(dataString);
2239
2266
  }
@@ -2426,7 +2453,7 @@ class VideoService extends SubService {
2426
2453
  this.latencyTestTimings.SetUETimings(latencyTimings);
2427
2454
  }
2428
2455
  setEncoder(data) {
2429
- this.VideoEncoderQP = Number(new TextDecoder('utf-16').decode(data));
2456
+ this.VideoEncoderQP = Number(decodeData(data));
2430
2457
  }
2431
2458
  create() {
2432
2459
  if (!this.container) {
@@ -4064,7 +4091,7 @@ class FreezeFrameService extends SubService {
4064
4091
  if (!this.jpeg) {
4065
4092
  return;
4066
4093
  }
4067
- const base64 = btoa(this.jpeg?.reduce((data, byte) => data + String.fromCharCode(byte), ''));
4094
+ const base64 = uint8ToBase64(this.jpeg);
4068
4095
  this.freezeFrameOverlay.src = 'data:image/jpeg;base64,' + base64;
4069
4096
  this.store.dispatch(setFreezeFrame({
4070
4097
  dataUrl: this.freezeFrameOverlay.src,
@@ -4162,7 +4189,7 @@ class FileReceiverService extends SubService {
4162
4189
  this.resetOnStart();
4163
4190
  }
4164
4191
  //Logger.info(extensionAsString);
4165
- this.extension = new TextDecoder('utf-16').decode(view.slice(1));
4192
+ this.extension = decodeData(view.slice(1).buffer);
4166
4193
  }
4167
4194
  /**
4168
4195
  * Processes a file mime type when received over data channel
@@ -4173,7 +4200,7 @@ class FileReceiverService extends SubService {
4173
4200
  if (!this.receiving) {
4174
4201
  this.resetOnStart();
4175
4202
  }
4176
- const mimeAsString = new TextDecoder('utf-16').decode(view.slice(1));
4203
+ const mimeAsString = decodeData(view.slice(1).buffer);
4177
4204
  // Logger.info(mimeAsString);
4178
4205
  this.mimetype = mimeAsString;
4179
4206
  }
@@ -4353,8 +4380,10 @@ class AggregatorService extends SubService {
4353
4380
  break;
4354
4381
  }
4355
4382
  case ToClientMessage.Command: {
4356
- const command = JSON.parse(decodeData(anyData));
4357
- Logger.colored(...COLOR_CODES.FROM_UNREAL, command);
4383
+ // The parsed command is only used for the dev log — skip the work in prod.
4384
+ if (Logger.isDevMode) {
4385
+ Logger.colored(...COLOR_CODES.FROM_UNREAL, JSON.parse(decodeData(anyData)));
4386
+ }
4358
4387
  break;
4359
4388
  }
4360
4389
  case ToClientMessage.FileExtension:
@@ -4388,10 +4417,14 @@ class AggregatorService extends SubService {
4388
4417
  break;
4389
4418
  }
4390
4419
  case ToClientMessage.InitialSettings:
4391
- Logger.colored(...COLOR_CODES.FROM_UNREAL, 'InitialSettings message :', JSON.parse(decodeData(anyData)));
4420
+ if (Logger.isDevMode) {
4421
+ Logger.colored(...COLOR_CODES.FROM_UNREAL, 'InitialSettings message :', JSON.parse(decodeData(anyData)));
4422
+ }
4392
4423
  break;
4393
4424
  case ToClientMessage.Protocol:
4394
- Logger.colored(...COLOR_CODES.FROM_UNREAL, 'Protocol message :', JSON.parse(decodeData(anyData)));
4425
+ if (Logger.isDevMode) {
4426
+ Logger.colored(...COLOR_CODES.FROM_UNREAL, 'Protocol message :', JSON.parse(decodeData(anyData)));
4427
+ }
4395
4428
  break;
4396
4429
  case ToClientMessage.InputControlOwnership:
4397
4430
  Logger.colored(...COLOR_CODES.FROM_UNREAL, 'InputControlOwnership message :', anyData);
@@ -4493,6 +4526,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4493
4526
  }], ctorParameters: () => [] });
4494
4527
 
4495
4528
  const INITIAL_FINGER_IDS = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
4529
+ const UNSIGNED_RANGE = 65536;
4530
+ const UNSIGNED_OUT_OF_RANGE = 65535;
4531
+ const SIGNED_RANGE = 32767;
4532
+ const WHEEL_DELTA_SCALE = -120;
4533
+ const RESIZE_DEBOUNCE_MS = 300;
4534
+ const FUNCTION_KEY_MIN = 112;
4535
+ const FUNCTION_KEY_MAX = 123;
4536
+ const TAB_KEY_CODE = 9;
4496
4537
  class InputService extends SubService {
4497
4538
  constructor() {
4498
4539
  super();
@@ -4522,19 +4563,25 @@ class InputService extends SubService {
4522
4563
  this.deactivatedVideoTrigger$ = NEVER;
4523
4564
  this.normalizeAndQuantizeUnsigned = (_x, _y) => ({
4524
4565
  inRange: false,
4525
- x: 65535,
4526
- y: 65535,
4566
+ x: UNSIGNED_OUT_OF_RANGE,
4567
+ y: UNSIGNED_OUT_OF_RANGE,
4527
4568
  });
4528
4569
  this.normalizeAndQuantizeSigned = (_x, _y) => ({ x: 0, y: 0 });
4529
4570
  this.videoBoundingRect = new DOMRect(0, 0, 1, 1);
4530
4571
  this.fingerIds = new Map();
4531
4572
  this.maxByteValue = 255;
4532
4573
  this.fingers = [...INITIAL_FINGER_IDS];
4533
- // Captured straight from normalizeCoordinates inside the emit* methods —
4534
- // i.e., the exact quantized values that just went out over the data channel.
4535
- // Resending these on resize avoids any re-transformation through stale
4536
- // offsetParams or container dimensions.
4537
- this.lastNormalizedCoordinates = null;
4574
+ // Reusable wire buffers. Input events fire at up to ~60 Hz; the data channel
4575
+ // copies the bytes synchronously on send (RTCDataChannel.send), so a single
4576
+ // buffer per message shape is safe and avoids per-event allocation/GC churn.
4577
+ this.mouseMoveView = new DataView(new ArrayBuffer(9));
4578
+ this.mouseButtonView = new DataView(new ArrayBuffer(6));
4579
+ this.mouseWheelView = new DataView(new ArrayBuffer(7));
4580
+ this.touchView = new DataView(new ArrayBuffer(9));
4581
+ this.keyPressView = new DataView(new ArrayBuffer(3));
4582
+ this.singleByteView = new DataView(new ArrayBuffer(1));
4583
+ this.keyDownBytes = new Uint8Array(3);
4584
+ this.keyUpBytes = new Uint8Array(2);
4538
4585
  this.offsetParams = {
4539
4586
  scaleX: 1,
4540
4587
  scaleY: 1,
@@ -4573,26 +4620,11 @@ class InputService extends SubService {
4573
4620
  this.fingerIds.clear();
4574
4621
  this.fingers = [...INITIAL_FINGER_IDS];
4575
4622
  merge(fromResizeObserver(video), this.actions$.pipe(ofType(videoAdaptedToContainer)))
4576
- .pipe(debounceTime$1(300), takeUntil$1(this.deactivatedVideoTrigger$))
4577
- .subscribe(() => {
4578
- this.setupNormalizeAndQuantize(video);
4579
- this.resendLastMousePosition();
4580
- });
4623
+ .pipe(debounceTime$1(RESIZE_DEBOUNCE_MS), takeUntil$1(this.deactivatedVideoTrigger$))
4624
+ .subscribe(() => this.setupNormalizeAndQuantize(video));
4581
4625
  this.setupNormalizeAndQuantize(video);
4582
4626
  this.registerInputs(video);
4583
4627
  }
4584
- resendLastMousePosition() {
4585
- const last = this.lastNormalizedCoordinates;
4586
- if (!last)
4587
- return;
4588
- const data = new DataView(new ArrayBuffer(9));
4589
- data.setUint8(0, MessageType.MouseMove);
4590
- data.setUint16(1, last.x, true);
4591
- data.setUint16(3, last.y, true);
4592
- data.setInt16(5, 0, true);
4593
- data.setInt16(7, 0, true);
4594
- this.sendInputData(data.buffer);
4595
- }
4596
4628
  /**
4597
4629
  * We use `object-fit: cover` on the video element. Since the video can have
4598
4630
  * a different aspect ratio from its container (and potentially be larger),
@@ -4610,18 +4642,13 @@ class InputService extends SubService {
4610
4642
  videoWidth,
4611
4643
  videoHeight,
4612
4644
  });
4613
- // Compute aspect ratios
4614
4645
  const containerRatio = width / height;
4615
4646
  const videoRatio = videoWidth / videoHeight;
4616
- // Determine how much to scale the video to fit the container (cover mode)
4617
4647
  const scale = videoRatio > containerRatio ? height / videoHeight : width / videoWidth;
4618
- // Calculate the displayed (scaled) dimensions of the video
4619
4648
  const scaledWidth = videoWidth * scale;
4620
4649
  const scaledHeight = videoHeight * scale;
4621
- // Offsets in x/y directions (like "padding") once the video is scaled
4622
4650
  const offsetByX = (width - scaledWidth) / 2;
4623
4651
  const offsetByY = (height - scaledHeight) / 2;
4624
- // Scale factors for coordinate transformation from container space to video space
4625
4652
  const scaleX = width / scaledWidth;
4626
4653
  const scaleY = height / scaledHeight;
4627
4654
  return {
@@ -4637,14 +4664,13 @@ class InputService extends SubService {
4637
4664
  this.videoBoundingRect = videoElement.getBoundingClientRect();
4638
4665
  this.offsetParams = this.getObjectFitOffset(videoElement);
4639
4666
  // Unsigned XY positions are the ratio (0.0..1.0) along a viewport axis,
4640
- // quantized into an uint16 (0..65536).
4667
+ // quantized into a uint16 (0..65536).
4641
4668
  // Signed XY deltas are the ratio (-1.0..1.0) along a viewport axis,
4642
4669
  // quantized into an int16 (-32767..32767).
4643
- // This allows the browser viewport and client viewport to have a different
4644
- // size.
4645
- // Hack: Currently, we set an out-of-range position to an extreme (65535)
4646
- // as we can't yet accurately detect mouse enter and leave events
4647
- // precisely inside a video with an aspect ratio which causes mattes.
4670
+ // This allows the browser viewport and client viewport to have different sizes.
4671
+ // Hack: an out-of-range position becomes UNSIGNED_OUT_OF_RANGE because we
4672
+ // can't yet accurately detect mouse enter/leave precisely inside a video
4673
+ // with an aspect ratio that causes mattes.
4648
4674
  this.normalizeAndQuantizeUnsigned = (x, y) => {
4649
4675
  const normalizedX = x / videoElement.clientWidth;
4650
4676
  const normalizedY = y / videoElement.clientHeight;
@@ -4654,25 +4680,22 @@ class InputService extends SubService {
4654
4680
  normalizedY > 1.0) {
4655
4681
  return {
4656
4682
  inRange: false,
4657
- x: 65535,
4658
- y: 65535,
4659
- };
4660
- }
4661
- else {
4662
- return {
4663
- inRange: true,
4664
- x: normalizedX * 65536,
4665
- y: normalizedY * 65536,
4683
+ x: UNSIGNED_OUT_OF_RANGE,
4684
+ y: UNSIGNED_OUT_OF_RANGE,
4666
4685
  };
4667
4686
  }
4687
+ return {
4688
+ inRange: true,
4689
+ x: normalizedX * UNSIGNED_RANGE,
4690
+ y: normalizedY * UNSIGNED_RANGE,
4691
+ };
4668
4692
  };
4669
- // Signed.
4670
4693
  this.normalizeAndQuantizeSigned = (x, y) => {
4671
4694
  const normalizedX = x / (0.5 * videoElement.clientWidth);
4672
4695
  const normalizedY = y / (0.5 * videoElement.clientHeight);
4673
4696
  return {
4674
- x: normalizedX * 32767,
4675
- y: normalizedY * 32767,
4697
+ x: normalizedX * SIGNED_RANGE,
4698
+ y: normalizedY * SIGNED_RANGE,
4676
4699
  };
4677
4700
  };
4678
4701
  }
@@ -4695,7 +4718,7 @@ class InputService extends SubService {
4695
4718
  y: (clientY - offsetByY - this.videoBoundingRect.top) * scaleY,
4696
4719
  };
4697
4720
  }
4698
- getMousePositionWithOffset({ offsetX, offsetY, }) {
4721
+ getMousePositionWithOffset({ offsetX, offsetY }) {
4699
4722
  const { offsetByX, offsetByY, scaleX, scaleY } = this.offsetParams;
4700
4723
  return {
4701
4724
  x: (offsetX - offsetByX) * scaleX,
@@ -4703,8 +4726,6 @@ class InputService extends SubService {
4703
4726
  };
4704
4727
  }
4705
4728
  registerTouchEvents(htmlVideoElement) {
4706
- // We need to assign a unique identifier to each finger.
4707
- // We do this by mapping each Touch object to the identifier.
4708
4729
  const rememberTouch = (touch) => {
4709
4730
  const finger = this.fingers.pop();
4710
4731
  if (finger === undefined) {
@@ -4715,39 +4736,41 @@ class InputService extends SubService {
4715
4736
  };
4716
4737
  const forgetTouch = (touch) => {
4717
4738
  const finger = this.fingerIds.get(touch.identifier);
4718
- if (finger) {
4739
+ if (finger !== undefined) {
4719
4740
  this.fingers.push(finger);
4741
+ // Re-sort descending so the lowest available id is popped next.
4742
+ this.fingers.sort((a, b) => b - a);
4720
4743
  }
4721
- // Sort array back into descending order. This means if finger '1' were to lift after finger '0', we would ensure that 0 will be the first index to pop
4722
- this.fingers.sort((a, b) => b - a);
4723
4744
  this.fingerIds.delete(touch.identifier);
4724
4745
  };
4725
4746
  const emitTouchData = (type, touches) => {
4726
4747
  // eslint-disable-next-line @typescript-eslint/prefer-for-of
4727
4748
  for (let t = 0; t < touches.length; t++) {
4728
4749
  let byte = 2;
4729
- const numTouches = 1; // the number of touches to be sent this message
4750
+ const numTouches = 1;
4730
4751
  const touch = touches[t];
4752
+ const finger = this.fingerIds.get(touch.identifier);
4753
+ // Skip unknown identifiers (e.g., stale touch on a fast retap) — sending
4754
+ // 0 here would alias finger '0' and corrupt UE's touch state.
4755
+ if (finger === undefined) {
4756
+ continue;
4757
+ }
4731
4758
  const { x, y } = this.getTouchPositionWithOffset(touch);
4732
- Logger.info(`F${this.fingerIds.get(touch.identifier)}=(${x}, ${y})`);
4733
- const normalizeCoordinates = this.normalizeAndQuantizeUnsigned(x, y);
4734
- const data = new DataView(new ArrayBuffer(9));
4759
+ Logger.info(`F${finger}=(${x}, ${y})`);
4760
+ const coord = this.normalizeAndQuantizeUnsigned(x, y);
4761
+ const data = this.touchView;
4735
4762
  data.setUint8(0, type);
4736
4763
  data.setUint8(1, numTouches);
4737
- data.setUint16(byte, normalizeCoordinates.x, true);
4764
+ data.setUint16(byte, coord.x, true);
4738
4765
  byte += 2;
4739
- data.setUint16(byte, normalizeCoordinates.y, true);
4766
+ data.setUint16(byte, coord.y, true);
4740
4767
  byte += 2;
4741
- const finger = this.fingerIds.get(touch.identifier);
4742
- if (finger) {
4743
- data.setUint8(byte, finger);
4744
- }
4768
+ data.setUint8(byte, finger);
4745
4769
  byte += 1;
4746
- // force is between 0.0 and 1.0, so quantize into byte.
4747
- // iOs bug: Forced to be always 0.5 on all devices as we do not use forced touch any way;/
4770
+ // iOS bug: force is locked to 0.5 since forced touch is unused.
4748
4771
  data.setUint8(byte, this.maxByteValue * 0.5);
4749
4772
  byte += 1;
4750
- data.setUint8(byte, normalizeCoordinates.inRange ? 1 : 0);
4773
+ data.setUint8(byte, coord.inRange ? 1 : 0);
4751
4774
  byte += 1;
4752
4775
  this.sendInputData(data.buffer);
4753
4776
  }
@@ -4757,7 +4780,6 @@ class InputService extends SubService {
4757
4780
  .subscribe((e) => {
4758
4781
  this.focusWindow();
4759
4782
  const touches = e.changedTouches;
4760
- // Assign a unique identifier to each touch.
4761
4783
  // eslint-disable-next-line @typescript-eslint/prefer-for-of
4762
4784
  for (let t = 0; t < touches.length; t++) {
4763
4785
  rememberTouch(touches[t]);
@@ -4772,7 +4794,6 @@ class InputService extends SubService {
4772
4794
  const touches = e.changedTouches;
4773
4795
  Logger.info('touch end');
4774
4796
  emitTouchData(MessageType.TouchEnd, touches);
4775
- // Re-cycle unique identifiers previously assigned to each touch.
4776
4797
  // eslint-disable-next-line @typescript-eslint/prefer-for-of
4777
4798
  for (let t = 0; t < touches.length; t++) {
4778
4799
  forgetTouch(touches[t]);
@@ -4790,45 +4811,41 @@ class InputService extends SubService {
4790
4811
  window.focus();
4791
4812
  }
4792
4813
  emitMouseMove(x, y, deltaX, deltaY) {
4793
- const normalizeCoordinates = this.normalizeAndQuantizeUnsigned(x, y);
4794
- this.lastNormalizedCoordinates = normalizeCoordinates;
4814
+ const coord = this.normalizeAndQuantizeUnsigned(x, y);
4795
4815
  const delta = this.normalizeAndQuantizeSigned(deltaX, deltaY);
4796
- const data = new DataView(new ArrayBuffer(9));
4816
+ const data = this.mouseMoveView;
4797
4817
  data.setUint8(0, MessageType.MouseMove);
4798
- data.setUint16(1, normalizeCoordinates.x, true);
4799
- data.setUint16(3, normalizeCoordinates.y, true);
4818
+ data.setUint16(1, coord.x, true);
4819
+ data.setUint16(3, coord.y, true);
4800
4820
  data.setInt16(5, delta.x, true);
4801
4821
  data.setInt16(7, delta.y, true);
4802
4822
  this.sendInputData(data.buffer);
4803
4823
  }
4804
4824
  emitMouseDown(button, x, y) {
4805
- const normalizeCoordinates = this.normalizeAndQuantizeUnsigned(x, y);
4806
- this.lastNormalizedCoordinates = normalizeCoordinates;
4807
- const data = new DataView(new ArrayBuffer(6));
4825
+ const coord = this.normalizeAndQuantizeUnsigned(x, y);
4826
+ const data = this.mouseButtonView;
4808
4827
  data.setUint8(0, MessageType.MouseDown);
4809
4828
  data.setUint8(1, button);
4810
- data.setUint16(2, normalizeCoordinates.x, true);
4811
- data.setUint16(4, normalizeCoordinates.y, true);
4829
+ data.setUint16(2, coord.x, true);
4830
+ data.setUint16(4, coord.y, true);
4812
4831
  this.sendInputData(data.buffer);
4813
4832
  }
4814
4833
  emitMouseUp(button, x, y) {
4815
- const normalizeCoordinates = this.normalizeAndQuantizeUnsigned(x, y);
4816
- this.lastNormalizedCoordinates = normalizeCoordinates;
4817
- const data = new DataView(new ArrayBuffer(6));
4834
+ const coord = this.normalizeAndQuantizeUnsigned(x, y);
4835
+ const data = this.mouseButtonView;
4818
4836
  data.setUint8(0, MessageType.MouseUp);
4819
4837
  data.setUint8(1, button);
4820
- data.setUint16(2, normalizeCoordinates.x, true);
4821
- data.setUint16(4, normalizeCoordinates.y, true);
4838
+ data.setUint16(2, coord.x, true);
4839
+ data.setUint16(4, coord.y, true);
4822
4840
  this.sendInputData(data.buffer);
4823
4841
  }
4824
4842
  emitMouseWheel(delta, x, y) {
4825
- const normalizeCoordinates = this.normalizeAndQuantizeUnsigned(x, y);
4826
- this.lastNormalizedCoordinates = normalizeCoordinates;
4827
- const data = new DataView(new ArrayBuffer(7));
4843
+ const coord = this.normalizeAndQuantizeUnsigned(x, y);
4844
+ const data = this.mouseWheelView;
4828
4845
  data.setUint8(0, MessageType.MouseWheel);
4829
4846
  data.setInt16(1, delta, true);
4830
- data.setUint16(3, normalizeCoordinates.x, true);
4831
- data.setUint16(5, normalizeCoordinates.y, true);
4847
+ data.setUint16(3, coord.x, true);
4848
+ data.setUint16(5, coord.y, true);
4832
4849
  this.sendInputData(data.buffer);
4833
4850
  }
4834
4851
  releaseMouseButtons(buttons, x, y) {
@@ -4848,7 +4865,6 @@ class InputService extends SubService {
4848
4865
  this.emitMouseUp(MouseButton.FifthButton, x, y);
4849
4866
  }
4850
4867
  }
4851
- // If the user has any mouse buttons pressed, then press them again.
4852
4868
  pressMouseButtons(buttons, x, y) {
4853
4869
  if (buttons & MouseButtonsMask.PrimaryButton) {
4854
4870
  this.emitMouseDown(MouseButton.MainButton, x, y);
@@ -4892,7 +4908,7 @@ class InputService extends SubService {
4892
4908
  fromEvent(htmlVideoElement, 'wheel')
4893
4909
  .pipe(takeUntil$1(this.deactivatedVideoTrigger$))
4894
4910
  .subscribe((e) => {
4895
- this.emitMouseWheel(e.deltaY * -120, e.offsetX, e.offsetY);
4911
+ this.emitMouseWheel(e.deltaY * WHEEL_DELTA_SCALE, e.offsetX, e.offsetY);
4896
4912
  e.preventDefault();
4897
4913
  });
4898
4914
  }
@@ -4901,7 +4917,7 @@ class InputService extends SubService {
4901
4917
  .pipe(takeUntil$1(this.deactivatedVideoTrigger$))
4902
4918
  .subscribe((e) => {
4903
4919
  this.focusWindow();
4904
- const data = new DataView(new ArrayBuffer(1));
4920
+ const data = this.singleByteView;
4905
4921
  data.setUint8(0, MessageType.MouseEnter);
4906
4922
  this.sendInputData(data.buffer);
4907
4923
  this.pressMouseButtons(e.buttons, e.offsetX, e.offsetY);
@@ -4909,8 +4925,7 @@ class InputService extends SubService {
4909
4925
  fromEvent(htmlVideoElement, 'mouseleave')
4910
4926
  .pipe(takeUntil$1(this.deactivatedVideoTrigger$))
4911
4927
  .subscribe((e) => {
4912
- this.lastNormalizedCoordinates = null;
4913
- const data = new DataView(new ArrayBuffer(1));
4928
+ const data = this.singleByteView;
4914
4929
  data.setUint8(0, MessageType.MouseLeave);
4915
4930
  this.sendInputData(data.buffer);
4916
4931
  this.releaseMouseButtons(e.buttons, e.offsetX, e.offsetY);
@@ -4918,8 +4933,8 @@ class InputService extends SubService {
4918
4933
  }
4919
4934
  // Browser keys do not have a charCode so we only need to test keyCode.
4920
4935
  isKeyCodeBrowserKey(keyCode) {
4921
- // Function keys or tab key.
4922
- return (keyCode >= 112 && keyCode <= 123) || keyCode === 9;
4936
+ return ((keyCode >= FUNCTION_KEY_MIN && keyCode <= FUNCTION_KEY_MAX) ||
4937
+ keyCode === TAB_KEY_CODE);
4923
4938
  }
4924
4939
  getKeyCode(e) {
4925
4940
  if (e.keyCode === SpecialKeyCodes.Shift && e.code === 'ShiftRight')
@@ -4932,9 +4947,7 @@ class InputService extends SubService {
4932
4947
  return e.keyCode;
4933
4948
  }
4934
4949
  registerKeyboardEvents(availableKeys) {
4935
- const availableKeyNames = Object.entries(availableKeys).map(([, value]) => {
4936
- return InvertedKeyMap[value];
4937
- });
4950
+ const availableKeyNames = availableKeys.map((value) => InvertedKeyMap[value]);
4938
4951
  const keyBlock = (event) => {
4939
4952
  const code = event.code;
4940
4953
  const isInputFieldFocused = event.target?.localName === 'input';
@@ -4949,7 +4962,6 @@ class InputService extends SubService {
4949
4962
  }
4950
4963
  startListenKeys(_filter) {
4951
4964
  const filter = _filter ?? (() => false);
4952
- // Use RxJS fromEvent with takeUntil to properly clean up subscriptions
4953
4965
  fromEvent(document, 'keydown')
4954
4966
  .pipe(takeUntil$1(this.deactivatedVideoTrigger$))
4955
4967
  .subscribe((e) => {
@@ -4962,15 +4974,15 @@ class InputService extends SubService {
4962
4974
  if (isCtrlOrCommand && isPlusOrMinus) {
4963
4975
  return;
4964
4976
  }
4965
- this.sendInputData(new Uint8Array([
4966
- MessageType.KeyDown,
4967
- this.getKeyCode(e),
4968
- Number(e.repeat),
4969
- ]).buffer);
4970
- // Backspace is not considered a keypress in JavaScript, but we need it
4971
- // to be so characters may be deleted in a UE4 text entry field.
4977
+ const keyDown = this.keyDownBytes;
4978
+ keyDown[0] = MessageType.KeyDown;
4979
+ keyDown[1] = this.getKeyCode(e);
4980
+ keyDown[2] = Number(e.repeat);
4981
+ this.sendInputData(keyDown.buffer);
4982
+ // Backspace isn't a keypress in JS, but UE4 text-entry fields need it
4983
+ // to delete characters.
4972
4984
  if (e.keyCode === SpecialKeyCodes.BackSpace) {
4973
- const data = new DataView(new ArrayBuffer(3));
4985
+ const data = this.keyPressView;
4974
4986
  data.setUint8(0, MessageType.KeyPress);
4975
4987
  data.setUint16(1, SpecialKeyCodes.BackSpace, true);
4976
4988
  this.sendInputData(data.buffer);
@@ -4986,7 +4998,10 @@ class InputService extends SubService {
4986
4998
  if (filter(e)) {
4987
4999
  return;
4988
5000
  }
4989
- this.sendInputData(new Uint8Array([MessageType.KeyUp, this.getKeyCode(e)]).buffer);
5001
+ const keyUp = this.keyUpBytes;
5002
+ keyUp[0] = MessageType.KeyUp;
5003
+ keyUp[1] = this.getKeyCode(e);
5004
+ this.sendInputData(keyUp.buffer);
4990
5005
  if (this.options.suppressBrowserKeys &&
4991
5006
  this.isKeyCodeBrowserKey(e.keyCode)) {
4992
5007
  e.preventDefault();
@@ -4998,7 +5013,7 @@ class InputService extends SubService {
4998
5013
  if (filter(e)) {
4999
5014
  return;
5000
5015
  }
5001
- const data = new DataView(new ArrayBuffer(3));
5016
+ const data = this.keyPressView;
5002
5017
  data.setUint8(0, MessageType.KeyPress);
5003
5018
  data.setUint16(1, e.charCode, true);
5004
5019
  this.sendInputData(data.buffer);
@@ -7236,5 +7251,5 @@ function provideUnrealModule() {
7236
7251
  * Generated bundle index. Do not edit.
7237
7252
  */
7238
7253
 
7239
- export { AFKService, AUTO_HIGH_RESOLUTION_IDLE_MS, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CONSOLE_COMMAND_DISABLE_MESSAGES, CONSOLE_COMMAND_ENABLE_MESSAGES, CONSOLE_COMMAND_PIXEL_QUALITY, CommandTelemetryService, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DEFAULT_RECONNECT_DELAY_MS, DEFAULT_RECONNECT_ENABLED, DEFAULT_RECONNECT_MAX_ATTEMPTS, DEFAULT_RECONNECT_ON_DATACHANNEL_CLOSE, DEFAULT_RECONNECT_ON_ICE_FAILURE, DESTROY_DISCONNECT_REASONS, DataFlowMonitor, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, FileHandlerService, FileReceiverService, FilterSettingsComponent, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, INNER_RETRY_LIMIT, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, KalmanFilter1D, LatencyTimings, LoadingProgressService, LowBandwidthDetectorComponent, LowBandwidthModalComponent, MINIMAL_FPS, MIN_CONTAINER_SIZE, MessageType, MouseButton, MouseButtonsMask, OUTER_RETRY_DELAY, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PingHandler, PlayerCountHandler, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_AUDIO_ID, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, StreamStatusTelemetryService, StreamStatusTelemetryService2, SubService, ToClientMessage, UNREAL_CONFIG, UnrealCallbackService, UnrealCommunicatorService, UnrealEffects, UnrealSceneComponent, UnrealStatusMessage, VideoService, VideoStatsComponent, WSCloseCode_CIRRUS_ABNORMAL_CLOSURE, WSCloseCode_CIRRUS_MAX_PLAYERS_ERROR, WSCloseCode_CIRRUS_PLAYER_DISCONNECTED, WSCloseCode_CIRRUS_STREAMER_KIKED_PLAYER, WSCloseCode_FORCE_CIRRUS_CLOSE, WSCloseCode_NORMAL_AFK_TIMEOUT, WSCloseCode_NORMAL_CLOSURE, WSCloseCode_NORMAL_MANUAL_DISCONNECT, WSCloseCode_UNKNOWN, WSCloseCodes, WS_OPEN_STATE, WS_RETRY_PER_INSTANCE, WS_TIMEOUT, WebRtcPlayerService, WebrtcErrorModalComponent, abortEstablishingConnection, answerSent, changeStatusMainVideoOnScene, changeStreamResolutionAction, commandCompleted, commandStarted, dataChannelConnected, dataChannelReady, decodeData, destroyRemoteConnections, destroyUnrealScene, disconnectStream, dispatchResize, dropConnection, dropDC, dropPC, floatToPercents, floatToSmoothPercents, forceResizeViewport, fromResizeObserver, getActiveUrl, getImageFromVideoStream, getResizeStudioValues, getResizeValuesUniversal, getRtcErrorMessage, iceConnectionFailed, initSignalling, instanceReady, keepMaxUntilReset, makeEven, pollingEnded, pollingStarted, provideUnrealModule, qpToQuality, reconnectPeer, reconnectPeerFailed, reconnectPeerSuccess, regionPingFetchEnd, regionPingFetchStart, regionResolved, resetAfk, resetConfig, resetDataChannelForReconnect, resetIntroSrc, resetUserInactivityTimeout, resetWarnTimeout, saveAnalyticsEvent, selectCirrusConnectionId, selectClientAndViewIds, selectCommandProgress, selectFreezeFrameCombinedDataUrl, selectFreezeFrameDataUrl, selectFreezeFrameDataUrlFromVideo, selectFreezeFrameProgressMessageFromVideo, selectIsAutostart, selectIsExistMatchUrls, selectIsFreezeFrameLoading, selectIsFullyConnected, selectIsLoaderScreenVisible, selectIsVideoPlayingAndDataChannelConnected, selectShowLoader, selectShowReconnectPopup, selectSignalingParameters, selectStreamConfig, selectTotalProgress, selectUserInactivityDetected, selectWarnTimeout, setAfkTimerCountdown, setAfkTimerHide, setAfkTimerVisible, setAwsInstance, setCirrusConnected, setCirrusDisconnected, setConfig, setEstablishingConnection, setFreezeFrame, setFreezeFrameFromVideo, setIntroImageSrc, setIntroVideoSrc, setLoadingImageSrc, setLoopBackCommandIsCompleted, setMaxFps, setOrchestrationContext, setOrchestrationMessage, setOrchestrationParameters, setOrchestrationProgress, setSignalingName, setStatusMessage, setStatusPercentSignallingServer, setStreamClientCompanyId, setStreamViewId, setUnrealPlaywrightConfig, setUserInactivityTimeout, setViewportNotReady, setViewportReady, showLoadingProgress, showResumeStreamPopup, showUnrealErrorMessage, showUserInactivityTimeoutPopup, signalingRetryAttempted, smoothTransition, startStream, trackMixpanelEvent, trackTelemetryData, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
7254
+ export { AFKService, AUTO_HIGH_RESOLUTION_IDLE_MS, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CONSOLE_COMMAND_DISABLE_MESSAGES, CONSOLE_COMMAND_ENABLE_MESSAGES, CONSOLE_COMMAND_PIXEL_QUALITY, CommandTelemetryService, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DEFAULT_RECONNECT_DELAY_MS, DEFAULT_RECONNECT_ENABLED, DEFAULT_RECONNECT_MAX_ATTEMPTS, DEFAULT_RECONNECT_ON_DATACHANNEL_CLOSE, DEFAULT_RECONNECT_ON_ICE_FAILURE, DESTROY_DISCONNECT_REASONS, DataFlowMonitor, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, FileHandlerService, FileReceiverService, FilterSettingsComponent, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, INNER_RETRY_LIMIT, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, KalmanFilter1D, LatencyTimings, LoadingProgressService, LowBandwidthDetectorComponent, LowBandwidthModalComponent, MINIMAL_FPS, MIN_CONTAINER_SIZE, MessageType, MouseButton, MouseButtonsMask, OUTER_RETRY_DELAY, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PingHandler, PlayerCountHandler, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_AUDIO_ID, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, StreamStatusTelemetryService, StreamStatusTelemetryService2, SubService, ToClientMessage, UNREAL_CONFIG, UnrealCallbackService, UnrealCommunicatorService, UnrealEffects, UnrealSceneComponent, UnrealStatusMessage, VideoService, VideoStatsComponent, WSCloseCode_CIRRUS_ABNORMAL_CLOSURE, WSCloseCode_CIRRUS_MAX_PLAYERS_ERROR, WSCloseCode_CIRRUS_PLAYER_DISCONNECTED, WSCloseCode_CIRRUS_STREAMER_KIKED_PLAYER, WSCloseCode_FORCE_CIRRUS_CLOSE, WSCloseCode_NORMAL_AFK_TIMEOUT, WSCloseCode_NORMAL_CLOSURE, WSCloseCode_NORMAL_MANUAL_DISCONNECT, WSCloseCode_UNKNOWN, WSCloseCodes, WS_OPEN_STATE, WS_RETRY_PER_INSTANCE, WS_TIMEOUT, WebRtcPlayerService, WebrtcErrorModalComponent, abortEstablishingConnection, answerSent, changeStatusMainVideoOnScene, changeStreamResolutionAction, commandCompleted, commandStarted, dataChannelConnected, dataChannelReady, decodeData, destroyRemoteConnections, destroyUnrealScene, disconnectStream, dispatchResize, dropConnection, dropDC, dropPC, floatToPercents, floatToSmoothPercents, forceResizeViewport, fromResizeObserver, getActiveUrl, getImageFromVideoStream, getResizeStudioValues, getResizeValuesUniversal, getRtcErrorMessage, iceConnectionFailed, initSignalling, instanceReady, keepMaxUntilReset, makeEven, pollingEnded, pollingStarted, provideUnrealModule, qpToQuality, reconnectPeer, reconnectPeerFailed, reconnectPeerSuccess, regionPingFetchEnd, regionPingFetchStart, regionResolved, resetAfk, resetConfig, resetDataChannelForReconnect, resetIntroSrc, resetUserInactivityTimeout, resetWarnTimeout, saveAnalyticsEvent, selectCirrusConnectionId, selectClientAndViewIds, selectCommandProgress, selectFreezeFrameCombinedDataUrl, selectFreezeFrameDataUrl, selectFreezeFrameDataUrlFromVideo, selectFreezeFrameProgressMessageFromVideo, selectIsAutostart, selectIsExistMatchUrls, selectIsFreezeFrameLoading, selectIsFullyConnected, selectIsLoaderScreenVisible, selectIsVideoPlayingAndDataChannelConnected, selectShowLoader, selectShowReconnectPopup, selectSignalingParameters, selectStreamConfig, selectTotalProgress, selectUserInactivityDetected, selectWarnTimeout, setAfkTimerCountdown, setAfkTimerHide, setAfkTimerVisible, setAwsInstance, setCirrusConnected, setCirrusDisconnected, setConfig, setEstablishingConnection, setFreezeFrame, setFreezeFrameFromVideo, setIntroImageSrc, setIntroVideoSrc, setLoadingImageSrc, setLoopBackCommandIsCompleted, setMaxFps, setOrchestrationContext, setOrchestrationMessage, setOrchestrationParameters, setOrchestrationProgress, setSignalingName, setStatusMessage, setStatusPercentSignallingServer, setStreamClientCompanyId, setStreamViewId, setUnrealPlaywrightConfig, setUserInactivityTimeout, setViewportNotReady, setViewportReady, showLoadingProgress, showResumeStreamPopup, showUnrealErrorMessage, showUserInactivityTimeoutPopup, signalingRetryAttempted, smoothTransition, startStream, trackMixpanelEvent, trackTelemetryData, uint8ToBase64, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
7240
7255
  //# sourceMappingURL=3dsource-angular-unreal-module.mjs.map