> ## Documentation Index
> Fetch the complete documentation index at: https://docs.videowise.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Live Shopping Channel

> Embed the live shopping channel in React Native, including modular layout and Picture-in-Picture.

Install the SDK first. See [React Native SDK](/sdk/react-native).

A full-screen live shopping channel component that embeds the Videowise live streaming experience. It renders a WebView with the live shopping channel and dispatches typed live-shopping events for user interactions like adding products to cart.

> **Note:** The `LiveShoppingChannel` component does **not** need to be wrapped inside `<VideowiseSDK.Provider>`. It operates independently.

### Props

| Prop                 | Type                                            | Required | Default        | Description                                                                   |
| -------------------- | ----------------------------------------------- | -------- | -------------- | ----------------------------------------------------------------------------- |
| `videowiseInfo`      | `object`                                        | Yes      | —              | Store configuration (see below)                                               |
| `lsId`               | `string`                                        | No       | —              | Live shopping ID (for autostart)                                              |
| `offsetY`            | `number`                                        | No       | `0`            | Reduces the height of the component wrapper by `screenHeight - offsetY`       |
| `playerTopOffset`    | `number`                                        | No       | —              | Top offset (px) for the Live Player component elements                        |
| `playerBottomOffset` | `number`                                        | No       | —              | Bottom offset (px) for the Live Player component elements                     |
| `vwStyle`            | `string`                                        | No       | —              | Custom CSS injected into the live shopping embed                              |
| `channelBodyHtml`    | `string`                                        | No       | monolith mount | Optional HTML for the WebView `<body>` (overrides default monolith)           |
| `enabledPipButton`   | `boolean`                                       | No       | `true`         | Show the PiP button in the live shopping player (`window.vwEnabledPipButton`) |
| `style`              | `StyleProp<ViewStyle>`                          | No       | —              | Custom style overrides for the component wrapper                              |
| `loggedInUserData`   | `{ email?: string; fullName?: string }`         | No       | —              | Logged-in user info passed to the embed                                       |
| `onEvent`            | `(event: OnEventType<'live-shopping'>) => void` | No       | —              | Callback for live shopping events                                             |
| `onAddToCart`        | `(event: OnEventType<'add-to-cart'>) => void`   | No       | —              | Callback when a product is added to cart                                      |
| `onCheckout`         | `(event: OnEventType<'live-shopping'>) => void` | No       | —              | Callback for checkout events                                                  |
| `environment`        | `'local' \| 'staging' \| 'production'`          | No       | `'production'` | Environment target                                                            |

#### `videowiseInfo` object

| Property | Type     | Required | Default | Description                                        |
| -------- | -------- | -------- | ------- | -------------------------------------------------- |
| `shop`   | `string` | Yes      | —       | Your store domain (e.g. `storename.myshopify.com`) |

#### `onEvent` callback

Triggered for all live shopping interaction events. The event object has the following structure:

| Property | Type              | Description                      |
| -------- | ----------------- | -------------------------------- |
| `type`   | `'live-shopping'` | Event type identifier            |
| `detail` | `object`          | Event detail payload (see below) |

**`detail` object (`LSTrackingEventData`)**

| Property              | Type                    | Required | Description                                                                            |
| --------------------- | ----------------------- | -------- | -------------------------------------------------------------------------------------- |
| `lsId`                | `string`                | Yes      | Live shopping session ID                                                               |
| `siteId`              | `string`                | Yes      | Site ID                                                                                |
| `organisationId`      | `string`                | Yes      | Organisation ID                                                                        |
| `clientTS`            | `Date`                  | Yes      | Client timestamp                                                                       |
| `videoElapsedMinutes` | `number`                | Yes      | Minutes elapsed in the video                                                           |
| `videoElapsedSeconds` | `number`                | Yes      | Seconds elapsed in the video                                                           |
| `currentTime`         | `number`                | No       | Current playback time                                                                  |
| `uid`                 | `string`                | Yes      | Unique user ID                                                                         |
| `device`              | `'mobile' \| 'desktop'` | Yes      | Device type                                                                            |
| `isLive`              | `boolean`               | Yes      | Whether the stream is currently live                                                   |
| `url`                 | `string`                | Yes      | Page URL                                                                               |
| `interactionType`     | `string`                | Yes      | Interaction type (see list below)                                                      |
| `eventType`           | `string`                | Yes      | Host event type: `'play'`, `'pause'`, `'stop'`, `'load'`, `'pending'`, `'interaction'` |
| `item`                | `object`                | No       | Product data (present for `add_to_cart` and `product_click` interactions)              |

**`item` object (when present)**

| Property              | Type               | Description                        |
| --------------------- | ------------------ | ---------------------------------- |
| `name`                | `string`           | Product name                       |
| `sku`                 | `string`           | Product SKU                        |
| `id`                  | `string`           | Product ID                         |
| `highlighted`         | `boolean`          | Whether the product is highlighted |
| `meta.customId`       | `string`           | Custom variant ID                  |
| `meta.vendor`         | `string`           | Product vendor                     |
| `meta.price`          | `number \| string` | Product price                      |
| `meta.compareAtPrice` | `unknown`          | Compare at price                   |
| `meta.quantity`       | `number`           | Quantity                           |
| `meta.currency`       | `string`           | Currency code                      |

**Possible interaction types (`interactionType`)**

* `mute_player` — Player muted
* `unmute_player` — Player unmuted
* `pause_player` — Player paused
* `resume_player` — Player resumed
* `like_reaction` — Like reaction
* `share` — Share clicked
* `add_to_calendar` — Added to calendar
* `send_chat` — Chat message sent
* `close_player` — Player closed
* `minimize_player` — Player minimized
* `maximize_player` — Player maximized
* `show_product_list` — Product list shown
* `hide_product_list` — Product list hidden
* `product_click` — Product clicked
* `add_to_cart` — Product added to cart
* `checkout` — Checkout clicked
* `show_cart` — Cart shown
* `hide_cart` — Cart hidden
* `toggle_cc_on` — Closed captions enabled
* `toggle_cc_off` — Closed captions disabled
* `replay` — Replay clicked
* `progress` — Playback progress

#### `onAddToCart` callback

Triggered when a user adds a product to the cart (`add_to_cart` interaction). The product data is unified into a standard format. The event object has the following structure:

| Property | Type            | Description             |
| -------- | --------------- | ----------------------- |
| `type`   | `'add-to-cart'` | Event type identifier   |
| `detail` | `object`        | Unified product payload |

**`detail` object**

| Property          | Type     | Description         |
| ----------------- | -------- | ------------------- |
| `productName`     | `string` | Product name        |
| `variantId`       | `number` | Variant ID          |
| `selectedVariant` | `number` | Selected variant ID |
| `qty`             | `number` | Quantity            |
| `price`           | `number` | Product price       |
| `currencyCode`    | `string` | Currency code       |

#### `onCheckout` callback

Triggered when a user clicks checkout. The event object has the following structure:

| Property | Type              | Description                                                        |
| -------- | ----------------- | ------------------------------------------------------------------ |
| `type`   | `'live-shopping'` | Event type identifier                                              |
| `detail` | `object`          | `LSTrackingEventData` payload (same structure as `onEvent` detail) |

### Example

```tsx theme={null}
<VideowiseSDK.LiveShoppingChannel
  videowiseInfo={{ shop: "storename.myshopify.com" }}
  lsId="your-live-stream-id"
  environment="production"
  offsetY={50}
  playerTopOffset={44}
  playerBottomOffset={34}
  vwStyle=".vw-player { border-radius: 8px; }"
  style={{ backgroundColor: "#000" }}
  loggedInUserData={{ email: "user@example.com", fullName: "Jane Doe" }}
  onEvent={(event) =>
    console.log("onEvent", event.detail.interactionType, event)
  }
  onAddToCart={(event) => console.log("onAddToCart", event.detail)}
  onCheckout={(event) => console.log("onCheckout", event)}
/>
```

See the full list of live shopping events: [Live shopping custom events](/live-shopping/custom-events)

### Custom channel body / modular section layout

By default the WebView body is a single monolith mount:

```html theme={null}
<div id="videowise_stream_channel"></div>
```

Pass `channelBodyHtml` to replace that body content and use [modular channel sections](/live-shopping/channel-layout) with your own HTML between mounts. Omit `#videowise_stream_channel` when using sections — the monolith container takes precedence and section mounts are ignored.

```tsx theme={null}
<VideowiseSDK.LiveShoppingChannel
  videowiseInfo={{ shop: "storename.myshopify.com" }}
  channelBodyHtml={`
    <div id="videowise_stream_channel_hero"></div>
    <div id="videowise_stream_channel_livenow"></div>
    <h2>Upcoming</h2>
    <div id="videowise_stream_channel_upcoming"></div>
    <h2>Past shows</h2>
    <div id="videowise_stream_channel_past"></div>
  `}
/>
```

Mount ids: `videowise_stream_channel_hero`, `videowise_stream_channel_livenow`, `videowise_stream_channel_upcoming`, `videowise_stream_channel_past`, optional `videowise_stream_channel_shared`.

See [Live channel page layout](/live-shopping/channel-layout) for the full web embed contract (same mount ids on the web and in the RN WebView body).

### Picture-in-Picture (PiP)

When a user taps PiP inside the live shopping WebView, the SDK:

1. Receives a `PIP_ENTER` message from the embed with the HLS stream URL, `lsId`, and playback position
2. Swaps the WebView for a native `react-native-video` player and enters system PiP
3. On PiP exit, remounts the WebView with the same `lsId` so the embed reloads the stream; for **recordings**, also writes playback position to session storage so the web player can seek on resume
4. On iOS, automatically calls `VwAppMinimizer.minimize()` when PiP becomes active (if the host app implements the native module)

Android typically backgrounds the app automatically when PiP starts.

#### Setup in your app

**1. Install peer dependencies**

```bash theme={null}
npm install react-native-video
cd ios && pod install && cd ..
```

**2. Render `LiveShoppingChannel`**

No extra PiP wiring is needed in JS — just render the component:

```tsx theme={null}
import VideowiseSDK from "@videowisehq/videowise-react-native-sdk";

export function LiveShoppingScreen() {
  return (
    <VideowiseSDK.LiveShoppingChannel
      videowiseInfo={{ shop: "storename.myshopify.com" }}
      environment="production"
      onEvent={(event) => console.log(event)}
      onAddToCart={(event) => console.log(event)}
    />
  );
}
```

Pass `lsId` to open a specific stream on first load:

```tsx theme={null}
<VideowiseSDK.LiveShoppingChannel
  videowiseInfo={{ shop: "storename.myshopify.com" }}
  lsId="your-live-stream-id"
/>
```

Disable the PiP button if needed (enabled by default):

```tsx theme={null}
<VideowiseSDK.LiveShoppingChannel
  videowiseInfo={{ shop: "storename.myshopify.com" }}
  enabledPipButton={false}
/>
```

**3. iOS — `Info.plist`**

Add background audio so PiP playback continues when the app is backgrounded:

```xml theme={null}
<key>UIBackgroundModes</key>
<array>
  <string>audio</string>
</array>
```

**4. Android — `AndroidManifest.xml`**

Enable PiP on your main activity:

```xml theme={null}
<activity
  android:name=".MainActivity"
  android:supportsPictureInPicture="true"
  ...>
```

**5. Optional — iOS `VwAppMinimizer` (host app native module)**

iOS has no public API to programmatically send the app to the background. The SDK calls `VwAppMinimizer.minimize()` automatically when PiP becomes active on iOS. Implement this native module in **your host app** (not in the SDK npm package) to enable auto-minimize.

> **App Store note:** the example below uses a private API (`UIApplication.suspend`). Use only if your team accepts that trade-off.

`ios/YourApp/VwAppMinimizer.swift`:

```swift theme={null}
import UIKit
import React

@objc(VwAppMinimizer)
class VwAppMinimizer: NSObject {
  @objc static func requiresMainQueueSetup() -> Bool {
    true
  }

  @objc func minimize() {
    DispatchQueue.main.async {
      UIApplication.shared.perform(Selector(("suspend")))
    }
  }
}
```

`ios/YourApp/VwAppMinimizer.m`:

```objc theme={null}
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(VwAppMinimizer, NSObject)
RCT_EXTERN_METHOD(minimize)
@end
```

**Register the files in your Xcode project**

Creating the files is not enough — they must be registered in your app target so Xcode compiles them. You can do this in Xcode or by editing `project.pbxproj` directly (useful if you manage the iOS project as code).

*Option A — Xcode UI*

1. Open `ios/YourApp.xcworkspace` in Xcode
2. Right-click your app folder (same group as `AppDelegate.swift`) → **Add Files to "YourApp"...**
3. Select `VwAppMinimizer.swift` and `VwAppMinimizer.m`
4. Ensure your app target is checked under **Add to targets**

*Option B — Edit `project.pbxproj`*

1. Place both files in `ios/YourApp/` next to `AppDelegate.swift`
2. Open `ios/YourApp.xcodeproj/project.pbxproj`
3. Generate four unique IDs (run `uuidgen` four times, remove dashes, uppercase)
4. Add entries following the same pattern as `AppDelegate.swift`:

```text theme={null}
/* PBXBuildFile — add to PBXBuildFile section */
SWIFT_BUILD_ID /* VwAppMinimizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = SWIFT_FILE_ID /* VwAppMinimizer.swift */; };
OBJC_BUILD_ID /* VwAppMinimizer.m in Sources */ = {isa = PBXBuildFile; fileRef = OBJC_FILE_ID /* VwAppMinimizer.m */; };

/* PBXFileReference — add to PBXFileReference section */
SWIFT_FILE_ID /* VwAppMinimizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = VwAppMinimizer.swift; path = YourApp/VwAppMinimizer.swift; sourceTree = "<group>"; };
OBJC_FILE_ID /* VwAppMinimizer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = VwAppMinimizer.m; path = YourApp/VwAppMinimizer.m; sourceTree = "<group>"; };
```

5. Add `SWIFT_FILE_ID` and `OBJC_FILE_ID` to your app's `PBXGroup` `children` array (same group that lists `AppDelegate.swift`)
6. Add `SWIFT_BUILD_ID` and `OBJC_BUILD_ID` to `PBXSourcesBuildPhase` → `files` array (Compile Sources)

Replace `YourApp` with your iOS app folder name. Use the code blocks above for the file contents and mirror how `AppDelegate.swift` is registered in your `project.pbxproj`.

If `VwAppMinimizer` is not registered, the SDK safely skips minimization — PiP still works.

**6. Install the SDK in your app and rebuild**

Install from npm in your React Native project (not a local `file:` link):

```bash theme={null}
npm install @videowisehq/videowise-react-native-sdk react-native-webview @react-native-async-storage/async-storage react-native-video
cd ios && pod install && cd ..
npx react-native run-ios   # physical iPhone/iPad required for PiP
npx react-native run-android
```

#### Testing notes

| Platform              | Notes                                                                 |
| --------------------- | --------------------------------------------------------------------- |
| iOS (physical device) | **Required** for PiP testing                                          |
| iOS Simulator         | PiP does not work — `isPictureInPictureSupported()` returns false     |
| Android               | PiP works on supported devices; app usually backgrounds automatically |

The floating PiP window always shows **system playback controls** (play/pause, close). Those cannot be removed by the SDK.

#### WebView → native PiP protocol

The live shopping embed sends this message when the user requests PiP:

```json theme={null}
{
  "request": "PIP_ENTER",
  "payload": {
    "lsId": "live-shopping-stream-id",
    "url": "https://your-cdn.example.com/live/stream.m3u8",
    "startTime": 142.5
  }
}
```

| Field       | Required | Description                                                |
| ----------- | -------- | ---------------------------------------------------------- |
| `lsId`      | Yes      | Used to restore the correct stream when PiP ends           |
| `url`       | Yes      | HLS (`.m3u8`) live stream URL                              |
| `startTime` | No       | Playback position in seconds where native PiP should start |

#### Native PiP → WebView resume

When PiP ends, the SDK remounts the WebView with `window.lsId` set so the embed auto-loads the same stream.

For **recordings** (not live streams), the SDK also writes the native player's current playback time to session storage:

```js theme={null}
sessionStorage.getItem("reeview_ls_session_recording_time");
// e.g. '{"time":187.4,"lsId":"live-shopping-stream-id"}'
```

The embed reads this key on init for recordings only, parses `{ time, lsId }`, and seeks the web player to resume from that position. Live streams do not use this key — they resume at the live edge.

## Related

* [React Native SDK](/sdk/react-native)
* [Live shopping](/live-shopping/overview)
* [Channel page layout](/live-shopping/channel-layout)
* [Live shopping custom events](/live-shopping/custom-events)
* [Order tracking](/live-shopping/order-tracking)
* [Tracking pixel (SDK)](/sdk/tracking-pixel)
