@videncrypt/js 0.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/README.md +232 -0
- package/dist/ve.cjs +382 -0
- package/dist/ve.cjs.map +1 -0
- package/dist/ve.d.cts +95 -0
- package/dist/ve.d.ts +95 -0
- package/dist/ve.js +2 -0
- package/dist/ve.js.map +1 -0
- package/dist/ve.mjs +355 -0
- package/dist/ve.mjs.map +1 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# @videncrypt/js
|
|
2
|
+
|
|
3
|
+
JavaScript SDK for embedding secure VidEncrypt videos on any website.
|
|
4
|
+
Works with plain HTML, Vue, Angular, Svelte, or any framework.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
### npm
|
|
9
|
+
```bash
|
|
10
|
+
npm install @videncrypt/js
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### CDN (script tag)
|
|
14
|
+
```html
|
|
15
|
+
<script src="https://cdn.videncrypt.com/sdk/ve.js"></script>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Quick start — script tag
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<div id="player"></div>
|
|
24
|
+
<script src="https://cdn.videncrypt.com/sdk/ve.js"></script>
|
|
25
|
+
<script>
|
|
26
|
+
const player = new VidEncrypt.Player({
|
|
27
|
+
videoId: 'YOUR_VIDEO_ID',
|
|
28
|
+
container: '#player',
|
|
29
|
+
onPlay: () => console.log('playing'),
|
|
30
|
+
onEnded: () => console.log('ended'),
|
|
31
|
+
});
|
|
32
|
+
</script>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick start — npm / ESM
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
import { Player } from '@videncrypt/js';
|
|
39
|
+
|
|
40
|
+
const player = new Player({
|
|
41
|
+
videoId: 'YOUR_VIDEO_ID',
|
|
42
|
+
container: '#player',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
player.on('ready', () => console.log('ready'));
|
|
46
|
+
player.on('play', () => console.log('playing'));
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Options
|
|
52
|
+
|
|
53
|
+
| Option | Type | Default | Description |
|
|
54
|
+
|---|---|---|---|
|
|
55
|
+
| `videoId` | `string` | **required** | Video ID from your VidEncrypt dashboard |
|
|
56
|
+
| `container` | `string \| HTMLElement` | **required** | CSS selector or DOM element |
|
|
57
|
+
| `width` | `string \| number` | `'100%'` | Player width |
|
|
58
|
+
| `aspectRatio` | `string` | `'16/9'` | Aspect ratio e.g. `'4/3'`, `'1/1'` |
|
|
59
|
+
| `title` | `string` | `''` | Player iframe title (accessibility) |
|
|
60
|
+
| `autoPlay` | `boolean` | `false` | Auto-play on load (requires `muted: true` in most browsers) |
|
|
61
|
+
| `muted` | `boolean` | `false` | Start muted |
|
|
62
|
+
| `loop` | `boolean` | `false` | Loop video |
|
|
63
|
+
| `startTime` | `number` | `0` | Start playback at N seconds |
|
|
64
|
+
| `showControls` | `boolean` | `true` | Show player controls |
|
|
65
|
+
| `showBranding` | `boolean` | `true` | Show VidEncrypt branding badge |
|
|
66
|
+
| `primaryColor` | `string` | `'#2563EB'` | Accent color (hex) |
|
|
67
|
+
| `embedBaseUrl` | `string` | `'https://app.videncrypt.com'` | Override for self-hosted |
|
|
68
|
+
| `onReady` | `() => void` | — | Player is ready |
|
|
69
|
+
| `onPlay` | `() => void` | — | Playback started |
|
|
70
|
+
| `onPause` | `() => void` | — | Playback paused |
|
|
71
|
+
| `onEnded` | `() => void` | — | Video ended |
|
|
72
|
+
| `onProgress` | `(currentTime, duration) => void` | — | Playback position update |
|
|
73
|
+
| `onError` | `(error: PlayerError) => void` | — | Playback error |
|
|
74
|
+
| `onFullscreenChange` | `(isFullscreen: boolean) => void` | — | Fullscreen toggled |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Methods
|
|
79
|
+
|
|
80
|
+
| Method | Returns | Description |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `play()` | `void` | Start playback |
|
|
83
|
+
| `pause()` | `void` | Pause playback |
|
|
84
|
+
| `seek(time)` | `void` | Seek to time in seconds |
|
|
85
|
+
| `setVolume(volume)` | `void` | Set volume 0–1 |
|
|
86
|
+
| `mute()` | `void` | Mute audio |
|
|
87
|
+
| `unmute()` | `void` | Unmute audio |
|
|
88
|
+
| `enterFullscreen()` | `void` | Enter fullscreen |
|
|
89
|
+
| `exitFullscreen()` | `void` | Exit fullscreen |
|
|
90
|
+
| `getState()` | `PlayerState` | Get current player state (readonly copy) |
|
|
91
|
+
| `isReady()` | `boolean` | Returns true after player is ready |
|
|
92
|
+
| `isPlaying()` | `boolean` | Returns true while playing |
|
|
93
|
+
| `isMuted()` | `boolean` | Returns true while muted |
|
|
94
|
+
| `getCurrentTime()` | `number` | Current playback position in seconds |
|
|
95
|
+
| `getDuration()` | `number` | Total video duration in seconds |
|
|
96
|
+
| `on(event, handler)` | `this` | Add event listener (chainable) |
|
|
97
|
+
| `off(event, handler)` | `this` | Remove event listener |
|
|
98
|
+
| `once(event, handler)` | `this` | Add one-time event listener |
|
|
99
|
+
| `destroy()` | `void` | Remove player from DOM and clean up |
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Events
|
|
104
|
+
|
|
105
|
+
| Event | Payload | Description |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| `ready` | — | Player initialized and ready |
|
|
108
|
+
| `play` | — | Playback started or resumed |
|
|
109
|
+
| `pause` | — | Playback paused |
|
|
110
|
+
| `ended` | — | Video reached the end |
|
|
111
|
+
| `progress` | `{ currentTime: number, duration: number }` | Fires every ~5 seconds while playing |
|
|
112
|
+
| `error` | `PlayerError` | Playback or network error |
|
|
113
|
+
| `fullscreenchange` | `boolean` | Fullscreen state changed |
|
|
114
|
+
| `statechange` | `Partial<PlayerState>` | Any state property changed |
|
|
115
|
+
|
|
116
|
+
### PlayerError
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
interface PlayerError {
|
|
120
|
+
code: 'token-expired' | 'not-found' | 'domain-not-allowed'
|
|
121
|
+
| 'network-error' | 'unknown';
|
|
122
|
+
message: string;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Framework examples
|
|
129
|
+
|
|
130
|
+
### Vue 3
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
import { onMounted, onUnmounted } from 'vue';
|
|
134
|
+
import { Player } from '@videncrypt/js';
|
|
135
|
+
|
|
136
|
+
export default {
|
|
137
|
+
setup() {
|
|
138
|
+
let player = null;
|
|
139
|
+
|
|
140
|
+
onMounted(() => {
|
|
141
|
+
player = new Player({
|
|
142
|
+
videoId: 'YOUR_VIDEO_ID',
|
|
143
|
+
container: '#player',
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
onUnmounted(() => player?.destroy());
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Angular
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { Component, OnInit, OnDestroy } from '@angular/core';
|
|
156
|
+
import { Player } from '@videncrypt/js';
|
|
157
|
+
|
|
158
|
+
@Component({
|
|
159
|
+
selector: 'app-video',
|
|
160
|
+
template: '<div id="player"></div>',
|
|
161
|
+
})
|
|
162
|
+
export class VideoComponent implements OnInit, OnDestroy {
|
|
163
|
+
private player: InstanceType<typeof Player>;
|
|
164
|
+
|
|
165
|
+
ngOnInit() {
|
|
166
|
+
this.player = new Player({
|
|
167
|
+
videoId: 'YOUR_VIDEO_ID',
|
|
168
|
+
container: '#player',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
ngOnDestroy() {
|
|
173
|
+
this.player.destroy();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Svelte
|
|
179
|
+
|
|
180
|
+
```svelte
|
|
181
|
+
<script>
|
|
182
|
+
import { onMount, onDestroy } from 'svelte';
|
|
183
|
+
import { Player } from '@videncrypt/js';
|
|
184
|
+
|
|
185
|
+
let player;
|
|
186
|
+
|
|
187
|
+
onMount(() => {
|
|
188
|
+
player = new Player({
|
|
189
|
+
videoId: 'YOUR_VIDEO_ID',
|
|
190
|
+
container: '#player',
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
onDestroy(() => player?.destroy());
|
|
195
|
+
</script>
|
|
196
|
+
|
|
197
|
+
<div id="player"></div>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Multiple players on one page
|
|
203
|
+
|
|
204
|
+
Each player filters postMessage events by `videoId` so multiple
|
|
205
|
+
instances never interfere with each other.
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
const player1 = new Player({ videoId: 'video-aaa', container: '#player1' });
|
|
209
|
+
const player2 = new Player({ videoId: 'video-bbb', container: '#player2' });
|
|
210
|
+
|
|
211
|
+
// Events on player1 never fire on player2 and vice versa
|
|
212
|
+
player1.on('play', () => console.log('player1 playing'));
|
|
213
|
+
player2.on('play', () => console.log('player2 playing'));
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Cleanup
|
|
219
|
+
|
|
220
|
+
Always call `destroy()` when removing the player to prevent memory
|
|
221
|
+
leaks and stop ongoing network requests.
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
// React
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
const player = new Player({ videoId, container: '#player' });
|
|
227
|
+
return () => player.destroy();
|
|
228
|
+
}, [videoId]);
|
|
229
|
+
|
|
230
|
+
// Plain JS
|
|
231
|
+
window.addEventListener('beforeunload', () => player.destroy());
|
|
232
|
+
```
|
package/dist/ve.cjs
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
Player: () => VidEncryptPlayer
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(src_exports);
|
|
26
|
+
|
|
27
|
+
// src/events.ts
|
|
28
|
+
var EventEmitter = class {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
31
|
+
}
|
|
32
|
+
on(event, handler) {
|
|
33
|
+
if (!this.handlers.has(event)) {
|
|
34
|
+
this.handlers.set(event, /* @__PURE__ */ new Set());
|
|
35
|
+
}
|
|
36
|
+
this.handlers.get(event).add(handler);
|
|
37
|
+
}
|
|
38
|
+
off(event, handler) {
|
|
39
|
+
var _a;
|
|
40
|
+
(_a = this.handlers.get(event)) == null ? void 0 : _a.delete(handler);
|
|
41
|
+
}
|
|
42
|
+
once(event, handler) {
|
|
43
|
+
const wrapper = (data) => {
|
|
44
|
+
handler(data);
|
|
45
|
+
this.off(event, wrapper);
|
|
46
|
+
};
|
|
47
|
+
this.on(event, wrapper);
|
|
48
|
+
}
|
|
49
|
+
emit(event, data) {
|
|
50
|
+
var _a;
|
|
51
|
+
(_a = this.handlers.get(event)) == null ? void 0 : _a.forEach((handler) => {
|
|
52
|
+
try {
|
|
53
|
+
handler(data);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.warn("[VidEncrypt] event handler error:", err);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
removeAll() {
|
|
60
|
+
this.handlers.clear();
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/utils.ts
|
|
65
|
+
function parseAspectRatio(ratio) {
|
|
66
|
+
const parts = ratio.split("/").map(Number);
|
|
67
|
+
const w = parts[0];
|
|
68
|
+
const h = parts[1];
|
|
69
|
+
if (!w || !h || isNaN(w) || isNaN(h) || w <= 0 || h <= 0) {
|
|
70
|
+
return { w: 16, h: 9 };
|
|
71
|
+
}
|
|
72
|
+
return { w, h };
|
|
73
|
+
}
|
|
74
|
+
function aspectRatioToPadding(ratio) {
|
|
75
|
+
const { w, h } = parseAspectRatio(ratio);
|
|
76
|
+
return `${(h / w * 100).toFixed(4)}%`;
|
|
77
|
+
}
|
|
78
|
+
function normalizeWidth(width) {
|
|
79
|
+
if (width === void 0 || width === null) return "100%";
|
|
80
|
+
if (typeof width === "number") return `${width}px`;
|
|
81
|
+
return width;
|
|
82
|
+
}
|
|
83
|
+
function isValidVideoId(id) {
|
|
84
|
+
return typeof id === "string" && id.trim().length > 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/iframe.ts
|
|
88
|
+
function createIframe(opts) {
|
|
89
|
+
const url = new URL(`${opts.embedBaseUrl}/embed/${opts.videoId}`);
|
|
90
|
+
if (opts.autoPlay) url.searchParams.set("autoplay", "1");
|
|
91
|
+
if (opts.muted) url.searchParams.set("muted", "1");
|
|
92
|
+
if (opts.loop) url.searchParams.set("loop", "1");
|
|
93
|
+
if (!opts.showControls) url.searchParams.set("controls", "0");
|
|
94
|
+
if (!opts.showBranding) url.searchParams.set("branding", "0");
|
|
95
|
+
if (opts.startTime && opts.startTime > 0) {
|
|
96
|
+
url.searchParams.set("start", String(opts.startTime));
|
|
97
|
+
}
|
|
98
|
+
if (opts.primaryColor && opts.primaryColor !== "#2563EB") {
|
|
99
|
+
url.searchParams.set("color", opts.primaryColor.replace("#", ""));
|
|
100
|
+
}
|
|
101
|
+
if (opts.title) {
|
|
102
|
+
url.searchParams.set("title", encodeURIComponent(opts.title));
|
|
103
|
+
}
|
|
104
|
+
const wrapper = document.createElement("div");
|
|
105
|
+
wrapper.style.cssText = [
|
|
106
|
+
"position: relative",
|
|
107
|
+
`width: ${normalizeWidth(opts.width)}`,
|
|
108
|
+
"background: #000",
|
|
109
|
+
"line-height: 0"
|
|
110
|
+
// prevents gap below iframe
|
|
111
|
+
].join("; ");
|
|
112
|
+
const aspectBox = document.createElement("div");
|
|
113
|
+
aspectBox.style.cssText = [
|
|
114
|
+
"position: relative",
|
|
115
|
+
`padding-bottom: ${aspectRatioToPadding(opts.aspectRatio)}`,
|
|
116
|
+
"height: 0",
|
|
117
|
+
"overflow: hidden"
|
|
118
|
+
].join("; ");
|
|
119
|
+
const iframe = document.createElement("iframe");
|
|
120
|
+
iframe.src = url.toString();
|
|
121
|
+
iframe.title = opts.title || "Video Player";
|
|
122
|
+
iframe.frameBorder = "0";
|
|
123
|
+
iframe.allowFullscreen = true;
|
|
124
|
+
iframe.setAttribute(
|
|
125
|
+
"allow",
|
|
126
|
+
"autoplay; fullscreen; picture-in-picture; clipboard-write"
|
|
127
|
+
);
|
|
128
|
+
iframe.setAttribute("loading", "lazy");
|
|
129
|
+
iframe.style.cssText = [
|
|
130
|
+
"position: absolute",
|
|
131
|
+
"top: 0",
|
|
132
|
+
"left: 0",
|
|
133
|
+
"width: 100%",
|
|
134
|
+
"height: 100%",
|
|
135
|
+
"border: none"
|
|
136
|
+
].join("; ");
|
|
137
|
+
aspectBox.appendChild(iframe);
|
|
138
|
+
wrapper.appendChild(aspectBox);
|
|
139
|
+
return { iframe, wrapper };
|
|
140
|
+
}
|
|
141
|
+
function destroyIframe(wrapper) {
|
|
142
|
+
const iframe = wrapper.querySelector("iframe");
|
|
143
|
+
if (iframe) {
|
|
144
|
+
iframe.src = "about:blank";
|
|
145
|
+
}
|
|
146
|
+
wrapper.remove();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/player.ts
|
|
150
|
+
var DEFAULTS = {
|
|
151
|
+
width: "100%",
|
|
152
|
+
aspectRatio: "16/9",
|
|
153
|
+
title: "",
|
|
154
|
+
autoPlay: false,
|
|
155
|
+
muted: false,
|
|
156
|
+
loop: false,
|
|
157
|
+
startTime: 0,
|
|
158
|
+
showControls: true,
|
|
159
|
+
showBranding: true,
|
|
160
|
+
primaryColor: "#2563EB",
|
|
161
|
+
embedBaseUrl: "https://app.videncrypt.com"
|
|
162
|
+
};
|
|
163
|
+
var VidEncryptPlayer = class {
|
|
164
|
+
constructor(options) {
|
|
165
|
+
this.iframe = null;
|
|
166
|
+
this.wrapper = null;
|
|
167
|
+
this.destroyed = false;
|
|
168
|
+
this.state = {
|
|
169
|
+
playing: false,
|
|
170
|
+
muted: false,
|
|
171
|
+
currentTime: 0,
|
|
172
|
+
duration: 0,
|
|
173
|
+
fullscreen: false,
|
|
174
|
+
ready: false
|
|
175
|
+
};
|
|
176
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
177
|
+
if (typeof options.container === "string") {
|
|
178
|
+
const el = document.querySelector(options.container);
|
|
179
|
+
if (!el) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`VidEncrypt: container "${options.container}" not found`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
this.container = el;
|
|
185
|
+
} else if (options.container instanceof HTMLElement) {
|
|
186
|
+
this.container = options.container;
|
|
187
|
+
} else {
|
|
188
|
+
throw new Error(
|
|
189
|
+
"VidEncrypt: container must be a CSS selector or HTMLElement"
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
if (!isValidVideoId(options.videoId)) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
"VidEncrypt: videoId is required and must be a non-empty string"
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
this.opts = {
|
|
198
|
+
...DEFAULTS,
|
|
199
|
+
...options,
|
|
200
|
+
// Strip undefined callbacks so they don't overwrite defaults
|
|
201
|
+
onReady: (_a = options.onReady) != null ? _a : void 0,
|
|
202
|
+
onPlay: (_b = options.onPlay) != null ? _b : void 0,
|
|
203
|
+
onPause: (_c = options.onPause) != null ? _c : void 0,
|
|
204
|
+
onEnded: (_d = options.onEnded) != null ? _d : void 0,
|
|
205
|
+
onProgress: (_e = options.onProgress) != null ? _e : void 0,
|
|
206
|
+
onError: (_f = options.onError) != null ? _f : void 0,
|
|
207
|
+
onFullscreenChange: (_g = options.onFullscreenChange) != null ? _g : void 0
|
|
208
|
+
};
|
|
209
|
+
this.emitter = new EventEmitter();
|
|
210
|
+
if (options.onReady) {
|
|
211
|
+
this.emitter.on("ready", options.onReady);
|
|
212
|
+
}
|
|
213
|
+
if (options.onPlay) {
|
|
214
|
+
this.emitter.on("play", options.onPlay);
|
|
215
|
+
}
|
|
216
|
+
if (options.onPause) {
|
|
217
|
+
this.emitter.on("pause", options.onPause);
|
|
218
|
+
}
|
|
219
|
+
if (options.onEnded) {
|
|
220
|
+
this.emitter.on("ended", options.onEnded);
|
|
221
|
+
}
|
|
222
|
+
if (options.onProgress) {
|
|
223
|
+
this.emitter.on(
|
|
224
|
+
"progress",
|
|
225
|
+
(d) => options.onProgress(d.currentTime, d.duration)
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (options.onError) {
|
|
229
|
+
this.emitter.on("error", options.onError);
|
|
230
|
+
}
|
|
231
|
+
if (options.onFullscreenChange) {
|
|
232
|
+
this.emitter.on("fullscreenchange", options.onFullscreenChange);
|
|
233
|
+
}
|
|
234
|
+
this.msgHandler = this.handleMessage.bind(this);
|
|
235
|
+
window.addEventListener("message", this.msgHandler);
|
|
236
|
+
const { iframe, wrapper } = createIframe(this.opts);
|
|
237
|
+
this.iframe = iframe;
|
|
238
|
+
this.wrapper = wrapper;
|
|
239
|
+
this.container.appendChild(wrapper);
|
|
240
|
+
}
|
|
241
|
+
// ── Playback controls ──────────────────────────────────
|
|
242
|
+
play() {
|
|
243
|
+
this.send("play");
|
|
244
|
+
}
|
|
245
|
+
pause() {
|
|
246
|
+
this.send("pause");
|
|
247
|
+
}
|
|
248
|
+
seek(time) {
|
|
249
|
+
this.send("seek", Math.max(0, time));
|
|
250
|
+
}
|
|
251
|
+
setVolume(volume) {
|
|
252
|
+
this.send("volume", Math.min(1, Math.max(0, volume)));
|
|
253
|
+
}
|
|
254
|
+
mute() {
|
|
255
|
+
this.send("mute");
|
|
256
|
+
}
|
|
257
|
+
unmute() {
|
|
258
|
+
this.send("unmute");
|
|
259
|
+
}
|
|
260
|
+
enterFullscreen() {
|
|
261
|
+
this.send("fullscreen");
|
|
262
|
+
}
|
|
263
|
+
exitFullscreen() {
|
|
264
|
+
this.send("exitFullscreen");
|
|
265
|
+
}
|
|
266
|
+
// ── State ──────────────────────────────────────────────
|
|
267
|
+
getState() {
|
|
268
|
+
return { ...this.state };
|
|
269
|
+
}
|
|
270
|
+
isReady() {
|
|
271
|
+
return this.state.ready;
|
|
272
|
+
}
|
|
273
|
+
isPlaying() {
|
|
274
|
+
return this.state.playing;
|
|
275
|
+
}
|
|
276
|
+
isMuted() {
|
|
277
|
+
return this.state.muted;
|
|
278
|
+
}
|
|
279
|
+
getCurrentTime() {
|
|
280
|
+
return this.state.currentTime;
|
|
281
|
+
}
|
|
282
|
+
getDuration() {
|
|
283
|
+
return this.state.duration;
|
|
284
|
+
}
|
|
285
|
+
// ── Event API ──────────────────────────────────────────
|
|
286
|
+
// All return `this` for chaining:
|
|
287
|
+
// player.on('play', ...).on('ended', ...)
|
|
288
|
+
on(event, handler) {
|
|
289
|
+
this.emitter.on(event, handler);
|
|
290
|
+
return this;
|
|
291
|
+
}
|
|
292
|
+
off(event, handler) {
|
|
293
|
+
this.emitter.off(event, handler);
|
|
294
|
+
return this;
|
|
295
|
+
}
|
|
296
|
+
once(event, handler) {
|
|
297
|
+
this.emitter.once(event, handler);
|
|
298
|
+
return this;
|
|
299
|
+
}
|
|
300
|
+
// ── Destroy ────────────────────────────────────────────
|
|
301
|
+
destroy() {
|
|
302
|
+
if (this.destroyed) return;
|
|
303
|
+
this.destroyed = true;
|
|
304
|
+
window.removeEventListener("message", this.msgHandler);
|
|
305
|
+
if (this.wrapper) {
|
|
306
|
+
destroyIframe(this.wrapper);
|
|
307
|
+
this.wrapper = null;
|
|
308
|
+
this.iframe = null;
|
|
309
|
+
}
|
|
310
|
+
this.emitter.removeAll();
|
|
311
|
+
}
|
|
312
|
+
// ── Private: send command to iframe ───────────────────
|
|
313
|
+
send(action, value) {
|
|
314
|
+
var _a;
|
|
315
|
+
if (this.destroyed) return;
|
|
316
|
+
if (!((_a = this.iframe) == null ? void 0 : _a.contentWindow)) return;
|
|
317
|
+
const cmd = {
|
|
318
|
+
action,
|
|
319
|
+
videoId: this.opts.videoId,
|
|
320
|
+
value
|
|
321
|
+
};
|
|
322
|
+
this.iframe.contentWindow.postMessage(cmd, "*");
|
|
323
|
+
}
|
|
324
|
+
// ── Private: handle incoming postMessage ──────────────
|
|
325
|
+
handleMessage(e) {
|
|
326
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
327
|
+
const msg = e.data;
|
|
328
|
+
if (!msg || msg.type !== "videncrypt:player") return;
|
|
329
|
+
if (msg.videoId !== this.opts.videoId) return;
|
|
330
|
+
switch (msg.event) {
|
|
331
|
+
case "ready":
|
|
332
|
+
this.state.ready = true;
|
|
333
|
+
this.emitter.emit("ready", void 0);
|
|
334
|
+
break;
|
|
335
|
+
case "play":
|
|
336
|
+
this.state.playing = true;
|
|
337
|
+
this.emitter.emit("play", void 0);
|
|
338
|
+
break;
|
|
339
|
+
case "pause":
|
|
340
|
+
this.state.playing = false;
|
|
341
|
+
this.emitter.emit("pause", void 0);
|
|
342
|
+
break;
|
|
343
|
+
case "ended":
|
|
344
|
+
this.state.playing = false;
|
|
345
|
+
this.emitter.emit("ended", void 0);
|
|
346
|
+
break;
|
|
347
|
+
case "progress": {
|
|
348
|
+
const currentTime = (_b = (_a = msg.data) == null ? void 0 : _a.currentTime) != null ? _b : 0;
|
|
349
|
+
const duration = (_d = (_c = msg.data) == null ? void 0 : _c.duration) != null ? _d : 0;
|
|
350
|
+
this.state.currentTime = currentTime;
|
|
351
|
+
this.state.duration = duration;
|
|
352
|
+
this.emitter.emit("progress", { currentTime, duration });
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
case "error": {
|
|
356
|
+
const error = {
|
|
357
|
+
code: (_f = (_e = msg.data) == null ? void 0 : _e.code) != null ? _f : "unknown",
|
|
358
|
+
message: (_h = (_g = msg.data) == null ? void 0 : _g.message) != null ? _h : "Unknown error"
|
|
359
|
+
};
|
|
360
|
+
this.emitter.emit("error", error);
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
case "fullscreenchange": {
|
|
364
|
+
const isFullscreen = (_j = (_i = msg.data) == null ? void 0 : _i.isFullscreen) != null ? _j : false;
|
|
365
|
+
this.state.fullscreen = isFullscreen;
|
|
366
|
+
this.emitter.emit("fullscreenchange", isFullscreen);
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
case "statechange": {
|
|
370
|
+
const partial = (_k = msg.data) != null ? _k : {};
|
|
371
|
+
this.state = { ...this.state, ...partial };
|
|
372
|
+
this.emitter.emit("statechange", partial);
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
379
|
+
0 && (module.exports = {
|
|
380
|
+
Player
|
|
381
|
+
});
|
|
382
|
+
//# sourceMappingURL=ve.cjs.map
|
package/dist/ve.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/events.ts","../src/utils.ts","../src/iframe.ts","../src/player.ts"],"sourcesContent":["// Named export for npm / ESM usage:\n// import { Player } from '@videncrypt/js'\nexport { VidEncryptPlayer as Player } from './player';\n\n// Type exports\nexport type {\n PlayerOptions,\n PlayerState,\n PlayerError,\n PlayerEventMap,\n IframeMessage,\n IframeCommand,\n IframeEvent,\n IframeAction,\n} from './types';\n\n// When built as IIFE for CDN, tsup sets globalName: 'VidEncrypt'\n// so window.VidEncrypt.Player is available after the script tag.\n","type AnyHandler = (data: unknown) => void;\n\n// The index signature makes EventMap compatible with Record<string, unknown>\n// which TypeScript requires for generic constraints on Map keys\nexport type EventMapBase = { [key: string]: unknown };\n\nexport class EventEmitter<EventMap extends EventMapBase> {\n private handlers = new Map<keyof EventMap, Set<AnyHandler>>();\n\n on<K extends keyof EventMap>(\n event: K,\n handler: (data: EventMap[K]) => void,\n ): void {\n if (!this.handlers.has(event)) {\n this.handlers.set(event, new Set());\n }\n this.handlers.get(event)!.add(handler as AnyHandler);\n }\n\n off<K extends keyof EventMap>(\n event: K,\n handler: (data: EventMap[K]) => void,\n ): void {\n this.handlers.get(event)?.delete(handler as AnyHandler);\n }\n\n once<K extends keyof EventMap>(\n event: K,\n handler: (data: EventMap[K]) => void,\n ): void {\n const wrapper = (data: EventMap[K]) => {\n handler(data);\n this.off(event, wrapper);\n };\n this.on(event, wrapper);\n }\n\n emit<K extends keyof EventMap>(event: K, data: EventMap[K]): void {\n this.handlers.get(event)?.forEach((handler) => {\n try {\n handler(data as unknown);\n } catch (err) {\n // A broken handler must never crash the player\n // or prevent other handlers from firing\n console.warn('[VidEncrypt] event handler error:', err);\n }\n });\n }\n\n removeAll(): void {\n this.handlers.clear();\n }\n}","// ── Aspect ratio ───────────────────────────────────────────\n\nexport function parseAspectRatio(ratio: string): { w: number; h: number } {\n const parts = ratio.split('/').map(Number);\n const w = parts[0];\n const h = parts[1];\n if (!w || !h || isNaN(w) || isNaN(h) || w <= 0 || h <= 0) {\n return { w: 16, h: 9 };\n }\n return { w, h };\n}\n\n// '16/9' → '56.2500%'\n// '4/3' → '75.0000%'\n// '1/1' → '100.0000%'\nexport function aspectRatioToPadding(ratio: string): string {\n const { w, h } = parseAspectRatio(ratio);\n return `${((h / w) * 100).toFixed(4)}%`;\n}\n\n// ── Width normalisation ────────────────────────────────────\n\n// 640 → '640px'\n// '640px' → '640px'\n// '100%' → '100%'\n// undefined → '100%'\nexport function normalizeWidth(width?: string | number): string {\n if (width === undefined || width === null) return '100%';\n if (typeof width === 'number') return `${width}px`;\n return width;\n}\n\n// ── ID generation ──────────────────────────────────────────\n\n// Generates a short unique ID for internal use.\n// Format: 've_' + 8 random alphanumeric chars → 've_a3f9k2m1'\nexport function generateId(): string {\n const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';\n let id = 've_';\n for (let i = 0; i < 8; i++) {\n id += chars[Math.floor(Math.random() * chars.length)];\n }\n return id;\n}\n\n// ── Validation ─────────────────────────────────────────────\n\nexport function isValidVideoId(id: unknown): id is string {\n return typeof id === 'string' && id.trim().length > 0;\n}\n","import type { PlayerOptions } from './types';\nimport { aspectRatioToPadding, normalizeWidth } from './utils';\n\n// ── createIframe ───────────────────────────────────────────\n\nexport function createIframe(opts: Required<PlayerOptions>): {\n iframe: HTMLIFrameElement;\n wrapper: HTMLElement;\n} {\n // ── Build embed URL ────────────────────────────────────\n const url = new URL(`${opts.embedBaseUrl}/embed/${opts.videoId}`);\n\n if (opts.autoPlay) url.searchParams.set('autoplay', '1');\n if (opts.muted) url.searchParams.set('muted', '1');\n if (opts.loop) url.searchParams.set('loop', '1');\n if (!opts.showControls) url.searchParams.set('controls', '0');\n if (!opts.showBranding) url.searchParams.set('branding', '0');\n if (opts.startTime && opts.startTime > 0) {\n url.searchParams.set('start', String(opts.startTime));\n }\n if (opts.primaryColor && opts.primaryColor !== '#2563EB') {\n url.searchParams.set('color', opts.primaryColor.replace('#', ''));\n }\n if (opts.title) {\n url.searchParams.set('title', encodeURIComponent(opts.title));\n }\n\n // ── Outer wrapper ──────────────────────────────────────\n const wrapper = document.createElement('div');\n wrapper.style.cssText = [\n 'position: relative',\n `width: ${normalizeWidth(opts.width)}`,\n 'background: #000',\n 'line-height: 0', // prevents gap below iframe\n ].join('; ');\n\n // ── Aspect ratio box ───────────────────────────────────\n // padding-bottom trick works in all browsers including\n // old Safari — no CSS aspect-ratio property needed\n const aspectBox = document.createElement('div');\n aspectBox.style.cssText = [\n 'position: relative',\n `padding-bottom: ${aspectRatioToPadding(opts.aspectRatio)}`,\n 'height: 0',\n 'overflow: hidden',\n ].join('; ');\n\n // ── iframe ─────────────────────────────────────────────\n const iframe = document.createElement('iframe');\n\n iframe.src = url.toString();\n iframe.title = opts.title || 'Video Player';\n iframe.frameBorder = '0';\n iframe.allowFullscreen = true;\n iframe.setAttribute('allow',\n 'autoplay; fullscreen; picture-in-picture; clipboard-write',\n );\n iframe.setAttribute('loading', 'lazy');\n\n iframe.style.cssText = [\n 'position: absolute',\n 'top: 0',\n 'left: 0',\n 'width: 100%',\n 'height: 100%',\n 'border: none',\n ].join('; ');\n\n aspectBox.appendChild(iframe);\n wrapper.appendChild(aspectBox);\n\n return { iframe, wrapper };\n}\n\n// ── destroyIframe ──────────────────────────────────────────\n\nexport function destroyIframe(wrapper: HTMLElement): void {\n // Blank src first — stops any ongoing HLS segment downloads\n // and frees memory held by the iframe's JS context\n const iframe = wrapper.querySelector('iframe');\n if (iframe) {\n iframe.src = 'about:blank';\n }\n wrapper.remove();\n}\n","import { EventEmitter, type EventMapBase } from './events';\nimport { createIframe, destroyIframe } from './iframe';\nimport { isValidVideoId } from './utils';\nimport type {\n PlayerOptions,\n PlayerState,\n PlayerEventMap,\n IframeMessage,\n IframeCommand,\n IframeAction,\n PlayerError,\n} from './types';\n\n// ── Defaults ───────────────────────────────────────────────\n\nconst DEFAULTS = {\n width: '100%',\n aspectRatio: '16/9',\n title: '',\n autoPlay: false,\n muted: false,\n loop: false,\n startTime: 0,\n showControls: true,\n showBranding: true,\n primaryColor: '#2563EB',\n embedBaseUrl: 'https://app.videncrypt.com',\n} as const;\n\n// ── VidEncryptPlayer ───────────────────────────────────────\n\nexport class VidEncryptPlayer {\n private opts: Required<PlayerOptions>;\n private container: HTMLElement;\n private iframe: HTMLIFrameElement | null = null;\n private wrapper: HTMLElement | null = null;\n private emitter: EventEmitter<PlayerEventMap>;\n private msgHandler: (e: MessageEvent) => void;\n private destroyed = false;\n\n private state: PlayerState = {\n playing: false,\n muted: false,\n currentTime: 0,\n duration: 0,\n fullscreen: false,\n ready: false,\n };\n\n constructor(options: PlayerOptions) {\n\n // ── Resolve container ────────────────────────────────\n if (typeof options.container === 'string') {\n const el = document.querySelector(options.container);\n if (!el) {\n throw new Error(\n `VidEncrypt: container \"${options.container}\" not found`,\n );\n }\n this.container = el as HTMLElement;\n } else if (options.container instanceof HTMLElement) {\n this.container = options.container;\n } else {\n throw new Error(\n 'VidEncrypt: container must be a CSS selector or HTMLElement',\n );\n }\n\n // ── Validate videoId ─────────────────────────────────\n if (!isValidVideoId(options.videoId)) {\n throw new Error(\n 'VidEncrypt: videoId is required and must be a non-empty string',\n );\n }\n\n // ── Merge options with defaults ──────────────────────\n this.opts = {\n ...DEFAULTS,\n ...options,\n // Strip undefined callbacks so they don't overwrite defaults\n onReady: options.onReady ?? undefined,\n onPlay: options.onPlay ?? undefined,\n onPause: options.onPause ?? undefined,\n onEnded: options.onEnded ?? undefined,\n onProgress: options.onProgress ?? undefined,\n onError: options.onError ?? undefined,\n onFullscreenChange: options.onFullscreenChange ?? undefined,\n } as Required<PlayerOptions>;\n\n // ── Event emitter ────────────────────────────────────\n this.emitter = new EventEmitter<PlayerEventMap>();\n\n // Wire option callbacks to the emitter\n if (options.onReady) {\n this.emitter.on('ready', options.onReady);\n }\n if (options.onPlay) {\n this.emitter.on('play', options.onPlay);\n }\n if (options.onPause) {\n this.emitter.on('pause', options.onPause);\n }\n if (options.onEnded) {\n this.emitter.on('ended', options.onEnded);\n }\n if (options.onProgress) {\n this.emitter.on('progress', (d) =>\n options.onProgress!(d.currentTime, d.duration),\n );\n }\n if (options.onError) {\n this.emitter.on('error', options.onError);\n }\n if (options.onFullscreenChange) {\n this.emitter.on('fullscreenchange', options.onFullscreenChange);\n }\n\n // ── postMessage listener ─────────────────────────────\n this.msgHandler = this.handleMessage.bind(this);\n window.addEventListener('message', this.msgHandler);\n\n // ── Create iframe ────────────────────────────────────\n const { iframe, wrapper } = createIframe(this.opts);\n this.iframe = iframe;\n this.wrapper = wrapper;\n this.container.appendChild(wrapper);\n }\n\n // ── Playback controls ──────────────────────────────────\n\n play(): void {\n this.send('play');\n }\n\n pause(): void {\n this.send('pause');\n }\n\n seek(time: number): void {\n this.send('seek', Math.max(0, time));\n }\n\n setVolume(volume: number): void {\n this.send('volume', Math.min(1, Math.max(0, volume)));\n }\n\n mute(): void {\n this.send('mute');\n }\n\n unmute(): void {\n this.send('unmute');\n }\n\n enterFullscreen(): void {\n this.send('fullscreen');\n }\n\n exitFullscreen(): void {\n this.send('exitFullscreen');\n }\n\n // ── State ──────────────────────────────────────────────\n\n getState(): Readonly<PlayerState> {\n return { ...this.state };\n }\n\n isReady(): boolean { return this.state.ready; }\n isPlaying(): boolean { return this.state.playing; }\n isMuted(): boolean { return this.state.muted; }\n\n getCurrentTime(): number { return this.state.currentTime; }\n getDuration(): number { return this.state.duration; }\n\n // ── Event API ──────────────────────────────────────────\n // All return `this` for chaining:\n // player.on('play', ...).on('ended', ...)\n\n on<K extends keyof PlayerEventMap>(\n event: K,\n handler: (data: PlayerEventMap[K]) => void,\n ): this {\n this.emitter.on(event, handler);\n return this;\n }\n\n off<K extends keyof PlayerEventMap>(\n event: K,\n handler: (data: PlayerEventMap[K]) => void,\n ): this {\n this.emitter.off(event, handler);\n return this;\n }\n\n once<K extends keyof PlayerEventMap>(\n event: K,\n handler: (data: PlayerEventMap[K]) => void,\n ): this {\n this.emitter.once(event, handler);\n return this;\n }\n\n // ── Destroy ────────────────────────────────────────────\n\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n\n window.removeEventListener('message', this.msgHandler);\n\n if (this.wrapper) {\n destroyIframe(this.wrapper);\n this.wrapper = null;\n this.iframe = null;\n }\n\n this.emitter.removeAll();\n }\n\n // ── Private: send command to iframe ───────────────────\n\n private send(action: IframeAction, value?: unknown): void {\n if (this.destroyed) return;\n if (!this.iframe?.contentWindow) return;\n\n const cmd: IframeCommand = {\n action,\n videoId: this.opts.videoId,\n value,\n };\n\n this.iframe.contentWindow.postMessage(cmd, '*');\n }\n\n // ── Private: handle incoming postMessage ──────────────\n\n private handleMessage(e: MessageEvent): void {\n const msg = e.data as IframeMessage;\n\n // Ignore non-SDK messages\n if (!msg || msg.type !== 'videncrypt:player') return;\n\n // Ignore messages for other player instances on same page\n if (msg.videoId !== this.opts.videoId) return;\n\n switch (msg.event) {\n case 'ready':\n this.state.ready = true;\n this.emitter.emit('ready', undefined as unknown as void);\n break;\n\n case 'play':\n this.state.playing = true;\n this.emitter.emit('play', undefined as unknown as void);\n break;\n\n case 'pause':\n this.state.playing = false;\n this.emitter.emit('pause', undefined as unknown as void);\n break;\n\n case 'ended':\n this.state.playing = false;\n this.emitter.emit('ended', undefined as unknown as void);\n break;\n\n case 'progress': {\n const currentTime = (msg.data?.currentTime as number) ?? 0;\n const duration = (msg.data?.duration as number) ?? 0;\n this.state.currentTime = currentTime;\n this.state.duration = duration;\n this.emitter.emit('progress', { currentTime, duration });\n break;\n }\n\n case 'error': {\n const error: PlayerError = {\n code: (msg.data?.code as PlayerError['code']) ?? 'unknown',\n message: (msg.data?.message as string) ?? 'Unknown error',\n };\n this.emitter.emit('error', error);\n break;\n }\n\n case 'fullscreenchange': {\n const isFullscreen = (msg.data?.isFullscreen as boolean) ?? false;\n this.state.fullscreen = isFullscreen;\n this.emitter.emit('fullscreenchange', isFullscreen);\n break;\n }\n\n case 'statechange': {\n const partial = (msg.data ?? {}) as Partial<PlayerState>;\n this.state = { ...this.state, ...partial };\n this.emitter.emit('statechange', partial);\n break;\n }\n }\n }\n}"],"mappings":"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","names":[]}
|