@obipascal/player 1.0.9 → 1.0.10

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 CHANGED
@@ -1421,11 +1421,20 @@ fileInput.addEventListener("change", async (event) => {
1421
1421
  console.log("- File Name:", videoInfo.fileName) // e.g., "my-video.mp4"
1422
1422
  console.log("- Extension:", videoInfo.fileExtension) // e.g., ".mp4"
1423
1423
  console.log("- Bitrate:", videoInfo.bitrate, "kbps") // e.g., 3500
1424
+ console.log("- Frame Rate:", videoInfo.frameRate, "fps") // e.g., 30 or 60
1425
+ console.log("- Has Audio:", videoInfo.hasAudio) // e.g., true
1426
+ console.log("- Audio Channels:", videoInfo.audioChannels) // e.g., 2 (stereo)
1424
1427
 
1425
1428
  // Get all info as object
1426
1429
  const allInfo = videoInfo.getInfo()
1427
1430
  console.log(allInfo)
1428
1431
 
1432
+ // Validate against platform requirements
1433
+ const isValid = validateVideo(videoInfo)
1434
+ if (!isValid.valid) {
1435
+ console.error("Validation errors:", isValid.errors)
1436
+ }
1437
+
1429
1438
  // Clean up when done
1430
1439
  videoInfo.destroy()
1431
1440
  } catch (error) {
@@ -1433,6 +1442,56 @@ fileInput.addEventListener("change", async (event) => {
1433
1442
  // Throws error if file is not a video
1434
1443
  }
1435
1444
  })
1445
+
1446
+ // Example validation function for educational platform
1447
+ function validateVideo(info: VideoFileInfo) {
1448
+ const errors: string[] = []
1449
+
1450
+ // Aspect Ratio: 16:9 required
1451
+ if (info.aspectRatio !== "16:9") {
1452
+ errors.push(`Aspect ratio must be 16:9, got ${info.aspectRatio}`)
1453
+ }
1454
+
1455
+ // Resolution: Minimum 1280×720
1456
+ if (info.height < 720 || info.width < 1280) {
1457
+ errors.push(`Minimum resolution is 1280×720, got ${info.width}×${info.height}`)
1458
+ }
1459
+
1460
+ // File Format: .MP4 or .MOV
1461
+ if (![".mp4", ".mov"].includes(info.fileExtension.toLowerCase())) {
1462
+ errors.push(`File format must be MP4 or MOV, got ${info.fileExtension}`)
1463
+ }
1464
+
1465
+ // Bitrate: 5-10 Mbps
1466
+ if (info.bitrate && (info.bitrate < 5000 || info.bitrate > 10000)) {
1467
+ errors.push(`Bitrate should be 5-10 Mbps, got ${info.bitrate} kbps`)
1468
+ }
1469
+
1470
+ // Audio: Must be stereo (2 channels)
1471
+ if (!info.hasAudio) {
1472
+ errors.push("Video must have audio track")
1473
+ } else if (info.audioChannels && info.audioChannels !== 2) {
1474
+ errors.push(`Audio must be stereo (2 channels), got ${info.audioChannels}`)
1475
+ }
1476
+
1477
+ // File Size: ≤4.0 GB
1478
+ const maxSize = 4 * 1024 * 1024 * 1024 // 4GB in bytes
1479
+ if (info.sizeInBytes > maxSize) {
1480
+ errors.push(`File size must be ≤4GB, got ${info.sizeFormatted}`)
1481
+ }
1482
+
1483
+ // Duration: 2 minutes to 2 hours
1484
+ if (info.durationInSeconds < 120 || info.durationInSeconds > 7200) {
1485
+ errors.push(`Duration must be 2min-2hrs, got ${info.durationFormatted}`)
1486
+ }
1487
+
1488
+ // Frame Rate: 30 or 60 fps
1489
+ if (info.frameRate && ![30, 60].includes(info.frameRate)) {
1490
+ errors.push(`Frame rate should be 30 or 60 fps, got ${info.frameRate}`)
1491
+ }
1492
+
1493
+ return { valid: errors.length === 0, errors }
1494
+ }
1436
1495
  ```
1437
1496
 
1438
1497
  #### WontumFileInfo API
@@ -1467,6 +1526,13 @@ Throws an error if the file is not a valid video file.
1467
1526
  - `fileName: string` - Original file name
1468
1527
  - `fileExtension: string` - File extension (e.g., ".mp4")
1469
1528
  - `bitrate: number | undefined` - Estimated bitrate in kbps
1529
+ - `frameRate: number | undefined` - Frame rate in fps (30, 60, etc.)
1530
+ - `hasAudio: boolean` - Whether video has an audio track
1531
+ - `audioChannels: number | undefined` - Number of audio channels (1=mono, 2=stereo)
1532
+
1533
+ **Validation Use Case:**
1534
+
1535
+ Perfect for validating videos against platform requirements (aspect ratio, resolution, format, bitrate, audio channels, file size, duration, frame rate).
1470
1536
 
1471
1537
  **Supported Video Formats:**
1472
1538
 
@@ -2,6 +2,15 @@
2
2
  * Video file information extractor
3
3
  * Extracts metadata from video files (width, height, duration, size, etc.)
4
4
  */
5
+ declare global {
6
+ interface HTMLVideoElement {
7
+ mozHasAudio?: boolean;
8
+ webkitAudioDecodedByteCount?: number;
9
+ audioTracks?: {
10
+ length: number;
11
+ };
12
+ }
13
+ }
5
14
  export interface VideoFileInfo {
6
15
  width: number;
7
16
  height: number;
@@ -17,12 +26,15 @@ export interface VideoFileInfo {
17
26
  fileExtension: string;
18
27
  bitrate?: number;
19
28
  frameRate?: number;
29
+ audioChannels?: number;
20
30
  videoCodec?: string;
21
31
  audioCodec?: string;
32
+ hasAudio?: boolean;
22
33
  }
23
34
  export declare class WontumFileInfo {
24
35
  private file;
25
36
  private videoElement;
37
+ private audioContext;
26
38
  private info;
27
39
  constructor(file: File);
28
40
  /**
@@ -37,6 +49,14 @@ export declare class WontumFileInfo {
37
49
  * Calculate aspect ratio (e.g., "16:9", "4:3")
38
50
  */
39
51
  private calculateAspectRatio;
52
+ /**
53
+ * Detect frame rate by analyzing video playback
54
+ */
55
+ private detectFrameRate;
56
+ /**
57
+ * Detect audio channel information using Web Audio API
58
+ */
59
+ private detectAudioInfo;
40
60
  /**
41
61
  * Get Greatest Common Divisor
42
62
  */
@@ -66,6 +86,9 @@ export declare class WontumFileInfo {
66
86
  get fileName(): string;
67
87
  get fileExtension(): string;
68
88
  get bitrate(): number | undefined;
89
+ get frameRate(): number | undefined;
90
+ get audioChannels(): number | undefined;
91
+ get hasAudio(): boolean;
69
92
  get quality(): string;
70
93
  /**
71
94
  * Get all information as object
@@ -1,4 +1,4 @@
1
- "use strict";var V=Object.create;var k=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var j=Object.getPrototypeOf,Q=Object.prototype.hasOwnProperty;var Y=(l,t,e)=>t in l?k(l,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):l[t]=e;var X=(l,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of _(t))!Q.call(l,n)&&n!==e&&k(l,n,{get:()=>t[n],enumerable:!(i=N(t,n))||i.enumerable});return l};var G=(l,t,e)=>(e=l!=null?V(j(l)):{},X(t||!l||!l.__esModule?k(e,"default",{value:l,enumerable:!0}):e,l));var s=(l,t,e)=>Y(l,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const g=require("hls.js"),B=require("react/jsx-runtime"),h=require("react");function K(l){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(l){for(const e in l)if(e!=="default"){const i=Object.getOwnPropertyDescriptor(l,e);Object.defineProperty(t,e,i.get?i:{enumerable:!0,get:()=>l[e]})}}return t.default=l,Object.freeze(t)}const F=K(h);class O{constructor(t){s(this,"config");s(this,"sessionId");s(this,"events",[]);s(this,"sessionStartTime");s(this,"playbackStartTime",null);s(this,"totalPlayTime",0);s(this,"totalBufferTime",0);s(this,"bufferStartTime",null);s(this,"rebufferCount",0);s(this,"seekCount",0);s(this,"webSocket",null);s(this,"socketIO",null);s(this,"wsReconnectTimeout",null);s(this,"isDestroyed",!1);var e,i;if(this.config=t,this.sessionId=(t==null?void 0:t.sessionId)||this.generateSessionId(),this.sessionStartTime=Date.now(),(e=this.config)!=null&&e.webSocket){const n=this.config.webSocket;"type"in n?n.type==="socket.io"?this.initializeSocketIO():this.initializeWebSocket():this.initializeWebSocket()}(i=this.config)!=null&&i.enabled&&this.trackEvent("session_start",this.getSessionData())}trackEvent(t,e={}){var n;if(!((n=this.config)!=null&&n.enabled))return;const i={eventType:t,timestamp:Date.now(),sessionId:this.sessionId,videoId:this.config.videoId,userId:this.config.userId,data:{...e,...this.getQoEMetrics()}};this.events.push(i),this.updateMetrics(t,e),this.webSocket&&this.webSocket.readyState===WebSocket.OPEN&&this.sendToWebSocket(i),this.socketIO&&this.socketIO.connected&&this.sendToSocketIO(i),this.config.endpoint&&this.sendEvent(i),process.env.NODE_ENV==="development"&&console.log("[Analytics]",t,i.data)}updateMetrics(t,e){switch(t){case"play":this.playbackStartTime=Date.now();break;case"pause":case"ended":this.playbackStartTime&&(this.totalPlayTime+=Date.now()-this.playbackStartTime,this.playbackStartTime=null);break;case"buffering_start":this.bufferStartTime=Date.now(),this.rebufferCount++;break;case"buffering_end":this.bufferStartTime&&(this.totalBufferTime+=Date.now()-this.bufferStartTime,this.bufferStartTime=null);break;case"seeked":this.seekCount++;break}}getQoEMetrics(){const t=Date.now()-this.sessionStartTime,e=this.totalPlayTime>0?this.totalBufferTime/this.totalPlayTime:0;return{sessionDuration:t,totalPlayTime:this.totalPlayTime,totalBufferTime:this.totalBufferTime,bufferingRatio:Math.round(e*1e3)/1e3,rebufferCount:this.rebufferCount,seekCount:this.seekCount}}getSessionData(){return{userAgent:navigator.userAgent,platform:navigator.platform,language:navigator.language,screenResolution:`${screen.width}x${screen.height}`,viewport:`${window.innerWidth}x${window.innerHeight}`,connection:this.getConnectionInfo()}}getConnectionInfo(){const t=navigator,e=t.connection||t.mozConnection||t.webkitConnection;return e?{effectiveType:e.effectiveType,downlink:e.downlink,rtt:e.rtt,saveData:e.saveData}:null}async sendEvent(t){var e;if((e=this.config)!=null&&e.endpoint)try{await fetch(this.config.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}catch(i){console.error("Failed to send analytics event:",i)}}generateSessionId(){return`session_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}async initializeSocketIO(){var e;if(!((e=this.config)!=null&&e.webSocket)||!("type"in this.config.webSocket))return;const t=this.config.webSocket;if(t.type==="socket.io")try{if(typeof t.connection=="string"){const n=(await import("socket.io-client")).default;this.socketIO=n(t.connection,t.options||{})}else this.socketIO=t.connection;if(!this.socketIO)return;this.socketIO.on("connect",()=>{process.env.NODE_ENV==="development"&&console.log("[Analytics Socket.IO] Connected"),t.onConnect&&t.onConnect()}),this.socketIO.on("connect_error",i=>{console.error("[Analytics Socket.IO] Connection error:",i),t.onError&&t.onError(i)}),this.socketIO.on("disconnect",i=>{process.env.NODE_ENV==="development"&&console.log("[Analytics Socket.IO] Disconnected:",i),t.onDisconnect&&t.onDisconnect(i)}),this.socketIO.on("error",i=>{console.error("[Analytics Socket.IO] Error:",i),t.onError&&t.onError(i)})}catch(i){console.error("[Analytics Socket.IO] Failed to initialize:",i)}}sendToSocketIO(t){var e;if(!(!this.socketIO||!this.socketIO.connected))try{const i=(e=this.config)==null?void 0:e.webSocket,n=i!=null&&i.transform?i.transform(t):t,o=(i==null?void 0:i.eventName)||"analytics";this.socketIO.emit(o,n),process.env.NODE_ENV==="development"&&console.log(`[Analytics Socket.IO] Emitted (${o}):`,t.eventType)}catch(i){console.error("[Analytics Socket.IO] Failed to emit event:",i)}}initializeWebSocket(){var e;if(!((e=this.config)!=null&&e.webSocket))return;const t=this.config.webSocket;try{typeof t.connection=="string"?this.webSocket=new WebSocket(t.connection):this.webSocket=t.connection,this.webSocket.onopen=i=>{process.env.NODE_ENV==="development"&&console.log("[Analytics WebSocket] Connected"),t.onOpen&&t.onOpen(i)},this.webSocket.onerror=i=>{console.error("[Analytics WebSocket] Error:",i),t.onError&&t.onError(i)},this.webSocket.onclose=i=>{if(process.env.NODE_ENV==="development"&&console.log("[Analytics WebSocket] Disconnected"),t.onClose&&t.onClose(i),t.autoReconnect!==!1&&!this.isDestroyed){const o=t.reconnectDelay||3e3;process.env.NODE_ENV==="development"&&console.log(`[Analytics WebSocket] Reconnecting in ${o}ms...`),this.wsReconnectTimeout=window.setTimeout(()=>{this.initializeWebSocket()},o)}}}catch(i){console.error("[Analytics WebSocket] Failed to initialize:",i)}}sendToWebSocket(t){var e;if(!(!this.webSocket||this.webSocket.readyState!==WebSocket.OPEN))try{const i=(e=this.config)==null?void 0:e.webSocket,n=i!=null&&i.transform?i.transform(t):t;this.webSocket.send(JSON.stringify(n)),process.env.NODE_ENV==="development"&&console.log("[Analytics WebSocket] Sent:",t.eventType)}catch(i){console.error("[Analytics WebSocket] Failed to send event:",i)}}getEvents(){return[...this.events]}getMetrics(){return{sessionId:this.sessionId,...this.getQoEMetrics(),eventCount:this.events.length}}destroy(){var t;this.isDestroyed=!0,(t=this.config)!=null&&t.enabled&&this.trackEvent("session_end",this.getSessionData()),this.wsReconnectTimeout&&(clearTimeout(this.wsReconnectTimeout),this.wsReconnectTimeout=null),this.webSocket&&(this.webSocket.close(),this.webSocket=null),this.socketIO&&(this.socketIO.removeAllListeners(),this.socketIO.disconnect(),this.socketIO=null),this.events=[]}}class ${constructor(t,e){s(this,"container");s(this,"player");s(this,"controlsContainer");s(this,"progressContainer");s(this,"progressBar");s(this,"playButton");s(this,"skipBackwardButton");s(this,"skipForwardButton");s(this,"volumeButton");s(this,"volumeContainer");s(this,"fullscreenButton");s(this,"pipButton");s(this,"settingsButton");s(this,"volumeSlider");s(this,"progressInput");s(this,"hideControlsTimeout",null);s(this,"stickyControls",!1);s(this,"isVolumeSliderActive",!1);this.container=t,this.player=e,this.injectStyles(),this.createProgressBar(),this.controlsContainer=this.createControls(),this.container.appendChild(this.controlsContainer),this.playButton=this.controlsContainer.querySelector(".wontum-play-btn"),this.skipBackwardButton=this.controlsContainer.querySelector(".wontum-skip-backward-btn"),this.skipForwardButton=this.controlsContainer.querySelector(".wontum-skip-forward-btn"),this.volumeButton=this.controlsContainer.querySelector(".wontum-volume-btn"),this.volumeContainer=this.controlsContainer.querySelector(".wontum-volume-container"),this.fullscreenButton=this.controlsContainer.querySelector(".wontum-fullscreen-btn"),this.pipButton=this.controlsContainer.querySelector(".wontum-pip-btn"),this.settingsButton=this.controlsContainer.querySelector(".wontum-settings-btn"),this.volumeSlider=this.controlsContainer.querySelector(".wontum-volume-slider"),this.progressInput=this.container.querySelector(".wontum-progress-input"),this.progressBar=this.container.querySelector(".wontum-progress-filled"),this.stickyControls=this.player.config.stickyControls||!1,this.stickyControls&&this.controlsContainer.classList.add("sticky"),this.setupEventListeners(),this.setupPlayerEventListeners()}injectStyles(){const t="wontum-player-styles";if(document.getElementById(t))return;const e=this.player.config.theme||{},i=e.primaryColor||"#3b82f6",n=e.accentColor||"#2563eb",o=e.fontFamily||"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",r=e.controlsBackground||"linear-gradient(to top, rgba(0,0,0,0.8), transparent)",a=e.buttonHoverBg||"rgba(255, 255, 255, 0.1)",c=e.progressHeight||"6px",u=e.borderRadius||"4px",p=document.createElement("style");p.id=t,p.textContent=`
1
+ "use strict";var W=Object.create;var S=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var j=Object.getPrototypeOf,Q=Object.prototype.hasOwnProperty;var Y=(l,t,e)=>t in l?S(l,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):l[t]=e;var X=(l,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of _(t))!Q.call(l,n)&&n!==e&&S(l,n,{get:()=>t[n],enumerable:!(i=N(t,n))||i.enumerable});return l};var G=(l,t,e)=>(e=l!=null?W(j(l)):{},X(t||!l||!l.__esModule?S(e,"default",{value:l,enumerable:!0}):e,l));var r=(l,t,e)=>Y(l,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const g=require("hls.js"),F=require("react/jsx-runtime"),h=require("react");function K(l){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(l){for(const e in l)if(e!=="default"){const i=Object.getOwnPropertyDescriptor(l,e);Object.defineProperty(t,e,i.get?i:{enumerable:!0,get:()=>l[e]})}}return t.default=l,Object.freeze(t)}const B=K(h);class O{constructor(t){r(this,"config");r(this,"sessionId");r(this,"events",[]);r(this,"sessionStartTime");r(this,"playbackStartTime",null);r(this,"totalPlayTime",0);r(this,"totalBufferTime",0);r(this,"bufferStartTime",null);r(this,"rebufferCount",0);r(this,"seekCount",0);r(this,"webSocket",null);r(this,"socketIO",null);r(this,"wsReconnectTimeout",null);r(this,"isDestroyed",!1);var e,i;if(this.config=t,this.sessionId=(t==null?void 0:t.sessionId)||this.generateSessionId(),this.sessionStartTime=Date.now(),(e=this.config)!=null&&e.webSocket){const n=this.config.webSocket;"type"in n?n.type==="socket.io"?this.initializeSocketIO():this.initializeWebSocket():this.initializeWebSocket()}(i=this.config)!=null&&i.enabled&&this.trackEvent("session_start",this.getSessionData())}trackEvent(t,e={}){var n;if(!((n=this.config)!=null&&n.enabled))return;const i={eventType:t,timestamp:Date.now(),sessionId:this.sessionId,videoId:this.config.videoId,userId:this.config.userId,data:{...e,...this.getQoEMetrics()}};this.events.push(i),this.updateMetrics(t,e),this.webSocket&&this.webSocket.readyState===WebSocket.OPEN&&this.sendToWebSocket(i),this.socketIO&&this.socketIO.connected&&this.sendToSocketIO(i),this.config.endpoint&&this.sendEvent(i),process.env.NODE_ENV==="development"&&console.log("[Analytics]",t,i.data)}updateMetrics(t,e){switch(t){case"play":this.playbackStartTime=Date.now();break;case"pause":case"ended":this.playbackStartTime&&(this.totalPlayTime+=Date.now()-this.playbackStartTime,this.playbackStartTime=null);break;case"buffering_start":this.bufferStartTime=Date.now(),this.rebufferCount++;break;case"buffering_end":this.bufferStartTime&&(this.totalBufferTime+=Date.now()-this.bufferStartTime,this.bufferStartTime=null);break;case"seeked":this.seekCount++;break}}getQoEMetrics(){const t=Date.now()-this.sessionStartTime,e=this.totalPlayTime>0?this.totalBufferTime/this.totalPlayTime:0;return{sessionDuration:t,totalPlayTime:this.totalPlayTime,totalBufferTime:this.totalBufferTime,bufferingRatio:Math.round(e*1e3)/1e3,rebufferCount:this.rebufferCount,seekCount:this.seekCount}}getSessionData(){return{userAgent:navigator.userAgent,platform:navigator.platform,language:navigator.language,screenResolution:`${screen.width}x${screen.height}`,viewport:`${window.innerWidth}x${window.innerHeight}`,connection:this.getConnectionInfo()}}getConnectionInfo(){const t=navigator,e=t.connection||t.mozConnection||t.webkitConnection;return e?{effectiveType:e.effectiveType,downlink:e.downlink,rtt:e.rtt,saveData:e.saveData}:null}async sendEvent(t){var e;if((e=this.config)!=null&&e.endpoint)try{await fetch(this.config.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}catch(i){console.error("Failed to send analytics event:",i)}}generateSessionId(){return`session_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}async initializeSocketIO(){var e;if(!((e=this.config)!=null&&e.webSocket)||!("type"in this.config.webSocket))return;const t=this.config.webSocket;if(t.type==="socket.io")try{if(typeof t.connection=="string"){const n=(await import("socket.io-client")).default;this.socketIO=n(t.connection,t.options||{})}else this.socketIO=t.connection;if(!this.socketIO)return;this.socketIO.on("connect",()=>{process.env.NODE_ENV==="development"&&console.log("[Analytics Socket.IO] Connected"),t.onConnect&&t.onConnect()}),this.socketIO.on("connect_error",i=>{console.error("[Analytics Socket.IO] Connection error:",i),t.onError&&t.onError(i)}),this.socketIO.on("disconnect",i=>{process.env.NODE_ENV==="development"&&console.log("[Analytics Socket.IO] Disconnected:",i),t.onDisconnect&&t.onDisconnect(i)}),this.socketIO.on("error",i=>{console.error("[Analytics Socket.IO] Error:",i),t.onError&&t.onError(i)})}catch(i){console.error("[Analytics Socket.IO] Failed to initialize:",i)}}sendToSocketIO(t){var e;if(!(!this.socketIO||!this.socketIO.connected))try{const i=(e=this.config)==null?void 0:e.webSocket,n=i!=null&&i.transform?i.transform(t):t,o=(i==null?void 0:i.eventName)||"analytics";this.socketIO.emit(o,n),process.env.NODE_ENV==="development"&&console.log(`[Analytics Socket.IO] Emitted (${o}):`,t.eventType)}catch(i){console.error("[Analytics Socket.IO] Failed to emit event:",i)}}initializeWebSocket(){var e;if(!((e=this.config)!=null&&e.webSocket))return;const t=this.config.webSocket;try{typeof t.connection=="string"?this.webSocket=new WebSocket(t.connection):this.webSocket=t.connection,this.webSocket.onopen=i=>{process.env.NODE_ENV==="development"&&console.log("[Analytics WebSocket] Connected"),t.onOpen&&t.onOpen(i)},this.webSocket.onerror=i=>{console.error("[Analytics WebSocket] Error:",i),t.onError&&t.onError(i)},this.webSocket.onclose=i=>{if(process.env.NODE_ENV==="development"&&console.log("[Analytics WebSocket] Disconnected"),t.onClose&&t.onClose(i),t.autoReconnect!==!1&&!this.isDestroyed){const o=t.reconnectDelay||3e3;process.env.NODE_ENV==="development"&&console.log(`[Analytics WebSocket] Reconnecting in ${o}ms...`),this.wsReconnectTimeout=window.setTimeout(()=>{this.initializeWebSocket()},o)}}}catch(i){console.error("[Analytics WebSocket] Failed to initialize:",i)}}sendToWebSocket(t){var e;if(!(!this.webSocket||this.webSocket.readyState!==WebSocket.OPEN))try{const i=(e=this.config)==null?void 0:e.webSocket,n=i!=null&&i.transform?i.transform(t):t;this.webSocket.send(JSON.stringify(n)),process.env.NODE_ENV==="development"&&console.log("[Analytics WebSocket] Sent:",t.eventType)}catch(i){console.error("[Analytics WebSocket] Failed to send event:",i)}}getEvents(){return[...this.events]}getMetrics(){return{sessionId:this.sessionId,...this.getQoEMetrics(),eventCount:this.events.length}}destroy(){var t;this.isDestroyed=!0,(t=this.config)!=null&&t.enabled&&this.trackEvent("session_end",this.getSessionData()),this.wsReconnectTimeout&&(clearTimeout(this.wsReconnectTimeout),this.wsReconnectTimeout=null),this.webSocket&&(this.webSocket.close(),this.webSocket=null),this.socketIO&&(this.socketIO.removeAllListeners(),this.socketIO.disconnect(),this.socketIO=null),this.events=[]}}class z{constructor(t,e){r(this,"container");r(this,"player");r(this,"controlsContainer");r(this,"progressContainer");r(this,"progressBar");r(this,"playButton");r(this,"skipBackwardButton");r(this,"skipForwardButton");r(this,"volumeButton");r(this,"volumeContainer");r(this,"fullscreenButton");r(this,"pipButton");r(this,"settingsButton");r(this,"volumeSlider");r(this,"progressInput");r(this,"hideControlsTimeout",null);r(this,"stickyControls",!1);r(this,"isVolumeSliderActive",!1);this.container=t,this.player=e,this.injectStyles(),this.createProgressBar(),this.controlsContainer=this.createControls(),this.container.appendChild(this.controlsContainer),this.playButton=this.controlsContainer.querySelector(".wontum-play-btn"),this.skipBackwardButton=this.controlsContainer.querySelector(".wontum-skip-backward-btn"),this.skipForwardButton=this.controlsContainer.querySelector(".wontum-skip-forward-btn"),this.volumeButton=this.controlsContainer.querySelector(".wontum-volume-btn"),this.volumeContainer=this.controlsContainer.querySelector(".wontum-volume-container"),this.fullscreenButton=this.controlsContainer.querySelector(".wontum-fullscreen-btn"),this.pipButton=this.controlsContainer.querySelector(".wontum-pip-btn"),this.settingsButton=this.controlsContainer.querySelector(".wontum-settings-btn"),this.volumeSlider=this.controlsContainer.querySelector(".wontum-volume-slider"),this.progressInput=this.container.querySelector(".wontum-progress-input"),this.progressBar=this.container.querySelector(".wontum-progress-filled"),this.stickyControls=this.player.config.stickyControls||!1,this.stickyControls&&this.controlsContainer.classList.add("sticky"),this.setupEventListeners(),this.setupPlayerEventListeners()}injectStyles(){const t="wontum-player-styles";if(document.getElementById(t))return;const e=this.player.config.theme||{},i=e.primaryColor||"#3b82f6",n=e.accentColor||"#2563eb",o=e.fontFamily||"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",s=e.controlsBackground||"linear-gradient(to top, rgba(0,0,0,0.8), transparent)",a=e.buttonHoverBg||"rgba(255, 255, 255, 0.1)",c=e.progressHeight||"6px",u=e.borderRadius||"4px",p=document.createElement("style");p.id=t,p.textContent=`
2
2
  .wontum-player-container {
3
3
  position: relative;
4
4
  background: #000;
@@ -6,7 +6,7 @@
6
6
  overflow: hidden;
7
7
  --primary-color: ${i};
8
8
  --accent-color: ${n};
9
- --controls-bg: ${r};
9
+ --controls-bg: ${s};
10
10
  --button-hover: ${a};
11
11
  --progress-height: ${c};
12
12
  --border-radius: ${u};
@@ -562,18 +562,18 @@
562
562
  <div class="wontum-loading" style="display: none;">
563
563
  <div class="wontum-spinner"></div>
564
564
  </div>
565
- `,t}setupEventListeners(){this.playButton.addEventListener("click",()=>{this.player.getState().playing?this.player.pause():this.player.play()}),this.skipBackwardButton.addEventListener("click",()=>{this.player.skipBackward(10)}),this.skipForwardButton.addEventListener("click",()=>{this.player.skipForward(10)}),this.progressInput.addEventListener("input",i=>{const n=i.target,o=parseFloat(n.value),r=this.player.getState(),a=o/100*r.duration;this.player.seek(a)}),this.volumeSlider.addEventListener("input",i=>{const n=i.target,o=parseFloat(n.value)/100;this.player.setVolume(o)}),this.volumeButton.addEventListener("click",()=>{this.player.getState().muted?this.player.unmute():this.player.mute()}),this.volumeContainer.addEventListener("mouseenter",()=>{this.isVolumeSliderActive=!0}),this.volumeContainer.addEventListener("mouseleave",()=>{this.isVolumeSliderActive=!1}),this.volumeSlider.addEventListener("input",()=>{this.isVolumeSliderActive=!0,this.resetHideControlsTimeout()}),this.volumeSlider.addEventListener("change",()=>{setTimeout(()=>{this.isVolumeSliderActive=!1},500)}),this.fullscreenButton.addEventListener("click",()=>{this.player.getState().fullscreen?this.player.exitFullscreen():this.player.enterFullscreen()}),this.pipButton.addEventListener("click",async()=>{try{await this.player.togglePictureInPicture()}catch(i){console.error("PiP error:",i)}}),this.settingsButton.addEventListener("click",()=>{const i=this.controlsContainer.querySelector(".wontum-settings-panel");i.classList.toggle("active"),i.classList.contains("active")&&(this.updateSettingsMenu(),this.updateQualityMenu(),this.updateSpeedMenu(),this.updateSubtitleMenu())});const t=this.controlsContainer.querySelectorAll(".wontum-tab");t.forEach(i=>{i.addEventListener("click",n=>{const o=n.currentTarget,r=o.getAttribute("data-tab");t.forEach(u=>u.classList.remove("active")),o.classList.add("active"),this.controlsContainer.querySelectorAll(".wontum-tab-panel").forEach(u=>u.classList.remove("active"));const c=this.controlsContainer.querySelector(`[data-panel="${r}"]`);c==null||c.classList.add("active")})}),this.player.getVideoElement().addEventListener("click",()=>{this.player.getState().playing?this.player.pause():this.player.play()}),this.container.addEventListener("mousemove",()=>{this.showControls(),this.resetHideControlsTimeout()}),this.container.addEventListener("mouseleave",()=>{this.hideControls()})}setupPlayerEventListeners(){this.player.on("play",()=>{this.playButton.innerHTML=this.getPauseIcon()}),this.player.on("pause",()=>{this.playButton.innerHTML=this.getPlayIcon()}),this.player.on("timeupdate",t=>{const{currentTime:e}=t.data,i=this.player.getState();if(i.duration>0){const o=e/i.duration*100;this.progressBar.style.width=`${o}%`,this.progressInput.value=o.toString()}const n=this.controlsContainer.querySelector(".wontum-current-time");n.textContent=this.formatTime(e)}),this.player.on("loadedmetadata",t=>{const{duration:e}=t.data,i=this.controlsContainer.querySelector(".wontum-duration");i.textContent=this.formatTime(e),t.data.qualities&&this.updateQualityMenu(t.data.qualities)}),this.player.on("volumechange",t=>{const{volume:e,muted:i}=t.data;this.volumeSlider.value=(e*100).toString(),this.volumeButton.innerHTML=i?this.getMutedIcon():this.getVolumeIcon()}),this.player.on("waiting",()=>{const t=this.controlsContainer.querySelector(".wontum-loading");t.style.display="block"}),this.player.on("canplay",()=>{const t=this.controlsContainer.querySelector(".wontum-loading");t.style.display="none"})}updateSubtitleMenu(){const t=this.controlsContainer.querySelector(".wontum-subtitle-menu"),e=this.player.getSubtitleTracks();if(e.length===0){t.innerHTML='<div class="wontum-subtitle-option">No subtitles available</div>';return}const i=e.findIndex(n=>n.mode==="showing");t.innerHTML=`
565
+ `,t}setupEventListeners(){this.playButton.addEventListener("click",()=>{this.player.getState().playing?this.player.pause():this.player.play()}),this.skipBackwardButton.addEventListener("click",()=>{this.player.skipBackward(10)}),this.skipForwardButton.addEventListener("click",()=>{this.player.skipForward(10)}),this.progressInput.addEventListener("input",i=>{const n=i.target,o=parseFloat(n.value),s=this.player.getState(),a=o/100*s.duration;this.player.seek(a)}),this.volumeSlider.addEventListener("input",i=>{const n=i.target,o=parseFloat(n.value)/100;this.player.setVolume(o)}),this.volumeButton.addEventListener("click",()=>{this.player.getState().muted?this.player.unmute():this.player.mute()}),this.volumeContainer.addEventListener("mouseenter",()=>{this.isVolumeSliderActive=!0}),this.volumeContainer.addEventListener("mouseleave",()=>{this.isVolumeSliderActive=!1}),this.volumeSlider.addEventListener("input",()=>{this.isVolumeSliderActive=!0,this.resetHideControlsTimeout()}),this.volumeSlider.addEventListener("change",()=>{setTimeout(()=>{this.isVolumeSliderActive=!1},500)}),this.fullscreenButton.addEventListener("click",()=>{this.player.getState().fullscreen?this.player.exitFullscreen():this.player.enterFullscreen()}),this.pipButton.addEventListener("click",async()=>{try{await this.player.togglePictureInPicture()}catch(i){console.error("PiP error:",i)}}),this.settingsButton.addEventListener("click",()=>{const i=this.controlsContainer.querySelector(".wontum-settings-panel");i.classList.toggle("active"),i.classList.contains("active")&&(this.updateSettingsMenu(),this.updateQualityMenu(),this.updateSpeedMenu(),this.updateSubtitleMenu())});const t=this.controlsContainer.querySelectorAll(".wontum-tab");t.forEach(i=>{i.addEventListener("click",n=>{const o=n.currentTarget,s=o.getAttribute("data-tab");t.forEach(u=>u.classList.remove("active")),o.classList.add("active"),this.controlsContainer.querySelectorAll(".wontum-tab-panel").forEach(u=>u.classList.remove("active"));const c=this.controlsContainer.querySelector(`[data-panel="${s}"]`);c==null||c.classList.add("active")})}),this.player.getVideoElement().addEventListener("click",()=>{this.player.getState().playing?this.player.pause():this.player.play()}),this.container.addEventListener("mousemove",()=>{this.showControls(),this.resetHideControlsTimeout()}),this.container.addEventListener("mouseleave",()=>{this.hideControls()})}setupPlayerEventListeners(){this.player.on("play",()=>{this.playButton.innerHTML=this.getPauseIcon()}),this.player.on("pause",()=>{this.playButton.innerHTML=this.getPlayIcon()}),this.player.on("timeupdate",t=>{const{currentTime:e}=t.data,i=this.player.getState();if(i.duration>0){const o=e/i.duration*100;this.progressBar.style.width=`${o}%`,this.progressInput.value=o.toString()}const n=this.controlsContainer.querySelector(".wontum-current-time");n.textContent=this.formatTime(e)}),this.player.on("loadedmetadata",t=>{const{duration:e}=t.data,i=this.controlsContainer.querySelector(".wontum-duration");i.textContent=this.formatTime(e),t.data.qualities&&this.updateQualityMenu(t.data.qualities)}),this.player.on("volumechange",t=>{const{volume:e,muted:i}=t.data;this.volumeSlider.value=(e*100).toString(),this.volumeButton.innerHTML=i?this.getMutedIcon():this.getVolumeIcon()}),this.player.on("waiting",()=>{const t=this.controlsContainer.querySelector(".wontum-loading");t.style.display="block"}),this.player.on("canplay",()=>{const t=this.controlsContainer.querySelector(".wontum-loading");t.style.display="none"})}updateSubtitleMenu(){const t=this.controlsContainer.querySelector(".wontum-subtitle-menu"),e=this.player.getSubtitleTracks();if(e.length===0){t.innerHTML='<div class="wontum-subtitle-option">No subtitles available</div>';return}const i=e.findIndex(n=>n.mode==="showing");t.innerHTML=`
566
566
  <div class="wontum-subtitle-option ${i===-1?"active":""}" data-track="-1">Off</div>
567
567
  ${e.map((n,o)=>`
568
568
  <div class="wontum-subtitle-option ${o===i?"active":""}" data-track="${o}">
569
569
  ${n.label||n.language||`Track ${o+1}`}
570
570
  </div>
571
571
  `).join("")}
572
- `,t.querySelectorAll(".wontum-subtitle-option").forEach(n=>{n.addEventListener("click",o=>{const r=o.target,a=parseInt(r.dataset.track||"-1");a===-1?this.player.disableSubtitles():this.player.enableSubtitles(a),t.querySelectorAll(".wontum-subtitle-option").forEach(c=>c.classList.remove("active")),r.classList.add("active")})})}updateSpeedMenu(){const t=this.controlsContainer.querySelector(".wontum-speed-menu"),i=this.player.getState().playbackRate||1,n=[.25,.5,.75,1,1.25,1.5,1.75,2];t.innerHTML=n.map(o=>`
572
+ `,t.querySelectorAll(".wontum-subtitle-option").forEach(n=>{n.addEventListener("click",o=>{const s=o.target,a=parseInt(s.dataset.track||"-1");a===-1?this.player.disableSubtitles():this.player.enableSubtitles(a),t.querySelectorAll(".wontum-subtitle-option").forEach(c=>c.classList.remove("active")),s.classList.add("active")})})}updateSpeedMenu(){const t=this.controlsContainer.querySelector(".wontum-speed-menu"),i=this.player.getState().playbackRate||1,n=[.25,.5,.75,1,1.25,1.5,1.75,2];t.innerHTML=n.map(o=>`
573
573
  <div class="wontum-speed-option ${i===o?"active":""}" data-speed="${o}">
574
574
  ${o===1?"Normal":o+"x"}
575
575
  </div>
576
- `).join(""),t.querySelectorAll(".wontum-speed-option").forEach(o=>{o.addEventListener("click",r=>{const a=r.target,c=parseFloat(a.dataset.speed||"1");this.player.setPlaybackRate(c),t.querySelectorAll(".wontum-speed-option").forEach(u=>u.classList.remove("active")),a.classList.add("active")})})}updateSettingsMenu(){const t=this.controlsContainer.querySelector(".wontum-settings-menu");t.innerHTML=`
576
+ `).join(""),t.querySelectorAll(".wontum-speed-option").forEach(o=>{o.addEventListener("click",s=>{const a=s.target,c=parseFloat(a.dataset.speed||"1");this.player.setPlaybackRate(c),t.querySelectorAll(".wontum-speed-option").forEach(u=>u.classList.remove("active")),a.classList.add("active")})})}updateSettingsMenu(){const t=this.controlsContainer.querySelector(".wontum-settings-menu");t.innerHTML=`
577
577
  <div class="wontum-settings-option" data-setting="sticky-controls">
578
578
  <span>Sticky Controls</span>
579
579
  <div class="wontum-toggle-switch ${this.stickyControls?"active":""}"></div>
@@ -583,7 +583,7 @@
583
583
  ${i.map((n,o)=>`
584
584
  <div class="wontum-quality-option" data-quality="${o}">${n.name}</div>
585
585
  `).join("")}
586
- `,e.querySelectorAll(".wontum-quality-option").forEach(n=>{n.addEventListener("click",o=>{const r=o.target,a=parseInt(r.dataset.quality||"-1");this.player.setQuality(a),e.querySelectorAll(".wontum-quality-option").forEach(c=>c.classList.remove("active")),r.classList.add("active")})})}showControls(){this.controlsContainer.classList.remove("hidden"),this.progressContainer.classList.remove("hidden")}hideControls(){if(this.stickyControls||this.isVolumeSliderActive)return;this.player.getState().playing&&(this.controlsContainer.classList.add("hidden"),this.progressContainer.classList.add("hidden"))}resetHideControlsTimeout(){this.stickyControls||(this.hideControlsTimeout&&clearTimeout(this.hideControlsTimeout),this.hideControlsTimeout=window.setTimeout(()=>{this.hideControls()},1e4))}formatTime(t){if(isNaN(t))return"0:00";const e=Math.floor(t/60),i=Math.floor(t%60);return`${e}:${i.toString().padStart(2,"0")}`}getPlayIcon(){return'<svg viewBox="0 0 24 24"><path fill="white" d="M8 5v14l11-7z"/></svg>'}getPauseIcon(){return'<svg viewBox="0 0 24 24"><path fill="white" d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/></svg>'}getVolumeIcon(){return'<svg viewBox="0 0 24 24"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/></svg>'}getMutedIcon(){return'<svg viewBox="0 0 24 24"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/></svg>'}getFullscreenIcon(){return'<svg viewBox="0 0 24 24"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>'}getPipIcon(){return'<svg viewBox="0 0 24 24"><path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 1.98 2 1.98h18c1.1 0 2-.88 2-1.98V5c0-1.1-.9-2-2-2zm0 16.01H3V4.98h18v14.03z"/></svg>'}getSkipBackwardIcon(){return`<svg viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
586
+ `,e.querySelectorAll(".wontum-quality-option").forEach(n=>{n.addEventListener("click",o=>{const s=o.target,a=parseInt(s.dataset.quality||"-1");this.player.setQuality(a),e.querySelectorAll(".wontum-quality-option").forEach(c=>c.classList.remove("active")),s.classList.add("active")})})}showControls(){this.controlsContainer.classList.remove("hidden"),this.progressContainer.classList.remove("hidden")}hideControls(){if(this.stickyControls||this.isVolumeSliderActive)return;this.player.getState().playing&&(this.controlsContainer.classList.add("hidden"),this.progressContainer.classList.add("hidden"))}resetHideControlsTimeout(){this.stickyControls||(this.hideControlsTimeout&&clearTimeout(this.hideControlsTimeout),this.hideControlsTimeout=window.setTimeout(()=>{this.hideControls()},1e4))}formatTime(t){if(isNaN(t))return"0:00";const e=Math.floor(t/60),i=Math.floor(t%60);return`${e}:${i.toString().padStart(2,"0")}`}getPlayIcon(){return'<svg viewBox="0 0 24 24"><path fill="white" d="M8 5v14l11-7z"/></svg>'}getPauseIcon(){return'<svg viewBox="0 0 24 24"><path fill="white" d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/></svg>'}getVolumeIcon(){return'<svg viewBox="0 0 24 24"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/></svg>'}getMutedIcon(){return'<svg viewBox="0 0 24 24"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/></svg>'}getFullscreenIcon(){return'<svg viewBox="0 0 24 24"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>'}getPipIcon(){return'<svg viewBox="0 0 24 24"><path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 1.98 2 1.98h18c1.1 0 2-.88 2-1.98V5c0-1.1-.9-2-2-2zm0 16.01H3V4.98h18v14.03z"/></svg>'}getSkipBackwardIcon(){return`<svg viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
587
587
  <circle cx="30" cy="30" r="28" stroke="white" stroke-width="2"/>
588
588
  <!-- Circular arrow backward -->
589
589
  <path d="M30 12 A18 18 0 1 0 30 48" stroke="white" stroke-width="2.5" stroke-linecap="round" fill="none"/>
@@ -595,4 +595,4 @@
595
595
  <path d="M30 12 A18 18 0 1 1 30 48" stroke="white" stroke-width="2.5" stroke-linecap="round" fill="none"/>
596
596
  <path d="M35 12 L30 12 L30 17" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
597
597
  <text x="30" y="35" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="white" text-anchor="middle">10</text>
598
- </svg>`}getSettingsIcon(){return'<svg viewBox="0 0 24 24"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>'}destroy(){this.hideControlsTimeout&&clearTimeout(this.hideControlsTimeout),this.controlsContainer.remove()}}class z{constructor(t){s(this,"config");s(this,"urlCache",new Map);s(this,"signedUrls",new Set);this.config=t}async processUrl(t){return this.isCloudFrontUrl(t)?this.signCloudFrontUrl(t):this.isS3Url(t)?this.getPresignedUrl(t):t}isCloudFrontUrl(t){var e;if(!((e=this.config)!=null&&e.cloudFrontDomains)||this.config.cloudFrontDomains.length===0)return!1;try{const i=new URL(t);return this.config.cloudFrontDomains.some(n=>i.hostname.includes(n))}catch{return!1}}isS3Url(t){return t.includes(".s3.")||t.includes("s3.amazonaws.com")||t.startsWith("s3://")}async signCloudFrontUrl(t,e=0){var o,r;if(this.signedUrls.has(t))return t;if((o=this.config)!=null&&o.signUrl)try{const a=await this.config.signUrl(t);return this.signedUrls.add(t),a}catch(a){const c=(a==null?void 0:a.name)==="AbortError"||((r=a==null?void 0:a.message)==null?void 0:r.includes("aborted"));if(c&&e<2)return console.warn(`Sign URL aborted, retrying (${e+1}/2)...`),await new Promise(u=>setTimeout(u,300)),this.signCloudFrontUrl(t,e+1);throw console.error("Failed to sign CloudFront URL:",a),c?new Error("Failed to sign CloudFront URL: Request was aborted. If using Apollo Client or other GraphQL clients, consider moving the query outside component lifecycle or using useQuery with skip option."):new Error(`Failed to sign CloudFront URL: ${(a==null?void 0:a.message)||"Unknown error"}`)}return console.warn("No signUrl function provided. CloudFront cookies may not be set."),t}extractS3Key(t){if(t.startsWith("s3://"))return t.replace("s3://","").split("/").slice(1).join("/");const e=t.match(/s3[.-]([^.]+)\.amazonaws\.com\/(.+)/);if(e)return e[2];const i=t.match(/([^.]+)\.s3\.amazonaws\.com\/(.+)/);return i?i[2]:t}async getPresignedUrl(t){var n;const e=this.extractS3Key(t),i=this.urlCache.get(e);if(i&&i.expiresAt>Date.now())return i.url;if((n=this.config)!=null&&n.getPresignedUrl)try{const o=await this.config.getPresignedUrl(e);return this.urlCache.set(e,{url:o,expiresAt:Date.now()+50*60*1e3}),o}catch(o){throw console.error("Failed to generate presigned URL:",o),new Error("Failed to generate presigned URL for S3 object")}return console.warn("No getPresignedUrl function provided. Using direct S3 URL (requires public bucket)"),t}static constructS3Url(t,e,i="us-east-1"){return`https://${t}.s3.${i}.amazonaws.com/${e}`}static parseS3Uri(t){if(!t.startsWith("s3://"))return null;const e=t.replace("s3://","").split("/"),i=e[0],n=e.slice(1).join("/");return{bucket:i,key:n}}clearCache(){this.urlCache.clear(),this.signedUrls.clear()}}class E{constructor(t){s(this,"container");s(this,"videoElement");s(this,"hls",null);s(this,"config");s(this,"eventListeners",new Map);s(this,"analytics");s(this,"s3Handler");s(this,"uiController");s(this,"qualities",[]);s(this,"state",{playing:!1,paused:!0,ended:!1,buffering:!1,currentTime:0,duration:0,volume:1,muted:!1,playbackRate:1,quality:"auto",availableQualities:[],fullscreen:!1});if(this.config=t,this.container=typeof t.container=="string"?document.querySelector(t.container):t.container,!this.container)throw new Error("Container element not found");this.analytics=new O(t.analytics),this.s3Handler=new z(t.s3Config),this.videoElement=this.createVideoElement(),this.container.appendChild(this.videoElement),this.uiController=new $(this.container,this),this.setupVideoListeners(),this.loadSource(t.src),t.autoplay&&(this.videoElement.autoplay=!0),t.muted&&this.mute(),t.poster&&(this.videoElement.poster=t.poster),t.preload&&(this.videoElement.preload=t.preload),t.subtitles&&this.addSubtitleTracks(t.subtitles)}addSubtitleTracks(t){t.forEach(e=>{const i=document.createElement("track");i.kind="subtitles",i.label=e.label,i.src=e.src,i.srclang=e.srclang,e.default&&(i.default=!0),this.videoElement.appendChild(i)})}createVideoElement(){const t=document.createElement("video");return t.className="wontum-player-video",t.style.width="100%",t.style.height="100%",t.playsInline=!0,t.crossOrigin="use-credentials",t}setupVideoListeners(){this.videoElement.addEventListener("play",()=>{this.state.playing=!0,this.state.paused=!1,this.emit("play"),this.analytics.trackEvent("play",this.getAnalyticsData())}),this.videoElement.addEventListener("pause",()=>{this.state.playing=!1,this.state.paused=!0,this.emit("pause"),this.analytics.trackEvent("pause",this.getAnalyticsData())}),this.videoElement.addEventListener("ended",()=>{this.state.ended=!0,this.state.playing=!1,this.emit("ended"),this.analytics.trackEvent("ended",this.getAnalyticsData())}),this.videoElement.addEventListener("timeupdate",()=>{this.state.currentTime=this.videoElement.currentTime,this.emit("timeupdate",{currentTime:this.state.currentTime})}),this.videoElement.addEventListener("loadedmetadata",()=>{this.state.duration=this.videoElement.duration,this.emit("loadedmetadata",{duration:this.state.duration}),this.analytics.trackEvent("loadedmetadata",this.getAnalyticsData())}),this.videoElement.addEventListener("volumechange",()=>{this.state.volume=this.videoElement.volume,this.state.muted=this.videoElement.muted,this.emit("volumechange",{volume:this.state.volume,muted:this.state.muted})}),this.videoElement.addEventListener("ratechange",()=>{this.state.playbackRate=this.videoElement.playbackRate,this.emit("ratechange",{playbackRate:this.state.playbackRate})}),this.videoElement.addEventListener("waiting",()=>{this.state.buffering=!0,this.emit("waiting"),this.analytics.trackEvent("buffering_start",this.getAnalyticsData())}),this.videoElement.addEventListener("canplay",()=>{this.state.buffering=!1,this.emit("canplay"),this.analytics.trackEvent("buffering_end",this.getAnalyticsData())}),this.videoElement.addEventListener("seeking",()=>{this.emit("seeking")}),this.videoElement.addEventListener("seeked",()=>{this.emit("seeked",{currentTime:this.state.currentTime}),this.analytics.trackEvent("seeked",this.getAnalyticsData())}),this.videoElement.addEventListener("error",t=>{const e=this.videoElement.error;this.emit("error",{error:e}),this.analytics.trackEvent("error",{...this.getAnalyticsData(),error:e==null?void 0:e.message})}),this.videoElement.addEventListener("loadstart",()=>{this.emit("loadstart")}),this.videoElement.addEventListener("loadeddata",()=>{this.emit("loadeddata")}),this.videoElement.addEventListener("canplaythrough",()=>{this.emit("canplaythrough")}),this.videoElement.addEventListener("playing",()=>{this.state.playing=!0,this.state.buffering=!1,this.emit("playing")}),this.videoElement.addEventListener("durationchange",()=>{this.state.duration=this.videoElement.duration,this.emit("durationchange",{duration:this.state.duration})}),this.videoElement.addEventListener("progress",()=>{this.emit("progress",{buffered:this.videoElement.buffered})}),this.videoElement.addEventListener("stalled",()=>{this.emit("stalled")}),this.videoElement.addEventListener("suspend",()=>{this.emit("suspend")}),this.videoElement.addEventListener("abort",()=>{this.emit("abort")}),this.videoElement.addEventListener("emptied",()=>{this.emit("emptied")}),this.videoElement.addEventListener("resize",()=>{this.emit("resize",{videoWidth:this.videoElement.videoWidth,videoHeight:this.videoElement.videoHeight})})}async loadSource(t){var e;try{const i=await this.s3Handler.processUrl(t);if(g.isSupported()){const n=((e=this.config.s3Config)==null?void 0:e.withCredentials)??!1,o={...this.config.hlsConfig,xhrSetup:(r,a)=>{var c;n&&(r.withCredentials=!0),(c=this.config.hlsConfig)!=null&&c.xhrSetup&&this.config.hlsConfig.xhrSetup(r,a)}};this.hls=new g(o),this.hls.loadSource(i),this.hls.attachMedia(this.videoElement),this.hls.on(g.Events.MANIFEST_PARSED,(r,a)=>{const c=this.extractQualities(a.levels);this.qualities=c}),this.hls.on(g.Events.LEVEL_SWITCHED,(r,a)=>{var u;const c=(u=this.hls)==null?void 0:u.levels[a.level];c&&(this.state.quality=`${c.height}p`,this.emit("qualitychange",{quality:this.state.quality}))}),this.hls.on(g.Events.ERROR,(r,a)=>{a.fatal&&this.handleHlsError(a)})}else if(this.videoElement.canPlayType("application/vnd.apple.mpegurl"))this.videoElement.src=i;else throw new Error("HLS is not supported in this browser")}catch(i){console.error("Failed to load video source:",i),this.emit("error",{error:i})}}extractQualities(t){return t.map(e=>({height:e.height,width:e.width,bitrate:e.bitrate,name:`${e.height}p`}))}handleHlsError(t){var e,i;switch(t.type){case g.ErrorTypes.NETWORK_ERROR:console.error("Network error occurred"),(e=this.hls)==null||e.startLoad();break;case g.ErrorTypes.MEDIA_ERROR:console.error("Media error occurred"),(i=this.hls)==null||i.recoverMediaError();break;default:console.error("Fatal error occurred:",t),this.destroy();break}}getAnalyticsData(){return{currentTime:this.state.currentTime,duration:this.state.duration,quality:this.state.quality,playbackRate:this.state.playbackRate,volume:this.state.volume,muted:this.state.muted}}play(){return this.videoElement.play()}pause(){this.videoElement.pause()}seek(t){this.videoElement.currentTime=t}skipForward(t=10){const e=Math.min(this.state.currentTime+t,this.state.duration);this.seek(e)}skipBackward(t=10){const e=Math.max(this.state.currentTime-t,0);this.seek(e)}setVolume(t){this.videoElement.volume=Math.max(0,Math.min(1,t))}mute(){this.videoElement.muted=!0}unmute(){this.videoElement.muted=!1}setPlaybackRate(t){this.videoElement.playbackRate=t}setQuality(t){this.hls&&(this.hls.currentLevel=t)}getQualities(){return this.qualities}enterFullscreen(){this.container.requestFullscreen&&(this.container.requestFullscreen(),this.state.fullscreen=!0,this.emit("fullscreenchange",{fullscreen:!0}))}exitFullscreen(){document.exitFullscreen&&(document.exitFullscreen(),this.state.fullscreen=!1,this.emit("fullscreenchange",{fullscreen:!1}))}async enterPictureInPicture(){if(document.pictureInPictureEnabled&&!this.videoElement.disablePictureInPicture)try{await this.videoElement.requestPictureInPicture(),this.emit("pictureinpictureenter",{})}catch(t){throw console.error("Failed to enter Picture-in-Picture:",t),t}}async exitPictureInPicture(){if(document.pictureInPictureElement)try{await document.exitPictureInPicture(),this.emit("pictureinpictureexit",{})}catch(t){throw console.error("Failed to exit Picture-in-Picture:",t),t}}async togglePictureInPicture(){document.pictureInPictureElement?await this.exitPictureInPicture():await this.enterPictureInPicture()}getState(){return{...this.state}}getVideoElement(){return this.videoElement}enableSubtitles(t){const e=this.videoElement.textTracks;for(let i=0;i<e.length;i++)e[i].mode=i===t?"showing":"hidden"}disableSubtitles(){const t=this.videoElement.textTracks;for(let e=0;e<t.length;e++)t[e].mode="hidden"}toggleSubtitles(){const t=this.videoElement.textTracks;return Array.from(t).some(i=>i.mode==="showing")?(this.disableSubtitles(),!1):t.length>0?(this.enableSubtitles(0),!0):!1}getSubtitleTracks(){return Array.from(this.videoElement.textTracks)}areSubtitlesEnabled(){const t=this.videoElement.textTracks;return Array.from(t).some(e=>e.mode==="showing")}on(t,e){this.eventListeners.has(t)||this.eventListeners.set(t,new Set),this.eventListeners.get(t).add(e)}off(t,e){var i;(i=this.eventListeners.get(t))==null||i.delete(e)}emit(t,e){var n;const i={type:t,data:e,timestamp:Date.now()};(n=this.eventListeners.get(t))==null||n.forEach(o=>{o(i)})}destroy(){this.hls&&(this.hls.destroy(),this.hls=null),this.uiController.destroy(),this.videoElement.remove(),this.eventListeners.clear(),this.analytics.destroy()}}class J{constructor(t){s(this,"file");s(this,"videoElement",null);s(this,"info",null);if(!this.isVideoFile(t))throw new Error(`Invalid file type: ${t.type}. Expected a video file.`);this.file=t}isVideoFile(t){if(t.type.startsWith("video/"))return!0;const e=[".mp4",".webm",".ogg",".mov",".avi",".mkv",".flv",".wmv",".m4v",".3gp",".ts",".m3u8"],i=t.name.toLowerCase();return e.some(n=>i.endsWith(n))}async extract(){return new Promise((t,e)=>{try{this.videoElement=document.createElement("video"),this.videoElement.preload="metadata",this.videoElement.muted=!0;const i=URL.createObjectURL(this.file);this.videoElement.onloadedmetadata=()=>{try{if(!this.videoElement){e(new Error("Video element not initialized"));return}const n=this.videoElement.videoWidth,o=this.videoElement.videoHeight,r=this.videoElement.duration,a=this.calculateAspectRatio(n,o),c=this.file.size,u=this.formatBytes(c),p=this.formatDuration(r),v=this.getFileExtension(this.file.name),f=r>0?Math.round(c*8/r/1e3):void 0;this.info={width:n,height:o,aspectRatio:a,size:c,sizeInBytes:c,sizeFormatted:u,duration:r,durationInSeconds:r,durationFormatted:p,mimeType:this.file.type||"video/unknown",fileName:this.file.name,fileExtension:v,bitrate:f},URL.revokeObjectURL(i),this.videoElement.remove(),t(this.info)}catch(n){URL.revokeObjectURL(i),e(n)}},this.videoElement.onerror=()=>{URL.revokeObjectURL(i),e(new Error(`Failed to load video file: ${this.file.name}`))},this.videoElement.src=i}catch(i){e(i)}})}calculateAspectRatio(t,e){const i=this.getGCD(t,e),n=t/i,o=e/i,r=n/o;return Math.abs(r-16/9)<.01?"16:9":Math.abs(r-4/3)<.01?"4:3":Math.abs(r-21/9)<.01?"21:9":Math.abs(r-1)<.01?"1:1":`${n}:${o}`}getGCD(t,e){return e===0?t:this.getGCD(e,t%e)}formatBytes(t){if(t===0)return"0 Bytes";const e=1024,i=["Bytes","KB","MB","GB","TB"],n=Math.floor(Math.log(t)/Math.log(e));return`${parseFloat((t/Math.pow(e,n)).toFixed(2))} ${i[n]}`}formatDuration(t){if(!isFinite(t)||t<0)return"00:00";const e=Math.floor(t/3600),i=Math.floor(t%3600/60),n=Math.floor(t%60);return e>0?`${e.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`:`${i.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`}getFileExtension(t){const e=t.split(".");return e.length>1?`.${e[e.length-1].toLowerCase()}`:""}get width(){var t;return((t=this.info)==null?void 0:t.width)||0}get height(){var t;return((t=this.info)==null?void 0:t.height)||0}get aspectRatio(){var t;return((t=this.info)==null?void 0:t.aspectRatio)||"unknown"}get size(){var t;return((t=this.info)==null?void 0:t.size)||0}get sizeInBytes(){var t;return((t=this.info)==null?void 0:t.sizeInBytes)||0}get sizeFormatted(){var t;return((t=this.info)==null?void 0:t.sizeFormatted)||"0 Bytes"}get duration(){var t;return((t=this.info)==null?void 0:t.duration)||0}get durationInSeconds(){var t;return((t=this.info)==null?void 0:t.durationInSeconds)||0}get durationFormatted(){var t;return((t=this.info)==null?void 0:t.durationFormatted)||"00:00"}get mimeType(){var t;return((t=this.info)==null?void 0:t.mimeType)||this.file.type||"video/unknown"}get fileName(){return this.file.name}get fileExtension(){var t;return((t=this.info)==null?void 0:t.fileExtension)||""}get bitrate(){var t;return(t=this.info)==null?void 0:t.bitrate}get quality(){if(!this.info)return"unknown";const t=this.info.height;return t>=2160?"4K (2160p)":t>=1440?"2K (1440p)":t>=1080?"Full HD (1080p)":t>=720?"HD (720p)":t>=480?"SD (480p)":t>=360?"360p":"Low Quality"}getInfo(){return this.info}destroy(){this.videoElement&&(this.videoElement.remove(),this.videoElement=null),this.info=null}}const Z=l=>{const{src:t,autoplay:e,muted:i,controls:n=!0,poster:o,preload:r,theme:a,s3Config:c,analytics:u,hlsConfig:p,subtitles:v,stickyControls:f,onReady:S,onPlay:x,onPause:C,onEnded:L,onTimeUpdate:T,onVolumeChange:I,onError:P,onLoadedMetadata:R,onQualityChange:M,style:U,className:H,width:y="100%",height:w="500px"}=l,b=h.useRef(null),q=h.useRef(null);return h.useEffect(()=>{if(!b.current)return;const W={src:t,container:b.current,autoplay:e,muted:i,controls:n,poster:o,preload:r,theme:a,s3Config:c,analytics:u,hlsConfig:p,subtitles:v,stickyControls:f},d=new E(W);return q.current=d,x&&d.on("play",x),C&&d.on("pause",C),L&&d.on("ended",L),P&&d.on("error",m=>{var A;return P((A=m.data)==null?void 0:A.error)}),R&&d.on("loadedmetadata",R),M&&d.on("qualitychange",m=>M(m.data.level)),T&&d.on("timeupdate",m=>T(m.data.currentTime)),I&&d.on("volumechange",m=>I(m.data.volume,m.data.muted)),S&&S(d),()=>{d.destroy(),q.current=null}},[t]),B.jsx("div",{ref:b,className:H,style:{width:typeof y=="number"?`${y}px`:y,height:typeof w=="number"?`${w}px`:w,...U}})},tt=l=>{const[t,e]=h.useState(null),[i,n]=h.useState(null),o=h.useRef(null);return h.useEffect(()=>{if(!o.current)return;const r=new E({...l,container:o.current});e(r);const a=()=>{n(r.getState())};return r.on("play",a),r.on("pause",a),r.on("timeupdate",a),r.on("volumechange",a),r.on("loadedmetadata",a),()=>{r.destroy()}},[l.src]),{containerRef:o,player:t,state:i}},D=F.createContext({player:null,state:null}),et=l=>{const{player:t,children:e}=l,[i,n]=h.useState(t.getState());return h.useEffect(()=>{const o=()=>{n(t.getState())};return t.on("play",o),t.on("pause",o),t.on("timeupdate",o),t.on("volumechange",o),t.on("loadedmetadata",o),()=>{}},[t]),B.jsx(D.Provider,{value:{player:t,state:i},children:e})},it=()=>{const l=F.useContext(D);if(!l.player)throw new Error("useWontumPlayerContext must be used within WontumPlayerProvider");return l};exports.Analytics=O;exports.S3Handler=z;exports.UIController=$;exports.WontumFileInfo=J;exports.WontumPlayer=E;exports.WontumPlayerProvider=et;exports.WontumPlayerReact=Z;exports.useWontumPlayer=tt;exports.useWontumPlayerContext=it;
598
+ </svg>`}getSettingsIcon(){return'<svg viewBox="0 0 24 24"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>'}destroy(){this.hideControlsTimeout&&clearTimeout(this.hideControlsTimeout),this.controlsContainer.remove()}}class D{constructor(t){r(this,"config");r(this,"urlCache",new Map);r(this,"signedUrls",new Set);this.config=t}async processUrl(t){return this.isCloudFrontUrl(t)?this.signCloudFrontUrl(t):this.isS3Url(t)?this.getPresignedUrl(t):t}isCloudFrontUrl(t){var e;if(!((e=this.config)!=null&&e.cloudFrontDomains)||this.config.cloudFrontDomains.length===0)return!1;try{const i=new URL(t);return this.config.cloudFrontDomains.some(n=>i.hostname.includes(n))}catch{return!1}}isS3Url(t){return t.includes(".s3.")||t.includes("s3.amazonaws.com")||t.startsWith("s3://")}async signCloudFrontUrl(t,e=0){var o,s;if(this.signedUrls.has(t))return t;if((o=this.config)!=null&&o.signUrl)try{const a=await this.config.signUrl(t);return this.signedUrls.add(t),a}catch(a){const c=(a==null?void 0:a.name)==="AbortError"||((s=a==null?void 0:a.message)==null?void 0:s.includes("aborted"));if(c&&e<2)return console.warn(`Sign URL aborted, retrying (${e+1}/2)...`),await new Promise(u=>setTimeout(u,300)),this.signCloudFrontUrl(t,e+1);throw console.error("Failed to sign CloudFront URL:",a),c?new Error("Failed to sign CloudFront URL: Request was aborted. If using Apollo Client or other GraphQL clients, consider moving the query outside component lifecycle or using useQuery with skip option."):new Error(`Failed to sign CloudFront URL: ${(a==null?void 0:a.message)||"Unknown error"}`)}return console.warn("No signUrl function provided. CloudFront cookies may not be set."),t}extractS3Key(t){if(t.startsWith("s3://"))return t.replace("s3://","").split("/").slice(1).join("/");const e=t.match(/s3[.-]([^.]+)\.amazonaws\.com\/(.+)/);if(e)return e[2];const i=t.match(/([^.]+)\.s3\.amazonaws\.com\/(.+)/);return i?i[2]:t}async getPresignedUrl(t){var n;const e=this.extractS3Key(t),i=this.urlCache.get(e);if(i&&i.expiresAt>Date.now())return i.url;if((n=this.config)!=null&&n.getPresignedUrl)try{const o=await this.config.getPresignedUrl(e);return this.urlCache.set(e,{url:o,expiresAt:Date.now()+50*60*1e3}),o}catch(o){throw console.error("Failed to generate presigned URL:",o),new Error("Failed to generate presigned URL for S3 object")}return console.warn("No getPresignedUrl function provided. Using direct S3 URL (requires public bucket)"),t}static constructS3Url(t,e,i="us-east-1"){return`https://${t}.s3.${i}.amazonaws.com/${e}`}static parseS3Uri(t){if(!t.startsWith("s3://"))return null;const e=t.replace("s3://","").split("/"),i=e[0],n=e.slice(1).join("/");return{bucket:i,key:n}}clearCache(){this.urlCache.clear(),this.signedUrls.clear()}}class x{constructor(t){r(this,"container");r(this,"videoElement");r(this,"hls",null);r(this,"config");r(this,"eventListeners",new Map);r(this,"analytics");r(this,"s3Handler");r(this,"uiController");r(this,"qualities",[]);r(this,"state",{playing:!1,paused:!0,ended:!1,buffering:!1,currentTime:0,duration:0,volume:1,muted:!1,playbackRate:1,quality:"auto",availableQualities:[],fullscreen:!1});if(this.config=t,this.container=typeof t.container=="string"?document.querySelector(t.container):t.container,!this.container)throw new Error("Container element not found");this.analytics=new O(t.analytics),this.s3Handler=new D(t.s3Config),this.videoElement=this.createVideoElement(),this.container.appendChild(this.videoElement),this.uiController=new z(this.container,this),this.setupVideoListeners(),this.loadSource(t.src),t.autoplay&&(this.videoElement.autoplay=!0),t.muted&&this.mute(),t.poster&&(this.videoElement.poster=t.poster),t.preload&&(this.videoElement.preload=t.preload),t.subtitles&&this.addSubtitleTracks(t.subtitles)}addSubtitleTracks(t){t.forEach(e=>{const i=document.createElement("track");i.kind="subtitles",i.label=e.label,i.src=e.src,i.srclang=e.srclang,e.default&&(i.default=!0),this.videoElement.appendChild(i)})}createVideoElement(){const t=document.createElement("video");return t.className="wontum-player-video",t.style.width="100%",t.style.height="100%",t.playsInline=!0,t.crossOrigin="use-credentials",t}setupVideoListeners(){this.videoElement.addEventListener("play",()=>{this.state.playing=!0,this.state.paused=!1,this.emit("play"),this.analytics.trackEvent("play",this.getAnalyticsData())}),this.videoElement.addEventListener("pause",()=>{this.state.playing=!1,this.state.paused=!0,this.emit("pause"),this.analytics.trackEvent("pause",this.getAnalyticsData())}),this.videoElement.addEventListener("ended",()=>{this.state.ended=!0,this.state.playing=!1,this.emit("ended"),this.analytics.trackEvent("ended",this.getAnalyticsData())}),this.videoElement.addEventListener("timeupdate",()=>{this.state.currentTime=this.videoElement.currentTime,this.emit("timeupdate",{currentTime:this.state.currentTime})}),this.videoElement.addEventListener("loadedmetadata",()=>{this.state.duration=this.videoElement.duration,this.emit("loadedmetadata",{duration:this.state.duration}),this.analytics.trackEvent("loadedmetadata",this.getAnalyticsData())}),this.videoElement.addEventListener("volumechange",()=>{this.state.volume=this.videoElement.volume,this.state.muted=this.videoElement.muted,this.emit("volumechange",{volume:this.state.volume,muted:this.state.muted})}),this.videoElement.addEventListener("ratechange",()=>{this.state.playbackRate=this.videoElement.playbackRate,this.emit("ratechange",{playbackRate:this.state.playbackRate})}),this.videoElement.addEventListener("waiting",()=>{this.state.buffering=!0,this.emit("waiting"),this.analytics.trackEvent("buffering_start",this.getAnalyticsData())}),this.videoElement.addEventListener("canplay",()=>{this.state.buffering=!1,this.emit("canplay"),this.analytics.trackEvent("buffering_end",this.getAnalyticsData())}),this.videoElement.addEventListener("seeking",()=>{this.emit("seeking")}),this.videoElement.addEventListener("seeked",()=>{this.emit("seeked",{currentTime:this.state.currentTime}),this.analytics.trackEvent("seeked",this.getAnalyticsData())}),this.videoElement.addEventListener("error",t=>{const e=this.videoElement.error;this.emit("error",{error:e}),this.analytics.trackEvent("error",{...this.getAnalyticsData(),error:e==null?void 0:e.message})}),this.videoElement.addEventListener("loadstart",()=>{this.emit("loadstart")}),this.videoElement.addEventListener("loadeddata",()=>{this.emit("loadeddata")}),this.videoElement.addEventListener("canplaythrough",()=>{this.emit("canplaythrough")}),this.videoElement.addEventListener("playing",()=>{this.state.playing=!0,this.state.buffering=!1,this.emit("playing")}),this.videoElement.addEventListener("durationchange",()=>{this.state.duration=this.videoElement.duration,this.emit("durationchange",{duration:this.state.duration})}),this.videoElement.addEventListener("progress",()=>{this.emit("progress",{buffered:this.videoElement.buffered})}),this.videoElement.addEventListener("stalled",()=>{this.emit("stalled")}),this.videoElement.addEventListener("suspend",()=>{this.emit("suspend")}),this.videoElement.addEventListener("abort",()=>{this.emit("abort")}),this.videoElement.addEventListener("emptied",()=>{this.emit("emptied")}),this.videoElement.addEventListener("resize",()=>{this.emit("resize",{videoWidth:this.videoElement.videoWidth,videoHeight:this.videoElement.videoHeight})})}async loadSource(t){var e;try{const i=await this.s3Handler.processUrl(t);if(g.isSupported()){const n=((e=this.config.s3Config)==null?void 0:e.withCredentials)??!1,o={...this.config.hlsConfig,xhrSetup:(s,a)=>{var c;n&&(s.withCredentials=!0),(c=this.config.hlsConfig)!=null&&c.xhrSetup&&this.config.hlsConfig.xhrSetup(s,a)}};this.hls=new g(o),this.hls.loadSource(i),this.hls.attachMedia(this.videoElement),this.hls.on(g.Events.MANIFEST_PARSED,(s,a)=>{const c=this.extractQualities(a.levels);this.qualities=c}),this.hls.on(g.Events.LEVEL_SWITCHED,(s,a)=>{var u;const c=(u=this.hls)==null?void 0:u.levels[a.level];c&&(this.state.quality=`${c.height}p`,this.emit("qualitychange",{quality:this.state.quality}))}),this.hls.on(g.Events.ERROR,(s,a)=>{a.fatal&&this.handleHlsError(a)})}else if(this.videoElement.canPlayType("application/vnd.apple.mpegurl"))this.videoElement.src=i;else throw new Error("HLS is not supported in this browser")}catch(i){console.error("Failed to load video source:",i),this.emit("error",{error:i})}}extractQualities(t){return t.map(e=>({height:e.height,width:e.width,bitrate:e.bitrate,name:`${e.height}p`}))}handleHlsError(t){var e,i;switch(t.type){case g.ErrorTypes.NETWORK_ERROR:console.error("Network error occurred"),(e=this.hls)==null||e.startLoad();break;case g.ErrorTypes.MEDIA_ERROR:console.error("Media error occurred"),(i=this.hls)==null||i.recoverMediaError();break;default:console.error("Fatal error occurred:",t),this.destroy();break}}getAnalyticsData(){return{currentTime:this.state.currentTime,duration:this.state.duration,quality:this.state.quality,playbackRate:this.state.playbackRate,volume:this.state.volume,muted:this.state.muted}}play(){return this.videoElement.play()}pause(){this.videoElement.pause()}seek(t){this.videoElement.currentTime=t}skipForward(t=10){const e=Math.min(this.state.currentTime+t,this.state.duration);this.seek(e)}skipBackward(t=10){const e=Math.max(this.state.currentTime-t,0);this.seek(e)}setVolume(t){this.videoElement.volume=Math.max(0,Math.min(1,t))}mute(){this.videoElement.muted=!0}unmute(){this.videoElement.muted=!1}setPlaybackRate(t){this.videoElement.playbackRate=t}setQuality(t){this.hls&&(this.hls.currentLevel=t)}getQualities(){return this.qualities}enterFullscreen(){this.container.requestFullscreen&&(this.container.requestFullscreen(),this.state.fullscreen=!0,this.emit("fullscreenchange",{fullscreen:!0}))}exitFullscreen(){document.exitFullscreen&&(document.exitFullscreen(),this.state.fullscreen=!1,this.emit("fullscreenchange",{fullscreen:!1}))}async enterPictureInPicture(){if(document.pictureInPictureEnabled&&!this.videoElement.disablePictureInPicture)try{await this.videoElement.requestPictureInPicture(),this.emit("pictureinpictureenter",{})}catch(t){throw console.error("Failed to enter Picture-in-Picture:",t),t}}async exitPictureInPicture(){if(document.pictureInPictureElement)try{await document.exitPictureInPicture(),this.emit("pictureinpictureexit",{})}catch(t){throw console.error("Failed to exit Picture-in-Picture:",t),t}}async togglePictureInPicture(){document.pictureInPictureElement?await this.exitPictureInPicture():await this.enterPictureInPicture()}getState(){return{...this.state}}getVideoElement(){return this.videoElement}enableSubtitles(t){const e=this.videoElement.textTracks;for(let i=0;i<e.length;i++)e[i].mode=i===t?"showing":"hidden"}disableSubtitles(){const t=this.videoElement.textTracks;for(let e=0;e<t.length;e++)t[e].mode="hidden"}toggleSubtitles(){const t=this.videoElement.textTracks;return Array.from(t).some(i=>i.mode==="showing")?(this.disableSubtitles(),!1):t.length>0?(this.enableSubtitles(0),!0):!1}getSubtitleTracks(){return Array.from(this.videoElement.textTracks)}areSubtitlesEnabled(){const t=this.videoElement.textTracks;return Array.from(t).some(e=>e.mode==="showing")}on(t,e){this.eventListeners.has(t)||this.eventListeners.set(t,new Set),this.eventListeners.get(t).add(e)}off(t,e){var i;(i=this.eventListeners.get(t))==null||i.delete(e)}emit(t,e){var n;const i={type:t,data:e,timestamp:Date.now()};(n=this.eventListeners.get(t))==null||n.forEach(o=>{o(i)})}destroy(){this.hls&&(this.hls.destroy(),this.hls=null),this.uiController.destroy(),this.videoElement.remove(),this.eventListeners.clear(),this.analytics.destroy()}}class J{constructor(t){r(this,"file");r(this,"videoElement",null);r(this,"audioContext",null);r(this,"info",null);if(!this.isVideoFile(t))throw new Error(`Invalid file type: ${t.type}. Expected a video file.`);this.file=t}isVideoFile(t){if(t.type.startsWith("video/"))return!0;const e=[".mp4",".webm",".ogg",".mov",".avi",".mkv",".flv",".wmv",".m4v",".3gp",".ts",".m3u8"],i=t.name.toLowerCase();return e.some(n=>i.endsWith(n))}async extract(){return new Promise((t,e)=>{try{this.videoElement=document.createElement("video"),this.videoElement.preload="metadata",this.videoElement.muted=!0;const i=URL.createObjectURL(this.file);this.videoElement.onloadedmetadata=async()=>{try{if(!this.videoElement){e(new Error("Video element not initialized"));return}const n=this.videoElement.videoWidth,o=this.videoElement.videoHeight,s=this.videoElement.duration,a=this.calculateAspectRatio(n,o),c=this.file.size,u=this.formatBytes(c),p=this.formatDuration(s),y=this.getFileExtension(this.file.name),w=s>0?Math.round(c*8/s/1e3):void 0,f=await this.detectFrameRate(),v=await this.detectAudioInfo(i);this.info={width:n,height:o,aspectRatio:a,size:c,sizeInBytes:c,sizeFormatted:u,duration:s,durationInSeconds:s,durationFormatted:p,mimeType:this.file.type||"video/unknown",fileName:this.file.name,fileExtension:y,bitrate:w,frameRate:f,audioChannels:v.channels,hasAudio:v.hasAudio},URL.revokeObjectURL(i),this.videoElement.remove(),t(this.info)}catch(n){URL.revokeObjectURL(i),e(n)}},this.videoElement.onerror=()=>{URL.revokeObjectURL(i),e(new Error(`Failed to load video file: ${this.file.name}`))},this.videoElement.src=i}catch(i){e(i)}})}calculateAspectRatio(t,e){const i=this.getGCD(t,e),n=t/i,o=e/i,s=n/o;return Math.abs(s-16/9)<.01?"16:9":Math.abs(s-4/3)<.01?"4:3":Math.abs(s-21/9)<.01?"21:9":Math.abs(s-1)<.01?"1:1":`${n}:${o}`}async detectFrameRate(){if(this.videoElement)try{return"requestVideoFrameCallback"in this.videoElement?new Promise(t=>{let e=0,i=0;const n=10,o=(s,a)=>{if(!this.videoElement){t(void 0);return}if(e++,e===1)i=s,this.videoElement.requestVideoFrameCallback(o);else if(e<n)this.videoElement.requestVideoFrameCallback(o);else{const c=(s-i)/1e3,u=Math.round((e-1)/c);t(u)}};this.videoElement?(this.videoElement.currentTime=1,this.videoElement.play().catch(()=>t(void 0)),this.videoElement.requestVideoFrameCallback(o)):t(void 0)}):void 0}catch{return}}async detectAudioInfo(t){var e;if(!this.videoElement)return{hasAudio:!1};try{if(!(this.videoElement.mozHasAudio||(this.videoElement.webkitAudioDecodedByteCount??0)>0||(((e=this.videoElement.audioTracks)==null?void 0:e.length)??0)>0))return{hasAudio:!1};try{const n=window.AudioContext||window.webkitAudioContext;if(!n)return{hasAudio:!0};this.audioContext=new n;const o=this.audioContext.createMediaElementSource(this.videoElement),s=this.audioContext.createAnalyser();return o.connect(s),s.connect(this.audioContext.destination),{hasAudio:!0,channels:o.channelCount}}catch{return{hasAudio:!0}}}catch{return{hasAudio:!1}}}getGCD(t,e){return e===0?t:this.getGCD(e,t%e)}formatBytes(t){if(t===0)return"0 Bytes";const e=1024,i=["Bytes","KB","MB","GB","TB"],n=Math.floor(Math.log(t)/Math.log(e));return`${parseFloat((t/Math.pow(e,n)).toFixed(2))} ${i[n]}`}formatDuration(t){if(!isFinite(t)||t<0)return"00:00";const e=Math.floor(t/3600),i=Math.floor(t%3600/60),n=Math.floor(t%60);return e>0?`${e.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`:`${i.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`}getFileExtension(t){const e=t.split(".");return e.length>1?`.${e[e.length-1].toLowerCase()}`:""}get width(){var t;return((t=this.info)==null?void 0:t.width)||0}get height(){var t;return((t=this.info)==null?void 0:t.height)||0}get aspectRatio(){var t;return((t=this.info)==null?void 0:t.aspectRatio)||"unknown"}get size(){var t;return((t=this.info)==null?void 0:t.size)||0}get sizeInBytes(){var t;return((t=this.info)==null?void 0:t.sizeInBytes)||0}get sizeFormatted(){var t;return((t=this.info)==null?void 0:t.sizeFormatted)||"0 Bytes"}get duration(){var t;return((t=this.info)==null?void 0:t.duration)||0}get durationInSeconds(){var t;return((t=this.info)==null?void 0:t.durationInSeconds)||0}get durationFormatted(){var t;return((t=this.info)==null?void 0:t.durationFormatted)||"00:00"}get mimeType(){var t;return((t=this.info)==null?void 0:t.mimeType)||this.file.type||"video/unknown"}get fileName(){return this.file.name}get fileExtension(){var t;return((t=this.info)==null?void 0:t.fileExtension)||""}get bitrate(){var t;return(t=this.info)==null?void 0:t.bitrate}get frameRate(){var t;return(t=this.info)==null?void 0:t.frameRate}get audioChannels(){var t;return(t=this.info)==null?void 0:t.audioChannels}get hasAudio(){var t;return((t=this.info)==null?void 0:t.hasAudio)||!1}get quality(){if(!this.info)return"unknown";const t=this.info.height;return t>=2160?"4K (2160p)":t>=1440?"2K (1440p)":t>=1080?"Full HD (1080p)":t>=720?"HD (720p)":t>=480?"SD (480p)":t>=360?"360p":"Low Quality"}getInfo(){return this.info}destroy(){this.videoElement&&(this.videoElement.pause(),this.videoElement.remove(),this.videoElement=null),this.audioContext&&(this.audioContext.close().catch(()=>{}),this.audioContext=null),this.info=null}}const Z=l=>{const{src:t,autoplay:e,muted:i,controls:n=!0,poster:o,preload:s,theme:a,s3Config:c,analytics:u,hlsConfig:p,subtitles:y,stickyControls:w,onReady:f,onPlay:v,onPause:C,onEnded:L,onTimeUpdate:T,onVolumeChange:I,onError:A,onLoadedMetadata:R,onQualityChange:P,style:U,className:V,width:b="100%",height:k="500px"}=l,E=h.useRef(null),M=h.useRef(null);return h.useEffect(()=>{if(!E.current)return;const H={src:t,container:E.current,autoplay:e,muted:i,controls:n,poster:o,preload:s,theme:a,s3Config:c,analytics:u,hlsConfig:p,subtitles:y,stickyControls:w},d=new x(H);return M.current=d,v&&d.on("play",v),C&&d.on("pause",C),L&&d.on("ended",L),A&&d.on("error",m=>{var q;return A((q=m.data)==null?void 0:q.error)}),R&&d.on("loadedmetadata",R),P&&d.on("qualitychange",m=>P(m.data.level)),T&&d.on("timeupdate",m=>T(m.data.currentTime)),I&&d.on("volumechange",m=>I(m.data.volume,m.data.muted)),f&&f(d),()=>{d.destroy(),M.current=null}},[t]),F.jsx("div",{ref:E,className:V,style:{width:typeof b=="number"?`${b}px`:b,height:typeof k=="number"?`${k}px`:k,...U}})},tt=l=>{const[t,e]=h.useState(null),[i,n]=h.useState(null),o=h.useRef(null);return h.useEffect(()=>{if(!o.current)return;const s=new x({...l,container:o.current});e(s);const a=()=>{n(s.getState())};return s.on("play",a),s.on("pause",a),s.on("timeupdate",a),s.on("volumechange",a),s.on("loadedmetadata",a),()=>{s.destroy()}},[l.src]),{containerRef:o,player:t,state:i}},$=B.createContext({player:null,state:null}),et=l=>{const{player:t,children:e}=l,[i,n]=h.useState(t.getState());return h.useEffect(()=>{const o=()=>{n(t.getState())};return t.on("play",o),t.on("pause",o),t.on("timeupdate",o),t.on("volumechange",o),t.on("loadedmetadata",o),()=>{}},[t]),F.jsx($.Provider,{value:{player:t,state:i},children:e})},it=()=>{const l=B.useContext($);if(!l.player)throw new Error("useWontumPlayerContext must be used within WontumPlayerProvider");return l};exports.Analytics=O;exports.S3Handler=D;exports.UIController=z;exports.WontumFileInfo=J;exports.WontumPlayer=x;exports.WontumPlayerProvider=et;exports.WontumPlayerReact=Z;exports.useWontumPlayer=tt;exports.useWontumPlayerContext=it;
@@ -1,26 +1,26 @@
1
- var H = Object.defineProperty;
2
- var V = (c, t, e) => t in c ? H(c, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : c[t] = e;
3
- var s = (c, t, e) => V(c, typeof t != "symbol" ? t + "" : t, e);
1
+ var V = Object.defineProperty;
2
+ var H = (c, t, e) => t in c ? V(c, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : c[t] = e;
3
+ var r = (c, t, e) => H(c, typeof t != "symbol" ? t + "" : t, e);
4
4
  import p from "hls.js";
5
- import { jsx as A } from "react/jsx-runtime";
6
- import * as F from "react";
7
- import { useRef as b, useEffect as E, useState as k } from "react";
5
+ import { jsx as P } from "react/jsx-runtime";
6
+ import * as B from "react";
7
+ import { useRef as E, useEffect as x, useState as S } from "react";
8
8
  class N {
9
9
  constructor(t) {
10
- s(this, "config");
11
- s(this, "sessionId");
12
- s(this, "events", []);
13
- s(this, "sessionStartTime");
14
- s(this, "playbackStartTime", null);
15
- s(this, "totalPlayTime", 0);
16
- s(this, "totalBufferTime", 0);
17
- s(this, "bufferStartTime", null);
18
- s(this, "rebufferCount", 0);
19
- s(this, "seekCount", 0);
20
- s(this, "webSocket", null);
21
- s(this, "socketIO", null);
22
- s(this, "wsReconnectTimeout", null);
23
- s(this, "isDestroyed", !1);
10
+ r(this, "config");
11
+ r(this, "sessionId");
12
+ r(this, "events", []);
13
+ r(this, "sessionStartTime");
14
+ r(this, "playbackStartTime", null);
15
+ r(this, "totalPlayTime", 0);
16
+ r(this, "totalBufferTime", 0);
17
+ r(this, "bufferStartTime", null);
18
+ r(this, "rebufferCount", 0);
19
+ r(this, "seekCount", 0);
20
+ r(this, "webSocket", null);
21
+ r(this, "socketIO", null);
22
+ r(this, "wsReconnectTimeout", null);
23
+ r(this, "isDestroyed", !1);
24
24
  var e, i;
25
25
  if (this.config = t, this.sessionId = (t == null ? void 0 : t.sessionId) || this.generateSessionId(), this.sessionStartTime = Date.now(), (e = this.config) != null && e.webSocket) {
26
26
  const n = this.config.webSocket;
@@ -195,32 +195,32 @@ class N {
195
195
  }
196
196
  class W {
197
197
  constructor(t, e) {
198
- s(this, "container");
199
- s(this, "player");
200
- s(this, "controlsContainer");
201
- s(this, "progressContainer");
202
- s(this, "progressBar");
203
- s(this, "playButton");
204
- s(this, "skipBackwardButton");
205
- s(this, "skipForwardButton");
206
- s(this, "volumeButton");
207
- s(this, "volumeContainer");
208
- s(this, "fullscreenButton");
209
- s(this, "pipButton");
210
- s(this, "settingsButton");
198
+ r(this, "container");
199
+ r(this, "player");
200
+ r(this, "controlsContainer");
201
+ r(this, "progressContainer");
202
+ r(this, "progressBar");
203
+ r(this, "playButton");
204
+ r(this, "skipBackwardButton");
205
+ r(this, "skipForwardButton");
206
+ r(this, "volumeButton");
207
+ r(this, "volumeContainer");
208
+ r(this, "fullscreenButton");
209
+ r(this, "pipButton");
210
+ r(this, "settingsButton");
211
211
  // private timeDisplay: HTMLElement
212
- s(this, "volumeSlider");
213
- s(this, "progressInput");
212
+ r(this, "volumeSlider");
213
+ r(this, "progressInput");
214
214
  // private controlsVisible = true
215
- s(this, "hideControlsTimeout", null);
216
- s(this, "stickyControls", !1);
217
- s(this, "isVolumeSliderActive", !1);
215
+ r(this, "hideControlsTimeout", null);
216
+ r(this, "stickyControls", !1);
217
+ r(this, "isVolumeSliderActive", !1);
218
218
  this.container = t, this.player = e, this.injectStyles(), this.createProgressBar(), this.controlsContainer = this.createControls(), this.container.appendChild(this.controlsContainer), this.playButton = this.controlsContainer.querySelector(".wontum-play-btn"), this.skipBackwardButton = this.controlsContainer.querySelector(".wontum-skip-backward-btn"), this.skipForwardButton = this.controlsContainer.querySelector(".wontum-skip-forward-btn"), this.volumeButton = this.controlsContainer.querySelector(".wontum-volume-btn"), this.volumeContainer = this.controlsContainer.querySelector(".wontum-volume-container"), this.fullscreenButton = this.controlsContainer.querySelector(".wontum-fullscreen-btn"), this.pipButton = this.controlsContainer.querySelector(".wontum-pip-btn"), this.settingsButton = this.controlsContainer.querySelector(".wontum-settings-btn"), this.volumeSlider = this.controlsContainer.querySelector(".wontum-volume-slider"), this.progressInput = this.container.querySelector(".wontum-progress-input"), this.progressBar = this.container.querySelector(".wontum-progress-filled"), this.stickyControls = this.player.config.stickyControls || !1, this.stickyControls && this.controlsContainer.classList.add("sticky"), this.setupEventListeners(), this.setupPlayerEventListeners();
219
219
  }
220
220
  injectStyles() {
221
221
  const t = "wontum-player-styles";
222
222
  if (document.getElementById(t)) return;
223
- const e = this.player.config.theme || {}, i = e.primaryColor || "#3b82f6", n = e.accentColor || "#2563eb", o = e.fontFamily || "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif", r = e.controlsBackground || "linear-gradient(to top, rgba(0,0,0,0.8), transparent)", a = e.buttonHoverBg || "rgba(255, 255, 255, 0.1)", l = e.progressHeight || "6px", u = e.borderRadius || "4px", m = document.createElement("style");
223
+ const e = this.player.config.theme || {}, i = e.primaryColor || "#3b82f6", n = e.accentColor || "#2563eb", o = e.fontFamily || "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif", s = e.controlsBackground || "linear-gradient(to top, rgba(0,0,0,0.8), transparent)", a = e.buttonHoverBg || "rgba(255, 255, 255, 0.1)", l = e.progressHeight || "6px", u = e.borderRadius || "4px", m = document.createElement("style");
224
224
  m.id = t, m.textContent = `
225
225
  .wontum-player-container {
226
226
  position: relative;
@@ -229,7 +229,7 @@ class W {
229
229
  overflow: hidden;
230
230
  --primary-color: ${i};
231
231
  --accent-color: ${n};
232
- --controls-bg: ${r};
232
+ --controls-bg: ${s};
233
233
  --button-hover: ${a};
234
234
  --progress-height: ${l};
235
235
  --border-radius: ${u};
@@ -803,7 +803,7 @@ class W {
803
803
  }), this.skipForwardButton.addEventListener("click", () => {
804
804
  this.player.skipForward(10);
805
805
  }), this.progressInput.addEventListener("input", (i) => {
806
- const n = i.target, o = parseFloat(n.value), r = this.player.getState(), a = o / 100 * r.duration;
806
+ const n = i.target, o = parseFloat(n.value), s = this.player.getState(), a = o / 100 * s.duration;
807
807
  this.player.seek(a);
808
808
  }), this.volumeSlider.addEventListener("input", (i) => {
809
809
  const n = i.target, o = parseFloat(n.value) / 100;
@@ -835,9 +835,9 @@ class W {
835
835
  const t = this.controlsContainer.querySelectorAll(".wontum-tab");
836
836
  t.forEach((i) => {
837
837
  i.addEventListener("click", (n) => {
838
- const o = n.currentTarget, r = o.getAttribute("data-tab");
838
+ const o = n.currentTarget, s = o.getAttribute("data-tab");
839
839
  t.forEach((u) => u.classList.remove("active")), o.classList.add("active"), this.controlsContainer.querySelectorAll(".wontum-tab-panel").forEach((u) => u.classList.remove("active"));
840
- const l = this.controlsContainer.querySelector(`[data-panel="${r}"]`);
840
+ const l = this.controlsContainer.querySelector(`[data-panel="${s}"]`);
841
841
  l == null || l.classList.add("active");
842
842
  });
843
843
  }), this.player.getVideoElement().addEventListener("click", () => {
@@ -893,8 +893,8 @@ class W {
893
893
  ).join("")}
894
894
  `, t.querySelectorAll(".wontum-subtitle-option").forEach((n) => {
895
895
  n.addEventListener("click", (o) => {
896
- const r = o.target, a = parseInt(r.dataset.track || "-1");
897
- a === -1 ? this.player.disableSubtitles() : this.player.enableSubtitles(a), t.querySelectorAll(".wontum-subtitle-option").forEach((l) => l.classList.remove("active")), r.classList.add("active");
896
+ const s = o.target, a = parseInt(s.dataset.track || "-1");
897
+ a === -1 ? this.player.disableSubtitles() : this.player.enableSubtitles(a), t.querySelectorAll(".wontum-subtitle-option").forEach((l) => l.classList.remove("active")), s.classList.add("active");
898
898
  });
899
899
  });
900
900
  }
@@ -907,8 +907,8 @@ class W {
907
907
  </div>
908
908
  `
909
909
  ).join(""), t.querySelectorAll(".wontum-speed-option").forEach((o) => {
910
- o.addEventListener("click", (r) => {
911
- const a = r.target, l = parseFloat(a.dataset.speed || "1");
910
+ o.addEventListener("click", (s) => {
911
+ const a = s.target, l = parseFloat(a.dataset.speed || "1");
912
912
  this.player.setPlaybackRate(l), t.querySelectorAll(".wontum-speed-option").forEach((u) => u.classList.remove("active")), a.classList.add("active");
913
913
  });
914
914
  });
@@ -941,8 +941,8 @@ class W {
941
941
  ).join("")}
942
942
  `, e.querySelectorAll(".wontum-quality-option").forEach((n) => {
943
943
  n.addEventListener("click", (o) => {
944
- const r = o.target, a = parseInt(r.dataset.quality || "-1");
945
- this.player.setQuality(a), e.querySelectorAll(".wontum-quality-option").forEach((l) => l.classList.remove("active")), r.classList.add("active");
944
+ const s = o.target, a = parseInt(s.dataset.quality || "-1");
945
+ this.player.setQuality(a), e.querySelectorAll(".wontum-quality-option").forEach((l) => l.classList.remove("active")), s.classList.add("active");
946
946
  });
947
947
  });
948
948
  }
@@ -1009,9 +1009,9 @@ class W {
1009
1009
  }
1010
1010
  class _ {
1011
1011
  constructor(t) {
1012
- s(this, "config");
1013
- s(this, "urlCache", /* @__PURE__ */ new Map());
1014
- s(this, "signedUrls", /* @__PURE__ */ new Set());
1012
+ r(this, "config");
1013
+ r(this, "urlCache", /* @__PURE__ */ new Map());
1014
+ r(this, "signedUrls", /* @__PURE__ */ new Set());
1015
1015
  this.config = t;
1016
1016
  }
1017
1017
  /**
@@ -1045,7 +1045,7 @@ class _ {
1045
1045
  * The endpoint should set signed cookies and return the URL
1046
1046
  */
1047
1047
  async signCloudFrontUrl(t, e = 0) {
1048
- var o, r;
1048
+ var o, s;
1049
1049
  if (this.signedUrls.has(t))
1050
1050
  return t;
1051
1051
  if ((o = this.config) != null && o.signUrl)
@@ -1053,7 +1053,7 @@ class _ {
1053
1053
  const a = await this.config.signUrl(t);
1054
1054
  return this.signedUrls.add(t), a;
1055
1055
  } catch (a) {
1056
- const l = (a == null ? void 0 : a.name) === "AbortError" || ((r = a == null ? void 0 : a.message) == null ? void 0 : r.includes("aborted"));
1056
+ const l = (a == null ? void 0 : a.name) === "AbortError" || ((s = a == null ? void 0 : a.message) == null ? void 0 : s.includes("aborted"));
1057
1057
  if (l && e < 2)
1058
1058
  return console.warn(`Sign URL aborted, retrying (${e + 1}/2)...`), await new Promise((u) => setTimeout(u, 300)), this.signCloudFrontUrl(t, e + 1);
1059
1059
  throw console.error("Failed to sign CloudFront URL:", a), l ? new Error(
@@ -1118,16 +1118,16 @@ class _ {
1118
1118
  }
1119
1119
  class $ {
1120
1120
  constructor(t) {
1121
- s(this, "container");
1122
- s(this, "videoElement");
1123
- s(this, "hls", null);
1124
- s(this, "config");
1125
- s(this, "eventListeners", /* @__PURE__ */ new Map());
1126
- s(this, "analytics");
1127
- s(this, "s3Handler");
1128
- s(this, "uiController");
1129
- s(this, "qualities", []);
1130
- s(this, "state", {
1121
+ r(this, "container");
1122
+ r(this, "videoElement");
1123
+ r(this, "hls", null);
1124
+ r(this, "config");
1125
+ r(this, "eventListeners", /* @__PURE__ */ new Map());
1126
+ r(this, "analytics");
1127
+ r(this, "s3Handler");
1128
+ r(this, "uiController");
1129
+ r(this, "qualities", []);
1130
+ r(this, "state", {
1131
1131
  playing: !1,
1132
1132
  paused: !0,
1133
1133
  ended: !1,
@@ -1215,19 +1215,19 @@ class $ {
1215
1215
  if (p.isSupported()) {
1216
1216
  const n = ((e = this.config.s3Config) == null ? void 0 : e.withCredentials) ?? !1, o = {
1217
1217
  ...this.config.hlsConfig,
1218
- xhrSetup: (r, a) => {
1218
+ xhrSetup: (s, a) => {
1219
1219
  var l;
1220
- n && (r.withCredentials = !0), (l = this.config.hlsConfig) != null && l.xhrSetup && this.config.hlsConfig.xhrSetup(r, a);
1220
+ n && (s.withCredentials = !0), (l = this.config.hlsConfig) != null && l.xhrSetup && this.config.hlsConfig.xhrSetup(s, a);
1221
1221
  }
1222
1222
  };
1223
- this.hls = new p(o), this.hls.loadSource(i), this.hls.attachMedia(this.videoElement), this.hls.on(p.Events.MANIFEST_PARSED, (r, a) => {
1223
+ this.hls = new p(o), this.hls.loadSource(i), this.hls.attachMedia(this.videoElement), this.hls.on(p.Events.MANIFEST_PARSED, (s, a) => {
1224
1224
  const l = this.extractQualities(a.levels);
1225
1225
  this.qualities = l;
1226
- }), this.hls.on(p.Events.LEVEL_SWITCHED, (r, a) => {
1226
+ }), this.hls.on(p.Events.LEVEL_SWITCHED, (s, a) => {
1227
1227
  var u;
1228
1228
  const l = (u = this.hls) == null ? void 0 : u.levels[a.level];
1229
1229
  l && (this.state.quality = `${l.height}p`, this.emit("qualitychange", { quality: this.state.quality }));
1230
- }), this.hls.on(p.Events.ERROR, (r, a) => {
1230
+ }), this.hls.on(p.Events.ERROR, (s, a) => {
1231
1231
  a.fatal && this.handleHlsError(a);
1232
1232
  });
1233
1233
  } else if (this.videoElement.canPlayType("application/vnd.apple.mpegurl"))
@@ -1397,9 +1397,10 @@ class $ {
1397
1397
  }
1398
1398
  class G {
1399
1399
  constructor(t) {
1400
- s(this, "file");
1401
- s(this, "videoElement", null);
1402
- s(this, "info", null);
1400
+ r(this, "file");
1401
+ r(this, "videoElement", null);
1402
+ r(this, "audioContext", null);
1403
+ r(this, "info", null);
1403
1404
  if (!this.isVideoFile(t))
1404
1405
  throw new Error(`Invalid file type: ${t.type}. Expected a video file.`);
1405
1406
  this.file = t;
@@ -1421,13 +1422,13 @@ class G {
1421
1422
  try {
1422
1423
  this.videoElement = document.createElement("video"), this.videoElement.preload = "metadata", this.videoElement.muted = !0;
1423
1424
  const i = URL.createObjectURL(this.file);
1424
- this.videoElement.onloadedmetadata = () => {
1425
+ this.videoElement.onloadedmetadata = async () => {
1425
1426
  try {
1426
1427
  if (!this.videoElement) {
1427
1428
  e(new Error("Video element not initialized"));
1428
1429
  return;
1429
1430
  }
1430
- const n = this.videoElement.videoWidth, o = this.videoElement.videoHeight, r = this.videoElement.duration, a = this.calculateAspectRatio(n, o), l = this.file.size, u = this.formatBytes(l), m = this.formatDuration(r), g = this.getFileExtension(this.file.name), v = r > 0 ? Math.round(l * 8 / r / 1e3) : void 0;
1431
+ const n = this.videoElement.videoWidth, o = this.videoElement.videoHeight, s = this.videoElement.duration, a = this.calculateAspectRatio(n, o), l = this.file.size, u = this.formatBytes(l), m = this.formatDuration(s), f = this.getFileExtension(this.file.name), y = s > 0 ? Math.round(l * 8 / s / 1e3) : void 0, v = await this.detectFrameRate(), g = await this.detectAudioInfo(i);
1431
1432
  this.info = {
1432
1433
  width: n,
1433
1434
  height: o,
@@ -1436,14 +1437,17 @@ class G {
1436
1437
  sizeInBytes: l,
1437
1438
  // raw value alias
1438
1439
  sizeFormatted: u,
1439
- duration: r,
1440
- durationInSeconds: r,
1440
+ duration: s,
1441
+ durationInSeconds: s,
1441
1442
  // raw value alias
1442
1443
  durationFormatted: m,
1443
1444
  mimeType: this.file.type || "video/unknown",
1444
1445
  fileName: this.file.name,
1445
- fileExtension: g,
1446
- bitrate: v
1446
+ fileExtension: f,
1447
+ bitrate: y,
1448
+ frameRate: v,
1449
+ audioChannels: g.channels,
1450
+ hasAudio: g.hasAudio
1447
1451
  }, URL.revokeObjectURL(i), this.videoElement.remove(), t(this.info);
1448
1452
  } catch (n) {
1449
1453
  URL.revokeObjectURL(i), e(n);
@@ -1460,8 +1464,59 @@ class G {
1460
1464
  * Calculate aspect ratio (e.g., "16:9", "4:3")
1461
1465
  */
1462
1466
  calculateAspectRatio(t, e) {
1463
- const i = this.getGCD(t, e), n = t / i, o = e / i, r = n / o;
1464
- return Math.abs(r - 16 / 9) < 0.01 ? "16:9" : Math.abs(r - 4 / 3) < 0.01 ? "4:3" : Math.abs(r - 21 / 9) < 0.01 ? "21:9" : Math.abs(r - 1) < 0.01 ? "1:1" : `${n}:${o}`;
1467
+ const i = this.getGCD(t, e), n = t / i, o = e / i, s = n / o;
1468
+ return Math.abs(s - 16 / 9) < 0.01 ? "16:9" : Math.abs(s - 4 / 3) < 0.01 ? "4:3" : Math.abs(s - 21 / 9) < 0.01 ? "21:9" : Math.abs(s - 1) < 0.01 ? "1:1" : `${n}:${o}`;
1469
+ }
1470
+ /**
1471
+ * Detect frame rate by analyzing video playback
1472
+ */
1473
+ async detectFrameRate() {
1474
+ if (this.videoElement)
1475
+ try {
1476
+ return "requestVideoFrameCallback" in this.videoElement ? new Promise((t) => {
1477
+ let e = 0, i = 0;
1478
+ const n = 10, o = (s, a) => {
1479
+ if (!this.videoElement) {
1480
+ t(void 0);
1481
+ return;
1482
+ }
1483
+ if (e++, e === 1)
1484
+ i = s, this.videoElement.requestVideoFrameCallback(o);
1485
+ else if (e < n)
1486
+ this.videoElement.requestVideoFrameCallback(o);
1487
+ else {
1488
+ const l = (s - i) / 1e3, u = Math.round((e - 1) / l);
1489
+ t(u);
1490
+ }
1491
+ };
1492
+ this.videoElement ? (this.videoElement.currentTime = 1, this.videoElement.play().catch(() => t(void 0)), this.videoElement.requestVideoFrameCallback(o)) : t(void 0);
1493
+ }) : void 0;
1494
+ } catch {
1495
+ return;
1496
+ }
1497
+ }
1498
+ /**
1499
+ * Detect audio channel information using Web Audio API
1500
+ */
1501
+ async detectAudioInfo(t) {
1502
+ var e;
1503
+ if (!this.videoElement) return { hasAudio: !1 };
1504
+ try {
1505
+ if (!(this.videoElement.mozHasAudio || (this.videoElement.webkitAudioDecodedByteCount ?? 0) > 0 || (((e = this.videoElement.audioTracks) == null ? void 0 : e.length) ?? 0) > 0))
1506
+ return { hasAudio: !1 };
1507
+ try {
1508
+ const n = window.AudioContext || window.webkitAudioContext;
1509
+ if (!n)
1510
+ return { hasAudio: !0 };
1511
+ this.audioContext = new n();
1512
+ const o = this.audioContext.createMediaElementSource(this.videoElement), s = this.audioContext.createAnalyser();
1513
+ return o.connect(s), s.connect(this.audioContext.destination), { hasAudio: !0, channels: o.channelCount };
1514
+ } catch {
1515
+ return { hasAudio: !0 };
1516
+ }
1517
+ } catch {
1518
+ return { hasAudio: !1 };
1519
+ }
1465
1520
  }
1466
1521
  /**
1467
1522
  * Get Greatest Common Divisor
@@ -1544,6 +1599,18 @@ class G {
1544
1599
  var t;
1545
1600
  return (t = this.info) == null ? void 0 : t.bitrate;
1546
1601
  }
1602
+ get frameRate() {
1603
+ var t;
1604
+ return (t = this.info) == null ? void 0 : t.frameRate;
1605
+ }
1606
+ get audioChannels() {
1607
+ var t;
1608
+ return (t = this.info) == null ? void 0 : t.audioChannels;
1609
+ }
1610
+ get hasAudio() {
1611
+ var t;
1612
+ return ((t = this.info) == null ? void 0 : t.hasAudio) || !1;
1613
+ }
1547
1614
  get quality() {
1548
1615
  if (!this.info) return "unknown";
1549
1616
  const t = this.info.height;
@@ -1559,7 +1626,8 @@ class G {
1559
1626
  * Clean up resources
1560
1627
  */
1561
1628
  destroy() {
1562
- this.videoElement && (this.videoElement.remove(), this.videoElement = null), this.info = null;
1629
+ this.videoElement && (this.videoElement.pause(), this.videoElement.remove(), this.videoElement = null), this.audioContext && (this.audioContext.close().catch(() => {
1630
+ }), this.audioContext = null), this.info = null;
1563
1631
  }
1564
1632
  }
1565
1633
  const K = (c) => {
@@ -1569,96 +1637,96 @@ const K = (c) => {
1569
1637
  muted: i,
1570
1638
  controls: n = !0,
1571
1639
  poster: o,
1572
- preload: r,
1640
+ preload: s,
1573
1641
  theme: a,
1574
1642
  s3Config: l,
1575
1643
  analytics: u,
1576
1644
  hlsConfig: m,
1577
- subtitles: g,
1578
- stickyControls: v,
1579
- onReady: S,
1580
- onPlay: x,
1645
+ subtitles: f,
1646
+ stickyControls: y,
1647
+ onReady: v,
1648
+ onPlay: g,
1581
1649
  onPause: C,
1582
1650
  onEnded: L,
1583
1651
  onTimeUpdate: T,
1584
1652
  onVolumeChange: I,
1585
- onError: M,
1653
+ onError: A,
1586
1654
  onLoadedMetadata: R,
1587
- onQualityChange: P,
1655
+ onQualityChange: M,
1588
1656
  style: D,
1589
1657
  className: O,
1590
- width: f = "100%",
1591
- height: y = "500px"
1592
- } = c, w = b(null), q = b(null);
1593
- return E(() => {
1594
- if (!w.current) return;
1658
+ width: w = "100%",
1659
+ height: b = "500px"
1660
+ } = c, k = E(null), q = E(null);
1661
+ return x(() => {
1662
+ if (!k.current) return;
1595
1663
  const U = {
1596
1664
  src: t,
1597
- container: w.current,
1665
+ container: k.current,
1598
1666
  autoplay: e,
1599
1667
  muted: i,
1600
1668
  controls: n,
1601
1669
  poster: o,
1602
- preload: r,
1670
+ preload: s,
1603
1671
  theme: a,
1604
1672
  s3Config: l,
1605
1673
  analytics: u,
1606
1674
  hlsConfig: m,
1607
- subtitles: g,
1608
- stickyControls: v
1675
+ subtitles: f,
1676
+ stickyControls: y
1609
1677
  }, d = new $(U);
1610
- return q.current = d, x && d.on("play", x), C && d.on("pause", C), L && d.on("ended", L), M && d.on("error", (h) => {
1611
- var B;
1612
- return M((B = h.data) == null ? void 0 : B.error);
1613
- }), R && d.on("loadedmetadata", R), P && d.on("qualitychange", (h) => P(h.data.level)), T && d.on("timeupdate", (h) => T(h.data.currentTime)), I && d.on("volumechange", (h) => I(h.data.volume, h.data.muted)), S && S(d), () => {
1678
+ return q.current = d, g && d.on("play", g), C && d.on("pause", C), L && d.on("ended", L), A && d.on("error", (h) => {
1679
+ var F;
1680
+ return A((F = h.data) == null ? void 0 : F.error);
1681
+ }), R && d.on("loadedmetadata", R), M && d.on("qualitychange", (h) => M(h.data.level)), T && d.on("timeupdate", (h) => T(h.data.currentTime)), I && d.on("volumechange", (h) => I(h.data.volume, h.data.muted)), v && v(d), () => {
1614
1682
  d.destroy(), q.current = null;
1615
1683
  };
1616
- }, [t]), /* @__PURE__ */ A(
1684
+ }, [t]), /* @__PURE__ */ P(
1617
1685
  "div",
1618
1686
  {
1619
- ref: w,
1687
+ ref: k,
1620
1688
  className: O,
1621
1689
  style: {
1622
- width: typeof f == "number" ? `${f}px` : f,
1623
- height: typeof y == "number" ? `${y}px` : y,
1690
+ width: typeof w == "number" ? `${w}px` : w,
1691
+ height: typeof b == "number" ? `${b}px` : b,
1624
1692
  ...D
1625
1693
  }
1626
1694
  }
1627
1695
  );
1628
1696
  }, J = (c) => {
1629
- const [t, e] = k(null), [i, n] = k(null), o = b(null);
1630
- return E(() => {
1697
+ const [t, e] = S(null), [i, n] = S(null), o = E(null);
1698
+ return x(() => {
1631
1699
  if (!o.current) return;
1632
- const r = new $({
1700
+ const s = new $({
1633
1701
  ...c,
1634
1702
  container: o.current
1635
1703
  });
1636
- e(r);
1704
+ e(s);
1637
1705
  const a = () => {
1638
- n(r.getState());
1706
+ n(s.getState());
1639
1707
  };
1640
- return r.on("play", a), r.on("pause", a), r.on("timeupdate", a), r.on("volumechange", a), r.on("loadedmetadata", a), () => {
1641
- r.destroy();
1708
+ return s.on("play", a), s.on("pause", a), s.on("timeupdate", a), s.on("volumechange", a), s.on("loadedmetadata", a), () => {
1709
+ s.destroy();
1642
1710
  };
1643
1711
  }, [c.src]), {
1644
1712
  containerRef: o,
1645
1713
  player: t,
1646
1714
  state: i
1647
1715
  };
1648
- }, z = F.createContext({
1716
+ }, z = B.createContext({
1649
1717
  player: null,
1650
1718
  state: null
1651
1719
  }), Z = (c) => {
1652
- const { player: t, children: e } = c, [i, n] = k(t.getState());
1653
- return E(() => {
1720
+ const { player: t, children: e } = c, [i, n] = S(t.getState());
1721
+ return x(() => {
1654
1722
  const o = () => {
1655
1723
  n(t.getState());
1656
1724
  };
1657
1725
  return t.on("play", o), t.on("pause", o), t.on("timeupdate", o), t.on("volumechange", o), t.on("loadedmetadata", o), () => {
1658
1726
  };
1659
- }, [t]), /* @__PURE__ */ A(z.Provider, { value: { player: t, state: i }, children: e });
1727
+ }, [t]), /* @__PURE__ */ P(z.Provider, { value: { player: t, state: i }, children: e });
1660
1728
  }, tt = () => {
1661
- const c = F.useContext(z);
1729
+ const c = B.useContext(z);
1662
1730
  if (!c.player)
1663
1731
  throw new Error("useWontumPlayerContext must be used within WontumPlayerProvider");
1664
1732
  return c;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obipascal/player",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "A modern HLS video player SDK for educational platforms with S3 integration",
5
5
  "main": "dist/wontum-player.cjs.js",
6
6
  "module": "dist/wontum-player.esm.js",