# @hiyve/angular

Angular SDK for Hiyve -- provider services with RxJS Observables, video tile and grid components, and a media control bar.

## Installation

```bash
npx @hiyve/cli login
npm install @hiyve/angular
```

### Peer Dependencies

```bash
npm install @hiyve/core @hiyve/rtc-client @hiyve/utilities @angular/material @angular/cdk rxjs
```

## Quick Start

Register Hiyve services in your application config:

```typescript
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHiyve } from '@hiyve/angular';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHiyve({
      generateRoomToken: async () => {
        const res = await fetch('/api/generate-room-token', { method: 'POST' });
        return (await res.json()).roomToken;
      },
    }),
  ],
};
```

Use components and services in your room component:

```typescript
import { Component, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import {
  ConnectionService,
  RoomService,
  LocalMediaService,
  VideoGridComponent,
  ControlBarComponent,
} from '@hiyve/angular';

@Component({
  selector: 'app-video-room',
  standalone: true,
  imports: [AsyncPipe, VideoGridComponent, ControlBarComponent],
  template: `
    @if ((room.isInRoom$ | async)) {
      <hiyve-video-grid
        [localUserName]="'Me'"
        [layout]="'grid'"
        [showNames]="true"
      />
      <hiyve-control-bar
        [showAudioToggle]="true"
        [showVideoToggle]="true"
        [showScreenShare]="true"
        [showSettings]="true"
        [showLeaveButton]="true"
        (leave)="onLeave()"
      />
    } @else {
      <button (click)="joinRoom()">Join Room</button>
    }
  `,
})
export class VideoRoomComponent {
  private connection = inject(ConnectionService);
  room = inject(RoomService);
  private localMedia = inject(LocalMediaService);

  async joinRoom() {
    await this.connection.joinRoom('my-room', 'user-123');
  }

  onLeave() {
    this.connection.leaveRoom();
  }
}
```

## Provider

### provideHiyve(config)

Registers all Hiyve services for dependency injection. Call in your application config or root module. Makes the `HIYVE_CONFIG` injection token available for direct injection in your components:

```typescript
import { inject } from '@angular/core';
import { HIYVE_CONFIG } from '@hiyve/angular';

const config = inject(HIYVE_CONFIG);
```

| Config Property | Type | Description |
|----------------|------|-------------|
| `generateRoomToken` | `() => Promise<string>` | Async function that fetches a room token from your backend. Defaults to POSTing to `/api/generate-room-token`. |
| `localVideoElementId` | `string` | ID of the HTML video element for local video preview (default: `'local-video'`) |
| `connectOptions` | `{ videoDeviceId?, audioDeviceId? }` | Initial device IDs to use when connecting |
| `persistDeviceChanges` | `boolean` | Persist device changes to localStorage (default: `false`) |
| `onError` | `(error: Error) => void` | Callback invoked when a client error occurs |

## Services

### HiyveService

Core service providing all state Observables and action methods directly.

| Observable | Type | Description |
|-----------|------|-------------|
| `connection$` | `Observable<ConnectionState>` | Connection status, errors |
| `room$` | `Observable<RoomState>` | Room info, owner status |
| `client$` | `Observable<{ client: Client \| null }>` | Raw RTC client instance |
| `participants$` | `Observable<ParticipantsState>` | Participant map and local user ID |
| `localMedia$` | `Observable<LocalMediaState>` | Mute states and screen sharing |
| `recording$` | `Observable<RecordingState>` | Recording status and timing |
| `streaming$` | `Observable<StreamingState>` | Streaming status and URL |
| `transcription$` | `Observable<TranscriptionState>` | Transcription entries |
| `chat$` | `Observable<ChatState>` | Messages and unread count |
| `waitingRoom$` | `Observable<WaitingRoomState>` | Waiting users and admission state |
| `waitForHost$` | `Observable<WaitForHostState>` | Wait-for-host status |
| `audioProcessing$` | `Observable<AudioProcessingState>` | Feedback detection and gain |
| `layout$` | `Observable<LayoutState>` | Dominant speaker |
| `handRaise$` | `Observable<HandRaiseState>` | Raised hands map |
| `aiChat$` | `Observable<{ messages: AiChatMessage[] }>` | AI chat messages |
| `intelligence$` | `Observable<IntelligenceState>` | AI coaching, talk ratio, topics |
| `storedRooms$` | `Observable<StoredRoomsState>` | Stored rooms list and loading state |
| `userFiles$` | `Observable<UserFilesState>` | User files list and loading state |
| `activeRooms$` | `Observable<ActiveRoomsState>` | Active rooms and connection state |

### Domain Services

Focused services for specific feature areas. Inject only what you need.

| Service | Observables | Actions |
|---------|------------|---------|
| `ConnectionService` | `isConnected$`, `isConnecting$`, `error$` | `createRoom()`, `joinRoom()`, `leaveRoom()` |
| `RoomService` | `roomInfo$`, `isOwner$`, `isInRoom$` | -- |
| `ParticipantsService` | `participantList$`, `remoteParticipants$`, `localUserId$`, `participantCount$` | `getParticipant(userId)` |
| `LocalMediaService` | `isAudioMuted$`, `isVideoMuted$`, `isScreenSharing$` | `toggleAudio()`, `toggleVideo()`, `startScreenShare()`, `stopScreenShare()` |
| `DevicesService` | -- | `setVideoDevice()`, `setAudioInputDevice()`, `setAudioOutputDevice()` |
| `RecordingService` | `isRecording$`, `isRecordingStarting$`, `recordingId$`, `recordingDuration$`, `error$`, `responseId$` | `startRecording()`, `stopRecording()`, `clearError()` |
| `StreamingService` | `isStreaming$`, `isStreamingStarting$`, `streamingDuration$`, `streamingUrl$`, `error$` | `startStreaming()`, `stopStreaming()`, `clearError()` |
| `TranscriptionService` | `isTranscribing$`, `transcriptions$` | `startTranscription()`, `stopTranscription()` |
| `ChatService` | `messages$`, `unreadCount$` | `sendMessage()`, `sendDataMessage()`, `clearUnread()`, `loadChatHistory()` |
| `WaitingRoomService` | `waitingUsers$`, `isWaitingForAdmission$`, `wasRejected$` | `admitUser()`, `rejectUser()` |
| `WaitForHostService` | `isWaiting$`, `roomName$`, `timeout$`, `elapsedTime$` | -- |
| `LayoutService` | `dominantSpeaker$` | `setDominant()` |
| `HandRaiseService` | `raisedHands$` | `isHandRaised$(userId)`, `toggleHandRaised()`, `lowerAllHands()` |
| `AudioProcessingService` | `feedbackDetected$`, `gainNode$` | `setGain()` |
| `AiChatService` | `messages$` | `setMessages()` |
| `JoinTokenService` | -- | `joinRoomWithToken()`, `getRoomInfoFromToken()` |

## Components

| Component | Selector | Description |
|-----------|----------|-------------|
| `VideoGridComponent` | `hiyve-video-grid` | Auto-layout container for video tiles with grid, speaker, and sidebar modes |
| `VideoTileComponent` | `hiyve-video-tile` | Displays a remote participant's video stream with status overlays |
| `LocalVideoTileComponent` | `hiyve-local-video-tile` | Displays the local user's video with flip and recording indicators |
| `ControlBarComponent` | `hiyve-control-bar` | Media controls, device selection, recording, layout, and leave button |

### Sub-components (Control Bar)

| Component | Selector | Description |
|-----------|----------|-------------|
| `MediaControlsComponent` | -- | Audio, video, and screen share toggle buttons |
| `DeviceMenuComponent` | -- | Camera, microphone, and speaker selection menus |
| `LayoutMenuComponent` | -- | Grid, speaker, and sidebar layout selector |
| `RecordingMenuComponent` | -- | Recording and streaming controls |
| `LeaveButtonComponent` | -- | Leave room with optional confirmation |
| `FeatureControlsComponent` | -- | Recording, streaming, and intelligence feature toggles |

## Directives and Pipes

| Export | Description |
|--------|-------------|
| `MediaStreamDirective` (`[hiyveMediaStream]`) | Attaches a `MediaStream` to a `<video>` or `<audio>` element |
| `HiyveDurationPipe` (`hiyveDuration`) | Formats seconds into `MM:SS` or `HH:MM:SS` |

### MediaStreamDirective

```html
<video [hiyveMediaStream]="participant.videoStream" autoplay></video>
<audio [hiyveMediaStream]="participant.audioStream" autoplay></audio>
```

### HiyveDurationPipe

```html
<span>{{ recordingDuration | hiyveDuration }}</span>
<!-- Output: "05:32" or "01:15:42" -->
```

## VideoGrid Inputs

| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `layout` | `'grid' \| 'speaker' \| 'sidebar' \| string` | `'grid'` | Layout mode |
| `customLayoutHandler` | `CustomLayoutHandler` | `null` | Custom layout calculation function |
| `gap` | `number` | -- | Gap between tiles (px) |
| `localVideoElementId` | `string` | `'local-video'` | HTML ID for local video element |
| `localUserName` | `string` | `''` | Display name for local video tile |
| `showLocalFlip` | `boolean` | `false` | Show mirror-flip on local tile |
| `showZoom` | `boolean` | `false` | Show zoom controls on tiles |
| `showNames` | `boolean` | `true` | Show participant names |
| `showMuteIndicators` | `boolean` | `true` | Show mute status badges |
| `showTimer` | `boolean` | `false` | Show recording timer |
| `showMood` | `boolean` | `false` | Show mood indicators |
| `showEngagement` | `boolean` | `false` | Show engagement indicators |
| `labelPosition` | `OverlayPosition` | `'bottom-left'` | Position of name labels |
| `statusPosition` | `OverlayPosition` | `'top-right'` | Position of mute status indicators |
| `controlPosition` | `OverlayPosition` | `'bottom-right'` | Position of tile controls |
| `moodPosition` | `OverlayPosition` | `'top-left'` | Position of mood indicators |
| `engagementPosition` | `OverlayPosition` | `'top-left'` | Position of engagement indicators |
| `tileLabels` | `VideoTileLabels` | `{}` | Label overrides for remote tiles |
| `tileColors` | `VideoTileColors` | `{}` | Color overrides for remote tiles |
| `tileStyles` | `VideoTileStyles` | `{}` | Style overrides for remote tiles |
| `localTileLabels` | `VideoTileLabels` | `{}` | Label overrides for local tile |
| `localTileColors` | `VideoTileColors` | `{}` | Color overrides for local tile |
| `localTileStyles` | `VideoTileStyles` | `{}` | Style overrides for local tile |
| `colors` | `VideoGridColors` | `{}` | Grid container color overrides |
| `styles` | `VideoGridStyles` | `{}` | Grid container style overrides |

| Output | Type | Description |
|--------|------|-------------|
| `participantClick` | `string` | Emits the userId when a remote tile is clicked |
| `localClick` | `void` | Emits when the local tile is clicked |
| `layoutChange` | `string` | Emits the new layout mode |

## ControlBar Inputs

| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `showAudioToggle` | `boolean` | `true` | Show mute/unmute button |
| `showVideoToggle` | `boolean` | `true` | Show camera toggle |
| `showScreenShare` | `boolean` | `true` | Show screen share button |
| `showSettings` | `boolean` | `true` | Show device settings menu |
| `showLeaveButton` | `boolean` | `true` | Show leave button |
| `showLeaveConfirmation` | `boolean` | `true` | Show confirmation dialog before leaving |
| `showLayoutSelector` | `boolean` | `false` | Show layout mode selector |
| `showHandRaise` | `boolean` | `false` | Show hand raise button |
| `showRecordingMenu` | `boolean` | `false` | Show recording/streaming menu |
| `showStreamingOption` | `boolean` | `false` | Show streaming option in menu |
| `showFeatureControls` | `boolean` | `false` | Show separate feature toggle buttons |
| `disabled` | `boolean` | `false` | Disable all controls |
| `layouts` | `LayoutDefinition[]` | Default grid/speaker/sidebar | Available layouts |
| `labels` | `ControlBarLabels` | `{}` | Label overrides (i18n) |
| `colors` | `ControlBarColors` | `{}` | Color overrides |
| `styles` | `ControlBarStyles` | `{}` | Style overrides |

| Output | Type | Description |
|--------|------|-------------|
| `leave` | `void` | Leave button clicked |
| `audioMuteChange` | `boolean` | Audio mute state changed |
| `videoMuteChange` | `boolean` | Video mute state changed |
| `screenShareChange` | `boolean` | Screen share toggled |
| `layoutChange` | `string` | Layout mode changed |
| `handRaiseChange` | `boolean` | Hand raise toggled |
| `recordingStarted` | `void` | Recording started |
| `recordingStopped` | `void` | Recording stopped |
| `streamingStarted` | `void` | Streaming started |
| `streamingStopped` | `void` | Streaming stopped |
| `videoDeviceChange` | `string` | Camera device changed (emits device ID) |
| `audioDeviceChange` | `string` | Microphone device changed (emits device ID) |
| `audioOutputDeviceChange` | `string` | Speaker device changed (emits device ID) |
| `controlBarError` | `Error` | Error occurred in control bar |

## Customization

All components accept partial label, color, and style objects. Unspecified values use built-in defaults.

### Labels (i18n)

```typescript
<hiyve-control-bar
  [labels]="{
    mute: 'Couper le micro',
    unmute: 'Activer le micro',
    leave: 'Quitter',
    shareScreen: 'Partager l\'ecran'
  }"
/>
```

### Colors

```typescript
<hiyve-video-grid
  [colors]="{ background: '#0a0a1a', dominantBorder: '#00ff88' }"
  [tileColors]="{ background: '#111133', dominantBorder: '#00ff88' }"
/>
```

### Styles

```typescript
<hiyve-control-bar
  [styles]="{
    gap: '12px',
    padding: '12px 24px',
    borderRadius: '16px',
    buttonSize: 'large'
  }"
/>
```

### Using Default Objects

Import defaults to see all available customization keys:

```typescript
import {
  DEFAULT_CONTROL_BAR_LABELS,
  DEFAULT_CONTROL_BAR_COLORS,
  DEFAULT_CONTROL_BAR_STYLES,
  DEFAULT_VIDEO_TILE_LABELS,
  DEFAULT_VIDEO_TILE_COLORS,
  DEFAULT_VIDEO_TILE_STYLES,
  DEFAULT_VIDEO_GRID_COLORS,
  DEFAULT_VIDEO_GRID_STYLES,
  DEFAULT_LAYOUTS,
} from '@hiyve/angular';
```

`DEFAULT_LAYOUTS` provides the built-in layout definitions used by the layout selector:

```typescript
// [{ id: 'grid', label: 'Grid', icon: 'grid_view' },
//  { id: 'speaker', label: 'Speaker', icon: 'person' },
//  { id: 'sidebar', label: 'Sidebar', icon: 'view_sidebar' }]
```

## Custom Layouts

Provide a custom layout handler to compute tile positions:

```typescript
import type { CustomLayoutHandler, CustomLayoutInfo, TilePosition } from '@hiyve/angular';

const myLayout: CustomLayoutHandler = (info: CustomLayoutInfo): Record<string, TilePosition> => {
  const positions: Record<string, TilePosition> = {};
  info.participants.forEach((p, i) => {
    positions[p.userId] = {
      x: (i % 3) * (info.availableWidth / 3),
      y: Math.floor(i / 3) * (info.availableHeight / 2),
      width: info.availableWidth / 3,
      height: info.availableHeight / 2,
    };
  });
  return positions;
};
```

```html
<hiyve-video-grid [layout]="'custom'" [customLayoutHandler]="myLayout" />
```

## Layout Utilities

Helper functions for computing grid and layout positioning.

| Function | Signature | Description |
|----------|-----------|-------------|
| `getGridColumns` | `(count: number) => number` | Returns the optimal column count for a given participant count |
| `getGridClass` | `(count: number) => string` | Returns a CSS class name (e.g. `'grid-4'`) for grid styling |
| `getPositionStyles` | `(position: OverlayPosition) => Record<string, string>` | Returns CSS positioning styles for an overlay at the given position |
| `calculateSpeakerLayout` | `(width, height, participantIds, dominantId?, gap?) => Record<string, TilePosition>` | Computes tile positions for speaker layout (one dominant, filmstrip below) |
| `calculateSidebarLayout` | `(width, height, participantIds, dominantId?, gap?) => Record<string, TilePosition>` | Computes tile positions for sidebar layout (one dominant, sidebar right) |

## Types

| Type | Description |
|------|-------------|
| `HiyveProviderConfig` | Configuration object for `provideHiyve()` |
| `Participant` | Participant data including userId, display name, and media streams |
| `AiChatMessage` | AI chat message with role, content, and metadata |
| `MuteStatus` | Mute state for audio and video |
| `MoodData` | Mood analysis data for a participant |
| `MoodType` | Mood classification type (e.g. happy, neutral, sad) |
| `LayoutMode` | Layout mode identifier (`'grid'`, `'speaker'`, `'sidebar'`, or custom) |
| `LayoutDefinition` | Layout option with `id`, `label`, and `icon` fields |
| `CustomLayoutHandler` | Function type for custom layout calculation |
| `OverlayPosition` | Tile overlay position (`'top-left'`, `'top-right'`, `'bottom-left'`, `'bottom-right'`) |
| `IntelligenceConfig` | Configuration for AI features: `enableMoodAnalysis`, `enableTranscription`, `enableAiChat`, `responseId` |
| `CoachingVariant` | Variant type for AI coaching state |
| `StoredRoomsState` | Stored rooms list and loading state |
| `UserFilesState` | User files list and loading state |
| `ActiveRoomsState` | Active rooms and connection state |

## Features

- RxJS Observables for all state (connection, room, participants, media, recording, chat, etc.)
- 16 injectable domain services for focused feature access
- Standalone components compatible with Angular 17--20
- Grid, speaker, and sidebar video layouts with custom layout support
- Participant name labels, mute indicators, dominant speaker highlighting, hand raise badges
- Recording timer, mood and engagement overlays
- Device selection menus for camera, microphone, and speaker
- Full i18n support through label overrides
- Color and style theming for all visual elements
- OnPush change detection for optimal performance

## Requirements

- Angular 17, 18, 19, or 20
- `@angular/material` and `@angular/cdk`
- `@hiyve/core` and `@hiyve/rtc-client` as peer dependencies
- `@hiyve/utilities` as a peer dependency
- RxJS 7+

## License

Proprietary - Hiyve SDK
