@mottosports/motto-video-player 1.0.1-rc.36 → 1.0.1-rc.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -26,11 +26,13 @@ interface Playready {
26
26
  }
27
27
  interface DRM {
28
28
  token?: string;
29
+ licenseCacheKey?: string;
29
30
  widevine?: Widevine;
30
31
  fairplay?: Fairplay;
31
32
  playready?: Playready;
32
33
  }
33
34
  interface Playlist {
35
+ id: string;
34
36
  url: string;
35
37
  format: string;
36
38
  dvr_settings?: DVRSettings;
package/dist/index.d.ts CHANGED
@@ -26,11 +26,13 @@ interface Playready {
26
26
  }
27
27
  interface DRM {
28
28
  token?: string;
29
+ licenseCacheKey?: string;
29
30
  widevine?: Widevine;
30
31
  fairplay?: Fairplay;
31
32
  playready?: Playready;
32
33
  }
33
34
  interface Playlist {
35
+ id: string;
34
36
  url: string;
35
37
  format: string;
36
38
  dvr_settings?: DVRSettings;
package/dist/index.js CHANGED
@@ -1147,7 +1147,7 @@ styleInject(`/*! tailwindcss v4.1.8 | MIT License | https://tailwindcss.com */
1147
1147
  var import_react12 = require("react");
1148
1148
  var import_shaka_player4 = __toESM(require("shaka-player/dist/shaka-player.ui"));
1149
1149
 
1150
- // src/hooks/useShakePlayer.ts
1150
+ // src/hooks/useShakaPlayer.ts
1151
1151
  var import_react = require("react");
1152
1152
  var import_shaka_player = __toESM(require("shaka-player/dist/shaka-player.ui"));
1153
1153
 
@@ -1171,17 +1171,186 @@ var isPlayReadySupported = () => {
1171
1171
  const isXbox = /Xbox/.test(userAgent);
1172
1172
  const isEdge = /Edg/.test(userAgent);
1173
1173
  const isIE = /Trident|MSIE/.test(userAgent);
1174
- return isXbox || isEdge || isIE;
1174
+ const isWindows = /Windows/.test(userAgent);
1175
+ return isXbox || (isEdge || isIE) && isWindows;
1176
+ };
1177
+ var isChrome64PlusOnMacOrWindows = () => {
1178
+ if (typeof navigator === "undefined") {
1179
+ return false;
1180
+ }
1181
+ const userAgent = navigator.userAgent || "";
1182
+ const isChromeMatch = userAgent.match(/Chrome\/(\d+)/);
1183
+ if (!isChromeMatch) {
1184
+ return false;
1185
+ }
1186
+ if (/Edg|OPR|Opera/.test(userAgent)) {
1187
+ return false;
1188
+ }
1189
+ const chromeVersion = parseInt(isChromeMatch[1], 10);
1190
+ if (chromeVersion < 64) {
1191
+ return false;
1192
+ }
1193
+ const isMacOS = /Mac OS X|Macintosh/.test(userAgent);
1194
+ const isWindows = /Windows/.test(userAgent);
1195
+ return isMacOS || isWindows;
1175
1196
  };
1176
1197
 
1177
- // src/hooks/useShakePlayer.ts
1198
+ // src/hooks/useShakaPlayer.ts
1178
1199
  var import_mux_data_shakaplayer = __toESM(require("@mux/mux-data-shakaplayer"));
1179
1200
 
1180
1201
  // package.json
1181
- var version = "1.0.1-rc.36";
1202
+ var version = "1.0.1-rc.38";
1203
+
1204
+ // src/utils/licenseCache.ts
1205
+ var PERSISTENT_LICENSE_PREFIX = "motto_lic_";
1206
+ var LICENSE_EXPIRY_MS = 7 * 24 * 60 * 60 * 1e3;
1207
+ var LICENSE_MAX_RETRY_ATTEMPTS = 10;
1208
+ var getAllLicenseCacheKeys = () => {
1209
+ const keys = [];
1210
+ for (let i = 0; i < localStorage.length; i++) {
1211
+ const key = localStorage.key(i);
1212
+ if (key?.startsWith(PERSISTENT_LICENSE_PREFIX)) {
1213
+ keys.push(key);
1214
+ }
1215
+ }
1216
+ return keys;
1217
+ };
1218
+ var getAllLicenseCacheEntries = () => {
1219
+ const keys = getAllLicenseCacheKeys();
1220
+ const entries = [];
1221
+ for (const key of keys) {
1222
+ try {
1223
+ const stored = localStorage.getItem(key);
1224
+ if (stored) {
1225
+ const data = JSON.parse(stored);
1226
+ entries.push({ key, data });
1227
+ }
1228
+ } catch (error) {
1229
+ console.warn(`Failed to parse license cache entry for key ${key}:`, error);
1230
+ localStorage.removeItem(key);
1231
+ }
1232
+ }
1233
+ return entries;
1234
+ };
1235
+ var evictLRUEntry = () => {
1236
+ const entries = getAllLicenseCacheEntries();
1237
+ if (entries.length === 0) {
1238
+ return false;
1239
+ }
1240
+ entries.sort((a, b) => {
1241
+ const oldestA = Math.min(...a.data.map((session) => session.timestamp));
1242
+ const oldestB = Math.min(...b.data.map((session) => session.timestamp));
1243
+ return oldestA - oldestB;
1244
+ });
1245
+ const lruEntry = entries[0];
1246
+ localStorage.removeItem(lruEntry.key);
1247
+ console.log(`Evicted LRU license cache entry: ${lruEntry.key}`);
1248
+ return true;
1249
+ };
1250
+ var storeWithQuotaHandling = (key, data) => {
1251
+ let attempts = 0;
1252
+ while (attempts < LICENSE_MAX_RETRY_ATTEMPTS) {
1253
+ try {
1254
+ localStorage.setItem(key, data);
1255
+ return true;
1256
+ } catch (error) {
1257
+ if (error instanceof DOMException && (error.code === 22 || // QUOTA_EXCEEDED_ERR
1258
+ error.name === "QuotaExceededError" || error.name === "NS_ERROR_DOM_QUOTA_REACHED")) {
1259
+ console.warn(`QuotaExceededError on attempt ${attempts + 1}, attempting LRU eviction...`);
1260
+ if (!evictLRUEntry()) {
1261
+ console.error("No more entries to evict, storage operation failed");
1262
+ return false;
1263
+ }
1264
+ attempts++;
1265
+ } else {
1266
+ console.error("Failed to store license cache data:", error);
1267
+ return false;
1268
+ }
1269
+ }
1270
+ }
1271
+ console.error(`Failed to store license cache data after ${LICENSE_MAX_RETRY_ATTEMPTS} eviction attempts`);
1272
+ return false;
1273
+ };
1274
+ var managePersistentLicenseStorage = (playlistId, licenseCacheKey, newSessionMetadata) => {
1275
+ try {
1276
+ const storageKey = `${PERSISTENT_LICENSE_PREFIX}${playlistId}_${licenseCacheKey}`;
1277
+ const stored = localStorage.getItem(storageKey);
1278
+ let existingSessions = [];
1279
+ if (stored) {
1280
+ try {
1281
+ existingSessions = JSON.parse(stored);
1282
+ } catch (parseError) {
1283
+ console.warn("Failed to parse existing persistent license data:", parseError);
1284
+ existingSessions = [];
1285
+ }
1286
+ }
1287
+ const now = Date.now();
1288
+ let validSessions = existingSessions.filter(
1289
+ (session) => now - session.timestamp < LICENSE_EXPIRY_MS
1290
+ );
1291
+ if (newSessionMetadata && newSessionMetadata.length > 0) {
1292
+ const newSessions = newSessionMetadata.map((session) => {
1293
+ const uint8Array = new Uint8Array(session.initData);
1294
+ const binaryString = Array.from(uint8Array).map((byte) => String.fromCharCode(byte)).join("");
1295
+ return {
1296
+ sessionId: session.sessionId,
1297
+ initData: btoa(binaryString),
1298
+ initDataType: session.initDataType,
1299
+ keySystem: session.keySystem,
1300
+ timestamp: now
1301
+ };
1302
+ });
1303
+ const newSessionIds = new Set(newSessions.map((s) => s.sessionId));
1304
+ validSessions = validSessions.filter((session) => !newSessionIds.has(session.sessionId));
1305
+ validSessions = [...validSessions, ...newSessions];
1306
+ }
1307
+ if (validSessions.length === 0) {
1308
+ localStorage.removeItem(storageKey);
1309
+ } else {
1310
+ const dataToStore = JSON.stringify(validSessions);
1311
+ const success = storeWithQuotaHandling(storageKey, dataToStore);
1312
+ if (!success) {
1313
+ console.error(`Failed to store license cache for ${storageKey} after quota handling`);
1314
+ return validSessions;
1315
+ }
1316
+ }
1317
+ return validSessions;
1318
+ } catch (error) {
1319
+ console.warn("Failed to manage persistent license storage:", error);
1320
+ return [];
1321
+ }
1322
+ };
1323
+ var storePersistentLicense = (playlistId, licenseCacheKey, sessionMetadata) => {
1324
+ managePersistentLicenseStorage(playlistId, licenseCacheKey, sessionMetadata);
1325
+ };
1326
+ var retrievePersistentLicense = (playlistId, licenseCacheKey) => {
1327
+ try {
1328
+ const validSessions = managePersistentLicenseStorage(playlistId, licenseCacheKey);
1329
+ return validSessions.map((session) => ({
1330
+ sessionId: session.sessionId,
1331
+ initData: Uint8Array.from(atob(session.initData), (c) => c.charCodeAt(0)).buffer,
1332
+ initDataType: session.initDataType,
1333
+ keySystem: session.keySystem
1334
+ }));
1335
+ } catch (error) {
1336
+ console.warn("Failed to retrieve persistent license:", error);
1337
+ return [];
1338
+ }
1339
+ };
1340
+ var clearAllPersistentLicenses = () => {
1341
+ try {
1342
+ const keys = getAllLicenseCacheKeys();
1343
+ for (const key of keys) {
1344
+ localStorage.removeItem(key);
1345
+ }
1346
+ console.log(`Cleared ${keys.length} persistent license cache entries`);
1347
+ } catch (error) {
1348
+ console.error("Failed to clear persistent licenses:", error);
1349
+ }
1350
+ };
1182
1351
 
1183
- // src/hooks/useShakePlayer.ts
1184
- var useShakePlayer = ({
1352
+ // src/hooks/useShakaPlayer.ts
1353
+ var useShakaPlayer = ({
1185
1354
  src,
1186
1355
  shakaConfig,
1187
1356
  drmConfig,
@@ -1192,8 +1361,26 @@ var useShakePlayer = ({
1192
1361
  onMuxDataUpdate
1193
1362
  }) => {
1194
1363
  const playerRef = (0, import_react.useRef)(null);
1195
- const initializePlayer = (0, import_react.useCallback)(async (video) => {
1364
+ const [isRetrying, setIsRetrying] = (0, import_react.useState)(false);
1365
+ const videoElementRef = (0, import_react.useRef)(null);
1366
+ const getManifestUrl = (0, import_react.useCallback)(() => {
1367
+ let manifestUrl = src.url;
1368
+ const isDRM = Boolean(src.drm);
1369
+ if (isDRM) {
1370
+ const isPlayReady = isPlayReadySupported();
1371
+ if (isAppleDevice() && src.drm.fairplay?.certificateUrl) {
1372
+ manifestUrl = src.drm.fairplay.playlistUrl;
1373
+ } else if (isPlayReady && src.drm.playready?.licenseUrl) {
1374
+ manifestUrl = src.drm.playready.playlistUrl;
1375
+ } else {
1376
+ manifestUrl = src.drm?.widevine?.playlistUrl || "";
1377
+ }
1378
+ }
1379
+ return manifestUrl;
1380
+ }, [src]);
1381
+ const initializePlayerInternal = (0, import_react.useCallback)(async (video) => {
1196
1382
  try {
1383
+ videoElementRef.current = video;
1197
1384
  import_shaka_player.default.polyfill.installAll();
1198
1385
  if (!import_shaka_player.default.Player.isBrowserSupported()) {
1199
1386
  throw new Error("Browser not supported by Shaka Player");
@@ -1204,36 +1391,55 @@ var useShakePlayer = ({
1204
1391
  if (shakaConfig) {
1205
1392
  player.configure(shakaConfig);
1206
1393
  }
1207
- let manifestUrl = src.url;
1394
+ const manifestUrl = getManifestUrl();
1395
+ let playlistId = src.id;
1208
1396
  const isDRM = Boolean(src.drm);
1209
- let cert = null;
1397
+ let storedSessionsMetadata = [];
1398
+ if (isDRM && playlistId) {
1399
+ storedSessionsMetadata = retrievePersistentLicense(playlistId, src.drm.licenseCacheKey ?? "");
1400
+ }
1210
1401
  if (isDRM) {
1211
- const isPlayReady = isPlayReadySupported();
1212
- if (isAppleDevice() && src.drm.fairplay?.certificateUrl) {
1213
- const req = await fetch(src.drm.fairplay.certificateUrl);
1214
- cert = await req.arrayBuffer();
1215
- manifestUrl = src.drm.fairplay.playlistUrl;
1216
- } else if (isPlayReady && src.drm.playready?.licenseUrl) {
1217
- manifestUrl = src.drm.playready.playlistUrl;
1218
- } else {
1219
- manifestUrl = src.drm?.widevine?.playlistUrl || "";
1220
- }
1221
- player.configure({
1222
- drm: {
1223
- servers: {
1224
- "com.widevine.alpha": src.drm.widevine?.licenseUrl,
1225
- "com.microsoft.playready": src.drm.playready?.licenseUrl,
1226
- "com.apple.fps": src.drm.fairplay?.licenseUrl
1227
- },
1228
- ...cert && {
1229
- advanced: {
1230
- "com.apple.fps": {
1231
- serverCertificate: new Uint8Array(cert)
1232
- }
1402
+ const drmConfig2 = {
1403
+ servers: {
1404
+ "com.widevine.alpha": src.drm.widevine?.licenseUrl,
1405
+ "com.microsoft.playready": src.drm.playready?.licenseUrl,
1406
+ "com.apple.fps": src.drm.fairplay?.licenseUrl
1407
+ },
1408
+ ...src.drm.fairplay && {
1409
+ advanced: {
1410
+ "com.apple.fps": {
1411
+ serverCertificateUri: src.drm.fairplay.certificateUrl
1233
1412
  }
1234
1413
  }
1235
1414
  }
1236
- });
1415
+ };
1416
+ drmConfig2.advanced = {
1417
+ ...drmConfig2.advanced,
1418
+ "com.widevine.alpha": {
1419
+ ...drmConfig2.advanced?.["com.widevine.alpha"],
1420
+ // set to `persistent-license` only on Chrome 64+ on MacOS/Windows: https://shaka-player-demo.appspot.com/docs/api/tutorial-faq.html
1421
+ sessionType: isChrome64PlusOnMacOrWindows() ? "persistent-license" : "temporary"
1422
+ },
1423
+ "com.microsoft.playready": {
1424
+ ...drmConfig2.advanced?.["com.microsoft.playready"],
1425
+ sessionType: "temporary"
1426
+ // PlayReady always uses temporary sessions
1427
+ },
1428
+ "com.apple.fps": {
1429
+ ...drmConfig2.advanced?.["com.apple.fps"],
1430
+ sessionType: "temporary"
1431
+ // FairPlay always uses temporary sessions - Safari won't play with persistent-license
1432
+ }
1433
+ };
1434
+ if (storedSessionsMetadata.length > 0) {
1435
+ drmConfig2.persistentSessionOnlinePlayback = true;
1436
+ drmConfig2.persistentSessionsMetadata = storedSessionsMetadata.map((session) => ({
1437
+ sessionId: session.sessionId,
1438
+ initData: new Uint8Array(session.initData),
1439
+ initDataType: session.initDataType
1440
+ }));
1441
+ }
1442
+ player.configure({ drm: drmConfig2 });
1237
1443
  const netEngine = player.getNetworkingEngine();
1238
1444
  if (netEngine) {
1239
1445
  netEngine.registerRequestFilter((type, request) => {
@@ -1257,6 +1463,28 @@ var useShakePlayer = ({
1257
1463
  if (error?.code === 7e3) {
1258
1464
  return;
1259
1465
  }
1466
+ if (error?.code >= 6e3 && error?.code < 7e3 && !isRetrying && videoElementRef.current) {
1467
+ console.warn(`DRM error detected (code: ${error.code}), clearing licenses and retrying...`);
1468
+ setIsRetrying(true);
1469
+ clearAllPersistentLicenses();
1470
+ setTimeout(async () => {
1471
+ try {
1472
+ const video2 = videoElementRef.current;
1473
+ const currentPlayer = playerRef.current;
1474
+ if (video2 && currentPlayer) {
1475
+ console.log("Reloading manifest after license cache clear...");
1476
+ await currentPlayer.load(getManifestUrl());
1477
+ console.log("Manifest reloaded successfully");
1478
+ }
1479
+ } catch (retryError) {
1480
+ console.error("Failed to retry after license clear:", retryError);
1481
+ onError?.(retryError);
1482
+ } finally {
1483
+ setIsRetrying(false);
1484
+ }
1485
+ }, 500);
1486
+ return;
1487
+ }
1260
1488
  console.error("Shaka Player Error:", error);
1261
1489
  onError?.(new Error(`Shaka Player Error: ${error.message || "Unknown error"}`));
1262
1490
  });
@@ -1288,6 +1516,30 @@ var useShakePlayer = ({
1288
1516
  }
1289
1517
  }
1290
1518
  await player.load(manifestUrl);
1519
+ if (isDRM && playlistId) {
1520
+ try {
1521
+ setTimeout(() => {
1522
+ const activeDrmSessions = player.getActiveSessionsMetadata?.();
1523
+ if (activeDrmSessions) {
1524
+ const persistentSessions = activeDrmSessions.filter(
1525
+ (session) => session.sessionType === "persistent-license"
1526
+ );
1527
+ if (persistentSessions.length > 0) {
1528
+ const sessionsToStore = persistentSessions.map((session) => ({
1529
+ sessionId: session.sessionId,
1530
+ initData: session.initData,
1531
+ initDataType: session.initDataType,
1532
+ keySystem: session.keySystem || "com.widevine.alpha"
1533
+ // fallback
1534
+ }));
1535
+ storePersistentLicense(playlistId, src.drm.licenseCacheKey ?? "", sessionsToStore);
1536
+ }
1537
+ }
1538
+ }, 2e3);
1539
+ } catch (error) {
1540
+ console.warn("Failed to store persistent license sessions:", error);
1541
+ }
1542
+ }
1291
1543
  onPlayerReady?.(player);
1292
1544
  return player;
1293
1545
  } catch (error) {
@@ -1298,7 +1550,10 @@ var useShakePlayer = ({
1298
1550
  onError?.(error);
1299
1551
  throw error;
1300
1552
  }
1301
- }, [shakaConfig, drmConfig, src, onError, onPlayerReady, muxConfig, onMuxReady]);
1553
+ }, [shakaConfig, drmConfig, src, onError, onPlayerReady, muxConfig, onMuxReady, isRetrying, getManifestUrl]);
1554
+ const initializePlayer = (0, import_react.useCallback)(async (video) => {
1555
+ return initializePlayerInternal(video);
1556
+ }, [initializePlayerInternal]);
1302
1557
  const destroyPlayer = (0, import_react.useCallback)(async () => {
1303
1558
  if (playerRef.current) {
1304
1559
  try {
@@ -1313,7 +1568,8 @@ var useShakePlayer = ({
1313
1568
  return {
1314
1569
  playerRef,
1315
1570
  initializePlayer,
1316
- destroyPlayer
1571
+ destroyPlayer,
1572
+ isRetrying
1317
1573
  };
1318
1574
  };
1319
1575
 
@@ -3335,7 +3591,7 @@ var Player = (0, import_react12.forwardRef)(
3335
3591
  const containerRef = (0, import_react12.useRef)(null);
3336
3592
  const adContainerRef = (0, import_react12.useRef)(null);
3337
3593
  (0, import_react12.useImperativeHandle)(ref, () => videoRef.current, []);
3338
- const { playerRef, initializePlayer, destroyPlayer } = useShakePlayer({
3594
+ const { playerRef, initializePlayer, destroyPlayer, isRetrying } = useShakaPlayer({
3339
3595
  src,
3340
3596
  shakaConfig,
3341
3597
  drmConfig,