Safari

PRESTOplay for web has dedicated support for Safari, automatically choosing the most appropriate player delegate for the stream format you’re playing.

HLS playback

For HLS streams on Safari, PRESTOplay uses a native player delegate that relies on Safari’s built-in HLS support through the <video> element. This is the default behavior and requires no extra configuration:

import { clpp } from "@castlabs/prestoplay";
import "@castlabs/prestoplay/cl.hls";

player.open({
  source: {
    url: 'https://example.com/stream.m3u8',
    type: clpp.Type.HLS,
  },
});

One strength of this default player is support for FairPlay-protected MPEG-TS streams, which the MSE player can’t provide due to limitations of the browser.

Using the MSE player for HLS

The MSE player can also play HLS on Safari — for example, when you need features only available through MSE-based playback. See Components for details on the MSE module. To opt in, set preferNativeHlsOnSafari to false:

import { clpp } from "@castlabs/prestoplay";
import "@castlabs/prestoplay/cl.hls";

player.open({
  source: {
    url: 'https://example.com/stream.m3u8',
    type: clpp.Type.HLS,
  },
  streaming: {
    preferNativeHlsOnSafari: false,
  },
});

DASH playback

For DASH streams on Safari, PRESTOplay uses the MSE player delegate, which requires the MSE and DASH modules:

import { clpp } from "@castlabs/prestoplay";
import "@castlabs/prestoplay/cl.mse";
import "@castlabs/prestoplay/cl.dash";

player.open({
  source: {
    url: 'https://example.com/stream.mpd',
    type: clpp.Type.DASH,
  },
});

Native HLS playback caveats

Native player delegate (HLS default) playback has some caveats due to limitations or known issues in Safari’s WebKit API.

IMA on iOS and tracks UI

On iOS, the IMA SDK runs in custom playback mode, so track objects are recreated when an ad break ends and playback resumes on the primary content.

Listen for clpp.events.TRACKS_REMOVED followed by clpp.events.TRACKS_ADDED to get notified.

Because tracks are recreated, your UI must reset its state at the start and end of each ad break. On AD_BREAK_STARTED, hide the track selection UI and clear any stale state. On AD_BREAK_STOPPED, read the current tracks from the track manager — PRESTOplay has restored them by this point — and show the UI again:

player.on(clpp.events.AD_BREAK_STARTED, () => {
  // Hide tracks UI
  trackSelectionUI.style.display = 'none';
  // Clear your stored tracks state
  resetTrackUI();
});

player.on(clpp.events.AD_BREAK_STOPPED, () => {
  // Track objects have changed — re-read data from the track manager
  // to populate your tracks state
  renderTrackUI(player.getAudioTracks(), player.getTextTracks());
  // Show tracks UI
  trackSelectionUI.style.display = '';
});

Audio tracks on iOS

Due to known WebKit issues (#301928, #297904) on iOS 26.4 and 26.3, audio tracks must be formatted in a specific way to work correctly.

Set AUTOSELECT=NO,DEFAULT=NO and use unintelligible language codes such as l1, l2, l3 in the HLS playlist:

#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio/mp4a.40.2",LANGUAGE="l1",NAME="English",AUTOSELECT=NO,DEFAULT=NO,CHANNELS="2",URI="audio-en-US-mp4a.40.2.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio/mp4a.40.2",LANGUAGE="l2",NAME="German",AUTOSELECT=NO,DEFAULT=NO,CHANNELS="2",URI="audio-de-DE-mp4a.40.2.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio/mp4a.40.2",LANGUAGE="l3",NAME="Spanish",AUTOSELECT=NO,DEFAULT=NO,CHANNELS="2",URI="audio-es-ES-mp4a.40.2.m3u8"

Then use the preferredAudioLanguage configuration option to select the default track, instead of relying on the AUTOSELECT and DEFAULT attributes:

player.open({
  source: '...',
  preferredAudioLanguage: ['l1'], // English
});

Lack of metadata about tracks

Safari doesn’t provide full metadata for tracks in general. See SafariConfiguration for options to enable richer track metadata with the native player delegate, or switch to the MSE player delegate for access to all playlist metadata.

Video track selection isn’t supported

Safari doesn’t support manual selection of video tracks or renditions (changing quality), so these APIs aren’t usable:

The native player delegate handles adaptive bitrate streaming (ABR) internally, so ABR-related configuration doesn’t apply either:

Switch to the MSE player delegate for full control over track selection and ABR.

Next topic: Smart TV
Previous topic: AirPlay