@epicgames-ps/lib-pixelstreamingfrontend-ue5.4
Version:
Frontend library for Unreal Engine 5.4 Pixel Streaming
209 lines (194 loc) • 7.89 kB
text/typescript
// Copyright Epic Games, Inc. All Rights Reserved.
import { Logger } from '../Logger/Logger';
import { CoordinateConverter } from '../Util/CoordinateConverter';
import { StreamMessageController } from '../UeInstanceMessage/StreamMessageController';
import { VideoPlayer } from '../VideoPlayer/VideoPlayer';
import { ITouchController } from './ITouchController';
import { EventListenerTracker } from '../Util/EventListenerTracker';
/**
* Handles the Touch input Events
*/
export class TouchController implements ITouchController {
toStreamerMessagesProvider: StreamMessageController;
videoElementProvider: VideoPlayer;
coordinateConverter: CoordinateConverter;
videoElementParent: HTMLVideoElement;
fingers = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
fingerIds = new Map();
maxByteValue = 255;
// Utility for keeping track of event handlers and unregistering them
private touchEventListenerTracker = new EventListenerTracker();
/**
* @param toStreamerMessagesProvider - Stream message instance
* @param videoElementProvider - Video Player instance
* @param coordinateConverter - A coordinate converter instance
*/
constructor(
toStreamerMessagesProvider: StreamMessageController,
videoElementProvider: VideoPlayer,
coordinateConverter: CoordinateConverter
) {
this.toStreamerMessagesProvider = toStreamerMessagesProvider;
this.videoElementProvider = videoElementProvider;
this.coordinateConverter = coordinateConverter;
this.videoElementParent = videoElementProvider.getVideoElement();
const ontouchstart = (ev: TouchEvent) =>
this.onTouchStart(ev);
const ontouchend = (ev: TouchEvent) =>
this.onTouchEnd(ev);
const ontouchmove = (ev: TouchEvent) =>
this.onTouchMove(ev);
this.videoElementParent.addEventListener('touchstart', ontouchstart);
this.videoElementParent.addEventListener('touchend', ontouchend);
this.videoElementParent.addEventListener('touchmove', ontouchmove);
this.touchEventListenerTracker.addUnregisterCallback(
() => this.videoElementParent.removeEventListener('touchstart', ontouchstart)
);
this.touchEventListenerTracker.addUnregisterCallback(
() => this.videoElementParent.removeEventListener('touchend', ontouchend)
);
this.touchEventListenerTracker.addUnregisterCallback(
() => this.videoElementParent.removeEventListener('touchmove', ontouchmove)
);
Logger.Log(Logger.GetStackTrace(), 'Touch Events Registered', 6);
// is this strictly necessary?
const preventOnTouchMove = (event: TouchEvent) => {
event.preventDefault();
};
document.addEventListener('touchmove', preventOnTouchMove);
this.touchEventListenerTracker.addUnregisterCallback(
() => document.removeEventListener('touchmove', preventOnTouchMove)
);
}
/**
* Unregister all touch events
*/
unregisterTouchEvents() {
this.touchEventListenerTracker.unregisterAll();
}
/**
* Remember a touch command
* @param touch - the touch command
*/
rememberTouch(touch: Touch) {
const finger = this.fingers.pop();
if (finger === undefined) {
Logger.Log(
Logger.GetStackTrace(),
'exhausted touch identifiers',
6
);
}
this.fingerIds.set(touch.identifier, finger);
}
/**
* Forgets a touch command
* @param touch - the touch command
*/
forgetTouch(touch: Touch) {
this.fingers.push(this.fingerIds.get(touch.identifier));
// 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
this.fingers.sort(function (a, b) {
return b - a;
});
this.fingerIds.delete(touch.identifier);
}
/**
* When a touch event starts
* @param touchEvent - the touch event being intercepted
*/
onTouchStart(touchEvent: TouchEvent) {
if (!this.videoElementProvider.isVideoReady()) {
return;
}
for (let t = 0; t < touchEvent.changedTouches.length; t++) {
this.rememberTouch(touchEvent.changedTouches[t]);
}
Logger.Log(Logger.GetStackTrace(), 'touch start', 6);
this.emitTouchData('TouchStart', touchEvent.changedTouches);
touchEvent.preventDefault();
}
/**
* When a touch event ends
* @param touchEvent - the touch event being intercepted
*/
onTouchEnd(touchEvent: TouchEvent) {
if (!this.videoElementProvider.isVideoReady()) {
return;
}
Logger.Log(Logger.GetStackTrace(), 'touch end', 6);
this.emitTouchData('TouchEnd', touchEvent.changedTouches);
// Re-cycle unique identifiers previously assigned to each touch.
for (let t = 0; t < touchEvent.changedTouches.length; t++) {
this.forgetTouch(touchEvent.changedTouches[t]);
}
touchEvent.preventDefault();
}
/**
* when a moving touch event occurs
* @param touchEvent - the touch event being intercepted
*/
onTouchMove(touchEvent: TouchEvent) {
if (!this.videoElementProvider.isVideoReady()) {
return;
}
Logger.Log(Logger.GetStackTrace(), 'touch move', 6);
this.emitTouchData('TouchMove', touchEvent.touches);
touchEvent.preventDefault();
}
emitTouchData(type: string, touches: TouchList) {
if (!this.videoElementProvider.isVideoReady()) {
return;
}
const offset = this.videoElementProvider.getVideoParentElement().getBoundingClientRect();
const toStreamerHandlers =
this.toStreamerMessagesProvider.toStreamerHandlers;
for (let t = 0; t < touches.length; t++) {
const numTouches = 1; // the number of touches to be sent this message
const touch = touches[t];
const x = touch.clientX - offset.left;
const y = touch.clientY - offset.top;
Logger.Log(
Logger.GetStackTrace(),
`F${this.fingerIds.get(touch.identifier)}=(${x}, ${y})`,
6
);
const coord = this.coordinateConverter.normalizeAndQuantizeUnsigned(
x,
y
);
switch (type) {
case 'TouchStart':
toStreamerHandlers.get('TouchStart')([
numTouches,
coord.x,
coord.y,
this.fingerIds.get(touch.identifier),
this.maxByteValue * (touch.force > 0 ? touch.force : 1),
coord.inRange ? 1 : 0
]);
break;
case 'TouchEnd':
toStreamerHandlers.get('TouchEnd')([
numTouches,
coord.x,
coord.y,
this.fingerIds.get(touch.identifier),
this.maxByteValue * touch.force,
coord.inRange ? 1 : 0
]);
break;
case 'TouchMove':
toStreamerHandlers.get('TouchMove')([
numTouches,
coord.x,
coord.y,
this.fingerIds.get(touch.identifier),
this.maxByteValue * (touch.force > 0 ? touch.force : 1),
coord.inRange ? 1 : 0
]);
break;
}
}
}
}