# Widget - Player Deck -Beta

`FwPlayerDeckView` is a horizontal video feed component in the Firework Android SDK. It displays a scrollable row of video thumbnails with built-in autoplay support and product panel

This page covers how to integrate `FwPlayerDeckView` into your Android app and documents every public method and property.

***

## 1. Adding the View in XML

Add `FwPlayerDeckView` to your layout file. The recommended way to configure `FwPlayerDeckView` is programmatically via `ViewOptions`, so the XML tag only needs `android:id`, `layout_width`, and `layout_height`:

```xml
<com.firework.videofeed.FwPlayerDeckView
    android:id="@+id/playerDeck"
    android:layout_width="match_parent"
    android:layout_height="500dp" />
```

> **Note:** Do not set custom XML attributes on `FwPlayerDeckView`. Use the `ViewOptions.Builder` API (described below) for all configuration. See the next section for how to choose the correct `layout_height`.

***

## 2. Recommended Height Configuration

Choosing the right height for `FwPlayerDeckView` is important for a good user experience. We recommend a height that shows approximately **1.5 deck items** on screen, so the user can see the current item fully and get a visual hint that more content is available by scrolling horizontally.

#### How Item Width Is Derived from View Height

Each deck item fills the full height of the `FwPlayerDeckView`. The item consists of a **9:16 video area** on top and a **72dp product panel** at the bottom. The SDK calculates item width from the available height:

```
videoHeight = viewHeight - 72dp
itemWidth   = videoHeight × 9 / 16
```

The number of items visible on screen is determined by: `screenWidth / itemWidth`.

#### The Formula

To display **1.5 items** horizontally, set the view height to:

```
recommendedHeight = screenWidth × (32 / 27) + 72dp
                  ≈ screenWidth × 1.185 + 72dp
```

This is derived from solving `itemWidth = screenWidth / 1.5`:

```
screenWidth / 1.5 = (H - 72dp) × 9 / 16
H = screenWidth × 16 / (9 × 1.5) + 72dp
H = screenWidth × 32 / 27 + 72dp
```

#### Reference Values for Common Screen Widths

| Screen Width (dp) | Recommended Height (dp) | Example Devices                      |
| ----------------- | ----------------------- | ------------------------------------ |
| 360               | \~499                   | Budget / mid-range phones            |
| 390               | \~534                   | Pixel 7, iPhone-class widths         |
| 412               | \~560                   | Pixel 7 Pro, Samsung Galaxy S series |

> **Tip:** For most portrait-mode phones, a height of **500dp** is a reasonable default that works well across common screen sizes. For a more precise fit, compute the height dynamically at runtime.

#### Dynamic Calculation at Runtime

To compute the optimal height programmatically based on the device's actual screen width:

**In traditional Views (Kotlin):**

```kotlin
import android.content.res.Resources

fun calculateRecommendedHeight(): Int {
    val screenWidthDp = Resources.getSystem().configuration.screenWidthDp
    val bottomPanelDp = 72
    val recommendedHeightDp = (screenWidthDp * 32.0 / 27.0 + bottomPanelDp).toInt()
    // Convert dp to pixels for use in LayoutParams
    val density = Resources.getSystem().displayMetrics.density
    return (recommendedHeightDp * density).toInt()
}

// Usage:
playerDeckView.layoutParams.height = calculateRecommendedHeight()
playerDeckView.requestLayout()
```

**In Jetpack Compose:**

```kotlin
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

@Composable
fun rememberRecommendedHeight(): Dp {
    val screenWidthDp = LocalConfiguration.current.screenWidthDp
    val bottomPanelDp = 72
    return (screenWidthDp * 32.0 / 27.0 + bottomPanelDp).dp
}

// Usage:
val deckHeight = rememberRecommendedHeight()
AndroidView(
    modifier = Modifier
        .fillMaxWidth()
        .height(deckHeight),
    // ...
)
```

> **Note:** If you configure `feedPadding` or `itemSpacing` in `LayoutOption`, the effective visible area changes slightly. The formula above ignores these values for simplicity; in practice the difference is negligible.

***

## 3. Programmatic Initialization (Fragment)

The typical lifecycle for `FwPlayerDeckView` in a Fragment is:

1. Build a `ViewOptions` instance via `ViewOptions.Builder`.
2. Call `init(viewOptions, fragmentManager)` to start loading content.
3. Call `destroy()` in `onDestroyView()` to release resources.

#### Full Example

```kotlin
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.firework.common.feed.FeedResource
import com.firework.videofeed.FwPlayerDeckView
import com.firework.viewoptions.BaseOption
import com.firework.viewoptions.LayoutOption
import com.firework.viewoptions.PlayerOption
import com.firework.viewoptions.ViewOptions

class MyPlayerDeckFragment : Fragment() {

    private var playerDeckView: FwPlayerDeckView? = null

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {
        // Inflate a layout that contains <FwPlayerDeckView android:id="@+id/playerDeck" android:layout_height="500dp" ... />
        return inflater.inflate(R.layout.fragment_my_player_deck, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        playerDeckView = view.findViewById(R.id.playerDeck)

        // 1. Build ViewOptions
        val viewOptions = ViewOptions.Builder()
            .baseOption(
                BaseOption.Builder()
                    .feedResource(FeedResource.Channel("your_channel_id"))
                    .build()
            )
            .playerOption(
                PlayerOption.Builder()
                    .autoplay(true)
                    .build()
            )
            .layoutOption(
                LayoutOption.Builder()
                    .backgroundColor(0xFFFFFFFF.toInt())
                    .itemSpacing(8)
                    .build()
            )
            .build()

        // 2. Set up listeners (optional, but recommended)
        playerDeckView?.setOnErrorListener { error ->
            // Handle SDK errors (SdkNotInitialized, VideoFeedError, PlayerError, etc.)
            android.util.Log.e("PlayerDeck", "Error: $error")
        }

        playerDeckView?.setOnClickFeedItemListener { index, videoInfo ->
            // Called when a user taps a video thumbnail
            android.util.Log.i("PlayerDeck", "Clicked index $index: ${videoInfo.videoId}")
            false // Return false to let the SDK open the player normally
        }

        playerDeckView?.setOnFeedViewStateListener { state ->
            // Observe feed loading state changes
            android.util.Log.d("PlayerDeck", "State: $state")
        }

        // 3. Initialize with options and FragmentManager
        playerDeckView?.init(viewOptions, childFragmentManager)
    }

    override fun onDestroyView() {
        // 4. Release all resources
        playerDeckView?.destroy()
        playerDeckView = null
        super.onDestroyView()
    }
}
```

#### Key Points

* **`feedResource`** determines the content source. Common options include:
  * `FeedResource.Discovery` — curated discovery feed
  * `FeedResource.Channel("channelId")` — all videos from a channel
  * `FeedResource.Playlist("channelId", "playlistId")` — a specific playlist
* **`autoplay`** in `PlayerOption` controls whether the first visible video plays automatically.
* **`init()` can be called multiple times** to reinitialize with new options (e.g., switching channels). Each call generates a new `feedId`.
* **`destroy()` must always be called** when the view is no longer needed to prevent memory leaks.

***

## 4. Compose Integration

Embed `FwPlayerDeckView` inside Jetpack Compose using `AndroidView`. Use the `factory` block for creation and initialization, and `onRelease` for cleanup.

```kotlin
import android.view.ViewGroup
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.fragment.app.FragmentActivity
import com.firework.common.feed.FeedResource
import com.firework.videofeed.FwPlayerDeckView
import com.firework.viewoptions.BaseOption
import com.firework.viewoptions.PlayerOption
import com.firework.viewoptions.ViewOptions

@Composable
fun PlayerDeckViewComposable(
    modifier: Modifier = Modifier,
) {
    val context = LocalContext.current

    // Build ViewOptions once and remember across recompositions
    val viewOptions = remember {
        ViewOptions.Builder()
            .baseOption(
                BaseOption.Builder()
                    .feedResource(FeedResource.Channel("your_channel_id"))
                    .build()
            )
            .playerOption(
                PlayerOption.Builder()
                    .autoplay(true)
                    .build()
            )
            .build()
    }

    AndroidView(
        modifier = modifier
            .fillMaxWidth()
            .height(500.dp), // See "Recommended Height Configuration" for how to choose this value
        factory = { androidContext ->
            FwPlayerDeckView(androidContext).apply {
                layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT,
                )

                // Set up error listener
                setOnErrorListener { error ->
                    android.util.Log.e("PlayerDeck", "Error: $error")
                }

                // Set up feed item click listener
                setOnClickFeedItemListener { index, videoInfo ->
                    android.util.Log.i("PlayerDeck", "Clicked index $index: ${videoInfo.videoId}")
                    false // Return false to let the SDK open the player normally
                }

                // Set up feed view state listener
                setOnFeedViewStateListener { state ->
                    android.util.Log.d("PlayerDeck", "State: $state")
                }

                // Initialize — obtain FragmentManager from the hosting FragmentActivity
                val fragmentManager = (context as FragmentActivity).supportFragmentManager
                init(viewOptions, fragmentManager)
            }
        },
        onRelease = { view ->
            // Clean up when the composable leaves the composition tree
            view.destroy()
        },
    )
}
```

#### Key Points

* The hosting Activity must extend `FragmentActivity` (or `AppCompatActivity`) so that `supportFragmentManager` is available.
* `onRelease` is called when the `AndroidView` is removed from the composition. This is the correct place to call `destroy()`.
* Wrap `ViewOptions` in `remember { }` to avoid rebuilding on every recomposition.

***

## 5. PlayerDeck Options

`PlayerDeckOption` configures UI elements specific to the inline PlayerDeck feed, such as the fullscreen icon, share button, and subtitle appearance. These options do not affect the fullscreen player (use `PlayerOption` for that).

```kotlin
val viewOptions = viewOptions {
    baseOptions {
        feedResource(FeedResource.Channel("your_channel_id"))
    }
    playerOptions {
        autoplay(true)
    }
    playerDeckOptions {
        showFullScreenIcon(true)
        showShareButton(true)
        showSubtitle(true)
        subtitleTextColor(Color.WHITE)
        subtitleBackgroundColor(Color.parseColor("#88000000"))
    }
}
```

For the full list of properties and defaults, see [PlayerDeck Options](/firework-for-developers/android-sdk/integration-guide/configuration/playerdeck-options.md). For which `ViewOptions` properties are supported by PlayerDeck, see [ViewOptions Support Reference](/firework-for-developers/android-sdk/integration-guide/01-basic-usage-and-api/03-playerdeck-viewoptions-applied.md).

***

## 6. Public API Reference

#### `init(options: ViewOptions?, fragmentManager: FragmentManager)`

Initializes the view with configuration options and starts loading content.

| Parameter         | Description                                                                                                       |
| ----------------- | ----------------------------------------------------------------------------------------------------------------- |
| `options`         | A `ViewOptions` instance built via `ViewOptions.Builder`. Pass `null` to use XML-declared attributes or defaults. |
| `fragmentManager` | The `FragmentManager` from the hosting Fragment (`childFragmentManager`) or Activity (`supportFragmentManager`).  |

**Usage notes:**

* Must be called before any content is displayed.
* Can be called multiple times to reinitialize with new options. Each call generates a new embed instance ID (`feedId`).
* If the Firework SDK has not been initialized, the error listener will receive `SdkNotInitialized`.

***

#### `destroy()`

Releases all resources held by the view: cancels coroutines, unbinds dependency injection, destroys impression trackers, and stops visibility tracking.

```kotlin
override fun onDestroyView() {
    playerDeckView.destroy()
    super.onDestroyView()
}
```

**Usage notes:**

* Must be called when the view is no longer needed (e.g., in `onDestroyView()` for Fragments, or `onRelease` for Compose `AndroidView`).
* If a fullscreen player or PiP session is active for this view, the DI scope is preserved until the player closes.

***

#### `refresh()`

Clears the current feed and reloads content from the data source.

```kotlin
playerDeckView.refresh()
```

**Usage notes:**

* Useful for pull-to-refresh implementations.
* If the SDK is not initialized, the error listener receives `SdkNotInitialized`.

***

#### `setOnErrorListener(listener: FwErrorListener)`

Registers a callback to receive SDK errors.

```kotlin
playerDeckView.setOnErrorListener { error ->
    when (error) {
        is SdkLevelError.SdkNotInitialized -> { /* SDK not initialized */ }
        is VideoFeedError -> { /* Feed loading error, check error.cause */ }
        is PlayerError -> { /* Playback error */ }
        else -> { /* Other errors */ }
    }
}
```

| Parameter  | Type              | Description                                             |
| ---------- | ----------------- | ------------------------------------------------------- |
| `listener` | `FwErrorListener` | A functional interface: `fun onFwError(error: FwError)` |

**Usage notes:**

* Set this before calling `init()` so that initialization errors (e.g., `SdkNotInitialized`) are captured.
* Only one listener can be active at a time. Setting a new listener replaces the previous one.

***

#### `setOnClickFeedItemListener(listener: OnClickFeedItemListener)`

Registers a click listener with **interception support**. If the listener returns `true`, the SDK will **not** open the fullscreen player (the click is intercepted). If it returns `false`, the SDK handles the click normally.

```kotlin
playerDeckView.setOnClickFeedItemListener { index, videoInfo ->
    Log.i("PlayerDeck", "Video clicked at index $index: ${videoInfo.videoId}")

    // Return true to intercept (prevent SDK from opening the player)
    // Return false to let the SDK handle the click normally
    false
}
```

| Parameter   | Type        | Description                              |
| ----------- | ----------- | ---------------------------------------- |
| `index`     | `Int`       | Position of the clicked item in the feed |
| `videoInfo` | `VideoInfo` | Detailed video metadata                  |

**Return value:** `Boolean` — `true` to intercept, `false` to allow the SDK to open the player.

***

#### `setOnFeedItemClickListener(listener: FeedItemClickListener)` *(Deprecated)*

> **Deprecated:** Use `setOnClickFeedItemListener` instead. This listener does not support click interception.

Registers a callback triggered when a user taps a video thumbnail.

```kotlin
playerDeckView.setOnFeedItemClickListener { item ->
    Log.i("PlayerDeck", "Video: ${item.id}, title: ${item.title}, " +
        "duration: ${item.duration}s, index: ${item.indexInTheList}")
}
```

The `FeedItem` data class contains:

| Field            | Type        | Description                                   |
| ---------------- | ----------- | --------------------------------------------- |
| `id`             | `String`    | The video ID                                  |
| `title`          | `String`    | The video caption/title                       |
| `duration`       | `Long`      | Video duration in seconds (0 for livestreams) |
| `indexInTheList` | `Int`       | Position in the feed list                     |
| `videoInfo`      | `VideoInfo` | Detailed video metadata for analytics         |

***

#### `setOnFeedViewStateListener(listener: FeedViewStateListener)`

Registers a callback for feed loading state changes.

```kotlin
playerDeckView.setOnFeedViewStateListener { state ->
    when (state) {
        is FeedViewState.Loading  -> { /* Feed is loading */ }
        is FeedViewState.LoadData -> { /* Data loaded successfully */ }
        is FeedViewState.EmptyFeed -> { /* No content available */ }
        is FeedViewState.EndOfFeed -> { /* All content has been loaded */ }
        is FeedViewState.Error    -> { /* Error: ${state.message} */ }
    }
}
```

| State            | Description                                            |
| ---------------- | ------------------------------------------------------ |
| `Loading`        | The feed is currently fetching data.                   |
| `LoadData`       | Data has been successfully loaded and displayed.       |
| `EmptyFeed`      | The data source returned no content.                   |
| `EndOfFeed`      | All available content has been loaded (no more pages). |
| `Error(message)` | An error occurred during loading.                      |

***

#### `setVisibilityTrackingEnabled(enabled: Boolean)`

Enables or disables automatic visibility-based autoplay control.

```kotlin
// Enable before or after init()
playerDeckView.setVisibilityTrackingEnabled(true)
```

**When enabled**, the view uses a `ViewTreeObserver`-based tracker (`OnScrollChangedListener` + `OnGlobalLayoutListener` + `getGlobalVisibleRect`) to automatically detect when the view scrolls in or out of the visible area. Playback is paused when less than 50% of the view is visible and resumed when at least 50% is visible again.

**Usage notes:**

* Can be called before or after `init()`.
* See Page 2: Handling Autoplay in Scrollable Containers for details and known Compose limitations.

***

#### `onViewPortEntered()`

Manually notifies the view that it has become visible in the viewport. Resumes autoplay if applicable.

```kotlin
playerDeckView.onViewPortEntered()
```

**Usage notes:**

* Use this when you are manually tracking visibility (Approach 1 in Page 2).
* Has no effect if `init()` has not been called.

***

#### `onViewPortLeft()`

Manually notifies the view that it has left the viewport. Pauses autoplay.

```kotlin
playerDeckView.onViewPortLeft()
```

**Usage notes:**

* Use this when you are manually tracking visibility (Approach 1 in Page 2).
* Has no effect if `init()` has not been called.

***

#### `feedId: String` (read-only property)

Returns the current embed instance ID. This value is unique per initialization cycle and changes each time `init()` is called.

```kotlin
val currentFeedId = playerDeckView.feedId
```

**Usage notes:**

* Useful for analytics or debugging to correlate events with a specific initialization cycle.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.firework.com/firework-for-developers/android-sdk/integration-guide/01-basic-usage-and-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
