@codenkay/video-nsgplayer-core 4.0.0 → 5.1.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add `streaming.mediaProxyUrlBuilder` so playlists, segments, and encryption keys are loaded through a same-origin proxy. The playback API rejects the browser origin and does not send CORS headers.
8
+ - Leave URLs that are already on the page origin unchanged, so a rewritten playlist is not proxied twice.
9
+ - Recognize `/live/` and `/hls-web-live/` playlists for direct `sourceUrl` playback, including when the live address is nested in a proxy `url` parameter.
10
+ - Send same-origin encryption-key requests with the site cookie and without a bearer token. The playback credential stays in the query string.
11
+
12
+ ## 5.0.0
13
+
14
+ ### Minor Changes
15
+
16
+ - Update meta data
17
+
3
18
  ## 4.0.0
4
19
 
5
20
  ### Major Changes
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Framework-agnostic HLS video player engine for NSG.
4
4
 
5
+ **Try it:** [nsgplayer-sample-react](https://github.com/codenannu/nsgplayer-sample-react) (npm package homepage). Also [Next.js BFF](https://github.com/codenannu/nsgplayer-sample-nextjs) · [Angular](https://github.com/codenannu/nsgplayer-sample-angular).
6
+
5
7
  ## Install
6
8
 
7
9
  ```bash
package/dist/index.cjs CHANGED
@@ -53,7 +53,9 @@ __export(index_exports, {
53
53
  getLiveEdgeTime: () => getLiveEdgeTime,
54
54
  isAtLiveEdge: () => isAtLiveEdge,
55
55
  isEncryptionKeyUrl: () => isEncryptionKeyUrl,
56
+ isLivePlaylistUrl: () => isLivePlaylistUrl,
56
57
  isPlainObject: () => isPlainObject,
58
+ isSameOriginHttpUrl: () => isSameOriginHttpUrl,
57
59
  mergePlayerConfig: () => mergePlayerConfig,
58
60
  redactObject: () => redactObject,
59
61
  redactSensitive: () => redactSensitive,
@@ -309,6 +311,7 @@ var SKIP_DEEP_SCAN_KEYS = /* @__PURE__ */ new Set([
309
311
  "getsource",
310
312
  "refreshsource",
311
313
  "keyproxyurlbuilder",
314
+ "mediaproxyurlbuilder",
312
315
  "getkeyaccesstoken"
313
316
  ]);
314
317
  var SECRET_SCAN_MAX_DEPTH = 32;
@@ -393,6 +396,7 @@ function mergePlayerConfig(partial) {
393
396
  ...DEFAULT_PLAYER_CONFIG.streaming.hls,
394
397
  ...partial?.streaming?.hls
395
398
  },
399
+ mediaProxyUrlBuilder: partial?.streaming?.mediaProxyUrlBuilder,
396
400
  keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder,
397
401
  getKeyAccessToken: partial?.streaming?.getKeyAccessToken,
398
402
  keyProxyMaxFailures: partial?.streaming?.keyProxyMaxFailures
@@ -834,6 +838,26 @@ function seekToLiveEdge(media, options) {
834
838
  return { moved: true, edge };
835
839
  }
836
840
 
841
+ // src/streaming/mediaUrl.ts
842
+ var LIVE_PLAYLIST_PATH = /\/(?:hls-web-live|live)\//i;
843
+ function isLivePlaylistUrl(value) {
844
+ if (LIVE_PLAYLIST_PATH.test(value)) return true;
845
+ try {
846
+ const parsed = new URL(value, "http://localhost");
847
+ const inner = parsed.searchParams.get("url");
848
+ return Boolean(inner && LIVE_PLAYLIST_PATH.test(inner));
849
+ } catch {
850
+ return false;
851
+ }
852
+ }
853
+ function isSameOriginHttpUrl(value, baseOrigin) {
854
+ try {
855
+ return new URL(value, baseOrigin).origin === new URL(baseOrigin).origin;
856
+ } catch {
857
+ return false;
858
+ }
859
+ }
860
+
837
861
  // src/streaming/authParamsLoader.ts
838
862
  var ENC_KEY_PATH_RE = /enc\.key$/i;
839
863
  var BUILTIN_LOADER_NAMES = /* @__PURE__ */ new Set(["XhrLoader", "FetchLoader"]);
@@ -903,8 +927,16 @@ function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof l
903
927
  mobile: snapshot.mobile
904
928
  });
905
929
  }
906
- if (!store.hasParams()) return parsed.toString();
907
- return store.applyToUrl(rawUrl, baseOrigin);
930
+ const withAuth = store.hasParams() ? store.applyToUrl(rawUrl, baseOrigin) : parsed.toString();
931
+ return applyMediaProxy(withAuth, options, baseOrigin);
932
+ }
933
+ function applyMediaProxy(url, options, baseOrigin) {
934
+ if (!options.mediaProxyUrlBuilder) return url;
935
+ if (isSameOriginHttpUrl(url, baseOrigin)) return url;
936
+ return options.mediaProxyUrlBuilder({
937
+ url,
938
+ videoId: options.videoId
939
+ });
908
940
  }
909
941
  function createLoaderStats() {
910
942
  const now = performance.now();
@@ -935,12 +967,14 @@ function isProxyUpstreamBlocked(status, contentType, bodyHead, errorText) {
935
967
  return false;
936
968
  }
937
969
  async function fetchEncryptionKeyBytes(url, accessToken, signal) {
938
- const headers = accessToken ? { Authorization: `Bearer ${accessToken}`, Accept: "*/*" } : void 0;
970
+ const pageOrigin = typeof location !== "undefined" ? location.origin : "http://localhost";
971
+ const sameOrigin = isSameOriginHttpUrl(url, pageOrigin);
972
+ const headers = !sameOrigin && accessToken ? { Authorization: `Bearer ${accessToken}`, Accept: "*/*" } : void 0;
939
973
  try {
940
974
  const response = await fetch(url, {
941
975
  method: "GET",
942
976
  ...headers ? { headers } : {},
943
- credentials: "omit",
977
+ credentials: sameOrigin ? "same-origin" : "omit",
944
978
  cache: "no-store",
945
979
  signal
946
980
  });
@@ -1231,7 +1265,7 @@ function createAuthParamsLoader(HlsModule, store, options = {}) {
1231
1265
  const originalUrl = context.url;
1232
1266
  const isKey = isEncryptionKeyUrl(originalUrl);
1233
1267
  if (!isKey) {
1234
- if (store.hasParams()) {
1268
+ if (store.hasParams() || options.mediaProxyUrlBuilder) {
1235
1269
  context.url = rewriteHlsRequestUrl(originalUrl, store, {
1236
1270
  ...options,
1237
1271
  keyProxyUrlBuilder: void 0
@@ -1356,7 +1390,7 @@ var HlsEngine = class {
1356
1390
  if (videoEl.canPlayType("application/vnd.apple.mpegurl")) {
1357
1391
  this.supportMode = "native";
1358
1392
  this.options.callbacks.onSupportMode?.("native");
1359
- videoEl.src = this.options.authParams.applyToUrl(playbackUrl);
1393
+ videoEl.src = this.playbackRequestUrl(playbackUrl);
1360
1394
  this.bindMediaEvents();
1361
1395
  this.bindNativeTextTracks();
1362
1396
  this.options.callbacks.onStateChange?.("ready");
@@ -1381,6 +1415,7 @@ var HlsEngine = class {
1381
1415
  } = this.options;
1382
1416
  const loader = createAuthParamsLoader(Hls, authParams, {
1383
1417
  videoId,
1418
+ mediaProxyUrlBuilder: this.options.mediaProxyUrlBuilder,
1384
1419
  keyProxyUrlBuilder,
1385
1420
  getKeyAccessToken,
1386
1421
  keyProxyMaxFailures: this.options.keyProxyMaxFailures
@@ -1409,7 +1444,7 @@ var HlsEngine = class {
1409
1444
  loader
1410
1445
  });
1411
1446
  this.hls.attachMedia(videoEl);
1412
- this.hls.loadSource(authParams.applyToUrl(playbackUrl));
1447
+ this.hls.loadSource(this.playbackRequestUrl(playbackUrl));
1413
1448
  this.hls.on(Hls.Events.MANIFEST_PARSED, (_event, data) => {
1414
1449
  if (this.destroyed) return;
1415
1450
  this.qualities = (data.levels ?? []).map((level, index) => ({
@@ -1587,13 +1622,20 @@ var HlsEngine = class {
1587
1622
  if (this.supportMode === "native" && this.options.videoEl) {
1588
1623
  const time = this.options.videoEl.currentTime;
1589
1624
  const wasPaused = this.options.videoEl.paused;
1590
- this.options.videoEl.src = this.options.authParams.applyToUrl(
1591
- this.options.playbackUrl
1592
- );
1625
+ this.options.videoEl.src = this.playbackRequestUrl(this.options.playbackUrl);
1593
1626
  this.options.videoEl.currentTime = time;
1594
1627
  if (!wasPaused) void this.options.videoEl.play();
1595
1628
  }
1596
1629
  }
1630
+ playbackRequestUrl(playbackUrl) {
1631
+ return rewriteHlsRequestUrl(playbackUrl, this.options.authParams, {
1632
+ videoId: this.options.videoId,
1633
+ mediaProxyUrlBuilder: this.options.mediaProxyUrlBuilder,
1634
+ keyProxyUrlBuilder: this.options.keyProxyUrlBuilder,
1635
+ getKeyAccessToken: this.options.getKeyAccessToken,
1636
+ keyProxyMaxFailures: this.options.keyProxyMaxFailures
1637
+ });
1638
+ }
1597
1639
  resumeAfterAuthRenew() {
1598
1640
  this.networkRecoveryCount = 0;
1599
1641
  this.hls?.startLoad();
@@ -1858,7 +1900,7 @@ function createVideoPlayer(options) {
1858
1900
  const source = {
1859
1901
  url: options.sourceUrl,
1860
1902
  expiresAt: Date.now() + 24 * 60 * 60 * 1e3,
1861
- live: /\/hls-web-live\//i.test(options.sourceUrl)
1903
+ live: isLivePlaylistUrl(options.sourceUrl)
1862
1904
  };
1863
1905
  sourceManager.applyDirectSource(source);
1864
1906
  playbackUrl = source.url;
@@ -1888,6 +1930,7 @@ function createVideoPlayer(options) {
1888
1930
  startPosition: config.player.startPosition,
1889
1931
  volume: config.player.volume,
1890
1932
  videoId: options.videoId,
1933
+ mediaProxyUrlBuilder: config.streaming.mediaProxyUrlBuilder,
1891
1934
  keyProxyUrlBuilder: config.streaming.keyProxyUrlBuilder,
1892
1935
  // Enterprise AES key auth (see authParamsLoader):
1893
1936
  // 1) keyProxyUrlBuilder BFF — max 2 failures then circuit
@@ -2219,6 +2262,7 @@ function createVideoPlayer(options) {
2219
2262
  streaming: {
2220
2263
  engine: "hls",
2221
2264
  hls: { ...config.streaming.hls, ...partial?.streaming?.hls },
2265
+ mediaProxyUrlBuilder: partial?.streaming?.mediaProxyUrlBuilder ?? config.streaming.mediaProxyUrlBuilder,
2222
2266
  keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder,
2223
2267
  getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken()),
2224
2268
  keyProxyMaxFailures: partial?.streaming?.keyProxyMaxFailures ?? config.streaming.keyProxyMaxFailures
@@ -2267,7 +2311,9 @@ function createVideoPlayer(options) {
2267
2311
  getLiveEdgeTime,
2268
2312
  isAtLiveEdge,
2269
2313
  isEncryptionKeyUrl,
2314
+ isLivePlaylistUrl,
2270
2315
  isPlainObject,
2316
+ isSameOriginHttpUrl,
2271
2317
  mergePlayerConfig,
2272
2318
  redactObject,
2273
2319
  redactSensitive,