@guardvideo/player-sdk 1.0.0 → 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.
- package/README.md +2 -2
- package/dist/core/player.d.ts +21 -0
- package/dist/core/player.d.ts.map +1 -1
- package/dist/core/types.d.ts +33 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.esm.js +395 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +395 -1
- package/dist/index.js.map +1 -1
- package/dist/react/GuardVideoPlayer.d.ts +4 -1
- package/dist/react/GuardVideoPlayer.d.ts.map +1 -1
- package/dist/react/hooks.d.ts +5 -2
- package/dist/react/hooks.d.ts.map +1 -1
- package/dist/vanilla/core/player.d.ts +21 -0
- package/dist/vanilla/core/player.d.ts.map +1 -1
- package/dist/vanilla/core/types.d.ts +33 -0
- package/dist/vanilla/core/types.d.ts.map +1 -1
- package/dist/vanilla/guardvideo-player.js +391 -0
- package/dist/vanilla/guardvideo-player.js.map +1 -1
- package/dist/vanilla/guardvideo-player.min.js +391 -0
- package/dist/vanilla/guardvideo-player.min.js.map +1 -1
- package/dist/vanilla/react/GuardVideoPlayer.d.ts +4 -1
- package/dist/vanilla/react/GuardVideoPlayer.d.ts.map +1 -1
- package/dist/vanilla/react/hooks.d.ts +5 -2
- package/dist/vanilla/react/hooks.d.ts.map +1 -1
- package/dist/vanilla/vanilla/index.d.ts +2 -2
- package/dist/vanilla/vanilla/index.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ yarn add @guardvideo/player-sdk
|
|
|
31
31
|
### CDN (for vanilla JavaScript)
|
|
32
32
|
|
|
33
33
|
```html
|
|
34
|
-
<script src="https://cdn.jsdelivr.net/npm/@guardvideo/player-sdk@latest/dist/guardvideo-player.
|
|
34
|
+
<script src="https://cdn.jsdelivr.net/npm/@guardvideo/player-sdk@latest/dist/vanilla/guardvideo-player.js"></script>
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
---
|
|
@@ -151,7 +151,7 @@ export default function VideoPage({ params }: { params: { videoId: string } }) {
|
|
|
151
151
|
<body>
|
|
152
152
|
<div id="video-container"></div>
|
|
153
153
|
|
|
154
|
-
<script src="https://cdn.jsdelivr.net/npm/@guardvideo/player-sdk@latest/dist/guardvideo-player.
|
|
154
|
+
<script src="https://cdn.jsdelivr.net/npm/@guardvideo/player-sdk@latest/dist/vanilla/guardvideo-player.js"></script>
|
|
155
155
|
<script>
|
|
156
156
|
// Create player
|
|
157
157
|
const player = GuardVideoPlayer.create('video-container', 'your-video-id', {
|
package/dist/core/player.d.ts
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -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;
|
package/dist/core/types.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
},
|