GuidesAPI ReferenceRelease Notes
HomeLog InHome
Guides

Live streaming - Android

Live streaming lets your app play live video streams (live events) through the standard Blaze Videos player and widgets, side by side with on-demand videos. There is no separate "live" player — a live stream is simply a video that carries stream metadata, so it flows through the same widgets, inline player, and full-screen player you already use for on-demand videos.

A video is treated as a stream when it has a stream state (LIVE, UPCOMING, or ENDED) and a start time. Every stream moves through the same lifecycle:

  • Upcoming — scheduled but not started yet. The player shows a waiting room (thumbnail, title, and start time) until the stream goes live.
  • Live — currently broadcasting. The player shows a live indicator and supports live-edge playback with DVR (pause, rewind, and return to live).
  • Ended — the broadcast has finished. The recording remains available for replay.
📘

Live streams are delivered as HLS and are enabled per account by WSC Sports. Once enabled, streams are managed in the CMS and appear in your existing Videos widgets and player with no code changes required.

Content types and stream states

BlazeVideoContentType

Identifies whether a video is on-demand or a live stream.

enum class BlazeVideoContentType(val rawValue: String) {
    VIDEO("Video"),
    STREAM("Stream");
}

BlazeLiveStreamStatus

The lifecycle state of a stream. On-demand videos have no stream state.

enum class BlazeLiveStreamStatus {
    LIVE,
    UPCOMING,
    ENDED;

    val isLive: Boolean
    val isUpcoming: Boolean
    val isEnded: Boolean
}

Discovering and filtering streams

By default, a widget or player shows whatever its data source provides (as configured in the CMS), which may include both on-demand videos and streams. Use BlazeVideosFilterParams to explicitly control which content types and stream states are shown.

BlazeVideosFilterParams

data class BlazeVideosFilterParams internal constructor(
    var contentTypes: List<BlazeVideoContentType>,
    var streamStates: List<BlazeLiveStreamStatus>,
) {
    companion object {
        fun base(): BlazeVideosFilterParams // all content types + all stream states
    }
}
  • contentTypes — which content types to include (VIDEO, STREAM). An empty list applies no content-type filter.
  • streamStates — which stream states to include (LIVE, UPCOMING, ENDED). An empty list applies no stream-state filter.
📘

If you do not pass videosFilterParams, the SDK applies no additional content-type or stream-state filtering — the widget or player shows whatever its data source returns.

📘

Create filter params with BlazeVideosFilterParams.base() (which includes all content types and all stream states), then adjust its contentTypes / streamStates properties.

In a widget

videosFilterParams is a parameter of initWidget on the Videos widgets (row and grid):

videosWidget.initWidget(
    widgetLayout = widgetLayout,
    dataSource = dataSource,
    widgetId = "live-events",
    widgetDelegate = widgetDelegate,
    // Show live and upcoming streams only:
    videosFilterParams = BlazeVideosFilterParams.base().apply {
        contentTypes = listOf(BlazeVideoContentType.STREAM)
        streamStates = listOf(BlazeLiveStreamStatus.LIVE, BlazeLiveStreamStatus.UPCOMING)
    },
)

In the full-screen player

videosFilterParams is available on BlazeSDK.playVideos and BlazeSDK.prepareVideos:

BlazeSDK.playVideos(
    dataSource = dataSource,
    videosFilterParams = BlazeVideosFilterParams.base(), // include videos + all stream states
)

In the inline player

BlazeVideosInlinePlayer always shows on-demand videos only. It still accepts videosFilterParams for source compatibility, but ignores the value.

Ordering streams

Ordering is set on the data source (BlazeDataSourceType.Labels and BlazeDataSourceType.Ids) through orderType and advancedOrderType.

  • BlazeOrderType.RECENTLY_STARTED_FIRST / RECENTLY_STARTED_LAST — order by the content/stream start time (descending / ascending). Meaningful for Videos only; for other content types the backend falls back to its default ordering.
  • BlazeAdvancedOrderType.LiveFirst — surface currently-live items first.
val dataSource = BlazeDataSourceType.Labels(
    blazeWidgetLabel = BlazeWidgetLabel.singleLabel("live-events"),
    orderType = BlazeOrderType.RECENTLY_STARTED_FIRST,
    advancedOrderType = BlazeAdvancedOrderType.LiveFirst,
)

The stream lifecycle

Upcoming (waiting room)

When a stream's state is UPCOMING, the player shows a waiting room instead of playback controls: the stream's thumbnail, an UPCOMING indicator, and the start time formatted for the user's locale. Playback and seeking are disabled in this state.

When the stream transitions to LIVE, the waiting room automatically switches to the live player — no reload and no user action required.

Live

While a stream is LIVE, the player:

  • shows a live indicator (a red dot with a "LIVE" label);
  • plays at the live edge (the furthest available point in the stream);
  • supports DVR — the viewer can pause, rewind, and scrub back through the available window, then return to live.

Ended

When a stream ends (state changes to ENDED), the item is not removed from the widget or player — the recording stays available for replay. Behavior depends on where the viewer is:

  • At the live edge — the player auto-advances to the next item immediately. If there is no next item, the standard end-of-widget behavior applies.
  • Behind the live edge — playback continues to the end of the recorded content, then auto-advances. The status chip changes from "LIVE" to "ENDED" and the live / back-to-live indicator is removed.

Live edge and DVR

For a live stream, the player tracks whether the viewer is watching at the live edge or behind live:

  • When the viewer pauses or scrubs back, they move behind live and a Back to Live control appears.
  • Tapping Back to Live, seeking forward to the edge, or dragging the scrubber to the live-edge position returns the viewer to live.
  • While behind live, the elapsed time is shown as negative relative to the live edge (for example, -0:45).

The seek bar reflects the DVR window (from the oldest available point up to the live edge); the size of that window is determined by the backend. Live-edge detection and DVR seeking are handled by the SDK automatically.

Keeping streams up to date

The player keeps stream state current with a lightweight polling mechanism — you do not need to trigger anything:

  • Polling runs when the player loads and then at a regular interval (about every 10 seconds).
  • It refreshes the stream items currently loaded in the player (those with an active, non-ended stream), so a stream flipping UPCOMING → LIVE → ENDED is picked up automatically.
  • Updates are reflected both in the player and at the widget level (the status chip on the card updates in place).
  • Polling is lifecycle-aware (it pauses while the app is backgrounded) and backs off automatically if requests fail.

Live UI in the player

The in-player live UI is styled by the SDK with sensible defaults:

  • Status indicator — a chip that reads LIVE, UPCOMING, or ENDED (a red chip with a dot for live, a light chip for upcoming, and a grey chip for ended).
  • Back to Live button — a pill that reads "Live" at the live edge and "Back to Live" when the viewer is behind live.

Customize both through BlazeVideosPlayerStyle:

val playerStyle = BlazeVideosPlayerStyle.base().apply {
    statusIndicator.streamStates.liveStreamState.text = "ON AIR"
    backToLiveButton.playbackStates.behindLiveState.text = "Go Live"
}
BlazeSDK.setDefaultVideosPlayerStyle(playerStyle)

statusIndicator (BlazeVideosPlayerStatusIndicatorStyle)

  • isVisible — shows or hides the status indicator.
  • padding — the padding between the chip text and its container.
  • streamStates — one style per stream state: upcomingStreamState, liveStreamState, and endedStreamState. Each state (BlazeVideosPlayerStatusIndicatorStateStyle) sets isVisible, text, textStyle, backgroundColor, backgroundImageResId, cornerRadius, cornerRadiusRatio, borderColor, and borderWidth.

backToLiveButton (BlazeVideosPlayerBackToLiveButtonStyle)

  • margins — the button margins relative to the player container.
  • padding — the padding between the button content and its container.
  • playbackStates — one style for atLiveEdgeState (reads "Live") and one for behindLiveState (reads "Back to Live"). Each state (BlazeVideosPlayerBackToLiveButtonStateStyle) sets text, textStyle, backgroundColor, cornerRadius, cornerRadiusRatio, and icon (iconResId and iconTint; set to null to hide the icon).

The widget-level live chip and event-time text are customized separately — see below.

Customizing live UI at the widget level

The live indicators shown on widget cards are customizable through BlazeWidgetItemStyle:

Audio track selection

When a live stream carries more than one audio track, for example several commentary feeds, the full-screen player shows an audio button. Tapping it opens a "Commentary audio" sheet where the viewer picks a track.

  • The button appears only while the stream is live (not upcoming or ended), only when the stream has more than one named audio track, and only in the full-screen player.
  • The sheet lists the tracks by the names set in the stream, in the stream's order. Tracks without a name, or with a name another track already uses, aren't listed.
  • The track the stream marks as default (DEFAULT=YES in the HLS manifest) is labeled "Main". If no track is marked, the first one is.
  • The viewer's pick is remembered by track name, across streams and app restarts. If a stream has no track with that name, its default track plays.
  • While casting to Chromecast, the button is hidden, an open sheet closes, and the receiver plays the stream's default audio.

The button is visible by default. To hide it:

val playerStyle = BlazeVideosPlayerStyle.base()
playerStyle.buttons.audio.isVisible = false
BlazeSDK.setDefaultVideosPlayerStyle(playerStyle)

Hiding the button also stops the SDK from applying a remembered track, so streams play their default audio.

Live catchup

While a stream is live and has a recap (a Story with the highlights so far), the full-screen player shows a recap button. A dot marks the button until the viewer opens that recap. Tapping the button opens the recap over the live stream, and closing the recap returns to the stream at the live edge.

📘

Live catchup is enabled per account by WSC Sports, which links the recaps to your live streams. It needs no code changes.

If the recap already exists when the viewer opens the stream, the player also suggests it, at most once each time the player opens:

  • A "Still time to catch up" popup with "Watch recap" and "Go to live", shown once per stream. The popup is enabled separately, per account, by WSC Sports.
  • Otherwise, when the popup is off or was already shown for this stream, a "Live recap" banner with "Watch recap", shown at most three times on the device.

A recap that becomes available while the viewer is already watching shows only the button. No catchup UI appears in picture-in-picture, or while the viewer's access to the stream is still being checked or has been denied (see Content entitlements).

The button is visible by default. To hide it:

val playerStyle = BlazeVideosPlayerStyle.base()
playerStyle.buttons.liveCatchup.isVisible = false
BlazeSDK.setDefaultVideosPlayerStyle(playerStyle)

Hiding the button hides only the button and its dot; the popup and the banner still appear. To turn off live catchup entirely, contact WSC Sports.

Stream tokenization

Stream tokenization protects your videos and live streams at the CDN, so a stream URL cannot be copied and played outside your app. See Stream tokenization for the concept.

📘

Stream tokenization is enabled per account by WSC Sports.

The only integration step is setting the viewer token, on init or through setExternalUserToken. Without it, no video or live stream plays on a tokenized account: the SDK raises onContentRestricted with the missing-token reason, and checks the item again once a token is set.

BlazeSDK.setExternalUserToken(viewerToken, BlazeFetchedContentPolicy.Evict)

Everything else is handled by the SDK, with no change to your widgets, players, or delegates. A stream the CDN refuses fails like any other stream that cannot load.

Related


Did this page help you?