> For the complete documentation index, see [llms.txt](https://docs.firework.com/firework-for-developers/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.firework.com/firework-for-developers/android-sdk/integration-guide/configure-video-feed.md).

# Widget - Video Feed View

`FwVideoFeedView` is a custom view that displays video thumbnails in a scrollable feed. Users can tap on any thumbnail to launch the fullscreen video player. The feed supports multiple layout types (horizontal, vertical, grid) and can display content from various sources.

## Integration

### Add to Layout

Add `FwVideoFeedView` to your layout XML file:

```xml
<com.firework.videofeed.FwVideoFeedView
    android:id="@+id/videoFeedView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
```

### Initialize

Initialize the feed view by calling `init()`:

```kotlin
class MainActivity : AppCompatActivity() {
    private lateinit var videoFeedView: FwVideoFeedView
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        videoFeedView = findViewById(R.id.videoFeedView)
        
        // Simple initialization with defaults
        videoFeedView.init()
    }
    
    override fun onDestroy() {
        videoFeedView.destroy()
        super.onDestroy()
    }
}
```

### Initialize with ViewOptions

Provide `ViewOptions` to customize the feed:

```kotlin
val viewOptions = viewOptions {
    baseOptions {
        feedResource(FeedResource.Discovery)
    }
    layoutOptions {
        feedLayout(FeedLayout.GRID)
        columnCount(3)
        itemSpacing(8.dpToPx())
    }
}

videoFeedView.init(viewOptions)
```

## Configuration Options

### Feed Source (BaseOption)

Configure the content source for the feed. See [Feed Sources](/firework-for-developers/android-sdk/integration-guide/feed-sources.md) for detailed information on each type.

```kotlin
viewOptions {
    baseOptions {
        // Discovery feed
        feedResource(FeedResource.Discovery)
        
        // Or Channel feed
        feedResource(FeedResource.Channel(channelId = "your_encoded_channel_id"))
        
        // Or Playlist feed
        feedResource(
            FeedResource.Playlist(
                channelId = "your_encoded_channel_id",
                playlistId = "your_encoded_playlist_id"
            )
        )
        
        // Or Single content
        feedResource(FeedResource.SingleContent(contentId = "your_encoded_content_id"))
        
        // Or SKU-based feed
        feedResource(
            FeedResource.Sku(
                channelId = "your_encoded_channel_id",
                productIds = listOf("sku1", "sku2")
            )
        )
    }
}
```

For more feed types (HashtagPlaylist, DynamicContent, ShareUrl), see [BaseOption Configuration](/firework-for-developers/android-sdk/integration-guide/configuration/base-options.md).

### Layout Configuration (LayoutOption)

Control the visual layout and appearance of the video feed.

#### Feed Layout Types

```kotlin
viewOptions {
    layoutOptions {
        // Horizontal scrolling row (default)
        feedLayout(FeedLayout.HORIZONTAL)
        
        // Or vertical scrolling column
        feedLayout(FeedLayout.VERTICAL)
        
        // Or multi-column grid
        feedLayout(FeedLayout.GRID)
        columnCount(3) // Only for grid layout
    }
}
```

#### Spacing and Appearance

```kotlin
viewOptions {
    layoutOptions {
        // Item spacing
        itemSpacing(12.dpToPx()) // Space between items
        
        // Background color
        backgroundColor(Color.parseColor("#F5F5F5"))
        
        // Rounded corners
        roundedCorner(true)
        roundedCornerRadius(12.dpToPx())
        
        // Play icon
        showPlayIcon(true)
        playIconWidth(32.dpToPx())
        
        // Aspect ratio
        aspectRatio(9.0 / 16.0) // 9:16 ratio
    }
}
```

#### Title Position

```kotlin
viewOptions {
    layoutOptions {
        // Title overlays thumbnail (default)
        feedTitlePosition(FeedTitlePosition.NESTED)
        
        // Or title appears below thumbnail
        feedTitlePosition(FeedTitlePosition.STACKED)
    }
}
```

For complete layout options, see [LayoutOption Configuration](/firework-for-developers/android-sdk/integration-guide/configuration/layout-options.md).

### Title Configuration (TitleOption)

Customize the appearance of feed titles.

```kotlin
viewOptions {
    titleOptions {
        // Visibility
        showFeedTitle(true)
        
        // Text styling
        feedTitleTextColor(Color.WHITE)
        feedTitleTextSize(18.spToPx())
        feedTitleTextNumberOfLines(2)
        feedTitleTextPadding(12.dpToPx())
        
        // Background
        feedTitleBackgroundColor(Color.parseColor("#80000000"))
        
        // Custom font
        feedTitleTextTypeface(
            ResourcesCompat.getFont(context, R.font.custom_font)
        )
    }
}
```

#### Gradient Background

```kotlin
viewOptions {
    titleOptions {
        feedTitleBackgroundDrawable(
            GradientDrawable().apply {
                colors = intArrayOf(
                    Color.parseColor("#667eea"),
                    Color.parseColor("#764ba2")
                )
                gradientType = GradientDrawable.LINEAR_GRADIENT
                orientation = GradientDrawable.Orientation.TOP_BOTTOM
            }
        )
    }
}
```

For complete title options, see [TitleOption Configuration](/firework-for-developers/android-sdk/integration-guide/configuration/title-options.md).

### Player Configuration (PlayerOption)

Configure the fullscreen video player that opens when users tap on video thumbnails.

```kotlin
viewOptions {
    playerOptions {
        // Autoplay first visible video in feed
        autoplay(true)
        
        // Player display mode
        playerMode(PlayerMode.FIT_MODE) // or FULL_BLEED_MODE
        
        // Auto-play next video when current ends
        autoPlayOnComplete(true)
        
        // UI elements
        showShareButton(true)
        showMuteButton(true)
        showFireworkLogo(false)
        
        // Picture-in-Picture
        enablePipMode(true)
    }
}
```

For complete player configuration options, see [Video Player Configuration](/firework-for-developers/android-sdk/integration-guide/video-player.md).

### CTA Configuration (CtaOption)

Configure Call-to-Action button behavior.

```kotlin
viewOptions {
    ctaOptions {
        // CTA visibility delay
        ctaDelay(CtaDelay(3.0f, CtaDelayUnit.SECONDS))
        // Or use percentage
        ctaDelay(CtaDelay(0.5f, CtaDelayUnit.PERCENTAGE))
        
        // CTA highlight delay
        ctaHighlightDelay(CtaDelay(2.0f, CtaDelayUnit.SECONDS))
    }
}
```

**Delay Units:**

* `CtaDelayUnit.SECONDS` - Delay in seconds (0.0 - 10.0, default: 3)
* `CtaDelayUnit.PERCENTAGE` - Percentage of video duration (0.0 - 1.0, default: 0.2)

For CTA handling, see [Shopping Integration](/firework-for-developers/android-sdk/integration-guide/shoppable-videos.md).

### Ad Badge Configuration (AdBadgeOption)

Customize the ad badge appearance on sponsored content.

```kotlin
viewOptions {
    adBadgeOptions {
        // Visibility
        adBadgeIsHidden(false)
        adBadgeShowOnThumbnails(true)
        
        // Appearance
        adBadgeBackColor(Color.parseColor("#FFFECD"))
        adBadgeTextColor(Color.BLACK)
        
        // Label text
        adBadgeLabel(AdBadgeTextType.SPONSORED) // or AdBadgeTextType.AD
        
        // Custom font
        adBadgeTypeface(ResourcesCompat.getFont(context, R.font.custom_font))
    }
}
```

### Chat Configuration (ChatOption)

Configure livestream chat appearance.

```kotlin
viewOptions {
    chatOptions {
        // Chat text color
        chatTextColor(Color.WHITE)
        
        // Chat text shadow
        chatTextShadowColor(Color.BLACK)
        chatTextShadowOffsetX(2f)
        chatTextShadowOffsetY(2f)
        chatTextShadowRadius(4f)
    }
}
```

## Feed Refresh

Manually refresh the feed to reload content:

```kotlin
videoFeedView.refresh()
```

Example with SwipeRefreshLayout:

```kotlin
swipeRefreshLayout.setOnRefreshListener {
    videoFeedView.refresh()
    swipeRefreshLayout.isRefreshing = false
}
```

## Feed State Monitoring

### Feed View State Listener

Monitor feed loading states and respond to state changes:

```kotlin
videoFeedView.setOnFeedViewStateListener { feedViewState ->
    when (feedViewState) {
        is FeedViewState.Loading -> {
            // The feed is currently loading content
         
        }
        is FeedViewState.LoadData -> {
            // Data has been successfully loaded and is available
        }
        is FeedViewState.EmptyFeed -> {
            // The feed has no content to display
        }
        is FeedViewState.EndOfFeed -> {
            // User has reached the end of available content
        }
        is FeedViewState.Error -> {
            // An error occurred while loading feed content
        }
    }
}
```

**Feed View States:**

* `Loading` - Feed is currently loading content
* `LoadData` - Data has been successfully loaded
* `EmptyFeed` - Feed has no content to display
* `EndOfFeed` - End of available content reached
* `Error` - Error occurred with message

### Feed Load Listener

Get notified when videos are loaded into the feed — for the initial page, pull-to-refresh, and each scroll-triggered load-more. The callback returns only the videos loaded by that request (not the cumulative list). It also fires for the new pages loaded by the fullscreen player opened from this feed.

```kotlin
videoFeedView.setOnFeedLoadListener { videos ->
    // videos: List<VideoInfo> loaded by this request
    videos.forEach { Log.d("VideoFeed", "loaded ${it.id}, feedId=${it.feedId}") }
}
```

> **Recommended:** Set the listener **before** calling `init()` to reliably receive the **first-page** callback. If you set it afterwards, refresh and load-more callbacks still arrive, but the first page may be missed.

**Notes:**

* Returns `List<VideoInfo>` — only the videos loaded by the current request.
* Fires for: initial page, pull-to-refresh, scroll load-more, and the new pages loaded by the fullscreen player opened from this feed.
* Does **not** fire for: end of feed, load failures (use the Error Listener), or simply opening the fullscreen player (that page was already reported).
* `VideoInfo.feedId` equals the view's instance id — use it to disambiguate when multiple feeds share one listener.

### Error Listener

Handle errors during feed operations:

```kotlin
videoFeedView.setOnErrorListener { error ->
    when (error) {
        is SdkLevelError.SdkNotInitialized -> {
            // SDK not initialized
            showError("Please initialize Firework SDK first")
        }
        is VideoFeedError.LoadingFailed -> {
            // Feed loading failed
            showError("Failed to load video feed")
        }
        is NetworkError -> {
            // Network error
            showError("Network error. Please check your connection")
        }
        is AdsError -> {
            // Ad-related error
            Log.e("VideoFeed", "Ad error: ${error::class.simpleName}")
        }
        is LivestreamError -> {
            // Livestream error
            Log.e("VideoFeed", "Livestream error: ${error::class.simpleName}")
        }
        else -> {
            showError("An error occurred: ${error::class.simpleName}")
        }
    }
}
```

**Error Types:**

* `SdkLevelError.SdkNotInitialized` - SDK not initialized
* `VideoFeedError.LoadingFailed` - Feed loading failed
* `AdsError` - Ad-related errors (VideoLoadFailed, PlaybackFailed, ImaAdFailed)
* `LivestreamError` - Livestream errors (MissingLivestreamInitializer, EngineFailed)
* `ShareContentError` - Share errors (EmptyShareUrl, SharingFailed)
* `NetworkError` - Network-related errors
* `PlayerError` - Video playback errors

### Feed Item Click Listener

#### setOnClickFeedItemListener (Recommended)

Register a click listener that supports **interception**. 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
videoFeedView.setOnClickFeedItemListener { index, videoInfo ->
    Log.d("VideoFeed", "Video clicked at index $index: ${videoInfo.videoId}")

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

**Parameters:**

| 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.

**Example: Intercept click to open a custom player**

```kotlin
videoFeedView.setOnClickFeedItemListener { index, videoInfo ->
    // Open your own player UI
    startActivity(MyCustomPlayerActivity.intent(this, videoInfo.videoId))
    true // Intercept — SDK will NOT open its player
}
```

#### setOnFeedItemClickListener (Deprecated)

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

Track when users tap on feed items:

```kotlin
videoFeedView.setOnFeedItemClickListener { feedItem ->
    Log.d("VideoFeed", "Video clicked: ${feedItem.title}")

    val videoId = feedItem.id
    val title = feedItem.title
    val index = feedItem.indexInTheList
    val duration = feedItem.duration
    val videoInfo = feedItem.videoInfo
}
```

**FeedItem Properties:**

* `id` - Video content ID
* `title` - Video title
* `indexInTheList` - Position in feed
* `duration` - Video duration in milliseconds
* `videoInfo` - Detailed video information

## Lifecycle Management

### Destroy in Activity

Call `destroy()` when the feed view is no longer needed:

```kotlin
class VideoFeedActivity : AppCompatActivity() {
    private lateinit var videoFeedView: FwVideoFeedView
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_video_feed)
        
        videoFeedView = findViewById(R.id.videoFeedView)
        videoFeedView.init()
    }
    
    override fun onDestroy() {
        videoFeedView.destroy()
        super.onDestroy()
    }
}
```

### Destroy in Fragment

In fragments, call `destroy()` in `onDestroyView()`:

```kotlin
class VideoFeedFragment : Fragment() {
    private var _binding: FragmentVideoFeedBinding? = null
    private val binding get() = _binding!!
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        _binding = FragmentVideoFeedBinding.bind(view)
        
        binding.videoFeedView.init()
    }
    
    override fun onDestroyView() {
        binding.videoFeedView.destroy()
        super.onDestroyView()
        _binding = null
    }
}
```

## Complete Configuration Examples

### Grid Layout Feed

```kotlin
class GridFeedActivity : AppCompatActivity() {
    private lateinit var videoFeedView: FwVideoFeedView
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_grid_feed)
        
        videoFeedView = findViewById(R.id.videoFeedView)
        
        val viewOptions = viewOptions {
            baseOptions {
                feedResource(FeedResource.Discovery)
            }
            layoutOptions {
                feedLayout(FeedLayout.GRID)
                columnCount(3)
                itemSpacing(8.dpToPx())
                roundedCorner(true)
                roundedCornerRadius(12.dpToPx())
                backgroundColor(Color.parseColor("#F8F9FA"))
                showPlayIcon(true)
            }
            titleOptions {
                showFeedTitle(true)
                feedTitleTextColor(Color.WHITE)
                feedTitleBackgroundColor(Color.parseColor("#80000000"))
                feedTitleTextSize(14.spToPx())
                feedTitleTextNumberOfLines(2)
            }
            playerOptions {
                autoplay(false)
                showShareButton(true)
                enablePipMode(true)
            }
        }
        
        videoFeedView.init(viewOptions)
    }
    
    override fun onDestroy() {
        videoFeedView.destroy()
        super.onDestroy()
    }
}
```

### Horizontal Story Feed

```kotlin
class StoryFeedActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_story_feed)
        
        val videoFeedView = findViewById<FwVideoFeedView>(R.id.videoFeedView)
        
        val viewOptions = viewOptions {
            baseOptions {
                feedResource(FeedResource.Channel(channelId = "your_channel_id"))
            }
            layoutOptions {
                feedLayout(FeedLayout.HORIZONTAL)
                itemSpacing(16.dpToPx())
                roundedCorner(true)
                roundedCornerRadius(12.dpToPx())
                showPlayIcon(false) // No play icon for story style
                aspectRatio(9.0 / 16.0)
            }
            titleOptions {
                showFeedTitle(false)
            }
            playerOptions {
                autoplay(false)
                scrollingOrientation(ScrollingOrientation.VERTICAL)
            }
        }
        
        videoFeedView.init(viewOptions)
    }
}
```

### Product Video Gallery

```kotlin
class ProductGalleryActivity : AppCompatActivity() {
    private fun loadProductVideos(productSku: String) {
        val videoFeedView = findViewById<FwVideoFeedView>(R.id.productVideos)
        
        // Set up error listener
        videoFeedView.setOnErrorListener { error ->
            Toast.makeText(this, "Error: ${error::class.simpleName}", Toast.LENGTH_SHORT).show()
        }
        
        // Set up state listener
        videoFeedView.setOnFeedViewStateListener { state ->
            when (state) {
                is FeedViewState.Loading -> {
                    progressBar.isVisible = true
                }
                is FeedViewState.LoadData -> {
                    progressBar.isVisible = false
                }
                is FeedViewState.EmptyFeed -> {
                    progressBar.isVisible = false
                    emptyView.isVisible = true
                }
                else -> {}
            }
        }

        videoFeedView.setOnFeedLoadListener { videos ->
            // Videos loaded by this request (set before init() to receive the first page)
            videos.forEach { Log.d("VideoFeed", "loaded ${it.id}") }
        }
        
        val viewOptions = viewOptions {
            baseOptions {
                feedResource(
                    FeedResource.Sku(
                        channelId = "your_channel_id",
                        productIds = listOf(productSku)
                    )
                )
            }
            layoutOptions {
                feedLayout(FeedLayout.GRID)
                columnCount(2)
                itemSpacing(8.dpToPx())
                roundedCorner(true)
                showPlayIcon(true)
            }
            playerOptions {
                autoplay(false)
                showShareButton(true)
            }
        }
        
        videoFeedView.init(viewOptions)
    }
}
```

## Important Notes

* Always call `init()` before the feed view can display content
* The SDK must be initialized (via `FireworkSdk.init()`) before calling `init()` on the feed view
* Always call `destroy()` when the feed view is no longer needed to free resources
* ViewOptions provided to `init()` allow complete customization of the feed
* For grid layout, `columnCount` determines the number of columns
* Autoplay on thumbnails can impact battery and data usage
* Use `dpToPx()` for density-independent pixel values
* Use `spToPx()` for text size values

## See Also

* [Feed Sources](/firework-for-developers/android-sdk/integration-guide/feed-sources.md) - Learn about different content sources
* [ViewOptions Configuration](/firework-for-developers/android-sdk/integration-guide/configuration.md) - Complete configuration system
* [BaseOption](/firework-for-developers/android-sdk/integration-guide/configuration/base-options.md) - Feed source configuration
* [LayoutOption](/firework-for-developers/android-sdk/integration-guide/configuration/layout-options.md) - Detailed layout customization
* [TitleOption](/firework-for-developers/android-sdk/integration-guide/configuration/title-options.md) - Title styling options
* [Video Player Configuration](/firework-for-developers/android-sdk/integration-guide/video-player.md) - Player configuration and features
* [Shopping Integration](/firework-for-developers/android-sdk/integration-guide/shoppable-videos.md) - CTA and shopping features
* [Widgets Overview](/firework-for-developers/android-sdk/integration-guide/widgets.md) - All available widgets
