# @3dsource/angular-unreal-module

A set of standalone Angular components, services, and providers for integrating Unreal Engine (WebRTC) scenes into Angular applications. It facilitates communication between Angular and Unreal Engine and enables interactive 3D experiences.

## Overview

This package provides:

- Standalone Unreal scene component to embed UE stream
- Communication bridge (commands, UI interactions, input data)
- Callback listener for Unreal events and command responses
- NgRx state and effects for 3D stream lifecycle
- Config and utilities for telemetry, errors, and regions ping
- Auto-reconnection support for WebRTC/DataChannel failures
- File receiving from Unreal Engine
- Analytics, FPS monitoring, and stream status telemetry
- Playwright testing mode with mock services

## Installation

### Prerequisites

- Angular 18+
- NgRx store and effects (v18+)
- Angular CDK (v18+) — used for dialog overlays
- `provideHttpClient()` — required by internal services (telemetry, signalling, regions ping, error reporting)

### Peer Dependencies

This library requires the following peer dependencies (match or exceed versions):

```json
{
  "@3dsource/source-ui-native": ">=1.0.9",
  "@3dsource/types-unreal": ">=0.0.7",
  "@3dsource/utils": ">=1.0.21",
  "@angular/cdk": ">=18.0.0",
  "@angular/common": ">=18.0.0",
  "@angular/core": ">=18.0.0",
  "@angular/forms": ">=18.0.0",
  "@ngrx/effects": ">=18.0.0",
  "@ngrx/store": ">=18.0.0"
}
```

### Library Installation

```shell
npm i @3dsource/angular-unreal-module
```

## Usage

The API is fully standalone (no NgModule). Use providers and components as shown below.

### 1) Provide the module services and store slice

Add providers in your application bootstrap (e.g., `app.config.ts`):

> **⚠️ Important:** `UNREAL_CONFIG` is **required** — multiple internal services inject it without `{ optional: true }`. Omitting it will cause a `NullInjectorError` at runtime. You can provide it with an empty object `{}` as a minimum.

```ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { provideStore } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
import { provideAngularUnrealModule, UNREAL_CONFIG } from '@3dsource/angular-unreal-module';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter([]),
    provideHttpClient(),

    // Root NgRx (if not already added in your app)
    provideStore(),
    provideEffects(),

    // Required: Unreal initial configuration
    {
      provide: UNREAL_CONFIG,
      useValue: {
        customErrorsEndpoint: '', // Endpoint for custom error reporting
        commandTelemetryReceiver: '', // Endpoint for command telemetry
        regionsPingUrl: '', // URL prefix for regions latency ping
        screenLockerContainerId: '', // DOM container id for screen locker overlay
        dataChannelConnectionTimeout: 8000, // Timeout in ms for data channel connection (default: 8000)
        playwright: false, // Mirrors the provider flag for services/effects
        reconnect: {
          // Auto-reconnection configuration
          enabled: true, // Enable auto-reconnection (default: true)
          maxAttempts: 3, // Max reconnection attempts (default: 3)
          delayMs: 1000, // Delay between attempts in ms (default: 1000)
          onIceFailure: true, // Reconnect on ICE connection failure (default: true)
          onDataChannelClose: true, // Reconnect on DataChannel close (default: true)
        },
      },
    },

    // Unreal providers (adds feature state and effects internally)
    // Tip: pass { playwright: true } to switch to testing/dummy services
    provideAngularUnrealModule({ playwright: false }),
  ],
};
```

#### Minimal configuration

If you don't need custom endpoints, provide `UNREAL_CONFIG` with an empty object:

```ts
{ provide: UNREAL_CONFIG, useValue: {} },
provideAngularUnrealModule(),
```

### 2) Use the Unreal scene component

Import the component into a standalone component and use it in the template.

```ts
import { Component } from '@angular/core';
import { UnrealSceneComponent } from '@3dsource/angular-unreal-module';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [UnrealSceneComponent],
  template: ` <app-unreal-scene [isStudio]="false" [useContainerAsSizeProvider]="true" [studioResolutionSize]="{ width: 1920, height: 1080 }" (changeMouseOverScene)="onHover($event)"> </app-unreal-scene> `,
})
export class AppComponent {
  onHover(isOver: boolean) {
    // handle mouse over scene
  }
}
```

Component selector: `<app-unreal-scene>`

Inputs:

| Input                        | Type                                | Default                         |
| ---------------------------- | ----------------------------------- | ------------------------------- |
| `isStudio`                   | `boolean`                           | `false`                         |
| `useContainerAsSizeProvider` | `boolean`                           | `true`                          |
| `studioResolutionSize`       | `{ width: number; height: number }` | `{ width: 1920, height: 1080 }` |

Outputs:

| Output                 | Type                        |
| ---------------------- | --------------------------- |
| `changeMouseOverScene` | `OutputEmitterRef<boolean>` |

### 3) Send commands / interactions to Unreal

Inject `UnrealCommunicatorService` to send commands or UI interactions. Types for command packets are provided by `@3dsource/types-unreal`.

There are three sending methods — choose based on your needs:

| Method                | Description                                                                                                                                |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `sendCommandToUnreal` | Full pipeline: adds `correlationId`, records telemetry, dispatches NgRx `commandStarted` action. **Recommended for application commands.** |
| `emitUIInteraction`   | Sends the packet as a raw `UIInteraction` message. No telemetry or store dispatch.                                                         |
| `emitCommand`         | Sends the packet as a raw `Command` message (for console commands, resolution changes, etc.).                                              |

```ts
import { Component, inject } from '@angular/core';
import { UnrealCommunicatorService } from '@3dsource/angular-unreal-module';
import { MetaBoxCommand } from '@3dsource/types-unreal';
import type { MetaBoxCommandPacket } from '@3dsource/types-unreal';

@Component({ standalone: true, template: '' })
export class MyComponent {
  private unreal = inject(UnrealCommunicatorService);

  sendSomeCommand() {
    // Recommended: use sendCommandToUnreal for full telemetry + store tracking
    this.unreal.sendCommandToUnreal({
      command: MetaBoxCommand.FChangeResolutionCommand,
      payload: { resolution: { x: 1920, y: 1080 } },
    });
  }

  sendRawUIInteraction() {
    // Low-level: sends UIInteraction message without telemetry tracking
    const packet = {
      command: 'CustomCommand',
      payload: { key: 'value' },
    } as MetaBoxCommandPacket;
    this.unreal.emitUIInteraction(packet);
  }
}
```

### 4) Store integration (actions & selectors)

The module registers an NgRx feature state `commandsFeature`. You can dispatch actions and select state in your components:

```ts
import { inject } from '@angular/core';
import { Store } from '@ngrx/store';
import { startStream, setConfig, setOrchestrationContext, disconnectStream, selectTotalProgress, selectShowLoader, commandsFeature } from '@3dsource/angular-unreal-module';

// Dispatch actions
const store = inject(Store);
store.dispatch(startStream({ config: { autoStart: true, warnTimeout: 120 } }));

// Select state
const progress = store.selectSignal(selectTotalProgress);
const isVideoPlaying = store.selectSignal(commandsFeature.selectIsVideoPlaying);
const dataChannelConnected = store.selectSignal(commandsFeature.selectDataChannelConnected);
```

**Key selectors:**

- `selectTotalProgress` — Scene load progress (0–1 float)
- `selectShowLoader` — Whether loader screen should be visible
- `selectShowReconnectPopup` — Whether reconnect popup should be shown
- `selectIsVideoPlayingAndDataChannelConnected` — Combined readiness check
- `selectStreamConfig` — Current stream configuration
- `commandsFeature.selectCirrusConnected` — Signalling server connection status
- `commandsFeature.selectDataChannelConnected` — Data channel status
- `commandsFeature.selectViewportReady` — Viewport readiness

**Key actions:**

- `startStream` — Start streaming with config
- `setConfig` — Update stream configuration
- `setOrchestrationContext` — Set orchestration URLs and environment
- `disconnectStream` — Disconnect with reason
- `destroyUnrealScene` — Full teardown
- `reconnectPeer` — Trigger peer reconnection

### 5) Listen for Unreal callbacks

Inject `UnrealCallbackService` to listen for callback events from Unreal Engine and to observe command responses.

There are two methods:

| Method                   | Description                                                                                                                                                            |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fromUnrealCallback`     | Listens for callbacks matching a command or event key. Supports MetaBox commands and custom Unreal callback events. Returns `Observable<UnrealCallbackDescriptor[K]>`. |
| `observeCommandResponse` | Sends a command and waits for its matching response by `correlationId`. Returns `Observable<MetaBoxCommandList[K]>`. Includes timeout and error handling.              |

**Custom Unreal callback events** (defined in `UnrealCallbackEventMap`):

| Event                      | Payload Type                                        |
| -------------------------- | --------------------------------------------------- |
| `onSceneState`             | `FSceneState`                                       |
| `onFocusObject`            | `FProductPayload`                                   |
| `cameraChanged`            | `FCameraChangedPayload`                             |
| `onObjectTransformChanged` | `{ objectName: string; transform: FTransformJson }` |
| `onChangeSequence`         | `unknown`                                           |
| `onFinishedSequence`       | `unknown`                                           |

```ts
import { Component, inject } from '@angular/core';
import { UnrealCallbackService } from '@3dsource/angular-unreal-module';
import { UnrealCommunicatorService } from '@3dsource/angular-unreal-module';
import { MetaBoxCommand } from '@3dsource/types-unreal';

@Component({ standalone: true, template: '' })
export class MyComponent {
  private callbackService = inject(UnrealCallbackService);
  private communicator = inject(UnrealCommunicatorService);

  listenForCallbacks() {
    // Listen for a MetaBox command callback
    this.callbackService.fromUnrealCallback(MetaBoxCommand.FLoadProductCommand).subscribe((data) => console.log('Product loaded:', data));

    // Listen for a custom Unreal event
    this.callbackService.fromUnrealCallback('cameraChanged').subscribe((data) => console.log('Camera changed:', data));
  }

  sendAndObserve() {
    // Send a command and observe its response (with correlationId matching)
    this.callbackService
      .observeCommandResponse(
        { command: MetaBoxCommand.FLoopBackCommand },
        (data) => this.communicator.sendCommandToUnreal(data),
        60000, // timeout in ms (default: 60000)
        true, // emit on timeout (default: true)
      )
      .subscribe((response) => console.log('Response:', response));
  }
}
```

## Exported API

### Components

| Component                        | Selector           | Description                            |
| -------------------------------- | ------------------ | -------------------------------------- |
| `UnrealSceneComponent`           | `app-unreal-scene` | Main scene container with video stream |
| `AfkTimeoutModalComponent`       | —                  | AFK timeout warning modal              |
| `FreezeFrameComponent`           | —                  | Freeze frame overlay                   |
| `LowBandwidthModalComponent`     | —                  | Low bandwidth warning modal            |
| `LowBandwidthIndicatorComponent` | —                  | Low bandwidth indicator                |
| `ImageLoadingSrcComponent`       | —                  | Loading image overlay                  |
| `IntroSrcComponent`              | —                  | Intro image/video overlay              |
| `VideoStatsComponent`            | —                  | Video statistics display               |
| `StatGraphComponent`             | —                  | Statistics graph                       |
| `WebrtcErrorModalComponent`      | —                  | WebRTC error modal                     |

### Services

| Service                        | Description                                                     |
| ------------------------------ | --------------------------------------------------------------- |
| `UnrealCommunicatorService`    | Send commands and UI interactions to Unreal                     |
| `UnrealCallbackService`        | Listen for Unreal callback events and observe command responses |
| `AggregatorService`            | Aggregates data channel messages from Unreal                    |
| `SignallingService`            | Manages WebSocket signalling connection                         |
| `VideoService`                 | Manages video element and stats                                 |
| `WebRtcPlayerService`          | Manages WebRTC peer connection                                  |
| `FreezeFrameService`           | Handles freeze frame images                                     |
| `AFKService`                   | AFK (away from keyboard) detection and timeout                  |
| `DevModeService`               | Toggle dev mode for debugging                                   |
| `FileReceiverService`          | Receives files from Unreal via data channel                     |
| `FileHandlerService`           | Processes received files                                        |
| `RegionsPingService`           | Pings regions to determine latency                              |
| `CommandTelemetryService`      | Records command telemetry                                       |
| `StreamStatusTelemetryService` | Reports stream status telemetry                                 |
| `AnalyticsService`             | Analytics event tracking                                        |
| `FpsMonitorService`            | FPS monitoring                                                  |

### Pipes

| Pipe           | Description                     |
| -------------- | ------------------------------- |
| `SafeHtmlPipe` | Bypasses Angular HTML sanitizer |

### Interfaces

| Interface                  | Description                                                                  |
| -------------------------- | ---------------------------------------------------------------------------- |
| `UnrealInitialConfig`      | Shape for `UNREAL_CONFIG` injection token                                    |
| `ReconnectConfig`          | Auto-reconnection behavior configuration                                     |
| `StreamConfig`             | Stream configuration (autoStart, warnTimeout)                                |
| `StreamResolutionProps`    | Stream resolution width/height                                               |
| `UnrealCallbackEventMap`   | Custom Unreal callback events pushed from Unreal via data channel            |
| `UnrealCallbackDescriptor` | Combined map of MetaBoxCommandList and UnrealCallbackEventMap callback types |

## Features

- Standalone Unreal Scene Component
- Command and UI Interaction API via `UnrealCommunicatorService`
- Callback listener and command response observer via `UnrealCallbackService`
- Event-driven status UI (freeze frame, video stats, play overlay, AFK, low bandwidth)
- NgRx-powered state management and effects
- **Required** initial configuration via `UNREAL_CONFIG` injection token
- Auto-reconnection on WebRTC/DataChannel failures (configurable)
- File receiving from Unreal Engine via data channel
- Analytics and FPS monitoring
- Playwright testing mode with mock service substitution

## Examples

Check the demo application for complete usage examples:

```shell
npm run demo:start
```

See also: `projects/demo/src/app/demo-layout/info-pages/unreal-scene-demo/constants/unreal.routes.ts` for a real-world provider configuration example.

## Engine requirements

- Node.js: >=20
- npm: >9
