GuidesAPI ReferenceRelease Notes
HomeLog InHome
Guides

Live streaming - iOS

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

public enum BlazeVideoContentType: String {
    case video = "Video"
    case stream = "Stream"
}

BlazeLiveStreamStatus is the lifecycle state of a stream (on-demand videos have no stream state):

public enum BlazeLiveStreamStatus: String {
    case live
    case upcoming
    case ended
}

Discovering and filtering streams

By default, the Videos API returns on-demand videos only (this preserves backwards compatibility). To include streams, pass a BlazeVideosFilterParams:

public struct BlazeVideosFilterParams {
    public let contentTypes: [BlazeVideoContentType]?
    public let streamStates: [BlazeLiveStreamStatus]?

    public init(contentTypes: [BlazeVideoContentType]? = nil,
                streamStates: [BlazeLiveStreamStatus]? = nil)
}
  • contentTypes — which content types to include (.video, .stream). When nil, only regular videos are returned.
  • streamStates — which stream states to include (.live, .upcoming, .ended). When nil, no stream-state filter is applied.
📘

Because contentTypes defaults to videos-only, you must include .stream to surface any live content.

On a widget

Set videosFilterParams on the Videos widget (BlazeVideosWidgetView for UIKit, or the SwiftUI widget view model):

widget.videosFilterParams = BlazeVideosFilterParams(
    contentTypes: [.stream],
    streamStates: [.live, .upcoming]
)

On the full-screen player

playVideos and prepareVideos on Blaze.shared accept videosFilterParams:

Blaze.shared.playVideos(
    dataSourceType: dataSource,
    videosFilterParams: BlazeVideosFilterParams(
        contentTypes: [.video, .stream],
        streamStates: [.live, .upcoming, .ended]
    )
)

Ordering streams

Ordering is configured on the data source (BlazeDataSourceType.labels / .ids) through orderType and advancedOrderType:

  • BlazeOrderType.startTimeDesc / .startTimeAsc — order by the content/stream start time (descending / ascending).
  • BlazeAdvancedOrderType.liveFirst — surface currently-live items first. It takes priority over orderType.
let dataSource = BlazeDataSourceType.labels(
    yourLabel, // your BlazeWidgetLabel
    orderType: .startTimeDesc,
    advancedOrderType: .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, the start time formatted for the user's locale, and a waiting-room message. The message text is set by BlazeVideosPlayerStyle.waitingRoomMessage (it has a localized default). 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-content 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 customizable through BlazeVideosPlayerStyle:

  • statusIndicator — the LIVE / UPCOMING / ENDED chip in the player. See BlazeVideosPlayerStatusIndicatorStyle.
  • backToLiveButton — the badge that reads "Live" at the live edge and "Back to Live" when behind live. See BlazeVideosPlayerBackToLiveButtonStyle.
  • waitingRoomMessage — the message shown in the upcoming-stream waiting room (a String on BlazeVideosPlayerStyle, with a localized default).
var playerStyle = BlazeVideosPlayerStyle.base()
playerStyle.statusIndicator.streamStates.liveStreamState.text = "ON AIR"
Blaze.shared.setDefaultVideosPlayerStyle(playerStyle)

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 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.
  • The selector keeps working during AirPlay. See AirPlay.

The button is visible by default. To hide it:

var playerStyle = BlazeVideosPlayerStyle.base()
playerStyle.buttons.audio.isVisible = false
Blaze.shared.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 starts watching 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 stream becomes the current video in the player, the player also suggests it, once per stream:

  • A "Still time to catch up" popup with "Watch recap" and "Go to live". The popup is enabled separately, per account, by WSC Sports.
  • Otherwise, when the popup is off, 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:

var playerStyle = BlazeVideosPlayerStyle.base()
playerStyle.buttons.liveCatchup.isVisible = false
Blaze.shared.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.

Customizing live UI at the widget level

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

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

Blaze.shared.setExternalUserToken(viewerToken, cachedContentPolicy: .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?