Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Offline storage silently failing on Safari #6228

Closed
gi11es opened this issue Feb 7, 2024 · 15 comments · Fixed by #5987
Closed

Offline storage silently failing on Safari #6228

gi11es opened this issue Feb 7, 2024 · 15 comments · Fixed by #5987
Labels
browser: Safari Issues affecting Safari or WebKit derivatives component: offline The issue involves the offline storage system of Shaka Player flag: seeking PR We are actively seeking PRs for this; we do not currently expect the core team will resolve this priority: P2 Smaller impact or easy workaround type: bug Something isn't working correctly
Milestone

Comments

@gi11es
Copy link

gi11es commented Feb 7, 2024

Have you read the FAQ and checked for duplicate open issues?

Yes

If the problem is related to FairPlay, have you read the tutorial?

Yes

What version of Shaka Player are you using?

4.7.9

Can you reproduce the issue with our latest release version?

Yes

Can you reproduce the issue with the latest code from main?

Haven't tried, but nothing in the 7 commits since the 4.7.9 tag touches the offline code.

Are you using the demo app or your own custom app?

Custom app

If custom app, can you reproduce the issue using our demo app?

The demo app doesn't support custom response rewriting without modifying the code, which is necessary for the DRM provider I'm using (PallyCon).

What browser and OS are you using?

MacOS 14.2.1 Safari

For embedded devices (smart TVs, etc.), what model and firmware version are you using?

n/a

What are the manifest and license server URIs?

See below, I'll share the entire repro case

What configuration are you using? What is the output of player.getConfiguration()?

See below, I'll share the entire repro case

What did you do?

Here's the entire code to repro this. It will attempt to put a random video from our catalogue into offline storage and play it back.

let currentResolution = '720p';
let currentContentId = 'layer';
let currentAssetid = false;
let keySystem = '';

async function checkDRMSupport(drmType) {
  try {
    if ('requestMediaKeySystemAccess' in navigator) {
      const config = [{
        initDataTypes: ['cenc'],
        audioCapabilities: [{contentType: 'audio/mp4;codecs="mp4a.40.2"'}],
        videoCapabilities: [{contentType: 'video/mp4;codecs="avc1.42E01E"'}]
      }];
      await navigator.requestMediaKeySystemAccess(drmType, config);
      console.log(drmType + ' is supported.');
      return true;
    } else {
      console.log(drmType + ' is not supported');
      return false;
    }
  } catch (error) {
    console.log(drmType + ' is not supported: ' + error.message);
    return false;
  }
}

async function preparePlayer(player) {
  const req = await fetch('https://license-global.pallycon.com/ri/fpsCert.do?siteId=TFEI');
  const cert = await req.arrayBuffer();

  player.configure({
    drm: {
      servers: {
        'com.widevine.alpha': 'https://nmfpoe4tycbo56gh6qsejeo3s40rsgml.lambda-url.us-east-2.on.aws/?drmType=Widevine',
        'com.apple.fps': 'https://nmfpoe4tycbo56gh6qsejeo3s40rsgml.lambda-url.us-east-2.on.aws/?drmType=FairPlay',
        'com.microsoft.playready': 'https://nmfpoe4tycbo56gh6qsejeo3s40rsgml.lambda-url.us-east-2.on.aws/?drmType=PlayReady'
      },
      advanced: {
        'com.apple.fps': {
          serverCertificate: new Uint8Array(cert)
        }
      },
      preferredKeySystems: [
        'com.apple.fps',
        // PlayReady must come before Widevine, for Windows, so it can leverage hardware protection
        'com.microsoft.playready.recommendation.3000',
        'com.microsoft.playready.recommendation', 
        'com.microsoft.playready', 
        'com.widevine.alpha'
      ],
      keySystemsMapping: {
        'com.microsoft.playready': 'com.microsoft.playready.recommendation.3000',
      }
    }
  });

  player.getNetworkingEngine().registerRequestFilter(async function (type, request) {
    console.log('Request filter: ' + type, request);
    console.log('Player key system: ' + keySystem);

    if (type == shaka.net.NetworkingEngine.RequestType.LICENSE) {
      request.headers['layer-auth-token'] = 'to-be-implemented-later';
      request.headers['layer-content-id'] = currentContentId;

      if (keySystem === 'com.apple.fps') {
        const originalPayload = new Uint8Array(request.body);
        const base64Payload = shaka.util.Uint8ArrayUtils.toBase64(originalPayload);
        const params = 'spc=' + encodeURIComponent(base64Payload);

        request.body = shaka.util.StringUtils.toUTF8(params);
        request.headers['Content-Type'] = 'application/x-www-form-urlencoded';
      }
    }
  });

  player.getNetworkingEngine().registerResponseFilter(function (type, response) {
    console.log('Response filter: ' + type, response);
    console.log('Player key system: ' + keySystem);

    if (type == shaka.net.NetworkingEngine.RequestType.LICENSE) {
      if (keySystem === 'com.apple.fps') {
        const responseText = shaka.util.StringUtils.fromUTF8(response.data).trim();
        response.data = shaka.util.Uint8ArrayUtils.fromBase64(responseText).buffer;
      }
    }
  });

  window.storage = new shaka.offline.Storage(player);
  window.storage.configure({
    offline: {
      usePersistentLicense: false,
      trackSelectionCallback: function(trackList) {
        console.log('trackSelectionCallback', trackList);
        return trackList;
      },
      downloadSizeCallback: function (size) {
        console.log('downloadSizeCallback', size);
        return true;
      }
    }
  });
}

async function playOffline(player, manifestURI) {
  let alreadyStored = false;

  await window.storage.list().then(function(content) {
    console.log('Stored offline content', content);
    content.forEach(function(line) {
      if (line.originalManifestUri == manifestURI) {
        alreadyStored = true;
      }
    });
  });
  
  if (!alreadyStored) {
    console.log('URI not already stored, storing now', manifestURI);
    await window.storage.store(manifestURI).promise;
    console.log('URI stored', manifestURI);
    // For some reason after offline storage the first play attempt seems to be a noop. Shaka bug perhaps?
    await playOffline(player, manifestURI);
    console.log('Pretended to play, now try again');
  }

  let offlineURIList = [];

  await window.storage.list().then(function(content) {
    console.log('Stored offline content', content);
    content.forEach(function(line) {
      // Offline storage will keep things from previous code iterations as well
      // We only want offline manifests that correspond to the current target manifest URI
      if (line.originalManifestUri == manifestURI) {
        offlineURIList.push(line.offlineUri);
      }
    });
  });

  console.log('Found offline content, play it', offlineURIList[0]);

  player.load(offlineURIList[0]);
}

async function getNewAssetId(resolution, codec) {
  const url = 'https://ozrp72erlzy6hdxdhgrmg7nrfq0vjjys.lambda-url.us-east-2.on.aws/';
  let request = '?resolution=' + resolution + '&codec=' + codec;
  if (currentAssetid) {
    request += '&assetid=' + currentAssetid;
  }

  const response = await fetch(url + request);
  currentAssetid = await response.text();
}

async function playDASHVP9(player) {
  await getNewAssetId(currentResolution, 'vp9');

  try {
    await playOffline(player, 'https://layer-aws-encrypted.b-cdn.net/' + currentAssetid + '/' + currentResolution +  '/vp9-q4.mp4/cmaf/stream.mpd');
  } catch (error) {
    console.error('Error code', error.code, 'object', error);
  }
}

async function playDASHHEVC(player) {
  await getNewAssetId(currentResolution, 'hevc');

  try {
    await playOffline(player, 'https://layer-aws-encrypted.b-cdn.net/' + currentAssetid + '/' + currentResolution +  '/hevc-q4.mp4/cmaf/stream.mpd');
  } catch (error) {
    console.error('Error code', error.code, 'object', error);
  }
}

async function playHLSHEVC(player) {
  await getNewAssetId(currentResolution, 'hevc');

  try {
    await playOffline(player, 'https://layer-aws-encrypted.b-cdn.net/' + currentAssetid + '/' + currentResolution +  '/hevc-q4.mp4/cmaf/master.m3u8');
  } catch (error) {
    console.error('Error code', error.code, 'object', error);
  }
}

async function play(player) {
  if (await checkDRMSupport('com.microsoft.playready')) {
    keySystem = 'com.microsoft.playready';
    return playDASHHEVC(player);
  } else if (await checkDRMSupport('com.apple.fps')) {
    keySystem = 'com.apple.fps';
    return playHLSHEVC(player);
  } else if (await checkDRMSupport('com.widevine.alpha')) {
    keySystem = 'com.widevine.alpha';
    return playDASHVP9(player);
  }
}

function onErrorEvent(event) {
  console.error('Error code', event.detail.code, 'object', event.detail);
}

async function init(domElement) {
  window.player = new shaka.Player();
  await window.player.attach(domElement);
  window.player.addEventListener('error', onErrorEvent);
  await preparePlayer(window.player);

  await play(window.player);
}

async function fetchAndGo() {
  const videoElement = document.querySelectorAll('.videoPlayer')[0];
  shaka.log.setLevel(shaka.log.Level.DEBUG);
  shaka.polyfill.installAll();

  if (shaka.Player.isBrowserSupported()) {
    await init(videoElement);
  } else {
    console.error('Browser not supported!');
  }
}

document.addEventListener('DOMContentLoaded', fetchAndGo);

What did you expect to happen?

The request manifest and streams are stored offline

What actually happened?

Execution gets stuck on:

await window.storage.store(manifestURI).promise;

By tracing execution with breakpoints, I was able to track it down to the following async call seemingly never completing:

const ids = await activeHandle.cell.addManifests([manifestDB]);

It seems to be the first attempt to store something in IndexedDB.

There are no errors, execution just stays stuck there.

@gi11es gi11es added the type: bug Something isn't working correctly label Feb 7, 2024
@shaka-bot shaka-bot added this to the v5.0 milestone Feb 7, 2024
@theodab
Copy link
Collaborator

theodab commented Feb 22, 2024

When using your sample code, I actually am hanging on an earlier part of the storage process on Safari. Specifically, the part where the DRM engine is created:

drmEngine = await this.createDrmEngine(
manifest,
(e) => { drmError = drmError || e; },
config);

So I can't confirm that that line you highlighted in specific is the problem. That aside...
According to our FAQ, we do not currently support persistent licenses (e.g. storing DRM content) on Safari:

Currently, persistent licenses are supported on Chromebooks and Chrome 64+ on Mac/Windows. On other platforms, you can only store clear content or store only the content offline (i.e. set usePersistentLicense configuration to false).

So I don't think this is actually a bug, per se. Though we could probably fail in a more informative manner than having a promise hang without resolving...

@gi11es
Copy link
Author

gi11es commented Feb 22, 2024

Persisted licenses and offline storage are two different things. You can use offline storage without persisted licenses. Since Safari supports IndexedDb, it should be compatible with offline storage without persisted licenses.

@gi11es
Copy link
Author

gi11es commented Feb 22, 2024

We’ve recently changed our DRM proxy server to do the base64 transformations for FairPlay so we don’t need the Shaka request and response filters anymore, which is probably why you ran into this issue with running this sample code. I’ll post a newer version of the code here shortly.

@gi11es
Copy link
Author

gi11es commented Feb 22, 2024

Here's an updated html+js page that reproduces the issue. The code is cross-platform, so you can see offline storage working correctly on Chrome and Edge and hanging at the first storage attempt on Safari.

If you uncomment the code section that talks about this github issue, you basically avoid using offline storage in Safari.

<html>
<head>
<title>Shaka issue 6228 repro case</title>
<style>
.collapsed {
  height: 0;
}
.expanded {
  height: 100%;
}
.videoPlayer {
  width: 100%;
}
#videoPlaceholder {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
}
#videoContainer {
  width: 720px;
  height: 720px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
body {
  margin: 0;
  background-color: #000;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/shaka-player@4.7.9/dist/shaka-player.compiled.debug.js"></script>
<script>
class LayerOfflinePlayer {
  constructor(authToken = '', resolution = '720p', contentId = 'layer') {
    this.resolution = resolution;
    this.contentId = contentId;
    this.assetId = false;
    this.authToken = authToken;
    this.ready = false;
    this.shakaPlayers = []; 
    this.storage = false;
    this.codec = 'hevc';
    this.manifest = 'stream.mpd';

    shaka.log.setLevel(shaka.log.Level.DEBUG);
    shaka.polyfill.installAll();

    if (!shaka.Player.isBrowserSupported()) {
      console.error('Browser not supported!');
      return;
    }

    

    this.ready = true;
  }

  async checkDRMSupport(drmType) {
    let codec = 'avc1.42E01E';

    if (drmType == 'com.widevine.alpha') {
      codec = 'vp09.00.10.08';
    }

    try {
      if ('requestMediaKeySystemAccess' in navigator) {
        const config = [{
          initDataTypes: ['cenc'],
          videoCapabilities: [{contentType: 'video/mp4;codecs="' + codec + '"'}],
        }];
        await navigator.requestMediaKeySystemAccess(drmType, config);
        console.log(drmType + ' is supported.');
        return true;
      } else {
        console.log(drmType + ' is not supported');
        return false;
      }
    } catch (error) {
      console.log(drmType + ' is not supported: ' + error.message);
      return false;
    }
  }

  async preparePlayer(player) {
    const drmTokenLambda = 'https://nmfpoe4tycbo56gh6qsejeo3s40rsgml.lambda-url.us-east-2.on.aws/?drmType=';

    player.configure({
      drm: {
        servers: {
          'com.widevine.alpha': drmTokenLambda + 'Widevine',
          'com.apple.fps': drmTokenLambda + 'FairPlay',
          'com.microsoft.playready': drmTokenLambda + 'PlayReady'
        },
        advanced: {
          'com.apple.fps': {
            serverCertificateUri: 'https://license-global.pallycon.com/ri/fpsCert.do?siteId=TFEI'
          }
        },
        preferredKeySystems: [
          'com.apple.fps',
          // PlayReady must come before Widevine, for Windows, so it can leverage hardware protection
          'com.microsoft.playready.recommendation.3000',
          'com.microsoft.playready.recommendation', 
          'com.microsoft.playready', 
          'com.widevine.alpha'
        ],
        keySystemsMapping: {
          'com.microsoft.playready': 'com.microsoft.playready.recommendation.3000',
        }
      }
    });

    if (await this.checkDRMSupport('com.microsoft.playready')) {
      this.keySystem = 'com.microsoft.playready';
    } else if (await this.checkDRMSupport('com.apple.fps')) {
      this.keySystem = 'com.apple.fps';
      this.manifest = 'master.m3u8'; // HLS
    } else if (await this.checkDRMSupport('com.widevine.alpha')) {
      this.keySystem = 'com.widevine.alpha';
      this.codec = 'vp9';
    }

    if (this.storage) {
      return;
    }

    this.storage = new shaka.offline.Storage(player);
    this.storage.configure({
      offline: {
        usePersistentLicense: false,
        trackSelectionCallback: function(trackList) {
          console.log('trackSelectionCallback', trackList);
          return trackList;
        },
        downloadSizeCallback: function (size) {
          console.log('downloadSizeCallback', size);
          return true;
        }
      }
    });
  }

  async getNewAssetId() {
    const nextAssetIdLambdaURI = 'https://ozrp72erlzy6hdxdhgrmg7nrfq0vjjys.lambda-url.us-east-2.on.aws/';

    let request = '?resolution=' + this.resolution + '&codec=' + this.codec;
    if (this.assetId) {
      request += '&assetid=' + this.assetId;
    }

    const response = await fetch(nextAssetIdLambdaURI + request);
    this.assetId = await response.text();
  }

  async play(player) {
    if (!this.ready) {
      console.error('Player not ready!');
      return;
    }

    try {
      await player.load(await this.getPlaybackURI(player));
    } catch (error) {
      console.error('Error code', error.code, 'object', error);
    }
  }

  async getPlaybackURI(player) {
    let uri = 'https://layer-aws-encrypted.b-cdn.net/' + this.assetId + '/' + this.resolution +  '/' + this.codec + '-q4.mp4/cmaf/' + this.manifest;
  
    // Shaka bug https://github.com/shaka-project/shaka-player/issues/6228
    // Don't try to use offline storage for FairPlay
    /* if (this.keySystem === 'com.apple.fps') {
      return uri;
    } */

    let alreadyStored = false;

    await this.storage.list().then(function(content) {
      console.log('Stored offline content', content);
      content.forEach(function(line) {
        if (line.originalManifestUri == uri) {
          alreadyStored = true;
        }
      });
    });
    
    if (!alreadyStored) {
      console.log('URI not already stored, storing now', uri);
      await this.storage.store(uri).promise;
      console.log('URI stored', uri);
      player.getMediaElement().play();
    }

    let offlineURIList = [];

    await this.storage.list().then(function(content) {
      console.log('Stored offline content', content);
      content.forEach(function(line) {
        // Offline storage will keep things from previous code iterations as well
        // We only want offline manifests that correspond to the current target manifest URI
        if (line.originalManifestUri == uri) {
          offlineURIList.push(line.offlineUri);
        }
      });
    });

    if (offlineURIList.length) {
      console.log('Found offline content, play it', offlineURIList[0]);
      return offlineURIList[0];
    }

    return uri;
  }

  onErrorEvent(event) {
    console.error('Error code', event.detail.code, 'object', event.detail);
  }

  async initPlayer(videoElement) {
    let player = new shaka.Player();
    this.shakaPlayers.push(player);
    await player.attach(videoElement);
    player.addEventListener('error', this.onErrorEvent);
    await this.preparePlayer(player);
    await this.getNewAssetId();
    await this.play(player);
  }
}

async function switchArtwork() {
  const firstPlayer = document.querySelectorAll('.videoPlayer')[0];
  const secondPlayer = document.querySelectorAll('.videoPlayer')[1];
  let backgroundShakaPlayer = this.shakaPlayers[0];

  if (firstPlayer.classList.contains('collapsed')) {
    firstPlayer.currentTime = 0;
    firstPlayer.classList.remove('collapsed');
    firstPlayer.classList.add('expanded');
    secondPlayer.classList.remove('expanded');
    secondPlayer.classList.add('collapsed');
    backgroundShakaPlayer = this.shakaPlayers[1];
  } else {
    firstPlayer.classList.remove('expanded');
    firstPlayer.classList.add('collapsed');
    secondPlayer.currentTime = 0;
    secondPlayer.classList.remove('collapsed');
    secondPlayer.classList.add('expanded');
  }

  console.log('Picking a new asset id');
  await this.getNewAssetId();
  console.log('Play it');
  await this.play(backgroundShakaPlayer);
}

document.addEventListener('DOMContentLoaded', async function() {
  const firstPlayer = document.querySelectorAll('.videoPlayer')[0];
  const secondPlayer = document.querySelectorAll('.videoPlayer')[1];

  const layerPlayer = new LayerOfflinePlayer('auth-token');

  if (!layerPlayer.ready) {
    return;
  }

  await layerPlayer.initPlayer(firstPlayer);

  const eventManager = new shaka.util.EventManager();
  eventManager.listenOnce(firstPlayer, 'timeupdate', () => {
    document.getElementById('videoPlaceholder').style.display = 'none';
    firstPlayer.classList.remove('collapsed');
    firstPlayer.classList.add('expanded');
  });

  setInterval(switchArtwork.bind(layerPlayer), 30000);

  await layerPlayer.initPlayer(secondPlayer);
});
</script>
</head>
<body>
  <div id="videoContainer">
    <div id="videoPlaceholder"><img src="loading.gif"></div>
    <video class="videoPlayer collapsed" autoplay loop muted playsinline></video>
    <video class="videoPlayer collapsed" autoplay loop muted playsinline></video>
  </div>
</body>
<script>
const videoContainer = document.getElementById('videoContainer');
videoContainer.addEventListener('dblclick', toggleFullscreen);

function toggleFullscreen() {
  if (document.fullscreenElement) {
    // If there is an element in full-screen mode, exit full-screen mode
    document.exitFullscreen();
  } else {
    // If no element is in full-screen mode, enter full-screen mode
    videoContainer.requestFullscreen();
  }
}
</script>
</html>

@theodab
Copy link
Collaborator

theodab commented Feb 23, 2024

I'm getting the same result with that code; the drm engine fails to load, and we never even get to that call to activeHandle.cell.addManifests.

Looking into the setup process of the drm engine, it looks like the loading process gets confused by the empty init data we generate for com.apple.fps content. If I change the drm engine to not count those init datas, the storage process actually then goes on to successfully store the asset... and then fail to play it, giving a LICENSE_RESPONSE_REJECTED error.

That change is presumably not a fix for this problem though. At no point have I managed to reproduce the issue you are reporting, and you haven't reported seeing the issue I am experiencing either. I am testing this on Safari on macOS 13.6.4, so perhaps there could be platform differences at play?

@gi11es
Copy link
Author

gi11es commented Feb 23, 2024

I'm using Safari Version 17.2.1 (19617.1.17.11.12) on MacOS 14.2.1 (23C71)

@joeyparrish joeyparrish added flag: seeking PR We are actively seeking PRs for this; we do not currently expect the core team will resolve this priority: P2 Smaller impact or easy workaround browser: Safari Issues affecting Safari or WebKit derivatives component: offline The issue involves the offline storage system of Shaka Player labels Feb 23, 2024
@joeyparrish
Copy link
Member

To start, we should never be failing silently. Thank you for your report.

As an aside, I see a couple small change you can/should make to your code to optimize a little. You don't need to list the stored contents again to get the offline URI. Metadata of the stored content is returned to you after the storage operation. You can also use for-of instead of forEach to return early without iterating through each item. So this:

    let alreadyStored = false;

    await this.storage.list().then(function(content) {
      console.log('Stored offline content', content);
      content.forEach(function(line) {
        if (line.originalManifestUri == uri) {
          alreadyStored = true;
        }
      });
    });
    
    if (!alreadyStored) {
      console.log('URI not already stored, storing now', uri);
      await this.storage.store(uri).promise;
      console.log('URI stored', uri);
      player.getMediaElement().play();
    }

    let offlineURIList = [];

    await this.storage.list().then(function(content) {
      console.log('Stored offline content', content);
      content.forEach(function(line) {
        // Offline storage will keep things from previous code iterations as well
        // We only want offline manifests that correspond to the current target manifest URI
        if (line.originalManifestUri == uri) {
          offlineURIList.push(line.offlineUri);
        }
      });
    });

    if (offlineURIList.length) {
      console.log('Found offline content, play it', offlineURIList[0]);
      return offlineURIList[0];
    }

    return uri;

Could simplify to this:

    const items = await this.storage.list();
    console.log('Stored offline content', items);
    for (const item of items) {
        if (item.originalManifestUri == uri) {
          return item.offlineUri;
        }
    }

    console.log('URI not already stored, storing now', uri);
    const newItem = await this.storage.store(uri).promise;
    console.log('URI stored', uri, 'as', newItem.offlineUri);
    return newItem.offlineUri;

I don't expect this will change the shape of your problem, but could you try it and confirm that you still see storage.store() hanging?

Thanks!

@gi11es
Copy link
Author

gi11es commented Feb 23, 2024

I've updated the code to use your snippet, you can test it live here: https://layerproject.github.io/shaka-predownload-pallycon.html

Here's a screen recording of the issue experienced in Safari: https://drive.google.com/file/d/1r6iHcL3oBzBUuQr17TqziQFY8F0aA9vN/view?usp=sharing

@theodab
Copy link
Collaborator

theodab commented Feb 24, 2024

Even with that uploaded code snippet, I still see it hanging on the DRM engine creation.
I'll update the OS on my macbook and try again, see if it changes things.

theodab added a commit to theodab/shaka-player that referenced this issue Feb 24, 2024
@theodab
Copy link
Collaborator

theodab commented Feb 24, 2024

Oh, right, my macbook is too old to run macOS Sonoma.
I can look into getting a new one from my work, I guess.

In the meantime, I uploaded the change I mentioned that fixed the problem I was running into, just in case.

@theodab
Copy link
Collaborator

theodab commented Mar 12, 2024

I got a new work MacBook that can run MacOS 14.4, and I am still seeing your demo hang on the creation of the DRM engine rather than the line you highlighted. That suggests this isn't a platform-specific issue.
I'll check how this looks with #6292 once I finish setting this laptop up.

@gi11es
Copy link
Author

gi11es commented Mar 12, 2024

I saw your access to our system in the logs earlier today and you only hit our DRM proxy URL with Widevine as the requested DRM type, FYI.

Here's an updated version of our self-contained repro case: https://layerproject.github.io/shaka-issue-6228-repro.html please use that one for further testing, as the old code will fire some alerts on our system.

For our own project we gave up on Shaka's offline storage for now, instead we switched to using a service worker of our own. It works great, including on Safari.

@theodab
Copy link
Collaborator

theodab commented Mar 15, 2024

I opened the page in Chrome once by accident that day, and when loading the page on Safari it doesn't even get to requesting DRM info, which probably is why you are only seeing a Widevine request. According to the info logs it has chosen com.apple.fps on Safari.

@gi11es
Copy link
Author

gi11es commented Mar 15, 2024

Yes, that's correct, it's the same behaviour I'm seeing. The last log message is Created MediaKeys object for key system"com.apple.fps" and no request to our servers is made.

@avelad
Copy link
Collaborator

avelad commented Apr 22, 2024

@gi11es I've tried our FairPlay example from the Demo, and I've modified your url to include https://nightly-dot-shaka-player-demo.appspot.com/dist/shaka-player.compiled.debug.js and everything works correctly. This week we are going to release 4.8.0 that includes all the main changes, so the problem would be resolved.

Note: I used Safari 17.3 (19617.2.4.11.8)

@avelad avelad closed this as completed Apr 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
browser: Safari Issues affecting Safari or WebKit derivatives component: offline The issue involves the offline storage system of Shaka Player flag: seeking PR We are actively seeking PRs for this; we do not currently expect the core team will resolve this priority: P2 Smaller impact or easy workaround type: bug Something isn't working correctly
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants