Skip to main content

When Multi-Key FairPlay Fails in Safari - a Shaka Player Issue and Its Workaround

· 7 min read
Senior Full Stack Engineer

Multi-key encryption is a DRM best practice, and we recommend it. On Safari with Shaka Player, it comes with one practical obstacle worth knowing about before you roll it out.

Multi-key FairPlay content can fail with Shaka error 3016, "Media failed to decode", while the same content encrypted with a single key plays without trouble. In the case we investigated, the cause lay in neither the packaging nor the license service. It came down to the order in which two Shaka polyfills were installed, and the fix was a few lines in the right place.

What you will see​

The failure has a distinctive shape:

  • Shaka error 3016, "Media failed to decode", often preceded by 6003 FAILED_TO_ATTACH_TO_VIDEO.
  • Shaka Player on Safari. Chrome, Edge and Firefox are unaffected.
  • Multi-key content only. Single-key FairPlay assets of the same codec play normally.
  • Not codec-specific. We saw identical behaviour with H.264 and H.265.

In our testing the affected content did not play at all. Reports upstream describe the same error appearing intermittently, so you may encounter either pattern.

Error 3016 is a generic decode error with several possible causes, so it is the combination above, rather than the code on its own, that points to this particular issue. If your content is single-key, if your users are not on Safari, or if you use a different player, this does not apply to you.

Why multi-key is the trigger​

With multi-key encryption you assign a separate content key to each quality tier and to audio, so a single playback session has to acquire several licenses rather than one. That is exactly what makes multi-key valuable: the license server can grant a device only the keys it is entitled to, as described in our DRM Security Best Practices guide.

It is also what exposes the problem.

Shaka handles FairPlay differently from every other DRM system it supports. An internal device check returns true for com.apple.fps and nothing else, and that makes Shaka skip its usual path of attaching the media keys up front. Instead it waits for an encrypted event from the video element, attaches the keys at that point, and then calls session.generateRequest() once per key without awaiting the result. Several license round-trips are therefore in flight at once, while the media keys are still being attached to the video element.

This is specific to Shaka rather than a limitation of Safari or of FairPlay. The same content plays in Shaka itself once the workaround below is in place, and the upstream report in shaka-player#9268 describes multi-key FairPlay assets playing in other HTML5 players on the same Safari. If Modern EME were simply unable to handle multi-key FairPlay, every player would fail on this content.

That path is fragile, and in our case it did not hold up. For a two-key asset only a single encrypted event ever reached the player, so only one license was requested and the second track was left without a key.

With a single key there is one session and one license, and nothing to miss. Adding keys is what exposes the problem.

Install the polyfill at module scope, not in your player setup​

Shaka documents the workaround. Its FairPlay tutorial states that "if your provider doesn't support Modern EME yet, you can use legacy Apple Media Keys" via shaka.polyfill.PatchedMediaKeysApple.install(). On the legacy API, keys arrive through webkitneedkey, one event per key, and the ordering problem disappears.

What the tutorial does not say is when to call it, and that turns out to matter a great deal.

PatchedMediaKeysApple.install() patches five browser APIs at once: window.MediaKeys, window.MediaKeySystemAccess, navigator.requestMediaKeySystemAccess, and HTMLMediaElement.prototype.setMediaKeys and mediaKeys. They only work as a set.

Meanwhile shaka.polyfill.installAll() installs a second polyfill, EmeEncryptionScheme, which swaps requestMediaKeySystemAccess for a probe wrapper. That wrapper reassigns the function a second time, asynchronously, on the first EME query rather than at install time.

If that asynchronous reassignment lands after the Apple polyfill has installed, you are left half-patched: requestMediaKeySystemAccess is back to the browser's native implementation while setMediaKeys is still the polyfill's. Natively created MediaKeys objects then reach the polyfill's setMediaKeys, which rejects them. That surfaces as 6003, and then 3016.

Reinstalling does not help, because install() returns early once window.shakaMediaKeysPolyfill is set.

The practical consequence: if you install the polyfill inside your player component's setup code, or anywhere an EME query may already have happened, it can be silently undone. Install it at module scope, before any player exists, so that both installs complete before your application can issue its first EME query.

One caveat on that: module scope orders these calls within your own import graph, not across the whole page. If anything else can query EME earlier, such as a second bundle or another microfrontend sharing the same window, confirm that it does not run before these installs.

The fix​

Install both polyfills at import time, in this order, before any player is constructed:

shaka.polyfill.installAll();
shaka.polyfill.PatchedMediaKeysApple.install(/* enableUninstall= */ true);

enableUninstall keeps PatchedMediaKeysApple.uninstall() available, as the tutorial recommends. Without it these global patches cannot be reversed for the lifetime of the page.

One further detail that is easy to miss: use a single Shaka bundle. Mixing shaka-player.compiled and shaka-player.ui in the same application creates two separate polyfill registries, and the fix is applied to only one of them.

The legacy API is a real trade-off, and it is worth knowing what you are giving up:

  • No MSE support, so this is mutually exclusive with streaming.useNativeHlsForFairPlay: false.
  • No license expiration. WebKitMediaKeySession does not expose it, so expiration is reported as NaN. Renewal still works, but anything driven off expiration values will not.
  • getStatusForPolicy() is stubbed to always return usable, so drm.minHdcpVersion becomes a silent no-op and HDCP support is over-reported on Safari.
  • Temporary sessions only, so persistent and offline FairPlay licenses are not available while the polyfill is installed.

Whether these matter depends on what your application does. If you rely on offline or persistent FairPlay licenses, on license expiration values, or on HDCP policy enforcement through drm.minHdcpVersion, they rule the workaround out.

Axinom's own Shaka test player has used this call for years, and plays the multi-key content that fails without it. You can use it to check your own content: see DRM video playback tools for how to set up a playback link.

What does not fix it, and who needs to act​

EXT-X-SESSION-KEY is not a fix. It is the first thing most teams reach for, so it is worth stating plainly: we tested multi-key playback with session keys both enabled and disabled, and the playback failure was identical. Session keys declare all of an asset's keys in the multivariant playlist so a player can preload them, which matters for offline and download scenarios. They do not change how a player applies those keys once it has them.

Chrome, Edge and Firefox need no action. PatchedMediaKeysApple.install() is a no-op unless window.WebKitMediaKeys exists, so adding it is safe in a cross-browser application and needs no browser detection of your own.

Customers using the Mosaic preview player need no action. The fix is already applied there.

If you run your own Shaka Player integration and serve multi-key FairPlay content, apply the change above.

Affected versions​

Upgrading alone does not resolve it. The issue is present in Shaka Player 5.2.12, the current release at the time of writing, and upstream reports describe it as far back as 4.11.

The issue has been raised upstream more than once, most recently in shaka-player#9268, but each report was closed without a diagnosis because no reproducible case was supplied.


For guidance on multi-key encryption and per-key usage policies, see our DRM Security Best Practices guide. For how multi-key fits into license server design more broadly, see DRM Beyond the Player.