English | [简体中文](./README-zh_CN.md)

# @ray-js/ipc-player-integration

[![latest](https://img.shields.io/npm/v/@ray-js/ipc-player-integration/latest.svg)](https://www.npmjs.com/package/@ray-js/ipc-player-integration) [![download](https://img.shields.io/npm/dt/@ray-js/ipc-player-integration.svg)](https://www.npmjs.com/package/@ray-js/ipc-player-integration)

> IPC integrated player — an all-in-one IPC (camera) player feature integration component for the Tuya Ray framework.

Built on top of [`@ray-js/ray-ipc-player`](https://www.npmjs.com/package/@ray-js/ray-ipc-player), it wraps a complete set of upper-layer player capabilities: resolution switching, screenshot, recording, mute, two-way talk, PTZ control, zoom, battery / temperature & humidity / signal status display, portrait–landscape switching, multi-camera layouts and more — ready to use out of the box, while remaining highly customizable for both widgets and styles.

## Table of Contents

- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [API](#api)
  - [`<IPCPlayerIntegration />`](#ipcplayerintegration-)
  - [`useCtx(options)`](#usectxoptions)
  - [`useStore(atoms)`](#usestoreatoms)
  - [`Features`](#features-1)
  - [`Widgets`](#widgets)
  - [`Hooks`](#hooks)
  - [`Utils` & device helpers](#utils--device-helpers)
- [Custom Styling](#custom-styling)
- [Events](#events)
- [Multi-Camera](#multi-camera)
- [Develop](#develop)

## Features

- 🎥 **Integrated player**: encapsulates preview, connection and play-state management on top of `@ray-js/ray-ipc-player`.
- 🧩 **Declarative widget orchestration**: initialize default widgets with one call to `Features.initPlayerWidgets`, or add/remove widgets in the four corners and the absolute-positioned area on demand.
- 🛠 **Rich built-in widgets**: resolution, screenshot, recording, mute, intercom, PTZ, zoom, battery, temperature & humidity, 4G signal, floodlight, siren, trial, and more.
- 🎨 **Customizable**: tailor brand color, icons and fonts via `brandColor` and `CSSVariable`.
- 📱 **Portrait / landscape / multi-camera**: supports portrait, fullscreen, landscape, and PiP / tile / grid multi-camera layouts.
- 🪝 **Hook toolkit**: `useBattery`, `usePtz`, `useTemperature` and others for convenient device-state access.

## Installation

```sh
$ npm install @ray-js/ipc-player-integration
# or
$ yarn add @ray-js/ipc-player-integration
```

> This component requires `ahooks` (a peerDependency); make sure it is installed in the host project.

## Quick Start

```tsx
import React, { useEffect, useRef } from 'react';
import { View } from '@ray-js/components';
import {
  IPCPlayerIntegration,
  useCtx,
  useStore,
  Features,
  EventInstance,
} from '@ray-js/ipc-player-integration';

export default function Home(props) {
  const devId = props.location.query?.deviceId;
  // 1. Create the player context instance
  const instance = useCtx({ devId });
  // 2. Subscribe to the state you want to render reactively
  const { screenType } = useStore({ screenType: instance.screenType });
  const eventRef = useRef<EventInstance>();

  // 3. Initialize default widgets (resolution / screenshot / record / fullscreen ...)
  useEffect(() => {
    Features.initPlayerWidgets(instance, {
      hideRecordVideoMenu: false,
      hideScreenShotMenu: false,
      hideResolutionMenu: false,
      showToggleVerticalFull: true,
    });
  }, []);

  return (
    <View>
      <IPCPlayerIntegration
        instance={instance}
        devId={instance.devId}
        eventRef={eventRef}
        deviceOnline
        brandColor="#FF592A"
        landscapeMode="fill"
        playerFit="contain"
        style={{ height: screenType === 'vertical' ? 'calc(100vw * 0.56)' : '100vh' }}
      />
    </View>
  );
}
```

## Core Concepts

| Concept           | Description                                                                                                                                                                                          |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **instance**      | Created by `useCtx({ devId })`, it spans the whole player and manages state atoms, the player instance, widget content and the event bus. Pass it to `<IPCPlayerIntegration instance={instance} />`. |
| **atom**          | Based on [jotai](https://jotai.org/). State on the `instance` (e.g. `screenType`, `mute`, `resolution`) are atoms; subscribe via `useStore` to render reactively.                                    |
| **content areas** | Widgets on the player are distributed across 5 areas: `topLeft`, `topRight`, `bottomLeft`, `bottomRight`, `absolute`. Managed via `addContent` / `deleteContent` / `updateContent`, etc.             |
| **event**         | `instance.event` provides `on/off/emit`. Expose it to the page through `eventRef` to listen for clicks, resolution switches and other events.                                                        |

## API

### `<IPCPlayerIntegration />`

The main player component.

| Prop                     | Type                             | Default              | Description                                                       |
| ------------------------ | -------------------------------- | -------------------- | ----------------------------------------------------------------- |
| `devId`                  | `string`                         | —                    | Device ID (required)                                              |
| `instance`               | `ReturnType<useCtx>`             | —                    | The context instance from `useCtx`; created internally if omitted |
| `eventRef`               | `React.RefObject<EventInstance>` | —                    | Event-bus ref for listening on the page side                      |
| `playerFit`              | `'contain' \| 'cover'`           | `'contain'`          | Video fit mode                                                    |
| `landscapeMode`          | `'fill' \| 'standard'`           | `'standard'`         | Landscape display mode                                            |
| `brandColor`             | `string`                         | `'#FF592A'`          | Brand color                                                       |
| `CSSVariable`            | `Partial<CSSVariable>`           | —                    | Custom CSS variables, see [Custom Styling](#custom-styling)       |
| `deviceOnline`           | `boolean`                        | `true`               | Whether the device is online                                      |
| `privateState`           | `boolean`                        | `false`              | Whether privacy mode (mask) is on                                 |
| `verticalMic`            | `boolean`                        | `true`               | Whether to show the mic intercom in portrait                      |
| `isShare`                | `boolean`                        | `false`              | Whether it is a shared device                                     |
| `ignoreHideStopPreview`  | `boolean`                        | `false`              | Ignore stop-preview when widgets hide                             |
| `awakeStatus`            | `boolean`                        | `undefined`          | Wake-up status for low-power devices                              |
| `autoWakeUp`             | `boolean`                        | `false`              | Auto wake up the device                                           |
| `limitFlow`              | `boolean`                        | `false`              | Whether flow is limited                                           |
| `showFlowLowTip`         | `boolean`                        | `false`              | Show the low-flow tip                                             |
| `previewEnabled`         | `boolean`                        | `undefined`          | Controlled preview switch                                         |
| `refreshElement`         | `boolean`                        | `false`              | Trigger a CoverView height refresh                                |
| `playerRoute`            | `string`                         | `'pages/home/index'` | Route the player lives on                                         |
| `style` / `className`    | `CSSProperties` / `string`       | —                    | Container style / className                                       |
| `onPlayStatus`           | `(data: PlayStatusData) => void` | —                    | Play-state change callback                                        |
| `onPlayerTap`            | `(data) => void`                 | —                    | Tap-on-video callback                                             |
| `onPreviewEnabledChange` | `(enabled: boolean) => void`     | —                    | Preview-switch change callback                                    |

### `useCtx(options)`

Creates the player context instance.

```ts
const instance = useCtx({
  devId: string,                       // Device ID (required)
  saveToAlbum?: 0 | 1,                 // 0: system album; 1: IPC album
  showPtzControlTip?: boolean,         // Show the PTZ control guide
  videoSplitProtocol?: VideoSplitProtocol, // Multi-camera split protocol
  initTopLeftContent?: ComponentConfig[],     // Initial widgets per area
  initTopRightContent?: ComponentConfig[],
  initBottomLeftContent?: ComponentConfig[],
  initBottomRightContent?: ComponentConfig[],
  initAbsoluteContent?: ComponentConfig[],
});
```

The returned `instance` provides:

- **State atoms**: `screenType`, `mute`, `intercom`, `recording`, `resolution`, `resolutionList`, `playState`, `brandColor`, `verticalMic`, `isVerticalFullLayout`, etc. (subscribe via `useStore`).
- **State setters**: `setMute`, `setIntercom`, `setRecording`, `setResolution`, `setScreenType`, `setBrandColor`, `setPlayState`, `changeStreamStatus`, `getStreamStatus`, etc.
- **Content management**: `addContent(type, config, position?)`, `deleteContent(type, id)`, `updateContent(type, data)`, `getContent()`, `hasContent`, `hideContent`, `showContent`.
- **Misc**: `IPCPlayerInstance` (the underlying player instance), `event` (event bus), `toast`, `multiCameraCtx` (multi-camera context).

### `useStore(atoms)`

Subscribes to state atoms on the `instance` and returns a reactive value object.

```tsx
const { screenType, mute, resolution } = useStore({
  screenType: instance.screenType,
  mute: instance.mute,
  resolution: instance.resolution,
});
```

### `Features`

Widget-orchestration capabilities, imported via `import { Features } from '@ray-js/ipc-player-integration'`.

#### `Features.initPlayerWidgets(instance, options)`

Initializes the default widgets based on the device's DP info. Common `options`:

| Option                                                                                                | Type                | Description                                      |
| ----------------------------------------------------------------------------------------------------- | ------------------- | ------------------------------------------------ |
| `hideScreenShotMenu`                                                                                  | `boolean`           | Hide the screenshot button                       |
| `hideRecordVideoMenu`                                                                                 | `boolean`           | Hide the record button                           |
| `hideResolutionMenu`                                                                                  | `boolean`           | Hide the resolution button                       |
| `hideKbsMenu`                                                                                         | `boolean`           | Hide the bitrate display                         |
| `hideSignalMenu`                                                                                      | `boolean`           | Hide the signal-strength button                  |
| `hideHorizontalMenu`                                                                                  | `boolean`           | Hide the enter-landscape button                  |
| `verticalResolutionCustomClick`                                                                       | `boolean`           | Custom click for portrait resolution             |
| `showToggleVerticalFull`                                                                              | `boolean`           | Show the single-cam portrait-fullscreen toggle   |
| `showRealTimeMagnification`                                                                           | `boolean`           | Show the real-time magnification button          |
| `hideSmartImageQualityState`                                                                          | `boolean`           | Hide the smart image-quality state               |
| `directionControlProps`                                                                               | `object`            | Pass-through props for the PTZ direction control |
| `topLeftContent` / `topRightContent` / `bottomLeftContent` / `bottomRightContent` / `absoluteContent` | `ComponentConfig[]` | Override default widget config                   |

#### `Features.updatePlayerWidgetProps(ctx, area, widgetId, newProps)`

Dynamically updates props of a widget in a given area.

```ts
Features.updatePlayerWidgetProps(instance, 'topRight', 'VideoBitKBP', {
  hideKbsMenu: true,
});
```

> `area`: `'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' | 'absolute'`; for `widgetId` see the widget list below.

### `Widgets`

Imported via `import { Widgets } from '@ray-js/ipc-player-integration'`; each widget can be used standalone. Common widgets:

| Widget                          | WidgetId                                      | Description                |
| ------------------------------- | --------------------------------------------- | -------------------------- |
| `Widgets.VoiceIntercom`         | `FullSmallIntercom` / `VerticalSmallIntercom` | Two-way talk button        |
| `Widgets.Screenshot`            | `Screenshot`                                  | Screenshot                 |
| `Widgets.RecordVideo`           | `RecordVideo`                                 | Recording                  |
| `Widgets.Muted`                 | `Muted`                                       | Mute                       |
| `Widgets.Resolution`            | `Resolution`                                  | Resolution switch          |
| `Widgets.FullScreen`            | `FullScreen`                                  | Fullscreen / landscape     |
| `Widgets.Ptz`                   | `Ptz`                                         | PTZ control                |
| `Widgets.VideoBitKBP`           | `VideoBitKBP`                                 | Bitrate / signal           |
| `Widgets.BatteryFull`           | `BatteryFull`                                 | Battery                    |
| `Widgets.TempHumidity`          | `TempHumidity`                                | Temperature & humidity     |
| `Widgets.Floodlight`            | `Floodlight`                                  | Floodlight                 |
| `Widgets.Siren`                 | `Siren`                                       | Siren                      |
| `Widgets.TryExperience`         | `TryExperience`                               | Trial                      |
| `Widgets.PtzControlTip`         | —                                             | PTZ control guide dialog   |
| `Widgets.RealTimeMagnification` | —                                             | Real-time magnification    |
| `Widgets.ToggleVerticalFull`    | —                                             | Portrait-fullscreen toggle |
| `Widgets.FlowLowTip`            | —                                             | Low-flow tip               |

Standalone `VoiceIntercom` example:

```tsx
<Widgets.VoiceIntercom
  ref={intercomRef}
  IPCPlayerContext={instance?.IPCPlayerInstance}
  {...instance}
  mode="circle" // 'verticalSmall' | 'fullSmall' | 'circle'
  disabled={false}
  icon={intercomLightSvg}
  talkingColor="#440F7C"
/>
```

Insert the PTZ guide dialog into the `absolute` area:

```tsx
instance.addContent('absolute', {
  id: 'ptzControlTipId',
  content: props => <Widgets.PtzControlTip {...props} />,
  absoluteContentClassName: 'ipc-player-plugin-ptz-control-tip-wrap',
  initProps: { ...props },
});
```

### `Hooks`

Convenient device-state access (all exported from the package root):

| Hook             | Signature                                      | Description                    |
| ---------------- | ---------------------------------------------- | ------------------------------ |
| `useBattery`     | `(devId) => { batteryValue, batteryCharging }` | Battery level / charging state |
| `usePtz`         | `(devId) => boolean`                           | Whether PTZ is supported       |
| `useTemperature` | `(devId) => number`                            | Temperature                    |
| `useHumidity`    | `() => number`                                 | Humidity                       |
| `useSignal4G`    | `(devId) => { dpValue, signalKey }`            | 4G signal strength             |
| `useDpState`     | —                                              | Read/write DP state            |
| `useDpSupport`   | —                                              | DP support check               |
| `useSchemaInfo`  | —                                              | Device schema info             |
| `useMemoizedFn`  | —                                              | Stable function reference      |

### `Utils` & device helpers

```ts
import { Utils } from '@ray-js/ipc-player-integration';
// Device helpers are imported from a sub-path
import {
  getDpValue,
  getDeviceInfo,
  getDpSchemaByCodes,
  getDpSchemaByCodesSync,
  hasDpCode,
} from '@ray-js/ipc-player-integration/utils/device';
```

- `getDpSchemaByCodes(devId, codes)`: batch-fetch schema by DP code.
- `hasDpCode(devId, code)`: check whether the device has a given DP.
- `Utils.radialGradient(color)` / `Utils.adjustBrightness(hex, factor)`: color helpers.

## Custom Styling

### Brand color

Set via the `brandColor` prop or `instance.setBrandColor(color)`.

### CSS variables

Override via the `CSSVariable` prop:

```tsx
<IPCPlayerIntegration
  CSSVariable={{
    '--iconColor': '#fff',
    '--iconActiveColor': '#ec653c',
    '--iconFontSize': '20px',
    '--bg-color': '#000',
    '--fontColor': '#fff',
    '--fontSize': '12px',
  }}
/>
```

Available variables: `--iconColor`, `--iconActiveColor`, `--iconFontSize`, `--iconPlayerSize`, `--iconBoxSize`, `--bg-color`, `--shot-card-bg-color`, `--fontColor`, `--fontSize`.

## Events

Once you have the event bus through `eventRef`, you can listen to internal player events:

```tsx
const eventRef = useRef<EventInstance>();

useEffect(() => {
  const onResolutionClick = () => console.log('resolution button clicked');
  const onWidgetClick = data => console.log('widget clicked', data);
  eventRef.current.on('resolutionBtnControlClick', onResolutionClick);
  eventRef.current.on('widgetClick', onWidgetClick);
  return () => {
    eventRef.current.off('resolutionBtnControlClick', onResolutionClick);
    eventRef.current.off('widgetClick', onWidgetClick);
  };
}, []);
```

## Multi-Camera

Supports PiP, tile, grid and thumbnail multi-camera layouts. Related types are exported from the package root:

- `MultiCameraScreenMode`: screen mode (`short` / `full` / `landscape`).
- `MultiCameraLayoutStyle`: layout style (`pip` / `tile` / `grid` / `thumbnail`).
- `VideoSplitProtocol`: the multi-camera split protocol, passed via `useCtx({ videoSplitProtocol })`.
- `MultiCameraLenInfo`: lens info (index, name, whether PTZ / Zoom / Localizer is supported).

The multi-camera context is accessible via `instance.multiCameraCtx`.

## Develop

```sh
# install deps
yarn

# watch & preview the demo (Tuya target)
yarn start:tuya

# build the component
yarn build
```

Other available scripts: `start:wechat`, `start:web`, `start:native`, `build:tuya`, `build:wechat`, `build:web`, `build:native`.