@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/dist/ve.d.cts ADDED
@@ -0,0 +1,95 @@
1
+ interface PlayerOptions {
2
+ videoId: string;
3
+ container: string | HTMLElement;
4
+ width?: string | number;
5
+ aspectRatio?: string;
6
+ title?: string;
7
+ autoPlay?: boolean;
8
+ muted?: boolean;
9
+ loop?: boolean;
10
+ startTime?: number;
11
+ showControls?: boolean;
12
+ showBranding?: boolean;
13
+ primaryColor?: string;
14
+ onReady?: () => void;
15
+ onPlay?: () => void;
16
+ onPause?: () => void;
17
+ onEnded?: () => void;
18
+ onProgress?: (currentTime: number, duration: number) => void;
19
+ onError?: (error: PlayerError) => void;
20
+ onFullscreenChange?: (isFullscreen: boolean) => void;
21
+ embedBaseUrl?: string;
22
+ }
23
+ interface PlayerState {
24
+ playing: boolean;
25
+ muted: boolean;
26
+ currentTime: number;
27
+ duration: number;
28
+ fullscreen: boolean;
29
+ ready: boolean;
30
+ }
31
+ interface PlayerError {
32
+ code: 'token-expired' | 'not-found' | 'domain-not-allowed' | 'network-error' | 'unknown';
33
+ message: string;
34
+ }
35
+ interface PlayerEventMap {
36
+ [key: string]: unknown;
37
+ ready: void;
38
+ play: void;
39
+ pause: void;
40
+ ended: void;
41
+ progress: {
42
+ currentTime: number;
43
+ duration: number;
44
+ };
45
+ error: PlayerError;
46
+ fullscreenchange: boolean;
47
+ statechange: Partial<PlayerState>;
48
+ }
49
+ interface IframeMessage {
50
+ type: 'videncrypt:player';
51
+ videoId: string;
52
+ event: IframeEvent;
53
+ data?: Record<string, unknown>;
54
+ }
55
+ type IframeEvent = 'ready' | 'play' | 'pause' | 'ended' | 'progress' | 'error' | 'fullscreenchange' | 'statechange';
56
+ interface IframeCommand {
57
+ action: IframeAction;
58
+ videoId: string;
59
+ value?: unknown;
60
+ }
61
+ type IframeAction = 'play' | 'pause' | 'seek' | 'volume' | 'mute' | 'unmute' | 'fullscreen' | 'exitFullscreen';
62
+
63
+ declare class VidEncryptPlayer {
64
+ private opts;
65
+ private container;
66
+ private iframe;
67
+ private wrapper;
68
+ private emitter;
69
+ private msgHandler;
70
+ private destroyed;
71
+ private state;
72
+ constructor(options: PlayerOptions);
73
+ play(): void;
74
+ pause(): void;
75
+ seek(time: number): void;
76
+ setVolume(volume: number): void;
77
+ mute(): void;
78
+ unmute(): void;
79
+ enterFullscreen(): void;
80
+ exitFullscreen(): void;
81
+ getState(): Readonly<PlayerState>;
82
+ isReady(): boolean;
83
+ isPlaying(): boolean;
84
+ isMuted(): boolean;
85
+ getCurrentTime(): number;
86
+ getDuration(): number;
87
+ on<K extends keyof PlayerEventMap>(event: K, handler: (data: PlayerEventMap[K]) => void): this;
88
+ off<K extends keyof PlayerEventMap>(event: K, handler: (data: PlayerEventMap[K]) => void): this;
89
+ once<K extends keyof PlayerEventMap>(event: K, handler: (data: PlayerEventMap[K]) => void): this;
90
+ destroy(): void;
91
+ private send;
92
+ private handleMessage;
93
+ }
94
+
95
+ export { type IframeAction, type IframeCommand, type IframeEvent, type IframeMessage, VidEncryptPlayer as Player, type PlayerError, type PlayerEventMap, type PlayerOptions, type PlayerState };
package/dist/ve.d.ts ADDED
@@ -0,0 +1,95 @@
1
+ interface PlayerOptions {
2
+ videoId: string;
3
+ container: string | HTMLElement;
4
+ width?: string | number;
5
+ aspectRatio?: string;
6
+ title?: string;
7
+ autoPlay?: boolean;
8
+ muted?: boolean;
9
+ loop?: boolean;
10
+ startTime?: number;
11
+ showControls?: boolean;
12
+ showBranding?: boolean;
13
+ primaryColor?: string;
14
+ onReady?: () => void;
15
+ onPlay?: () => void;
16
+ onPause?: () => void;
17
+ onEnded?: () => void;
18
+ onProgress?: (currentTime: number, duration: number) => void;
19
+ onError?: (error: PlayerError) => void;
20
+ onFullscreenChange?: (isFullscreen: boolean) => void;
21
+ embedBaseUrl?: string;
22
+ }
23
+ interface PlayerState {
24
+ playing: boolean;
25
+ muted: boolean;
26
+ currentTime: number;
27
+ duration: number;
28
+ fullscreen: boolean;
29
+ ready: boolean;
30
+ }
31
+ interface PlayerError {
32
+ code: 'token-expired' | 'not-found' | 'domain-not-allowed' | 'network-error' | 'unknown';
33
+ message: string;
34
+ }
35
+ interface PlayerEventMap {
36
+ [key: string]: unknown;
37
+ ready: void;
38
+ play: void;
39
+ pause: void;
40
+ ended: void;
41
+ progress: {
42
+ currentTime: number;
43
+ duration: number;
44
+ };
45
+ error: PlayerError;
46
+ fullscreenchange: boolean;
47
+ statechange: Partial<PlayerState>;
48
+ }
49
+ interface IframeMessage {
50
+ type: 'videncrypt:player';
51
+ videoId: string;
52
+ event: IframeEvent;
53
+ data?: Record<string, unknown>;
54
+ }
55
+ type IframeEvent = 'ready' | 'play' | 'pause' | 'ended' | 'progress' | 'error' | 'fullscreenchange' | 'statechange';
56
+ interface IframeCommand {
57
+ action: IframeAction;
58
+ videoId: string;
59
+ value?: unknown;
60
+ }
61
+ type IframeAction = 'play' | 'pause' | 'seek' | 'volume' | 'mute' | 'unmute' | 'fullscreen' | 'exitFullscreen';
62
+
63
+ declare class VidEncryptPlayer {
64
+ private opts;
65
+ private container;
66
+ private iframe;
67
+ private wrapper;
68
+ private emitter;
69
+ private msgHandler;
70
+ private destroyed;
71
+ private state;
72
+ constructor(options: PlayerOptions);
73
+ play(): void;
74
+ pause(): void;
75
+ seek(time: number): void;
76
+ setVolume(volume: number): void;
77
+ mute(): void;
78
+ unmute(): void;
79
+ enterFullscreen(): void;
80
+ exitFullscreen(): void;
81
+ getState(): Readonly<PlayerState>;
82
+ isReady(): boolean;
83
+ isPlaying(): boolean;
84
+ isMuted(): boolean;
85
+ getCurrentTime(): number;
86
+ getDuration(): number;
87
+ on<K extends keyof PlayerEventMap>(event: K, handler: (data: PlayerEventMap[K]) => void): this;
88
+ off<K extends keyof PlayerEventMap>(event: K, handler: (data: PlayerEventMap[K]) => void): this;
89
+ once<K extends keyof PlayerEventMap>(event: K, handler: (data: PlayerEventMap[K]) => void): this;
90
+ destroy(): void;
91
+ private send;
92
+ private handleMessage;
93
+ }
94
+
95
+ export { type IframeAction, type IframeCommand, type IframeEvent, type IframeMessage, VidEncryptPlayer as Player, type PlayerError, type PlayerEventMap, type PlayerOptions, type PlayerState };
package/dist/ve.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var VidEncrypt=(()=>{var p=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var T=(t,e)=>{for(var r in e)p(t,r,{get:e[r],enumerable:!0})},K=(t,e,r,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of x(e))!I.call(t,n)&&n!==r&&p(t,n,{get:()=>e[n],enumerable:!(a=k(e,n))||a.enumerable});return t};var C=t=>K(p({},"__esModule",{value:!0}),t);var S={};T(S,{Player:()=>f});var h=class{constructor(){this.handlers=new Map}on(e,r){this.handlers.has(e)||this.handlers.set(e,new Set),this.handlers.get(e).add(r)}off(e,r){var a;(a=this.handlers.get(e))==null||a.delete(r)}once(e,r){let a=n=>{r(n),this.off(e,a)};this.on(e,a)}emit(e,r){var a;(a=this.handlers.get(e))==null||a.forEach(n=>{try{n(r)}catch(o){console.warn("[VidEncrypt] event handler error:",o)}})}removeAll(){this.handlers.clear()}};function H(t){let e=t.split("/").map(Number),r=e[0],a=e[1];return!r||!a||isNaN(r)||isNaN(a)||r<=0||a<=0?{w:16,h:9}:{w:r,h:a}}function E(t){let{w:e,h:r}=H(t);return`${(r/e*100).toFixed(4)}%`}function P(t){return t==null?"100%":typeof t=="number"?`${t}px`:t}function w(t){return typeof t=="string"&&t.trim().length>0}function M(t){let e=new URL(`${t.embedBaseUrl}/embed/${t.videoId}`);t.autoPlay&&e.searchParams.set("autoplay","1"),t.muted&&e.searchParams.set("muted","1"),t.loop&&e.searchParams.set("loop","1"),t.showControls||e.searchParams.set("controls","0"),t.showBranding||e.searchParams.set("branding","0"),t.startTime&&t.startTime>0&&e.searchParams.set("start",String(t.startTime)),t.primaryColor&&t.primaryColor!=="#2563EB"&&e.searchParams.set("color",t.primaryColor.replace("#","")),t.title&&e.searchParams.set("title",encodeURIComponent(t.title));let r=document.createElement("div");r.style.cssText=["position: relative",`width: ${P(t.width)}`,"background: #000","line-height: 0"].join("; ");let a=document.createElement("div");a.style.cssText=["position: relative",`padding-bottom: ${E(t.aspectRatio)}`,"height: 0","overflow: hidden"].join("; ");let n=document.createElement("iframe");return n.src=e.toString(),n.title=t.title||"Video Player",n.frameBorder="0",n.allowFullscreen=!0,n.setAttribute("allow","autoplay; fullscreen; picture-in-picture; clipboard-write"),n.setAttribute("loading","lazy"),n.style.cssText=["position: absolute","top: 0","left: 0","width: 100%","height: 100%","border: none"].join("; "),a.appendChild(n),r.appendChild(a),{iframe:n,wrapper:r}}function b(t){let e=t.querySelector("iframe");e&&(e.src="about:blank"),t.remove()}var R={width:"100%",aspectRatio:"16/9",title:"",autoPlay:!1,muted:!1,loop:!1,startTime:0,showControls:!0,showBranding:!0,primaryColor:"#2563EB",embedBaseUrl:"https://app.videncrypt.com"},f=class{constructor(e){this.iframe=null;this.wrapper=null;this.destroyed=!1;this.state={playing:!1,muted:!1,currentTime:0,duration:0,fullscreen:!1,ready:!1};var n,o,d,l,m,u,c;if(typeof e.container=="string"){let s=document.querySelector(e.container);if(!s)throw new Error(`VidEncrypt: container "${e.container}" not found`);this.container=s}else if(e.container instanceof HTMLElement)this.container=e.container;else throw new Error("VidEncrypt: container must be a CSS selector or HTMLElement");if(!w(e.videoId))throw new Error("VidEncrypt: videoId is required and must be a non-empty string");this.opts={...R,...e,onReady:(n=e.onReady)!=null?n:void 0,onPlay:(o=e.onPlay)!=null?o:void 0,onPause:(d=e.onPause)!=null?d:void 0,onEnded:(l=e.onEnded)!=null?l:void 0,onProgress:(m=e.onProgress)!=null?m:void 0,onError:(u=e.onError)!=null?u:void 0,onFullscreenChange:(c=e.onFullscreenChange)!=null?c:void 0},this.emitter=new h,e.onReady&&this.emitter.on("ready",e.onReady),e.onPlay&&this.emitter.on("play",e.onPlay),e.onPause&&this.emitter.on("pause",e.onPause),e.onEnded&&this.emitter.on("ended",e.onEnded),e.onProgress&&this.emitter.on("progress",s=>e.onProgress(s.currentTime,s.duration)),e.onError&&this.emitter.on("error",e.onError),e.onFullscreenChange&&this.emitter.on("fullscreenchange",e.onFullscreenChange),this.msgHandler=this.handleMessage.bind(this),window.addEventListener("message",this.msgHandler);let{iframe:r,wrapper:a}=M(this.opts);this.iframe=r,this.wrapper=a,this.container.appendChild(a)}play(){this.send("play")}pause(){this.send("pause")}seek(e){this.send("seek",Math.max(0,e))}setVolume(e){this.send("volume",Math.min(1,Math.max(0,e)))}mute(){this.send("mute")}unmute(){this.send("unmute")}enterFullscreen(){this.send("fullscreen")}exitFullscreen(){this.send("exitFullscreen")}getState(){return{...this.state}}isReady(){return this.state.ready}isPlaying(){return this.state.playing}isMuted(){return this.state.muted}getCurrentTime(){return this.state.currentTime}getDuration(){return this.state.duration}on(e,r){return this.emitter.on(e,r),this}off(e,r){return this.emitter.off(e,r),this}once(e,r){return this.emitter.once(e,r),this}destroy(){this.destroyed||(this.destroyed=!0,window.removeEventListener("message",this.msgHandler),this.wrapper&&(b(this.wrapper),this.wrapper=null,this.iframe=null),this.emitter.removeAll())}send(e,r){var n;if(this.destroyed||!((n=this.iframe)!=null&&n.contentWindow))return;let a={action:e,videoId:this.opts.videoId,value:r};this.iframe.contentWindow.postMessage(a,"*")}handleMessage(e){var a,n,o,d,l,m,u,c,s,y,v;let r=e.data;if(!(!r||r.type!=="videncrypt:player")&&r.videoId===this.opts.videoId)switch(r.event){case"ready":this.state.ready=!0,this.emitter.emit("ready",void 0);break;case"play":this.state.playing=!0,this.emitter.emit("play",void 0);break;case"pause":this.state.playing=!1,this.emitter.emit("pause",void 0);break;case"ended":this.state.playing=!1,this.emitter.emit("ended",void 0);break;case"progress":{let i=(n=(a=r.data)==null?void 0:a.currentTime)!=null?n:0,g=(d=(o=r.data)==null?void 0:o.duration)!=null?d:0;this.state.currentTime=i,this.state.duration=g,this.emitter.emit("progress",{currentTime:i,duration:g});break}case"error":{let i={code:(m=(l=r.data)==null?void 0:l.code)!=null?m:"unknown",message:(c=(u=r.data)==null?void 0:u.message)!=null?c:"Unknown error"};this.emitter.emit("error",i);break}case"fullscreenchange":{let i=(y=(s=r.data)==null?void 0:s.isFullscreen)!=null?y:!1;this.state.fullscreen=i,this.emitter.emit("fullscreenchange",i);break}case"statechange":{let i=(v=r.data)!=null?v:{};this.state={...this.state,...i},this.emitter.emit("statechange",i);break}}}};return C(S);})();
2
+ //# sourceMappingURL=ve.js.map
package/dist/ve.js.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":["src_exports","__export","VidEncryptPlayer","EventEmitter","event","handler","_a","wrapper","data","err","parseAspectRatio","ratio","parts","w","h","aspectRatioToPadding","normalizeWidth","width","isValidVideoId","id","createIframe","opts","url","wrapper","normalizeWidth","aspectBox","aspectRatioToPadding","iframe","destroyIframe","DEFAULTS","VidEncryptPlayer","options","_a","_b","_c","_d","_e","_f","_g","el","isValidVideoId","EventEmitter","d","iframe","wrapper","createIframe","time","volume","event","handler","destroyIframe","action","value","cmd","_h","_i","_j","_k","msg","currentTime","duration","error","isFullscreen","partial"]}
package/dist/ve.mjs ADDED
@@ -0,0 +1,355 @@
1
+ // src/events.ts
2
+ var EventEmitter = class {
3
+ constructor() {
4
+ this.handlers = /* @__PURE__ */ new Map();
5
+ }
6
+ on(event, handler) {
7
+ if (!this.handlers.has(event)) {
8
+ this.handlers.set(event, /* @__PURE__ */ new Set());
9
+ }
10
+ this.handlers.get(event).add(handler);
11
+ }
12
+ off(event, handler) {
13
+ var _a;
14
+ (_a = this.handlers.get(event)) == null ? void 0 : _a.delete(handler);
15
+ }
16
+ once(event, handler) {
17
+ const wrapper = (data) => {
18
+ handler(data);
19
+ this.off(event, wrapper);
20
+ };
21
+ this.on(event, wrapper);
22
+ }
23
+ emit(event, data) {
24
+ var _a;
25
+ (_a = this.handlers.get(event)) == null ? void 0 : _a.forEach((handler) => {
26
+ try {
27
+ handler(data);
28
+ } catch (err) {
29
+ console.warn("[VidEncrypt] event handler error:", err);
30
+ }
31
+ });
32
+ }
33
+ removeAll() {
34
+ this.handlers.clear();
35
+ }
36
+ };
37
+
38
+ // src/utils.ts
39
+ function parseAspectRatio(ratio) {
40
+ const parts = ratio.split("/").map(Number);
41
+ const w = parts[0];
42
+ const h = parts[1];
43
+ if (!w || !h || isNaN(w) || isNaN(h) || w <= 0 || h <= 0) {
44
+ return { w: 16, h: 9 };
45
+ }
46
+ return { w, h };
47
+ }
48
+ function aspectRatioToPadding(ratio) {
49
+ const { w, h } = parseAspectRatio(ratio);
50
+ return `${(h / w * 100).toFixed(4)}%`;
51
+ }
52
+ function normalizeWidth(width) {
53
+ if (width === void 0 || width === null) return "100%";
54
+ if (typeof width === "number") return `${width}px`;
55
+ return width;
56
+ }
57
+ function isValidVideoId(id) {
58
+ return typeof id === "string" && id.trim().length > 0;
59
+ }
60
+
61
+ // src/iframe.ts
62
+ function createIframe(opts) {
63
+ const url = new URL(`${opts.embedBaseUrl}/embed/${opts.videoId}`);
64
+ if (opts.autoPlay) url.searchParams.set("autoplay", "1");
65
+ if (opts.muted) url.searchParams.set("muted", "1");
66
+ if (opts.loop) url.searchParams.set("loop", "1");
67
+ if (!opts.showControls) url.searchParams.set("controls", "0");
68
+ if (!opts.showBranding) url.searchParams.set("branding", "0");
69
+ if (opts.startTime && opts.startTime > 0) {
70
+ url.searchParams.set("start", String(opts.startTime));
71
+ }
72
+ if (opts.primaryColor && opts.primaryColor !== "#2563EB") {
73
+ url.searchParams.set("color", opts.primaryColor.replace("#", ""));
74
+ }
75
+ if (opts.title) {
76
+ url.searchParams.set("title", encodeURIComponent(opts.title));
77
+ }
78
+ const wrapper = document.createElement("div");
79
+ wrapper.style.cssText = [
80
+ "position: relative",
81
+ `width: ${normalizeWidth(opts.width)}`,
82
+ "background: #000",
83
+ "line-height: 0"
84
+ // prevents gap below iframe
85
+ ].join("; ");
86
+ const aspectBox = document.createElement("div");
87
+ aspectBox.style.cssText = [
88
+ "position: relative",
89
+ `padding-bottom: ${aspectRatioToPadding(opts.aspectRatio)}`,
90
+ "height: 0",
91
+ "overflow: hidden"
92
+ ].join("; ");
93
+ const iframe = document.createElement("iframe");
94
+ iframe.src = url.toString();
95
+ iframe.title = opts.title || "Video Player";
96
+ iframe.frameBorder = "0";
97
+ iframe.allowFullscreen = true;
98
+ iframe.setAttribute(
99
+ "allow",
100
+ "autoplay; fullscreen; picture-in-picture; clipboard-write"
101
+ );
102
+ iframe.setAttribute("loading", "lazy");
103
+ iframe.style.cssText = [
104
+ "position: absolute",
105
+ "top: 0",
106
+ "left: 0",
107
+ "width: 100%",
108
+ "height: 100%",
109
+ "border: none"
110
+ ].join("; ");
111
+ aspectBox.appendChild(iframe);
112
+ wrapper.appendChild(aspectBox);
113
+ return { iframe, wrapper };
114
+ }
115
+ function destroyIframe(wrapper) {
116
+ const iframe = wrapper.querySelector("iframe");
117
+ if (iframe) {
118
+ iframe.src = "about:blank";
119
+ }
120
+ wrapper.remove();
121
+ }
122
+
123
+ // src/player.ts
124
+ var DEFAULTS = {
125
+ width: "100%",
126
+ aspectRatio: "16/9",
127
+ title: "",
128
+ autoPlay: false,
129
+ muted: false,
130
+ loop: false,
131
+ startTime: 0,
132
+ showControls: true,
133
+ showBranding: true,
134
+ primaryColor: "#2563EB",
135
+ embedBaseUrl: "https://app.videncrypt.com"
136
+ };
137
+ var VidEncryptPlayer = class {
138
+ constructor(options) {
139
+ this.iframe = null;
140
+ this.wrapper = null;
141
+ this.destroyed = false;
142
+ this.state = {
143
+ playing: false,
144
+ muted: false,
145
+ currentTime: 0,
146
+ duration: 0,
147
+ fullscreen: false,
148
+ ready: false
149
+ };
150
+ var _a, _b, _c, _d, _e, _f, _g;
151
+ if (typeof options.container === "string") {
152
+ const el = document.querySelector(options.container);
153
+ if (!el) {
154
+ throw new Error(
155
+ `VidEncrypt: container "${options.container}" not found`
156
+ );
157
+ }
158
+ this.container = el;
159
+ } else if (options.container instanceof HTMLElement) {
160
+ this.container = options.container;
161
+ } else {
162
+ throw new Error(
163
+ "VidEncrypt: container must be a CSS selector or HTMLElement"
164
+ );
165
+ }
166
+ if (!isValidVideoId(options.videoId)) {
167
+ throw new Error(
168
+ "VidEncrypt: videoId is required and must be a non-empty string"
169
+ );
170
+ }
171
+ this.opts = {
172
+ ...DEFAULTS,
173
+ ...options,
174
+ // Strip undefined callbacks so they don't overwrite defaults
175
+ onReady: (_a = options.onReady) != null ? _a : void 0,
176
+ onPlay: (_b = options.onPlay) != null ? _b : void 0,
177
+ onPause: (_c = options.onPause) != null ? _c : void 0,
178
+ onEnded: (_d = options.onEnded) != null ? _d : void 0,
179
+ onProgress: (_e = options.onProgress) != null ? _e : void 0,
180
+ onError: (_f = options.onError) != null ? _f : void 0,
181
+ onFullscreenChange: (_g = options.onFullscreenChange) != null ? _g : void 0
182
+ };
183
+ this.emitter = new EventEmitter();
184
+ if (options.onReady) {
185
+ this.emitter.on("ready", options.onReady);
186
+ }
187
+ if (options.onPlay) {
188
+ this.emitter.on("play", options.onPlay);
189
+ }
190
+ if (options.onPause) {
191
+ this.emitter.on("pause", options.onPause);
192
+ }
193
+ if (options.onEnded) {
194
+ this.emitter.on("ended", options.onEnded);
195
+ }
196
+ if (options.onProgress) {
197
+ this.emitter.on(
198
+ "progress",
199
+ (d) => options.onProgress(d.currentTime, d.duration)
200
+ );
201
+ }
202
+ if (options.onError) {
203
+ this.emitter.on("error", options.onError);
204
+ }
205
+ if (options.onFullscreenChange) {
206
+ this.emitter.on("fullscreenchange", options.onFullscreenChange);
207
+ }
208
+ this.msgHandler = this.handleMessage.bind(this);
209
+ window.addEventListener("message", this.msgHandler);
210
+ const { iframe, wrapper } = createIframe(this.opts);
211
+ this.iframe = iframe;
212
+ this.wrapper = wrapper;
213
+ this.container.appendChild(wrapper);
214
+ }
215
+ // ── Playback controls ──────────────────────────────────
216
+ play() {
217
+ this.send("play");
218
+ }
219
+ pause() {
220
+ this.send("pause");
221
+ }
222
+ seek(time) {
223
+ this.send("seek", Math.max(0, time));
224
+ }
225
+ setVolume(volume) {
226
+ this.send("volume", Math.min(1, Math.max(0, volume)));
227
+ }
228
+ mute() {
229
+ this.send("mute");
230
+ }
231
+ unmute() {
232
+ this.send("unmute");
233
+ }
234
+ enterFullscreen() {
235
+ this.send("fullscreen");
236
+ }
237
+ exitFullscreen() {
238
+ this.send("exitFullscreen");
239
+ }
240
+ // ── State ──────────────────────────────────────────────
241
+ getState() {
242
+ return { ...this.state };
243
+ }
244
+ isReady() {
245
+ return this.state.ready;
246
+ }
247
+ isPlaying() {
248
+ return this.state.playing;
249
+ }
250
+ isMuted() {
251
+ return this.state.muted;
252
+ }
253
+ getCurrentTime() {
254
+ return this.state.currentTime;
255
+ }
256
+ getDuration() {
257
+ return this.state.duration;
258
+ }
259
+ // ── Event API ──────────────────────────────────────────
260
+ // All return `this` for chaining:
261
+ // player.on('play', ...).on('ended', ...)
262
+ on(event, handler) {
263
+ this.emitter.on(event, handler);
264
+ return this;
265
+ }
266
+ off(event, handler) {
267
+ this.emitter.off(event, handler);
268
+ return this;
269
+ }
270
+ once(event, handler) {
271
+ this.emitter.once(event, handler);
272
+ return this;
273
+ }
274
+ // ── Destroy ────────────────────────────────────────────
275
+ destroy() {
276
+ if (this.destroyed) return;
277
+ this.destroyed = true;
278
+ window.removeEventListener("message", this.msgHandler);
279
+ if (this.wrapper) {
280
+ destroyIframe(this.wrapper);
281
+ this.wrapper = null;
282
+ this.iframe = null;
283
+ }
284
+ this.emitter.removeAll();
285
+ }
286
+ // ── Private: send command to iframe ───────────────────
287
+ send(action, value) {
288
+ var _a;
289
+ if (this.destroyed) return;
290
+ if (!((_a = this.iframe) == null ? void 0 : _a.contentWindow)) return;
291
+ const cmd = {
292
+ action,
293
+ videoId: this.opts.videoId,
294
+ value
295
+ };
296
+ this.iframe.contentWindow.postMessage(cmd, "*");
297
+ }
298
+ // ── Private: handle incoming postMessage ──────────────
299
+ handleMessage(e) {
300
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
301
+ const msg = e.data;
302
+ if (!msg || msg.type !== "videncrypt:player") return;
303
+ if (msg.videoId !== this.opts.videoId) return;
304
+ switch (msg.event) {
305
+ case "ready":
306
+ this.state.ready = true;
307
+ this.emitter.emit("ready", void 0);
308
+ break;
309
+ case "play":
310
+ this.state.playing = true;
311
+ this.emitter.emit("play", void 0);
312
+ break;
313
+ case "pause":
314
+ this.state.playing = false;
315
+ this.emitter.emit("pause", void 0);
316
+ break;
317
+ case "ended":
318
+ this.state.playing = false;
319
+ this.emitter.emit("ended", void 0);
320
+ break;
321
+ case "progress": {
322
+ const currentTime = (_b = (_a = msg.data) == null ? void 0 : _a.currentTime) != null ? _b : 0;
323
+ const duration = (_d = (_c = msg.data) == null ? void 0 : _c.duration) != null ? _d : 0;
324
+ this.state.currentTime = currentTime;
325
+ this.state.duration = duration;
326
+ this.emitter.emit("progress", { currentTime, duration });
327
+ break;
328
+ }
329
+ case "error": {
330
+ const error = {
331
+ code: (_f = (_e = msg.data) == null ? void 0 : _e.code) != null ? _f : "unknown",
332
+ message: (_h = (_g = msg.data) == null ? void 0 : _g.message) != null ? _h : "Unknown error"
333
+ };
334
+ this.emitter.emit("error", error);
335
+ break;
336
+ }
337
+ case "fullscreenchange": {
338
+ const isFullscreen = (_j = (_i = msg.data) == null ? void 0 : _i.isFullscreen) != null ? _j : false;
339
+ this.state.fullscreen = isFullscreen;
340
+ this.emitter.emit("fullscreenchange", isFullscreen);
341
+ break;
342
+ }
343
+ case "statechange": {
344
+ const partial = (_k = msg.data) != null ? _k : {};
345
+ this.state = { ...this.state, ...partial };
346
+ this.emitter.emit("statechange", partial);
347
+ break;
348
+ }
349
+ }
350
+ }
351
+ };
352
+ export {
353
+ VidEncryptPlayer as Player
354
+ };
355
+ //# sourceMappingURL=ve.mjs.map