@mottosports/motto-video-player 1.0.1-rc.35 → 1.0.1-rc.37

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