@guardvideo/player-sdk 1.0.1 → 1.0.2

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.
@@ -2,16 +2,37 @@ import { PlayerConfig, PlayerState, PlayerInstance, QualityLevel } from './types
2
2
  export declare class GuardVideoPlayer implements PlayerInstance {
3
3
  private videoId;
4
4
  private videoElement;
5
+ private container;
5
6
  private hls;
6
7
  private config;
7
8
  private state;
8
9
  private embedToken;
9
10
  private currentQuality;
11
+ private ctxMenu;
12
+ private ctxStyleTag;
13
+ private watermarkEl;
14
+ private watermarkObserver;
15
+ private _onCtx;
16
+ private _onDocClick;
17
+ private _onKeyDown;
18
+ private _onRateChange;
19
+ private _onSelectStart;
20
+ private _onDragStart;
10
21
  constructor(videoElement: HTMLVideoElement, videoId: string, config: PlayerConfig);
11
22
  private log;
12
23
  private error;
13
24
  private setState;
25
+ private checkAllowedDomain;
26
+ private applySecurity;
27
+ private handleContextMenu;
28
+ private showContextMenu;
29
+ private hideContextMenu;
30
+ private injectProtectiveStyles;
31
+ private createWatermark;
32
+ private handleKeyDown;
33
+ private enforceMaxRate;
14
34
  private initialize;
35
+ private fetchAndApplyWatermark;
15
36
  private fetchEmbedToken;
16
37
  private initializePlayer;
17
38
  private initializeHls;
@@ -1 +1 @@
1
- {"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../../src/core/player.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,YAAY,EAEZ,WAAW,EACX,cAAc,EACd,YAAY,EAEb,MAAM,SAAS,CAAC;AAEjB,qBAAa,gBAAiB,YAAW,cAAc;IAUnD,OAAO,CAAC,OAAO;IATjB,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,GAAG,CAAoB;IAC/B,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,UAAU,CAAmC;IACrD,OAAO,CAAC,cAAc,CAA6B;gBAGjD,YAAY,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,EACvB,MAAM,EAAE,YAAY;IAsBtB,OAAO,CAAC,GAAG;IAMX,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,QAAQ;YAQF,UAAU;YAoBV,eAAe;YAwBf,gBAAgB;IAgC9B,OAAO,CAAC,aAAa;IAsErB,OAAO,CAAC,wBAAwB;IAiChC,OAAO,CAAC,WAAW;IAQN,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B,KAAK,IAAI,IAAI;IAIb,cAAc,IAAI,MAAM;IAIxB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIxB,WAAW,IAAI,MAAM;IAIrB,SAAS,IAAI,MAAM;IAInB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B,gBAAgB,IAAI,YAAY,EAAE;IAYlC,iBAAiB,IAAI,YAAY,GAAG,IAAI;IAIxC,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAOpC,QAAQ,IAAI,WAAW;IAIvB,OAAO,IAAI,IAAI;CAYvB"}
1
+ {"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../../src/core/player.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,YAAY,EAEZ,WAAW,EACX,cAAc,EACd,YAAY,EAKb,MAAM,SAAS,CAAC;AA4BjB,qBAAa,gBAAiB,YAAW,cAAc;IA4BnD,OAAO,CAAC,OAAO;IA3BjB,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,GAAG,CAAoB;IAC/B,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,UAAU,CAAmC;IACrD,OAAO,CAAC,cAAc,CAA6B;IAGnD,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,WAAW,CAAiC;IAGpD,OAAO,CAAC,WAAW,CAA+B;IAElD,OAAO,CAAC,iBAAiB,CAAiC;IAG1D,OAAO,CAAC,MAAM,CAAqC;IACnD,OAAO,CAAC,WAAW,CAAmC;IACtD,OAAO,CAAC,UAAU,CAAiC;IACnD,OAAO,CAAC,aAAa,CAAkC;IACvD,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,YAAY,CAAoC;gBAGtD,YAAY,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,EACvB,MAAM,EAAE,YAAY;IAwCtB,OAAO,CAAC,GAAG;IAMX,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,aAAa;IA0DrB,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,eAAe;IAqHvB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,sBAAsB;IA4G9B,OAAO,CAAC,eAAe;IAgCvB,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,cAAc;YAWR,UAAU;YAwBV,sBAAsB;YA8BtB,eAAe;YA2Bf,gBAAgB;IAgC9B,OAAO,CAAC,aAAa;IAsErB,OAAO,CAAC,wBAAwB;IAiChC,OAAO,CAAC,WAAW;IASN,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B,KAAK,IAAI,IAAI;IAIb,cAAc,IAAI,MAAM;IAIxB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIxB,WAAW,IAAI,MAAM;IAIrB,SAAS,IAAI,MAAM;IAInB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B,gBAAgB,IAAI,YAAY,EAAE;IAYlC,iBAAiB,IAAI,YAAY,GAAG,IAAI;IAIxC,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAOpC,QAAQ,IAAI,WAAW;IAIvB,OAAO,IAAI,IAAI;CA2BvB"}
@@ -6,6 +6,33 @@ export interface EmbedTokenResponse {
6
6
  maxViews: number | null;
7
7
  playerUrl: string;
8
8
  embedCode?: string;
9
+ watermarkText?: string | null;
10
+ forensicWatermark?: boolean;
11
+ }
12
+ export interface ContextMenuItem {
13
+ label: string;
14
+ href?: string;
15
+ onClick?: () => void;
16
+ icon?: string;
17
+ separator?: boolean;
18
+ }
19
+ export interface BrandingConfig {
20
+ name?: string;
21
+ url?: string;
22
+ logoUrl?: string;
23
+ accentColor?: string;
24
+ }
25
+ export interface SecurityConfig {
26
+ disableRightClick?: boolean;
27
+ disableSelection?: boolean;
28
+ disableDrag?: boolean;
29
+ enableWatermark?: boolean;
30
+ watermarkText?: string;
31
+ disablePiP?: boolean;
32
+ disableScreenCapture?: boolean;
33
+ blockDevTools?: boolean;
34
+ maxPlaybackRate?: number;
35
+ allowedDomains?: string[];
9
36
  }
10
37
  export interface PlayerConfig {
11
38
  embedTokenEndpoint: string;
@@ -16,6 +43,12 @@ export interface PlayerConfig {
16
43
  className?: string;
17
44
  style?: React.CSSProperties | Record<string, string>;
18
45
  hlsConfig?: Partial<HlsConfig>;
46
+ branding?: BrandingConfig;
47
+ contextMenuItems?: ContextMenuItem[];
48
+ security?: SecurityConfig;
49
+ viewerName?: string;
50
+ viewerEmail?: string;
51
+ forensicWatermark?: boolean;
19
52
  onReady?: () => void;
20
53
  onError?: (error: PlayerError) => void;
21
54
  onQualityChange?: (quality: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAK3B,kBAAkB,EAAE,MAAM,CAAC;IAM3B,UAAU,CAAC,EAAE,MAAM,CAAC;IAMpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAMhB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAMnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAKnB,SAAS,CAAC,EAAE,MAAM,CAAC;IAKnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAKrD,SAAS,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAK/B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAKrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAKvC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAK5C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;CAC9C;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,GAAG,CAAC;CACf;AAED,oBAAY,WAAW;IACrB,IAAI,SAAS;IACb,OAAO,YAAY;IACnB,KAAK,UAAU;IACf,OAAO,YAAY;IACnB,MAAM,WAAW;IACjB,SAAS,cAAc;IACvB,KAAK,UAAU;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAI7B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAKtB,KAAK,IAAI,IAAI,CAAC;IAKd,cAAc,IAAI,MAAM,CAAC;IAKzB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAKzB,WAAW,IAAI,MAAM,CAAC;IAKtB,SAAS,IAAI,MAAM,CAAC;IAKpB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAKhC,gBAAgB,IAAI,YAAY,EAAE,CAAC;IAKnC,iBAAiB,IAAI,YAAY,GAAG,IAAI,CAAC;IAKzC,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAKrC,QAAQ,IAAI,WAAW,CAAC;IAKxB,OAAO,IAAI,IAAI,CAAC;CACjB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
package/dist/index.esm.js CHANGED
@@ -12,14 +12,44 @@ var PlayerState;
12
12
  PlayerState["ERROR"] = "error";
13
13
  })(PlayerState || (PlayerState = {}));
14
14
 
15
+ const DEFAULT_BRANDING = {
16
+ name: 'GuardVideo',
17
+ url: 'https://guardvid.com',
18
+ logoUrl: '',
19
+ accentColor: '#44c09b',
20
+ };
21
+ const DEFAULT_SECURITY = {
22
+ disableRightClick: false,
23
+ disableSelection: true,
24
+ disableDrag: true,
25
+ enableWatermark: false,
26
+ watermarkText: '',
27
+ disablePiP: false,
28
+ disableScreenCapture: false,
29
+ blockDevTools: false,
30
+ maxPlaybackRate: 2,
31
+ allowedDomains: [],
32
+ };
15
33
  class GuardVideoPlayer {
16
34
  constructor(videoElement, videoId, config) {
17
35
  this.videoId = videoId;
36
+ this.container = null;
18
37
  this.hls = null;
19
38
  this.state = PlayerState.IDLE;
20
39
  this.embedToken = null;
21
40
  this.currentQuality = null;
41
+ this.ctxMenu = null;
42
+ this.ctxStyleTag = null;
43
+ this.watermarkEl = null;
44
+ this.watermarkObserver = null;
45
+ this._onCtx = this.handleContextMenu.bind(this);
46
+ this._onDocClick = this.hideContextMenu.bind(this);
47
+ this._onKeyDown = this.handleKeyDown.bind(this);
48
+ this._onRateChange = this.enforceMaxRate.bind(this);
49
+ this._onSelectStart = (e) => e.preventDefault();
50
+ this._onDragStart = (e) => e.preventDefault();
22
51
  this.videoElement = videoElement;
52
+ this.container = videoElement.parentElement;
23
53
  this.config = {
24
54
  embedTokenEndpoint: config.embedTokenEndpoint,
25
55
  apiBaseUrl: config.apiBaseUrl || '',
@@ -29,12 +59,21 @@ class GuardVideoPlayer {
29
59
  className: config.className || '',
30
60
  style: config.style || {},
31
61
  hlsConfig: config.hlsConfig || {},
62
+ branding: { ...DEFAULT_BRANDING, ...config.branding },
63
+ contextMenuItems: config.contextMenuItems || [],
64
+ security: { ...DEFAULT_SECURITY, ...config.security },
65
+ viewerName: config.viewerName || '',
66
+ viewerEmail: config.viewerEmail || '',
67
+ forensicWatermark: config.forensicWatermark || false,
32
68
  onReady: config.onReady || (() => { }),
33
69
  onError: config.onError || (() => { }),
34
70
  onQualityChange: config.onQualityChange || (() => { }),
35
71
  onStateChange: config.onStateChange || (() => { }),
36
72
  };
37
73
  this.log('Initializing GuardVideo Player', { videoId, config });
74
+ if (!this.checkAllowedDomain())
75
+ return;
76
+ this.applySecurity();
38
77
  this.initialize();
39
78
  }
40
79
  log(message, data) {
@@ -52,12 +91,321 @@ class GuardVideoPlayer {
52
91
  this.log(`State changed to: ${newState}`);
53
92
  }
54
93
  }
94
+ checkAllowedDomain() {
95
+ const domains = this.config.security.allowedDomains;
96
+ if (!domains || domains.length === 0)
97
+ return true;
98
+ const currentOrigin = typeof window !== 'undefined' ? window.location.origin : '';
99
+ const allowed = domains.some((d) => currentOrigin === d || currentOrigin.endsWith(`.${d.replace(/^https?:\/\//, '')}`));
100
+ if (!allowed) {
101
+ this.handleError({
102
+ code: 'DOMAIN_NOT_ALLOWED',
103
+ message: `This player is not authorized to run on ${currentOrigin}`,
104
+ fatal: true,
105
+ });
106
+ return false;
107
+ }
108
+ return true;
109
+ }
110
+ applySecurity() {
111
+ const sec = this.config.security;
112
+ const target = this.container || this.videoElement;
113
+ target.addEventListener('contextmenu', this._onCtx);
114
+ document.addEventListener('click', this._onDocClick);
115
+ if (sec.disableSelection) {
116
+ target.addEventListener('selectstart', this._onSelectStart);
117
+ target.style.userSelect = 'none';
118
+ target.style.webkitUserSelect = 'none';
119
+ }
120
+ if (sec.disableDrag) {
121
+ this.videoElement.addEventListener('dragstart', this._onDragStart);
122
+ this.videoElement.draggable = false;
123
+ }
124
+ if (sec.disablePiP) {
125
+ this.videoElement.disablePictureInPicture = true;
126
+ }
127
+ if (sec.disableScreenCapture) {
128
+ if ('mediaKeys' in this.videoElement && typeof navigator.requestMediaKeySystemAccess === 'function') {
129
+ this.log('Screen-capture protection: EME hint applied');
130
+ }
131
+ target.style.setProperty('-webkit-app-region', 'no-drag');
132
+ }
133
+ if (sec.blockDevTools) {
134
+ document.addEventListener('keydown', this._onKeyDown);
135
+ }
136
+ if (sec.maxPlaybackRate) {
137
+ this.videoElement.addEventListener('ratechange', this._onRateChange);
138
+ }
139
+ if (sec.enableWatermark && sec.watermarkText && this.container) {
140
+ this.createWatermark(sec.watermarkText);
141
+ }
142
+ this.injectProtectiveStyles();
143
+ }
144
+ handleContextMenu(e) {
145
+ e.preventDefault();
146
+ e.stopPropagation();
147
+ const sec = this.config.security;
148
+ if (sec.disableRightClick)
149
+ return;
150
+ const me = e;
151
+ this.showContextMenu(me.clientX, me.clientY);
152
+ }
153
+ showContextMenu(x, y) {
154
+ this.hideContextMenu();
155
+ const branding = this.config.branding;
156
+ const extraItems = this.config.contextMenuItems;
157
+ const menu = document.createElement('div');
158
+ menu.className = 'gv-ctx-menu';
159
+ menu.setAttribute('role', 'menu');
160
+ const header = document.createElement('a');
161
+ header.className = 'gv-ctx-header';
162
+ header.href = branding.url;
163
+ header.target = '_blank';
164
+ header.rel = 'noopener noreferrer';
165
+ header.setAttribute('role', 'menuitem');
166
+ if (branding.logoUrl) {
167
+ const logo = document.createElement('img');
168
+ logo.src = branding.logoUrl;
169
+ logo.alt = branding.name;
170
+ logo.className = 'gv-ctx-logo';
171
+ logo.width = 20;
172
+ logo.height = 20;
173
+ header.appendChild(logo);
174
+ }
175
+ else {
176
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
177
+ svg.setAttribute('width', '18');
178
+ svg.setAttribute('height', '18');
179
+ svg.setAttribute('viewBox', '0 0 24 24');
180
+ svg.setAttribute('fill', 'none');
181
+ svg.setAttribute('stroke', branding.accentColor);
182
+ svg.setAttribute('stroke-width', '2');
183
+ svg.setAttribute('stroke-linecap', 'round');
184
+ svg.setAttribute('stroke-linejoin', 'round');
185
+ svg.innerHTML = '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>';
186
+ header.appendChild(svg);
187
+ }
188
+ const nameSpan = document.createElement('span');
189
+ nameSpan.className = 'gv-ctx-brand-name';
190
+ nameSpan.textContent = branding.name;
191
+ header.appendChild(nameSpan);
192
+ const tagSpan = document.createElement('span');
193
+ tagSpan.className = 'gv-ctx-tag';
194
+ tagSpan.textContent = 'Secure Video Player';
195
+ header.appendChild(tagSpan);
196
+ menu.appendChild(header);
197
+ if (extraItems.length > 0) {
198
+ extraItems.forEach((item) => {
199
+ if (item.separator) {
200
+ const sep = document.createElement('div');
201
+ sep.className = 'gv-ctx-sep';
202
+ menu.appendChild(sep);
203
+ }
204
+ const row = document.createElement('div');
205
+ row.className = 'gv-ctx-item';
206
+ row.setAttribute('role', 'menuitem');
207
+ row.tabIndex = 0;
208
+ if (item.icon) {
209
+ const ico = document.createElement('img');
210
+ ico.src = item.icon;
211
+ ico.width = 14;
212
+ ico.height = 14;
213
+ ico.className = 'gv-ctx-item-icon';
214
+ row.appendChild(ico);
215
+ }
216
+ const label = document.createElement('span');
217
+ label.textContent = item.label;
218
+ row.appendChild(label);
219
+ row.addEventListener('click', (ev) => {
220
+ ev.stopPropagation();
221
+ this.hideContextMenu();
222
+ if (item.onClick) {
223
+ item.onClick();
224
+ }
225
+ else if (item.href) {
226
+ window.open(item.href, '_blank', 'noopener,noreferrer');
227
+ }
228
+ });
229
+ menu.appendChild(row);
230
+ });
231
+ }
232
+ const sep = document.createElement('div');
233
+ sep.className = 'gv-ctx-sep';
234
+ menu.appendChild(sep);
235
+ const version = document.createElement('div');
236
+ version.className = 'gv-ctx-version';
237
+ version.textContent = `${branding.name} Player v1.0`;
238
+ menu.appendChild(version);
239
+ document.body.appendChild(menu);
240
+ const rect = menu.getBoundingClientRect();
241
+ const vw = window.innerWidth;
242
+ const vh = window.innerHeight;
243
+ menu.style.left = `${x + rect.width > vw ? vw - rect.width - 8 : x}px`;
244
+ menu.style.top = `${y + rect.height > vh ? vh - rect.height - 8 : y}px`;
245
+ this.ctxMenu = menu;
246
+ }
247
+ hideContextMenu() {
248
+ if (this.ctxMenu) {
249
+ this.ctxMenu.remove();
250
+ this.ctxMenu = null;
251
+ }
252
+ }
253
+ injectProtectiveStyles() {
254
+ if (this.ctxStyleTag)
255
+ return;
256
+ const branding = this.config.branding;
257
+ const accent = branding.accentColor;
258
+ const css = `
259
+ /* GuardVideo branded context menu */
260
+ .gv-ctx-menu {
261
+ position: fixed;
262
+ z-index: 2147483647;
263
+ min-width: 220px;
264
+ background: rgba(18, 18, 22, 0.96);
265
+ backdrop-filter: blur(12px);
266
+ -webkit-backdrop-filter: blur(12px);
267
+ border: 1px solid rgba(255,255,255,0.08);
268
+ border-radius: 10px;
269
+ padding: 6px 0;
270
+ box-shadow: 0 8px 32px rgba(0,0,0,0.45), 0 0 0 1px rgba(255,255,255,0.04);
271
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
272
+ font-size: 13px;
273
+ color: #e4e4e7;
274
+ user-select: none;
275
+ animation: gv-ctx-in 0.12s ease-out;
276
+ }
277
+ @keyframes gv-ctx-in {
278
+ from { opacity: 0; transform: scale(0.96); }
279
+ to { opacity: 1; transform: scale(1); }
280
+ }
281
+
282
+ .gv-ctx-header {
283
+ display: flex;
284
+ align-items: center;
285
+ gap: 8px;
286
+ padding: 8px 14px 8px 12px;
287
+ text-decoration: none;
288
+ color: inherit;
289
+ transition: background 0.15s;
290
+ border-radius: 6px 6px 0 0;
291
+ }
292
+ .gv-ctx-header:hover { background: rgba(255,255,255,0.06); }
293
+
294
+ .gv-ctx-logo { border-radius: 4px; }
295
+
296
+ .gv-ctx-brand-name {
297
+ font-weight: 600;
298
+ color: ${accent};
299
+ white-space: nowrap;
300
+ }
301
+
302
+ .gv-ctx-tag {
303
+ margin-left: auto;
304
+ font-size: 10px;
305
+ color: rgba(255,255,255,0.35);
306
+ white-space: nowrap;
307
+ }
308
+
309
+ .gv-ctx-sep {
310
+ height: 1px;
311
+ margin: 4px 10px;
312
+ background: rgba(255,255,255,0.07);
313
+ }
314
+
315
+ .gv-ctx-item {
316
+ display: flex;
317
+ align-items: center;
318
+ gap: 8px;
319
+ padding: 7px 14px 7px 12px;
320
+ cursor: pointer;
321
+ transition: background 0.15s;
322
+ }
323
+ .gv-ctx-item:hover { background: rgba(255,255,255,0.06); }
324
+ .gv-ctx-item-icon { border-radius: 2px; }
325
+
326
+ .gv-ctx-version {
327
+ padding: 4px 14px 6px 12px;
328
+ font-size: 10px;
329
+ color: rgba(255,255,255,0.25);
330
+ }
331
+
332
+ /* Watermark overlay */
333
+ .gv-watermark {
334
+ position: absolute;
335
+ inset: 0;
336
+ pointer-events: none;
337
+ overflow: hidden;
338
+ z-index: 10;
339
+ }
340
+ .gv-watermark-text {
341
+ position: absolute;
342
+ white-space: nowrap;
343
+ font-size: 14px;
344
+ font-family: monospace;
345
+ color: rgba(255,255,255,0.07);
346
+ transform: rotate(-30deg);
347
+ user-select: none;
348
+ pointer-events: none;
349
+ }
350
+ `;
351
+ const tag = document.createElement('style');
352
+ tag.setAttribute('data-guardvideo', 'player-styles');
353
+ tag.textContent = css;
354
+ document.head.appendChild(tag);
355
+ this.ctxStyleTag = tag;
356
+ }
357
+ createWatermark(text) {
358
+ if (!this.container)
359
+ return;
360
+ const overlay = document.createElement('div');
361
+ overlay.className = 'gv-watermark';
362
+ for (let row = 0; row < 5; row++) {
363
+ for (let col = 0; col < 4; col++) {
364
+ const span = document.createElement('span');
365
+ span.className = 'gv-watermark-text';
366
+ span.textContent = text;
367
+ span.style.left = `${col * 28 + (row % 2) * 14}%`;
368
+ span.style.top = `${row * 22}%`;
369
+ overlay.appendChild(span);
370
+ }
371
+ }
372
+ this.container.style.position = 'relative';
373
+ this.container.appendChild(overlay);
374
+ this.watermarkEl = overlay;
375
+ this.watermarkObserver = new MutationObserver(() => {
376
+ if (this.container && this.watermarkEl && !this.container.contains(this.watermarkEl)) {
377
+ this.container.appendChild(this.watermarkEl);
378
+ }
379
+ });
380
+ this.watermarkObserver.observe(this.container, { childList: true, subtree: false });
381
+ }
382
+ handleKeyDown(e) {
383
+ if (e.key === 'F12') {
384
+ e.preventDefault();
385
+ return;
386
+ }
387
+ if (e.ctrlKey && e.shiftKey && ['I', 'J', 'C'].includes(e.key.toUpperCase())) {
388
+ e.preventDefault();
389
+ return;
390
+ }
391
+ if (e.ctrlKey && e.key.toUpperCase() === 'U') {
392
+ e.preventDefault();
393
+ }
394
+ }
395
+ enforceMaxRate() {
396
+ const max = this.config.security.maxPlaybackRate;
397
+ if (this.videoElement.playbackRate > max) {
398
+ this.videoElement.playbackRate = max;
399
+ this.log(`Playback rate clamped to ${max}`);
400
+ }
401
+ }
55
402
  async initialize() {
56
403
  try {
57
404
  this.setState(PlayerState.LOADING);
58
405
  this.embedToken = await this.fetchEmbedToken();
59
406
  this.log('Embed token received', this.embedToken);
60
407
  await this.initializePlayer();
408
+ await this.fetchAndApplyWatermark();
61
409
  }
62
410
  catch (err) {
63
411
  this.handleError({
@@ -68,6 +416,35 @@ class GuardVideoPlayer {
68
416
  });
69
417
  }
70
418
  }
419
+ async fetchAndApplyWatermark() {
420
+ if (!this.embedToken || !this.config.apiBaseUrl)
421
+ return;
422
+ try {
423
+ const tokenId = this.embedToken.tokenId;
424
+ const url = `${this.config.apiBaseUrl}/videos/stream/${this.videoId}/viewer-config?token=${encodeURIComponent(tokenId)}`;
425
+ const resp = await fetch(url, { credentials: 'omit' });
426
+ if (!resp.ok) {
427
+ this.log('viewer-config fetch failed, falling back to SDK config', resp.status);
428
+ const sec = this.config.security;
429
+ if (sec.enableWatermark && sec.watermarkText && this.container) {
430
+ this.createWatermark(sec.watermarkText);
431
+ }
432
+ return;
433
+ }
434
+ const cfg = await resp.json();
435
+ this.log('Watermark config from server:', cfg);
436
+ if (cfg.enableWatermark && cfg.watermarkText && this.container) {
437
+ this.createWatermark(cfg.watermarkText);
438
+ }
439
+ }
440
+ catch (err) {
441
+ this.log('fetchAndApplyWatermark error (non-fatal):', err);
442
+ const sec = this.config.security;
443
+ if (sec.enableWatermark && sec.watermarkText && this.container) {
444
+ this.createWatermark(sec.watermarkText);
445
+ }
446
+ }
447
+ }
71
448
  async fetchEmbedToken() {
72
449
  const url = `${this.config.embedTokenEndpoint}/${this.videoId}`;
73
450
  this.log('Fetching embed token from', url);
@@ -80,6 +457,9 @@ class GuardVideoPlayer {
80
457
  allowedDomain: window.location.origin,
81
458
  expiresInMinutes: 120,
82
459
  maxViews: null,
460
+ ...(this.config.viewerName ? { viewerName: this.config.viewerName } : {}),
461
+ ...(this.config.viewerEmail ? { viewerEmail: this.config.viewerEmail } : {}),
462
+ ...(this.config.forensicWatermark ? { forensicWatermark: this.config.forensicWatermark } : {}),
83
463
  }),
84
464
  });
85
465
  if (!response.ok) {
@@ -256,6 +636,17 @@ class GuardVideoPlayer {
256
636
  }
257
637
  destroy() {
258
638
  this.log('Destroying player');
639
+ const target = this.container || this.videoElement;
640
+ target.removeEventListener('contextmenu', this._onCtx);
641
+ target.removeEventListener('selectstart', this._onSelectStart);
642
+ this.videoElement.removeEventListener('dragstart', this._onDragStart);
643
+ this.videoElement.removeEventListener('ratechange', this._onRateChange);
644
+ document.removeEventListener('click', this._onDocClick);
645
+ document.removeEventListener('keydown', this._onKeyDown);
646
+ this.hideContextMenu();
647
+ this.watermarkObserver?.disconnect();
648
+ this.watermarkEl?.remove();
649
+ this.ctxStyleTag?.remove();
259
650
  if (this.hls) {
260
651
  this.hls.destroy();
261
652
  this.hls = null;
@@ -267,7 +658,7 @@ class GuardVideoPlayer {
267
658
  }
268
659
 
269
660
  const GuardVideoPlayerComponent = forwardRef((props, ref) => {
270
- const { videoId, width = '100%', height = 'auto', embedTokenEndpoint, apiBaseUrl, debug = false, autoplay = false, controls = true, className = '', style = {}, hlsConfig, onReady, onError, onQualityChange, onStateChange, onTimeUpdate, onEnded, } = props;
661
+ const { videoId, width = '100%', height = 'auto', embedTokenEndpoint, apiBaseUrl, debug = false, autoplay = false, controls = true, className = '', style = {}, hlsConfig, branding, contextMenuItems, security, onReady, onError, onQualityChange, onStateChange, onTimeUpdate, onEnded, } = props;
271
662
  const videoRef = useRef(null);
272
663
  const playerRef = useRef(null);
273
664
  const [error, setError] = useState(null);
@@ -284,6 +675,9 @@ const GuardVideoPlayerComponent = forwardRef((props, ref) => {
284
675
  className,
285
676
  style,
286
677
  hlsConfig,
678
+ branding,
679
+ contextMenuItems,
680
+ security,
287
681
  onReady: () => {
288
682
  onReady?.();
289
683
  },